@@ -23,6 +23,7 @@ import type {
23
23
CoordinateSpaceTransform ,
24
24
WatchableCoordinateSpaceTransform ,
25
25
} from "#src/coordinate_transform.js" ;
26
+ import { WatchableValue } from "#src/trackable_value.js" ;
26
27
import { arraysEqual } from "#src/util/array.js" ;
27
28
import {
28
29
packColor ,
@@ -106,6 +107,13 @@ export interface AnnotationNumericPropertySpec
106
107
min ?: number ;
107
108
max ?: number ;
108
109
step ?: number ;
110
+ tag ?: string ;
111
+ }
112
+
113
+ export interface AnnotationTagPropertySpec
114
+ extends AnnotationNumericPropertySpec {
115
+ type : "int8" ;
116
+ tag : string ;
109
117
}
110
118
111
119
export const propertyTypeDataType : Record <
@@ -127,6 +135,18 @@ export type AnnotationPropertySpec =
127
135
| AnnotationColorPropertySpec
128
136
| AnnotationNumericPropertySpec ;
129
137
138
+ export function isAnnotationNumericPropertySpec (
139
+ spec : AnnotationPropertySpec ,
140
+ ) : spec is AnnotationNumericPropertySpec {
141
+ return spec . type !== "rgb" && spec . type !== "rgba" ;
142
+ }
143
+
144
+ export function isAnnotationTagPropertySpec (
145
+ spec : AnnotationPropertySpec ,
146
+ ) : spec is AnnotationTagPropertySpec {
147
+ return spec . type === "uint8" && spec . tag !== undefined ;
148
+ }
149
+
130
150
export interface AnnotationPropertyTypeHandler {
131
151
serializedBytes ( rank : number ) : number ;
132
152
alignment ( rank : number ) : number ;
@@ -569,6 +589,7 @@ function parseAnnotationPropertySpec(obj: unknown): AnnotationPropertySpec {
569
589
) ;
570
590
let enumValues : number [ ] | undefined ;
571
591
let enumLabels : string [ ] | undefined ;
592
+ let tag : string | undefined ;
572
593
switch ( type ) {
573
594
case "rgb" :
574
595
case "rgba" :
@@ -593,6 +614,7 @@ function parseAnnotationPropertySpec(obj: unknown): AnnotationPropertySpec {
593
614
) ,
594
615
) ;
595
616
}
617
+ tag = verifyOptionalObjectProperty ( obj , "tag" , verifyString ) ;
596
618
}
597
619
}
598
620
return {
@@ -602,15 +624,23 @@ function parseAnnotationPropertySpec(obj: unknown): AnnotationPropertySpec {
602
624
default : defaultValue ,
603
625
enumValues,
604
626
enumLabels,
627
+ tag,
605
628
} as AnnotationPropertySpec ;
606
629
}
607
630
608
631
function annotationPropertySpecToJson ( spec : AnnotationPropertySpec ) {
609
632
const defaultValue = spec . default ;
633
+ const isNumeric = isAnnotationNumericPropertySpec ( spec ) ;
634
+ const tag = isNumeric ? spec . tag : undefined ;
635
+ const enum_values = isNumeric ? spec . enumValues : undefined ;
636
+ const enum_labels = isNumeric ? spec . enumLabels : undefined ;
610
637
return {
611
638
id : spec . identifier ,
612
639
description : spec . description ,
613
640
type : spec . type ,
641
+ tag,
642
+ enum_values,
643
+ enum_labels,
614
644
default :
615
645
defaultValue === 0
616
646
? undefined
@@ -1000,7 +1030,7 @@ export const annotationTypeHandlers: Record<
1000
1030
export interface AnnotationSchema {
1001
1031
rank : number ;
1002
1032
relationships : readonly string [ ] ;
1003
- properties : readonly AnnotationPropertySpec [ ] ;
1033
+ properties : WatchableValue < readonly Readonly < AnnotationPropertySpec > [ ] > ;
1004
1034
}
1005
1035
1006
1036
export function annotationToJson (
@@ -1020,8 +1050,8 @@ export function annotationToJson(
1020
1050
segments . map ( ( x ) => x . toString ( ) ) ,
1021
1051
) ;
1022
1052
}
1023
- if ( schema . properties . length !== 0 ) {
1024
- const propertySpecs = schema . properties ;
1053
+ const propertySpecs = schema . properties . value ;
1054
+ if ( propertySpecs . length !== 0 ) {
1025
1055
result . props = annotation . properties . map ( ( prop , i ) =>
1026
1056
annotationPropertyTypeHandlers [ propertySpecs [ i ] . type ] . serializeJson ( prop ) ,
1027
1057
) ;
@@ -1061,9 +1091,9 @@ function restoreAnnotation(
1061
1091
) ;
1062
1092
} ) ;
1063
1093
const properties = verifyObjectProperty ( obj , "props" , ( propsObj ) => {
1064
- const propSpecs = schema . properties ;
1094
+ const propSpecs = schema . properties . value ;
1065
1095
if ( propsObj === undefined ) return propSpecs . map ( ( x ) => x . default ) ;
1066
- return parseArray ( expectArray ( propsObj , schema . properties . length ) , ( x , i ) =>
1096
+ return parseArray ( expectArray ( propsObj , propSpecs . length ) , ( x , i ) =>
1067
1097
annotationPropertyTypeHandlers [ propSpecs [ i ] . type ] . deserializeJson ( x ) ,
1068
1098
) ;
1069
1099
} ) ;
@@ -1111,13 +1141,15 @@ export class AnnotationSource
1111
1141
constructor (
1112
1142
rank : number ,
1113
1143
public readonly relationships : readonly string [ ] = [ ] ,
1114
- public readonly properties : Readonly < AnnotationPropertySpec > [ ] = [ ] ,
1144
+ public readonly properties : WatchableValue <
1145
+ readonly Readonly < AnnotationPropertySpec > [ ]
1146
+ > = new WatchableValue ( [ ] ) ,
1115
1147
) {
1116
1148
super ( ) ;
1117
1149
this . rank_ = rank ;
1118
1150
this . annotationPropertySerializers = makeAnnotationPropertySerializers (
1119
1151
rank ,
1120
- properties ,
1152
+ properties . value ,
1121
1153
) ;
1122
1154
}
1123
1155
@@ -1261,16 +1293,56 @@ export class LocalAnnotationSource extends AnnotationSource {
1261
1293
1262
1294
constructor (
1263
1295
public watchableTransform : WatchableCoordinateSpaceTransform ,
1264
- properties : AnnotationPropertySpec [ ] ,
1296
+ public readonly properties : WatchableValue <
1297
+ AnnotationPropertySpec [ ]
1298
+ > = new WatchableValue ( [ ] ) ,
1265
1299
relationships : string [ ] ,
1266
1300
) {
1267
1301
super ( watchableTransform . value . sourceRank , relationships , properties ) ;
1268
1302
this . curCoordinateTransform = watchableTransform . value ;
1269
1303
this . registerDisposer (
1270
1304
watchableTransform . changed . add ( ( ) => this . ensureUpdated ( ) ) ,
1271
1305
) ;
1306
+
1307
+ this . registerDisposer (
1308
+ properties . changed . add ( ( ) => {
1309
+ this . updateAnnotationPropertySerializers ( ) ;
1310
+ this . changed . dispatch ( ) ;
1311
+ } ) ,
1312
+ ) ;
1313
+ }
1314
+
1315
+ updateAnnotationPropertySerializers ( ) {
1316
+ this . annotationPropertySerializers = makeAnnotationPropertySerializers (
1317
+ this . rank_ ,
1318
+ this . properties . value ,
1319
+ ) ;
1272
1320
}
1273
1321
1322
+ addProperty ( property : AnnotationPropertySpec ) {
1323
+ this . properties . value . push ( property ) ;
1324
+ for ( const annotation of this ) {
1325
+ annotation . properties . push ( property . default ) ;
1326
+ }
1327
+ this . properties . changed . dispatch ( ) ;
1328
+ }
1329
+
1330
+ removeProperty ( identifier : string ) {
1331
+ const propertyIndex = this . properties . value . findIndex (
1332
+ ( x ) => x . identifier === identifier ,
1333
+ ) ;
1334
+ this . properties . value . splice ( propertyIndex , 1 ) ;
1335
+ for ( const annotation of this ) {
1336
+ annotation . properties . splice ( propertyIndex , 1 ) ;
1337
+ }
1338
+ this . properties . changed . dispatch ( ) ;
1339
+ }
1340
+
1341
+ getTagProperties = ( ) => {
1342
+ const { properties } = this ;
1343
+ return properties . value . filter ( isAnnotationTagPropertySpec ) ;
1344
+ } ;
1345
+
1274
1346
ensureUpdated ( ) {
1275
1347
const transform = this . watchableTransform . value ;
1276
1348
const { curCoordinateTransform } = this ;
@@ -1325,10 +1397,7 @@ export class LocalAnnotationSource extends AnnotationSource {
1325
1397
}
1326
1398
if ( this . rank_ !== sourceRank ) {
1327
1399
this . rank_ = sourceRank ;
1328
- this . annotationPropertySerializers = makeAnnotationPropertySerializers (
1329
- this . rank_ ,
1330
- this . properties ,
1331
- ) ;
1400
+ this . updateAnnotationPropertySerializers ( ) ;
1332
1401
}
1333
1402
this . changed . dispatch ( ) ;
1334
1403
}
0 commit comments