From 1c4d9bc19e28cdc54d15f8e8be9f0c6af3ab146a Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Tue, 26 Aug 2025 18:09:00 +0200 Subject: [PATCH 1/6] feat: scaffolding an enum default select --- src/ui/annotation_schema_tab.css | 31 +++++++++++++++++++ src/ui/annotation_schema_tab.ts | 53 +++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/ui/annotation_schema_tab.css b/src/ui/annotation_schema_tab.css index 109095928..9c57891f8 100644 --- a/src/ui/annotation_schema_tab.css +++ b/src/ui/annotation_schema_tab.css @@ -60,6 +60,37 @@ width: 100%; } +.neuroglancer-annotation-schema-enum-default-selector { + display: flex; + align-items: center; + margin-bottom: 8px; + padding: 4px; + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; +} + +.neuroglancer-annotation-schema-enum-default-label { + margin-right: 8px; + font-size: 12px; + color: #ccc; + white-space: nowrap; +} + +.neuroglancer-annotation-schema-enum-default-select { + flex: 1; + background: #2a2a2a; + border: 1px solid #555; + color: white; + padding: 2px 4px; + border-radius: 2px; + font-size: 12px; +} + +.neuroglancer-annotation-schema-enum-default-select:focus { + border-color: #007acc; + outline: none; +} + .neuroglancer-annotation-schema-default-value-input { text-align: left; width: 100%; diff --git a/src/ui/annotation_schema_tab.ts b/src/ui/annotation_schema_tab.ts index 9ac8403a3..07cb20703 100644 --- a/src/ui/annotation_schema_tab.ts +++ b/src/ui/annotation_schema_tab.ts @@ -511,6 +511,12 @@ class AnnotationUIProperty extends RefCounted { const inputs: (HTMLInputElement | HTMLTextAreaElement)[] = []; + // Add default value selector at the top + if (!this.readonly) { + const defaultSelector = this.createEnumDefaultSelector(oldProperty); + enumContainer.appendChild(defaultSelector); + } + for (let i = 0; i < enumValues!.length; i++) { const entryInputs = this.createEnumEntry( enumValues![i], @@ -532,6 +538,38 @@ class AnnotationUIProperty extends RefCounted { return inputs; } + private createEnumDefaultSelector(oldProperty: any): HTMLDivElement { + const selectorContainer = document.createElement("div"); + selectorContainer.className = "neuroglancer-annotation-schema-enum-default-selector"; + + const label = document.createElement("label"); + label.textContent = "Default value: "; + label.className = "neuroglancer-annotation-schema-enum-default-label"; + + const select = document.createElement("select"); + select.className = "neuroglancer-annotation-schema-enum-default-select"; + + // Populate options + oldProperty.enumValues.forEach((value: number, index: number) => { + const option = document.createElement("option"); + option.value = String(value); + option.textContent = oldProperty.enumLabels[index]; + option.selected = value === oldProperty.default; + select.appendChild(option); + }); + + // Handle selection change + select.addEventListener("change", (event) => { + const selectedValue = parseFloat((event.target as HTMLSelectElement).value); + this.updateProperty(oldProperty, { default: selectedValue }); + }); + + selectorContainer.appendChild(label); + selectorContainer.appendChild(select); + + return selectorContainer; + } + private createNumericInputs( type: AnnotationPropertyType, oldProperty: any, @@ -585,13 +623,19 @@ class AnnotationUIProperty extends RefCounted { ); const newEnumValues = [...currentEnumValues!, suggestedEnumValue]; + // Keep the current default value if it's still valid, otherwise use the first value + const currentDefault = currentProperty.default; + const newDefault = newEnumValues.includes(currentDefault) + ? currentDefault + : newEnumValues[0]; + this.updateProperty(currentProperty, { enumValues: newEnumValues, enumLabels: [ ...currentProperty.enumLabels!, `${suggestedEnumValue} (label)`, ], - default: newEnumValues[0], + default: newDefault, } as AnnotationNumericPropertySpec); }, }); @@ -700,9 +744,16 @@ class AnnotationUIProperty extends RefCounted { (_, i) => i !== enumIndex, ); + // If we're deleting the current default value, set the new default to the first remaining value + const deletedValue = currentProperty.enumValues![enumIndex]; + const newDefault = currentProperty.default === deletedValue + ? newEnumValues[0] + : currentProperty.default; + this.updateProperty(currentProperty, { enumValues: newEnumValues, enumLabels: newEnumLabels, + default: newDefault, }); }, }); From 6eb6f415d63d0c1f9eafecd77346b1fdf91f8da5 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 27 Aug 2025 13:27:07 +0200 Subject: [PATCH 2/6] feat: hook up selector properly for default --- src/ui/annotation_schema_tab.ts | 58 ++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/ui/annotation_schema_tab.ts b/src/ui/annotation_schema_tab.ts index 84130b6bd..ff05c3fdc 100644 --- a/src/ui/annotation_schema_tab.ts +++ b/src/ui/annotation_schema_tab.ts @@ -511,7 +511,6 @@ class AnnotationUIProperty extends RefCounted { const inputs: (HTMLInputElement | HTMLTextAreaElement)[] = []; - // Add default value selector at the top if (!this.readonly) { const defaultSelector = this.createEnumDefaultSelector(oldProperty); enumContainer.appendChild(defaultSelector); @@ -538,35 +537,45 @@ class AnnotationUIProperty extends RefCounted { return inputs; } - private createEnumDefaultSelector(oldProperty: any): HTMLDivElement { + private createEnumDefaultSelector( + enumProperty: AnnotationNumericPropertySpec, + ): HTMLDivElement { const selectorContainer = document.createElement("div"); - selectorContainer.className = "neuroglancer-annotation-schema-enum-default-selector"; - + selectorContainer.className = + "neuroglancer-annotation-schema-enum-default-selector"; + const label = document.createElement("label"); label.textContent = "Default value: "; label.className = "neuroglancer-annotation-schema-enum-default-label"; - + const select = document.createElement("select"); select.className = "neuroglancer-annotation-schema-enum-default-select"; - - // Populate options - oldProperty.enumValues.forEach((value: number, index: number) => { + + enumProperty.enumValues?.forEach((value: number, index: number) => { const option = document.createElement("option"); option.value = String(value); - option.textContent = oldProperty.enumLabels[index]; - option.selected = value === oldProperty.default; + if (!enumProperty.enumLabels) { + option.textContent = "Non-schema value"; + } else { + option.textContent = enumProperty.enumLabels[index]; + } + option.selected = value === enumProperty.default; select.appendChild(option); }); - - // Handle selection change + select.addEventListener("change", (event) => { - const selectedValue = parseFloat((event.target as HTMLSelectElement).value); - this.updateProperty(oldProperty, { default: selectedValue }); + const selectedValue = parseFloat( + (event.target as HTMLSelectElement).value, + ); + const existingProperty = this.getPropertyByIdentifier( + enumProperty.identifier, + ) as AnnotationNumericPropertySpec; + this.updateProperty(existingProperty, { default: selectedValue }); }); - + selectorContainer.appendChild(label); selectorContainer.appendChild(select); - + return selectorContainer; } @@ -623,10 +632,12 @@ class AnnotationUIProperty extends RefCounted { ); const newEnumValues = [...currentEnumValues!, suggestedEnumValue]; - // Keep the current default value if it's still valid, otherwise use the first value + // Keep the current default value if it's still valid + // otherwise use the first value + // this could occur if the user deletes all values then adds a new one const currentDefault = currentProperty.default; - const newDefault = newEnumValues.includes(currentDefault) - ? currentDefault + const newDefault = newEnumValues.includes(currentDefault) + ? currentDefault : newEnumValues[0]; this.updateProperty(currentProperty, { @@ -746,9 +757,10 @@ class AnnotationUIProperty extends RefCounted { // If we're deleting the current default value, set the new default to the first remaining value const deletedValue = currentProperty.enumValues![enumIndex]; - const newDefault = currentProperty.default === deletedValue - ? newEnumValues[0] - : currentProperty.default; + const newDefault = + currentProperty.default === deletedValue + ? (newEnumValues[0] ?? 0) + : currentProperty.default; this.updateProperty(currentProperty, { enumValues: newEnumValues, @@ -1521,7 +1533,7 @@ export class AnnotationSchemaView extends Tab { if (isEnum && isAnnotationTypeNumeric(type)) { return { enumValues: [0], - enumLabels: ["Default"], + enumLabels: ["0 (label)"], }; } return {}; From 56afaea12528e26b50c2732344dbe1aaa25b46a6 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 27 Aug 2025 13:32:00 +0200 Subject: [PATCH 3/6] fix: don't update default on enums in UI --- src/ui/annotation_schema_tab.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ui/annotation_schema_tab.ts b/src/ui/annotation_schema_tab.ts index ff05c3fdc..ae6a5d515 100644 --- a/src/ui/annotation_schema_tab.ts +++ b/src/ui/annotation_schema_tab.ts @@ -196,7 +196,14 @@ class AnnotationUIProperty extends RefCounted { // For numeric types, we can set the default value directly const type = this.spec.type; if (isAnnotationTypeNumeric(type)) { - this.defaultValueElements[0].value = numberToStringFixed(defaultValue, 4); + const enumLabels = (this.spec as AnnotationNumericPropertySpec) + .enumLabels; + if (!isEnumType(enumLabels)) { + this.defaultValueElements[0].value = numberToStringFixed( + defaultValue, + 4, + ); + } } // For color types, we need to unpack the color and set the RGB values else if (type.startsWith("rgb")) { From e65fed3f1143f36fd2f6a627f8c6f31c369c60ef Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 27 Aug 2025 13:43:05 +0200 Subject: [PATCH 4/6] fix: allow schema to move in default --- src/ui/annotation_schema_tab.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/annotation_schema_tab.css b/src/ui/annotation_schema_tab.css index 9c57891f8..acb1c95e5 100644 --- a/src/ui/annotation_schema_tab.css +++ b/src/ui/annotation_schema_tab.css @@ -73,17 +73,18 @@ margin-right: 8px; font-size: 12px; color: #ccc; - white-space: nowrap; } .neuroglancer-annotation-schema-enum-default-select { - flex: 1; background: #2a2a2a; border: 1px solid #555; color: white; padding: 2px 4px; border-radius: 2px; font-size: 12px; + max-width: 100px; + min-width: 20px; + width: 100%; } .neuroglancer-annotation-schema-enum-default-select:focus { From 38348b1021b1e2919c9a762fc15c126f2a35562b Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 27 Aug 2025 13:43:30 +0200 Subject: [PATCH 5/6] feat: explain default updates --- src/ui/annotation_schema_tab.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/annotation_schema_tab.ts b/src/ui/annotation_schema_tab.ts index ae6a5d515..a4f857880 100644 --- a/src/ui/annotation_schema_tab.ts +++ b/src/ui/annotation_schema_tab.ts @@ -1091,7 +1091,10 @@ export class AnnotationSchemaView extends Tab { private updateAnnotationText() { const setOrViewText = this.readonly.value ? "View read-only" : "Set"; - this.schemaViewTextElement.textContent = `${setOrViewText} annotation property (metadata) schema for this layer which applies to all annotations in this layer.`; + const setExplainText = this.readonly.value + ? "" + : " Changing a default value in the schema is not retroactive and only applies to new annotations."; + this.schemaViewTextElement.textContent = `${setOrViewText} annotation property (metadata) schema for this layer which applies to all annotations in this layer.${setExplainText}`; } private updateElementVisibility() { From 7471b552dfc258fe420a9eff60b231a0e3dda293 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Wed, 27 Aug 2025 13:47:49 +0200 Subject: [PATCH 6/6] fix: small css fixes --- src/ui/annotation_schema_tab.css | 7 +------ src/ui/annotation_schema_tab.ts | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ui/annotation_schema_tab.css b/src/ui/annotation_schema_tab.css index acb1c95e5..6e8295c2b 100644 --- a/src/ui/annotation_schema_tab.css +++ b/src/ui/annotation_schema_tab.css @@ -63,10 +63,7 @@ .neuroglancer-annotation-schema-enum-default-selector { display: flex; align-items: center; - margin-bottom: 8px; - padding: 4px; - background: rgba(255, 255, 255, 0.05); - border-radius: 4px; + margin-bottom: 12px; } .neuroglancer-annotation-schema-enum-default-label { @@ -82,8 +79,6 @@ padding: 2px 4px; border-radius: 2px; font-size: 12px; - max-width: 100px; - min-width: 20px; width: 100%; } diff --git a/src/ui/annotation_schema_tab.ts b/src/ui/annotation_schema_tab.ts index a4f857880..764d768c7 100644 --- a/src/ui/annotation_schema_tab.ts +++ b/src/ui/annotation_schema_tab.ts @@ -552,7 +552,7 @@ class AnnotationUIProperty extends RefCounted { "neuroglancer-annotation-schema-enum-default-selector"; const label = document.createElement("label"); - label.textContent = "Default value: "; + label.textContent = "Default:"; label.className = "neuroglancer-annotation-schema-enum-default-label"; const select = document.createElement("select");