Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/ui/annotation_schema_tab.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@
width: 100%;
}

.neuroglancer-annotation-schema-enum-default-selector {
display: flex;
align-items: center;
margin-bottom: 12px;
}

.neuroglancer-annotation-schema-enum-default-label {
margin-right: 8px;
font-size: 12px;
color: #ccc;
}

.neuroglancer-annotation-schema-enum-default-select {
background: #2a2a2a;
border: 1px solid #555;
color: white;
padding: 2px 4px;
border-radius: 2px;
font-size: 12px;
width: 100%;
}

.neuroglancer-annotation-schema-enum-default-select:focus {
border-color: #007acc;
outline: none;
}

.neuroglancer-annotation-schema-default-value-input {
text-align: left;
width: 100%;
Expand Down
81 changes: 77 additions & 4 deletions src/ui/annotation_schema_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down Expand Up @@ -511,6 +518,11 @@ class AnnotationUIProperty extends RefCounted {

const inputs: (HTMLInputElement | HTMLTextAreaElement)[] = [];

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],
Expand All @@ -532,6 +544,48 @@ class AnnotationUIProperty extends RefCounted {
return inputs;
}

private createEnumDefaultSelector(
enumProperty: AnnotationNumericPropertySpec,
): HTMLDivElement {
const selectorContainer = document.createElement("div");
selectorContainer.className =
"neuroglancer-annotation-schema-enum-default-selector";

const label = document.createElement("label");
label.textContent = "Default:";
label.className = "neuroglancer-annotation-schema-enum-default-label";

const select = document.createElement("select");
select.className = "neuroglancer-annotation-schema-enum-default-select";

enumProperty.enumValues?.forEach((value: number, index: number) => {
const option = document.createElement("option");
option.value = String(value);
if (!enumProperty.enumLabels) {
option.textContent = "Non-schema value";
Comment on lines +564 to +565
Copy link
Preview

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback text 'Non-schema value' is misleading and could confuse users. If enumLabels is missing, consider using the numeric value itself or a more descriptive fallback like 'Value: {value}'.

Suggested change
if (!enumProperty.enumLabels) {
option.textContent = "Non-schema value";
option.textContent = `Value: ${value}`;

Copilot uses AI. Check for mistakes.

} else {
option.textContent = enumProperty.enumLabels[index];
}
option.selected = value === enumProperty.default;
select.appendChild(option);
});

select.addEventListener("change", (event) => {
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;
}

private createNumericInputs(
type: AnnotationPropertyType,
oldProperty: any,
Expand Down Expand Up @@ -585,13 +639,21 @@ class AnnotationUIProperty extends RefCounted {
);
const newEnumValues = [...currentEnumValues!, suggestedEnumValue];

// 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
: newEnumValues[0];

this.updateProperty(currentProperty, {
enumValues: newEnumValues,
enumLabels: [
...currentProperty.enumLabels!,
`${suggestedEnumValue} (label)`,
],
default: newEnumValues[0],
default: newDefault,
} as AnnotationNumericPropertySpec);
},
});
Expand Down Expand Up @@ -700,9 +762,17 @@ 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] ?? 0)
: currentProperty.default;

this.updateProperty(currentProperty, {
enumValues: newEnumValues,
enumLabels: newEnumLabels,
default: newDefault,
Copy link
Preview

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing property name - should be 'default: newDefault,' to match the object property syntax used in the rest of the codebase.

Copilot uses AI. Check for mistakes.

});
},
});
Expand Down Expand Up @@ -1021,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() {
Expand Down Expand Up @@ -1470,7 +1543,7 @@ export class AnnotationSchemaView extends Tab {
if (isEnum && isAnnotationTypeNumeric(type)) {
return {
enumValues: [0],
enumLabels: ["Default"],
enumLabels: ["0 (label)"],
};
}
return {};
Expand Down