Skip to content

Commit 6dbaacc

Browse files
committed
Allow to customize created elements and links from MetadataProvider:
* 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); * Deprecate and hide by default "Edit" and "Delete" action buttons in `StandardEntity` expanded state (can be re-enabled by setting `showActions` prop to `true`).
1 parent 59fbfc2 commit 6dbaacc

9 files changed

Lines changed: 95 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
5151
* Change CSS class for default link template from `reactodia-default-link` to `reactodia-standard-link`.
5252
- Move "expand/collapse on double click" global element behavior to `StandardEntity` and `ClassicEntity` implementation only.
5353
- Add `setTemplateProperty()` utility function to easily set or unset template state property.
54+
- 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).
5455
- 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.
56+
- Deprecate and hide by default "Edit" and "Delete" action buttons in `StandardEntity` expanded state (can be re-enabled by setting `showActions` prop to `true`).
5557

5658
#### 🐛 Fixed
5759
- Fix `HaloLink` and visual authoring link path highlight being rendered on top on elements by placing it onto `overLinkGeometry` widget layer instead.

examples/resources/exampleMetadata.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,29 @@ export class ExampleMetadataProvider extends Reactodia.BaseMetadataProvider {
3333
.toString(16).substring(1);
3434
const typeLabel = Reactodia.Rdf.getLocalName(type) ?? 'Entity';
3535
return {
36-
id: `${type}_${random32BitDigits}`,
37-
types: [type],
38-
properties: {
39-
[Reactodia.rdfs.label]: [
40-
Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`)
41-
]
36+
data: {
37+
id: `${type}_${random32BitDigits}`,
38+
types: [type],
39+
properties: {
40+
[Reactodia.rdfs.label]: [
41+
Reactodia.Rdf.DefaultDataFactory.literal(`New ${typeLabel}`)
42+
]
43+
},
4244
},
45+
elementState: type === owl.Class ? {
46+
[Reactodia.TemplateProperties.Expanded]: true,
47+
} : undefined,
4348
};
4449
},
4550
createRelation: async (source, target, linkType, {signal}) => {
4651
await Reactodia.delay(SIMULATED_DELAY, {signal: signal});
4752
return {
48-
sourceId: source.id,
49-
targetId: target.id,
50-
linkTypeId: linkType,
51-
properties: {},
53+
data: {
54+
sourceId: source.id,
55+
targetId: target.id,
56+
linkTypeId: linkType,
57+
properties: {},
58+
},
5259
};
5360
},
5461
canConnect: async (source, target, linkType, {signal}) => {

src/data/metadataProvider.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ElementTemplateState, LinkTemplateState } from '../diagram/elements';
2+
13
import type * as Rdf from './rdf/rdfModel';
24
import type {
35
ElementModel, ElementTypeIri, LinkTypeIri, PropertyTypeIri, LinkModel,
@@ -20,14 +22,14 @@ export interface MetadataProvider {
2022
createEntity(
2123
type: ElementTypeIri,
2224
options: { readonly signal?: AbortSignal }
23-
): Promise<ElementModel>;
25+
): Promise<MetadataCreatedEntity>;
2426

2527
createRelation(
2628
source: ElementModel,
2729
target: ElementModel,
2830
linkType: LinkTypeIri,
2931
options: { readonly signal?: AbortSignal }
30-
): Promise<LinkModel>;
32+
): Promise<MetadataCreatedRelation>;
3133

3234
canConnect(
3335
source: ElementModel,
@@ -64,6 +66,16 @@ export interface MetadataProvider {
6466
): Promise<ReadonlySet<ElementTypeIri>>;
6567
}
6668

69+
export interface MetadataCreatedEntity {
70+
readonly data: ElementModel;
71+
readonly elementState?: ElementTemplateState;
72+
}
73+
74+
export interface MetadataCreatedRelation {
75+
readonly data: LinkModel;
76+
readonly linkState?: LinkTemplateState;
77+
}
78+
6779
export interface MetadataCanConnect {
6880
readonly targetTypes: ReadonlySet<ElementTypeIri>;
6981
readonly inLinks: ReadonlyArray<LinkTypeIri>;
@@ -142,14 +154,16 @@ export class BaseMetadataProvider implements MetadataProvider {
142154
async createEntity(
143155
type: ElementTypeIri,
144156
options: { readonly signal?: AbortSignal; }
145-
): Promise<ElementModel> {
157+
): Promise<MetadataCreatedEntity> {
146158
if (this.methods.createEntity) {
147159
return this.methods.createEntity(type, options);
148160
}
149161
return {
150-
id: '',
151-
types: [],
152-
properties: {},
162+
data: {
163+
id: '',
164+
types: [],
165+
properties: {},
166+
},
153167
};
154168
}
155169

@@ -158,15 +172,17 @@ export class BaseMetadataProvider implements MetadataProvider {
158172
target: ElementModel,
159173
linkType: LinkTypeIri,
160174
options: { readonly signal?: AbortSignal; }
161-
): Promise<LinkModel> {
175+
): Promise<MetadataCreatedRelation> {
162176
if (this.methods.createRelation) {
163177
return this.methods.createRelation(source, target, linkType, options);
164178
}
165179
return {
166-
linkTypeId: linkType,
167-
sourceId: source.id,
168-
targetId: target.id,
169-
properties: {},
180+
data: {
181+
linkTypeId: linkType,
182+
sourceId: source.id,
183+
targetId: target.id,
184+
properties: {},
185+
},
170186
};
171187
}
172188

src/editor/editorController.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,6 @@ export class EditorController {
312312
);
313313

314314
const element = model.createElement(data);
315-
// TODO: customize initial state via MetadataProvider
316-
element.setElementState({
317-
...element.elementState,
318-
[TemplateProperties.Expanded]: true,
319-
});
320315

321316
if (options.temporary) {
322317
this.setTemporaryState(

src/forms/elementTypeSelector.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { EventObserver } from '../coreUtils/events';
66
import { Translation } from '../coreUtils/i18n';
77

88
import { PlaceholderEntityType } from '../data/schema';
9-
import { ElementModel, ElementTypeIri } from '../data/model';
10-
import { DataProviderLookupItem } from '../data/dataProvider';
9+
import type { ElementModel, ElementTypeIri } from '../data/model';
10+
import type { DataProviderLookupItem } from '../data/dataProvider';
11+
import type { MetadataCreatedEntity } from '../data/metadataProvider';
1112

1213
import { HtmlSpinner } from '../diagram/spinner';
1314

@@ -28,7 +29,7 @@ export interface ElementTypeSelectorProps {
2829
}
2930

3031
export interface ElementValue {
31-
value: ElementModel;
32+
value: MetadataCreatedEntity;
3233
isNew: boolean;
3334
loading: boolean;
3435
error?: string;
@@ -188,13 +189,13 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
188189

189190
const {onChange, workspace: {editor: {metadataProvider}}} = this.props;
190191
const elementTypeIri: ElementTypeIri = (e.target as HTMLSelectElement).value;
191-
let elementModel: ElementModel | null;
192+
let createdEntity: MetadataCreatedEntity | null;
192193
try {
193-
elementModel = await mapAbortedToNull(
194+
createdEntity = await mapAbortedToNull(
194195
metadataProvider!.createEntity(elementTypeIri, {signal}),
195196
signal
196197
);
197-
if (elementModel === null) {
198+
if (createdEntity === null) {
198199
return;
199200
}
200201
} catch (err) {
@@ -205,7 +206,7 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
205206

206207
this.setState({elementTypesState: 'none'});
207208
onChange({
208-
value: elementModel,
209+
value: createdEntity,
209210
isNew: true,
210211
loading: false,
211212
});
@@ -228,7 +229,7 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
228229
private renderElementTypeSelector() {
229230
const {elementValue, workspace: {translation: t}} = this.props;
230231
const {elementTypes, elementTypesState} = this.state;
231-
const value = elementValue.value.types.length ? elementValue.value.types[0] : '';
232+
const value = elementValue.value.data.types.length ? elementValue.value.data.types[0] : '';
232233
if (elementTypesState !== 'none') {
233234
return (
234235
<HtmlSpinner width={20} height={20}
@@ -283,7 +284,7 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
283284
<ListElementView key={element.id}
284285
element={element}
285286
disabled={isAlreadyOnDiagram || !hasAppropriateType}
286-
selected={element.id === elementValue.value.id}
287+
selected={element.id === elementValue.value.data.id}
287288
onClick={(e, model) => void this.onSelectExistingItem(model)}
288289
/>
289290
);
@@ -299,12 +300,12 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
299300
this.loadingItemCancellation = new AbortController();
300301
const signal = this.loadingItemCancellation.signal;
301302

302-
onChange({value: data, isNew: false, loading: true});
303+
onChange({value: {data}, isNew: false, loading: true});
303304
const result = await model.dataProvider.elements({elementIds: [data.id]});
304305
if (signal.aborted) { return; }
305306

306307
const loadedModel = result.get(data.id)!;
307-
onChange({value: loadedModel, isNew: false, loading: false});
308+
onChange({value: {data: loadedModel}, isNew: false, loading: false});
308309
}
309310

310311
render() {

src/forms/findOrCreateEntityForm.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
4949
this.link = originalLink;
5050
this.state = {
5151
elementValue: {
52-
value: target.data,
52+
value: {data: target.data},
5353
isNew: initialTargetIsNew,
5454
loading: false,
5555
validated: true,
@@ -85,7 +85,7 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
8585
this.validationCancellation = new AbortController();
8686
const signal = this.validationCancellation.signal;
8787

88-
const validateElement = validateElementType(elementValue.value, this.context);
88+
const validateElement = validateElementType(elementValue.value.data, this.context);
8989
const validateLink = validateLinkType(
9090
dataFromExtendedLink(linkValue.link),
9191
originalLink.data,
@@ -126,7 +126,7 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
126126
link: {
127127
base: originalLink.data,
128128
source: source.data,
129-
target: newState.value,
129+
target: newState.value.data,
130130
direction: 'out',
131131
},
132132
validated: false,
@@ -198,7 +198,7 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
198198
editor.setTemporaryState(temporaryState);
199199
// Target IRI change may also update link data
200200
const batch = model.history.startBatch();
201-
batch.history.execute(changeEntityData(model, target.iri, elementValue.value));
201+
batch.history.execute(changeEntityData(model, target.iri, elementValue.value.data));
202202
batch.discard();
203203

204204
temporaryState = TemporaryState.addEntity(temporaryState, target.data);
@@ -222,7 +222,7 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
222222
if (!elementValue.isNew) {
223223
editor.setTemporaryState(TemporaryState.removeEntity(
224224
editor.temporaryState,
225-
elementValue.value
225+
elementValue.value.data
226226
));
227227
}
228228
editor.removeTemporaryCells([target, link]);
@@ -235,16 +235,12 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
235235

236236
if (elementValue.isNew) {
237237
model.addElement(target);
238-
// TODO: customize initial state via MetadataProvider
239-
target.setElementState({
240-
...target.elementState,
241-
[TemplateProperties.Expanded]: true,
242-
});
238+
target.setElementState(elementValue.value.elementState);
243239
editor.setAuthoringState(
244240
AuthoringState.addEntity(editor.authoringState, target.data)
245241
);
246242
} else {
247-
void model.requestLinks({addedElements: [elementValue.value.id]});
243+
void model.requestLinks({addedElements: [elementValue.value.data.id]});
248244
}
249245

250246
const linkBase = relationFromExtendedLink(linkValue.link, source, target);

src/templates/standardTemplate.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,17 @@ export const StandardTemplate: ElementTemplate = {
5353
*
5454
* @see {@link StandardEntity}
5555
*/
56-
export interface StandardEntityProps extends TemplateProps {}
56+
export interface StandardEntityProps extends TemplateProps {
57+
/**
58+
* When set to `true`, allows to edite or delete the entity
59+
* using corresponding buttons in the expanded state.
60+
*
61+
* @default false
62+
* @deprecated Edities can be editied or deleted via inline action decorators
63+
* when {@link VisualAuthoringProps.inlineEntityActions} is enabled.
64+
*/
65+
showActions?: boolean;
66+
}
5767

5868
const CLASS_NAME = 'reactodia-standard-template';
5969

@@ -67,14 +77,11 @@ const CLASS_NAME = 'reactodia-standard-template';
6777
* - {@link TemplateProperties.Expanded}
6878
* - {@link TemplateProperties.PinnedProperties}
6979
*
70-
* Entities can be edited or deleted using corresponding buttons
71-
* from the expanded state.
72-
*
7380
* @category Components
7481
* @see {@link StandardTemplate}
7582
*/
7683
export function StandardEntity(props: StandardEntityProps) {
77-
const {element, isExpanded, elementState} = props;
84+
const {showActions, element, isExpanded, elementState} = props;
7885
const workspace = useWorkspace();
7986
const {model, editor, translation: t, getElementTypeStyle} = workspace;
8087

@@ -226,7 +233,7 @@ export function StandardEntity(props: StandardEntityProps) {
226233
<div className={`${CLASS_NAME}__dropdown-content`}>
227234
{renderIri(data)}
228235
<PropertyList data={data} />
229-
{editor.inAuthoringMode ? <>
236+
{showActions && editor.inAuthoringMode ? <>
230237
<hr className={`${CLASS_NAME}__hr`}
231238
data-reactodia-no-export='true'
232239
/>
@@ -521,6 +528,9 @@ function PropertyList(props: {
521528
);
522529
}
523530

531+
/**
532+
* @deprecated
533+
*/
524534
function Actions(props: {
525535
target: Element;
526536
entityContext: AuthoredEntityContext;

src/widgets/classTree/classTree.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,17 +418,18 @@ class ClassTreeInner extends React.Component<ClassTreeInnerProps, State> {
418418
this.createElementCancellation = new AbortController();
419419
const signal = this.createElementCancellation.signal;
420420

421-
const elementModel = await mapAbortedToNull(
421+
const createdEntity = await mapAbortedToNull(
422422
editor.metadataProvider!.createEntity(elementType, {signal}),
423423
signal
424424
);
425-
if (elementModel === null) {
425+
if (createdEntity === null) {
426426
return;
427427
}
428428

429-
const element = editor.createEntity(elementModel);
430-
let createAt: [CanvasApi, Vector] | undefined;
429+
const element = editor.createEntity(createdEntity.data);
430+
element.setElementState(createdEntity.elementState);
431431

432+
let createAt: [CanvasApi, Vector] | undefined;
432433
if (dropEvent) {
433434
createAt = [dropEvent.source, dropEvent.position];
434435
} else {

0 commit comments

Comments
 (0)