diff --git a/CHANGELOG.md b/CHANGELOG.md index f5193234..d1dac178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p * Change CSS class for default link template from `reactodia-default-link` to `reactodia-standard-link`. - Move "expand/collapse on double click" global element behavior to `StandardEntity` and `ClassicEntity` implementation only. - Add `setTemplateProperty()` utility function to easily set or unset template state property. +- Change `MetadataProvider.{createEntity, createRelation}` to return result object with initial template state in addition to the data to customize the created cells (i.e. new elements can be expanded or collapsed). - Add `EditorController.applyAuthoringChanges()` method to apply current authoring changes to the diagram (i.e. change entity data, delete relations, etc) and reset the change state to be empty. +- Deprecate and hide by default "Edit" and "Delete" action buttons in `StandardEntity` expanded state (can be re-enabled by setting `showActions` prop to `true`). #### 🐛 Fixed - Fix `HaloLink` and visual authoring link path highlight being rendered on top on elements by placing it onto `overLinkGeometry` widget layer instead. diff --git a/examples/resources/exampleMetadata.ts b/examples/resources/exampleMetadata.ts index f7cddda9..b2a88ff2 100644 --- a/examples/resources/exampleMetadata.ts +++ b/examples/resources/exampleMetadata.ts @@ -33,22 +33,29 @@ export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider { .toString(16).substring(1); const typeLabel = Reactodia.Rdf.getLocalName(type) ?? 'Entity'; return { - id: `${type}_${random32BitDigits}`, - types: [type], - properties: { - [Reactodia.rdfs.label]: [ - Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`) - ] + data: { + id: `${type}_${random32BitDigits}`, + types: [type], + properties: { + [Reactodia.rdfs.label]: [ + Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`) + ] + }, }, + elementState: type === owl.Class ? { + [Reactodia.TemplateProperties.Expanded]: true, + } : undefined, }; }, createRelation: async (source, target, linkType, {signal}) => { await Reactodia.delay(SIMULATED_DELAY, {signal: signal}); return { - sourceId: source.id, - targetId: target.id, - linkTypeId: linkType, - properties: {}, + data: { + sourceId: source.id, + targetId: target.id, + linkTypeId: linkType, + properties: {}, + }, }; }, canConnect: async (source, target, linkType, {signal}) => { diff --git a/src/data/metadataProvider.ts b/src/data/metadataProvider.ts index 92c66a44..04c31804 100644 --- a/src/data/metadataProvider.ts +++ b/src/data/metadataProvider.ts @@ -1,3 +1,5 @@ +import type { ElementTemplateState, LinkTemplateState } from '../diagram/elements'; + import type * as Rdf from './rdf/rdfModel'; import type { ElementModel, ElementTypeIri, LinkTypeIri, PropertyTypeIri, LinkModel, @@ -20,14 +22,14 @@ export interface MetadataProvider { createEntity( type: ElementTypeIri, options: { readonly signal?: AbortSignal } - ): Promise; + ): Promise; createRelation( source: ElementModel, target: ElementModel, linkType: LinkTypeIri, options: { readonly signal?: AbortSignal } - ): Promise; + ): Promise; canConnect( source: ElementModel, @@ -64,6 +66,16 @@ export interface MetadataProvider { ): Promise>; } +export interface MetadataCreatedEntity { + readonly data: ElementModel; + readonly elementState?: ElementTemplateState; +} + +export interface MetadataCreatedRelation { + readonly data: LinkModel; + readonly linkState?: LinkTemplateState; +} + export interface MetadataCanConnect { readonly targetTypes: ReadonlySet; readonly inLinks: ReadonlyArray; @@ -142,14 +154,16 @@ export class BaseMetadataProvider implements MetadataProvider { async createEntity( type: ElementTypeIri, options: { readonly signal?: AbortSignal; } - ): Promise { + ): Promise { if (this.methods.createEntity) { return this.methods.createEntity(type, options); } return { - id: '', - types: [], - properties: {}, + data: { + id: '', + types: [], + properties: {}, + }, }; } @@ -158,15 +172,17 @@ export class BaseMetadataProvider implements MetadataProvider { target: ElementModel, linkType: LinkTypeIri, options: { readonly signal?: AbortSignal; } - ): Promise { + ): Promise { if (this.methods.createRelation) { return this.methods.createRelation(source, target, linkType, options); } return { - linkTypeId: linkType, - sourceId: source.id, - targetId: target.id, - properties: {}, + data: { + linkTypeId: linkType, + sourceId: source.id, + targetId: target.id, + properties: {}, + }, }; } diff --git a/src/editor/editorController.tsx b/src/editor/editorController.tsx index da51bb47..8a386ff6 100644 --- a/src/editor/editorController.tsx +++ b/src/editor/editorController.tsx @@ -312,11 +312,6 @@ export class EditorController { ); const element = model.createElement(data); - // TODO: customize initial state via MetadataProvider - element.setElementState({ - ...element.elementState, - [TemplateProperties.Expanded]: true, - }); if (options.temporary) { this.setTemporaryState( diff --git a/src/forms/elementTypeSelector.tsx b/src/forms/elementTypeSelector.tsx index eb74c7e0..5a910325 100644 --- a/src/forms/elementTypeSelector.tsx +++ b/src/forms/elementTypeSelector.tsx @@ -6,8 +6,9 @@ import { EventObserver } from '../coreUtils/events'; import { Translation } from '../coreUtils/i18n'; import { PlaceholderEntityType } from '../data/schema'; -import { ElementModel, ElementTypeIri } from '../data/model'; -import { DataProviderLookupItem } from '../data/dataProvider'; +import type { ElementModel, ElementTypeIri } from '../data/model'; +import type { DataProviderLookupItem } from '../data/dataProvider'; +import type { MetadataCreatedEntity } from '../data/metadataProvider'; import { HtmlSpinner } from '../diagram/spinner'; @@ -28,7 +29,7 @@ export interface ElementTypeSelectorProps { } export interface ElementValue { - value: ElementModel; + value: MetadataCreatedEntity; isNew: boolean; loading: boolean; error?: string; @@ -188,13 +189,13 @@ export class ElementTypeSelectorInner extends React.Component void this.onSelectExistingItem(model)} /> ); @@ -299,12 +300,12 @@ export class ElementTypeSelectorInner extends React.Component {renderIri(data)} - {editor.inAuthoringMode ? <> + {showActions && editor.inAuthoringMode ? <>
@@ -521,6 +528,9 @@ function PropertyList(props: { ); } +/** + * @deprecated + */ function Actions(props: { target: Element; entityContext: AuthoredEntityContext; diff --git a/src/widgets/classTree/classTree.tsx b/src/widgets/classTree/classTree.tsx index 5874fb85..f475b0e1 100644 --- a/src/widgets/classTree/classTree.tsx +++ b/src/widgets/classTree/classTree.tsx @@ -418,17 +418,18 @@ class ClassTreeInner extends React.Component { this.createElementCancellation = new AbortController(); const signal = this.createElementCancellation.signal; - const elementModel = await mapAbortedToNull( + const createdEntity = await mapAbortedToNull( editor.metadataProvider!.createEntity(elementType, {signal}), signal ); - if (elementModel === null) { + if (createdEntity === null) { return; } - const element = editor.createEntity(elementModel); - let createAt: [CanvasApi, Vector] | undefined; + const element = editor.createEntity(createdEntity.data); + element.setElementState(createdEntity.elementState); + let createAt: [CanvasApi, Vector] | undefined; if (dropEvent) { createAt = [dropEvent.source, dropEvent.position]; } else { diff --git a/src/widgets/visualAuthoring/dragEditLayer.tsx b/src/widgets/visualAuthoring/dragEditLayer.tsx index a55c081a..60ee310e 100644 --- a/src/widgets/visualAuthoring/dragEditLayer.tsx +++ b/src/widgets/visualAuthoring/dragEditLayer.tsx @@ -230,12 +230,15 @@ class EnitityDragConnection implements DragLinkConnection { elementTypes.add(typeIri); } } - const selectedType = elementTypes.size === 1 ? Array.from(elementTypes)[0] : PlaceholderEntityType; - const elementModel = await editor.metadataProvider.createEntity( + const selectedType = elementTypes.size === 1 + ? Array.from(elementTypes)[0] : PlaceholderEntityType; + const createdEntity = await editor.metadataProvider.createEntity( selectedType, {signal} ); - return editor.createEntity(elementModel, {temporary: true}); + const element = editor.createEntity(createdEntity.data, {temporary: true}); + element.setElementState(createdEntity.elementState); + return element; } private async createNewLink( @@ -285,7 +288,7 @@ class EnitityDragConnection implements DragLinkConnection { if (direction === 'in') { [effectiveSource, effectiveTarget] = [effectiveTarget, effectiveSource]; } - const data = await editor.metadataProvider.createRelation( + const createdRelation = await editor.metadataProvider.createRelation( effectiveSource.data, effectiveTarget.data, linkTypeIri, @@ -294,7 +297,8 @@ class EnitityDragConnection implements DragLinkConnection { const link = new RelationLink({ sourceId: effectiveSource.id, targetId: effectiveTarget.id, - data, + data: createdRelation.data, + linkState: createdRelation.linkState, }); const existingLink = model.findLink(link.typeId, link.sourceId, link.targetId); return existingLink instanceof RelationLink diff --git a/src/workspace.ts b/src/workspace.ts index 509f605e..be8ca085 100644 --- a/src/workspace.ts +++ b/src/workspace.ts @@ -22,8 +22,9 @@ export { Debouncer, animateInterval } from './coreUtils/scheduler'; export * from './data/dataProvider'; export * from './data/model'; export { - MetadataProvider, MetadataCanConnect, MetadataCanModifyEntity, MetadataCanModifyRelation, - MetadataEntityShape, MetadataRelationShape, MetadataPropertyShape, BaseMetadataProvider, + MetadataProvider, MetadataCreatedEntity, MetadataCreatedRelation, MetadataCanConnect, + MetadataCanModifyEntity, MetadataCanModifyRelation, MetadataEntityShape, + MetadataRelationShape, MetadataPropertyShape, BaseMetadataProvider, } from './data/metadataProvider'; export { ValidationProvider, ValidationEvent, ValidationResult, ValidatedElement, ValidatedLink,