diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7766ef74a..c99af4beb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,17 +17,17 @@ jobs: strategy: matrix: node-version: - - "16.x" + - "20.x" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: "**/node_modules" key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }} @@ -50,7 +50,7 @@ jobs: run: echo ${{ env.BUILD_INFO }} >> src/version.json shell: bash - name: Build client bundles - run: node ./config/esbuild-cli.js --config=min --no-typecheck --define STATE_SERVERS=$(cat config/state_servers.json | tr -d " \t\n\r") --define NEUROGLANCER_BUILD_INFO='${{ env.BUILD_INFO }}' --define CUSTOM_BINDINGS=$(cat config/custom-keybinds.json | tr -d " \t\n\r") + run: npm run build -- --no-typecheck --no-lint - run: cp -r ./dist/min appengine/frontend/static/ - name: start deployment uses: bobheadxi/deployments@v1 @@ -61,12 +61,12 @@ jobs: env: ${{ env.BRANCH_NAME }} desc: Setting up staging deployment for ${{ env.BRANCH_NAME }} - id: "auth" - uses: "google-github-actions/auth@v1" + uses: "google-github-actions/auth@v2" with: workload_identity_provider: "projects/483670036293/locations/global/workloadIdentityPools/neuroglancer-github/providers/github" service_account: "chris-apps-deploy@seung-lab.iam.gserviceaccount.com" - id: deploy - uses: google-github-actions/deploy-appengine@main + uses: google-github-actions/deploy-appengine@v2 with: version: ${{ env.GITHUB_SHA }} deliverables: appengine/frontend/app.yaml diff --git a/.github/workflows/build_preview.yml b/.github/workflows/build_preview.yml index 35007ee2e..e833ff1bf 100644 --- a/.github/workflows/build_preview.yml +++ b/.github/workflows/build_preview.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/src/credentials_provider/index.ts b/src/credentials_provider/index.ts index f67fbd52e..384911cf5 100644 --- a/src/credentials_provider/index.ts +++ b/src/credentials_provider/index.ts @@ -17,14 +17,15 @@ /** * @file Generic facility for providing authentication/authorization credentials. */ - +import { type OAuth2Credentials } from "#src/credentials_provider/oauth2.js"; import type { CancellationToken } from "#src/util/cancellation.js"; import { MultipleConsumerCancellationTokenSource } from "#src/util/cancellation.js"; import type { Owned } from "#src/util/disposable.js"; import { RefCounted } from "#src/util/disposable.js"; +import { type HttpError } from "#src/util/http_request.js"; import { StringMemoize } from "#src/util/memoize.js"; -import { HttpError } from "#src/util/http_request"; -import { OAuth2Credentials } from "#src/credentials_provider/oauth2"; + + /** * Wraps an arbitrary JSON credentials object with a generation number. diff --git a/src/datasource/graphene/frontend.ts b/src/datasource/graphene/frontend.ts index 34075c637..8a0493ab2 100644 --- a/src/datasource/graphene/frontend.ts +++ b/src/datasource/graphene/frontend.ts @@ -15,29 +15,32 @@ */ import "#src/datasource/graphene/graphene.css"; +import { debounce } from "lodash-es"; import { AnnotationDisplayState, AnnotationLayerState, } from "#src/annotation/annotation_layer_state.js"; -import type { - AnnotationReference, - Annotation, - AnnotationSource, - AnnotationType, - Line, - Point, +import { type MultiscaleAnnotationSource } from "#src/annotation/frontend_source.js"; +import { + type AnnotationReference, + type Annotation, + type AnnotationSource, AnnotationType, + type Line, + type Point, + LocalAnnotationSource, + makeDataBoundsBoundingBoxAnnotationSet, } from "#src/annotation/index.js"; import { LayerChunkProgressInfo } from "#src/chunk_manager/base.js"; -import type { ChunkManager, WithParameters } from "#src/chunk_manager/frontend.js"; +import { type ChunkManager, WithParameters } from "#src/chunk_manager/frontend.js"; import { makeIdentityTransform } from "#src/coordinate_transform.js"; import { WithCredentialsProvider } from "#src/credentials_provider/chunk_source_frontend.js"; import type { CredentialsManager } from "#src/credentials_provider/index.js"; + import type { ChunkedGraphChunkSource as ChunkedGraphChunkSourceInterface, ChunkedGraphChunkSpecification, MultiscaleMeshMetadata, } from "#src/datasource/graphene/base.js"; -import { MultiscaleAnnotationSource } from "#src/annotation/frontend_source"; import { CHUNKED_GRAPH_LAYER_RPC_ID, CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, @@ -78,6 +81,7 @@ import type { import type { LoadedDataSubsource } from "#src/layer/layer_data_source.js"; import { LoadedLayerDataSource } from "#src/layer/layer_data_source.js"; import { SegmentationUserLayer } from "#src/layer/segmentation/index.js"; +import * as json_keys from "#src/layer/segmentation/json_keys.js"; import { MeshSource } from "#src/mesh/frontend.js"; import type { DisplayDimensionRenderInfo } from "#src/navigation_state.js"; import type { @@ -143,19 +147,24 @@ import { MergedAnnotationStates, PlaceLineTool, } from "#src/ui/annotations.js"; -import { getDefaultAnnotationListBindings } from "#src/ui/default_input_event_bindings"; +import { getDefaultAnnotationListBindings } from "#src/ui/default_input_event_bindings.js"; import { LayerTool, makeToolActivationStatusMessageWithHeader, makeToolButton, registerLegacyTool, registerTool, - ToolActivation + type ToolActivation } from "#src/ui/tool.js"; -import type { Uint64Set } from "#src/uint64_set.js"; +import { Uint64Set } from "#src/uint64_set.js"; +import { + type CancellationToken, + CancellationTokenSource, +} from "#src/util/cancellation.js"; import { packColor } from "#src/util/color.js"; -import type { Owned, RefCounted } from "#src/util/disposable.js"; -import { ValueOrError, makeValueOrError, valueOrThrow } from "#src/util/error.js"; +import { type Owned, RefCounted } from "#src/util/disposable.js"; +import { removeChildren } from "#src/util/dom.js"; +import { type ValueOrError, makeValueOrError, valueOrThrow } from "#src/util/error.js"; import { EventActionMap } from "#src/util/event_action_map.js"; import { mat4, vec3, vec4 } from "#src/util/geom.js"; import { @@ -163,7 +172,7 @@ import { isNotFoundError, responseJson, } from "#src/util/http_request.js"; -import { removeChildren } from "#src/util/dom"; + import { parseArray, parseFixedLengthArray, @@ -184,13 +193,13 @@ import { verifyString, verifyStringArray } from "#src/util/json.js"; +import { MouseEventBinder } from "#src/util/mouse_bindings.js"; import { getObjectId } from "#src/util/object_id.js"; import { NullarySignal } from "#src/util/signal.js"; import type { SpecialProtocolCredentials, SpecialProtocolCredentialsProvider, } from "#src/util/special_protocol_request.js"; -import { MouseEventBinder } from "#src/util/mouse_bindings"; import { cancellableFetchSpecialOk, parseSpecialUrl, @@ -200,11 +209,11 @@ import { Uint64 } from "#src/util/uint64.js"; import { makeDeleteButton } from "#src/widget/delete_button.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; import { makeIcon } from "#src/widget/icon.js"; -import { - CancellationToken, - CancellationTokenSource, -} from "#src/util/cancellation"; -import { debounce } from "lodash"; + +import type { LayerControlDefinition } from "#src/widget/layer_control.js"; +import { addLayerControlToOptionsTab } from "#src/widget/layer_control.js"; +import { rangeLayerControl } from "#src/widget/layer_control_range.js"; + function vec4FromVec3(vec: vec3, alpha = 0) { const res = vec4.clone([...vec]); @@ -1626,7 +1635,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { ); const segmentConst = segmentId.clone(); if (added && isBaseSegment) { - this.graph.getRoot(segmentConst).then((rootId) => { + this.graph.getRoot(segmentConst, this.layer.displayState.stopLayer.value).then((rootId) => { if (segmentsState.visibleSegments.has(segmentConst)) { segmentsState.visibleSegments.add(rootId); } @@ -1952,11 +1961,14 @@ class GrapheneGraphServerInterface { private credentialsProvider: SpecialProtocolCredentialsProvider, ) { } - async getRoot(segment: Uint64, timestamp = "") { + async getRoot(segment: Uint64, timestamp = "", stop_layer: number | undefined = undefined) { const timestampEpoch = new Date(timestamp).valueOf() / 1000; - const url = `${this.url}/node/${String(segment)}/root?int64_as_str=1${Number.isNaN(timestampEpoch) ? "" : `×tamp=${timestampEpoch}` + let url = `${this.url}/node/${String(segment)}/root?int64_as_str=1${Number.isNaN(timestampEpoch) ? "" : `×tamp=${timestampEpoch}` }`; + if (stop_layer !== undefined) { + url += "&stop_layer=" + stop_layer; + } const promise = cancellableFetchSpecialOk( this.credentialsProvider, @@ -2125,6 +2137,17 @@ class GrapheneGraphServerInterface { } } +export const LAYER_CONTROLS: LayerControlDefinition[] = [ + { + label: "Stop Layer", // Adjust the label as needed + toolJson: json_keys.STOP_LAYER, + // ... other properties (toolJson, isValid, title) based on your requirements + ...rangeLayerControl((layer) => ({ + value: layer.displayState.stopLayer, + options: { min: 0.0, max: 10, step: 1.0 } + })), // Integrate your getter function + }] + class GrapheneGraphSource extends SegmentationGraphSource { private connections = new Set(); public graphServer: GrapheneGraphServerInterface; @@ -2188,8 +2211,14 @@ class GrapheneGraphSource extends SegmentationGraphSource { } } - getRoot(segment: Uint64) { - return this.graphServer.getRoot(segment); + + getRoot(segment: Uint64, stop_layer: number) { + if (stop_layer > 0) { + return this.graphServer.getRoot(segment, "", stop_layer); + } + else { + return this.graphServer.getRoot(segment); + } } async findPath( @@ -2276,7 +2305,15 @@ class GrapheneGraphSource extends SegmentationGraphSource { title: "Find Path", }), ); + for (const control of LAYER_CONTROLS) { + toolbox.appendChild( + addLayerControlToOptionsTab(tab, layer, tab.visibility, control), + ); + } + + parent.appendChild(toolbox); + parent.appendChild( context.registerDisposer( new MulticutAnnotationLayerView(layer, layer.annotationDisplayState), @@ -2310,6 +2347,7 @@ class GrapheneGraphSource extends SegmentationGraphSource { } } + class ChunkedGraphChunkSource extends SliceViewChunkSource implements ChunkedGraphChunkSourceInterface { diff --git a/src/datasource/middleauth/credentials_provider.ts b/src/datasource/middleauth/credentials_provider.ts index 1fe7f1e19..f8f870b1a 100644 --- a/src/datasource/middleauth/credentials_provider.ts +++ b/src/datasource/middleauth/credentials_provider.ts @@ -22,15 +22,16 @@ import { CredentialsProvider, makeCredentialsGetter, } from "#src/credentials_provider/index.js"; +import { type OAuth2Credentials } from "#src/credentials_provider/oauth2.js"; import { StatusMessage } from "#src/status.js"; +import { type HttpError } from "#src/util/http_request.js"; import { verifyObject, verifyObjectProperty, verifyString, verifyStringArray, } from "#src/util/json.js"; -import { HttpError } from "#src/util/http_request"; -import { OAuth2Credentials } from "#src/credentials_provider/oauth2"; + export type MiddleAuthToken = { tokenType: string; diff --git a/src/layer/segmentation/index.ts b/src/layer/segmentation/index.ts index 4427ad264..282981168 100644 --- a/src/layer/segmentation/index.ts +++ b/src/layer/segmentation/index.ts @@ -107,6 +107,7 @@ import { DisplayOptionsTab } from "#src/ui/segmentation_display_options_tab.js"; import { Uint64Map } from "#src/uint64_map.js"; import { Uint64OrderedSet } from "#src/uint64_ordered_set.js"; import { Uint64Set } from "#src/uint64_set.js"; +import { gatherUpdate } from "#src/util/array.js"; import { packColor, parseRGBColorSpecification, @@ -122,6 +123,7 @@ import { verifyFiniteNonNegativeFloat, verifyObjectAsMap, verifyOptionalObjectProperty, + verifyPositiveInt, verifyString, } from "#src/util/json.js"; import { Signal } from "#src/util/signal.js"; @@ -129,7 +131,6 @@ import { Uint64 } from "#src/util/uint64.js"; import { makeWatchableShaderError } from "#src/webgl/dynamic_shader.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; import { registerLayerShaderControlsTool } from "#src/widget/shader_controls.js"; -import { gatherUpdate } from "#src/util/array"; export const SKELETON_RENDERING_SHADER_CONTROL_TOOL_ID = "skeletonShaderControl"; @@ -485,6 +486,7 @@ class SegmentationUserLayerDisplayState implements SegmentationDisplayState { ); objectAlpha = trackableAlphaValue(1.0); ignoreNullVisibleSet = new TrackableBoolean(true, true); + stopLayer = new TrackableValue(0, verifyPositiveInt); skeletonRenderingOptions = new SkeletonRenderingOptions(); shaderError = makeWatchableShaderError(); renderScaleHistogram = new RenderScaleHistogram(); @@ -595,7 +597,11 @@ export class SegmentationUserLayer extends Base { }; displayState = new SegmentationUserLayerDisplayState(this); - + stopLayer = new TrackableValue( + 0, + verifyFiniteNonNegativeFloat, + 0, + ); anchorSegment = new TrackableValue(undefined, (x) => x === undefined ? undefined : Uint64.parseString(x), ); diff --git a/src/layer/segmentation/json_keys.ts b/src/layer/segmentation/json_keys.ts index 96417a61c..6d8442f25 100644 --- a/src/layer/segmentation/json_keys.ts +++ b/src/layer/segmentation/json_keys.ts @@ -9,6 +9,7 @@ export const IGNORE_NULL_VISIBLE_SET_JSON_KEY = "ignoreNullVisibleSet"; export const MESH_JSON_KEY = "mesh"; export const SKELETONS_JSON_KEY = "skeletons"; export const SEGMENTS_JSON_KEY = "segments"; +export const STOP_LAYER = 'stop_layer'; export const EQUIVALENCES_JSON_KEY = "equivalences"; export const COLOR_SEED_JSON_KEY = "colorSeed"; export const SEGMENT_STATED_COLORS_JSON_KEY = "segmentColors"; diff --git a/src/segmentation_display_state/frontend.ts b/src/segmentation_display_state/frontend.ts index bc50bca9c..aa0723a64 100644 --- a/src/segmentation_display_state/frontend.ts +++ b/src/segmentation_display_state/frontend.ts @@ -55,16 +55,16 @@ import { } from "#src/util/color.js"; import { RefCounted } from "#src/util/disposable.js"; import { measureElementClone } from "#src/util/dom.js"; -import type { vec3 } from "#src/util/geom.js"; -import { kOneVec, vec4 } from "#src/util/geom.js"; +import { vec3, kOneVec, vec4 } from "#src/util/geom.js"; import { NullarySignal } from "#src/util/signal.js"; import { Uint64 } from "#src/util/uint64.js"; import { withSharedVisibility } from "#src/visibility_priority/frontend.js"; +import { ColorWidget } from "#src/widget/color.js"; import { makeCopyButton } from "#src/widget/copy_button.js"; import { makeEyeButton } from "#src/widget/eye_button.js"; import { makeFilterButton } from "#src/widget/filter_button.js"; import { makeStarButton } from "#src/widget/star_button.js"; -import { ColorWidget } from "#src/widget/color"; + export class Uint64MapEntry { constructor( diff --git a/src/ui/default_viewer_setup.ts b/src/ui/default_viewer_setup.ts index f141f391f..7606fedb1 100644 --- a/src/ui/default_viewer_setup.ts +++ b/src/ui/default_viewer_setup.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { type UserLayer, type UserLayerConstructor, layerTypes } from "#src/layer/index.js"; import { StatusMessage } from "#src/status.js"; import { bindDefaultCopyHandler, @@ -23,15 +23,16 @@ import { setDefaultInputEventBindings } from "#src/ui/default_input_event_bindin import { makeDefaultViewer } from "#src/ui/default_viewer.js"; import type { MinimalViewerOptions } from "#src/ui/minimal_viewer.js"; import { bindTitle } from "#src/ui/title.js"; +import { type Tool, restoreTool } from "#src/ui/tool.js"; import { UrlHashBinding } from "#src/ui/url_hash_binding.js"; -import { UserLayer, UserLayerConstructor } from "#src/layer"; -import { Tool, restoreTool } from "./tool"; -import { SegmentationUserLayer } from "#src/segmentation_user_layer"; import { verifyObject, verifyObjectProperty, verifyString, -} from "#src/util/json"; +} from "#src/util/json.js"; + + + declare let NEUROGLANCER_DEFAULT_STATE_FRAGMENT: string | undefined; @@ -63,7 +64,7 @@ export function setupDefaultViewer(options?: Partial) { desiredLayerType: UserLayerConstructor, desiredProvider?: string, ) => { - let previousTool: Tool | undefined; + let previousTool: Tool | undefined; let previousLayer: UserLayer | undefined; if (typeof obj === "string") { obj = { type: obj }; diff --git a/src/ui/tool.ts b/src/ui/tool.ts index e77a44e8a..b5fff453c 100644 --- a/src/ui/tool.ts +++ b/src/ui/tool.ts @@ -129,7 +129,7 @@ export abstract class LegacyTool< } abstract trigger(mouseState: MouseSelectionState): void; abstract toJSON(): any; - deactivate(): void {} + deactivate(): void { } abstract description: string; unbind() { const { layer } = this; @@ -318,7 +318,7 @@ export class GlobalToolBinder extends RefCounted { this.changed.dispatch(); } - activate(key: string, tool?: Tool): Borrowed | undefined { + activate(key: string, tool?: Tool): Borrowed | undefined { tool = tool || this.get(key); if (tool === undefined) { this.deactivate_(); diff --git a/src/viewer.ts b/src/viewer.ts index 370494cb5..cb63483ee 100644 --- a/src/viewer.ts +++ b/src/viewer.ts @@ -93,7 +93,7 @@ import { SelectionDetailsPanel } from "#src/ui/selection_details.js"; import { SidePanelManager } from "#src/ui/side_panel.js"; import { StateEditorDialog } from "#src/ui/state_editor.js"; import { StatisticsDisplayState, StatisticsPanel } from "#src/ui/statistics.js"; -import { GlobalToolBinder, LocalToolBinder, Tool } from "#src/ui/tool.js"; +import { type Tool, GlobalToolBinder, LocalToolBinder } from "#src/ui/tool.js"; import { ViewerSettingsPanel, ViewerSettingsPanelState, @@ -1124,7 +1124,7 @@ export class Viewer extends RefCounted implements ViewerState { new LocalToolBinder(this, this.globalToolBinder), ); - activateTool(key: string, tool?: Tool) { + activateTool(key: string, tool?: Tool) { this.globalToolBinder.activate(key, tool); }