1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | import * as go from '../release/go-module.js';
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | export class Inspector {
|
68 | private _div: HTMLDivElement;
|
69 | private _diagram: go.Diagram;
|
70 | private _inspectedObject: go.ObjectData | null = null;
|
71 |
|
72 | private _inspectSelection: boolean = true;
|
73 | private _includesOwnProperties: boolean = true;
|
74 | private _properties: { [index: string]: any } = {};
|
75 | private _propertyModified: ((a: string, b: string, c: Inspector) => void) | null = null;
|
76 | private _multipleSelection: boolean = false;
|
77 | private _showUnionProperties: boolean = false;
|
78 | private _showLimit: number = 0;
|
79 |
|
80 | // Private variables used to keep track of internal state
|
81 | private inspectedProperties: { [index: string]: any } = {};
|
82 | private multipleProperties: { [index: string]: any } = {};
|
83 | private tabIndex: number;
|
84 | // Functions used to keep the Inspector up-to-date
|
85 | private inspectOnModelChanged: ((e: go.ChangedEvent) => void);
|
86 | private inspectOnSelectionChanged: ((e: go.DiagramEvent) => void);
|
87 |
|
88 | /**
|
89 | * Constructs an Inspector and sets up properties based on the options provided.
|
90 | * Also sets up change listeners on the Diagram so the Inspector stays up-to-date.
|
91 | * @param {string} divid a string referencing the HTML ID of the to-be Inspector's div
|
92 | * @param {Diagram} diagram a reference to a GoJS Diagram
|
93 | * @param {Object=} options an optional JS Object describing options for the inspector
|
94 | */
|
95 | constructor(divid: string, diagram: go.Diagram, options?: { [index: string]: any }) {
|
96 | const mainDiv = document.getElementById(divid) as HTMLDivElement;
|
97 | mainDiv.className = 'inspector';
|
98 | mainDiv.innerHTML = '';
|
99 | this._div = mainDiv;
|
100 | this._diagram = diagram;
|
101 | this.tabIndex = 0;
|
102 | // Set properties based on options
|
103 | if (options !== undefined) {
|
104 | if (options.inspectSelection !== undefined) this._inspectSelection = options.inspectSelection;
|
105 | if (options.includesOwnProperties !== undefined) this._includesOwnProperties = options.includesOwnProperties;
|
106 | if (options.properties !== undefined) this._properties = options.properties;
|
107 | if (options.propertyModified !== undefined) this._propertyModified = options.propertyModified;
|
108 | if (options.multipleSelection !== undefined) this._multipleSelection = options.multipleSelection;
|
109 | if (options.showUnionProperties !== undefined) this._showUnionProperties = options.showUnionProperties;
|
110 | if (options.showLimit !== undefined) this._showLimit = options.showLimit;
|
111 | }
|
112 | // Prepare change listeners
|
113 | const self = this;
|
114 | this.inspectOnModelChanged = (e: go.ChangedEvent) => {
|
115 | if (e.isTransactionFinished) self.inspectObject();
|
116 | };
|
117 | this.inspectOnSelectionChanged = (e: go.DiagramEvent) => { self.inspectObject(); };
|
118 | this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
119 | if (this._inspectSelection) {
|
120 | this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
121 | }
|
122 | }
|
123 |
|
124 | |
125 |
|
126 |
|
127 | get div(): HTMLDivElement { return this._div; }
|
128 |
|
129 | |
130 |
|
131 |
|
132 | get diagram(): go.Diagram { return this._diagram; }
|
133 | set diagram(val: go.Diagram) {
|
134 | if (val !== this._diagram) {
|
135 |
|
136 | this._diagram.removeModelChangedListener(this.inspectOnModelChanged);
|
137 | this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
138 |
|
139 | this._diagram = val;
|
140 | this._diagram.addModelChangedListener(this.inspectOnModelChanged);
|
141 | if (this._inspectSelection) {
|
142 | this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
143 | this.inspectObject();
|
144 | } else {
|
145 | this.inspectObject(null);
|
146 | }
|
147 | }
|
148 | }
|
149 |
|
150 | |
151 |
|
152 |
|
153 |
|
154 |
|
155 | get inspectedObject(): go.ObjectData | null { return this._inspectedObject; }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | get inspectSelection(): boolean { return this._inspectSelection; }
|
164 | set inspectSelection(val: boolean) {
|
165 | if (val !== this._inspectSelection) {
|
166 | this._inspectSelection = val;
|
167 | if (this._inspectSelection) {
|
168 | this._diagram.addDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
169 | this.inspectObject();
|
170 | } else {
|
171 | this._diagram.removeDiagramListener('ChangedSelection', this.inspectOnSelectionChanged);
|
172 | this.inspectObject(null);
|
173 | }
|
174 | }
|
175 | }
|
176 |
|
177 | |
178 |
|
179 |
|
180 |
|
181 |
|
182 | get includesOwnProperties(): boolean { return this._includesOwnProperties; }
|
183 | set includesOwnProperties(val: boolean) {
|
184 | if (val !== this._includesOwnProperties) {
|
185 | this._includesOwnProperties = val;
|
186 | this.inspectObject();
|
187 | }
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 | get properties(): go.ObjectData { return this._properties; }
|
198 | set properties(val: go.ObjectData) {
|
199 | if (val !== this._properties) {
|
200 | this._properties = val;
|
201 | this.inspectObject();
|
202 | }
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | get propertyModified(): ((a: string, b: string, c: Inspector) => void) | null { return this._propertyModified; }
|
212 | set propertyModified(val: ((a: string, b: string, c: Inspector) => void) | null) {
|
213 | if (val !== this._propertyModified) {
|
214 | this._propertyModified = val;
|
215 | }
|
216 | }
|
217 |
|
218 | /**
|
219 | * Gets or sets whether the Inspector displays properties for multiple selected objects or just the first.
|
220 | *
|
221 | * The default value is false, meaning only the first item in the {@link Diagram#selection} is inspected.
|
222 | */
|
223 | get multipleSelection(): boolean { return this._multipleSelection; }
|
224 | set multipleSelection(val: boolean) {
|
225 | if (val !== this._multipleSelection) {
|
226 | this._multipleSelection = val;
|
227 | this.inspectObject();
|
228 | }
|
229 | }
|
230 |
|
231 | /**
|
232 | * Gets or sets whether the Inspector displays the union or intersection of properties for multiple selected objects.
|
233 | *
|
234 | * The default value is false, meaning the intersection of properties is inspected.
|
235 | */
|
236 | get showUnionProperties(): boolean { return this._showUnionProperties; }
|
237 | set showUnionProperties(val: boolean) {
|
238 | if (val !== this._showUnionProperties) {
|
239 | this._showUnionProperties = val;
|
240 | this.inspectObject();
|
241 | }
|
242 | }
|
243 |
|
244 | /**
|
245 | * Gets or sets how many objects will be displayed when {@link #multipleSelection} is true.
|
246 | *
|
247 | * The default value is 0, meaning all selected objects will be displayed for a given property.
|
248 | */
|
249 | get showLimit(): number { return this._showLimit; }
|
250 | set showLimit(val: number) {
|
251 | if (val !== this._showLimit) {
|
252 | this._showLimit = val;
|
253 | this.inspectObject();
|
254 | }
|
255 | }
|
256 |
|
257 | /**
|
258 | * This predicate function can be used as a value for the `show` option for properties.
|
259 | * When used, the property will only be shown when inspecting a {@link Node}.
|
260 | * @param {Part} part the Part being inspected
|
261 | * @return {boolean}
|
262 | */
|
263 | public static showIfNode(part: go.Part): boolean { return part instanceof go.Node; }
|
264 |
|
265 | /**
|
266 | * This predicate function can be used as a value for the `show` option for properties.
|
267 | * When used, the property will only be shown when inspecting a {@link Link}.
|
268 | * @param {Part} part the Part being inspected
|
269 | * @return {boolean}
|
270 | */
|
271 | public static showIfLink(part: go.Part): boolean { return part instanceof go.Link; }
|
272 |
|
273 | /**
|
274 | * This predicate function can be used as a value for the `show` option for properties.
|
275 | * When used, the property will only be shown when inspecting a {@link Group}.
|
276 | * @param {Part} part the Part being inspected
|
277 | * @return {boolean}
|
278 | */
|
279 | public static showIfGroup(part: go.Part): boolean { return part instanceof go.Group; }
|
280 |
|
281 | /**
|
282 | * This predicate function can be used as a value for the `show` option for properties.
|
283 | * When used, the property will only be shown if present.
|
284 | * Useful for properties such as `key`, which will be shown on Nodes and Groups, but normally not on Links
|
285 | * @param {Part|null} part the Part being inspected
|
286 | * @param {string} propname the property to check presence of
|
287 | * @return {boolean}
|
288 | */
|
289 | public static showIfPresent(data: go.Part | null, propname: string): boolean {
|
290 | if (data instanceof go.Part) data = data.data;
|
291 | return typeof data === 'object' && (data as any)[propname] !== undefined;
|
292 | }
|
293 |
|
294 | /**
|
295 | * Update the HTML state of this Inspector with the given object.
|
296 | *
|
297 | * If passed an object, the Inspector will inspect that object.
|
298 | * If passed null, this will do nothing.
|
299 | * If no parameter is supplied, the {@link #inspectedObject} will be set based on the value of {@link #inspectSelection}.
|
300 | * @param {Object=} object an optional argument, used when {@link #inspectSelection} is false to
|
301 | * set {@link #inspectedObject} and show and edit that object's properties.
|
302 | */
|
303 | public inspectObject(object?: go.ObjectData | null): void {
|
304 | let inspectedObject: go.ObjectData | null = null;
|
305 | let inspectedObjects: go.Set<go.ObjectData> | null = null;
|
306 | if (object === null) return;
|
307 | if (object === undefined) {
|
308 | if (this._inspectSelection) {
|
309 | if (this._multipleSelection) { // gets the selection if multiple selection is true
|
310 | inspectedObjects = this._diagram.selection;
|
311 | } else { // otherwise grab the first object
|
312 | inspectedObject = this._diagram.selection.first();
|
313 | }
|
314 | } else { // if there is a single inspected object
|
315 | inspectedObject = this._inspectedObject;
|
316 | }
|
317 | } else { // if object was passed in as a parameter
|
318 | inspectedObject = object;
|
319 | }
|
320 | if (!inspectedObjects && inspectedObject) {
|
321 | inspectedObjects = new go.Set<go.ObjectData>();
|
322 | inspectedObjects.add(inspectedObject);
|
323 | }
|
324 | if (!inspectedObjects || inspectedObjects.count < 1) { // if nothing is selected
|
325 | this.updateAllHTML();
|
326 | return;
|
327 | }
|
328 |
|
329 | if (inspectedObjects) {
|
330 | const mainDiv = this._div;
|
331 | mainDiv.innerHTML = '';
|
332 | const shared: go.Map<string, any> = new go.Map<string, any>(); // for properties that the nodes have in common
|
333 | const properties: go.Map<string, any> = new go.Map<string, any>(); // for adding properties
|
334 | const all: go.Map<string, any> = new go.Map<string, any>(); // used later to prevent changing properties when unneeded
|
335 | const it = inspectedObjects.iterator;
|
336 | let nodecount = 2;
|
337 | // Build table:
|
338 | const table = document.createElement('table');
|
339 | const tbody = document.createElement('tbody');
|
340 | this.inspectedProperties = {};
|
341 | this.tabIndex = 0;
|
342 | const declaredProperties = this._properties;
|
343 | it.next();
|
344 | inspectedObject = it.value;
|
345 | this._inspectedObject = inspectedObject;
|
346 | let data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
347 | if (data) { // initial pass to set shared and all
|
348 | // Go through all the properties passed in to the inspector and add them to the map, if appropriate:
|
349 | for (const name in declaredProperties) {
|
350 | const desc = declaredProperties[name];
|
351 | if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
352 | const val = this.findValue(name, desc, data);
|
353 | if (val === '' && this._properties[name] && this._properties[name].type === 'checkbox') {
|
354 | shared.add(name, false);
|
355 | all.add(name, false);
|
356 | } else {
|
357 | shared.add(name, val);
|
358 | all.add(name, val);
|
359 | }
|
360 | }
|
361 | // Go through all the properties on the model data and add them to the map, if appropriate:
|
362 | if (this._includesOwnProperties) {
|
363 | for (const k in data) {
|
364 | if (k === '__gohashid') continue; // skip internal GoJS hash property
|
365 | if (this.inspectedProperties[k]) continue; // already exists
|
366 | if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
367 | shared.add(k, data[k]);
|
368 | all.add(k, data[k]);
|
369 | }
|
370 | }
|
371 | }
|
372 | while (it.next() && (this._showLimit < 1 || nodecount <= this._showLimit)) { // grabs all the properties from the other selected objects
|
373 | properties.clear();
|
374 | inspectedObject = it.value;
|
375 | if (inspectedObject) {
|
376 | // use either the Part.data or the object itself (for model.modelData)
|
377 | data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
378 | if (data) {
|
379 | // Go through all the properties passed in to the inspector and add them to properties to add, if appropriate:
|
380 | for (const name in declaredProperties) {
|
381 | const desc = declaredProperties[name];
|
382 | if (!this.canShowProperty(name, desc, inspectedObject)) continue;
|
383 | const val = this.findValue(name, desc, data);
|
384 | if (val === '' && this._properties[name] && this._properties[name].type === 'checkbox') {
|
385 | properties.add(name, false);
|
386 | } else {
|
387 | properties.add(name, val);
|
388 | }
|
389 | }
|
390 | // Go through all the properties on the model data and add them to properties to add, if appropriate:
|
391 | if (this._includesOwnProperties) {
|
392 | for (const k in data) {
|
393 | if (k === '__gohashid') continue; // skip internal GoJS hash property
|
394 | if (this.inspectedProperties[k]) continue; // already exists
|
395 | if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
|
396 | properties.add(k, data[k]);
|
397 | }
|
398 | }
|
399 | }
|
400 | }
|
401 | if (!this._showUnionProperties) {
|
402 | // Cleans up shared map with properties that aren't shared between the selected objects
|
403 | // Also adds properties to the add and shared maps if applicable
|
404 | const addIt = shared.iterator;
|
405 | const toRemove: Array<string> = [];
|
406 | while (addIt.next()) {
|
407 | if (properties.has(addIt.key)) {
|
408 | let newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
409 | all.set(addIt.key, newVal);
|
410 | if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
411 | && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
412 | || !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
413 | newVal = shared.get(addIt.key) + '|' + properties.get(addIt.key);
|
414 | shared.set(addIt.key, newVal);
|
415 | }
|
416 | } else { // toRemove array since addIt is still iterating
|
417 | toRemove.push(addIt.key);
|
418 | }
|
419 | }
|
420 | for (let i = 0; i < toRemove.length; i++) { // removes anything that doesn't showUnionProperties
|
421 | shared.remove(toRemove[i]);
|
422 | all.remove(toRemove[i]);
|
423 | }
|
424 | } else {
|
425 | // Adds missing properties to all with the correct amount of seperators
|
426 | let addIt = properties.iterator;
|
427 | while (addIt.next()) {
|
428 | if (all.has(addIt.key)) {
|
429 | if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
430 | && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
431 | || !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
432 | const newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
|
433 | all.set(addIt.key, newVal);
|
434 | }
|
435 | } else {
|
436 | let newVal = '';
|
437 | for (let i = 0; i < nodecount - 1; i++) newVal += '|';
|
438 | newVal += properties.get(addIt.key);
|
439 | all.set(addIt.key, newVal);
|
440 | }
|
441 | }
|
442 | // Adds bars in case properties is not in all
|
443 | addIt = all.iterator;
|
444 | while (addIt.next()) {
|
445 | if (!properties.has(addIt.key)) {
|
446 | if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
|
447 | && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
|
448 | || !declaredProperties[addIt.key]) { // for non-string properties i.e color
|
449 | const newVal = all.get(addIt.key) + '|';
|
450 | all.set(addIt.key, newVal);
|
451 | }
|
452 | }
|
453 | }
|
454 | }
|
455 | nodecount++;
|
456 | }
|
457 | // builds the table property rows and sets multipleProperties to help with updateall
|
458 | let mapIt;
|
459 | if (!this._showUnionProperties) mapIt = shared.iterator;
|
460 | else mapIt = all.iterator;
|
461 | while (mapIt.next()) {
|
462 | tbody.appendChild(this.buildPropertyRow(mapIt.key, mapIt.value)); // shows the properties that are allowed
|
463 | }
|
464 | table.appendChild(tbody);
|
465 | mainDiv.appendChild(table);
|
466 | const allIt = all.iterator;
|
467 | while (allIt.next()) {
|
468 | this.multipleProperties[allIt.key] = allIt.value; // used for updateall to know which properties to change
|
469 | }
|
470 | }
|
471 | }
|
472 |
|
473 | /**
|
474 | * This predicate should be false if the given property should not be shown.
|
475 | * Normally it only checks the value of "show" on the property descriptor.
|
476 | *
|
477 | * The default value is true.
|
478 | * @param {string} propertyName the property name
|
479 | * @param {Object} propertyDesc the property descriptor
|
480 | * @param {Object} inspectedObject the data object
|
481 | * @return {boolean} whether a particular property should be shown in this Inspector
|
482 | */
|
483 | public canShowProperty(propertyName: string, propertyDesc: go.ObjectData, inspectedObject: go.ObjectData): boolean {
|
484 | const prop = propertyDesc as any;
|
485 | if (prop.show === false) return false;
|
486 | // if "show" is a predicate, make sure it passes or do not show this property
|
487 | if (typeof prop.show === 'function') return prop.show(inspectedObject, propertyName);
|
488 | return true;
|
489 | }
|
490 |
|
491 | /**
|
492 | * This predicate should be false if the given property should not be editable by the user.
|
493 | * Normally it only checks the value of "readOnly" on the property descriptor.
|
494 | *
|
495 | * The default value is true.
|
496 | * @param {string} propertyName the property name
|
497 | * @param {Object} propertyDesc the property descriptor
|
498 | * @param {Object} inspectedObject the data object
|
499 | * @return {boolean} whether a particular property should be shown in this Inspector
|
500 | */
|
501 | public canEditProperty(propertyName: string, propertyDesc: go.ObjectData, inspectedObject: go.ObjectData | null): boolean {
|
502 | if (this._diagram.isReadOnly || this._diagram.isModelReadOnly) return false;
|
503 | if (inspectedObject === null) return false;
|
504 | // assume property values that are functions of Objects cannot be edited
|
505 | const data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
|
506 | const valtype = typeof data[propertyName];
|
507 | if (valtype === 'function') return false;
|
508 | if (propertyDesc) {
|
509 | const prop = propertyDesc as any;
|
510 | if (prop.readOnly === true) return false;
|
511 | // if "readOnly" is a predicate, make sure it passes or do not show this property
|
512 | if (typeof prop.readOnly === 'function') return !prop.readOnly(inspectedObject, propertyName);
|
513 | }
|
514 | return true;
|
515 | }
|
516 |
|
517 | /**
|
518 | * @ignore
|
519 | * @param propName
|
520 | * @param propDesc
|
521 | * @param data
|
522 | */
|
523 | private findValue(propName: string, propDesc: any, data: any): any {
|
524 | let val = '';
|
525 | if (propDesc && propDesc.defaultValue !== undefined) val = propDesc.defaultValue;
|
526 | if (data[propName] !== undefined) val = data[propName];
|
527 | if (val === undefined) return '';
|
528 | return val;
|
529 | }
|
530 |
|
531 | /**
|
532 | * This sets `inspectedProperties[propertyName]` and creates the HTML table row for a given property:
|
533 | * ```html
|
534 | * <tr>
|
535 | * <td>propertyName</td>
|
536 | * <td><input value=propertyValue /></td>
|
537 | * </tr>
|
538 | * ```
|
539 | *
|
540 | * This method can be customized to change how an Inspector row is rendered.
|
541 | * @param {string} propertyName the property name
|
542 | * @param {*} propertyValue the property value
|
543 | * @return {HTMLTableRowElement} the table row
|
544 | */
|
545 | public buildPropertyRow(propertyName: string, propertyValue: any): HTMLTableRowElement {
|
546 | const tr = document.createElement('tr');
|
547 |
|
548 | const td1 = document.createElement('td');
|
549 | let displayName;
|
550 | if (this._properties[propertyName] && this._properties[propertyName].name !== undefined) { // name changes the dispaly name shown on inspector
|
551 | displayName = this._properties[propertyName].name;
|
552 | } else {
|
553 | displayName = propertyName;
|
554 | }
|
555 | td1.textContent = displayName;
|
556 |
|
557 | tr.appendChild(td1);
|
558 |
|
559 | const td2 = document.createElement('td');
|
560 | const decProp = this._properties[propertyName];
|
561 | let input: HTMLInputElement | HTMLSelectElement | null = null;
|
562 | const self = this;
|
563 | function updateall() {
|
564 | if (self._diagram.selection.count === 1 || !self.multipleSelection) {
|
565 | self.updateAllProperties();
|
566 | } else {
|
567 | self.updateAllObjectsProperties();
|
568 | }
|
569 | }
|
570 |
|
571 | if (decProp && decProp.type === 'select') {
|
572 | const inputs = input = document.createElement('select') as HTMLSelectElement;
|
573 | this.updateSelect(decProp, inputs, propertyName, propertyValue);
|
574 | inputs.addEventListener('change', updateall);
|
575 | } else {
|
576 | const inputi = input = document.createElement('input') as HTMLInputElement;
|
577 | if (inputi && inputi.setPointerCapture) {
|
578 | inputi.addEventListener("pointerdown", e => inputi.setPointerCapture(e.pointerId));
|
579 | }
|
580 | inputi.value = this.convertToString(propertyValue);
|
581 | if (decProp) {
|
582 | const t = decProp.type;
|
583 | if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
|
584 | t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
|
585 | t !== 'rect' && t !== 'spot' && t !== 'margin') {
|
586 | inputi.setAttribute('type', decProp.type);
|
587 | }
|
588 | if (decProp.type === 'color') {
|
589 | if (inputi.type === 'color') {
|
590 | inputi.value = this.convertToColor(propertyValue);
|
591 | // input.addEventListener('input', updateall); // removed with multi select
|
592 | inputi.addEventListener('change', updateall);
|
593 | }
|
594 | } if (decProp.type === 'checkbox') {
|
595 | inputi.checked = !!propertyValue;
|
596 | inputi.addEventListener('change', updateall);
|
597 | }
|
598 | }
|
599 | if (inputi.type !== 'color') inputi.addEventListener('blur', updateall);
|
600 | }
|
601 |
|
602 | if (input) {
|
603 | input.tabIndex = this.tabIndex++;
|
604 | input.disabled = !this.canEditProperty(propertyName, decProp, this._inspectedObject);
|
605 | td2.appendChild(input);
|
606 | }
|
607 | tr.appendChild(td2);
|
608 |
|
609 | this.inspectedProperties[propertyName] = input;
|
610 | return tr;
|
611 | }
|
612 |
|
613 | /**
|
614 | * @hidden @ignore
|
615 | * HTML5 color input will only take hex,
|
616 | * so let HTML5 canvas convert the color into hex format.
|
617 | * This converts "rgb(255, 0, 0)" into "#FF0000", etc.
|
618 | */
|
619 | public convertToColor(propertyValue: string): string {
|
620 | const ctx: CanvasRenderingContext2D | null = document.createElement('canvas').getContext('2d');
|
621 | if (ctx === null) return '#000000';
|
622 | ctx.fillStyle = propertyValue;
|
623 | return ctx.fillStyle;
|
624 | }
|
625 |
|
626 | /**
|
627 | * @hidden @ignore
|
628 | */
|
629 | public convertToArrayOfNumber(propertyValue: string): Array<number> | null {
|
630 | if (propertyValue === 'null') return null;
|
631 | const split = propertyValue.split(' ');
|
632 | const arr = [];
|
633 | for (let i = 0; i < split.length; i++) {
|
634 | const str = split[i];
|
635 | if (!str) continue;
|
636 | arr.push(parseFloat(str));
|
637 | }
|
638 | return arr;
|
639 | }
|
640 |
|
641 | /**
|
642 | * @hidden @ignore
|
643 | */
|
644 | public convertToString(x: any): string {
|
645 | if (x === undefined) return 'undefined';
|
646 | if (x === null) return 'null';
|
647 | if (x instanceof go.Point) return go.Point.stringify(x);
|
648 | if (x instanceof go.Size) return go.Size.stringify(x);
|
649 | if (x instanceof go.Rect) return go.Rect.stringify(x);
|
650 | if (x instanceof go.Spot) return go.Spot.stringify(x);
|
651 | if (x instanceof go.Margin) return go.Margin.stringify(x);
|
652 | if (x instanceof go.List) return this.convertToString(x.toArray());
|
653 | if (Array.isArray(x)) {
|
654 | let str = '';
|
655 | for (let i = 0; i < x.length; i++) {
|
656 | if (i > 0) str += ' ';
|
657 | const v = x[i];
|
658 | str += this.convertToString(v);
|
659 | }
|
660 | return str;
|
661 | }
|
662 | return x.toString();
|
663 | }
|
664 |
|
665 | /**
|
666 | * @hidden @ignore
|
667 | * Update all of the HTML in this Inspector.
|
668 | */
|
669 | public updateAllHTML(): void {
|
670 | const inspectedProps = this.inspectedProperties;
|
671 | const isPart = this._inspectedObject instanceof go.Part;
|
672 | const data = isPart ? (this._inspectedObject as any).data : this._inspectedObject;
|
673 | if (!data) { // clear out all of the fields
|
674 | for (const name in inspectedProps) {
|
675 | const input = inspectedProps[name];
|
676 | if (input instanceof HTMLSelectElement) {
|
677 | input.innerHTML = '';
|
678 | } else if (input.type === 'color') {
|
679 | input.value = '#000000';
|
680 | } else if (input.type === 'checkbox') {
|
681 | input.checked = false;
|
682 | } else {
|
683 | input.value = '';
|
684 | }
|
685 | }
|
686 | } else {
|
687 | for (const name in inspectedProps) {
|
688 | const input = inspectedProps[name];
|
689 | const propertyValue = data[name];
|
690 | if (input instanceof HTMLSelectElement) {
|
691 | const decProp = this._properties[name];
|
692 | this.updateSelect(decProp, input, name, propertyValue);
|
693 | } else if (input.type === 'color') {
|
694 | input.value = this.convertToColor(propertyValue);
|
695 | } else if (input.type === 'checkbox') {
|
696 | input.checked = !!propertyValue;
|
697 | } else {
|
698 | input.value = this.convertToString(propertyValue);
|
699 | }
|
700 | }
|
701 | }
|
702 | }
|
703 |
|
704 | /**
|
705 | * @hidden @ignore
|
706 | * Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
|
707 | */
|
708 | public updateSelect(decProp: any, select: HTMLSelectElement, propertyName: string, propertyValue: any): void {
|
709 | select.innerHTML = ''; // clear out anything that was there
|
710 | let choices = decProp.choices;
|
711 | if (typeof choices === 'function') choices = choices(this._inspectedObject, propertyName);
|
712 | if (!Array.isArray(choices)) choices = [];
|
713 | decProp.choicesArray = choices; // remember list of actual choice values (not strings)
|
714 | for (let i = 0; i < choices.length; i++) {
|
715 | const choice = choices[i];
|
716 | const opt = document.createElement('option');
|
717 | opt.text = this.convertToString(choice);
|
718 | select.add(opt);
|
719 | }
|
720 | select.value = this.convertToString(propertyValue);
|
721 | }
|
722 |
|
723 | private parseValue(decProp: any, value: any, input: any, oldval: any) {
|
724 | // If it's a boolean, or if its previous value was boolean,
|
725 | // parse the value to be a boolean and then update the input.value to match
|
726 | let type = '';
|
727 | if (decProp !== undefined && decProp.type !== undefined) {
|
728 | type = decProp.type;
|
729 | }
|
730 | if (type === '') {
|
731 | if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
|
732 | else if (typeof oldval === 'number') type = 'number';
|
733 | else if (oldval instanceof go.Point) type = 'point';
|
734 | else if (oldval instanceof go.Size) type = 'size';
|
735 | else if (oldval instanceof go.Rect) type = 'rect';
|
736 | else if (oldval instanceof go.Spot) type = 'spot';
|
737 | else if (oldval instanceof go.Margin) type = 'margin';
|
738 | }
|
739 |
|
740 | // convert to specific type, if needed
|
741 | switch (type) {
|
742 | case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
|
743 | case 'number': value = parseFloat(value); break;
|
744 | case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
|
745 | case 'point': value = go.Point.parse(value); break;
|
746 | case 'size': value = go.Size.parse(value); break;
|
747 | case 'rect': value = go.Rect.parse(value); break;
|
748 | case 'spot': value = go.Spot.parse(value); break;
|
749 | case 'margin': value = go.Margin.parse(value); break;
|
750 | case 'checkbox': value = input.checked; break;
|
751 | case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
|
752 | }
|
753 |
|
754 | return value;
|
755 | }
|
756 |
|
757 | /**
|
758 | * @hidden @ignore
|
759 | * Update all of the data properties of all the objects in {@link #inspectedObjects} according to the
|
760 | * current values held in the HTML input elements.
|
761 | */
|
762 | private updateAllObjectsProperties() {
|
763 | const inspectedProps = this.inspectedProperties;
|
764 | const diagram = this._diagram;
|
765 | diagram.startTransaction('set all properties');
|
766 | for (const name in inspectedProps) {
|
767 | const input = inspectedProps[name];
|
768 | let value = input.value;
|
769 | const arr1: Array<string> = value.split('|');
|
770 | let arr2: Array<string> = [];
|
771 | if (this.multipleProperties[name]) {
|
772 | // don't split if it is union and its checkbox type
|
773 | if (this._properties[name] && this._properties[name].type === 'checkbox' && this._showUnionProperties) {
|
774 | arr2.push(this.multipleProperties[name]);
|
775 | } else if (this._properties[name]) {
|
776 | arr2 = this.multipleProperties[name].toString().split('|');
|
777 | }
|
778 | }
|
779 | const it = diagram.selection.iterator;
|
780 | let change = false;
|
781 | if (this._properties[name] && this._properties[name].type === 'checkbox') change = true; // always change checkbox
|
782 | if (arr1.length < arr2.length
|
783 | && (!this._properties[name]
|
784 | || !(this._properties[name]
|
785 | && (this._properties[name].type === 'color' || this._properties[name].type === 'checkbox' || this._properties[name].type === 'choices')))) {
|
786 | change = true;
|
787 | } else { // standard detection in change in properties
|
788 | for (let j = 0; j < arr1.length && j < arr2.length; j++) {
|
789 | if (!(arr1[j] === arr2[j])
|
790 | && !(this._properties[name] && this._properties[name].type === 'color' && arr1[j].toLowerCase() === arr2[j].toLowerCase())) {
|
791 | change = true;
|
792 | }
|
793 | }
|
794 | }
|
795 | if (change) { // only change properties it needs to change instead all of them
|
796 | for (let i = 0; i < diagram.selection.count; i++) {
|
797 | it.next();
|
798 | const isPart = it.value instanceof go.Part;
|
799 | const data = isPart ? it.value.data : it.value;
|
800 |
|
801 | if (data) { // ignores the selected node if there is no data
|
802 | if (i < arr1.length) value = arr1[i];
|
803 | else value = arr1[0];
|
804 |
|
805 | // don't update "readOnly" data properties
|
806 | const decProp = this._properties[name];
|
807 | if (!this.canEditProperty(name, decProp, it.value)) continue;
|
808 |
|
809 | const oldval = data[name];
|
810 | value = this.parseValue(decProp, value, input, oldval);
|
811 |
|
812 | // in case parsed to be different, such as in the case of boolean values,
|
813 | // the value shown should match the actual value
|
814 | input.value = value;
|
815 |
|
816 | // modify the data object in an undo-able fashion
|
817 | diagram.model.setDataProperty(data, name, value);
|
818 |
|
819 | // notify any listener
|
820 | if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
821 | }
|
822 | }
|
823 | }
|
824 | }
|
825 | diagram.commitTransaction('set all properties');
|
826 | }
|
827 |
|
828 | /**
|
829 | * @hidden @ignore
|
830 | * Update all of the data properties of {@link #inspectedObject} according to the
|
831 | * current values held in the HTML input elements.
|
832 | */
|
833 | private updateAllProperties() {
|
834 | const inspectedProps = this.inspectedProperties;
|
835 | const diagram = this._diagram;
|
836 | const isPart = this.inspectedObject instanceof go.Part;
|
837 | const data = isPart ? (this.inspectedObject as any).data : this.inspectedObject;
|
838 | if (!data) return; // must not try to update data when there's no data!
|
839 |
|
840 | diagram.startTransaction('set all properties');
|
841 | for (const name in inspectedProps) {
|
842 | const input = inspectedProps[name];
|
843 | let value = input.value;
|
844 |
|
845 | // don't update "readOnly" data properties
|
846 | const decProp = this._properties[name];
|
847 | if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;
|
848 |
|
849 | const oldval = data[name];
|
850 | value = this.parseValue(decProp, value, input, oldval);
|
851 |
|
852 | // in case parsed to be different, such as in the case of boolean values,
|
853 | // the value shown should match the actual value
|
854 | input.value = value;
|
855 |
|
856 | // modify the data object in an undo-able fashion
|
857 | diagram.model.setDataProperty(data, name, value);
|
858 |
|
859 | // notify any listener
|
860 | if (this.propertyModified !== null) this.propertyModified(name, value, this);
|
861 | }
|
862 | diagram.commitTransaction('set all properties');
|
863 | }
|
864 | }
|
865 |
|
\ | No newline at end of file |