From f6e934413ad21d0567e42950c93c6e990a6f68f6 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Fri, 6 Jun 2025 15:50:32 +0200 Subject: [PATCH 01/24] feat: create common code editor dialog This also involves a refactor. But primarily it is designed so that the dialogs which contain code editors can share styles and features across them. The layer can also inform the header of the code editor, like "Annotation shader editor" --- src/layer/annotation/index.ts | 18 +---- src/layer/image/index.ts | 17 +--- src/layer/single_mesh/index.ts | 26 ++----- src/overlay.css | 4 +- src/overlay.ts | 4 +- src/ui/segmentation_display_options_tab.ts | 23 +----- src/ui/shader_code_dialog.css | 51 ++++++++++++ src/ui/shader_code_dialog.ts | 90 ++++++++++++++++++++++ src/ui/state_editor.css | 14 ++-- src/ui/state_editor.ts | 30 +++++--- src/widget/shader_code_widget.css | 4 - src/widget/shader_code_widget.ts | 28 +++---- 12 files changed, 204 insertions(+), 105 deletions(-) create mode 100644 src/ui/shader_code_dialog.css create mode 100644 src/ui/shader_code_dialog.ts diff --git a/src/layer/annotation/index.ts b/src/layer/annotation/index.ts index 945026ed78..e988aad57e 100644 --- a/src/layer/annotation/index.ts +++ b/src/layer/annotation/index.ts @@ -39,7 +39,6 @@ import { } from "#src/layer/index.js"; import type { LoadedDataSubsource } from "#src/layer/layer_data_source.js"; import { SegmentationUserLayer } from "#src/layer/segmentation/index.js"; -import { Overlay } from "#src/overlay.js"; import { getWatchableRenderLayerTransform } from "#src/render_coordinate_transform.js"; import { RenderLayerRole } from "#src/renderlayer.js"; import type { SegmentationDisplayState } from "#src/segmentation_display_state/frontend.js"; @@ -743,16 +742,6 @@ function makeShaderCodeWidget(layer: AnnotationUserLayer) { }); } -class ShaderCodeOverlay extends Overlay { - codeWidget: ShaderCodeWidget; - constructor(public layer: AnnotationUserLayer) { - super(); - this.codeWidget = this.registerDisposer(makeShaderCodeWidget(this.layer)); - this.content.appendChild(this.codeWidget.element); - this.codeWidget.textEditor.refresh(); - } -} - class RenderingOptionsTab extends Tab { codeWidget: ShaderCodeWidget; constructor(public layer: AnnotationUserLayer) { @@ -806,11 +795,12 @@ class RenderingOptionsTab extends Tab { element.appendChild( makeShaderCodeWidgetTopRow( this.layer, - this.codeWidget, - ShaderCodeOverlay, + this.codeWidget.element, + makeShaderCodeWidget, { - title: "Documentation on image layer rendering", + title: "Documentation on annotation layer rendering", href: "https://github.com/google/neuroglancer/blob/master/src/annotation/rendering.md", + type: "Annotation" }, "neuroglancer-annotation-dropdown-shader-top-row", ), diff --git a/src/layer/image/index.ts b/src/layer/image/index.ts index 241b69853e..5f5a01b0c0 100644 --- a/src/layer/image/index.ts +++ b/src/layer/image/index.ts @@ -34,7 +34,6 @@ import { UserLayer, } from "#src/layer/index.js"; import type { LoadedDataSubsource } from "#src/layer/layer_data_source.js"; -import { Overlay } from "#src/overlay.js"; import { getChannelSpace } from "#src/render_coordinate_transform.js"; import { RenderScaleHistogram, @@ -544,11 +543,12 @@ class RenderingOptionsTab extends Tab { element.appendChild( makeShaderCodeWidgetTopRow( this.layer, - this.codeWidget, - ShaderCodeOverlay, + this.codeWidget.element, + makeShaderCodeWidget, { title: "Documentation on image layer rendering", href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", + type: "Image" }, "neuroglancer-image-dropdown-top-row", ), @@ -576,17 +576,6 @@ class RenderingOptionsTab extends Tab { } } -class ShaderCodeOverlay extends Overlay { - codeWidget: ShaderCodeWidget; - constructor(public layer: ImageUserLayer) { - super(); - this.codeWidget = this.registerDisposer(makeShaderCodeWidget(this.layer)); - this.content.classList.add("neuroglancer-image-layer-shader-overlay"); - this.content.appendChild(this.codeWidget.element); - this.codeWidget.textEditor.refresh(); - } -} - registerLayerType(ImageUserLayer); registerVolumeLayerType(VolumeType.IMAGE, ImageUserLayer); // Use ImageUserLayer as a fallback layer type if there is a `volume` subsource. diff --git a/src/layer/single_mesh/index.ts b/src/layer/single_mesh/index.ts index d1e25a03d3..23050c6cf6 100644 --- a/src/layer/single_mesh/index.ts +++ b/src/layer/single_mesh/index.ts @@ -23,7 +23,6 @@ import { UserLayer, } from "#src/layer/index.js"; import type { LoadedDataSubsource } from "#src/layer/layer_data_source.js"; -import { Overlay } from "#src/overlay.js"; import type { VertexAttributeInfo } from "#src/single_mesh/base.js"; import { getShaderAttributeType, @@ -139,7 +138,7 @@ function makeShaderCodeWidget(layer: SingleMeshUserLayer) { }); } -class VertexAttributeWidget extends RefCounted { +export class VertexAttributeWidget extends RefCounted { element = document.createElement("div"); constructor( public attributes: WatchableValueInterface< @@ -215,11 +214,12 @@ class DisplayOptionsTab extends Tab { element.appendChild( makeShaderCodeWidgetTopRow( this.layer, - this.codeWidget, - ShaderCodeOverlay, + this.codeWidget.element, + makeShaderCodeWidget, { - title: "Documentation on image layer rendering", + title: "Documentation on mesh rendering", href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", + type: "Mesh", }, "neuroglancer-single-mesh-dropdown-top-row", ), @@ -239,22 +239,6 @@ class DisplayOptionsTab extends Tab { } } -class ShaderCodeOverlay extends Overlay { - attributeWidget: VertexAttributeWidget; - codeWidget: ShaderCodeWidget; - constructor(public layer: SingleMeshUserLayer) { - super(); - this.attributeWidget = this.registerDisposer( - makeVertexAttributeWidget(layer), - ); - this.codeWidget = this.registerDisposer(makeShaderCodeWidget(layer)); - this.content.classList.add("neuroglancer-single-mesh-layer-shader-overlay"); - this.content.appendChild(this.attributeWidget.element); - this.content.appendChild(this.codeWidget.element); - this.codeWidget.textEditor.refresh(); - } -} - registerLayerType(SingleMeshUserLayer); registerLayerTypeDetector((subsource) => { if (subsource.singleMesh !== undefined) { diff --git a/src/overlay.css b/src/overlay.css index 35d4c06e5e..42cee6c1db 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -14,7 +14,7 @@ * limitations under the License. */ -.overlay { +.neuroglancer-overlay { height: 100%; width: 100%; position: fixed; @@ -24,7 +24,7 @@ background-color: rgba(0, 0, 0, 0.8); } -.overlay-content { +.neuroglancer-overlay-content { position: absolute; top: 50%; left: 50%; diff --git a/src/overlay.ts b/src/overlay.ts index 8ead82ac04..13b84148b7 100644 --- a/src/overlay.ts +++ b/src/overlay.ts @@ -39,10 +39,10 @@ export class Overlay extends RefCounted { this.keyMap.addParent(defaultEventMap, Number.NEGATIVE_INFINITY); ++overlaysOpen; const container = (this.container = document.createElement("div")); - container.className = "overlay"; + container.className = "neuroglancer-overlay"; const content = (this.content = document.createElement("div")); this.registerDisposer(new AutomaticallyFocusedElement(content)); - content.className = "overlay-content"; + content.className = "neuroglancer-overlay-content"; container.appendChild(content); document.body.appendChild(container); this.registerDisposer(new KeyboardEventBinder(this.container, this.keyMap)); diff --git a/src/ui/segmentation_display_options_tab.ts b/src/ui/segmentation_display_options_tab.ts index 7bb6e9fc80..cb62cfd21b 100644 --- a/src/ui/segmentation_display_options_tab.ts +++ b/src/ui/segmentation_display_options_tab.ts @@ -17,7 +17,6 @@ import type { SegmentationUserLayer } from "#src/layer/segmentation/index.js"; import { SKELETON_RENDERING_SHADER_CONTROL_TOOL_ID } from "#src/layer/segmentation/json_keys.js"; import { LAYER_CONTROLS } from "#src/layer/segmentation/layer_controls.js"; -import { Overlay } from "#src/overlay.js"; import { DependentViewWidget } from "#src/widget/dependent_view_widget.js"; import { addLayerControlToOptionsTab } from "#src/widget/layer_control.js"; import { LinkedLayerGroupWidget } from "#src/widget/linked_layer.js"; @@ -80,11 +79,12 @@ export class DisplayOptionsTab extends Tab { parent.appendChild( makeShaderCodeWidgetTopRow( this.layer, - codeWidget, - ShaderCodeOverlay, + codeWidget.element, + makeSkeletonShaderCodeWidget, { - title: "Documentation on image layer rendering", + title: "Documentation on skeleton rendering", href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", + type: "Skeleton" }, "neuroglancer-segmentation-dropdown-skeleton-shader-header", ), @@ -111,18 +111,3 @@ export class DisplayOptionsTab extends Tab { element.appendChild(skeletonControls.element); } } - -class ShaderCodeOverlay extends Overlay { - codeWidget: ShaderCodeWidget; - constructor(public layer: SegmentationUserLayer) { - super(); - this.codeWidget = this.registerDisposer( - makeSkeletonShaderCodeWidget(layer), - ); - this.content.classList.add( - "neuroglancer-segmentation-layer-skeleton-shader-overlay", - ); - this.content.appendChild(this.codeWidget.element); - this.codeWidget.textEditor.refresh(); - } -} diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css new file mode 100644 index 0000000000..3cf0d5119d --- /dev/null +++ b/src/ui/shader_code_dialog.css @@ -0,0 +1,51 @@ +.neuroglancer-code-editor-dialog { + padding: 0 !important; + outline: 0 !important; +} + +.neuroglancer-code-editor-dialog .neuroglancer-shader-code-widget { + width: 80vw; + height: 80vh; +} + +.neuroglancer-code-editor-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.5rem; + border-bottom: 1px solid #ccc; + height: 2rem; +} + +.neuroglancer-code-editor-dialog-body { + padding: 0 1.5rem; + overflow: auto; +} + +.neuroglancer-code-editor-dialog-title { + margin-right: auto; + font-size: large; + font-weight: bold; + color: #333; +} + +.neuroglancer-code-editor-dialog-footer { + display: flex; + justify-content: space-between; + padding: 0.5rem 1.5rem; + border-top: 1px solid #ccc; +} + +.neuroglancer-code-editor-dialog-close-button { + background-color: transparent; + border: none; + padding: 0; +} + +.neuroglancer-code-editor-dialog-close-button:hover { + background: none; +} + +.neuroglancer-code-editor-dialog-close-button svg { + stroke: rgba(20, 20, 21, 0.4); +} diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts new file mode 100644 index 0000000000..5aa980fa36 --- /dev/null +++ b/src/ui/shader_code_dialog.ts @@ -0,0 +1,90 @@ +/** + * @license + * Copyright 2016 Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import "#src/ui/shader_code_dialog.css"; +import type { VertexAttributeWidget } from "#src/layer/single_mesh/index.js"; +import { Overlay } from "#src/overlay.js"; +import svg_close from "ikonate/icons/close.svg?raw"; +import { makeIcon } from "#src/widget/icon.js"; +import type { ShaderCodeWidget } from "#src/widget/shader_code_widget.js"; +import { UserLayer } from "#src/layer/index.js"; + +interface ShaderCodeOverlayOptions { + additionalClass?: string; + title?: string; +} + +export class CodeEditorDialog extends Overlay { + header: HTMLDivElement; + body: HTMLDivElement; + footer?: HTMLDivElement; + constructor(title: string = "Code editor", hasFooter: boolean = false) { + super(); + this.content.classList.add("neuroglancer-code-editor-dialog"); + + const header = (this.header = document.createElement("div")); + const closeMenuIcon = makeIcon({ svg: svg_close }); + closeMenuIcon.addEventListener("click", () => this.close()); + closeMenuIcon.classList.add("neuroglancer-code-editor-dialog-close-button"); + const titleText = document.createElement("p"); + titleText.textContent = title; + titleText.classList.add("neuroglancer-code-editor-dialog-title"); + header.classList.add("neuroglancer-code-editor-dialog-header"); + header.appendChild(titleText); + header.appendChild(closeMenuIcon); + this.content.appendChild(header); + + const body = (this.body = document.createElement("div")); + body.classList.add("neuroglancer-code-editor-dialog-body"); + this.content.appendChild(body); + + if (hasFooter) { + const footer = (this.footer = document.createElement("div")); + footer.classList.add("neuroglancer-code-editor-dialog-footer"); + this.content.appendChild(this.footer); + } + } +} + +export class ShaderCodeEditorDialog extends CodeEditorDialog { + footerActionsBtnContainer: HTMLDivElement; + footerBtnsWrapper: HTMLDivElement; + constructor( + public layer: UserLayer, + private makeShaderCodeWidget: (layer: UserLayer) => ShaderCodeWidget, + options: ShaderCodeOverlayOptions = {}, + makeVertexAttributeWidget?: (layer: UserLayer) => VertexAttributeWidget, + ) { + const { additionalClass, title = "Shader editor" } = options; + super(title); + + if (additionalClass) { + this.content.classList.add(additionalClass); + } + + const codeWidget = this.registerDisposer( + this.makeShaderCodeWidget(this.layer), + ); + if (makeVertexAttributeWidget) { + const attributeWidget = this.registerDisposer( + makeVertexAttributeWidget(this.layer), + ); + this.body.appendChild(attributeWidget.element); + } + this.body.appendChild(codeWidget.element); + codeWidget.textEditor.refresh(); + } +} diff --git a/src/ui/state_editor.css b/src/ui/state_editor.css index c4eda07694..88cd2bb9e7 100644 --- a/src/ui/state_editor.css +++ b/src/ui/state_editor.css @@ -14,11 +14,13 @@ * limitations under the License. */ -.neuroglancer-state-editor { - width: 80%; +.neuroglancer-state-editor .neuroglancer-state-editor-text-editor { + width: 80vw; + height: 80vh; } -.close-button { - position: absolute; - right: 15px; -} +.neuroglancer-state-editor-save-container { + display: flex; + justify-content: flex-end; + align-items: center; +} \ No newline at end of file diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index d22a6aca4f..436f98a209 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -27,7 +27,7 @@ import "codemirror/addon/fold/foldgutter.css"; import "codemirror/addon/lint/lint.css"; import CodeMirror from "codemirror"; import { debounce } from "lodash-es"; -import { Overlay } from "#src/overlay.js"; +import { CodeEditorDialog } from "#src/ui/shader_code_dialog.js"; import "#src/ui/state_editor.css"; import { getCachedJson } from "#src/util/trackable.js"; @@ -35,34 +35,42 @@ import type { Viewer } from "#src/viewer.js"; const valueUpdateDelay = 100; -export class StateEditorDialog extends Overlay { +export class StateEditorDialog extends CodeEditorDialog { textEditor: CodeMirror.Editor; applyButton: HTMLButtonElement; downloadButton: HTMLButtonElement; closeButton: HTMLButtonElement; constructor(public viewer: Viewer) { - super(); + super("State editor", true); this.content.classList.add("neuroglancer-state-editor"); + const saveAndCloseWrapper = document.createElement("div"); + saveAndCloseWrapper.classList.add( + "neuroglancer-state-editor-save-container", + ); const buttonApply = (this.applyButton = document.createElement("button")); buttonApply.textContent = "Apply changes"; - this.content.appendChild(buttonApply); + saveAndCloseWrapper.appendChild(buttonApply); buttonApply.addEventListener("click", () => this.applyChanges()); buttonApply.disabled = true; const buttonClose = (this.closeButton = document.createElement("button")); - buttonClose.classList.add("close-button"); - buttonClose.textContent = "Close"; - this.content.appendChild(buttonClose); - buttonClose.addEventListener("click", () => this.dispose()); + buttonClose.classList.add("neuroglancer-state-editor-close-button"); + buttonClose.textContent = "Save & close"; + saveAndCloseWrapper.appendChild(buttonClose); + buttonClose.addEventListener("click", () => { + this.applyChanges(); + this.dispose(); + }); const downloadButton = (this.downloadButton = document.createElement("button")); downloadButton.textContent = "Download"; downloadButton.title = "Download state as a JSON file"; - this.content.appendChild(downloadButton); downloadButton.addEventListener("click", () => this.downloadState()); + this.footer?.appendChild(downloadButton); + this.footer?.appendChild(saveAndCloseWrapper); this.textEditor = CodeMirror((_element) => {}, { value: "", @@ -76,7 +84,9 @@ export class StateEditorDialog extends Overlay { this.debouncedValueUpdater(); }); - this.content.appendChild(this.textEditor.getWrapperElement()); + const textEditorWrapper = this.textEditor.getWrapperElement(); + textEditorWrapper.classList.add("neuroglancer-state-editor-text-editor"); + this.body.appendChild(textEditorWrapper); this.textEditor.refresh(); } diff --git a/src/widget/shader_code_widget.css b/src/widget/shader_code_widget.css index baa0cb8bc0..266baf28e5 100644 --- a/src/widget/shader_code_widget.css +++ b/src/widget/shader_code_widget.css @@ -18,10 +18,6 @@ border: 1px solid red; } -.neuroglancer-shader-code-widget.valid-input { - border: 1px solid green; -} - .neuroglancer-shader-code-widget { border: 1px solid transparent; } diff --git a/src/widget/shader_code_widget.ts b/src/widget/shader_code_widget.ts index 003bec948a..e4fbcd5ab1 100644 --- a/src/widget/shader_code_widget.ts +++ b/src/widget/shader_code_widget.ts @@ -22,12 +22,10 @@ import "codemirror/lib/codemirror.css"; import "codemirror/addon/lint/lint.css"; import { debounce } from "lodash-es"; -import type { UserLayer } from "#src/layer/index.js"; -import type { Overlay } from "#src/overlay.js"; import glslCodeMirror from "#src/third_party/codemirror-glsl.js"; import { ElementVisibilityFromTrackableBoolean, - type TrackableBoolean, + TrackableBoolean, } from "#src/trackable_boolean.js"; import type { WatchableValue } from "#src/trackable_value.js"; import { RefCounted } from "#src/util/disposable.js"; @@ -44,6 +42,8 @@ import type { import { CheckboxIcon } from "#src/widget/checkbox_icon.js"; import { makeHelpButton } from "#src/widget/help_button.js"; import { makeMaximizeButton } from "#src/widget/maximize_button.js"; +import { ShaderCodeEditorDialog } from "#src/ui/shader_code_dialog.js"; +import { UserLayer } from "#src/layer/index.js"; // Install glsl support in CodeMirror. glslCodeMirror(CodeMirror); @@ -54,6 +54,10 @@ glslCodeMirror(CodeMirror); */ const SHADER_UPDATE_DELAY = 500; +type UserLayerWithCodeEditor = UserLayer & { + codeVisible: TrackableBoolean; +}; + interface ShaderCodeState { shaderError: WatchableShaderError; shaderControlState?: ShaderControlState; @@ -201,18 +205,14 @@ export class ShaderCodeWidget extends RefCounted { } } -type UserLayerWithCodeEditor = UserLayer & { codeVisible: TrackableBoolean }; -type ShaderCodeOverlayConstructor = new ( - layer: UserLayerWithCodeEditor, -) => T; - -export function makeShaderCodeWidgetTopRow( +export function makeShaderCodeWidgetTopRow( layer: UserLayerWithCodeEditor, - codeWidget: ShaderCodeWidget, - ShaderCodeOverlay: ShaderCodeOverlayConstructor, + codeWidgetElement: HTMLElement, + makeShaderCodeWidget: (layer: UserLayerWithCodeEditor) => ShaderCodeWidget, help: { title: string; href: string; + type: string; }, className: string, ) { @@ -227,7 +227,7 @@ export function makeShaderCodeWidgetTopRow( layer.registerDisposer( new ElementVisibilityFromTrackableBoolean( layer.codeVisible, - codeWidget.element, + codeWidgetElement, ), ); @@ -243,7 +243,9 @@ export function makeShaderCodeWidgetTopRow( makeMaximizeButton({ title: "Show larger editor view", onClick: () => { - new ShaderCodeOverlay(layer); + new ShaderCodeEditorDialog(layer, makeShaderCodeWidget, { + title: `${help.type} shader editor`, + }); }, }), ); From 949da74305248164e2bf17a9983ac4c3df3c5a16 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Fri, 6 Jun 2025 16:22:01 +0200 Subject: [PATCH 02/24] feat: include a close button in the footer --- src/ui/shader_code_dialog.css | 11 ++++++++--- src/ui/shader_code_dialog.ts | 23 +++++++++++++++-------- src/ui/state_editor.ts | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 3cf0d5119d..d325c5fa1c 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -36,16 +36,21 @@ border-top: 1px solid #ccc; } -.neuroglancer-code-editor-dialog-close-button { +.neuroglancer-code-editor-dialog-close-icon { background-color: transparent; border: none; padding: 0; } -.neuroglancer-code-editor-dialog-close-button:hover { +.neuroglancer-code-editor-dialog-close-icon:hover { background: none; } -.neuroglancer-code-editor-dialog-close-button svg { +.neuroglancer-code-editor-dialog-close-icon svg { stroke: rgba(20, 20, 21, 0.4); } + +.neuroglancer-shader-code-editor-dialog-close-button { + margin-left: auto; + cursor: pointer; +} diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts index 5aa980fa36..613ad95fd2 100644 --- a/src/ui/shader_code_dialog.ts +++ b/src/ui/shader_code_dialog.ts @@ -30,15 +30,15 @@ interface ShaderCodeOverlayOptions { export class CodeEditorDialog extends Overlay { header: HTMLDivElement; body: HTMLDivElement; - footer?: HTMLDivElement; - constructor(title: string = "Code editor", hasFooter: boolean = false) { + footer: HTMLDivElement; + constructor(title: string = "Code editor") { super(); this.content.classList.add("neuroglancer-code-editor-dialog"); const header = (this.header = document.createElement("div")); const closeMenuIcon = makeIcon({ svg: svg_close }); closeMenuIcon.addEventListener("click", () => this.close()); - closeMenuIcon.classList.add("neuroglancer-code-editor-dialog-close-button"); + closeMenuIcon.classList.add("neuroglancer-code-editor-dialog-close-icon"); const titleText = document.createElement("p"); titleText.textContent = title; titleText.classList.add("neuroglancer-code-editor-dialog-title"); @@ -51,11 +51,9 @@ export class CodeEditorDialog extends Overlay { body.classList.add("neuroglancer-code-editor-dialog-body"); this.content.appendChild(body); - if (hasFooter) { - const footer = (this.footer = document.createElement("div")); - footer.classList.add("neuroglancer-code-editor-dialog-footer"); - this.content.appendChild(this.footer); - } + const footer = (this.footer = document.createElement("div")); + footer.classList.add("neuroglancer-code-editor-dialog-footer"); + this.content.appendChild(this.footer); } } @@ -85,6 +83,15 @@ export class ShaderCodeEditorDialog extends CodeEditorDialog { this.body.appendChild(attributeWidget.element); } this.body.appendChild(codeWidget.element); + + const closeButton = document.createElement("button"); + closeButton.classList.add( + "neuroglancer-shader-code-editor-dialog-close-button", + ); + closeButton.textContent = "Close"; + closeButton.addEventListener("click", () => this.close()); + this.footer.appendChild(closeButton); + codeWidget.textEditor.refresh(); } } diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index 436f98a209..98948af93d 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -41,7 +41,7 @@ export class StateEditorDialog extends CodeEditorDialog { downloadButton: HTMLButtonElement; closeButton: HTMLButtonElement; constructor(public viewer: Viewer) { - super("State editor", true); + super("State editor"); this.content.classList.add("neuroglancer-state-editor"); From ffafb945c97858b678cf1dd4daaaa23b5b3bde17 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Fri, 6 Jun 2025 16:23:53 +0200 Subject: [PATCH 03/24] fix: license info on new files --- src/ui/shader_code_dialog.css | 16 ++++++++++++++++ src/ui/shader_code_dialog.ts | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index d325c5fa1c..53f470daa9 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -1,3 +1,19 @@ +/** + * @license + * Copyright 2025 Google Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .neuroglancer-code-editor-dialog { padding: 0 !important; outline: 0 !important; diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts index 613ad95fd2..12807963e2 100644 --- a/src/ui/shader_code_dialog.ts +++ b/src/ui/shader_code_dialog.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2016 Google Inc. + * Copyright 2025 Google Inc. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at From e96963619d941e240c07c8288e738d90a1dc0564 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 10 Jun 2025 17:15:39 +0200 Subject: [PATCH 04/24] refactor: remove uneeded control over top row class with the changes, the top row is now shared logic across layers, so a specific class is not needed as the style can be controlled from one place --- src/layer/annotation/index.ts | 3 +-- src/layer/annotation/style.css | 6 ----- src/layer/image/index.ts | 3 +-- src/layer/image/style.css | 6 ----- src/layer/segmentation/style.css | 6 ----- src/layer/single_mesh/index.ts | 1 - src/layer/single_mesh/style.css | 6 ----- src/ui/segmentation_display_options_tab.ts | 3 +-- src/ui/shader_code_dialog.ts | 16 +++---------- src/ui/state_editor.css | 2 +- src/widget/shader_code_widget.css | 6 +++++ src/widget/shader_code_widget.ts | 26 +++++++++++++--------- 12 files changed, 28 insertions(+), 56 deletions(-) diff --git a/src/layer/annotation/index.ts b/src/layer/annotation/index.ts index e988aad57e..de1e56bba8 100644 --- a/src/layer/annotation/index.ts +++ b/src/layer/annotation/index.ts @@ -800,9 +800,8 @@ class RenderingOptionsTab extends Tab { { title: "Documentation on annotation layer rendering", href: "https://github.com/google/neuroglancer/blob/master/src/annotation/rendering.md", - type: "Annotation" + type: "Annotation", }, - "neuroglancer-annotation-dropdown-shader-top-row", ), ); diff --git a/src/layer/annotation/style.css b/src/layer/annotation/style.css index 137b1a2dd9..1c9238ff8f 100644 --- a/src/layer/annotation/style.css +++ b/src/layer/annotation/style.css @@ -24,12 +24,6 @@ flex-shrink: 0; } -.neuroglancer-annotation-dropdown-shader-top-row { - display: flex; - flex-direction: row; - align-items: center; -} - .neuroglancer-annotation-shader-property-list { max-height: 8em; overflow: auto; diff --git a/src/layer/image/index.ts b/src/layer/image/index.ts index 5f5a01b0c0..95a45b8114 100644 --- a/src/layer/image/index.ts +++ b/src/layer/image/index.ts @@ -548,9 +548,8 @@ class RenderingOptionsTab extends Tab { { title: "Documentation on image layer rendering", href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", - type: "Image" + type: "Image", }, - "neuroglancer-image-dropdown-top-row", ), ); element.appendChild( diff --git a/src/layer/image/style.css b/src/layer/image/style.css index 2a90dc0f48..683628758f 100644 --- a/src/layer/image/style.css +++ b/src/layer/image/style.css @@ -27,12 +27,6 @@ border: 1px solid transparent; } -.neuroglancer-image-dropdown-top-row { - display: flex; - flex-direction: row; - align-items: center; -} - .neuroglancer-image-layer-shader-overlay .neuroglancer-shader-code-widget { width: 80vw; height: 80vh; diff --git a/src/layer/segmentation/style.css b/src/layer/segmentation/style.css index dd92be0194..3b9feb53ab 100644 --- a/src/layer/segmentation/style.css +++ b/src/layer/segmentation/style.css @@ -24,12 +24,6 @@ height: 6em; } -.neuroglancer-segmentation-dropdown-skeleton-shader-header { - display: flex; - flex-direction: row; - align-items: center; -} - .neuroglancer-segmentation-layer-skeleton-shader-overlay .neuroglancer-shader-code-widget { width: 80vw; diff --git a/src/layer/single_mesh/index.ts b/src/layer/single_mesh/index.ts index 23050c6cf6..4c5ace025d 100644 --- a/src/layer/single_mesh/index.ts +++ b/src/layer/single_mesh/index.ts @@ -221,7 +221,6 @@ class DisplayOptionsTab extends Tab { href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", type: "Mesh", }, - "neuroglancer-single-mesh-dropdown-top-row", ), ); element.appendChild(this.attributeWidget.element); diff --git a/src/layer/single_mesh/style.css b/src/layer/single_mesh/style.css index 5986dc4d47..e285a4d829 100644 --- a/src/layer/single_mesh/style.css +++ b/src/layer/single_mesh/style.css @@ -25,12 +25,6 @@ border: 1px solid transparent; } -.neuroglancer-single-mesh-dropdown-top-row { - display: flex; - flex-direction: row; - align-items: center; -} - .neuroglancer-single-mesh-shader-overlay .neuroglancer-shader-code-widget { width: 80vw; height: 80vh; diff --git a/src/ui/segmentation_display_options_tab.ts b/src/ui/segmentation_display_options_tab.ts index cb62cfd21b..7120848687 100644 --- a/src/ui/segmentation_display_options_tab.ts +++ b/src/ui/segmentation_display_options_tab.ts @@ -84,9 +84,8 @@ export class DisplayOptionsTab extends Tab { { title: "Documentation on skeleton rendering", href: "https://github.com/google/neuroglancer/blob/master/src/sliceview/image_layer_rendering.md", - type: "Skeleton" + type: "Skeleton", }, - "neuroglancer-segmentation-dropdown-skeleton-shader-header", ), ); parent.appendChild(codeWidget.element); diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts index 12807963e2..0f8bc7bdde 100644 --- a/src/ui/shader_code_dialog.ts +++ b/src/ui/shader_code_dialog.ts @@ -15,17 +15,12 @@ */ import "#src/ui/shader_code_dialog.css"; +import svg_close from "ikonate/icons/close.svg?raw"; +import type { UserLayer } from "#src/layer/index.js"; import type { VertexAttributeWidget } from "#src/layer/single_mesh/index.js"; import { Overlay } from "#src/overlay.js"; -import svg_close from "ikonate/icons/close.svg?raw"; import { makeIcon } from "#src/widget/icon.js"; import type { ShaderCodeWidget } from "#src/widget/shader_code_widget.js"; -import { UserLayer } from "#src/layer/index.js"; - -interface ShaderCodeOverlayOptions { - additionalClass?: string; - title?: string; -} export class CodeEditorDialog extends Overlay { header: HTMLDivElement; @@ -63,16 +58,11 @@ export class ShaderCodeEditorDialog extends CodeEditorDialog { constructor( public layer: UserLayer, private makeShaderCodeWidget: (layer: UserLayer) => ShaderCodeWidget, - options: ShaderCodeOverlayOptions = {}, + title: string = "Shader editor", makeVertexAttributeWidget?: (layer: UserLayer) => VertexAttributeWidget, ) { - const { additionalClass, title = "Shader editor" } = options; super(title); - if (additionalClass) { - this.content.classList.add(additionalClass); - } - const codeWidget = this.registerDisposer( this.makeShaderCodeWidget(this.layer), ); diff --git a/src/ui/state_editor.css b/src/ui/state_editor.css index 88cd2bb9e7..11856bfffd 100644 --- a/src/ui/state_editor.css +++ b/src/ui/state_editor.css @@ -23,4 +23,4 @@ display: flex; justify-content: flex-end; align-items: center; -} \ No newline at end of file +} diff --git a/src/widget/shader_code_widget.css b/src/widget/shader_code_widget.css index 266baf28e5..66ba3f0c50 100644 --- a/src/widget/shader_code_widget.css +++ b/src/widget/shader_code_widget.css @@ -21,3 +21,9 @@ .neuroglancer-shader-code-widget { border: 1px solid transparent; } + +.neuroglancer-shader-code-widget-top-row { + display: flex; + flex-direction: row; + align-items: center; +} diff --git a/src/widget/shader_code_widget.ts b/src/widget/shader_code_widget.ts index e4fbcd5ab1..12091c6ff8 100644 --- a/src/widget/shader_code_widget.ts +++ b/src/widget/shader_code_widget.ts @@ -22,12 +22,13 @@ import "codemirror/lib/codemirror.css"; import "codemirror/addon/lint/lint.css"; import { debounce } from "lodash-es"; +import type { UserLayer } from "#src/layer/index.js"; +import type { VertexAttributeWidget } from "#src/layer/single_mesh/index.js"; import glslCodeMirror from "#src/third_party/codemirror-glsl.js"; -import { - ElementVisibilityFromTrackableBoolean, - TrackableBoolean, -} from "#src/trackable_boolean.js"; +import type { TrackableBoolean } from "#src/trackable_boolean.js"; +import { ElementVisibilityFromTrackableBoolean } from "#src/trackable_boolean.js"; import type { WatchableValue } from "#src/trackable_value.js"; +import { ShaderCodeEditorDialog } from "#src/ui/shader_code_dialog.js"; import { RefCounted } from "#src/util/disposable.js"; import { removeFromParent } from "#src/util/dom.js"; import type { WatchableShaderError } from "#src/webgl/dynamic_shader.js"; @@ -42,8 +43,6 @@ import type { import { CheckboxIcon } from "#src/widget/checkbox_icon.js"; import { makeHelpButton } from "#src/widget/help_button.js"; import { makeMaximizeButton } from "#src/widget/maximize_button.js"; -import { ShaderCodeEditorDialog } from "#src/ui/shader_code_dialog.js"; -import { UserLayer } from "#src/layer/index.js"; // Install glsl support in CodeMirror. glslCodeMirror(CodeMirror); @@ -214,13 +213,15 @@ export function makeShaderCodeWidgetTopRow( href: string; type: string; }, - className: string, + makeVertexAttributeWidget?: ( + layer: UserLayerWithCodeEditor, + ) => VertexAttributeWidget, ) { const spacer = document.createElement("div"); spacer.style.flex = "1"; const topRow = document.createElement("div"); - topRow.className = className; + topRow.classList.add("neuroglancer-shader-code-widget-top-row"); topRow.appendChild(document.createTextNode("Shader")); topRow.appendChild(spacer); @@ -243,9 +244,12 @@ export function makeShaderCodeWidgetTopRow( makeMaximizeButton({ title: "Show larger editor view", onClick: () => { - new ShaderCodeEditorDialog(layer, makeShaderCodeWidget, { - title: `${help.type} shader editor`, - }); + new ShaderCodeEditorDialog( + layer, + makeShaderCodeWidget, + `${help.type} shader editor`, + makeVertexAttributeWidget, + ); }, }), ); From 85692475f03243f7ef61beb08afd23c8268c5445 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 10 Jun 2025 17:27:38 +0200 Subject: [PATCH 05/24] refactor: remove uneeded css around sizing in overlay mode --- src/layer/image/style.css | 5 ----- src/layer/segmentation/style.css | 6 ------ src/layer/single_mesh/style.css | 12 ------------ src/ui/shader_code_dialog.css | 4 ++++ 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/layer/image/style.css b/src/layer/image/style.css index 683628758f..0e7f684e0f 100644 --- a/src/layer/image/style.css +++ b/src/layer/image/style.css @@ -27,11 +27,6 @@ border: 1px solid transparent; } -.neuroglancer-image-layer-shader-overlay .neuroglancer-shader-code-widget { - width: 80vw; - height: 80vh; -} - .neuroglancer-selection-details-value-grid { display: grid; grid-auto-rows: auto; diff --git a/src/layer/segmentation/style.css b/src/layer/segmentation/style.css index 3b9feb53ab..9044d1dd96 100644 --- a/src/layer/segmentation/style.css +++ b/src/layer/segmentation/style.css @@ -24,12 +24,6 @@ height: 6em; } -.neuroglancer-segmentation-layer-skeleton-shader-overlay - .neuroglancer-shader-code-widget { - width: 80vw; - height: 80vh; -} - .neuroglancer-segment-list-entry { display: flex; flex-direction: row; diff --git a/src/layer/single_mesh/style.css b/src/layer/single_mesh/style.css index e285a4d829..8366345e1d 100644 --- a/src/layer/single_mesh/style.css +++ b/src/layer/single_mesh/style.css @@ -21,13 +21,6 @@ .neuroglancer-single-mesh-dropdown .neuroglancer-shader-code-widget { height: 6em; - width: 60ch; - border: 1px solid transparent; -} - -.neuroglancer-single-mesh-shader-overlay .neuroglancer-shader-code-widget { - width: 80vw; - height: 80vh; } .neuroglancer-single-mesh-attribute-widget { @@ -37,11 +30,6 @@ grid-template-columns: [type] auto [name] auto [range] auto; } -.neuroglancer-single-mesh-layer-shader-overlay - .neuroglancer-single-mesh-attribute-widget { - max-height: 20vh; -} - .neuroglancer-single-mesh-attribute { font-family: monospace; display: contents; diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 53f470daa9..7e6371996b 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -70,3 +70,7 @@ margin-left: auto; cursor: pointer; } + +.neuroglancer-code-editor-dialog .neuroglancer-single-mesh-attribute-widget { + max-height: 12vh; +} \ No newline at end of file From f165eff5a1b1fc7a8d7358493c6ddd0e44657b09 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 10 Jun 2025 17:31:55 +0200 Subject: [PATCH 06/24] refactor: normalise button naming --- src/ui/shader_code_dialog.css | 2 +- src/ui/state_editor.ts | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 7e6371996b..35d89215d1 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -73,4 +73,4 @@ .neuroglancer-code-editor-dialog .neuroglancer-single-mesh-attribute-widget { max-height: 12vh; -} \ No newline at end of file +} diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index 98948af93d..9b7cb06f25 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -49,17 +49,17 @@ export class StateEditorDialog extends CodeEditorDialog { saveAndCloseWrapper.classList.add( "neuroglancer-state-editor-save-container", ); - const buttonApply = (this.applyButton = document.createElement("button")); - buttonApply.textContent = "Apply changes"; - saveAndCloseWrapper.appendChild(buttonApply); - buttonApply.addEventListener("click", () => this.applyChanges()); - buttonApply.disabled = true; - - const buttonClose = (this.closeButton = document.createElement("button")); - buttonClose.classList.add("neuroglancer-state-editor-close-button"); - buttonClose.textContent = "Save & close"; - saveAndCloseWrapper.appendChild(buttonClose); - buttonClose.addEventListener("click", () => { + const applyButton = (this.applyButton = document.createElement("button")); + applyButton.textContent = "Apply changes"; + saveAndCloseWrapper.appendChild(applyButton); + applyButton.addEventListener("click", () => this.applyChanges()); + applyButton.disabled = true; + + const closeButton = (this.closeButton = document.createElement("button")); + closeButton.classList.add("neuroglancer-state-editor-close-button"); + closeButton.textContent = "Save & close"; + saveAndCloseWrapper.appendChild(closeButton); + closeButton.addEventListener("click", () => { this.applyChanges(); this.dispose(); }); @@ -69,8 +69,8 @@ export class StateEditorDialog extends CodeEditorDialog { downloadButton.textContent = "Download"; downloadButton.title = "Download state as a JSON file"; downloadButton.addEventListener("click", () => this.downloadState()); - this.footer?.appendChild(downloadButton); - this.footer?.appendChild(saveAndCloseWrapper); + this.footer.appendChild(downloadButton); + this.footer.appendChild(saveAndCloseWrapper); this.textEditor = CodeMirror((_element) => {}, { value: "", From 7b97d6f58ea2c6d40c6d05061b776674c60a56a5 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 10 Jun 2025 17:37:53 +0200 Subject: [PATCH 07/24] fix: avoid any overlaps between code gutter and header/footer border --- src/ui/shader_code_dialog.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 35d89215d1..b18c7d2d17 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -31,6 +31,7 @@ padding: 0 1.5rem; border-bottom: 1px solid #ccc; height: 2rem; + margin-bottom: 1px; } .neuroglancer-code-editor-dialog-body { @@ -47,9 +48,11 @@ .neuroglancer-code-editor-dialog-footer { display: flex; + align-items: center; justify-content: space-between; padding: 0.5rem 1.5rem; border-top: 1px solid #ccc; + margin-top: 1px; } .neuroglancer-code-editor-dialog-close-icon { From 564c54ddad1c9e633d6d536ec5ae8f3ea08906c4 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 10 Jun 2025 18:26:24 +0200 Subject: [PATCH 08/24] feat: add line numbers to shader code --- src/widget/shader_code_widget.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widget/shader_code_widget.ts b/src/widget/shader_code_widget.ts index 12091c6ff8..bbed91c058 100644 --- a/src/widget/shader_code_widget.ts +++ b/src/widget/shader_code_widget.ts @@ -85,6 +85,7 @@ export class ShaderCodeWidget extends RefCounted { value: this.state.fragmentMain.value, mode: "glsl", gutters: ["CodeMirror-lint-markers"], + lineNumbers: true, }); this.textEditor.on("change", () => { this.setValidState(undefined); From 05685f3ee11e395e03b6f1a4afa6ca02c94ed4fb Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 25 Jun 2025 13:53:25 +0200 Subject: [PATCH 09/24] fix: add class to apply button --- src/ui/state_editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index 9b7cb06f25..bdb02d0ba6 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -50,6 +50,7 @@ export class StateEditorDialog extends CodeEditorDialog { "neuroglancer-state-editor-save-container", ); const applyButton = (this.applyButton = document.createElement("button")); + applyButton.classList.add("neuroglancer-state-editor-apply-button"); applyButton.textContent = "Apply changes"; saveAndCloseWrapper.appendChild(applyButton); applyButton.addEventListener("click", () => this.applyChanges()); @@ -61,7 +62,7 @@ export class StateEditorDialog extends CodeEditorDialog { saveAndCloseWrapper.appendChild(closeButton); closeButton.addEventListener("click", () => { this.applyChanges(); - this.dispose(); + this.close(); }); const downloadButton = (this.downloadButton = From 76104320e68fd1a3395200080080849e1312a0f6 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 25 Jun 2025 15:43:21 +0200 Subject: [PATCH 10/24] fix: don't change styling with !important flag --- src/overlay.css | 1 - src/ui/shader_code_dialog.css | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index 42cee6c1db..a122b31656 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -32,5 +32,4 @@ background-color: white; z-index: 100; color: black; - padding: 1em; } diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index b18c7d2d17..30d24db430 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -14,11 +14,6 @@ * limitations under the License. */ -.neuroglancer-code-editor-dialog { - padding: 0 !important; - outline: 0 !important; -} - .neuroglancer-code-editor-dialog .neuroglancer-shader-code-widget { width: 80vw; height: 80vh; From 37806b9f436e79c7aadf4b8a918b7f1b38d2d623 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 25 Jun 2025 15:49:08 +0200 Subject: [PATCH 11/24] feat: reduce size of code overlay a little in height The previous was a bit too big --- src/ui/shader_code_dialog.css | 2 +- src/ui/state_editor.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 30d24db430..3db8bf97c2 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -16,7 +16,7 @@ .neuroglancer-code-editor-dialog .neuroglancer-shader-code-widget { width: 80vw; - height: 80vh; + height: 65vh; } .neuroglancer-code-editor-dialog-header { diff --git a/src/ui/state_editor.css b/src/ui/state_editor.css index 11856bfffd..3fef492f96 100644 --- a/src/ui/state_editor.css +++ b/src/ui/state_editor.css @@ -16,7 +16,7 @@ .neuroglancer-state-editor .neuroglancer-state-editor-text-editor { width: 80vw; - height: 80vh; + height: 65vh; } .neuroglancer-state-editor-save-container { From a4ed9f1f1c8c93f2bbd1892765e25223dbad22fd Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 25 Jun 2025 16:03:04 +0200 Subject: [PATCH 12/24] feat: add class for download button --- src/ui/state_editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index bdb02d0ba6..11d714a7aa 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -67,6 +67,7 @@ export class StateEditorDialog extends CodeEditorDialog { const downloadButton = (this.downloadButton = document.createElement("button")); + downloadButton.classList.add("neuroglancer-state-editor-download-button"); downloadButton.textContent = "Download"; downloadButton.title = "Download state as a JSON file"; downloadButton.addEventListener("click", () => this.downloadState()); From f3ccfccebb8eb91c91e672092786b6ec9db8612e Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Mon, 7 Jul 2025 20:19:12 +0200 Subject: [PATCH 13/24] feat: add framed dialog base class --- src/overlay.css | 56 +++++++++++++++++++++++++++++++++++++++-- src/overlay.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index a122b31656..af3d4846c8 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -14,11 +14,11 @@ * limitations under the License. */ -.neuroglancer-overlay { + .neuroglancer-overlay { height: 100%; width: 100%; position: fixed; - z-index: 99; + z-index: 100; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.8); @@ -33,3 +33,55 @@ z-index: 100; color: black; } + +.neuroglancer-framed-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 1.5rem; + border-bottom: 1px solid #ccc; + height: 2rem; + margin-bottom: 1px; +} + +.neuroglancer-framed-dialog-body { + display: flex; + padding: 1.5rem; + overflow: auto; +} + +.neuroglancer-framed-dialog-title { + margin-right: auto; + font-size: large; + font-weight: bold; + color: #333; +} + +.neuroglancer-framed-dialog-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem; + border-top: 1px solid #ccc; + margin-top: 1px; +} + +.neuroglancer-framed-dialog-primary-button { + background-color: transparent; + border: none; + padding: 0; +} + +.neuroglancer-framed-dialog-primary-button:hover { + background: none; +} + +.neuroglancer-framed-dialog-close-icon svg { + stroke: #141415; + opacity: 0.6; +} + +.neuroglancer-framed-dialog-close-icon:hover svg { + stroke: white; + opacity: 1; +} \ No newline at end of file diff --git a/src/overlay.ts b/src/overlay.ts index 13b84148b7..ff28d89cfa 100644 --- a/src/overlay.ts +++ b/src/overlay.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import svg_close from "ikonate/icons/close.svg?raw"; import { AutomaticallyFocusedElement } from "#src/util/automatic_focus.js"; import { RefCounted } from "#src/util/disposable.js"; import { @@ -21,6 +22,7 @@ import { KeyboardEventBinder, } from "#src/util/keyboard_bindings.js"; import "#src/overlay.css"; +import { makeIcon } from "#src/widget/icon.js"; export const overlayKeyboardHandlerPriority = 100; @@ -62,3 +64,68 @@ export class Overlay extends RefCounted { super.disposed(); } } + +/** + * A dialog that has a header, body, and footer. + * The header contains a title and a close icon. + * The footer contains a primary button. + * The body is where the main content goes. + * @param title - The title text to display in the header + * @param primaryButtonText - The text to display on the primary button + * @param extraClassPrefix - Optional CSS class prefix to add to all dialog elements + * @param onPrimaryButtonClick - Optional callback function to execute when primary button is clicked. Provide null to give no action. + */ + +export class FramedDialog extends Overlay { + header: HTMLDivElement; + headerTitle: HTMLSpanElement; + closeMenuIcon: HTMLElement; + primaryButton: HTMLButtonElement; + body: HTMLDivElement; + footer: HTMLDivElement; + constructor( + title: string = "Dialog", + primaryButtonText: string = "Close", + extraClassPrefix?: string, + onPrimaryButtonClick?: () => void | null, + ) { + super(); + + const header = (this.header = document.createElement("div")); + const closeMenuIcon = (this.closeMenuIcon = makeIcon({ svg: svg_close })); + this.registerEventListener(closeMenuIcon, "click", () => this.close()); + const headerTitle = (this.headerTitle = document.createElement("span")); + headerTitle.textContent = title; + header.appendChild(headerTitle); + header.appendChild(closeMenuIcon); + this.content.appendChild(header); + + const body = (this.body = document.createElement("div")); + this.content.appendChild(body); + + const footer = (this.footer = document.createElement("div")); + const primaryButton = (this.primaryButton = + document.createElement("button")); + primaryButton.textContent = primaryButtonText; + if (onPrimaryButtonClick === undefined) { + this.registerEventListener(primaryButton, "click", () => this.close()); + } else if (onPrimaryButtonClick !== null) { + this.registerEventListener(primaryButton, "click", onPrimaryButtonClick); + } + footer.appendChild(primaryButton); + this.content.appendChild(this.footer); + + const classPrefixes = ["neuroglancer-framed-dialog", extraClassPrefix]; + for (const classPrefix of classPrefixes) { + if (classPrefix !== undefined) { + this.content.classList.add(`${classPrefix}`); + this.header.classList.add(`${classPrefix}-header`); + this.headerTitle.classList.add(`${classPrefix}-title`); + this.closeMenuIcon.classList.add(`${classPrefix}-close-icon`); + this.body.classList.add(`${classPrefix}-body`); + this.footer.classList.add(`${classPrefix}-footer`); + this.primaryButton.classList.add(`${classPrefix}-primary-button`); + } + } + } +} From 15488f2122d675bbd3afc0462ba91881b19ccd0b Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Mon, 7 Jul 2025 20:36:25 +0200 Subject: [PATCH 14/24] refactor: bring code dialog up a level for more reuse --- src/ui/shader_code_dialog.css | 59 +++-------------------------------- src/ui/shader_code_dialog.ts | 50 +++-------------------------- src/ui/state_editor.ts | 20 +++--------- 3 files changed, 13 insertions(+), 116 deletions(-) diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 3db8bf97c2..9a7a6bb438 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -14,61 +14,12 @@ * limitations under the License. */ -.neuroglancer-code-editor-dialog .neuroglancer-shader-code-widget { + .neuroglancer-shader-code-editor-dialog-body .neuroglancer-shader-code-widget { width: 80vw; - height: 65vh; + height: 100%; } -.neuroglancer-code-editor-dialog-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 1.5rem; - border-bottom: 1px solid #ccc; - height: 2rem; - margin-bottom: 1px; -} - -.neuroglancer-code-editor-dialog-body { - padding: 0 1.5rem; - overflow: auto; -} - -.neuroglancer-code-editor-dialog-title { - margin-right: auto; - font-size: large; - font-weight: bold; - color: #333; -} - -.neuroglancer-code-editor-dialog-footer { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.5rem 1.5rem; - border-top: 1px solid #ccc; - margin-top: 1px; -} - -.neuroglancer-code-editor-dialog-close-icon { - background-color: transparent; - border: none; - padding: 0; -} - -.neuroglancer-code-editor-dialog-close-icon:hover { - background: none; -} - -.neuroglancer-code-editor-dialog-close-icon svg { - stroke: rgba(20, 20, 21, 0.4); -} - -.neuroglancer-shader-code-editor-dialog-close-button { - margin-left: auto; - cursor: pointer; -} - -.neuroglancer-code-editor-dialog .neuroglancer-single-mesh-attribute-widget { +.neuroglancer-shader-code-editor-dialog + .neuroglancer-single-mesh-attribute-widget { max-height: 12vh; -} +} \ No newline at end of file diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts index 0f8bc7bdde..fc450939dc 100644 --- a/src/ui/shader_code_dialog.ts +++ b/src/ui/shader_code_dialog.ts @@ -15,53 +15,19 @@ */ import "#src/ui/shader_code_dialog.css"; -import svg_close from "ikonate/icons/close.svg?raw"; import type { UserLayer } from "#src/layer/index.js"; import type { VertexAttributeWidget } from "#src/layer/single_mesh/index.js"; -import { Overlay } from "#src/overlay.js"; -import { makeIcon } from "#src/widget/icon.js"; +import { FramedDialog } from "#src/overlay.js"; import type { ShaderCodeWidget } from "#src/widget/shader_code_widget.js"; -export class CodeEditorDialog extends Overlay { - header: HTMLDivElement; - body: HTMLDivElement; - footer: HTMLDivElement; - constructor(title: string = "Code editor") { - super(); - this.content.classList.add("neuroglancer-code-editor-dialog"); - - const header = (this.header = document.createElement("div")); - const closeMenuIcon = makeIcon({ svg: svg_close }); - closeMenuIcon.addEventListener("click", () => this.close()); - closeMenuIcon.classList.add("neuroglancer-code-editor-dialog-close-icon"); - const titleText = document.createElement("p"); - titleText.textContent = title; - titleText.classList.add("neuroglancer-code-editor-dialog-title"); - header.classList.add("neuroglancer-code-editor-dialog-header"); - header.appendChild(titleText); - header.appendChild(closeMenuIcon); - this.content.appendChild(header); - - const body = (this.body = document.createElement("div")); - body.classList.add("neuroglancer-code-editor-dialog-body"); - this.content.appendChild(body); - - const footer = (this.footer = document.createElement("div")); - footer.classList.add("neuroglancer-code-editor-dialog-footer"); - this.content.appendChild(this.footer); - } -} - -export class ShaderCodeEditorDialog extends CodeEditorDialog { - footerActionsBtnContainer: HTMLDivElement; - footerBtnsWrapper: HTMLDivElement; +export class ShaderCodeEditorDialog extends FramedDialog { constructor( public layer: UserLayer, private makeShaderCodeWidget: (layer: UserLayer) => ShaderCodeWidget, title: string = "Shader editor", makeVertexAttributeWidget?: (layer: UserLayer) => VertexAttributeWidget, ) { - super(title); + super(title, "Close editor", "neuroglancer-shader-code-editor-dialog"); const codeWidget = this.registerDisposer( this.makeShaderCodeWidget(this.layer), @@ -74,14 +40,6 @@ export class ShaderCodeEditorDialog extends CodeEditorDialog { } this.body.appendChild(codeWidget.element); - const closeButton = document.createElement("button"); - closeButton.classList.add( - "neuroglancer-shader-code-editor-dialog-close-button", - ); - closeButton.textContent = "Close"; - closeButton.addEventListener("click", () => this.close()); - this.footer.appendChild(closeButton); - codeWidget.textEditor.refresh(); } -} +} \ No newline at end of file diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index 11d714a7aa..c33ee7b1ca 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -27,23 +27,19 @@ import "codemirror/addon/fold/foldgutter.css"; import "codemirror/addon/lint/lint.css"; import CodeMirror from "codemirror"; import { debounce } from "lodash-es"; -import { CodeEditorDialog } from "#src/ui/shader_code_dialog.js"; import "#src/ui/state_editor.css"; - +import { FramedDialog } from "#src/overlay.js"; import { getCachedJson } from "#src/util/trackable.js"; import type { Viewer } from "#src/viewer.js"; const valueUpdateDelay = 100; -export class StateEditorDialog extends CodeEditorDialog { +export class StateEditorDialog extends FramedDialog { textEditor: CodeMirror.Editor; applyButton: HTMLButtonElement; downloadButton: HTMLButtonElement; - closeButton: HTMLButtonElement; constructor(public viewer: Viewer) { - super("State editor"); - - this.content.classList.add("neuroglancer-state-editor"); + super("State editor", "Close", "neuroglancer-state-editor"); const saveAndCloseWrapper = document.createElement("div"); saveAndCloseWrapper.classList.add( @@ -56,18 +52,10 @@ export class StateEditorDialog extends CodeEditorDialog { applyButton.addEventListener("click", () => this.applyChanges()); applyButton.disabled = true; - const closeButton = (this.closeButton = document.createElement("button")); - closeButton.classList.add("neuroglancer-state-editor-close-button"); - closeButton.textContent = "Save & close"; - saveAndCloseWrapper.appendChild(closeButton); - closeButton.addEventListener("click", () => { - this.applyChanges(); - this.close(); - }); + saveAndCloseWrapper.appendChild(this.primaryButton); const downloadButton = (this.downloadButton = document.createElement("button")); - downloadButton.classList.add("neuroglancer-state-editor-download-button"); downloadButton.textContent = "Download"; downloadButton.title = "Download state as a JSON file"; downloadButton.addEventListener("click", () => this.downloadState()); From 1692934245b633064787e0b4b6e4ada8187c6518 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Mon, 7 Jul 2025 20:36:46 +0200 Subject: [PATCH 15/24] feat: add line numbers for now to both. Will remove or add together --- src/ui/state_editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/state_editor.ts b/src/ui/state_editor.ts index c33ee7b1ca..a6e4cda7e3 100644 --- a/src/ui/state_editor.ts +++ b/src/ui/state_editor.ts @@ -67,6 +67,7 @@ export class StateEditorDialog extends FramedDialog { mode: { name: "javascript", json: true }, foldGutter: true, gutters: ["CodeMirror-lint-markers", "CodeMirror-foldgutter"], + lineNumbers: true, }); this.updateView(); From e86378afec033f25ecfd186b9b3d864f4ca99934 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Mon, 7 Jul 2025 20:56:50 +0200 Subject: [PATCH 16/24] refactor: reduce code in screenshot with new shared frame --- src/ui/screenshot_menu.css | 20 +++++----- src/ui/screenshot_menu.ts | 81 +++++++++++--------------------------- 2 files changed, 34 insertions(+), 67 deletions(-) diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 98ecb1e831..6b74ef322b 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -34,7 +34,7 @@ outline: 0; } -.neuroglancer-screenshot-dialog { +.neuroglancer-screenshot { width: 48.75rem; padding: 0; margin: 1.25rem auto; @@ -46,11 +46,13 @@ font-family: sans-serif; } -.neuroglancer-screenshot-main-body-container { +.neuroglancer-screenshot-body { height: auto; max-height: calc(100vh - 200px); overflow-y: auto; overflow-x: hidden; + display: block; + padding: 0; } .neuroglancer-screenshot-title { @@ -73,11 +75,10 @@ } /* Div at the top which contains the close */ -.neuroglancer-screenshot-close { - border-bottom: 1px solid var(--gray50); +.neuroglancer-screenshot-header { display: flex; align-items: center; - padding: 0.75rem 1rem; + padding: 1.5rem 1rem; gap: 10px; } @@ -185,7 +186,7 @@ padding: 0; } -.neuroglancer-screenshot-close-button { +.neuroglancer-screenshot-close-icon { margin-left: auto; background-color: transparent; border: 0; @@ -218,8 +219,8 @@ } .neuroglancer-screenshot-resolution-preview-container { - border-top: 1px solid var(--gray50); - border-bottom: 1px solid var(--gray50); + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; background: var(--gray500); width: 100%; padding: 1rem 1rem 0.5rem 1rem; @@ -339,11 +340,10 @@ } /* Footer with progress and buttons */ -.neuroglancer-screenshot-footer-container { +.neuroglancer-screenshot-footer { margin: 0; display: flex; padding: 0.75rem 1rem; - border-top: 1px solid var(--gray50); } .neuroglancer-screenshot-progress-text { diff --git a/src/ui/screenshot_menu.ts b/src/ui/screenshot_menu.ts index 636ca1fe18..3ac66ec3d0 100644 --- a/src/ui/screenshot_menu.ts +++ b/src/ui/screenshot_menu.ts @@ -17,10 +17,9 @@ */ import "#src/ui/screenshot_menu.css"; -import svg_close from "ikonate/icons/close.svg?raw"; import svg_help from "ikonate/icons/help.svg?raw"; import { throttle } from "lodash-es"; -import { Overlay } from "#src/overlay.js"; +import { FramedDialog } from "#src/overlay.js"; import { StatusMessage } from "#src/status.js"; import { setClipboard } from "#src/util/clipboard.js"; import type { @@ -242,10 +241,8 @@ function parseResolution( * For example, an x2 scale will cause the viewer in slice views to zoom in by a factor of 2 * such that when the number of pixels in the slice view is doubled, the FOV remains the same. */ -export class ScreenshotDialog extends Overlay { +export class ScreenshotDialog extends FramedDialog { private nameInput: HTMLInputElement; - private takeScreenshotButton: HTMLButtonElement; - private closeMenuButton: HTMLButtonElement; private cancelScreenshotButton: HTMLButtonElement; private forceScreenshotButton: HTMLButtonElement; private statisticsTable: HTMLTableElement; @@ -255,7 +252,6 @@ export class ScreenshotDialog extends Overlay { private filenameInputContainer: HTMLDivElement; private screenshotSizeText: HTMLDivElement; private warningElement: HTMLDivElement; - private footerScreenshotActionBtnsContainer: HTMLDivElement; private progressText: HTMLParagraphElement; private scaleRadioButtonsContainer: HTMLDivElement; private keepSliceFOVFixedCheckbox: HTMLInputElement; @@ -281,7 +277,14 @@ export class ScreenshotDialog extends Overlay { private screenshotHeight: number = 0; private screenshotPixelSize: HTMLElement; constructor(private screenshotManager: ScreenshotManager) { - super(); + super( + "Screenshot" /* Header title */, + "Take screenshot" /* Primary button text */, + "neuroglancer-screenshot" /* Extra class prefix */, + () => { + this.screenshot(); + } /* Primary button action */, + ); this.initializeUI(); this.setupEventListeners(); @@ -363,27 +366,13 @@ export class ScreenshotDialog extends Overlay { parentElement.classList.add("neuroglancer-screenshot-overlay"); } - const titleText = document.createElement("h2"); - titleText.classList.add("neuroglancer-screenshot-title"); - titleText.textContent = "Screenshot"; - - this.closeMenuButton = this.createButton( - null, - () => this.close(), - "neuroglancer-screenshot-close-button", - svg_close, - ); - this.cancelScreenshotButton = this.createButton( "Cancel screenshot", () => this.cancelScreenshot(), "neuroglancer-screenshot-footer-button", ); - this.takeScreenshotButton = this.createButton( - "Take screenshot", - () => this.screenshot(), - "neuroglancer-screenshot-footer-button", - ); + const takeScreenshotButton = this.primaryButton; + takeScreenshotButton.classList.add("neuroglancer-screenshot-footer-button"); this.forceScreenshotButton = this.createButton( "Force screenshot", () => this.forceScreenshot(), @@ -407,21 +396,8 @@ export class ScreenshotDialog extends Overlay { this.filenameInputContainer.appendChild(nameInputLabel); this.filenameInputContainer.appendChild(this.createNameInput()); - const closeAndHelpContainer = document.createElement("div"); - closeAndHelpContainer.classList.add("neuroglancer-screenshot-close"); - - closeAndHelpContainer.appendChild(titleText); - closeAndHelpContainer.appendChild(this.closeMenuButton); - - // This is the header - this.content.appendChild(closeAndHelpContainer); - - const mainBody = document.createElement("div"); - mainBody.classList.add("neuroglancer-screenshot-main-body-container"); - this.content.appendChild(mainBody); - - mainBody.appendChild(this.filenameInputContainer); - mainBody.appendChild(this.createScaleRadioButtons()); + this.body.appendChild(this.filenameInputContainer); + this.body.appendChild(this.createScaleRadioButtons()); const previewContainer = document.createElement("div"); previewContainer.classList.add( @@ -477,26 +453,15 @@ export class ScreenshotDialog extends Overlay { settingsPreview.appendChild(this.createPanelResolutionTable()); settingsPreview.appendChild(this.createLayerResolutionTable()); - mainBody.appendChild(previewContainer); - mainBody.appendChild(this.createStatisticsTable()); + this.body.appendChild(previewContainer); + this.body.appendChild(this.createStatisticsTable()); - this.footerScreenshotActionBtnsContainer = document.createElement("div"); - this.footerScreenshotActionBtnsContainer.classList.add( - "neuroglancer-screenshot-footer-container", - ); this.progressText = document.createElement("p"); this.progressText.classList.add("neuroglancer-screenshot-progress-text"); - this.footerScreenshotActionBtnsContainer.appendChild(this.progressText); - this.footerScreenshotActionBtnsContainer.appendChild( - this.cancelScreenshotButton, - ); - this.footerScreenshotActionBtnsContainer.appendChild( - this.takeScreenshotButton, - ); - this.footerScreenshotActionBtnsContainer.appendChild( - this.forceScreenshotButton, - ); - this.content.appendChild(this.footerScreenshotActionBtnsContainer); + this.footer.appendChild(this.progressText); + this.footer.appendChild(this.cancelScreenshotButton); + this.footer.appendChild(takeScreenshotButton); + this.footer.appendChild(this.forceScreenshotButton); this.screenshotManager.previewScreenshot(); this.updateUIBasedOnMode(); @@ -932,7 +897,8 @@ export class ScreenshotDialog extends Overlay { this.keepSliceFOVFixedCheckbox.disabled = false; this.forceScreenshotButton.disabled = true; this.cancelScreenshotButton.disabled = true; - this.takeScreenshotButton.disabled = false; + const takeScreenshotButton = this.primaryButton; + takeScreenshotButton.disabled = false; this.progressText.textContent = ""; this.forceScreenshotButton.title = ""; } else { @@ -945,7 +911,8 @@ export class ScreenshotDialog extends Overlay { this.keepSliceFOVFixedCheckbox.disabled = true; this.forceScreenshotButton.disabled = false; this.cancelScreenshotButton.disabled = false; - this.takeScreenshotButton.disabled = true; + const takeScreenshotButton = this.primaryButton; + takeScreenshotButton.disabled = true; this.progressText.textContent = "Screenshot in progress..."; this.forceScreenshotButton.title = "Force a screenshot of the current view without waiting for all data to be loaded and rendered"; From b9e140c083b9e3622a879fa0edf98eda6a9be8a7 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Mon, 7 Jul 2025 21:30:12 +0200 Subject: [PATCH 17/24] fix: correct styles after combining --- src/overlay.css | 16 ++++++++-------- src/ui/screenshot_menu.css | 14 ++++++-------- src/ui/shader_code_dialog.css | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index af3d4846c8..f30810bd75 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -14,7 +14,7 @@ * limitations under the License. */ - .neuroglancer-overlay { +.neuroglancer-overlay { height: 100%; width: 100%; position: fixed; @@ -46,7 +46,7 @@ .neuroglancer-framed-dialog-body { display: flex; - padding: 1.5rem; + padding: 0 1.5rem; overflow: auto; } @@ -61,21 +61,21 @@ display: flex; align-items: center; justify-content: space-between; - padding: 1.5rem; + padding: 1rem 1.5rem; border-top: 1px solid #ccc; margin-top: 1px; } .neuroglancer-framed-dialog-primary-button { + margin-left: auto; +} + +.neuroglancer-framed-dialog-close-icon { background-color: transparent; border: none; padding: 0; } -.neuroglancer-framed-dialog-primary-button:hover { - background: none; -} - .neuroglancer-framed-dialog-close-icon svg { stroke: #141415; opacity: 0.6; @@ -84,4 +84,4 @@ .neuroglancer-framed-dialog-close-icon:hover svg { stroke: white; opacity: 1; -} \ No newline at end of file +} diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 6b74ef322b..625c24ff74 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -186,12 +186,6 @@ padding: 0; } -.neuroglancer-screenshot-close-icon { - margin-left: auto; - background-color: transparent; - border: 0; -} - /* Screenshot resolution table - voxel resolution, panel resolution */ .neuroglancer-screenshot-size-text { margin: 0.75rem 0 0.75rem 0; @@ -327,10 +321,13 @@ height: 1rem; } +.neuroglancer-screenshot-dialog .neuroglancer-screenshot-close-icon svg { + stroke: #141415; +} + .neuroglancer-screenshot-dialog .neuroglancer-icon { width: 1rem; height: 1rem; - min-width: inherit; min-height: inherit; padding: 0; } @@ -350,11 +347,11 @@ margin: 0; flex: 1; font-weight: 590; - cursor: pointer; padding: 0; font-size: 0.813rem; align-self: center; color: var(--primary700); + cursor: wait; } .neuroglancer-screenshot-footer-button { @@ -366,6 +363,7 @@ font-weight: 590; color: var(--gray800); margin: 0 0 0 0.25rem; + cursor: pointer; } .neuroglancer-screenshot-footer-button:disabled { diff --git a/src/ui/shader_code_dialog.css b/src/ui/shader_code_dialog.css index 9a7a6bb438..6a28aac5fa 100644 --- a/src/ui/shader_code_dialog.css +++ b/src/ui/shader_code_dialog.css @@ -14,12 +14,12 @@ * limitations under the License. */ - .neuroglancer-shader-code-editor-dialog-body .neuroglancer-shader-code-widget { +.neuroglancer-shader-code-editor-dialog-body .neuroglancer-shader-code-widget { width: 80vw; - height: 100%; + height: 65vh; } .neuroglancer-shader-code-editor-dialog .neuroglancer-single-mesh-attribute-widget { max-height: 12vh; -} \ No newline at end of file +} From 16085eb114c4c7e9da8a4c484e9840b2c1666491 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 10:07:09 +0200 Subject: [PATCH 18/24] fix: small styling fixes --- src/overlay.css | 4 ++++ src/ui/screenshot_menu.css | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index f30810bd75..365010815c 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -34,6 +34,10 @@ color: black; } +.neuroglancer-framed-dialog:focus-visible { + outline: none; +} + .neuroglancer-framed-dialog-header { display: flex; align-items: center; diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 625c24ff74..e79bf8d0ae 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -36,13 +36,11 @@ .neuroglancer-screenshot { width: 48.75rem; - padding: 0; margin: 1.25rem auto; position: relative; transform: none; left: auto; top: auto; - border-radius: 0.5rem; font-family: sans-serif; } From db430387d04e74b5bb6bc444fb1eac6026cb25d8 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 10:32:47 +0200 Subject: [PATCH 19/24] fix: correct hover states in screenshot modal --- src/ui/screenshot_menu.css | 30 +++++++++++++-------- src/ui/screenshot_menu.ts | 53 ++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index e79bf8d0ae..403ec23ef3 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -258,13 +258,19 @@ outline: 0; border: 0; cursor: pointer; - height: 1rem; margin-left: auto; position: relative; - width: 33.33%; justify-content: flex-end; } +.neuroglancer-screenshot-copy-icon:hover svg { + stroke: var(--gray600); +} + +.neuroglancer-screenshot-copy-icon:hover { + background-color: var(--gray300); +} + .neuroglancer-screenshot-dimension { color: var(--gray600); } @@ -313,27 +319,29 @@ } /* Icons in the dialog */ -.neuroglancer-screenshot-dialog .neuroglancer-icon svg { - stroke: rgba(20, 20, 21, 0.4); +.neuroglancer-screenshot-tooltip svg, +.neuroglancer-screenshot-copy-icon svg { + stroke: var(--gray400); width: 1rem; height: 1rem; } -.neuroglancer-screenshot-dialog .neuroglancer-screenshot-close-icon svg { - stroke: #141415; -} - -.neuroglancer-screenshot-dialog .neuroglancer-icon { +.neuroglancer-screenshot-tooltip { + padding: 0; width: 1rem; height: 1rem; min-height: inherit; - padding: 0; + cursor: auto; } -.neuroglancer-screenshot-dialog .neuroglancer-icon:hover { +.neuroglancer-screenshot-tooltip:hover { background: none; } +.neuroglancer-screenshot-tooltip:hover svg{ + stroke: var(--gray600); +} + /* Footer with progress and buttons */ .neuroglancer-screenshot-footer { margin: 0; diff --git a/src/ui/screenshot_menu.ts b/src/ui/screenshot_menu.ts index 3ac66ec3d0..1daebefbbe 100644 --- a/src/ui/screenshot_menu.ts +++ b/src/ui/screenshot_menu.ts @@ -315,39 +315,30 @@ export class ScreenshotDialog extends FramedDialog { } private setupHelpTooltips() { - const generalSettingsTooltip = makeIcon({ - svg: svg_help, - title: splitIntoLines(TOOLTIPS.generalSettingsTooltip), - }); - - const orthographicSettingsTooltip = makeIcon({ - svg: svg_help, - title: TOOLTIPS.orthographicSettingsTooltip, - }); - orthographicSettingsTooltip.classList.add( - "neuroglancer-screenshot-resolution-table-tooltip", - ); - - const layerDataTooltip = makeIcon({ - svg: svg_help, - title: splitIntoLines(TOOLTIPS.layerDataTooltip), - }); - layerDataTooltip.classList.add( - "neuroglancer-screenshot-resolution-table-tooltip", + const makeTooltip = (title: string, inTable = false) => { + const tooltip = makeIcon({ + svg: svg_help, + title: splitIntoLines(title), + }); + tooltip.classList.add("neuroglancer-screenshot-tooltip"); + if (inTable) { + tooltip.classList.add( + "neuroglancer-screenshot-resolution-table-tooltip", + ); + } + return tooltip; + }; + const generalSettingsTooltip = makeTooltip(TOOLTIPS.generalSettingsTooltip); + const orthographicSettingsTooltip = makeTooltip( + TOOLTIPS.orthographicSettingsTooltip, + true, ); - - const scaleFactorHelpTooltip = makeIcon({ - svg: svg_help, - title: splitIntoLines(TOOLTIPS.scaleFactorHelpTooltip), - }); - - const panelScaleTooltip = makeIcon({ - svg: svg_help, - title: splitIntoLines(TOOLTIPS.panelScaleTooltip), - }); - panelScaleTooltip.classList.add( - "neuroglancer-screenshot-resolution-table-tooltip", + const layerDataTooltip = makeTooltip(TOOLTIPS.layerDataTooltip, true); + const scaleFactorHelpTooltip = makeTooltip( + TOOLTIPS.scaleFactorHelpTooltip, + true, ); + const panelScaleTooltip = makeTooltip(TOOLTIPS.panelScaleTooltip, true); return (this.helpTooltips = { generalSettingsTooltip, From 7338d0d5942ce1678e46d06ccd2ebf16e7777d92 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 10:42:48 +0200 Subject: [PATCH 20/24] feat: reuse button style from screenshot --- src/overlay.css | 15 +++++++++++++++ src/ui/screenshot_menu.css | 8 -------- src/ui/state_editor.css | 4 ++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index 365010815c..1c044c06d7 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -70,6 +70,21 @@ margin-top: 1px; } +.neuroglancer-framed-dialog-footer button { + border-radius: 0.5rem; + border: 1px solid #d0d5dd; + background: white; + padding: 0.5rem 0.75rem; + font-size: 0.813rem; + font-weight: 590; + color: #344054; + cursor: pointer; +} + +.neuroglancer-framed-dialog-footer button:hover { + color: #141415; +} + .neuroglancer-framed-dialog-primary-button { margin-left: auto; } diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 403ec23ef3..5e68c2514e 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -361,15 +361,7 @@ } .neuroglancer-screenshot-footer-button { - border-radius: 0.5rem; - border: 1px solid var(--gray300); - background: white; - padding: 0.5rem 0.75rem; - font-size: 0.813rem; - font-weight: 590; - color: var(--gray800); margin: 0 0 0 0.25rem; - cursor: pointer; } .neuroglancer-screenshot-footer-button:disabled { diff --git a/src/ui/state_editor.css b/src/ui/state_editor.css index 3fef492f96..d72827cbd2 100644 --- a/src/ui/state_editor.css +++ b/src/ui/state_editor.css @@ -24,3 +24,7 @@ justify-content: flex-end; align-items: center; } + +.neuroglancer-state-editor-save-container button { + margin: 0 0 0 0.25rem; +} From 17ec6249d517762976f45b83e7c8049d67aa8426 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 10:58:20 +0200 Subject: [PATCH 21/24] refactor: remove uneeded styling --- src/ui/screenshot_menu.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 5e68c2514e..7f297c23d3 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -74,10 +74,7 @@ /* Div at the top which contains the close */ .neuroglancer-screenshot-header { - display: flex; - align-items: center; padding: 1.5rem 1rem; - gap: 10px; } /* Filename input menu */ From 4f7132a0ef38e23bc8265e62f545a8cc741b2bfc Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 10:58:22 +0200 Subject: [PATCH 22/24] Revert "feat: reuse button style from screenshot" This reverts commit 7338d0d5942ce1678e46d06ccd2ebf16e7777d92. --- src/overlay.css | 15 --------------- src/ui/screenshot_menu.css | 8 ++++++++ src/ui/state_editor.css | 4 ---- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/overlay.css b/src/overlay.css index 1c044c06d7..365010815c 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -70,21 +70,6 @@ margin-top: 1px; } -.neuroglancer-framed-dialog-footer button { - border-radius: 0.5rem; - border: 1px solid #d0d5dd; - background: white; - padding: 0.5rem 0.75rem; - font-size: 0.813rem; - font-weight: 590; - color: #344054; - cursor: pointer; -} - -.neuroglancer-framed-dialog-footer button:hover { - color: #141415; -} - .neuroglancer-framed-dialog-primary-button { margin-left: auto; } diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index 7f297c23d3..feefb5c557 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -358,7 +358,15 @@ } .neuroglancer-screenshot-footer-button { + border-radius: 0.5rem; + border: 1px solid var(--gray300); + background: white; + padding: 0.5rem 0.75rem; + font-size: 0.813rem; + font-weight: 590; + color: var(--gray800); margin: 0 0 0 0.25rem; + cursor: pointer; } .neuroglancer-screenshot-footer-button:disabled { diff --git a/src/ui/state_editor.css b/src/ui/state_editor.css index d72827cbd2..3fef492f96 100644 --- a/src/ui/state_editor.css +++ b/src/ui/state_editor.css @@ -24,7 +24,3 @@ justify-content: flex-end; align-items: center; } - -.neuroglancer-state-editor-save-container button { - margin: 0 0 0 0.25rem; -} From 6b75dc176b01a4a3fe18650e03af43764fd76906 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 11:00:23 +0200 Subject: [PATCH 23/24] fix: add hover state to screenshot button --- src/ui/screenshot_menu.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/screenshot_menu.css b/src/ui/screenshot_menu.css index feefb5c557..2c6c7953db 100644 --- a/src/ui/screenshot_menu.css +++ b/src/ui/screenshot_menu.css @@ -335,7 +335,7 @@ background: none; } -.neuroglancer-screenshot-tooltip:hover svg{ +.neuroglancer-screenshot-tooltip:hover svg { stroke: var(--gray600); } @@ -369,6 +369,10 @@ cursor: pointer; } +.neuroglancer-screenshot-footer-button:hover { + color: var(--gray600); +} + .neuroglancer-screenshot-footer-button:disabled { display: none; } From 851a079df8d79f482fa67b521b6fdbc67091f6c8 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 8 Jul 2025 12:52:44 +0200 Subject: [PATCH 24/24] style: format --- src/ui/shader_code_dialog.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/shader_code_dialog.ts b/src/ui/shader_code_dialog.ts index fc450939dc..c64b9b6dd1 100644 --- a/src/ui/shader_code_dialog.ts +++ b/src/ui/shader_code_dialog.ts @@ -42,4 +42,4 @@ export class ShaderCodeEditorDialog extends FramedDialog { codeWidget.textEditor.refresh(); } -} \ No newline at end of file +}