diff --git a/services/madoc-ts/.eslintrc.js b/services/madoc-ts/.eslintrc.js index f2f1ba834..3401d7946 100644 --- a/services/madoc-ts/.eslintrc.js +++ b/services/madoc-ts/.eslintrc.js @@ -51,17 +51,17 @@ module.exports = { additionalHooks: '(useEventHandler)', }, ], - 'import/no-restricted-paths': [ - 2, - { - zones: [ - { - target: './src/!(frontend)/**/*', - from: './src/frontend/**/*', - }, - ], - }, - ], + // 'import/no-restricted-paths': [ + // 2, + // { + // zones: [ + // { + // target: './src/!(frontend)/**/*', + // from: './src/frontend/**/*', + // }, + // ], + // }, + // ], 'no-undef': OFF, 'no-use-before-define': OFF, 'react-hooks/rules-of-hooks': 'error', diff --git a/services/madoc-ts/.storybook/main.js b/services/madoc-ts/.storybook/main.js index 6fbd24cf2..68318cefb 100644 --- a/services/madoc-ts/.storybook/main.js +++ b/services/madoc-ts/.storybook/main.js @@ -1,5 +1,8 @@ module.exports = { - stories: ['../stories/**/*.stories.@(tsx|mdx)'], + stories: [ + '../stories/**/*.stories.@(tsx|mdx)', + '../src/**/*.stories.@(tsx|mdx)' + ], addons: [ '@storybook/preset-typescript', '@storybook/addon-knobs', diff --git a/services/madoc-ts/__tests__/capture-model-to-indexables.ts b/services/madoc-ts/__tests__/capture-model-to-indexables.ts index ee306383b..5b5514c23 100644 --- a/services/madoc-ts/__tests__/capture-model-to-indexables.ts +++ b/services/madoc-ts/__tests__/capture-model-to-indexables.ts @@ -1,5 +1,6 @@ -import { generateId, hydrateCompressedModel } from '@capture-models/helpers'; -import { CaptureModel } from '@capture-models/types'; +import { generateId } from '../src/frontend/shared/capture-models/helpers/generate-id'; +import { hydrateCompressedModel } from '../src/frontend/shared/capture-models/helpers/hydrate-compressed-model'; +import { CaptureModel } from '../src/frontend/shared/capture-models/types/capture-model'; import { captureModelToIndexables } from '../src/utility/capture-model-to-indexables'; describe('Capture model to indexables', () => { diff --git a/services/madoc-ts/__tests__/capture-models/document-to-default-structure.ts b/services/madoc-ts/__tests__/capture-models/document-to-default-structure.ts index dd553131a..848e25d2f 100644 --- a/services/madoc-ts/__tests__/capture-models/document-to-default-structure.ts +++ b/services/madoc-ts/__tests__/capture-models/document-to-default-structure.ts @@ -1,6 +1,6 @@ -import { captureModelShorthand } from '@capture-models/helpers'; -import { documentFragmentWrapper } from '../../src/frontend/shared/caputre-models/utility/document-fragment-wrapper'; -import { documentToDefaultStructure } from '../../src/frontend/shared/caputre-models/utility/document-to-default-structure'; +import { captureModelShorthand } from '../../src/frontend/shared/capture-models/helpers/capture-model-shorthand'; +import { documentFragmentWrapper } from '../../src/frontend/shared/capture-models/utility/document-fragment-wrapper'; +import { documentToDefaultStructure } from '../../src/frontend/shared/capture-models/utility/document-to-default-structure'; import { siteConfigurationModel } from '../../src/frontend/shared/configuration/site-config'; describe('document to default structure', () => { diff --git a/services/madoc-ts/__tests__/capture-models/editor/bugs/01-nuking-selector.test.ts b/services/madoc-ts/__tests__/capture-models/editor/bugs/01-nuking-selector.test.ts new file mode 100644 index 000000000..a71850d3f --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/editor/bugs/01-nuking-selector.test.ts @@ -0,0 +1,1855 @@ +/** + * @jest-environment node + */ + +import { hydrateRevisionStore } from '../../../../src/frontend/shared/capture-models/editor/stores/revisions/revisions-store'; +import { registerField } from '../../../../src/frontend/shared/capture-models/plugin-api/global-store'; + +registerField({ + label: 'Text field', + type: 'text-field', + description: 'Simple text field for plain text', + Component: undefined as any, + defaultValue: '', + allowMultiple: true, + defaultProps: {}, + Editor: undefined as any, + // Editor: TextFieldEditor, + TextPreview: undefined as any, +} as any); + +describe('1. Nuking selector bug', () => { + test('it should not revise the selector', () => { + const state = { + revisions: { + '9c2c6558-703d-4276-ac44-01c78e66ecef': { + captureModelId: 'fb97bec1-5ff4-4413-bdb2-96fff1b70ae3', + revision: { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + fields: ['regionOfInterest'], + approved: true, + structureId: '9c2c6558-703d-4276-ac44-01c78e66ecef', + label: 'Default', + }, + source: 'canonical', + document: { + id: '7d313298-e8d7-4ecf-a459-b6e9768013e3', + type: 'entity', + label: 'Untitled document', + properties: { + regionOfInterest: [ + { + id: 'e2ab2dd6-a80d-41ea-b289-38966eb092f5', + type: 'text-field', + label: 'regionOfInterest test test', + value: '', + description: 'test', + selector: { + id: '4d60c63c-d3e3-40e7-9243-a0b98a6daba6', + type: 'box-selector', + state: null, + }, + }, + ], + }, + }, + }, + '5b21c821-0600-4530-80bf-43fbe09e4469': { + captureModelId: 'fb97bec1-5ff4-4413-bdb2-96fff1b70ae3', + revision: { + structureId: '9c2c6558-703d-4276-ac44-01c78e66ecef', + approved: false, + label: 'Default', + id: '5b21c821-0600-4530-80bf-43fbe09e4469', + fields: ['regionOfInterest'], + status: 'draft', + revises: '9c2c6558-703d-4276-ac44-01c78e66ecef', + authors: ['urn:madoc:user:1'], + deletedFields: null, + }, + document: { + id: '7d313298-e8d7-4ecf-a459-b6e9768013e3', + type: 'entity', + label: 'Untitled document', + properties: { + regionOfInterest: [ + { + id: '05127e2f-4b7e-4dfe-8061-8ca6914b02ae', + type: 'text-field', + label: 'regionOfInterest test test', + value: 'test again', + revises: 'e2ab2dd6-a80d-41ea-b289-38966eb092f5', + description: 'test', + selector: { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + revision: '5b21c821-0600-4530-80bf-43fbe09e4469', + }, + ], + }, + }, + source: 'structure', + }, + }, + revisionEditMode: true, + currentRevisionId: '5b21c821-0600-4530-80bf-43fbe09e4469', + currentRevision: { + captureModelId: 'fb97bec1-5ff4-4413-bdb2-96fff1b70ae3', + revision: { + structureId: '9c2c6558-703d-4276-ac44-01c78e66ecef', + approved: false, + label: 'Default', + id: '5b21c821-0600-4530-80bf-43fbe09e4469', + fields: ['regionOfInterest'], + status: 'draft', + revises: '9c2c6558-703d-4276-ac44-01c78e66ecef', + authors: ['urn:madoc:user:1'], + deletedFields: null, + }, + document: { + id: '7d313298-e8d7-4ecf-a459-b6e9768013e3', + type: 'entity', + label: 'Untitled document', + properties: { + regionOfInterest: [ + { + id: '05127e2f-4b7e-4dfe-8061-8ca6914b02ae', + type: 'text-field', + label: 'regionOfInterest test test', + value: 'test again', + revises: 'e2ab2dd6-a80d-41ea-b289-38966eb092f5', + description: 'test', + selector: { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + revision: '5b21c821-0600-4530-80bf-43fbe09e4469', + }, + ], + }, + }, + source: 'structure', + }, + unsavedRevisionIds: [], + currentRevisionReadMode: false, + revisionSubtreePath: [], + revisionSelectedFieldProperty: null, + revisionSelectedFieldInstance: null, + revisionSubtree: { + id: '7d313298-e8d7-4ecf-a459-b6e9768013e3', + type: 'entity', + label: 'Untitled document', + properties: { + regionOfInterest: [ + { + id: '05127e2f-4b7e-4dfe-8061-8ca6914b02ae', + type: 'text-field', + label: 'regionOfInterest test test', + value: 'test again', + revises: 'e2ab2dd6-a80d-41ea-b289-38966eb092f5', + description: 'test', + selector: { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + revision: '5b21c821-0600-4530-80bf-43fbe09e4469', + }, + ], + }, + }, + revisionSubtreeFieldKeys: ['regionOfInterest'], + revisionSubtreeFields: [ + { + term: 'regionOfInterest', + value: [ + { + id: '05127e2f-4b7e-4dfe-8061-8ca6914b02ae', + type: 'text-field', + label: 'regionOfInterest test test', + value: 'test again', + revises: 'e2ab2dd6-a80d-41ea-b289-38966eb092f5', + description: 'test', + selector: { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + revision: '5b21c821-0600-4530-80bf-43fbe09e4469', + }, + ], + }, + ], + structure: { + id: '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + type: 'choice', + description: 'test', + label: 'First project', + items: [ + { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + type: 'model', + description: 'test test', + label: 'Default', + fields: ['regionOfInterest'], + instructions: 'test test test test', + }, + ], + }, + idStack: ['9c2c6558-703d-4276-ac44-01c78e66ecef'], + isThankYou: false, + isPreviewing: false, + structureMap: { + '9c2c6558-703d-4276-ac44-01c78e66ecef': { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + structure: { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + type: 'model', + description: 'test test', + label: 'Default', + fields: ['regionOfInterest'], + instructions: 'test test test test', + }, + path: [ + '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + '9c2c6558-703d-4276-ac44-01c78e66ecef', + ], + }, + '16e4789c-6866-4271-bdfd-ee5220e9ffeb': { + id: '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + structure: { + id: '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + type: 'choice', + description: 'test', + label: 'First project', + items: [ + { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + type: 'model', + description: 'test test', + label: 'Default', + fields: ['regionOfInterest'], + instructions: 'test test test test', + }, + ], + }, + path: ['16e4789c-6866-4271-bdfd-ee5220e9ffeb'], + }, + }, + currentStructureId: '9c2c6558-703d-4276-ac44-01c78e66ecef', + currentStructure: { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + type: 'model', + description: 'test test', + label: 'Default', + fields: ['regionOfInterest'], + instructions: 'test test test test', + }, + choiceStack: [ + { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + structure: { + id: '9c2c6558-703d-4276-ac44-01c78e66ecef', + type: 'model', + description: 'test test', + label: 'Default', + fields: ['regionOfInterest'], + instructions: 'test test test test', + }, + path: [ + '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + '16e4789c-6866-4271-bdfd-ee5220e9ffeb', + '9c2c6558-703d-4276-ac44-01c78e66ecef', + ], + }, + ], + selector: { + availableSelectors: [ + { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + ], + currentSelectorId: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + selectorPreviewData: {}, + currentSelectorState: null, + topLevelSelector: null, + visibleSelectorIds: [], + selectorPaths: { + 'bd8599f7-98f1-48ef-b926-61b53a7a9711': [['regionOfInterest', '05127e2f-4b7e-4dfe-8061-8ca6914b02ae']], + }, + }, + visibleCurrentLevelSelectorIds: ['bd8599f7-98f1-48ef-b926-61b53a7a9711'], + revisionAdjacentSubtreeFields: { + fields: [], + }, + visibleAdjacentSelectorIds: [], + resolvedSelectors: [ + { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + ], + visibleCurrentLevelSelectors: [ + { + id: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + type: 'box-selector', + state: null, + }, + ], + visibleAdjacentSelectors: [], + }; + + const store = hydrateRevisionStore(state); + + const actions = store.getActions(); + + actions.updateSelector({ + selectorId: 'bd8599f7-98f1-48ef-b926-61b53a7a9711', + state: { + x: 1826, + y: 307, + width: 431, + height: 272, + }, + }); + + const newState: any = store.getState(); + + expect(newState.currentRevision.document.properties.regionOfInterest[0].selector.revisedBy).not.toBeDefined(); + }); + + test('it should update the selector', () => { + const state = { + revisions: { + '501dbece-137d-4180-9bbd-c3ae022ad10b': { + captureModelId: '25024a58-8816-49e6-83de-dc4004325f45', + revision: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: true, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + }, + source: 'canonical', + document: { + id: 'feded624-8a56-4398-91ca-44adc7392c65', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + }, + ], + 'entity-multiple': [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-multiple': [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '39a18d5a-728e-422f-ad04-e80c3a280fae', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: 'e4399ba6-9adf-4cbd-bce5-80dd26ab094e', + type: 'box-selector', + state: null, + }, + }, + ], + }, + }, + }, + 'a85e9b77-1e29-49b0-b229-b1a771e75482': { + captureModelId: '25024a58-8816-49e6-83de-dc4004325f45', + revision: { + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + approved: false, + label: 'Default', + id: 'a85e9b77-1e29-49b0-b229-b1a771e75482', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + status: 'draft', + revises: '501dbece-137d-4180-9bbd-c3ae022ad10b', + authors: ['urn:madoc:user:3'], + deletedFields: null, + }, + document: { + id: 'feded624-8a56-4398-91ca-44adc7392c65', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + }, + ], + 'entity-multiple': [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-multiple': [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '94a7ee49-62a3-4fa6-86e5-09e52089ba8c', + type: 'text-field', + label: 'Field', + value: 'An example value with selector', + revises: '39a18d5a-728e-422f-ad04-e80c3a280fae', + selector: { + id: 'ed97dd6f-fdcf-427d-b501-068793ba6c44', + type: 'box-selector', + state: null, + }, + revision: 'a85e9b77-1e29-49b0-b229-b1a771e75482', + }, + ], + }, + }, + source: 'structure', + }, + 'dfff8c9e-28d1-4e39-bd5c-353621e50a81': { + captureModelId: '25024a58-8816-49e6-83de-dc4004325f45', + revision: { + id: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: false, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + revises: '501dbece-137d-4180-9bbd-c3ae022ad10b', + }, + document: { + id: 'feded624-8a56-4398-91ca-44adc7392c65', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '57ad1c35-8495-4716-b70a-fa6a18534e92', + type: 'text-field', + label: 'Field', + value: 'test', + selector: { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + revision: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + revises: '39a18d5a-728e-422f-ad04-e80c3a280fae', + }, + ], + }, + immutable: true, + }, + source: 'structure', + }, + }, + revisionEditMode: true, + currentRevisionId: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + currentRevision: { + captureModelId: '25024a58-8816-49e6-83de-dc4004325f45', + revision: { + id: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: false, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + revises: '501dbece-137d-4180-9bbd-c3ae022ad10b', + }, + document: { + id: 'feded624-8a56-4398-91ca-44adc7392c65', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '57ad1c35-8495-4716-b70a-fa6a18534e92', + type: 'text-field', + label: 'Field', + value: 'test', + selector: { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + revision: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + revises: '39a18d5a-728e-422f-ad04-e80c3a280fae', + }, + ], + }, + immutable: true, + }, + source: 'structure', + }, + unsavedRevisionIds: ['dfff8c9e-28d1-4e39-bd5c-353621e50a81'], + currentRevisionReadMode: false, + revisionSubtreePath: [], + revisionSelectedFieldProperty: null, + revisionSelectedFieldInstance: null, + revisionSubtree: { + id: 'feded624-8a56-4398-91ca-44adc7392c65', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '57ad1c35-8495-4716-b70a-fa6a18534e92', + type: 'text-field', + label: 'Field', + value: 'test', + selector: { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + revision: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + revises: '39a18d5a-728e-422f-ad04-e80c3a280fae', + }, + ], + }, + immutable: true, + }, + revisionSubtreeFieldKeys: ['entity-single', 'entity-multiple', 'field-multiple', 'field-single'], + revisionSubtreeFields: [ + { + term: 'entity-single', + value: [ + { + id: '9e475583-8fbc-48f6-ae08-250779822fce', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'ca7e58cf-a552-4ac5-abc3-3cc7934dbb66', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + }, + { + term: 'entity-multiple', + value: [ + { + id: '06726886-6244-4dc8-89b5-0eaf29e5c089', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'ba3e1cf6-6f65-4fd3-8cf3-80ec88453987', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + }, + { + term: 'field-multiple', + value: [ + { + id: '976510eb-e05b-4296-b164-44c42c2011fa', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + }, + ], + }, + { + term: 'field-single', + value: [ + { + id: '57ad1c35-8495-4716-b70a-fa6a18534e92', + type: 'text-field', + label: 'Field', + value: 'test', + selector: { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + revision: 'dfff8c9e-28d1-4e39-bd5c-353621e50a81', + revises: '39a18d5a-728e-422f-ad04-e80c3a280fae', + }, + ], + }, + ], + structure: { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + type: 'choice', + label: 'Project with selectors', + items: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + ], + }, + idStack: ['501dbece-137d-4180-9bbd-c3ae022ad10b'], + isThankYou: false, + isPreviewing: false, + structureMap: { + '501dbece-137d-4180-9bbd-c3ae022ad10b': { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + structure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + path: [ + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '501dbece-137d-4180-9bbd-c3ae022ad10b', + ], + }, + '707daf5f-648f-4ae2-a5b4-86ed3986c703': { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + structure: { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + type: 'choice', + label: 'Project with selectors', + items: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + ], + }, + path: ['707daf5f-648f-4ae2-a5b4-86ed3986c703'], + }, + }, + currentStructureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + currentStructure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + choiceStack: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + structure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + path: [ + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '501dbece-137d-4180-9bbd-c3ae022ad10b', + ], + }, + ], + selector: { + availableSelectors: [ + { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + { + id: 'e4399ba6-9adf-4cbd-bce5-80dd26ab094e', + type: 'box-selector', + state: null, + }, + { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + ], + currentSelectorId: '170081c2-1d55-40ec-b698-55476877806c', + selectorPreviewData: {}, + currentSelectorState: null, + topLevelSelector: null, + visibleSelectorIds: [], + selectorPaths: { + '80e91e80-3b5d-42b5-94ab-d1f51bf47142': [['entity-single', '9e475583-8fbc-48f6-ae08-250779822fce']], + '88e7437b-925a-460c-9674-ef980bff2051': [['entity-multiple', '06726886-6244-4dc8-89b5-0eaf29e5c089']], + '281db7c8-41fa-4553-9cac-2fa8246263bb': [['field-multiple', '976510eb-e05b-4296-b164-44c42c2011fa']], + 'e4399ba6-9adf-4cbd-bce5-80dd26ab094e': [['field-single', '39a18d5a-728e-422f-ad04-e80c3a280fae']], + '170081c2-1d55-40ec-b698-55476877806c': [['field-single', '39a18d5a-728e-422f-ad04-e80c3a280fae']], + }, + }, + visibleCurrentLevelSelectorIds: [ + '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + '88e7437b-925a-460c-9674-ef980bff2051', + '281db7c8-41fa-4553-9cac-2fa8246263bb', + '170081c2-1d55-40ec-b698-55476877806c', + ], + revisionAdjacentSubtreeFields: { + fields: [], + }, + visibleAdjacentSelectorIds: [], + resolvedSelectors: [ + { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + { + id: 'e4399ba6-9adf-4cbd-bce5-80dd26ab094e', + type: 'box-selector', + state: null, + }, + { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + ], + visibleCurrentLevelSelectors: [ + { + id: '80e91e80-3b5d-42b5-94ab-d1f51bf47142', + type: 'box-selector', + state: null, + }, + { + id: '88e7437b-925a-460c-9674-ef980bff2051', + type: 'box-selector', + state: null, + }, + { + id: '281db7c8-41fa-4553-9cac-2fa8246263bb', + type: 'box-selector', + state: null, + }, + { + id: '170081c2-1d55-40ec-b698-55476877806c', + type: 'box-selector', + state: null, + }, + ], + visibleAdjacentSelectors: [], + }; + + const store = hydrateRevisionStore(state); + + const actions = store.getActions(); + + actions.updateCurrentSelector({ + x: 1155, + y: 536, + width: 607, + height: 285, + } as any); + + const newState: any = store.getState(); + + expect(newState.currentRevision.document.properties['field-single'][0].selector.state).toEqual({ + x: 1155, + y: 536, + width: 607, + height: 285, + }); + }); + + test('it should not copy revised by when creating new entity instance', () => { + const state = { + revisions: { + '501dbece-137d-4180-9bbd-c3ae022ad10b': { + captureModelId: 'cf1063cd-22de-4c71-9cd3-410aee300e39', + revision: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: true, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + }, + source: 'canonical', + document: { + id: '31fd3e04-1fc2-4cab-b0c9-88b3e654fc06', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '46ffe8a3-1663-4457-bb20-c1ced15f8e68', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'af393ef9-6753-436d-bc18-c677e45c5b77', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + }, + ], + 'entity-multiple': [ + { + id: '4ff479d3-c6aa-4c73-97c6-49dc1a84f653', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: 'b81f9016-1ac4-412b-9067-49e9160c9e65', + type: 'text-field', + label: 'name', + value: '', + }, + ], + }, + selector: { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-multiple': [ + { + id: '7a9408ca-975c-4b0a-9d36-2b9a00907930', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '6222fcce-3dea-4a17-b8e0-b1ce466db3d9', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + }, + ], + }, + }, + }, + '36110f66-81fd-4567-8f85-8042658be21c': { + captureModelId: 'cf1063cd-22de-4c71-9cd3-410aee300e39', + revision: { + id: '36110f66-81fd-4567-8f85-8042658be21c', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: false, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + revises: '501dbece-137d-4180-9bbd-c3ae022ad10b', + }, + document: { + id: '31fd3e04-1fc2-4cab-b0c9-88b3e654fc06', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '46ffe8a3-1663-4457-bb20-c1ced15f8e68', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'af393ef9-6753-436d-bc18-c677e45c5b77', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '4ff479d3-c6aa-4c73-97c6-49dc1a84f653', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: '74fff33d-911f-4490-8f2f-911c91d76998', + type: 'text-field', + label: 'name', + value: 'entity 1', + revision: '36110f66-81fd-4567-8f85-8042658be21c', + revises: 'b81f9016-1ac4-412b-9067-49e9160c9e65', + }, + ], + }, + selector: { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '7a9408ca-975c-4b0a-9d36-2b9a00907930', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '6222fcce-3dea-4a17-b8e0-b1ce466db3d9', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + }, + ], + }, + immutable: true, + }, + source: 'structure', + }, + }, + revisionEditMode: true, + currentRevisionId: '36110f66-81fd-4567-8f85-8042658be21c', + currentRevision: { + captureModelId: 'cf1063cd-22de-4c71-9cd3-410aee300e39', + revision: { + id: '36110f66-81fd-4567-8f85-8042658be21c', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + approved: false, + structureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + label: 'Default', + revises: '501dbece-137d-4180-9bbd-c3ae022ad10b', + }, + document: { + id: '31fd3e04-1fc2-4cab-b0c9-88b3e654fc06', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '46ffe8a3-1663-4457-bb20-c1ced15f8e68', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'af393ef9-6753-436d-bc18-c677e45c5b77', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '4ff479d3-c6aa-4c73-97c6-49dc1a84f653', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: '74fff33d-911f-4490-8f2f-911c91d76998', + type: 'text-field', + label: 'name', + value: 'entity 1', + revision: '36110f66-81fd-4567-8f85-8042658be21c', + revises: 'b81f9016-1ac4-412b-9067-49e9160c9e65', + }, + ], + }, + selector: { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '7a9408ca-975c-4b0a-9d36-2b9a00907930', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '6222fcce-3dea-4a17-b8e0-b1ce466db3d9', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + }, + ], + }, + immutable: true, + }, + source: 'structure', + }, + unsavedRevisionIds: ['36110f66-81fd-4567-8f85-8042658be21c'], + currentRevisionReadMode: false, + revisionSubtreePath: [], + revisionSelectedFieldProperty: null, + revisionSelectedFieldInstance: null, + revisionSubtree: { + id: '31fd3e04-1fc2-4cab-b0c9-88b3e654fc06', + type: 'entity', + label: 'Project with selectors', + properties: { + 'entity-single': [ + { + id: '46ffe8a3-1663-4457-bb20-c1ced15f8e68', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'af393ef9-6753-436d-bc18-c677e45c5b77', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + 'entity-multiple': [ + { + id: '4ff479d3-c6aa-4c73-97c6-49dc1a84f653', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: '74fff33d-911f-4490-8f2f-911c91d76998', + type: 'text-field', + label: 'name', + value: 'entity 1', + revision: '36110f66-81fd-4567-8f85-8042658be21c', + revises: 'b81f9016-1ac4-412b-9067-49e9160c9e65', + }, + ], + }, + selector: { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + immutable: true, + }, + ], + 'field-multiple': [ + { + id: '7a9408ca-975c-4b0a-9d36-2b9a00907930', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + }, + ], + 'field-single': [ + { + id: '6222fcce-3dea-4a17-b8e0-b1ce466db3d9', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + }, + ], + }, + immutable: true, + }, + revisionSubtreeFieldKeys: ['entity-single', 'entity-multiple', 'field-multiple', 'field-single'], + revisionSubtreeFields: [ + { + term: 'entity-single', + value: [ + { + id: '46ffe8a3-1663-4457-bb20-c1ced15f8e68', + type: 'entity', + label: 'entity', + labelledBy: 'test', + properties: { + test: [ + { + id: 'af393ef9-6753-436d-bc18-c677e45c5b77', + type: 'text-field', + label: 'Test', + value: '', + }, + ], + }, + selector: { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + immutable: true, + }, + ], + }, + { + term: 'entity-multiple', + value: [ + { + id: '4ff479d3-c6aa-4c73-97c6-49dc1a84f653', + type: 'entity', + label: 'Entity multiple', + allowMultiple: true, + properties: { + name: [ + { + id: '74fff33d-911f-4490-8f2f-911c91d76998', + type: 'text-field', + label: 'name', + value: 'entity 1', + revision: '36110f66-81fd-4567-8f85-8042658be21c', + revises: 'b81f9016-1ac4-412b-9067-49e9160c9e65', + }, + ], + }, + selector: { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + immutable: true, + }, + ], + }, + { + term: 'field-multiple', + value: [ + { + id: '7a9408ca-975c-4b0a-9d36-2b9a00907930', + type: 'text-field', + label: 'Field multiple', + value: '', + allowMultiple: true, + selector: { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + }, + ], + }, + { + term: 'field-single', + value: [ + { + id: '6222fcce-3dea-4a17-b8e0-b1ce466db3d9', + type: 'text-field', + label: 'Field', + value: '', + selector: { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + }, + ], + }, + ], + structure: { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + type: 'choice', + label: 'Project with selectors', + items: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + ], + }, + idStack: ['501dbece-137d-4180-9bbd-c3ae022ad10b'], + isThankYou: false, + isPreviewing: false, + structureMap: { + '501dbece-137d-4180-9bbd-c3ae022ad10b': { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + structure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + path: [ + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '501dbece-137d-4180-9bbd-c3ae022ad10b', + ], + }, + '707daf5f-648f-4ae2-a5b4-86ed3986c703': { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + structure: { + id: '707daf5f-648f-4ae2-a5b4-86ed3986c703', + type: 'choice', + label: 'Project with selectors', + items: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + ], + }, + path: ['707daf5f-648f-4ae2-a5b4-86ed3986c703'], + }, + }, + currentStructureId: '501dbece-137d-4180-9bbd-c3ae022ad10b', + currentStructure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + choiceStack: [ + { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + structure: { + id: '501dbece-137d-4180-9bbd-c3ae022ad10b', + type: 'model', + label: 'Default', + fields: [['entity-single', ['test']], ['entity-multiple', ['name']], 'field-multiple', 'field-single'], + }, + path: [ + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '707daf5f-648f-4ae2-a5b4-86ed3986c703', + '501dbece-137d-4180-9bbd-c3ae022ad10b', + ], + }, + ], + selector: { + availableSelectors: [ + { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: null, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + ], + currentSelectorId: null, + selectorPreviewData: { + '147c59cc-589f-46f6-84f0-e7a446fdfe9a': + 'https://view.nls.uk/iiif/7443/74438658.5/680,213,466,268/256,/0/default.jpg', + }, + currentSelectorState: null, + topLevelSelector: null, + visibleSelectorIds: [], + selectorPaths: { + '11e0389a-d697-4865-91b3-c79b9f18e108': [['entity-single', '46ffe8a3-1663-4457-bb20-c1ced15f8e68']], + '147c59cc-589f-46f6-84f0-e7a446fdfe9a': [['entity-multiple', '4ff479d3-c6aa-4c73-97c6-49dc1a84f653']], + 'f5f5e733-ac67-4298-9be9-a0478223eb11': [['field-multiple', '7a9408ca-975c-4b0a-9d36-2b9a00907930']], + '710cd08d-9925-4732-b726-2cdae56c11df': [['field-single', '6222fcce-3dea-4a17-b8e0-b1ce466db3d9']], + }, + }, + visibleCurrentLevelSelectorIds: [ + '11e0389a-d697-4865-91b3-c79b9f18e108', + '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + 'f5f5e733-ac67-4298-9be9-a0478223eb11', + '710cd08d-9925-4732-b726-2cdae56c11df', + ], + revisionAdjacentSubtreeFields: { + fields: [], + }, + visibleAdjacentSelectorIds: [], + resolvedSelectors: [ + { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + ], + visibleCurrentLevelSelectors: [ + { + id: '11e0389a-d697-4865-91b3-c79b9f18e108', + type: 'box-selector', + state: null, + }, + { + id: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisedBy: [ + { + id: '3ba99318-dda3-4be4-894c-153f0809cd3f', + type: 'box-selector', + state: { + x: 680, + y: 213, + width: 466, + height: 268, + }, + revisionId: '36110f66-81fd-4567-8f85-8042658be21c', + revises: '147c59cc-589f-46f6-84f0-e7a446fdfe9a', + }, + ], + }, + { + id: 'f5f5e733-ac67-4298-9be9-a0478223eb11', + type: 'box-selector', + state: null, + }, + { + id: '710cd08d-9925-4732-b726-2cdae56c11df', + type: 'box-selector', + state: null, + }, + ], + visibleAdjacentSelectors: [], + }; + + const store = hydrateRevisionStore(state); + + const actions = store.getActions(); + + actions.createNewEntityInstance({ + property: 'entity-multiple', + path: [], + } as any); + + const newState: any = store.getState(); + + expect(newState.currentRevision.document.properties['entity-multiple'][1].selector.revisedBy).not.toBeDefined(); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/editor/context-editor.test.ts b/services/madoc-ts/__tests__/capture-models/editor/context-editor.test.ts new file mode 100644 index 000000000..56982d30c --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/editor/context-editor.test.ts @@ -0,0 +1,365 @@ +/** + * @jest-environment jsdom + */ + +import { + addContext, + addDefaultContext, + removeContext, + removeDefaultContext, +} from '../../../src/frontend/shared/capture-models/editor/core/context-editor'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +describe('document editor', () => { + const emptyModel: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + describe('addDefaultContext', () => { + test('add context to empty model', () => { + const result = addDefaultContext(emptyModel, 'http://example.org/context'); + + expect(result).toEqual({ + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': 'http://example.org/context', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }); + }); + + test('it throws when default context already exists', () => { + const result = addDefaultContext(emptyModel, 'http://example.org/context/1'); + + expect(() => addDefaultContext(result, 'http://example.org/context/2')).toThrowErrorMatchingInlineSnapshot( + `"Cannot add default context, context already exists"` + ); + }); + + test('it throws when default context already exists (2)', () => { + const result: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { '@vocab': 'http://example.org/context/1' }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + expect(() => addDefaultContext(result, 'http://example.org/context/2')).toThrowErrorMatchingInlineSnapshot( + `"Cannot add default context, context already exists"` + ); + }); + + test('it allows the same default context to be added', () => { + const result = addDefaultContext(emptyModel, 'http://example.org/context/1'); + + expect(addDefaultContext(result, 'http://example.org/context/1')).toEqual({ + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { '@vocab': 'http://example.org/context/1' }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }); + }); + + test('it allows the same default context to be added (using @vocab)', () => { + const result: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { '@vocab': 'http://example.org/context/1' }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + expect(addDefaultContext(result, 'http://example.org/context/1')).toEqual({ + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { '@vocab': 'http://example.org/context/1' }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }); + }); + }); + + describe('addContext', () => { + test('you can add context to empty model', () => { + const result = addContext(emptyModel, 'http://example.org/context/3', 'c1'); + + expect(result.document['@context']).toEqual({ + c1: 'http://example.org/context/3', + }); + }); + + test('you can add multiple contexts', () => { + const c1 = addContext(emptyModel, 'http://example.org/context/1', 'c1'); + + const c2 = addContext(c1, 'http://example.org/context/2', 'c2'); + + expect(c2.document['@context']).toEqual({ + c1: 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }); + }); + + test('it throws if you add the same alias twice', () => { + const c1 = addContext(emptyModel, 'http://example.org/context/1', 'c1'); + + expect(() => addContext(c1, 'http://example.org/context/2', 'c1')).toThrowErrorMatchingInlineSnapshot( + `"Cannot add context c1, context already exists (http://example.org/context/1)"` + ); + }); + + test('it can add when default context exists', () => { + const c1 = addDefaultContext(emptyModel, 'http://example.org/context/1'); + + const c2 = addContext(c1, 'http://example.org/context/2', 'c2'); + + expect(c2.document['@context']).toEqual({ + '@vocab': 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }); + }); + + test('it can add when default context exists (string)', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': 'http://example.org/context/1', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + const c2 = addContext(c1, 'http://example.org/context/2', 'c2'); + + expect(c2.document['@context']).toEqual({ + '@vocab': 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }); + }); + }); + + describe('removeContext', () => { + test('removing non-existent context', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { + '@vocab': 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + expect(removeContext(c1, 'c1').document['@context']).toEqual({ + '@vocab': 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }); + }); + + test('removing non-existent context (string)', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': 'http://example.org/context/1', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + expect(removeContext(c1, 'c1').document['@context']).toEqual('http://example.org/context/1'); + }); + + test('removing non-existent context (undefined)', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + + expect(removeContext(c1, 'c1').document['@context']).not.toBeDefined(); + }); + + test('removing existing context', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { + '@vocab': 'http://example.org/context/1', + c2: 'http://example.org/context/2', + }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + expect(removeContext(c1, 'c2').document['@context']).toEqual({ + '@vocab': 'http://example.org/context/1', + }); + }); + + test('removing last context', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { + c2: 'http://example.org/context/2', + }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + expect(removeContext(c1, 'c2').document['@context']).toEqual({}); + }); + }); + + describe('removeDefaultContext', () => { + test('remove default context', () => { + const c1: CaptureModel = { + structure: { + id: '1', + label: '', + type: 'model', + fields: [], + }, + document: { + id: '2', + '@context': { + '@vocab': 'http://example.org/context/2', + c1: 'http://example.org/context/1', + }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + expect(removeDefaultContext(c1).document['@context']).toEqual({ + c1: 'http://example.org/context/1', + }); + }); + + test('remove default context (string)', () => { + const c1: CaptureModel = { + structure: { id: '1', label: '', type: 'model', fields: [] }, + document: { + id: '2', + '@context': 'http://example.org/context/2', + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + expect(removeDefaultContext(c1).document['@context']).not.toBeDefined(); + }); + + test('remove default context (non-existent)', () => { + const c1: CaptureModel = { + structure: { id: '1', label: '', type: 'model', fields: [] }, + document: { + id: '2', + '@context': { c2: 'http://example.org/context/2' }, + type: 'entity', + label: 'untitled entity', + properties: {}, + }, + }; + expect(removeDefaultContext(c1)).toEqual(c1); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/editor/current-form.test.ts b/services/madoc-ts/__tests__/capture-models/editor/current-form.test.ts new file mode 100644 index 000000000..04b5e5c87 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/editor/current-form.test.ts @@ -0,0 +1,162 @@ +import { createFormFieldReducer } from '../../../src/frontend/shared/capture-models/editor/core/current-form'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +describe('current form', () => { + describe('createFormFieldReducer', () => { + const doc: CaptureModel = require('../../../fixtures/simple.json'); + test('simple flat fields', () => { + const reducer = createFormFieldReducer(doc.document); + + expect(['name', 'description'].reduce(reducer, [])).toMatchInlineSnapshot(` + Array [ + Object { + "list": Array [ + Object { + "label": "Enter the name of the book", + "selector": Object { + "state": null, + "type": "box-selector", + }, + "term": "name", + "type": "text-field", + "value": "The Hitchhiker's Guide to the Galaxy", + }, + ], + "type": "fields", + }, + Object { + "list": Array [ + Object { + "label": "Enter a description of the book", + "term": "description", + "type": "text-field", + "value": "The Hitchhiker's Guide to the Galaxy is the first of five books in the Hitchhiker's Guide to the Galaxy comedy science fiction \\"trilogy\\" by Douglas Adams. The novel is an adaptation of the first four parts of Adams' radio series of the same name.", + }, + ], + "type": "fields", + }, + ] + `); + }); + + test('nested model', () => { + const reducer = createFormFieldReducer(doc.document); + + // @ts-ignore + expect(['name', ['review', ['name', 'reviewBody']]].reduce(reducer, [])).toMatchInlineSnapshot(` + Array [ + Object { + "list": Array [ + Object { + "label": "Enter the name of the book", + "selector": Object { + "state": null, + "type": "box-selector", + }, + "term": "name", + "type": "text-field", + "value": "The Hitchhiker's Guide to the Galaxy", + }, + ], + "type": "fields", + }, + Object { + "list": Array [ + Object { + "conformsTo": "Review", + "fields": Array [ + Object { + "list": Array [ + Object { + "label": "Short name of your review", + "term": "name", + "type": "text-field", + "value": "A masterpiece of literature", + }, + ], + "type": "fields", + }, + Object { + "list": Array [ + Object { + "label": "Write your review", + "term": "reviewBody", + "type": "text-field", + "value": "Very simply, the book is one of the funniest SF spoofs ever written, with hyperbolic ideas folding in on themselves", + }, + ], + "type": "fields", + }, + ], + "label": "Review", + "term": "review", + "type": "entity", + }, + Object { + "conformsTo": "Review", + "fields": Array [ + Object { + "list": Array [ + Object { + "label": "Short name of your review", + "term": "name", + "type": "text-field", + "value": "", + }, + ], + "type": "fields", + }, + Object { + "list": Array [ + Object { + "label": "Write your review", + "term": "reviewBody", + "type": "text-field", + "value": "", + }, + ], + "type": "fields", + }, + ], + "label": "Review", + "term": "review", + "type": "entity", + }, + Object { + "conformsTo": "Review", + "fields": Array [ + Object { + "list": Array [ + Object { + "label": "Short name of your review", + "term": "name", + "type": "text-field", + "value": "A great book", + }, + ], + "type": "fields", + }, + Object { + "list": Array [ + Object { + "label": "Write your review", + "term": "reviewBody", + "type": "text-field", + "value": "It's very great", + }, + ], + "type": "fields", + }, + ], + "label": "Review", + "term": "review", + "type": "entity", + }, + ], + "type": "documents", + }, + ] + `); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/editor/revision-store.test.ts b/services/madoc-ts/__tests__/capture-models/editor/revision-store.test.ts new file mode 100644 index 000000000..106a4ebd5 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/editor/revision-store.test.ts @@ -0,0 +1,922 @@ +/** + * @jest-environment node + */ + +import { createRevisionStore } from '../../../src/frontend/shared/capture-models/editor/stores/revisions/revisions-store'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; +import { StructureType } from '../../../src/frontend/shared/capture-models/types/utility'; + +registerField({ + label: 'Text field', + type: 'text-field', + description: 'Simple text field for plain text', + Component: undefined as any, + defaultValue: '', + allowMultiple: true, + defaultProps: {}, + Editor: undefined as any, + // Editor: TextFieldEditor, + TextPreview: undefined as any, +} as any); + +const models: () => any[] = () => [ + require('../../../fixtures/03-revisions/01-single-field-with-revision.json'), + require('../../../fixtures/03-revisions/02-single-field-with-multiple-revisions.json'), + require('../../../fixtures/03-revisions/03-nested-revision.json'), + require('../../../fixtures/03-revisions/04-dual-transcription.json'), + require('../../../fixtures/03-revisions/05-allow-multiple-transcriptions.json'), + require('../../../fixtures/04-selectors/01-simple-selector.json'), + require('../../../fixtures/04-selectors/02-multiple-selectors.json'), + require('../../../fixtures/04-selectors/03-nested-selector.json'), + require('../../../fixtures/04-selectors/08-hocr-output.json'), +]; + +describe('Revision store', () => { + describe('reading computed revision', () => { + test('revision exists', () => { + const store = createRevisionStore({ + captureModel: models()[0], + initialRevision: '7c26cf57-5950-4849-b533-11e0ee4afa4b', + }); + + expect(store.getState().currentRevisionId).toEqual('7c26cf57-5950-4849-b533-11e0ee4afa4b'); + + expect(store.getState().currentRevision).toMatchInlineSnapshot(` + Object { + "captureModelId": "b329e009-1c8a-4bed-bfde-c2a587a22f97", + "document": Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "label": "Simple document", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "eafb62d7-71b7-47bd-b887-def8655d8d2a", + "label": "Name", + "revision": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "type": "text-field", + "value": "Some value that was submitted", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "name", + ], + "id": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "structureId": "31b27c9b-2388-47df-b6f4-73fb4878c1fa", + }, + "source": "structure", + } + `); + }); + + test('revision does not exist', () => { + const store = createRevisionStore({ captureModel: models()[0] }); + + expect(store.getState().currentRevision).toBeNull(); + }); + }); + + describe('choosing selectors', () => { + const selectorStore = (revision?: boolean, num = 5) => { + const mod: CaptureModel = models()[num]; + const revId = (mod.structure as StructureType<'choice'>).items[0].id; + const store = createRevisionStore({ + captureModel: models()[num], + initialRevision: revision ? revId : undefined, + }); + + return [store, store.getActions(), revId] as const; + }; + + test('selectors are available on model', () => { + const [store, { selectRevision }, revId] = selectorStore(); + expect(store.getState().selector.availableSelectors).toEqual([]); + selectRevision({ revisionId: revId }); + expect(store.getState().selector.availableSelectors).toEqual([ + { id: '0c15c2b8-48e9-4c83-b77e-054cd8215f93', state: null, type: 'box-selector' }, + ]); + }); + + test('can choose a selector that exists', () => { + const [store, { chooseSelector }] = selectorStore(true); + chooseSelector({ selectorId: '0c15c2b8-48e9-4c83-b77e-054cd8215f93' }); + expect(store.getState().selector.currentSelectorId).toEqual('0c15c2b8-48e9-4c83-b77e-054cd8215f93'); + }); + + test('can choose a selector that does not exist', () => { + const [store, { chooseSelector }] = selectorStore(); + chooseSelector({ selectorId: 'DOES NOT EXIST' }); + expect(store.getState().selector.currentSelectorId).toEqual(null); + }); + + test('can choose a selector when one is already selected', () => { + const [store, { chooseSelector }] = selectorStore(true, 6); + chooseSelector({ selectorId: '6ffcfd34-4141-4f38-918d-5348e9d42ab5' }); + expect(store.getState().selector.currentSelectorId).toEqual('6ffcfd34-4141-4f38-918d-5348e9d42ab5'); + chooseSelector({ selectorId: '9b25da35-f1fb-4b58-92e4-0eef3d929029' }); + expect(store.getState().selector.currentSelectorId).toEqual('9b25da35-f1fb-4b58-92e4-0eef3d929029'); + }); + + test('no top level selector is selected by default', () => { + const [store] = selectorStore(true, 6); + expect(store.getState().selector.topLevelSelector).toEqual(null); + }); + + test('can choose a top level selector', () => { + const [store, { setTopLevelSelector }] = selectorStore(true, 6); + setTopLevelSelector({ selectorId: '6ffcfd34-4141-4f38-918d-5348e9d42ab5' }); + expect(store.getState().selector.topLevelSelector).toEqual('6ffcfd34-4141-4f38-918d-5348e9d42ab5'); + }); + + test('can clear the current selector', () => { + const [store, { chooseSelector, clearSelector }] = selectorStore(true, 5); + + chooseSelector({ selectorId: '0c15c2b8-48e9-4c83-b77e-054cd8215f93' }); + expect(store.getState().selector.currentSelectorId).toEqual('0c15c2b8-48e9-4c83-b77e-054cd8215f93'); + + clearSelector(); + expect(store.getState().selector.currentSelectorId).toEqual(null); + }); + + test('can clear the current selector when no selector is chosen', () => { + const [, { clearSelector }] = selectorStore(true); + + expect(() => clearSelector()).not.toThrow(); + }); + + test('can clear the current top level selector', () => { + const [store, { setTopLevelSelector, clearTopLevelSelector }] = selectorStore(true); + + setTopLevelSelector({ selectorId: '0c15c2b8-48e9-4c83-b77e-054cd8215f93' }); + expect(store.getState().selector.topLevelSelector).toEqual('0c15c2b8-48e9-4c83-b77e-054cd8215f93'); + + clearTopLevelSelector(); + expect(store.getState().selector.topLevelSelector).toEqual(null); + }); + + test('can build a selector index', () => { + const [store, { selectRevision }] = selectorStore(false, 7); + + selectRevision({ revisionId: '18e0d089-e87c-448f-9ef2-3282a44cf6d2' }); + + expect(store.getState().selector.selectorPaths).toMatchInlineSnapshot(` + Object { + "007a5ace-b1a4-49ca-8dd3-c78aee2d5409": Array [ + Array [ + "person", + "56405dc7-b910-45e0-8ade-898594276795", + ], + Array [ + "firstName", + "9a55a096-4a79-46b0-8111-d9775d074a14", + ], + ], + "05a99c13-eedd-4a14-940a-f774fc461ca4": Array [ + Array [ + "person", + "cc2570c7-b3a5-4e5b-8f3d-3b3693769969", + ], + Array [ + "firstName", + "c3434721-19f7-4402-9bfb-1e11875a0de0", + ], + ], + } + `); + }); + }); + + describe('revision mutations', () => { + test('create revision', () => { + const store = createRevisionStore({ captureModel: models()[0] }); + const { createRevision } = store.getActions(); + + createRevision({ revisionId: '31b27c9b-2388-47df-b6f4-73fb4878c1fa', cloneMode: 'FORK_TEMPLATE' }); + + const { revisions } = store.getState(); + + expect(Object.keys(revisions).length).toEqual(3); + }); + + test('duplicating field - allowMultiple=false', () => { + const store = createRevisionStore({ + captureModel: models()[7], + }); + const { createNewFieldInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: '18e0d089-e87c-448f-9ef2-3282a44cf6d2' }); + + expect(() => + createNewFieldInstance({ + property: 'firstName', + path: [['person', '56405dc7-b910-45e0-8ade-898594276795']], + }) + ).toThrowErrorMatchingInlineSnapshot(`"field does not support multiple values."`); + }); + + test('duplicating field - allowMultiple=true', () => { + const store = createRevisionStore({ + captureModel: models()[4], + }); + const { createNewFieldInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'fd847948-11bf-42ca-bfdd-cab85ea818f3' }); + + expect((store.getState().revisionSubtree as any).properties.transcription).toHaveLength(2); + + expect(() => + createNewFieldInstance({ + property: 'transcription', + path: [], + }) + ).not.toThrow(); + + expect((store.getState().revisionSubtree as any).properties.transcription).toHaveLength(3); + + expect((store.getState().revisionSubtree as any).properties.transcription[2].label).toEqual('Transcription'); + expect((store.getState().revisionSubtree as any).properties.transcription[2].type).toEqual('text-field'); + expect((store.getState().revisionSubtree as any).properties.transcription[2].value).toEqual(''); + }); + + test('duplicating field with selector', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const { createNewFieldInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + expect(() => + createNewFieldInstance({ + property: 'text', + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ], + }) + ).not.toThrow(); + + expect( + (store.getState().revisionSubtree as any).properties.paragraph[0].properties.lines[0].properties.text + ).toHaveLength(2); + + const selectorId = (store.getState().revisionSubtree as any).properties.paragraph[0].properties.lines[0] + .properties.text[1].selector.id; + + expect(selectorId).toBeDefined(); + + expect(store.getState().selector.availableSelectors.map(e => e.id)).toContain(selectorId); + expect(store.getState().selector.selectorPaths).toHaveProperty(selectorId); + // The selectors should be the same size. + const selector = store.getState().selector.selectorPaths[selectorId]; + + expect(selector[0]).toMatchInlineSnapshot(` + Array [ + "paragraph", + "159621fb-4f93-4cd7-a394-5a1141fc1091", + ] + `); + expect(selector[1]).toMatchInlineSnapshot(` + Array [ + "lines", + "64e82cb7-16f8-432e-b2b7-3828233a134c", + ] + `); + expect(selector[2][0]).toEqual('text'); + }); + + test('duplicating entity - allowMultiple=false', () => { + const model = require('../../../fixtures/02-nesting/01-nested-model.json'); + + const store = createRevisionStore({ + captureModel: model, + }); + const { createNewEntityInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: '6fb10d2e-8a88-4a5f-a318-ac6542f073de' }); + + expect(() => + createNewEntityInstance({ + property: 'person', + path: [], + }) + ).toThrowErrorMatchingInlineSnapshot(`"entity does not support multiple values."`); + }); + test('duplicating entity - allowMultiple=true', () => { + const model = require('../../../fixtures/02-nesting/05-nested-model-multiple.json'); + + const store = createRevisionStore({ + captureModel: model, + }); + const { createNewEntityInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: '0e29f176-aeeb-4bf3-a92c-d64654e29c90' }); + + expect(() => + createNewEntityInstance({ + property: 'person', + path: [], + }) + ).not.toThrow(); + + expect((store.getState().revisionSubtree as CaptureModel['document'])?.properties.person).toHaveLength(3); + + expect( + (store.getState().revisionSubtree as CaptureModel['document'])?.properties.person[2] + ).toMatchInlineSnapshot( + { + id: expect.any(String), + properties: { + firstName: [{ id: expect.any(String) }], + lastName: [{ id: expect.any(String) }], + }, + }, + ` + Object { + "allowMultiple": true, + "description": "Describe a person", + "id": Any, + "immutable": false, + "label": "Person", + "labelledBy": "firstName", + "properties": Object { + "firstName": Array [ + Object { + "id": Any, + "label": "First name", + "revision": "0e29f176-aeeb-4bf3-a92c-d64654e29c90", + "type": "text-field", + "value": "", + }, + ], + "lastName": Array [ + Object { + "id": Any, + "label": "Last name", + "revision": "0e29f176-aeeb-4bf3-a92c-d64654e29c90", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "0e29f176-aeeb-4bf3-a92c-d64654e29c90", + "type": "entity", + } + ` + ); + }); + + test('duplicating entity with selector', () => { + const model = require('../../../fixtures/04-selectors/08-hocr-output.json'); + + const store = createRevisionStore({ + captureModel: model, + }); + const { createNewEntityInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + expect(() => + createNewEntityInstance({ + property: 'lines', + path: [['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091']], + }) + ).not.toThrow(); + + const newLine = ((store.getState().revisionSubtree as CaptureModel['document'])?.properties + .paragraph[0] as CaptureModel['document']).properties.lines[1]; + + expect(newLine).toMatchInlineSnapshot( + { + id: expect.any(String), + properties: { + text: [{ id: expect.any(String), selector: { id: expect.any(String) } }], + }, + selector: { id: expect.any(String) }, + }, + ` + Object { + "allowMultiple": true, + "description": "All of the lines inside of a paragraph", + "id": Any, + "immutable": false, + "label": "Line", + "labelledBy": "text", + "pluralLabel": "Lines", + "properties": Object { + "text": Array [ + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": Any, + "label": "Text of line", + "multiline": false, + "pluralField": "Text of lines", + "revision": "c8bb939a-7a76-4b15-9f77-81375519128c", + "selector": Object { + "id": Any, + "state": null, + "type": "box-selector", + }, + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "c8bb939a-7a76-4b15-9f77-81375519128c", + "selector": Object { + "id": Any, + "state": null, + "type": "box-selector", + }, + "type": "entity", + } + ` + ); + + const newTextSelector = (newLine.selector as any).id; + const newLineSelector = (newLine as any).properties.text[0].selector.id; + + expect(store.getState().selector.availableSelectors).toHaveLength(5); + expect(store.getState().selector.availableSelectors.map(e => e.id)).toContain(newTextSelector); + expect(store.getState().selector.availableSelectors.map(e => e.id)).toContain(newLineSelector); + }); + + test('removing entity instance', () => { + const model = require('../../../fixtures/04-selectors/08-hocr-output.json'); + + const store = createRevisionStore({ + captureModel: model, + }); + const { createNewEntityInstance, removeInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + expect(() => + createNewEntityInstance({ + property: 'lines', + path: [['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091']], + }) + ).not.toThrow(); + + const newLine = ((store.getState().revisionSubtree as CaptureModel['document'])?.properties + .paragraph[0] as CaptureModel['document']).properties.lines[1]; + + removeInstance({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', newLine.id], + ], + }); + + expect( + ((store.getState().revisionSubtree as CaptureModel['document'])?.properties + .paragraph[0] as CaptureModel['document']).properties.lines + ).toHaveLength(1); + + const newTextSelector = (newLine.selector as any).id; + const newLineSelector = (newLine as any).properties.text[0].selector.id; + + expect(store.getState().selector.availableSelectors).toHaveLength(3); + expect(store.getState().selector.availableSelectors.map(e => e.id)).not.toContain(newTextSelector); + expect(store.getState().selector.availableSelectors.map(e => e.id)).not.toContain(newLineSelector); + }); + + test('removing field instance', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const { createNewFieldInstance, removeInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + expect(() => + createNewFieldInstance({ + property: 'text', + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ], + }) + ).not.toThrow(); + + const text = (store.getState().revisionSubtree as any).properties.paragraph[0].properties.lines[0].properties + .text[1]; + + removeInstance({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ['text', text.id], + ], + }); + + expect( + (store.getState().revisionSubtree as any).properties.paragraph[0].properties.lines[0].properties.text + ).toHaveLength(1); + + expect(store.getState().selector.availableSelectors).toHaveLength(3); + expect(store.getState().selector.availableSelectors.map(e => e.id)).not.toContain(text.selector.id); + }); + + test('removing field instance - not last one', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const { removeInstance, selectRevision } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + const text = (store.getState().revisionSubtree as any).properties.paragraph[0].properties.lines[0].properties + .text[0]; + + expect(() => + removeInstance({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ['text', text.id], + ], + }) + ).toThrowErrorMatchingInlineSnapshot(`"Cannot delete last item"`); + }); + }); + + describe('revision field path 2', () => { + // Fields and navigation. + + describe('simple navigation', () => { + const store = createRevisionStore({ + captureModel: models()[7], + }); + const { selectRevision, revisionPushSubtree, revisionSelectField, revisionPopSubtree } = store.getActions(); + + it('should show the available keys when selecting a revision', () => { + selectRevision({ revisionId: '18e0d089-e87c-448f-9ef2-3282a44cf6d2' }); + + expect(store.getState().revisionSubtreeFieldKeys).toEqual(['person']); + }); + + it('should allow you to navigate down a tree', () => { + revisionPushSubtree({ term: 'person', id: '56405dc7-b910-45e0-8ade-898594276795' }); + + expect(store.getState().revisionSubtreeFieldKeys).toEqual(['firstName', 'lastName']); + + expect((store.getState().revisionSubtree as CaptureModel['document'])?.id).toEqual( + '56405dc7-b910-45e0-8ade-898594276795' + ); + }); + + it('should let choose a single field', () => { + revisionSelectField({ id: '9a55a096-4a79-46b0-8111-d9775d074a14', term: 'firstName' }); + + const revisionSelectState = store.getState(); + + expect(revisionSelectState.revisionSelectedFieldProperty).toEqual('firstName'); + expect(revisionSelectState.revisionSelectedFieldInstance).toEqual('9a55a096-4a79-46b0-8111-d9775d074a14'); + expect(revisionSelectState.revisionSubtreeField).toMatchInlineSnapshot(` + Object { + "id": "9a55a096-4a79-46b0-8111-d9775d074a14", + "label": "First name", + "selector": Object { + "id": "007a5ace-b1a4-49ca-8dd3-c78aee2d5409", + "state": null, + "type": "box-selector", + }, + "type": "text-field", + "value": "second first name", + } + `); + }); + it('should remove the selected field when navigating', () => { + revisionPopSubtree(undefined); + + const revisionSelectStatePopped = store.getState(); + + expect(revisionSelectStatePopped.revisionSelectedFieldProperty).toEqual(null); + expect(revisionSelectStatePopped.revisionSelectedFieldInstance).toEqual(null); + }); + }); + + // Selectors. + describe('selectors while navigating revision', () => { + test('can select linear path', () => { + const store = createRevisionStore({ + captureModel: require('../../../fixtures/04-selectors/08-hocr-output.json'), + }); + const { selectRevision, revisionPushSubtree } = store.getActions(); + + selectRevision({ revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c' }); + + // What are the selectors before. + + expect(store.getState().visibleCurrentLevelSelectorIds).toHaveLength(1); + + revisionPushSubtree({ id: '159621fb-4f93-4cd7-a394-5a1141fc1091', term: 'paragraph' }); + + // now what are the selectors. + expect(store.getState().visibleCurrentLevelSelectorIds).toHaveLength(2); + expect(store.getState().visibleCurrentLevelSelectorIds).toEqual([ + '2f32a9f6-525e-4cba-9bef-115129680fce', + '0d0a0325-6d9a-407a-93c2-db0f55bb7209', + ]); + + revisionPushSubtree({ id: '64e82cb7-16f8-432e-b2b7-3828233a134c', term: 'lines' }); + + expect(store.getState().visibleCurrentLevelSelectorIds).toHaveLength(2); + expect(store.getState().visibleCurrentLevelSelectorIds).toEqual([ + '0d0a0325-6d9a-407a-93c2-db0f55bb7209', + 'da7e26f8-9797-423e-a0cf-276df7b859ea', + ]); + }); + + test.todo('can navigate to adjacent field'); // select adjacent line + test.todo('can navigate to deeper field'); // select text directly, emulating what happens when selector is clicked. + test.todo('setting current level selectors only configuration'); + }); + }); + + describe('revision with continuous forking', () => { + test('revision can fork field', () => { + const store = createRevisionStore({ + captureModel: models()[0], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: '31b27c9b-2388-47df-b6f4-73fb4878c1fa', + cloneMode: 'EDIT_ALL_VALUES', + }); + + actions.updateFieldValue({ + path: [['name', 'aca844a3-836a-45c3-bb16-dc28bcdce46f']], + value: 'Testing a new value', + }); + + // @ts-ignore + expect(store.getState().currentRevision.document.properties.name[0].revises).toBeDefined(); + }); + + test('revision can fork entity', () => { + const store = createRevisionStore({ + captureModel: models()[2], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: 'd4000be7-1407-43a4-b5e7-75479fb96a0d', + cloneMode: 'EDIT_ALL_VALUES', + }); + + actions.updateFieldValue({ + path: [ + ['person', '5c8a5874-8bca-422c-be71-300612d67c72'], + ['firstName', '7b45ec25-15a6-40dd-9a1d-0fd1d673df15'], + ], + value: 'Testing a new value', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.person[0].properties.firstName[0].revises + ).toBeDefined(); + }); + + // OCR Tests + test('Correct a word in OCR', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c', + cloneMode: 'EDIT_ALL_VALUES', + }); + + actions.updateFieldValue({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ['text', 'eb122262-fab3-43c8-9432-ac93dad3abf8'], + ], + value: 'Testing a new value', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0].properties.text[0].revises + ).toBeDefined(); + }); + + test('Add a missing line', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c', + cloneMode: 'EDIT_ALL_VALUES', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[1] + ).not.toBeDefined(); + + actions.createNewEntityInstance({ + path: [['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091']], + property: 'lines', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[1] + ).toBeDefined(); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[1].properties.text[0] + ).toMatchInlineSnapshot( + { + id: expect.any(String), + revision: expect.any(String), + selector: { + id: expect.any(String), + }, + }, + ` + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": Any, + "label": "Text of line", + "multiline": false, + "pluralField": "Text of lines", + "revision": Any, + "selector": Object { + "id": Any, + "state": null, + "type": "box-selector", + }, + "type": "text-field", + "value": "", + } + ` + ); + }); + test('Change the bounding box of a word', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c', + cloneMode: 'EDIT_ALL_VALUES', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[1] + ).not.toBeDefined(); + + actions.updateSelector({ + state: { x: 1, y: 2, width: 3, height: 4 }, + selectorId: 'da7e26f8-9797-423e-a0cf-276df7b859ea', + }); + + // The original should remain unchanged. + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0].properties.text[0] + .selector + ).toMatchInlineSnapshot( + { + id: expect.any(String), + revisedBy: expect.any(Array), + }, + ` + Object { + "id": Any, + "revisedBy": Any, + "state": Object { + "height": 40, + "width": 30, + "x": 10, + "y": 20, + }, + "type": "box-selector", + } + ` + ); + + // But revised selector should be updated. + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0].properties.text[0] + .selector.revisedBy + ).toHaveLength(1); + + // @todo And then when we change the field, it should revert the original and just keep the fork + actions.updateFieldValue({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ['text', 'eb122262-fab3-43c8-9432-ac93dad3abf8'], + ], + value: 'Testing a new value', + }); + + expect( + // @ts-ignore + store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0].properties.text[0] + .selector + ).toMatchInlineSnapshot( + { + id: expect.any(String), + }, + ` + Object { + "id": Any, + "state": Object { + "height": 4, + "width": 3, + "x": 1, + "y": 2, + }, + "type": "box-selector", + } + ` + ); + + // Make sure the selectors are right. + // @ts-ignore + const newSelectorId = store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0] + .properties.text[0].selector.id; + + expect(store.getState().resolvedSelectors.map(r => r.id)).toContain(newSelectorId); + }); + + test('Change the bounding box of word and then change the word', () => { + const store = createRevisionStore({ + captureModel: models()[8], + }); + const actions = store.getActions(); + + actions.setRevisionMode({ editMode: true }); + + actions.createRevision({ + revisionId: 'c8bb939a-7a76-4b15-9f77-81375519128c', + cloneMode: 'EDIT_ALL_VALUES', + }); + + // First we update the selector. + actions.updateSelector({ + state: { x: 1, y: 2, width: 3, height: 4 }, + selectorId: 'da7e26f8-9797-423e-a0cf-276df7b859ea', + }); + + // Then we update the field value. What we are looking for is that the selector is reset to be a canonical value. + actions.updateFieldValue({ + path: [ + ['paragraph', '159621fb-4f93-4cd7-a394-5a1141fc1091'], + ['lines', '64e82cb7-16f8-432e-b2b7-3828233a134c'], + ['text', 'eb122262-fab3-43c8-9432-ac93dad3abf8'], + ], + value: 'Testing a new value', + }); + + // @ts-ignore + const newText = store.getState().currentRevision.document.properties.paragraph[0].properties.lines[0].properties + .text[0]; + + expect(newText.id).not.toEqual('eb122262-fab3-43c8-9432-ac93dad3abf8'); + expect(newText.selector.id).not.toEqual('da7e26f8-9797-423e-a0cf-276df7b859ea'); + expect(newText.selector.revisedBy).not.toBeDefined(); + expect(newText.selector.state).toEqual({ + x: 1, + y: 2, + width: 3, + height: 4, + }); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/editor/structure-editor.test.ts b/services/madoc-ts/__tests__/capture-models/editor/structure-editor.test.ts new file mode 100644 index 000000000..4d2cacec0 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/editor/structure-editor.test.ts @@ -0,0 +1,255 @@ +/** + * @jest-environment jsdom + */ + +import { + documentFieldOptionsToStructure, + getDocumentFields, + mergeFlatKeys, + structureToFlatStructureDefinition, +} from '../../../src/frontend/shared/capture-models/editor/core/structure-editor'; +import { expandModelFields } from '../../../src/frontend/shared/capture-models/helpers/expand-model-fields'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +registerField({ + label: 'Text field', + type: 'text-field', + description: 'Simple text field for plain text', + Component: undefined as any, + defaultValue: '', + allowMultiple: true, + defaultProps: {}, + Editor: undefined as any, + // Editor: TextFieldEditor, + TextPreview: undefined as any, +} as any); + +describe('structure editor', () => { + const DEFAULT_MODEL: CaptureModel = { + structure: { id: '1', type: 'model', label: 'empty', fields: [] }, + document: { id: '1', type: 'entity', label: 'Untitled document', properties: {} }, + }; + + describe('mergeFlatKeys', () => { + test('simple fields', () => { + expect(mergeFlatKeys([['field1'], ['field2'], ['field3']])).toEqual(['field1', 'field2', 'field3']); + }); + + test('simple fields nested', () => { + expect( + mergeFlatKeys([ + ['field1', 'field1.1'], + ['field1', 'field1.2'], + ['field1', 'field1.3'], + ['field2', 'field2.1'], + ['field3'], + ]) + ).toEqual([['field1', ['field1.1', 'field1.2', 'field1.3']], ['field2', ['field2.1']], 'field3']); + }); + + test('complex fields nested', () => { + expect( + mergeFlatKeys([ + ['field1', 'field1.1'], + ['field1', 'field1.2'], + ['field2', 'field2.1'], + ['field1', 'field1.3'], + ['field2', 'field2.2'], + ['field2', 'field2.3'], + ['field3'], + ]) + ).toEqual([ + ['field1', ['field1.1', 'field1.2', 'field1.3']], + ['field2', ['field2.1', 'field2.2', 'field2.3']], + 'field3', + ]); + }); + + test('duplicate fields nested', () => { + expect( + mergeFlatKeys([ + ['field1', 'field1.1'], + ['field1', 'field1.2'], + ['field3'], + ['field1', 'field1.1'], + ['field1', 'field1.2'], + ['field1', 'entity2', 'entity2.2', 'entity.2'], + ['field3'], + ['field1', 'field1.1'], + ['field1', 'entity2', 'entity2.3', 'entity.2'], + ['field1', 'field1.2'], + ['field3'], + ['field1', 'entity2', 'entity2.2', 'entity.3'], + ['field1', 'entity2', 'entity2.2', 'entity.3'], + ]) + ).toEqual([ + [ + 'field1', + [ + 'field1.1', + 'field1.2', + [ + 'entity2', + [ + ['entity2.2', ['entity.2', 'entity.3']], + ['entity2.3', ['entity.2']], + ], + ], + ], + ], + 'field3', + ]); + }); + }); + + describe('getDocumentFields', () => { + test('simple fields', () => { + expect( + getDocumentFields({ + id: '1', + type: 'entity', + label: 'Person', + properties: { + firstName: [ + { + id: 'f1', + type: 'text-field', + label: 'First name', + value: '', + }, + ], + lastName: [ + { + id: 'f2', + type: 'text-field', + label: 'Last name', + value: '', + }, + ], + }, + }) + ).toMatchInlineSnapshot(` + Object { + "fields": Array [ + Object { + "key": Array [ + "firstName", + ], + "label": "First name", + "type": "text-field", + }, + Object { + "key": Array [ + "lastName", + ], + "label": "Last name", + "type": "text-field", + }, + ], + "key": Array [], + "label": "Person", + "type": "model", + } + `); + }); + }); + + describe('structureToFlatStructureDefinition', () => { + test('simple', () => { + const model: CaptureModel['document'] = { + id: '1', + type: 'entity', + label: 'Untitled document', + properties: { + field1: [{ id: '1', type: 'text-field', label: 'field 1', value: '' }], + field2: [{ id: '2', type: 'text-field', label: 'field 2', value: '' }], + field3: [{ id: '3', type: 'text-field', label: 'field 3', value: '' }], + }, + }; + + const def = structureToFlatStructureDefinition(model, ['field1']); + expect(def).toEqual([{ key: ['field1'], label: 'field 1', type: 'text-field' }]); + + const def2 = structureToFlatStructureDefinition(model, ['field2', 'field3']); + expect(def2).toEqual([ + { key: ['field2'], label: 'field 2', type: 'text-field' }, + { key: ['field3'], label: 'field 3', type: 'text-field' }, + ]); + }); + + test('nested', () => { + const model: CaptureModel['document'] = { + id: '1', + type: 'entity', + label: 'Untitled document', + properties: { + field1: [{ id: 'f1', type: 'text-field', label: 'field 1', value: '' }], + entity1: [ + { + id: 'e1', + type: 'entity', + label: 'field 2', + properties: { + field2: [{ id: 'f2', type: 'text-field', label: 'field 2', value: '' }], + field3: [ + { + id: 'f3', + type: 'text-field', + label: 'field 3', + value: '', + }, + ], + field4: [ + { + id: 'f1', + type: 'text-field', + label: 'field 4', + value: '', + }, + ], + }, + }, + ], + }, + }; + + const def = structureToFlatStructureDefinition(model, [['entity1', ['field2', 'field4']]]); + + expect(def).toEqual([ + { + key: ['entity1', 'field2'], + label: 'field 2', + type: 'text-field', + }, + { + key: ['entity1', 'field4'], + label: 'field 4', + type: 'text-field', + }, + ]); + + expect(documentFieldOptionsToStructure(def)).toEqual([['entity1', ['field2', 'field4']]]); + }); + }); + + describe('expandModelFields', () => { + test('expandModelFields', () => { + expect( + expandModelFields([ + ['field1', ['field1.1', 'field1.2', 'field1.3']], + ['field2', ['field2.1', 'field2.2', 'field2.3']], + 'field3', + ]) + ).toEqual([ + ['field1', 'field1.1'], + ['field1', 'field1.2'], + ['field1', 'field1.3'], + ['field2', 'field2.1'], + ['field2', 'field2.2'], + ['field2', 'field2.3'], + ['field3'], + ]); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/__snapshots__/create-revision.test.ts.snap b/services/madoc-ts/__tests__/capture-models/helpers/__snapshots__/create-revision.test.ts.snap new file mode 100644 index 000000000..080790fcb --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/__snapshots__/create-revision.test.ts.snap @@ -0,0 +1,3478 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - depth 3 Edit values - should... 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "immutable": true, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "immutable": true, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "23fb8e8b-6ae4-4503-b688-80116b5df127", + "label": "Third level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - depth 3 Fork template, should... 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "immutable": true, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revises": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revises": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - depth 3 Fork values - should... 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "immutable": true, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revises": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revises": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (no duplicate allow) [model-b] Edit values - model-b should be immutable, the rest should be the same 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "immutable": true, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "immutable": true, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "23fb8e8b-6ae4-4503-b688-80116b5df127", + "label": "Third level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (no duplicate allow) [model-b] Fork template, model-b should be immutable, the rest mutable and forked 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "d948d756-14ad-4554-92ce-0296e5ce8735", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (no duplicate allow) [model-b] Fork values - model-b should be immutable, the rest mutable with values 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "d948d756-14ad-4554-92ce-0296e5ce8735", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (root on allowMultiple=false) Edit values - model-b and c should be immutable, the rest should be the same 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "immutable": true, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "immutable": true, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "23fb8e8b-6ae4-4503-b688-80116b5df127", + "label": "Third level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (root on allowMultiple=false) Fork template, no models should be forked, since allowMultiple is in the root 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model B - edit nested field (root on allowMultiple=false) Fork values - model-b and c should be immutable, the rest mutable with values 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-b": Array [ + Object { + "allowMultiple": false, + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "immutable": true, + "label": "Model B.1: Allow multiple = false", + "properties": Object { + "field-d": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.2: Allow multiple = true", + "properties": Object { + "field-e": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-d": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model B.3: Allow multiple = false", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Third level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 1 Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "immutable": true, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": true, + "id": "1a7e07f0-f111-406e-972e-71976258a47f", + "immutable": true, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "1ecda588-c846-48c1-813c-2fd4ccfd6700", + "label": "Second level text field (first)", + "type": "text-field", + "value": "value for field (first)", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "20254fca-fdad-4c70-b748-590f206e51d8", + "immutable": true, + "label": "Model F.3: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "5019c619-a64c-4893-bf39-857df15f1e92", + "label": "Second level text field (second)", + "type": "text-field", + "value": "value for field (second)", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 1 Fork template, should only contain 1 model 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field (first)", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 1 Fork values - should only contain ??? model 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field (first)", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field (first)", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.3: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field (second)", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field (second)", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 2 Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "immutable": true, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": false, + "id": "1a7e07f0-f111-406e-972e-71976258a47f", + "immutable": true, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "1ecda588-c846-48c1-813c-2fd4ccfd6700", + "label": "Second level text field (first)", + "type": "text-field", + "value": "value for field (first)", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": false, + "id": "20254fca-fdad-4c70-b748-590f206e51d8", + "immutable": true, + "label": "Model F.3: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "5019c619-a64c-4893-bf39-857df15f1e92", + "label": "Second level text field (second)", + "type": "text-field", + "value": "value for field (second)", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 2 Fork template, should only contain 1 model matching ID 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "immutable": true, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field (first)", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revises": "1a7e07f0-f111-406e-972e-71976258a47f", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Edit model F - depth 2 Fork values - should only contain ??? model 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-f": Array [ + Object { + "allowMultiple": false, + "id": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "immutable": true, + "label": "Model F.1: Allow multiple = false", + "properties": Object { + "model-g": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model F.2: Allow multiple = true", + "properties": Object { + "field-f": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field (first)", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field (first)", + }, + ], + }, + "revises": "1a7e07f0-f111-406e-972e-71976258a47f", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 0 Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": true, + "id": "ab70224e-4cb8-4d8d-a379-9769ad3e06e5", + "immutable": true, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "ecbd3446-6da3-4125-934f-1a3b766b8afe", + "immutable": true, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "e72d1003-5b50-42ed-9495-f4d80cc53961", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "7de07c37-6157-4d63-8097-d2d4a78b778f", + "immutable": true, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8d4e3122-0f15-42db-864d-52c1a2843a90", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "immutable": true, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "immutable": true, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 0 Fork template, should only contain one model at each level 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 0 Fork values - should only contain all models, with values but no revises 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at depth below should _not_ filter Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "ab70224e-4cb8-4d8d-a379-9769ad3e06e5", + "immutable": true, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "ecbd3446-6da3-4125-934f-1a3b766b8afe", + "immutable": true, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "e72d1003-5b50-42ed-9495-f4d80cc53961", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "7de07c37-6157-4d63-8097-d2d4a78b778f", + "immutable": true, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8d4e3122-0f15-42db-864d-52c1a2843a90", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "immutable": true, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "immutable": true, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at depth below should _not_ filter Fork template, should only contain one model at each level, not any special selection 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at depth below should _not_ filter Fork values - should only contain all models, and not filter 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at first depth Edit values - should contain all models under chosen model h 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "immutable": true, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "immutable": true, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at first depth Fork template, should only contain one model at each level, using chosen model H 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 - with field value at first depth Fork values - should only contain all models under model h, with values but no revises 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revises": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "ab70224e-4cb8-4d8d-a379-9769ad3e06e5", + "immutable": true, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "ecbd3446-6da3-4125-934f-1a3b766b8afe", + "immutable": true, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "e72d1003-5b50-42ed-9495-f4d80cc53961", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "7de07c37-6157-4d63-8097-d2d4a78b778f", + "immutable": true, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8d4e3122-0f15-42db-864d-52c1a2843a90", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "immutable": true, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "immutable": true, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 Fork template, should only contain one model at each level 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 1 Fork values - should only contain all models, with values but no revises 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.1: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.1: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.2: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at all depths Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": false, + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "immutable": true, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at all depths Fork template, should only contain one model at each level 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revises": "915106fd-f663-4e55-a727-8253e251c62d", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at all depths Fork values - should only contain all models, with values but no revises 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model I.4: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revises": "915106fd-f663-4e55-a727-8253e251c62d", + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at different depths – invalid state Edit values - should contain all models 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at different depths – invalid state Fork template, should only contain one model at each level 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [ + Object { + "allowMultiple": false, + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "label": "Model I.3: Allow multiple = true", + "properties": Object { + "field-g": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revises": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Multi-dimensional revision creation (adjacent at all levels) Model H - 2x2 models test model-h depth 2 - with field value at different depths – invalid state Fork values - should only contain all models, with values but no revises 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-h": Array [ + Object { + "allowMultiple": false, + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "immutable": true, + "label": "Model H.2: Allow multiple = true", + "properties": Object { + "model-i": Array [], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit field A 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field over Edit values should should keep model a immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "5f0e4511-957d-429c-8151-396ec4585a2d", + "label": "Top level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field over Fork template should keep model a immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field over Fork values should keep model a immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field under Edit values should set values immutable until field-b and model-c 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field under Fork template should create new field-b and model-c 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 + field under Fork values should create new field-b and model-c with values 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 Edit values should return the original documents 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 Fork template should fork at Model A.1 with nuked values throughout 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 1 Fork values should fork at Model A.1 with the same values but new ids 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over (x2) Edit values should should keep model a immutable, but keep all 2 fields over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "5f0e4511-957d-429c-8151-396ec4585a2d", + "label": "Top level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over (x2) Fork template should keep model a immutable, but keep all 2 fields over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over (x2) Fork values should keep model a immutable, but keep all 2 fields over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revises": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over Edit values should should keep both models immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "5f0e4511-957d-429c-8151-396ec4585a2d", + "label": "Top level text field", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over Fork template should keep both models immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 + field over Fork values should keep both models immutable, but keep the field over mutable 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 Edit values should return the original documents 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 Fork template should fork at Model C.1 with nuked values throughout 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - model root depth 2 Fork values should fork at Model C.1 with the same values but new ids 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - no model root Edit values should return the original documents 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "immutable": true, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "label": "Second level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - no model root Fork template should fork at Model A.1 with nuked values throughout 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model C - no model root Fork values should fork at Model A.1 with the same values but new ids 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "model-c": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model C.1: Allow multiple = true", + "properties": Object { + "field-c": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Second level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a - model root and field above root This should throw as an invalid structure because the field is above the root 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "field-a": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Top level text field", + "revises": "5f0e4511-957d-429c-8151-396ec4585a2d", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a - with model root Edit values should return the original documents 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a - with model root Fork template should fork at Model A.1 with nuked values throughout 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a - with model root Fork values should create a new document with pre-filled fields and different ID 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": false, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a Edit values should return the original documents 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "immutable": true, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "label": "First level text field", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a Fork template should fork at Model A.1 with nuked values throughout 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; + +exports[`create revision Single dimension revision creation (non-adjacent) Edit model a Fork values should create a new document with pre-filled fields and different ID 1`] = ` +Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "immutable": true, + "label": "Simple document", + "properties": Object { + "model-a": Array [ + Object { + "allowMultiple": true, + "id": "[--------GENERATED-ID--------]", + "immutable": false, + "label": "Model A.1: Allow multiple = true", + "properties": Object { + "field-b": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First level text field", + "revision": "REVISION-ID", + "type": "text-field", + "value": "value for field", + }, + ], + }, + "revision": "REVISION-ID", + "type": "entity", + }, + ], + }, + "type": "entity", +} +`; diff --git a/services/madoc-ts/__tests__/capture-models/helpers/capture-model-hydration.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-hydration.test.ts new file mode 100644 index 000000000..d76e3496c --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-hydration.test.ts @@ -0,0 +1,505 @@ +import { hydrateCaptureModel } from '../../../src/frontend/shared/capture-models/helpers/hydrate-capture-model'; +import { v4 } from 'uuid'; +import { hydrateCompressedModel } from '../../../src/frontend/shared/capture-models/helpers/hydrate-compressed-model'; +import { captureModelShorthand } from '../../../src/frontend/shared/capture-models/helpers/capture-model-shorthand'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id'); +const { generateId } = require('../../../src/frontend/shared/capture-models/helpers/generate-id'); +const GENERATED_ID = '[--------GENERATED-ID--------]'; + +generateId.mockImplementation(() => GENERATED_ID); + +describe('capture model hydration', () => { + const simpleModel: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + value: '', + }, + ], + name: [ + { + id: v4(), + label: 'Name of person', + type: 'text-field', + value: '', + }, + ], + }, + }; + + test('it can hydrate simple model', () => { + expect( + hydrateCaptureModel(simpleModel, { + label: 'Testing', + }) + ).toMatchInlineSnapshot(` + Object { + "id": "[--------GENERATED-ID--------]", + "label": "My form", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing", + }, + ], + }, + "type": "entity", + } + `); + }); + + test('it can hydrate while keeping blank fields', () => { + expect( + hydrateCaptureModel( + simpleModel, + { + label: 'Testing', + }, + { keepExtraFields: true } + ) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "My form", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing", + }, + ], + "name": Object { + "id": "[--------GENERATED-ID--------]", + "label": "Name of person", + "type": "text-field", + "value": "", + }, + }, + "type": "entity", +} +`); + }); + + test('it can hydrate model with multiple values', () => { + expect( + hydrateCaptureModel(simpleModel, { + label: ['Testing A', 'Testing B'], + }) + ).toMatchInlineSnapshot(` + Object { + "id": "[--------GENERATED-ID--------]", + "label": "My form", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing A", + }, + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing B", + }, + ], + }, + "type": "entity", + } + `); + }); + + test('it can hydrate model with multiple fields', () => { + expect( + hydrateCaptureModel(simpleModel, { + label: ['Testing A', 'Testing B'], + name: 'testing', + }) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "My form", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing A", + }, + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Testing B", + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Name of person", + "selector": undefined, + "type": "text-field", + "value": "testing", + }, + ], + }, + "type": "entity", +} +`); + }); + + const complexModelWithEntity: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + value: '', + }, + ], + people: [ + { + id: v4(), + label: 'Name of person', + type: 'entity', + properties: { + name: [ + { + id: v4(), + label: 'First name', + type: 'text-field', + value: '', + }, + ], + city: [ + { + id: v4(), + label: 'City', + type: 'text-field', + value: '', + }, + ], + }, + }, + ], + }, + }; + + test('it can hydrate a list of entities', () => { + const doc = { + label: 'Some label', + people: [ + { + name: 'Stephen', + city: 'Glasgow', + }, + { + name: 'Bob', + city: 'Edinburgh', + }, + ], + }; + + expect(hydrateCaptureModel(complexModelWithEntity, doc)).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "My form", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "The label", + "selector": undefined, + "type": "text-field", + "value": "Some label", + }, + ], + "people": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Name of person", + "properties": Object { + "city": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "City", + "selector": undefined, + "type": "text-field", + "value": "Glasgow", + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First name", + "selector": undefined, + "type": "text-field", + "value": "Stephen", + }, + ], + }, + "type": "entity", + }, + Object { + "id": "[--------GENERATED-ID--------]", + "label": "Name of person", + "properties": Object { + "city": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "City", + "selector": undefined, + "type": "text-field", + "value": "Edinburgh", + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "First name", + "selector": undefined, + "type": "text-field", + "value": "Bob", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`); + }); + + test('short hand capture model', () => { + expect( + captureModelShorthand({ + label: 'text-field', + name: 'text-field', + }) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "Root", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "label", + "type": "text-field", + "value": null, + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "name", + "type": "text-field", + "value": null, + }, + ], + }, + "type": "entity", +} +`); + }); + + test('nested short hand capture model', () => { + expect( + captureModelShorthand({ + label: 'text-field', + 'person.name': 'text-field', + 'person.city': 'text-field', + 'person.relation.description': 'text-field', + 'person.relation.label': 'text-field', + }) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "Root", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "label", + "type": "text-field", + "value": null, + }, + ], + "person": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "person", + "properties": Object { + "city": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "city", + "type": "text-field", + "value": null, + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "name", + "type": "text-field", + "value": null, + }, + ], + "relation": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "relation", + "properties": Object { + "description": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "description", + "type": "text-field", + "value": null, + }, + ], + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "label", + "type": "text-field", + "value": null, + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`); + }); + + test('nested short hand capture model with extra options', () => { + expect( + captureModelShorthand({ + label: 'text-field', + 'person.name': 'text-field', + 'person.city': { + type: 'dropdown', + options: [ + { value: 'aberdeen', text: 'Aberdeen' }, + { value: 'glasgow', text: 'Glasgow' }, + ], + }, + }) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "Root", + "properties": Object { + "label": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "label", + "type": "text-field", + "value": null, + }, + ], + "person": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "person", + "properties": Object { + "city": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "city", + "options": Array [ + Object { + "text": "Aberdeen", + "value": "aberdeen", + }, + Object { + "text": "Glasgow", + "value": "glasgow", + }, + ], + "type": "dropdown", + "value": null, + }, + ], + "name": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "name", + "type": "text-field", + "value": null, + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`); + }); + + test('hydrating compressed model', () => { + expect( + hydrateCompressedModel({ + __meta__: { + title: 'text-field', + }, + title: 'testing a title', + }) + ).toMatchInlineSnapshot(` +Object { + "id": "[--------GENERATED-ID--------]", + "label": "Root", + "properties": Object { + "title": Array [ + Object { + "id": "[--------GENERATED-ID--------]", + "label": "title", + "selector": undefined, + "type": "text-field", + "value": "testing a title", + }, + ], + }, + "type": "entity", +} +`); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/capture-model-serialisation.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-serialisation.test.ts new file mode 100644 index 000000000..64e30a544 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-serialisation.test.ts @@ -0,0 +1,491 @@ +import { serialiseCaptureModel } from '../../../src/frontend/shared/capture-models/helpers/serialise-capture-model'; +import { v4 } from 'uuid'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +describe('capture model serialisation', () => { + test('it can serialise simple models', () => { + expect( + serialiseCaptureModel({ + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + value: 'Test label', + }, + ], + name: [ + { + id: v4(), + label: 'Name of person', + type: 'text-field', + value: 'Test name', + }, + ], + }, + }) + ).toMatchInlineSnapshot(` + Object { + "label": "Test label", + "name": "Test name", + } + `); + }); + + test('it can serialise simple models with metadata', () => { + expect( + serialiseCaptureModel( + { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + value: 'Test label', + }, + ], + name: [ + { + id: v4(), + label: 'Name of person', + type: 'text-field', + value: 'Test name', + }, + ], + }, + }, + { addMetadata: true } + ) + ).toMatchInlineSnapshot(` + Object { + "__meta__": Object { + "label": "text-field", + "name": "text-field", + }, + "label": "Test label", + "name": "Test name", + } + `); + }); + + test('it can serialise model with entity', () => { + const complexModelWithEntity: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + value: '', + }, + ], + people: [ + { + id: v4(), + label: 'Name of person', + type: 'entity', + properties: { + name: [ + { + id: v4(), + label: 'First name', + type: 'text-field', + value: 'First persons name', + }, + ], + city: [ + { + id: v4(), + label: 'City', + type: 'text-field', + value: 'Aberdeen', + }, + ], + }, + }, + { + id: v4(), + label: 'Name of person', + type: 'entity', + properties: { + name: [ + { + id: v4(), + label: 'First name', + type: 'text-field', + value: 'Second persons name', + }, + ], + city: [ + { + id: v4(), + label: 'City', + type: 'text-field', + value: 'Glasgow', + }, + ], + }, + }, + ], + }, + }; + + expect(serialiseCaptureModel(complexModelWithEntity, { addMetadata: true })).toMatchInlineSnapshot(` + Object { + "__meta__": Object { + "label": "text-field", + "people.city": "text-field", + "people.name": "text-field", + }, + "label": "", + "people": Array [ + Object { + "city": "Aberdeen", + "name": "First persons name", + }, + Object { + "city": "Glasgow", + "name": "Second persons name", + }, + ], + } + `); + }); + + test('simple model with selector', () => { + const complexModelWithEntity: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + selector: { + id: v4(), + type: 'box-selector', + state: { + x: 10, + y: 20, + width: 130, + height: 140, + }, + }, + value: 'Some value of the label', + }, + ], + }, + }; + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: false })).toMatchInlineSnapshot(` + Object { + "label": "Some value of the label", + } + `); + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: true })).toMatchInlineSnapshot(` + Object { + "label": Object { + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + "value": "Some value of the label", + }, + } + `); + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: true, rdfValue: true })) + .toMatchInlineSnapshot(` + Object { + "label": Object { + "@value": "Some value of the label", + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + }, + } + `); + }); + test('simple model with multiple selectors', () => { + const complexModelWithEntity: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + label: [ + { + id: v4(), + label: 'The label', + type: 'text-field', + selector: { + id: v4(), + type: 'box-selector', + state: { + x: 10, + y: 20, + width: 130, + height: 140, + }, + }, + value: 'Some value of the label', + }, + { + id: v4(), + label: 'The label', + type: 'text-field', + selector: { + id: v4(), + type: 'box-selector', + state: { + x: 20, + y: 40, + width: 160, + height: 180, + }, + }, + value: 'Second value', + }, + { + id: v4(), + label: 'The label', + type: 'text-field', + selector: { + id: v4(), + type: 'box-selector', + state: null, + }, + value: 'Some value of the label', + }, + ], + }, + }; + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: false })).toMatchInlineSnapshot(` +Object { + "label": Array [ + "Some value of the label", + "Second value", + "Some value of the label", + ], +} +`); + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: true })).toMatchInlineSnapshot(` +Object { + "label": Array [ + Object { + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + "value": "Some value of the label", + }, + Object { + "selector": Object { + "height": 180, + "width": 160, + "x": 20, + "y": 40, + }, + "value": "Second value", + }, + "Some value of the label", + ], +} +`); + + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: true, rdfValue: true })) + .toMatchInlineSnapshot(` +Object { + "label": Array [ + Object { + "@value": "Some value of the label", + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + }, + Object { + "@value": "Second value", + "selector": Object { + "height": 180, + "width": 160, + "x": 20, + "y": 40, + }, + }, + "Some value of the label", + ], +} +`); + expect( + serialiseCaptureModel(complexModelWithEntity, { addSelectors: true, rdfValue: true, normalisedValueLists: true }) + ).toMatchInlineSnapshot(` +Object { + "label": Array [ + Object { + "@value": "Some value of the label", + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + }, + Object { + "@value": "Second value", + "selector": Object { + "height": 180, + "width": 160, + "x": 20, + "y": 40, + }, + }, + Object { + "@value": "Some value of the label", + }, + ], +} +`); + }); + + test('simple model with entity selector', () => { + const complexModelWithEntity: CaptureModel['document'] = { + id: v4(), + type: 'entity', + label: 'My form', + properties: { + people: [ + { + id: v4(), + type: 'entity', + label: 'Person', + allowMultiple: true, + selector: { + id: v4(), + type: 'box-selector', + state: { + x: 10, + y: 20, + width: 130, + height: 140, + }, + }, + properties: { + name: [ + { + id: v4(), + label: 'Name', + type: 'text-field', + value: 'Some value of the label', + }, + ], + }, + }, + ], + details: [ + { + id: v4(), + type: 'entity', + label: 'Details', + allowMultiple: false, + selector: { + id: v4(), + type: 'box-selector', + state: { + x: 20, + y: 40, + width: 160, + height: 180, + }, + }, + properties: { + label: [ + { + id: v4(), + label: 'Label', + type: 'text-field', + value: 'The label of an object', + }, + ], + description: [ + { + id: v4(), + label: 'Description', + type: 'text-field', + value: 'The description of an object', + }, + ], + }, + }, + ], + }, + }; + + expect(serialiseCaptureModel(complexModelWithEntity)).toMatchInlineSnapshot(` + Object { + "details": Object { + "description": "The description of an object", + "label": "The label of an object", + }, + "people": Array [ + Object { + "name": "Some value of the label", + }, + ], + } + `); + expect(serialiseCaptureModel(complexModelWithEntity, { addSelectors: true })).toMatchInlineSnapshot(` + Object { + "details": Object { + "properties": Object { + "description": "The description of an object", + "label": "The label of an object", + }, + "selector": Object { + "height": 180, + "width": 160, + "x": 20, + "y": 40, + }, + }, + "people": Array [ + Object { + "properties": Object { + "name": "Some value of the label", + }, + "selector": Object { + "height": 140, + "width": 130, + "x": 10, + "y": 20, + }, + }, + ], + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/capture-model-to-revision-list.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-to-revision-list.test.ts new file mode 100644 index 000000000..d706b2135 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/capture-model-to-revision-list.test.ts @@ -0,0 +1,656 @@ +import { captureModelToRevisionList } from '../../../src/frontend/shared/capture-models/helpers/capture-model-to-revision-list'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +// @ts-ignore +import single01 from '../../../fixtures/03-revisions/01-single-field-with-revision.json'; +// @ts-ignore +import single02 from '../../../fixtures/03-revisions/02-single-field-with-multiple-revisions.json'; +// @ts-ignore +import single03 from '../../../fixtures/03-revisions/03-nested-revision.json'; +// @ts-ignore +import single04 from '../../../fixtures/03-revisions/04-dual-transcription.json'; +// @ts-ignore +import single05 from '../../../fixtures/03-revisions/05-allow-multiple-transcriptions.json'; +// @ts-ignore +import ames02 from '../../../fixtures/99-unrealistic/02-ames.json'; + +describe('capture model to revision list', () => { + test('single-field-with-revision', () => { + expect(captureModelToRevisionList(single01 as CaptureModel)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "b329e009-1c8a-4bed-bfde-c2a587a22f97", + "document": Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "label": "Simple document", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "eafb62d7-71b7-47bd-b887-def8655d8d2a", + "label": "Name", + "revision": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "type": "text-field", + "value": "Some value that was submitted", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "name", + ], + "id": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "structureId": "31b27c9b-2388-47df-b6f4-73fb4878c1fa", + }, + "source": "structure", + }, + ] + `); + }); + + test('single-field-with-revision', () => { + expect(captureModelToRevisionList(single02 as CaptureModel)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "93d09b85-9332-4b71-8e27-1294c8a963f3", + "document": Object { + "description": "", + "id": "b3f53013-23cc-45db-825a-12500bf3c20e", + "label": "Simple document", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "baf51d8c-ce99-4bf4-afd0-0ca2092a7784", + "label": "Name", + "revision": "514c8d52-80b0-49c1-ab97-24a67f29d194", + "type": "text-field", + "value": "Person A wrote this", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "name", + ], + "id": "514c8d52-80b0-49c1-ab97-24a67f29d194", + }, + "source": "unknown", + }, + Object { + "captureModelId": "93d09b85-9332-4b71-8e27-1294c8a963f3", + "document": Object { + "description": "", + "id": "b3f53013-23cc-45db-825a-12500bf3c20e", + "label": "Simple document", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "205c9b62-48e3-43ff-8853-222dcd357710", + "label": "Name", + "revision": "b4077dff-3bea-4783-9712-32b52a1146e3", + "type": "text-field", + "value": "Person B wrote this", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "name", + ], + "id": "b4077dff-3bea-4783-9712-32b52a1146e3", + }, + "source": "unknown", + }, + ] + `); + }); + + test('nested-revision', () => { + expect(captureModelToRevisionList(single03 as CaptureModel)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "2cc4131d-4f8d-4ceb-b140-48cd513b5e4f", + "document": Object { + "description": "", + "id": "a8d5ff43-adb2-456a-a615-3d24fbfa05f7", + "label": "Nested choices", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "a1ed84d2-c44c-4877-ac3d-10559acd7fce", + "label": "Name", + "type": "text-field", + "value": "", + }, + ], + "person": Array [ + Object { + "description": "Describe a person", + "id": "5c8a5874-8bca-422c-be71-300612d67c72", + "label": "Person", + "properties": Object { + "firstName": Array [ + Object { + "id": "7b45ec25-15a6-40dd-9a1d-0fd1d673df15", + "label": "First name", + "type": "text-field", + "value": "", + }, + Object { + "id": "dda6d8bc-ca6d-48e0-8bcc-a24537586346", + "label": "First name", + "revision": "fa500021-7408-4318-ab05-ac6e4d4a3096", + "type": "text-field", + "value": "Some value", + }, + ], + "lastName": Array [ + Object { + "id": "f5e7480c-411e-486d-a91e-0bf24f146ab5", + "label": "Last name", + "type": "text-field", + "value": "", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + "lastName", + ], + ], + "name", + ], + "id": "fa500021-7408-4318-ab05-ac6e4d4a3096", + }, + "source": "unknown", + }, + ] + `); + }); + + test('dual-transcription', () => { + expect(captureModelToRevisionList(single04 as CaptureModel)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "737150d3-2c72-4eeb-9fd4-42ad085cdf7f", + "document": Object { + "id": "279e8fb2-13c3-43cc-aff2-2b41d9025828", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": false, + "id": "c0ac6fd6-9146-4eac-a2b3-0067bc689cb6", + "label": "Transcription", + "revision": "04267f75-bb8d-4321-8046-12db3f9d6ceb", + "type": "text-field", + "value": "Person A created this one", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "transcription", + ], + "id": "04267f75-bb8d-4321-8046-12db3f9d6ceb", + }, + "source": "unknown", + }, + Object { + "captureModelId": "737150d3-2c72-4eeb-9fd4-42ad085cdf7f", + "document": Object { + "id": "279e8fb2-13c3-43cc-aff2-2b41d9025828", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": false, + "id": "c2b68f02-cce4-4a12-940b-d1359d89e807", + "label": "Transcription", + "revision": "81ab315e-200e-4649-bb11-99db766a5f66", + "type": "text-field", + "value": "Person B created this one", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "transcription", + ], + "id": "81ab315e-200e-4649-bb11-99db766a5f66", + }, + "source": "unknown", + }, + ] + `); + }); + + test('dual-transcription', () => { + expect(captureModelToRevisionList(single05 as CaptureModel)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "143d9c4a-5d4e-4ca2-89f3-dc4bd7b45e3e", + "document": Object { + "id": "47e8a9d8-76f8-422b-91af-b457d1c624a0", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": true, + "id": "2666cf79-ef2f-419f-a3f4-038216a89783", + "label": "Transcription", + "revises": "1615a172-b2c5-4192-bcc1-606a871b6230", + "revision": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "type": "text-field", + "value": "Person A created this one, which revises the original", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "approved": true, + "fields": Array [ + "transcription", + ], + "id": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "status": "accepted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + }, + "source": "structure", + }, + Object { + "captureModelId": "143d9c4a-5d4e-4ca2-89f3-dc4bd7b45e3e", + "document": Object { + "id": "47e8a9d8-76f8-422b-91af-b457d1c624a0", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": true, + "id": "1efd5946-a3a1-484f-a862-710741a3b682", + "label": "Transcription", + "revision": "daf3f9d9-2a16-4c1f-8657-3560775bd9eb", + "type": "text-field", + "value": "Person B created this one, which is completely new", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "approved": true, + "fields": Array [ + "transcription", + ], + "id": "daf3f9d9-2a16-4c1f-8657-3560775bd9eb", + "status": "accepted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + }, + "source": "structure", + }, + Object { + "captureModelId": "143d9c4a-5d4e-4ca2-89f3-dc4bd7b45e3e", + "document": Object { + "id": "47e8a9d8-76f8-422b-91af-b457d1c624a0", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": true, + "id": "892f3abe-bbbe-4b1e-9167-a52ec76ea5c1", + "label": "Transcription", + "revision": "bb5d55b1-6c38-4bb9-a6e6-ed236347671b", + "type": "text-field", + "value": "Person C created this one", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "transcription", + ], + "id": "bb5d55b1-6c38-4bb9-a6e6-ed236347671b", + "status": "submitted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + }, + "source": "structure", + }, + Object { + "captureModelId": "143d9c4a-5d4e-4ca2-89f3-dc4bd7b45e3e", + "document": Object { + "id": "47e8a9d8-76f8-422b-91af-b457d1c624a0", + "label": "Name of entity", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": true, + "id": "2666cf79-ef2f-419f-a3f4-038216a89783", + "label": "Transcription", + "revises": "1615a172-b2c5-4192-bcc1-606a871b6230", + "revision": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "type": "text-field", + "value": "Person A created this one, which revises the original", + }, + Object { + "allowMultiple": true, + "id": "912683e3-fd6e-4599-bb0f-acd232c9cf87", + "label": "Transcription", + "revises": "2666cf79-ef2f-419f-a3f4-038216a89783", + "revision": "896c6278-655a-4a39-965c-08abbffb0bf2", + "type": "text-field", + "value": "Person D created this one, which overrides Person As one", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "transcription", + ], + "id": "896c6278-655a-4a39-965c-08abbffb0bf2", + "revises": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "status": "submitted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + }, + "source": "structure", + }, + ] + `); + }); + + test('ames', () => { + expect(captureModelToRevisionList(ames02 as CaptureModel, true)).toMatchInlineSnapshot(` + Array [ + Object { + "captureModelId": "5aff4557-b331-4181-9030-1097f5220b67", + "document": Object { + "description": "Entities and fields that can be used to annotate court proceeding documents", + "id": "a539ffda-aaec-4885-bc6c-a01cebeed1b4", + "label": "Early Court Records", + "properties": Object { + "Nation": Array [ + Object { + "id": "2ea4f20c-86ff-4da6-8b81-8e38abd532e3", + "label": "Nation", + "properties": Object { + "Early America": Array [ + Object { + "id": "172d059a-ffe9-4568-a35f-b5de60538a59", + "label": "Early America", + "properties": Object { + "Massachusetts": Array [ + Object { + "id": "9cf9ca8e-2e2f-4f25-9c91-4cf714067d03", + "label": "Massachusetts", + "properties": Object { + "CourtProceeding": Array [ + Object { + "id": "4368c4a8-ef38-4a25-95e4-3420af5ca2ee", + "label": "CourtProceeding", + "properties": Object { + "CaseName": Array [ + Object { + "description": "The name that a case would be commonly called, such as Jones v. Smith", + "id": "c19add98-ea9a-4033-8e47-d71d377ddddf", + "label": "Case Name", + "selector": Object { + "id": "951fe3ba-b39d-4c16-93b6-ea1069bd60a5", + "state": null, + "type": "box-selector", + }, + "type": "text-field", + "value": "", + }, + ], + "ColonyName": Array [ + Object { + "description": "The name of the colony in which the court proceeding took place.", + "id": "a7680c67-b616-45f2-b925-b016c644e4a0", + "label": "Colony Name", + "type": "text-field", + "value": "", + }, + ], + "CourtProceedingBeginMonth": Array [ + Object { + "description": "The month of the year in which the court began its session.", + "id": "0baa18ce-231f-4122-83e5-fe5e9478e2b2", + "label": "CourtTermBeginMonth", + "type": "text-field", + "value": "", + }, + ], + "CourtProceedingType": Array [ + Object { + "clearable": true, + "description": "Indicates what kind of proceeding is reflected in the court record, what exactly the court is doing.", + "id": "d28140bf-238f-48d0-af77-02c5bc41e223", + "label": "Court Proceeding Type", + "options": Array [ + Object { + "text": "Admission of Attorneys", + "value": "Admission of Attorneys", + }, + Object { + "text": "Appeal [judges only]", + "value": "Appeal [judges only]", + }, + Object { + "text": "Appeal by Review", + "value": "Appeal by Review", + }, + Object { + "text": "Appeal in Chancery", + "value": "Appeal in Chancery", + }, + Object { + "text": "Appeal with Jury", + "value": "Appeal with Jury", + }, + Object { + "text": "Appeal with Referee", + "value": "Appeal with Referee", + }, + Object { + "text": "Hearing - Petition", + "value": "Hearing - Petition", + }, + Object { + "text": "Hearing - Petition for sale of real estate", + "value": "Hearing - Petition for sale of real estate", + }, + Object { + "text": "Hearing - Petition for division of real estate", + "value": "Hearing - Petition for division of real estate", + }, + Object { + "text": "Naturalization", + "value": "Naturalization", + }, + Object { + "text": "Trial of First Instance", + "value": "Trial of First Instance", + }, + Object { + "text": "Other", + "value": "Other", + }, + ], + "type": "dropdown-field", + "value": null, + }, + ], + "CourtTermBeginYear": Array [ + Object { + "description": "The year in which the court began a session.", + "id": "6545da7e-1bad-4b9e-afbf-221d3e17320d", + "label": "Court Term Begin Year", + "type": "text-field", + "value": "", + }, + ], + "CourtTermCity": Array [ + Object { + "description": "The city in which the court held a session.", + "id": "80cbcf7c-c89c-400b-a971-8b3c897a3fb8", + "label": "Court Term City", + "type": "text-field", + "value": "", + }, + ], + "OtherCourtProceedingType": Array [ + Object { + "description": "A court proceeding type that is no a common proceeding, but has been noted in the court record.", + "id": "d4e53036-be4e-4b3c-9d1f-7ca6dc1b3962", + "label": "Other Court Proceeding Type", + "type": "text-field", + "value": "", + }, + ], + "ProceedingID": Array [ + Object { + "description": "The unique identifier of the court proceeding in the format XXYYY-zzzzzzzzz, where + XX = colony abbreviation (i.e., MA for Massachusetts), + YYY = tribunal (i.e., SCJ for Superior Court of Judicature) (may be more or less that 3 characters), + zzzzzzzzz = incremental number beginning with 000000001", + "id": "e8bc6b2a-0853-445b-b592-1eb1df038ad3", + "label": "Court Proceeding ID", + "type": "text-field", + "value": "", + }, + ], + "TribunalName": Array [ + Object { + "description": "The name of the court in which the proceeding occurred.", + "id": "a86ac9fc-031a-4424-b86d-1d7642800122", + "label": "Tribunal Name", + "type": "text-field", + "value": "", + }, + ], + "VariantCaseName": Array [ + Object { + "description": "An alternate name for the case, as found in the court record.", + "id": "5a21cf64-b799-4370-b356-fe3305b5fcb8", + "label": "Variant Case Name", + "selector": Object { + "id": "887f2af0-912f-499a-ac03-46f486ff27db", + "state": null, + "type": "box-selector", + }, + "type": "text-field", + "value": "", + }, + ], + "VolumeName": Array [ + Object { + "description": "The name of the volume containing the record of the court proceeding.", + "id": "27fd80ed-971e-4220-b4a4-4d75d44c36a1", + "label": "VolumeName", + "type": "text-field", + "value": "", + }, + ], + "VolumePage": Array [ + Object { + "description": "Identifies the specific page within a volume of court proceedings, i.e., 31 recto for front side of page 31 or 31 verso for backside of page 31.", + "id": "45565ffa-211a-4dba-be4e-95995be9f87d", + "label": "Volume Page", + "type": "text-field", + "value": "", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + "modelRoot": undefined, + "revision": Object { + "approved": true, + "fields": Array [ + Array [ + "Nation", + Array [ + Array [ + "Early America", + Array [ + Array [ + "Massachusetts", + Array [ + Array [ + "CourtProceeding", + Array [ + "ProceedingID", + "ColonyName", + "TribunalName", + "VolumeName", + "VolumePage", + "CourtProceedingBeginMonth", + "CourtTermBeginYear", + "CourtTermCity", + "CaseName", + "VariantCaseName", + "CourtProceedingType", + "OtherCourtProceedingType", + ], + ], + ], + ], + ], + ], + ], + ], + ], + "id": "4f8d8d46-1e27-4d77-a33e-56a37992487d", + "label": "Court Proceeding ID", + "structureId": "4f8d8d46-1e27-4d77-a33e-56a37992487d", + }, + "source": "canonical", + }, + ] + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/create-new-entity-instance.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/create-new-entity-instance.test.ts new file mode 100644 index 000000000..01268a325 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/create-new-entity-instance.test.ts @@ -0,0 +1,151 @@ +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id', () => { + return { + __esModule: true, + generateId() { + return '[auto-generated]'; + }, + }; +}); + +import { createNewEntityInstance } from '../../../src/frontend/shared/capture-models/helpers/create-new-entity-instance'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../../src/frontend/shared/capture-models/types/field-types'; + +registerField({ + type: 'JEST_CUSTOM_FIELD', + defaultValue: '', +} as any); + +describe('create-new-entity-instance', () => { + test('Simple nuke value', () => { + expect( + createNewEntityInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + id: 'entity-1', + label: 'Some nested entity', + type: 'entity', + allowMultiple: true, + properties: { + label: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'Test field A', + value: 'Testing a value', + id: 'field-1', + } as BaseField, + ], + }, + } as CaptureModel['document'], + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "immutable": false, + "label": "Some nested entity", + "properties": Object { + "label": Array [ + Object { + "id": "[auto-generated]", + "label": "Test field A", + "type": "JEST_CUSTOM_FIELD", + "value": "", + }, + ], + }, + "type": "entity", + } + `); + }); + test('2x2 entity', () => { + expect( + createNewEntityInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + id: 'entity-1', + label: 'Some nested entity', + allowMultiple: true, + type: 'entity', + properties: { + label: [ + { + type: 'JEST_CUSTOM_FIELD', + allowMultiple: true, + label: 'Test field A', + value: 'Testing a value', + id: 'field-1', + } as BaseField, + { + type: 'JEST_CUSTOM_FIELD', + allowMultiple: true, + label: 'NOT THIS ONE', + value: 'NOT THIS ONE', + id: 'field-2', + } as BaseField, + ], + }, + } as CaptureModel['document'], + { + id: 'entity-2', + label: 'NOT THIS ONE', + type: 'entity', + properties: { + label: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'NOT THIS ONE', + value: 'NOT THIS ONE', + id: 'field-3', + } as BaseField, + { + type: 'JEST_CUSTOM_FIELD', + label: 'NOT THIS ONE', + value: 'NOT THIS ONE', + id: 'field-4', + } as BaseField, + ], + }, + } as CaptureModel['document'], + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "immutable": false, + "label": "Some nested entity", + "properties": Object { + "label": Array [ + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "label": "Test field A", + "type": "JEST_CUSTOM_FIELD", + "value": "", + }, + ], + }, + "type": "entity", + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/create-new-field-instance.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/create-new-field-instance.test.ts new file mode 100644 index 000000000..ed55f3df5 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/create-new-field-instance.test.ts @@ -0,0 +1,159 @@ +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id', () => { + return { + __esModule: true, + generateId() { + return '[auto-generated]'; + }, + }; +}); + +import { createNewFieldInstance } from '../../../src/frontend/shared/capture-models/helpers/create-new-field-instance'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { BaseField } from '../../../src/frontend/shared/capture-models/types/field-types'; + +registerField({ + type: 'JEST_CUSTOM_FIELD', + defaultValue: '', +} as any); + +describe('create-new-field-instance', () => { + test('Simple nuke value', () => { + expect( + createNewFieldInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'Test field A', + allowMultiple: true, + value: 'Testing a value', + id: '1', + } as BaseField, + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "label": "Test field A", + "type": "JEST_CUSTOM_FIELD", + "value": "", + } + `); + }); + test('Should only fork first value', () => { + expect( + createNewFieldInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'Test field A', + allowMultiple: true, + value: 'Testing a value', + id: '1', + } as BaseField, + { type: 'INVALID', label: 'IGNORED', allowMultiple: true, value: 'IGNORED', id: '2' } as BaseField, + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "label": "Test field A", + "type": "JEST_CUSTOM_FIELD", + "value": "", + } + `); + }); + + test('Should change ID of selector', () => { + expect( + createNewFieldInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'Test field A', + value: 'Testing a value', + allowMultiple: true, + id: '1', + selector: { id: 'selector-1', type: 'box-selector', state: null }, + } as BaseField, + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "label": "Test field A", + "selector": Object { + "id": "[auto-generated]", + "state": null, + "type": "box-selector", + }, + "type": "JEST_CUSTOM_FIELD", + "value": "", + } + `); + }); + + test('Should reset value of selector', () => { + expect( + createNewFieldInstance( + { + id: '1', + type: 'entity', + label: 'Some entity', + properties: { + test: [ + { + type: 'JEST_CUSTOM_FIELD', + label: 'Test field A', + value: 'Testing a value', + allowMultiple: true, + id: '1', + selector: { id: 'selector-1', type: 'box-selector', state: { x: 0, y: 0, width: 100, height: 100 } }, + } as BaseField, + ], + }, + }, + 'test' + ) + ).toMatchInlineSnapshot(` + Object { + "allowMultiple": true, + "id": "[auto-generated]", + "label": "Test field A", + "selector": Object { + "id": "[auto-generated]", + "state": null, + "type": "box-selector", + }, + "type": "JEST_CUSTOM_FIELD", + "value": "", + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/create-revision.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/create-revision.test.ts new file mode 100644 index 000000000..e4cab5d12 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/create-revision.test.ts @@ -0,0 +1,984 @@ +/** + * @jest-environment jsdom + */ + +import * as React from 'react'; +import { captureModelToRevisionList } from '../../../src/frontend/shared/capture-models/helpers/capture-model-to-revision-list'; +import { forkDocument } from '../../../src/frontend/shared/capture-models/helpers/create-revision-document'; +import { filterDocumentGraph } from '../../../src/frontend/shared/capture-models/helpers/filter-document-graph'; +import { splitDocumentByModelRoot } from '../../../src/frontend/shared/capture-models/helpers/split-document-by-model-root'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +// Mocked field. +registerField({ + type: 'text-field', + allowMultiple: true, + defaultValue: '', + TextPreview: props => props.value, + Component: () => React.createElement(React.Fragment), + defaultProps: {}, + description: '', + Editor: () => React.createElement(React.Fragment), + label: '', +}); + +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id'); +const { generateId } = require('../../../src/frontend/shared/capture-models/helpers/generate-id'); + +generateId.mockImplementation(() => '[--------GENERATED-ID--------]'); + +describe('create revision', () => { + const captureModel: CaptureModel = require('../../../fixtures/03-revisions/06-model-root.json'); + const [ + createFieldA, + editModelA, + editModelAWithRoot, + fieldAboveRoot, + modelCNoModelRoot, + modelCNoModelRootDepth1, + modelCNoModelRootDepth2, + modelCNoModelRootDepth1FieldUnder, + modelCNoModelRootDepth1FieldOver, + modelCNoModelRootDepth2FieldOver, + modelCNoModelRootDepth2FieldOver2x, + editModelBDepth1, + editModelBDepth2, + editModelBDepth3, + modelFDepth1, + modelFDepth2, + modelIDepth0, + modelIDepth1, + modelIDepth2, + ] = captureModelToRevisionList(captureModel, true); + + describe('splitDocumentByModelRoot', () => { + test('top level', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document); + + expect(immutableDocuments).toHaveLength(0); + expect(modelRootDocuments).toHaveLength(1); + expect(modelRootDocuments[0].parent).toBeUndefined(); + expect(modelRootDocuments[0].documents).toHaveLength(1); + expect(modelRootDocuments[0].documents[0].id).toEqual('3353dc03-9f35-49e7-9b81-4090fa533c64'); + }); + + test('single depth', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-a']); + + expect(immutableDocuments).toHaveLength(1); + expect(modelRootDocuments).toHaveLength(1); + expect(immutableDocuments[0].documents).toHaveLength(1); + expect(immutableDocuments[0].documents[0].id).toEqual('3353dc03-9f35-49e7-9b81-4090fa533c64'); + }); + + test('second depth', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document, [ + 'model-a', + 'model-c', + ]); + + expect(immutableDocuments).toHaveLength(2); + expect(modelRootDocuments).toHaveLength(1); + }); + + test('third depth', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document, [ + 'model-b', + 'model-c', + 'model-d', + ]); + + expect(immutableDocuments).toHaveLength(3); + expect(modelRootDocuments).toHaveLength(1); + }); + + test('second depth - with 2 items', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document, [ + 'model-f', + 'model-g', + ]); + + expect(immutableDocuments).toHaveLength(2); + expect(immutableDocuments[0].documents).toHaveLength(1); + expect(modelRootDocuments).toHaveLength(1); + expect(modelRootDocuments[0].documents).toHaveLength(2); + }); + + test('second depth - with adjacent models', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot(captureModel.document, [ + 'model-h', + 'model-i', + ]); + + expect(immutableDocuments).toHaveLength(2); + expect(immutableDocuments[0].documents).toHaveLength(1); + expect(immutableDocuments[1].documents).toHaveLength(2); + expect(modelRootDocuments).toHaveLength(2); + expect(modelRootDocuments[0].documents).toHaveLength(2); + expect(modelRootDocuments[1].documents).toHaveLength(2); + + expect(modelRootDocuments[0].documents[0].id).toEqual('ecbd3446-6da3-4125-934f-1a3b766b8afe'); + expect(modelRootDocuments[0].documents[1].id).toEqual('7de07c37-6157-4d63-8097-d2d4a78b778f'); + expect(modelRootDocuments[1].documents[0].id).toEqual('3c5abf69-5258-4b0f-a082-a7e53ec15c47'); + expect(modelRootDocuments[1].documents[1].id).toEqual('915106fd-f663-4e55-a727-8253e251c62d'); + }); + + test('invalid depth', () => { + expect(() => splitDocumentByModelRoot(captureModel.document, ['model-a', 'model-c', 'NOT EXIST'])).toThrow( + 'Invalid modelRoot provided' + ); + }); + + test('Fixture - Edit model C - model root depth 2', () => { + const [immutableDocuments, modelRootDocuments] = splitDocumentByModelRoot( + modelCNoModelRootDepth2.document, + modelCNoModelRootDepth2.modelRoot + ); + + expect(immutableDocuments).toHaveLength(2); + expect(modelRootDocuments).toHaveLength(1); + }); + }); + + describe('filterDocumentGraph', () => { + test('single depth', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-h', 'model-i']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + }); + + expect(documentsToRemove).toHaveLength(1); + expect(documentsToRemove[0].id).toEqual('ab70224e-4cb8-4d8d-a379-9769ad3e06e5'); + + expect(documentsToKeep).toHaveLength(1); + expect(documentsToKeep[0].id).toEqual('aa6fe309-deef-4211-802c-dbc0f2ba16ed'); + }); + + test('no filters', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-h', 'model-i']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, {}); + + expect(documentsToRemove).toHaveLength(0); + + expect(documentsToKeep).toHaveLength(2); + expect(documentsToKeep[0].id).toEqual('ab70224e-4cb8-4d8d-a379-9769ad3e06e5'); + expect(documentsToKeep[1].id).toEqual('aa6fe309-deef-4211-802c-dbc0f2ba16ed'); + }); + + test('depth 3, no filters', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-b', 'model-c', 'model-d']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, {}); + + expect(documentsToRemove).toHaveLength(0); + + expect(documentsToKeep).toHaveLength(1); + expect(documentsToKeep[0].id).toEqual('b51eae75-e76f-496f-b048-f0b5b7f6cacd'); + }); + + test('depth 3, single filter', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-b', 'model-c', 'model-d']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, { + 'model-b': 'd948d756-14ad-4554-92ce-0296e5ce8735', + }); + + expect(documentsToRemove).toHaveLength(0); + + expect(documentsToKeep).toHaveLength(1); + expect(documentsToKeep[0].id).toEqual('d948d756-14ad-4554-92ce-0296e5ce8735'); + + expect(documentsToRemove).toHaveLength(0); + }); + + test('depth 3, double filter', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-b', 'model-c', 'model-d']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, { + 'model-b': 'd948d756-14ad-4554-92ce-0296e5ce8735', + 'model-c': 'b51eae75-e76f-496f-b048-f0b5b7f6cacd', + }); + + expect(documentsToRemove).toHaveLength(0); + + expect(documentsToKeep).toHaveLength(1); + expect(documentsToKeep[0].id).toEqual('b51eae75-e76f-496f-b048-f0b5b7f6cacd'); + expect(documentsToRemove).toHaveLength(0); + }); + + test('depth 3, single middle filter', () => { + const [immutableDocuments] = splitDocumentByModelRoot(captureModel.document, ['model-b', 'model-c', 'model-d']); + + const [documentsToKeep, documentsToRemove] = filterDocumentGraph(immutableDocuments, { + 'model-c': 'b51eae75-e76f-496f-b048-f0b5b7f6cacd', + }); + + expect(documentsToRemove).toHaveLength(0); + + expect(documentsToKeep).toHaveLength(1); + expect(documentsToKeep[0].id).toEqual('b51eae75-e76f-496f-b048-f0b5b7f6cacd'); + expect(documentsToRemove).toHaveLength(0); + }); + }); + + describe('Single dimension revision creation (non-adjacent)', () => { + test('Edit field A', () => { + // Everything above model-i is immutable + // Only the model-h selected should be in the form (H1) + // Capture model should be filtered, as per normal capture models. + expect( + forkDocument(createFieldA.document, { + modelRoot: createFieldA.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + + describe('Edit model a', () => { + test('Fork template should fork at Model A.1 with nuked values throughout', () => { + expect( + forkDocument(editModelA.document, { + modelRoot: editModelA.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should create a new document with pre-filled fields and different ID', () => { + expect( + forkDocument(editModelA.document, { + modelRoot: editModelA.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should return the original documents', () => { + expect( + forkDocument(editModelA.document, { + modelRoot: editModelA.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model a - with model root', () => { + test('Fork template should fork at Model A.1 with nuked values throughout', () => { + expect( + forkDocument(editModelAWithRoot.document, { + modelRoot: editModelAWithRoot.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + + test('Fork values should create a new document with pre-filled fields and different ID', () => { + expect( + forkDocument(editModelAWithRoot.document, { + modelRoot: editModelAWithRoot.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should return the original documents', () => { + expect( + forkDocument(editModelAWithRoot.document, { + modelRoot: editModelAWithRoot.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model a - model root and field above root', () => { + // Come back to this one. + test('This should throw as an invalid structure because the field is above the root', () => { + // @todo this may be invalid, but the model is holding! + expect( + forkDocument(fieldAboveRoot.document, { + modelRoot: fieldAboveRoot.modelRoot, + revisionId: 'REVISION-ID', + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - no model root', () => { + test('Fork template should fork at Model A.1 with nuked values throughout', () => { + expect( + forkDocument(modelCNoModelRoot.document, { + modelRoot: modelCNoModelRoot.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should fork at Model A.1 with the same values but new ids', () => { + expect( + forkDocument(modelCNoModelRoot.document, { + modelRoot: modelCNoModelRoot.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should return the original documents', () => { + expect( + forkDocument(modelCNoModelRoot.document, { + modelRoot: modelCNoModelRoot.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 1', () => { + test('Fork template should fork at Model A.1 with nuked values throughout', () => { + expect( + forkDocument(modelCNoModelRootDepth1.document, { + modelRoot: modelCNoModelRootDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should fork at Model A.1 with the same values but new ids', () => { + expect( + forkDocument(modelCNoModelRootDepth1.document, { + modelRoot: modelCNoModelRootDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should return the original documents', () => { + expect( + forkDocument(modelCNoModelRootDepth1.document, { + modelRoot: modelCNoModelRootDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 2', () => { + test('Fork template should fork at Model C.1 with nuked values throughout', () => { + expect( + forkDocument(modelCNoModelRootDepth2.document, { + modelRoot: modelCNoModelRootDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should fork at Model C.1 with the same values but new ids', () => { + expect( + forkDocument(modelCNoModelRootDepth2.document, { + modelRoot: modelCNoModelRootDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should return the original documents', () => { + expect( + forkDocument(modelCNoModelRootDepth2.document, { + modelRoot: modelCNoModelRootDepth2.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 1 + field under', () => { + test('Fork template should create new field-b and model-c', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldUnder.document, { + modelRoot: modelCNoModelRootDepth1FieldUnder.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should create new field-b and model-c with values', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldUnder.document, { + modelRoot: modelCNoModelRootDepth1FieldUnder.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should set values immutable until field-b and model-c', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldUnder.document, { + modelRoot: modelCNoModelRootDepth1FieldUnder.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 1 + field over', () => { + test('Fork template should keep model a immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldOver.document, { + modelRoot: modelCNoModelRootDepth1FieldOver.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should keep model a immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldOver.document, { + modelRoot: modelCNoModelRootDepth1FieldOver.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should should keep model a immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth1FieldOver.document, { + modelRoot: modelCNoModelRootDepth1FieldOver.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 2 + field over', () => { + test('Fork template should keep both models immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver.document, { + modelRoot: modelCNoModelRootDepth2FieldOver.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should keep both models immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver.document, { + modelRoot: modelCNoModelRootDepth2FieldOver.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should should keep both models immutable, but keep the field over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver.document, { + modelRoot: modelCNoModelRootDepth2FieldOver.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model C - model root depth 2 + field over (x2)', () => { + test('Fork template should keep model a immutable, but keep all 2 fields over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver2x.document, { + modelRoot: modelCNoModelRootDepth2FieldOver2x.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values should keep model a immutable, but keep all 2 fields over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver2x.document, { + modelRoot: modelCNoModelRootDepth2FieldOver2x.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values should should keep model a immutable, but keep all 2 fields over mutable', () => { + expect( + forkDocument(modelCNoModelRootDepth2FieldOver2x.document, { + modelRoot: modelCNoModelRootDepth2FieldOver2x.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + }); + + describe('Multi-dimensional revision creation (adjacent at all levels)', () => { + describe('Edit model B - edit nested field (no duplicate allow) [model-b]', () => { + test('Fork template, model-b should be immutable, the rest mutable and forked', () => { + expect( + forkDocument(editModelBDepth1.document, { + modelRoot: editModelBDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - model-b should be immutable, the rest mutable with values', () => { + expect( + forkDocument(editModelBDepth1.document, { + modelRoot: editModelBDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - model-b should be immutable, the rest should be the same', () => { + expect( + forkDocument(editModelBDepth1.document, { + modelRoot: editModelBDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model B - edit nested field (root on allowMultiple=false)', () => { + test('Fork template, no models should be forked, since allowMultiple is in the root', () => { + expect( + forkDocument(editModelBDepth2.document, { + modelRoot: editModelBDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - model-b and c should be immutable, the rest mutable with values', () => { + expect( + forkDocument(editModelBDepth2.document, { + modelRoot: editModelBDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - model-b and c should be immutable, the rest should be the same', () => { + expect( + forkDocument(editModelBDepth2.document, { + modelRoot: editModelBDepth2.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model B - depth 3', () => { + test('Fork template, should...', () => { + expect( + forkDocument(editModelBDepth3.document, { + modelRoot: editModelBDepth3.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should...', () => { + expect( + forkDocument(editModelBDepth3.document, { + modelRoot: editModelBDepth3.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should...', () => { + expect( + forkDocument(editModelBDepth3.document, { + modelRoot: editModelBDepth3.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + // modelMapping: { + // 'model-g': '20254fca-fdad-4c70-b748-590f206e51d8', + // }, + + describe('Edit model F - depth 1', () => { + test('Fork template, should only contain 1 model', () => { + expect( + forkDocument(modelFDepth1.document, { + modelRoot: modelFDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain ??? model', () => { + expect( + forkDocument(modelFDepth1.document, { + modelRoot: modelFDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelFDepth1.document, { + modelRoot: modelFDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Edit model F - depth 2', () => { + test('Fork template, should only contain 1 model matching ID', () => { + expect( + forkDocument(modelFDepth2.document, { + modelRoot: modelFDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + modelMapping: { + 'model-g': '1a7e07f0-f111-406e-972e-71976258a47f', + }, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain ??? model', () => { + const forkedValues = forkDocument(modelFDepth2.document, { + modelRoot: modelFDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-g': '1a7e07f0-f111-406e-972e-71976258a47f', + }, + }); + + // This is what's wrong with the forked values here. + expect( + (forkedValues as any).properties['model-f'][0].properties['model-g'][0].properties['field-f'][0].revises + ).toBeUndefined(); + + expect( + forkDocument(modelFDepth2.document, { + modelRoot: modelFDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-g': '1a7e07f0-f111-406e-972e-71976258a47f', + }, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelFDepth2.document, { + modelRoot: modelFDepth2.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + + describe('Model H - 2x2 models', () => { + describe('test model-h depth 0', () => { + test('Fork template, should only contain one model at each level', () => { + expect( + forkDocument(modelIDepth0.document, { + modelRoot: modelIDepth0.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models, with values but no revises', () => { + expect( + forkDocument(modelIDepth0.document, { + modelRoot: modelIDepth0.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelIDepth0.document, { + modelRoot: modelIDepth0.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + describe('test model-h depth 1', () => { + test('Fork template, should only contain one model at each level', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models, with values but no revises', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + }) + ).toMatchSnapshot(); + }); + }); + describe('test model-h depth 1 - with field value at depth below should _not_ filter', () => { + test('Fork template, should only contain one model at each level, not any special selection', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + modelMapping: { + 'model-i': '7de07c37-6157-4d63-8097-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models, and not filter', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-i': '7de07c37-6157-4d63-809 7-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + modelMapping: { + 'model-i': '7de07c37-6157-4d63-8097-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + }); + describe('test model-h depth 1 - with field value at first depth', () => { + test('Fork template, should only contain one model at each level, using chosen model H', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + }, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models under model h, with values but no revises', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + }, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models under chosen model h', () => { + expect( + forkDocument(modelIDepth1.document, { + modelRoot: modelIDepth1.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + }, + }) + ).toMatchSnapshot(); + }); + }); + describe('test model-h depth 2 - with field value at all depths', () => { + test('Fork template, should only contain one model at each level', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '915106fd-f663-4e55-a727-8253e251c62d', + }, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models, with values but no revises', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '915106fd-f663-4e55-a727-8253e251c62d', + }, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '915106fd-f663-4e55-a727-8253e251c62d', + }, + }) + ).toMatchSnapshot(); + }); + }); + describe('test model-h depth 2 - with field value at different depths – invalid state', () => { + test('Fork template, should only contain one model at each level', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '7de07c37-6157-4d63-8097-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + test('Fork values - should only contain all models, with values but no revises', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + removeValues: false, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '7de07c37-6157-4d63-8097-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + test('Edit values - should contain all models', () => { + expect( + forkDocument(modelIDepth2.document, { + modelRoot: modelIDepth2.modelRoot, + revisionId: 'REVISION-ID', + editValues: true, + modelMapping: { + 'model-h': 'aa6fe309-deef-4211-802c-dbc0f2ba16ed', + 'model-i': '7de07c37-6157-4d63-8097-d2d4a78b778f', + }, + }) + ).toMatchSnapshot(); + }); + }); + }); + + // Documentation + // - immutable based on model root + // - Cloning first OR provided model id for forks + // - Editing any using provided model id + // - Fields above the model root + + // Based on missing features. + test.todo('Test `editableAboveRoot` option should make fields above root immutable'); + test.todo('Test `preventAdditionsAdjacentToRoot` option should make entities at root immutable'); + + // Based on coverage. + test.todo('Filtering documents using filterDocumentGraph for immutable top half'); + test.todo('Allow multiple immutable top level documents'); + test.todo('Check error testing for 0 mutable documents'); + test.todo('Check that when a parent mutable document is removed, the child is too'); + test.todo('Check for when plugin does not exist for field, that it is filtered out'); + test.todo('Handling of parent branching and setting a new ID on the field as a result'); + }); + + describe('Revision with revised field', () => { + const multi = require('../../../fixtures/03-revisions/05-allow-multiple-transcriptions.json'); + test('Canonical revision should contain both types and filter out the empty', () => { + const [main] = captureModelToRevisionList(multi, true); + + expect(main.document.properties.transcription).toHaveLength(2); + }); + test('When forking a revision, only the canonical revisions should be used as a template (no revises)', () => { + const [main] = captureModelToRevisionList(multi, true); + + expect( + forkDocument(main.document, { + modelRoot: main.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + }).properties.transcription + ).toHaveLength(2); + }); + + test('Revising a revision that is not canonical should only return those fields with revises', () => { + const [, second] = captureModelToRevisionList(multi, true); + + expect( + forkDocument(second.document, { + modelRoot: second.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: false, + removeValues: false, + addRevises: true, // <-- this will be the case when a revision is revised, this decides if its a new person, or editing of a person. + }).properties.transcription[0].revises + ).toEqual('2666cf79-ef2f-419f-a3f4-038216a89783'); + }); + test('There should be a way to create a new empty transcription using fork template', () => { + const [main] = captureModelToRevisionList(multi, true); + + expect( + forkDocument(main.document, { + modelRoot: main.modelRoot, + revisionId: 'REVISION-ID', + removeDefaultValues: true, + addRevises: false, + }).properties.transcription + ).toHaveLength(1); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/filter-capture-model.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/filter-capture-model.test.ts new file mode 100644 index 000000000..52c3ba20c --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/filter-capture-model.test.ts @@ -0,0 +1,135 @@ +import { filterCaptureModel } from '../../../src/frontend/shared/capture-models/helpers/filter-capture-model'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +describe('filterCaptureModel', () => { + const personModel: CaptureModel = require('../../../fixtures/02-nesting/05-nested-model-multiple.json'); + test('non-filter entity maintains structure', () => { + const filtered = filterCaptureModel( + 'something', + personModel.document, + [ + ['person', 'firstName'], + ['person', 'lastName'], + ], + () => true + ); + + expect(filtered).toMatchInlineSnapshot(` + Object { + "description": "", + "id": "626422b1-abbf-4f46-b160-d9bb768b2e29", + "label": "Nested choices", + "properties": Object { + "person": Array [ + Object { + "allowMultiple": true, + "description": "Describe a person", + "id": "3036e4a5-c350-426b-82b5-8fafdfe55e27", + "label": "Person", + "labelledBy": "firstName", + "properties": Object { + "firstName": Array [ + Object { + "id": "1da67423-f4a1-49e6-8561-55c40be47c00", + "label": "First name", + "type": "text-field", + "value": "first first name", + }, + ], + "lastName": Array [ + Object { + "id": "6b7ce0c3-2a13-4ea3-a190-822fee80176b", + "label": "Last name", + "type": "text-field", + "value": "first last name", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "description": "Describe a person", + "id": "41cf9550-af77-4310-82ca-130141ed215d", + "label": "Person", + "labelledBy": "firstName", + "properties": Object { + "firstName": Array [ + Object { + "id": "f8678b43-f803-40b2-8e2e-fcc014631e1a", + "label": "First name", + "type": "text-field", + "value": "second first name", + }, + ], + "lastName": Array [ + Object { + "id": "12e92ef6-6ebc-49af-95b3-42718ac92c26", + "label": "Last name", + "type": "text-field", + "value": "second last name", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + } + `); + }); + + test('filter entity maintains structure', () => { + const filtered = filterCaptureModel('something', personModel.document, [['person', 'firstName']], () => true); + + expect(filtered).toMatchInlineSnapshot(` + Object { + "description": "", + "id": "626422b1-abbf-4f46-b160-d9bb768b2e29", + "label": "Nested choices", + "properties": Object { + "person": Array [ + Object { + "allowMultiple": true, + "description": "Describe a person", + "id": "3036e4a5-c350-426b-82b5-8fafdfe55e27", + "label": "Person", + "labelledBy": "firstName", + "properties": Object { + "firstName": Array [ + Object { + "id": "1da67423-f4a1-49e6-8561-55c40be47c00", + "label": "First name", + "type": "text-field", + "value": "first first name", + }, + ], + }, + "type": "entity", + }, + Object { + "allowMultiple": true, + "description": "Describe a person", + "id": "41cf9550-af77-4310-82ca-130141ed215d", + "label": "Person", + "labelledBy": "firstName", + "properties": Object { + "firstName": Array [ + Object { + "id": "f8678b43-f803-40b2-8e2e-fcc014631e1a", + "label": "First name", + "type": "text-field", + "value": "second first name", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/filter-empty-structures.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/filter-empty-structures.test.ts new file mode 100644 index 000000000..5431cb249 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/filter-empty-structures.test.ts @@ -0,0 +1,153 @@ +import { filterEmptyStructureFields } from '../../../src/frontend/shared/capture-models/helpers/filter-empty-structure-fields'; +import { filterEmptyStructures } from '../../../src/frontend/shared/capture-models/helpers/filter-empty-structures'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +const filterEmpty = require('../../../fixtures/06-invalid/01-empty-structure.json'); + +describe('filterEmptyStructures', () => { + test('filterEmptyStructureFields - non-nested', () => { + expect(filterEmptyStructureFields(['a', 'b', 'c'], { properties: { a: [], c: [{}] } } as any)).toEqual(['c']); + }); + + test('filterEmptyStructureFields - nested', () => { + expect( + filterEmptyStructureFields(['a', ['b', ['c', 'd']]], { + properties: { + a: [], // empty. + b: [ + { + type: 'entity', + properties: { + c: [], // empty. + d: [{}], // not empty. + }, + }, + ], + }, + } as any) + ).toEqual([['b', ['d']]]); + }); + + test('it can filter empty structures', () => { + const model = filterEmpty as CaptureModel; + + const structure = filterEmptyStructures(model); + + expect(structure).toMatchInlineSnapshot(` + Object { + "description": "Contains single choice with single text field", + "id": "cdee3083-5f74-43c1-9d80-d97349f5fa69", + "items": Array [ + Object { + "fields": Array [ + "label", + ], + "id": "f0c7f99c-9e4b-4211-8dc5-f94c1287690b", + "label": "Completely valid", + "type": "model", + }, + Object { + "fields": Array [ + "label", + ], + "id": "c9db4483-9421-4370-abab-2c24ee656de2", + "label": "Partially valid", + "type": "model", + }, + Object { + "description": "This is a nested choice", + "id": "983b5776-4a2a-4107-a50d-eab94f0d2c0e", + "items": Array [ + Object { + "fields": Array [ + "label", + ], + "id": "c40a1d4b-6bcc-4dd0-89f3-f8146a0837d0", + "label": "Nested - partially valid", + "type": "model", + }, + Object { + "fields": Array [ + "label", + ], + "id": "6961fa68-0886-4bbe-b680-9f6004a2fe12", + "label": "Nested - completely valid", + "type": "model", + }, + ], + "label": "Nested choice - partially valid", + "type": "choice", + }, + Object { + "description": "The second choice in the list", + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + "lastName", + ], + ], + ], + "id": "3cb849fe-e2fe-4d0d-87ab-dff1519c6c3c", + "label": "Valid Person", + "type": "model", + }, + Object { + "description": "The second choice in the list", + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + ], + ], + ], + "id": "bd34d28a-0299-4407-af0a-113f027bd3d0", + "label": "Partially valid person", + "type": "model", + }, + Object { + "description": "This is a nested choice", + "id": "0c1675fc-96d0-42f3-bfb9-f9f8fa56ffa0", + "items": Array [ + Object { + "description": "The second choice in the list", + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + "lastName", + ], + ], + ], + "id": "3404e64c-dae4-470a-a1b0-3f1d453c80c6", + "label": "Valid Person", + "type": "model", + }, + Object { + "description": "The second choice in the list", + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + ], + ], + ], + "id": "77a0b28d-d072-4862-a0d1-cb23d17f9fe5", + "label": "Partially valid person", + "type": "model", + }, + ], + "label": "Partially valid choice", + "type": "choice", + }, + ], + "label": "invalid - empty structure", + "type": "choice", + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/hydrate-partial-document.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/hydrate-partial-document.test.ts new file mode 100644 index 000000000..fc1b384fd --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/hydrate-partial-document.test.ts @@ -0,0 +1,147 @@ +/** + * @jest-environment jsdom + */ + +// @ts-ignore +// eslint-disable-next-line +if (global) { global.globalThis = global; } else { try { globalThis = typeof globalThis !== 'undefined' ? this : globalThis; } catch (e) {} } + +import * as React from 'react'; +import { captureModelToRevisionList } from '../../../src/frontend/shared/capture-models/helpers/capture-model-to-revision-list'; +import { hydratePartialDocument } from '../../../src/frontend/shared/capture-models/helpers/hydrate-partial-document'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +// Mocked field. +registerField({ + type: 'text-field', + allowMultiple: true, + defaultValue: '', + TextPreview: props => props.value, + Component: () => React.createElement(React.Fragment), + defaultProps: {}, + description: '', + Editor: () => React.createElement(React.Fragment), + label: '', +}); + +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id'); +const { generateId } = require('../../../src/frontend/shared/capture-models/helpers/generate-id'); + +const GENERATED_ID = '[--------GENERATED-ID--------]'; + +generateId.mockImplementation(() => GENERATED_ID); + +describe('hydratePartialDocument', () => { + const captureModel: CaptureModel = require('../../../fixtures/03-revisions/06-model-root.json'); + const [ + createFieldA, + editModelA, + editModelAWithRoot, + fieldAboveRoot, + modelCNoModelRoot, + modelCNoModelRootDepth1, + modelCNoModelRootDepth2, + modelCNoModelRootDepth1FieldUnder, + modelCNoModelRootDepth1FieldOver, + modelCNoModelRootDepth2FieldOver, + modelCNoModelRootDepth2FieldOver2x, + editModelBDepth1, + editModelBDepth2, + editModelBDepth3, + modelFDepth1, + modelFDepth2, + modelIDepth0, + modelIDepth1, + modelIDepth2, + modelB1Field, + ] = captureModelToRevisionList(captureModel, true); + + test('Addition of missing entity', () => { + const newDocument = hydratePartialDocument( + // @ts-ignore + editModelAWithRoot.document.properties['model-a'][0], + captureModel.document.properties['model-a'][0], + { + keepValues: true, + markAsImmutable: false, + clone: true, + } + ); + expect(newDocument).toHaveProperty(['properties', 'model-c']); + expect(newDocument).toHaveProperty(['properties', 'model-c', 0, 'id'], GENERATED_ID); + expect(newDocument).toHaveProperty(['properties', 'model-c', 0, 'properties', 'field-c']); + expect(newDocument).toHaveProperty(['properties', 'model-c', 0, 'properties', 'field-c', 0, 'id'], GENERATED_ID); + }); + + test('Addition of missing field', () => { + const newDocument = hydratePartialDocument( + // @ts-ignore + modelCNoModelRoot.document.properties['model-a'][0], + captureModel.document.properties['model-a'][0], + { + keepValues: true, + markAsImmutable: false, + clone: true, + } + ); + + // To have the original document + expect(newDocument).toHaveProperty(['properties', 'model-c']); + // And the missing field + expect(newDocument).toHaveProperty(['properties', 'field-b']); + expect(newDocument).toHaveProperty(['properties', 'field-b', 0, 'id'], GENERATED_ID); + }); + + test('Addition of 2nd depth field', () => { + const newDocument = hydratePartialDocument( + // @ts-ignore + modelFDepth1.document.properties['model-f'][0], + captureModel.document.properties['model-f'][0], + { + keepValues: true, + markAsImmutable: false, + clone: true, + } + ); + // model-f, model-g, field-f + expect(newDocument).toHaveProperty(['properties', 'field-e']); + expect(newDocument).toHaveProperty(['properties', 'field-e', 0, 'id'], GENERATED_ID); + }); + + test('Addition of 2nd depth entity', () => { + const newDocument = hydratePartialDocument( + // @ts-ignore + modelB1Field.document.properties['model-b'][0], + captureModel.document.properties['model-b'][0], + { + keepValues: true, + markAsImmutable: false, + clone: true, + } + ); + expect(newDocument).toHaveProperty(['properties', 'field-d']); + expect(newDocument).toHaveProperty(['properties', 'field-d', 0, 'id'], GENERATED_ID); + expect(newDocument).toHaveProperty(['properties', 'model-c', 0, 'properties', 'field-e']); + expect(newDocument).toHaveProperty(['properties', 'model-c', 0, 'properties', 'field-e', 0, 'id'], GENERATED_ID); + }); + + test('Can skip hydrating the root', () => { + const newDocument = hydratePartialDocument( + // @ts-ignore + modelB1Field.document, + captureModel.document, + { + hydrateRoot: false, + keepValues: true, + markAsImmutable: false, + clone: true, + } + ); + expect(newDocument).not.toHaveProperty(['properties', 'model-a']); + expect(newDocument).not.toHaveProperty(['properties', 'model-h']); + }); + + test.todo('Error when property mismatch'); + test.todo('Error when no values in property from reference document'); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/ocr.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/ocr.test.ts new file mode 100644 index 000000000..cd7ae8043 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/ocr.test.ts @@ -0,0 +1,457 @@ +jest.mock('.../../../src/frontend/shared/capture-models/helpers/generate-id', () => { + return { + __esModule: true, + generateId() { + return '[auto-generated]'; + }, + }; +}); + +import * as React from 'react'; +import { createRevisionDocument } from '../../../src/frontend/shared/capture-models/helpers/create-revision-document'; +import { validateRevision } from '../../../src/frontend/shared/capture-models/helpers/validate-revision'; +import { traverseDocument } from '../../../src/frontend/shared/capture-models/helpers/traverse-document'; +import { registerField } from '../../../src/frontend/shared/capture-models/plugin-api/global-store'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../../src/frontend/shared/capture-models/types/field-types'; +import { RevisionRequest } from '../../../src/frontend/shared/capture-models/types/revision-request'; + +registerField({ + type: 'text-field', + allowMultiple: true, + defaultValue: '', + TextPreview: props => props.value, + Component: () => React.createElement(React.Fragment), + defaultProps: {}, + description: '', + Editor: () => React.createElement(React.Fragment), + label: '', +}); + +// import { fieldsToInserts } from '../../../database/src/utility/fields-to-inserts'; + +function getModel() { + return require('../../../fixtures/02-nesting/06-ocr.json') as CaptureModel; +} +function getRevisionA() { + return require('../../../fixtures/98-revision-requests/add-ocr-text.json') as RevisionRequest; +} + +describe('OCR Capture model', () => { + // Granularity + test('When I see a typo in a word, I can make a correction only for that single word', () => { + const model = getModel(); + const newRevisionId = '[generated]'; + const mode = 'FORK_SOME_VALUES'; + const modelRoot: string[] = ['transcription', 'lines']; + const modelMapping: any = { + transcription: 'b2867260-78e3-484f-8549-40a89a55d51c', // First paragraph. + lines: 'a9d8cae8-db25-4880-bc33-e4c0ff192939', // 4th Line. + }; + const fieldsToEdit = ['a29eb861-a893-4f2d-a3d2-e420ea405e4a']; + // How to do it in code? + const revisionDoc = createRevisionDocument( + newRevisionId, + model.document, + mode, + modelRoot, + modelMapping, + fieldsToEdit + ); + + expect(revisionDoc).toMatchInlineSnapshot(` + Object { + "id": "aa2344b4-a70b-41cf-aa62-5e211e76ee2c", + "immutable": true, + "label": "Untitled document", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": false, + "description": "Region of the page denoting a single paragraph", + "id": "b2867260-78e3-484f-8549-40a89a55d51c", + "immutable": true, + "label": "Paragraph", + "labelledBy": "lines", + "pluralLabel": "Paragraphs", + "properties": Object { + "lines": Array [ + Object { + "allowMultiple": false, + "description": "All of the lines inside of a paragraph", + "id": "a9d8cae8-db25-4880-bc33-e4c0ff192939", + "immutable": true, + "label": "Line", + "labelledBy": "text", + "pluralLabel": "Lines", + "properties": Object { + "text": Array [ + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revises": "a29eb861-a893-4f2d-a3d2-e420ea405e4a", + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 48, + "width": 265, + "x": 746, + "y": 2504, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "beglücken.", + }, + ], + }, + "selector": Object { + "id": "98751c14-a342-4958-9803-5e0bd5d11b6a", + "state": Object { + "height": 53, + "width": 1695, + "x": 312, + "y": 2499, + }, + "type": "box-selector", + }, + "type": "entity", + }, + ], + }, + "selector": Object { + "id": "750603ef-bf14-464a-b812-6540f53497dd", + "state": Object { + "height": 2582, + "width": 1730, + "x": 296, + "y": 310, + }, + "type": "box-selector", + }, + "type": "entity", + }, + ], + }, + "type": "entity", + } + `); + }); + test('When I see a few typos in a line, I can make a correction only for that line', () => { + // I want to replace the line entity. + const model = getModel(); + const newRevisionId = '[generated]'; + const mode = 'FORK_LISTED_VALUES'; + const modelRoot: string[] = ['transcription', 'lines']; + const modelMapping: any = { + transcription: 'b2867260-78e3-484f-8549-40a89a55d51c', // First paragraph. + lines: 'a9d8cae8-db25-4880-bc33-e4c0ff192939', // 4th Line. + }; + // How to do it in code? + const revisionDoc = createRevisionDocument(newRevisionId, model.document, mode, modelRoot, modelMapping); + + expect(revisionDoc).toMatchInlineSnapshot(` +Object { + "id": "aa2344b4-a70b-41cf-aa62-5e211e76ee2c", + "immutable": true, + "label": "Untitled document", + "properties": Object { + "transcription": Array [ + Object { + "allowMultiple": false, + "description": "Region of the page denoting a single paragraph", + "id": "b2867260-78e3-484f-8549-40a89a55d51c", + "immutable": true, + "label": "Paragraph", + "labelledBy": "lines", + "pluralLabel": "Paragraphs", + "properties": Object { + "lines": Array [ + Object { + "allowMultiple": false, + "description": "All of the lines inside of a paragraph", + "id": "[auto-generated]", + "immutable": false, + "label": "Line", + "labelledBy": "text", + "pluralLabel": "Lines", + "properties": Object { + "text": Array [ + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 39, + "width": 262, + "x": 312, + "y": 2503, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "bereidiern", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 38, + "width": 92, + "x": 617, + "y": 2504, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "und", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 48, + "width": 265, + "x": 746, + "y": 2504, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "beglücken.", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 37, + "width": 111, + "x": 1065, + "y": 2504, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "Man", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 43, + "width": 126, + "x": 1216, + "y": 2504, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "lernt,", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 37, + "width": 153, + "x": 1394, + "y": 2503, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "indem", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 27, + "width": 105, + "x": 1595, + "y": 2512, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "man", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 40, + "width": 99, + "x": 1739, + "y": 2499, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "aufs", + }, + Object { + "allowMultiple": true, + "description": "Single word, phrase or the whole line", + "id": "[auto-generated]", + "label": "Text of line", + "pluralField": "Text of lines", + "previewInline": true, + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 37, + "width": 131, + "x": 1876, + "y": 2500, + }, + "type": "box-selector", + }, + "type": "text-field", + "value": "beste", + }, + ], + }, + "revises": "a9d8cae8-db25-4880-bc33-e4c0ff192939", + "revision": "[generated]", + "selector": Object { + "id": "[auto-generated]", + "state": Object { + "height": 53, + "width": 1695, + "x": 312, + "y": 2499, + }, + "type": "box-selector", + }, + "type": "entity", + }, + ], + }, + "selector": Object { + "id": "750603ef-bf14-464a-b812-6540f53497dd", + "state": Object { + "height": 2582, + "width": 1730, + "x": 296, + "y": 310, + }, + "type": "box-selector", + }, + "type": "entity", + }, + ], + }, + "type": "entity", +} +`); + }); + test.todo('When I see a many typos I can make a correction for multiple lines'); + test.todo('When I want to replace the whole transcription, I can fork the whole model'); + + // Saving + test.todo('If I fork the whole transcription when I merge there still only exists one single transcription'); + test.todo('If I remove a field, the field will be removed when saving the model'); + test.todo('If I remove add a field, it will be place in the correct order.'); + + describe('saving revision', () => { + const captureModel = getModel(); + const req = getRevisionA(); + + test('validation', () => { + expect(() => + validateRevision(req, captureModel, { + allowAnonymous: true, + allowCanonicalChanges: false, + allowCustomStructure: true, + }) + ).not.toThrow(); + }); + + test('addingFields', () => { + const fieldsToAdd: Array<{ field: BaseField; term: string; parent: CaptureModel['document'] }> = []; + const docsToHydrate: Array<{ + entity: CaptureModel['document']; + term?: string; + parent?: CaptureModel['document']; + }> = []; + + traverseDocument(req.document, { + visitField(field, term, parent) { + if (parent.immutable) { + fieldsToAdd.push({ field, term, parent }); + } + }, + visitEntity(entity, term, parent) { + if (entity.immutable === false && parent && parent.immutable) { + docsToHydrate.push({ entity, term, parent }); + } + }, + }); + + expect(fieldsToAdd).toHaveLength(1); + expect(docsToHydrate).toHaveLength(0); + }); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/rdf-vocab.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/rdf-vocab.test.ts new file mode 100644 index 000000000..e95cdb598 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/rdf-vocab.test.ts @@ -0,0 +1,299 @@ +/** + * @jest-environment jsdom + */ +import { parseRdfVocab } from '../../../src/frontend/shared/capture-models/helpers/rdf-vocab/rdf-vocab'; + +describe('RDF Vocab', () => { + test('parse dublin core', () => { + const xml = ` + + + + + + + + + ]> + + + DCMI Metadata Terms - other + + 2012-06-14 + + + Title + A name given to the resource. + + 2008-01-14 + 2010-10-11 + + + + + + + Creator + An entity primarily responsible for making the resource. + Examples of a Creator include a person, an organization, or a service. + + + 2008-01-14 + 2010-10-11 + + + + + + + + + Subject + The topic of the resource. + Typically, the subject will be represented using keywords, key phrases, or + classification codes. Recommended best practice is to use a controlled vocabulary. + + + 2008-01-14 + 2012-06-14 + + + This term is intended to be used with non-literal values as defined in the DCMI + Abstract Model (http://dublincore.org/documents/abstract-model/). As of December 2007, the DCMI Usage Board + is seeking a way to express this intention with a formal range declaration. + + + + + Bibliographic Resource + A book, article, or other documentary resource. + + 2008-01-14 + + + + + File Format + A digital resource format. + Examples include the formats defined by the list of Internet Media Types. + + 2008-01-14 + + + + + + Frequency + A rate at which something recurs. + + 2008-01-14 + + + + + Jurisdiction + The extent or range of judicial, law enforcement, or other authority. + + 2008-01-14 + + + + + + `; + + expect(parseRdfVocab(xml)).toMatchInlineSnapshot(` + Object { + "classes": Array [ + Object { + "description": "A book, article, or other documentary resource.", + "label": "Bibliographic Resource", + "term": "dcterms:BibliographicResource", + "uri": "http://purl.org/dc/terms/BibliographicResource", + }, + Object { + "description": "A digital resource format.", + "label": "File Format", + "term": "dcterms:FileFormat", + "uri": "http://purl.org/dc/terms/FileFormat", + }, + Object { + "description": "A rate at which something recurs.", + "label": "Frequency", + "term": "dcterms:Frequency", + "uri": "http://purl.org/dc/terms/Frequency", + }, + Object { + "description": "The extent or range of judicial, law enforcement, or other authority.", + "label": "Jurisdiction", + "term": "dcterms:Jurisdiction", + "uri": "http://purl.org/dc/terms/Jurisdiction", + }, + ], + "namespaces": Object { + "dcam": "http://purl.org/dc/dcam/", + "dcterms": "http://purl.org/dc/terms/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "skos": "http://www.w3.org/2004/02/skos/core#", + }, + "properties": Array [ + Object { + "description": "A name given to the resource.", + "label": "Title", + "term": "dcterms:title", + "uri": "http://purl.org/dc/terms/title", + }, + Object { + "description": "An entity primarily responsible for making the resource.", + "label": "Creator", + "term": "dcterms:creator", + "uri": "http://purl.org/dc/terms/creator", + }, + Object { + "description": "The topic of the resource.", + "label": "Subject", + "term": "dcterms:subject", + "uri": "http://purl.org/dc/terms/subject", + }, + ], + } + `); + }); + + test('parse foaf', () => { + const xml = ` + + + + + + Label Property + A foaf:LabelProperty is any RDF property with texual values that serve as labels. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; + + expect(parseRdfVocab(xml)).toMatchInlineSnapshot(` + Object { + "classes": Array [ + Object { + "description": "A foaf:LabelProperty is any RDF property with texual values that serve as labels.", + "label": "Label Property", + "term": "foaf:LabelProperty", + "uri": "http://xmlns.com/foaf/0.1/LabelProperty", + }, + Object { + "description": "A person.", + "label": "Person", + "term": "foaf:Person", + "uri": "http://xmlns.com/foaf/0.1/Person", + }, + Object { + "description": null, + "label": "Person", + "term": "http://www.w3.org/2000/10/swap/pim/contact#Person", + "uri": "http://www.w3.org/2000/10/swap/pim/contact#Person", + }, + Object { + "description": null, + "label": "Spatial Thing", + "term": "http://www.w3.org/2003/01/geo/wgs84_pos#SpatialThing", + "uri": "http://www.w3.org/2003/01/geo/wgs84_pos#SpatialThing", + }, + ], + "namespaces": Object { + "dc": "http://purl.org/dc/elements/1.1/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "vs": "http://www.w3.org/2003/06/sw-vocab-status/ns#", + "wot": "http://xmlns.com/wot/0.1/", + }, + "properties": Array [ + Object { + "description": "The birthday of this Agent, represented in mm-dd string form, eg. '12-31'.", + "label": "birthday", + "term": "foaf:birthday", + "uri": "http://xmlns.com/foaf/0.1/birthday", + }, + Object { + "description": "The age in years of some agent.", + "label": "age", + "term": "foaf:age", + "uri": "http://xmlns.com/foaf/0.1/age", + }, + Object { + "description": "A string expressing what the user is happy for the general public (normally) to know about their current activity.", + "label": "status", + "term": "foaf:status", + "uri": "http://xmlns.com/foaf/0.1/status", + }, + ], + } + `); + }); +}); diff --git a/services/madoc-ts/__tests__/capture-models/helpers/revision-filter.test.ts b/services/madoc-ts/__tests__/capture-models/helpers/revision-filter.test.ts new file mode 100644 index 000000000..ba091d059 --- /dev/null +++ b/services/madoc-ts/__tests__/capture-models/helpers/revision-filter.test.ts @@ -0,0 +1,107 @@ +const single01 = require('../../../fixtures/03-revisions/01-single-field-with-revision.json'); +const single02 = require('../../../fixtures/03-revisions/02-single-field-with-multiple-revisions.json'); +const single03 = require('../../../fixtures/03-revisions/03-nested-revision.json'); +const single04 = require('../../../fixtures/03-revisions/04-dual-transcription.json'); +const single05 = require('../../../fixtures/03-revisions/05-allow-multiple-transcriptions.json'); +import { revisionFilter } from '../../../src/frontend/shared/capture-models/helpers/revision-filter'; +import { CaptureModel } from '../../../src/frontend/shared/capture-models/types/capture-model'; + +describe('revisionFilter', () => { + test('single-field-with-revision non-existent', () => { + expect(revisionFilter(single01 as CaptureModel, 'NOPE')).toMatchInlineSnapshot(`null`); + }); + test('single-field-with-revision', () => { + expect(revisionFilter(single01 as CaptureModel, '7c26cf57-5950-4849-b533-11e0ee4afa4b')).toMatchInlineSnapshot(` + Object { + "captureModelId": "b329e009-1c8a-4bed-bfde-c2a587a22f97", + "document": Object { + "description": "", + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "label": "Simple document", + "properties": Object { + "name": Array [ + Object { + "description": "The name of the thing", + "id": "eafb62d7-71b7-47bd-b887-def8655d8d2a", + "label": "Name", + "revision": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "type": "text-field", + "value": "Some value that was submitted", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + "name", + ], + "id": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "structureId": "31b27c9b-2388-47df-b6f4-73fb4878c1fa", + }, + "source": "structure", + } + `); + }); + test('single-field-with-multiple-revisions', () => { + expect(revisionFilter(single02 as CaptureModel, 'test-person-a')).toMatchInlineSnapshot(`null`); + expect(revisionFilter(single02 as CaptureModel, 'test-person-b')).toMatchInlineSnapshot(`null`); + }); + test('nested-revision', () => { + expect(revisionFilter(single03 as CaptureModel, 'fa500021-7408-4318-ab05-ac6e4d4a3096')).toMatchInlineSnapshot(` +Object { + "captureModelId": "2cc4131d-4f8d-4ceb-b140-48cd513b5e4f", + "document": Object { + "description": "", + "id": "a8d5ff43-adb2-456a-a615-3d24fbfa05f7", + "label": "Nested choices", + "properties": Object { + "person": Array [ + Object { + "description": "Describe a person", + "id": "5c8a5874-8bca-422c-be71-300612d67c72", + "label": "Person", + "properties": Object { + "firstName": Array [ + Object { + "id": "dda6d8bc-ca6d-48e0-8bcc-a24537586346", + "label": "First name", + "revision": "fa500021-7408-4318-ab05-ac6e4d4a3096", + "type": "text-field", + "value": "Some value", + }, + ], + }, + "type": "entity", + }, + ], + }, + "type": "entity", + }, + "revision": Object { + "fields": Array [ + Array [ + "person", + Array [ + "firstName", + "lastName", + ], + ], + "name", + ], + "id": "fa500021-7408-4318-ab05-ac6e4d4a3096", + }, + "source": "structure", +} +`); + }); + test('dual-transcription', () => { + expect(revisionFilter(single04 as any, 'test-person-a')).toMatchInlineSnapshot(`null`); + expect(revisionFilter(single04 as any, 'test-person-b')).toMatchInlineSnapshot(`null`); + }); + test('allow-multiple-transcriptions', () => { + expect(revisionFilter(single05 as any, 'test-person-a')).toMatchInlineSnapshot(`null`); + expect(revisionFilter(single05 as any, 'test-person-b')).toMatchInlineSnapshot(`null`); + expect(revisionFilter(single05 as any, 'test-person-c')).toMatchInlineSnapshot(`null`); + }); +}); diff --git a/services/madoc-ts/__tests__/routes/create-resource-claim.test.ts b/services/madoc-ts/__tests__/routes/create-resource-claim.test.ts index f78b34930..3ceef7155 100644 --- a/services/madoc-ts/__tests__/routes/create-resource-claim.test.ts +++ b/services/madoc-ts/__tests__/routes/create-resource-claim.test.ts @@ -1,4 +1,4 @@ -import { CaptureModel } from '@capture-models/types'; +import { CaptureModel } from '../../src/frontend/shared/capture-models/types/capture-model'; import { ProjectFull } from '../../src/types/project-full'; import { ApiMock } from '../../test-utility/api-mock'; import { DatabaseMock } from '../../test-utility/database-mock'; diff --git a/services/madoc-ts/fixtures/01-basic/01-single-field.json b/services/madoc-ts/fixtures/01-basic/01-single-field.json new file mode 100644 index 000000000..f73d2d79e --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/01-single-field.json @@ -0,0 +1,45 @@ +{ + "structure": { + "id": "b6098ae7-c474-4061-957f-358368f24213", + "type": "choice", + "label": "basic - single field", + "description": "Contains single choice with single text field", + "items": [ + { + "id": "e25a8ece-df85-4fc2-b6ad-0fd1006dd4c1", + "type": "model", + "label": "Metadata", + "fields": [ + "label" + ] + } + ] + }, + "target": [ + { + "type": "manifest", + "id": "https://view.nls.uk/manifest/7446/74464117/manifest.json" + }, + { + "type": "canvas", + "id": "https://view.nls.uk/iiif/7446/74464117/canvas/3" + } + ], + "document": { + "id": "0bc81bf9-807a-423e-a4b2-d8ada10de95a", + "type": "entity", + "label": "Label", + "labelledBy": "label", + "properties": { + "label": [ + { + "id": "9a98d760-56a3-4474-8080-346200910907", + "type": "text-field", + "label": "Name", + "value": "Forth Road Bridge" + } + ] + } + }, + "id": "f8302402-0c99-4eb9-88fe-9588f9366378" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/02-multiple-fields.json b/services/madoc-ts/fixtures/01-basic/02-multiple-fields.json new file mode 100644 index 000000000..6c6e3c2d9 --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/02-multiple-fields.json @@ -0,0 +1,45 @@ +{ + "structure": { + "id": "38102e81-04b2-4bfe-8096-3ac1c856368f", + "type": "choice", + "label": "basic - multiple fields", + "items": [ + { + "id": "8f60e6aa-0c1e-458b-a7ef-6de6996420d8", + "label": "Only choice - multiple fields", + "type": "model", + "fields": [ + "name", + "description" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with multiple single text fields" + }, + "document": { + "id": "5b85d67a-5038-45ad-82f1-cc9290e9def1", + "type": "entity", + "properties": { + "name": [ + { + "id": "5940778a-b2b9-4e47-9d80-53dfa697b596", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "78728361-d1f1-4db7-a9c3-52f8bdbc1a88", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "id": "74d96f87-b4ad-490a-aad6-f318d61ca658" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/03-multiple-choice.json b/services/madoc-ts/fixtures/01-basic/03-multiple-choice.json new file mode 100644 index 000000000..c0dbca9d4 --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/03-multiple-choice.json @@ -0,0 +1,53 @@ +{ + "structure": { + "id": "2f5a6921-23b7-4618-9ba1-74849f0b4949", + "type": "choice", + "label": "basic - multiple choice", + "items": [ + { + "id": "4e989334-cc39-426a-af77-f77963ebf475", + "label": "Choice 1", + "type": "model", + "fields": [ + "name" + ], + "description": "The first choice in the list" + }, + { + "id": "6f7f2d6c-2457-479a-830d-a80ea39304d0", + "label": "Choice 2", + "type": "model", + "fields": [ + "description" + ], + "description": "The second choice in the list" + } + ], + "description": "Contains 2 choices with multiple text fields" + }, + "document": { + "id": "600f7437-4511-46c0-ada5-ecc907bacbea", + "type": "entity", + "properties": { + "name": [ + { + "id": "63003ba2-bf3b-41e9-8e63-5cb8e4ebd049", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "53a76e7b-f7f8-4460-853c-64588aeaaa86", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ] + }, + "label": "Multiple choices", + "description": "" + }, + "id": "8eae33be-2c12-4230-9cc4-6ee5a2b1fc15" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/04-nested-choice.json b/services/madoc-ts/fixtures/01-basic/04-nested-choice.json new file mode 100644 index 000000000..8f294cacc --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/04-nested-choice.json @@ -0,0 +1,69 @@ +{ + "structure": { + "id": "423381d2-8a56-4791-b5a6-e250781e12b6", + "type": "choice", + "label": "basic - nested choice", + "items": [ + { + "id": "bc3cfee2-ddf6-495b-91b3-3561d814b5a7", + "label": "Choice 1", + "type": "model", + "fields": [ + "name" + ], + "description": "The first choice in the list" + }, + { + "id": "8105a457-5329-4498-933d-f7b396cba695", + "label": "Choice 2", + "type": "model", + "fields": [ + "description" + ], + "description": "The second choice in the list" + }, + { + "id": "067f652d-275e-4812-b257-7b4b6954e0d4", + "label": "Nested choice", + "type": "choice", + "items": [ + { + "label": "Choice 3", + "id": "1da44ff0-1b93-40c4-bb13-f37b77b6e57e", + "type": "model", + "fields": [ + "name", + "description" + ] + } + ], + "description": "This is a nested choice" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "dec387c1-55fb-45ba-ba6d-49d9effb073d", + "type": "entity", + "label": "Nested choices", + "properties": { + "name": [ + { + "id": "73d82757-442c-4661-8b5c-692b63d60670", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "ff309a32-7cf1-494e-a484-1b9943021fff", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ] + } + }, + "id": "db84d51c-8148-4fc8-9974-8d99fca350c7" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/05-multiple-fields-multiple-values.json b/services/madoc-ts/fixtures/01-basic/05-multiple-fields-multiple-values.json new file mode 100644 index 000000000..e9961a6ab --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/05-multiple-fields-multiple-values.json @@ -0,0 +1,73 @@ +{ + "structure": { + "id": "04e37aea-9a1e-48f1-af1c-35fe3a83f3d9", + "type": "choice", + "label": "basic - multiple fields multiple values", + "items": [ + { + "id": "e28edc8c-66a2-453a-aaf1-55ff3b833efa", + "label": "Only choice - multiple fields, multiple values", + "type": "model", + "fields": [ + "name", + "description" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with multiple single text fields" + }, + "document": { + "id": "9fc61fd8-9f0c-4bfd-b7a5-4e60b909cb0f", + "type": "entity", + "label": "Simple document", + "description": "", + "properties": { + "name": [ + { + "id": "ae2b084e-9eb0-4514-83e9-51f2f18aa785", + "type": "text-field", + "label": "Name", + "allowMultiple": true, + "description": "The name of the thing", + "value": "The first name in the list" + }, + { + "id": "701abade-3c4a-4574-8714-535192500317", + "type": "text-field", + "label": "Name", + "allowMultiple": true, + "description": "The name of the thing", + "value": "The second name in the list" + } + ], + "description": [ + { + "id": "686fa013-8ad6-4ab0-bbe2-57b9694f81a9", + "type": "text-field", + "label": "Description", + "allowMultiple": true, + "description": "Tell us something about the thing", + "value": "First description" + }, + { + "id": "8aa400bd-5e74-4f34-ab9b-4892dc97c472", + "type": "text-field", + "label": "Description", + "allowMultiple": true, + "description": "Tell us something about the thing", + "value": "Second description" + }, + { + "id": "f94f9dec-0245-4fdf-84e0-9535c3fcf732", + "type": "text-field", + "label": "Description", + "allowMultiple": true, + "description": "Tell us something about the thing", + "value": "Third description" + } + ] + } + }, + "id": "5a2bee89-9e7a-4850-b2e9-cdae75f428c0" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/06-single-field-value.json b/services/madoc-ts/fixtures/01-basic/06-single-field-value.json new file mode 100644 index 000000000..618a2aee5 --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/06-single-field-value.json @@ -0,0 +1,35 @@ +{ + "structure": { + "id": "efcfc161-4942-4dfa-9017-5530b3084944", + "type": "choice", + "label": "basic - single field value", + "description": "Contains single choice with single text field and a pre-filled value", + "items": [ + { + "id": "77baec35-90bc-4509-9335-97604177dd40", + "type": "model", + "label": "Only choice", + "description": "This could be a top level item without a choice.", + "fields": [ + "name" + ] + } + ] + }, + "document": { + "id": "44a62032-2e14-471d-86e8-4b5701b68daa", + "type": "entity", + "label": "Simple document", + "properties": { + "name": [ + { + "id": "8f12928a-67a7-48ba-9038-e682fd8e2fb2", + "type": "text-field", + "label": "Name", + "value": "Forth Bridge Illustrations" + } + ] + } + }, + "id": "0b826265-f985-49a1-a628-d2ee95f669ae" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/01-basic/07-choice-example.json b/services/madoc-ts/fixtures/01-basic/07-choice-example.json new file mode 100644 index 000000000..747b73cd9 --- /dev/null +++ b/services/madoc-ts/fixtures/01-basic/07-choice-example.json @@ -0,0 +1,217 @@ +{ + "id": "5bd7c9b6-9128-4861-91c2-73d88468a882", + "structure": { + "id": "79f2ef76-e661-44d7-881c-9829d0d5ae88", + "type": "choice", + "description": "Example showing nested levels of navigation", + "label": "basic - choice example", + "items": [ + { + "id": "d018606d-1bcc-4d04-b073-4d42b8a08e59", + "type": "model", + "description": "This is a model choice at the top level", + "label": "Top level model", + "fields": [ + "field-a" + ] + }, + { + "id": "2139c4da-73a5-45c1-8326-dfc8b0e97bcc", + "type": "choice", + "description": "This choice is 2 levels deep, taking you through a nested example of structured navigation.", + "label": "2 Deep choice", + "items": [ + { + "id": "50bca69d-b01f-4f2c-aa9d-189b8d6dbf34", + "type": "choice", + "description": "This is the first of two choices.", + "label": "Choice A", + "items": [ + { + "id": "01698929-fe2e-4976-869a-f565e9d2b8b7", + "type": "model", + "label": "Model A", + "fields": [ + "field-a" + ] + }, + { + "id": "895c69ac-5e45-4ea1-a6b5-7ec7fb141e0d", + "type": "model", + "label": "Model B", + "fields": [ + "field-b" + ] + } + ] + }, + { + "id": "f4a38c60-32ae-4b66-b663-fa55f7107e37", + "type": "choice", + "description": "This is the second of 2 choices.", + "label": "Choice B", + "items": [ + { + "id": "b20e3231-6332-447f-9bd8-f0170c9e510f", + "type": "model", + "label": "Model C", + "fields": [ + "field-c" + ] + } + ] + } + ] + }, + { + "id": "27ccfb83-8331-4f2a-b783-9a9ffb58f6c6", + "type": "choice", + "description": "This choice is 3 levels deep and has a many choices.", + "label": "3 Deep choice", + "items": [ + { + "id": "111f5ff0-6275-48ec-ad60-5514846694a3", + "type": "choice", + "description": "This has 2 models", + "label": "Choice A", + "items": [ + { + "id": "81013c61-bb89-4cad-b282-99c4b5e5b02a", + "type": "model", + "label": "Model A", + "fields": [ + "field-a" + ] + }, + { + "id": "26313e4d-12e7-4e57-9d14-acf61de29eca", + "type": "model", + "label": "Model B", + "fields": [ + "field-b" + ] + } + ] + }, + { + "id": "fd78366c-877d-48c1-9b99-d80d16defcd2", + "type": "choice", + "description": "This has 2 models", + "label": "Choice B", + "items": [ + { + "id": "e3a4bcde-54fd-4b8b-b369-8a4ce864c606", + "type": "model", + "label": "Model A", + "fields": [ + "field-a" + ] + }, + { + "id": "21e770f4-183c-4750-86da-8e5c8abc78fb", + "type": "model", + "description": "These are examples, but show the many variations in navigation you can provide users to break down a crowdsourcing document.", + "label": "Model B - in choice B", + "fields": [ + "field-a", + "field-c", + "field-b" + ] + } + ] + }, + { + "id": "8416974d-3a15-467c-96d9-7c0c2c127d0e", + "type": "choice", + "description": "This has a model choice.", + "label": "Choice C", + "items": [ + { + "id": "bd52999e-9abf-4bdb-babb-b017da8743a2", + "type": "model", + "description": "The only choice here", + "label": "Model choice", + "fields": [ + "field-b" + ] + } + ] + }, + { + "id": "d32ec353-7a2f-4f61-924f-3ac93003dad3", + "type": "choice", + "description": "This has a mix of models and further choices.", + "label": "Choice D", + "items": [ + { + "id": "db753f53-33c5-4fed-996e-55a214c51a26", + "type": "model", + "description": "This is a model", + "label": "Choice A", + "fields": [ + "field-c" + ] + }, + { + "id": "db870187-92b3-4ffb-8bbe-ba66a327f551", + "type": "choice", + "description": "But this is another nested level", + "label": "Choice B", + "items": [ + { + "id": "652c6ca9-05be-4835-8cef-c7ea758f4e85", + "type": "model", + "label": "Model A", + "fields": [ + "field-a" + ] + }, + { + "id": "97537f77-cd58-451c-abe7-cf4540973a5a", + "type": "model", + "label": "Model B", + "fields": [ + "field-b" + ] + } + ] + } + ] + } + ] + } + ] + }, + "document": { + "id": "809fb289-0f2f-491e-9a3b-4847a57fc19a", + "type": "entity", + "label": "Navigation demo", + "properties": { + "field-a": [ + { + "id": "55d48800-685b-4a9a-9580-5d13f7cc6d3f", + "type": "text-field", + "label": "Field A", + "value": "" + } + ], + "field-c": [ + { + "id": "6988ae7d-5e9d-4e03-8c2e-98d331fff8ec", + "type": "text-field", + "label": "Field C", + "value": "" + } + ], + "field-b": [ + { + "id": "a9e8311e-233d-4b07-aee1-e67e0953c752", + "type": "text-field", + "label": "Field B", + "value": "" + } + ] + } + }, + "target": null +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/01-nested-model.json b/services/madoc-ts/fixtures/02-nesting/01-nested-model.json new file mode 100644 index 000000000..d358a0c4b --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/01-nested-model.json @@ -0,0 +1,84 @@ +{ + "structure": { + "id": "44a1c57f-8dc5-41a4-a020-91adfa3f402b", + "type": "choice", + "label": "nesting - nested model", + "items": [ + { + "id": "1a0d894a-1897-4448-abd0-0acc451e76fc", + "label": "Choice 1", + "type": "model", + "fields": [ + "name", + "description" + ], + "description": "The first choice in the list" + }, + { + "id": "6fb10d2e-8a88-4a5f-a318-ac6542f073de", + "label": "Person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "0169c23d-c72a-4d7d-8728-27c306cd9860", + "type": "entity", + "properties": { + "name": [ + { + "id": "c312dc73-c278-4edd-a649-f3bdc9828a6d", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "d4429fce-3edd-4997-856c-f3c90e304962", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ], + "person": [ + { + "id": "1ad53177-c2c6-45c0-a202-f485db1e57df", + "type": "entity", + "label": "Person", + "properties": { + "firstName": [ + { + "id": "4e79f032-e8da-4619-91e1-0e9716d7c2d8", + "type": "text-field", + "label": "First name" + } + ], + "lastName": [ + { + "id": "7fcbe69a-c9b2-4519-a9bb-cf5b05c6a87a", + "type": "text-field", + "label": "Last name" + } + ] + }, + "description": "Describe a person" + } + ] + }, + "label": "Nested choices", + "description": "" + }, + "id": "c8e4436b-ac8a-4935-bce6-f423aee5c9ec" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/02-nested-mixed.json b/services/madoc-ts/fixtures/02-nesting/02-nested-mixed.json new file mode 100644 index 000000000..f4b934da1 --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/02-nested-mixed.json @@ -0,0 +1,76 @@ +{ + "structure": { + "type": "choice", + "label": "nesting - nested mixed", + "description": "Entity and fields in the same structure", + "items": [ + { + "label": "Person + data", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ], + "name", + "description" + ], + "description": "The only choice in the list", + "id": "f8c51997-bc5b-4777-8e50-f9a7ed1b495d" + } + ], + "id": "6fbc611b-e16d-45e0-9637-bff692ca6802" + }, + "document": { + "type": "entity", + "label": "Nested choices", + "description": "", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "description": "The name of the thing", + "id": "6c72806b-7177-453b-97c2-08c0d31fb3e1" + } + ], + "description": [ + { + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing", + "id": "bf338019-1768-4450-b892-329e2a657c8f" + } + ], + "person": [ + { + "type": "entity", + "label": "Person", + "properties": { + "firstName": [ + { + "type": "text-field", + "label": "First name", + "id": "12f7de50-fd13-4325-b264-f0221c86619b" + } + ], + "lastName": [ + { + "type": "text-field", + "label": "Last name", + "id": "1d3054ca-bfdc-4a1f-8c44-f6e7d48c2dd7" + } + ] + }, + "description": "Describe a person", + "id": "975182d0-7e8d-468d-8936-8aadbe0fd4c8" + } + ] + }, + "id": "077bde44-f69e-4b6c-95e8-06fd208e8053" + }, + "id": "52a61437-4b3d-4fbb-95cb-ee6abfc563bf" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/03-deeply-nested-subset.json b/services/madoc-ts/fixtures/02-nesting/03-deeply-nested-subset.json new file mode 100644 index 000000000..faf4176ac --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/03-deeply-nested-subset.json @@ -0,0 +1,106 @@ +{ + "structure": { + "type": "choice", + "label": "nesting - deeply nested subset", + "items": [ + { + "label": "Edit all names", + "type": "model", + "fields": [ + [ + "depth1", + [ + [ + "second", + [ + [ + "third", + [ + "name" + ] + ] + ] + ] + ] + ] + ], + "id": "25db5732-2b41-4bd0-86a7-9e805805bca0" + }, + { + "label": "Edit all descriptions", + "type": "model", + "fields": [ + [ + "depth1", + [ + [ + "second", + [ + [ + "third", + [ + "description" + ] + ] + ] + ] + ] + ] + ], + "id": "78a999ee-70e5-4f05-a77f-95edd22e0dc8" + } + ], + "description": "A document with a nested structure, but only editing a single field deep in the tree.", + "id": "ca92dd59-4de9-4b76-8c87-900717bd13f0" + }, + "document": { + "type": "entity", + "properties": { + "depth1": [ + { + "type": "entity", + "label": "First depth", + "properties": { + "second": [ + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "id": "30c19e58-108c-48ea-a147-89ec29493805" + } + ], + "description": [ + { + "type": "text-field", + "label": "description", + "id": "970fffe8-f5ae-4d8a-8762-0368858fc254" + } + ] + }, + "id": "0ada8521-6acc-498d-b502-0cec689aceff" + } + ] + }, + "description": "Second depth", + "id": "0f7cc808-7179-440b-b3dd-d43bdc967b1f" + } + ] + }, + "id": "0a097b04-249f-4c23-9e40-7fa29015951d" + } + ] + }, + "label": "Deeply nested subset", + "id": "4a07959a-0333-460c-82ea-4cf377050f22" + }, + "id": "201453f3-ebfb-4966-80ea-152b03741db1" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/04-deeply-nested-mixed-instance.json b/services/madoc-ts/fixtures/02-nesting/04-deeply-nested-mixed-instance.json new file mode 100644 index 000000000..445e436ec --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/04-deeply-nested-mixed-instance.json @@ -0,0 +1,280 @@ +{ + "structure": { + "type": "choice", + "label": "nesting - deeply nested mixed instance", + "items": [ + { + "label": "Edit all names", + "type": "model", + "fields": [ + [ + "depth1", + [ + [ + "second", + [ + [ + "third", + [ + "name" + ] + ] + ] + ] + ] + ] + ], + "id": "a7555a55-4e3f-4bf2-954e-5f1e51a7b35c" + } + ], + "description": "A document with a nested structure, but only editing a single field deep in the tree.", + "id": "0a6ec3f0-4f9e-49c4-9051-eadbb5a6df09" + }, + "document": { + "type": "entity", + "properties": { + "depth1": [ + { + "type": "entity", + "label": "First depth", + "properties": { + "second": [ + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "One instance at the depth", + "id": "e3a2be45-88f9-476b-b7de-7a089cdf104f" + } + ] + }, + "id": "8ac324b8-23a7-4533-8ef5-4739fa035697" + } + ] + }, + "description": "Second depth", + "id": "770c6a1d-da3e-4449-965b-39d19a5bbc83" + } + ] + }, + "id": "613f6989-9e39-443d-97d0-d663f35371a7" + }, + { + "type": "entity", + "label": "First depth", + "properties": { + "second": [ + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "two instances at second level (first)", + "id": "ccf79c5c-b7ff-4e90-9811-90e3ce3c7fd6" + } + ] + }, + "id": "e1f99235-83af-4633-a82e-28b088fef2f8" + } + ] + }, + "description": "Second depth", + "id": "23d7e13e-a165-4343-8f10-db9d80efe4af" + }, + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "two instances at second level (second)", + "id": "45e19d0c-354a-456b-858b-37f1ade18744" + }, + { + "type": "text-field", + "label": "Name", + "value": "two instances at second level (third)", + "id": "a616e598-2443-43e7-9b4a-56b05d4b9345" + } + ] + }, + "id": "cd295cc9-21bc-443c-895f-0e5d0d349fd3" + } + ] + }, + "description": "Second depth", + "id": "5d4bc3a1-be93-4ec5-a271-22b49667820d" + } + ] + }, + "id": "f64a0886-548e-43ae-8c9e-00bee45028b7" + }, + { + "type": "entity", + "label": "First depth", + "properties": { + "second": [ + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "two instance at third depth (first)", + "id": "a9814af8-8dc3-4cdb-9e34-c7ecb4f76d68" + } + ] + }, + "id": "70614425-da9a-4841-97bf-65fa5d36892d" + }, + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "two instance at third depth (second)", + "id": "3ed870a8-5b66-4259-93e6-6a2e4a952463" + } + ] + }, + "id": "5f46b580-788b-4f99-a0d8-31ba158024b7" + } + ] + }, + "description": "Second depth", + "id": "0dae58b6-4143-4852-a3bd-f2b9aadc34de" + } + ] + }, + "id": "cab51c44-fbf5-405e-8829-4f01480e5f36" + }, + { + "type": "entity", + "label": "First depth", + "properties": { + "second": [ + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "2x2 at second and third levels (first 1-1)", + "id": "e3e37449-aa19-43ec-93fe-7a41f296c215" + } + ] + }, + "id": "2d48cf1f-4379-4cb8-96e5-0258db40926e" + }, + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "2x2 at second and third levels (second 1-2)", + "id": "54e8e65c-de36-4f26-bdb1-21da73a2054d" + } + ] + }, + "id": "dd335cbb-7569-46da-90ee-0a4f5849af5b" + } + ] + }, + "description": "Second depth", + "id": "b2650738-5304-4c69-b54d-b9d13d27d104" + }, + { + "type": "entity", + "label": "second", + "properties": { + "third": [ + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "2x2 at second and third levels (third 2-1)", + "id": "c9616dc5-8204-421d-8bbf-86824d67c1ec" + } + ] + }, + "id": "41f10183-bc70-4fdf-878d-8841d96f4cfe" + }, + { + "type": "entity", + "label": "Third depth", + "properties": { + "name": [ + { + "type": "text-field", + "label": "Name", + "value": "2x2 at second and third levels (fourth 2-2)", + "id": "6ff7d593-4f5f-4cdc-8a7d-9712f8ac5291" + } + ] + }, + "id": "40e6b0bf-74c5-4b5b-a43d-b3c5cf71d4c7" + } + ] + }, + "description": "Second depth", + "id": "38910bae-d1a1-443d-a590-c5add5cb9dbe" + } + ] + }, + "id": "fa769b2e-268f-4489-99c4-bfb22ece4da0" + } + ] + }, + "label": "Deeply nested subset", + "id": "f62184ff-ba48-438c-a318-9b7471530baa" + }, + "id": "59b58f08-9267-4913-8c4f-ad6011939e6b" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/05-nested-model-multiple.json b/services/madoc-ts/fixtures/02-nesting/05-nested-model-multiple.json new file mode 100644 index 000000000..972d3bab0 --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/05-nested-model-multiple.json @@ -0,0 +1,114 @@ +{ + "structure": { + "id": "f197bd74-5ede-407b-aace-a2223c520b8f", + "type": "choice", + "label": "nesting - nested model multiple", + "items": [ + { + "id": "b0405656-79cb-4295-b7bb-940e0318ae1b", + "label": "Choice 1", + "type": "model", + "fields": [ + "name", + "description" + ], + "description": "The first choice in the list" + }, + { + "id": "0e29f176-aeeb-4bf3-a92c-d64654e29c90", + "label": "Person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "626422b1-abbf-4f46-b160-d9bb768b2e29", + "type": "entity", + "properties": { + "name": [ + { + "id": "714479f5-f46a-486e-8b86-2638d9ae0259", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "54e3f035-fd5c-4593-bd28-8af49f039d27", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ], + "person": [ + { + "id": "3036e4a5-c350-426b-82b5-8fafdfe55e27", + "type": "entity", + "label": "Person", + "labelledBy": "firstName", + "allowMultiple": true, + "properties": { + "firstName": [ + { + "id": "1da67423-f4a1-49e6-8561-55c40be47c00", + "type": "text-field", + "label": "First name", + "value": "first first name" + } + ], + "lastName": [ + { + "id": "6b7ce0c3-2a13-4ea3-a190-822fee80176b", + "type": "text-field", + "label": "Last name", + "value": "first last name" + } + ] + }, + "description": "Describe a person" + }, + { + "id": "41cf9550-af77-4310-82ca-130141ed215d", + "type": "entity", + "label": "Person", + "labelledBy": "firstName", + "allowMultiple": true, + "properties": { + "firstName": [ + { + "id": "f8678b43-f803-40b2-8e2e-fcc014631e1a", + "type": "text-field", + "label": "First name", + "value": "second first name" + } + ], + "lastName": [ + { + "id": "12e92ef6-6ebc-49af-95b3-42718ac92c26", + "type": "text-field", + "label": "Last name", + "value": "second last name" + } + ] + }, + "description": "Describe a person" + } + ] + }, + "label": "Nested choices", + "description": "" + }, + "id": "a09e5ee4-9c95-4403-b152-278b0a9be9f8" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/02-nesting/06-ocr.json b/services/madoc-ts/fixtures/02-nesting/06-ocr.json new file mode 100644 index 000000000..351f4e501 --- /dev/null +++ b/services/madoc-ts/fixtures/02-nesting/06-ocr.json @@ -0,0 +1,2158 @@ +{ + "id": "ece903d1-9e17-41b0-af83-5f417df2a22c", + "structure": { + "id": "27fb84fe-f116-4907-9860-6fbc834021e8", + "type": "choice", + "label": "nesting - ocr", + "items": [ + { + "id": "320a754b-4546-4271-b226-c97a90807950", + "type": "model", + "label": "Transcription", + "fields": [ + [ + "transcription", + [ + [ + "lines", + [ + "text" + ] + ] + ] + ] + ] + } + ] + }, + "document": { + "id": "aa2344b4-a70b-41cf-aa62-5e211e76ee2c", + "type": "entity", + "label": "Untitled document", + "properties": { + "transcription": [ + { + "id": "b2867260-78e3-484f-8549-40a89a55d51c", + "type": "entity", + "label": "Paragraph", + "pluralLabel": "Paragraphs", + "description": "Region of the page denoting a single paragraph", + "allowMultiple": true, + "labelledBy": "lines", + "properties": { + "lines": [ + { + "id": "6186d27e-31c4-43d5-96c1-07943cb88ea8", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "a910c803-a0f8-4c7e-963b-a046e04311ed", + "type": "text-field", + "label": "Text of line", + "value": "Von", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d8286aeb-e6c0-47ca-80bc-e546ae8c7ddf", + "type": "box-selector", + "state": { + "x": 314, + "y": 2257, + "width": 99, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "001edf22-8ae8-4825-b480-b2b18320db45", + "type": "text-field", + "label": "Text of line", + "value": "Eugen", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3a544420-51c6-44b4-a74c-dedcd49dd560", + "type": "box-selector", + "state": { + "x": 442, + "y": 2258, + "width": 186, + "height": 47 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "fa149bdb-1238-40a1-8ec8-db18b2e4c868", + "type": "text-field", + "label": "Text of line", + "value": "Sc", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "e69371ee-7d41-4b5e-9c56-a4dcda4d0ed5", + "type": "box-selector", + "state": { + "x": 660, + "y": 2256, + "width": 61, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "6ee985e8-bee2-4fcc-9b32-8b54008e74c9", + "type": "text-field", + "label": "Text of line", + "value": "huh", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "afdfdb87-28f5-4a95-8634-783c588b6b0f", + "type": "box-selector", + "state": { + "x": 735, + "y": 2255, + "width": 109, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "fbedf83a-f10d-4b03-8884-1045b81fa1cc", + "type": "text-field", + "label": "Text of line", + "value": "m", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b9d2b7ab-b619-4335-adba-966885cd25c8", + "type": "box-selector", + "state": { + "x": 857, + "y": 2265, + "width": 42, + "height": 28 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "18a8883f-c2fc-489d-bdf8-816dc16dd047", + "type": "text-field", + "label": "Text of line", + "value": "ach", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "f88ca6ef-52f6-4fc8-9525-e6f983d299ee", + "type": "box-selector", + "state": { + "x": 918, + "y": 2253, + "width": 102, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "ad4d2037-a3ff-42f6-93c6-2625398e4388", + "type": "text-field", + "label": "Text of line", + "value": "er", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "9c5bb8dc-9bfa-458b-8876-3ddce478424d", + "type": "box-selector", + "state": { + "x": 1037, + "y": 2264, + "width": 54, + "height": 28 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "c25ad11f-3b88-445b-82f5-c5bae8631431", + "type": "text-field", + "label": "Text of line", + "value": "-", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "8be0a26c-8583-42f9-a74f-e61c1f614ef5", + "type": "box-selector", + "state": { + "x": 1120, + "y": 2273, + "width": 17, + "height": 8 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "4fec4f93-b1b0-498b-bc30-b4b2d234bf18", + "type": "text-field", + "label": "Text of line", + "value": "96", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "48bfc9a2-5a6c-4797-b05e-673ca0604f78", + "type": "box-selector", + "state": { + "x": 1167, + "y": 2253, + "width": 50, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "12cf6554-07fd-46e3-aee1-5543033adafe", + "type": "text-field", + "label": "Text of line", + "value": "Seiten", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d0ff3096-6582-421f-b367-f0111d7283ca", + "type": "box-selector", + "state": { + "x": 1239, + "y": 2253, + "width": 151, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "21eb4bac-950b-4430-aecb-362050487689", + "type": "text-field", + "label": "Text of line", + "value": "Text", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "7c4debf2-2a3e-4985-9bc6-4a949e4d1d62", + "type": "box-selector", + "state": { + "x": 1412, + "y": 2253, + "width": 105, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "456556c9-96f6-4def-8ebc-155b931c6cb1", + "type": "text-field", + "label": "Text of line", + "value": "mit", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "1a986467-0f7d-4694-adbc-3ffa306efc4a", + "type": "box-selector", + "state": { + "x": 1537, + "y": 2251, + "width": 78, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "38212dfe-9379-454c-a688-de7b12b41c83", + "type": "text-field", + "label": "Text of line", + "value": "124", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b11866a2-3555-4b7f-b05f-806a2797b220", + "type": "box-selector", + "state": { + "x": 1642, + "y": 2251, + "width": 72, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "6a79a9ed-aeb4-4bfe-b400-79f3e56726e0", + "type": "text-field", + "label": "Text of line", + "value": "Bildern", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "c209b614-ed1c-489e-a936-b5cbb649ee92", + "type": "box-selector", + "state": { + "x": 1734, + "y": 2249, + "width": 177, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "92fff73e-e5fd-4386-beda-e65a7d6bf0a1", + "type": "text-field", + "label": "Text of line", + "value": "aut", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "00b7d306-ebad-4d5e-8987-4c72d27d6a51", + "type": "box-selector", + "state": { + "x": 1933, + "y": 2252, + "width": 75, + "height": 33 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "7c933ba4-0637-469c-957c-8519f1d3afc3", + "type": "box-selector", + "state": { + "x": 314, + "y": 2249, + "width": 1694, + "height": 56 + } + } + }, + { + "id": "6cc50e60-3749-4be0-8b9d-feced17caa8a", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "a5279d47-e2f6-4c22-877e-9ab22d56dac2", + "type": "text-field", + "label": "Text of line", + "value": "64", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "f947204e-2889-4796-8002-1190c8b28e09", + "type": "box-selector", + "state": { + "x": 314, + "y": 2327, + "width": 50, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "fef17132-f12e-47a7-8522-9360a34b4ee0", + "type": "text-field", + "label": "Text of line", + "value": "Taieln", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "78a60967-175c-4a95-8c00-fc508a03e1e8", + "type": "box-selector", + "state": { + "x": 385, + "y": 2325, + "width": 152, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "07273699-5749-4afd-9c55-6e44cb2842c7", + "type": "text-field", + "label": "Text of line", + "value": "-", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "2cc3269b-be45-4950-a08d-d917333eb1fe", + "type": "box-selector", + "state": { + "x": 559, + "y": 2343, + "width": 16, + "height": 9 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "4a6d32a9-0d11-494f-9d75-bb92caa480fb", + "type": "text-field", + "label": "Text of line", + "value": "In", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b6bc56a0-850b-410c-b9b5-973abcd45150", + "type": "box-selector", + "state": { + "x": 593, + "y": 2327, + "width": 48, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9b5c9f65-c21c-4280-95c6-e4577c0193b5", + "type": "text-field", + "label": "Text of line", + "value": "Ganzleinen^", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "8e7d0e62-190d-4f95-86c2-edb0f2510c78", + "type": "box-selector", + "state": { + "x": 665, + "y": 2325, + "width": 295, + "height": 44 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9c57c6b0-369e-4aa8-83d6-7a0454d89202", + "type": "text-field", + "label": "Text of line", + "value": "mit", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "5830569d-4c08-4bb3-b91b-5706e5cd86af", + "type": "box-selector", + "state": { + "x": 983, + "y": 2325, + "width": 79, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "5bfba1d0-c7ed-4544-aedf-8919a3ea98c4", + "type": "text-field", + "label": "Text of line", + "value": "prächtigem", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "cccda4f4-98fe-4ef5-8121-54a1c0267cdb", + "type": "box-selector", + "state": { + "x": 1082, + "y": 2324, + "width": 270, + "height": 47 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "f4a23b10-a773-4d44-a2ef-49a6bae1b6f7", + "type": "text-field", + "label": "Text of line", + "value": "Schutzumschlag", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "bd7e40f1-42a2-4c92-9f38-b7532f1ccfc4", + "type": "box-selector", + "state": { + "x": 1371, + "y": 2319, + "width": 388, + "height": 49 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "49c9dd77-d9fe-44b7-b04a-91c7a86ea2bb", + "type": "text-field", + "label": "Text of line", + "value": "DM", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "c1a82a55-165d-4359-8e9a-41a5b93d88d8", + "type": "box-selector", + "state": { + "x": 1780, + "y": 2319, + "width": 85, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9bf6b827-3220-49f5-96d3-1e8430bfaeaa", + "type": "text-field", + "label": "Text of line", + "value": "12.80", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "63e9d8cb-85c7-4c5c-b19f-e7d0bca4abc0", + "type": "box-selector", + "state": { + "x": 1890, + "y": 2319, + "width": 117, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "0449d4cc-7c79-42cc-bed5-03f9eab615ea", + "type": "box-selector", + "state": { + "x": 314, + "y": 2319, + "width": 1693, + "height": 52 + } + } + }, + { + "id": "423777d8-7c95-424a-a7b6-afd4e1b3ab0e", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "669a0265-3c60-48ab-a2e0-ecf76234514d", + "type": "text-field", + "label": "Text of line", + "value": "oDas", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "96d5a674-e4dd-4ac5-a448-87de48f4029a", + "type": "box-selector", + "state": { + "x": 317, + "y": 2434, + "width": 113, + "height": 45 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "0fe73c4c-62eb-4ffb-bc0b-9f0e19bb0a25", + "type": "text-field", + "label": "Text of line", + "value": "Buch", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3e00bb67-b867-4c5f-bd1d-42799d73ce20", + "type": "box-selector", + "state": { + "x": 454, + "y": 2433, + "width": 116, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "875961bb-11da-49a3-80e3-3e7eecaa9444", + "type": "text-field", + "label": "Text of line", + "value": "wird", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "68982866-6d04-4549-b7f0-f507ace84afc", + "type": "box-selector", + "state": { + "x": 602, + "y": 2433, + "width": 111, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "e52f1a32-fbcc-4f49-9a05-0917ccd149f2", + "type": "text-field", + "label": "Text of line", + "value": "jeden", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "6c30a310-d154-49af-b98b-847056fbfbef", + "type": "box-selector", + "state": { + "x": 740, + "y": 2433, + "width": 144, + "height": 47 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "ced3b9f4-88cd-4c1a-bf11-63845dff8d27", + "type": "text-field", + "label": "Text of line", + "value": "Tierfreund", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "80dc5eae-8840-424b-abd9-d78e804c09d3", + "type": "box-selector", + "state": { + "x": 915, + "y": 2431, + "width": 264, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "7b70dc5c-513c-493a-8519-455f16ed2f32", + "type": "text-field", + "label": "Text of line", + "value": "—", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "da361328-f015-4019-9d1d-0006c9760a9f", + "type": "box-selector", + "state": { + "x": 1211, + "y": 2452, + "width": 52, + "height": 7 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "a338770f-0477-4136-af26-c52c843803af", + "type": "text-field", + "label": "Text of line", + "value": "und", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "7dedabae-a941-4733-904f-f92d7bd55ef7", + "type": "box-selector", + "state": { + "x": 1304, + "y": 2431, + "width": 90, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "bbbbbb8a-e177-424d-956c-9eb9353e2386", + "type": "text-field", + "label": "Text of line", + "value": "wer", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "9b2d98f2-36ed-40d7-9af7-8b076050c710", + "type": "box-selector", + "state": { + "x": 1424, + "y": 2440, + "width": 92, + "height": 28 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "84740ca6-0849-4be9-8224-313949207f86", + "type": "text-field", + "label": "Text of line", + "value": "wäre", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "aa5bf2cd-91ee-49ce-8e35-7eff4ec395c1", + "type": "box-selector", + "state": { + "x": 1546, + "y": 2430, + "width": 121, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "0dc623fe-9f79-48ff-be11-5f5219cacc96", + "type": "text-field", + "label": "Text of line", + "value": "das", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "765a37df-bd05-4b6b-bae4-0b858ccb066c", + "type": "box-selector", + "state": { + "x": 1699, + "y": 2428, + "width": 79, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "8b87744a-b091-4103-9a39-93562a8cbbf2", + "type": "text-field", + "label": "Text of line", + "value": "nicht", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "ea19a639-a275-4f95-83e3-ac8740768749", + "type": "box-selector", + "state": { + "x": 1808, + "y": 2427, + "width": 117, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "df398d8a-13a9-4b56-a13a-de2e6418b47b", + "type": "text-field", + "label": "Text of line", + "value": "—", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "1839ac92-eee3-4c6a-881d-cbebd902be3d", + "type": "box-selector", + "state": { + "x": 1957, + "y": 2446, + "width": 51, + "height": 7 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "84c9b76a-9a04-414a-a6cf-8e91b27efa95", + "type": "box-selector", + "state": { + "x": 317, + "y": 2427, + "width": 1691, + "height": 53 + } + } + }, + { + "id": "a9d8cae8-db25-4880-bc33-e4c0ff192939", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "09332a38-935a-401e-8123-833c2829efda", + "type": "text-field", + "label": "Text of line", + "value": "bereidiern", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "4f17d9bb-3115-4892-a97b-899c55120092", + "type": "box-selector", + "state": { + "x": 312, + "y": 2503, + "width": 262, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "4ce0a61a-9bdc-4d98-8a98-f3d588c6b665", + "type": "text-field", + "label": "Text of line", + "value": "und", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d91bb599-46d3-4ad2-9ef2-930540ec369f", + "type": "box-selector", + "state": { + "x": 617, + "y": 2504, + "width": 92, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "a29eb861-a893-4f2d-a3d2-e420ea405e4a", + "type": "text-field", + "label": "Text of line", + "value": "beglücken.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "c0b4f503-be94-4e02-9688-71f88584e879", + "type": "box-selector", + "state": { + "x": 746, + "y": 2504, + "width": 265, + "height": 48 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "e938c21b-6835-47ab-9893-84d78d45fc1b", + "type": "text-field", + "label": "Text of line", + "value": "Man", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "89846882-502f-48ef-8cf0-41615adcc390", + "type": "box-selector", + "state": { + "x": 1065, + "y": 2504, + "width": 111, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "de15ae27-8f64-4c7d-b8d9-f56dfbfd486f", + "type": "text-field", + "label": "Text of line", + "value": "lernt,", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "36000b7a-09a6-49e3-b940-6d9325c2fb4f", + "type": "box-selector", + "state": { + "x": 1216, + "y": 2504, + "width": 126, + "height": 43 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "922f4711-8fbd-4d12-920b-fcd2f6266de8", + "type": "text-field", + "label": "Text of line", + "value": "indem", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "048003c1-4334-406f-8e29-7229b78ff875", + "type": "box-selector", + "state": { + "x": 1394, + "y": 2503, + "width": 153, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "06c88890-384f-4168-bb67-f984c5250c2e", + "type": "text-field", + "label": "Text of line", + "value": "man", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "03af8b63-85ff-488a-92f7-70b1ae3d1617", + "type": "box-selector", + "state": { + "x": 1595, + "y": 2512, + "width": 105, + "height": 27 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "42822e09-3835-4ef6-9dfa-a332bbf2f7f4", + "type": "text-field", + "label": "Text of line", + "value": "aufs", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "184f48df-2a9a-4c0c-a1f4-0092904c3493", + "type": "box-selector", + "state": { + "x": 1739, + "y": 2499, + "width": 99, + "height": 40 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "0df27cd9-ad27-49f6-9e85-e800772785d8", + "type": "text-field", + "label": "Text of line", + "value": "beste", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "9fb8cbe3-9379-4c46-8e93-2d9ace4ceed5", + "type": "box-selector", + "state": { + "x": 1876, + "y": 2500, + "width": 131, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "98751c14-a342-4958-9803-5e0bd5d11b6a", + "type": "box-selector", + "state": { + "x": 312, + "y": 2499, + "width": 1695, + "height": 53 + } + } + }, + { + "id": "b09e2de4-253e-41c2-a630-be441d2ad55e", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "fc1588c3-8083-49ce-a20b-a6649f589db1", + "type": "text-field", + "label": "Text of line", + "value": "unterhalten", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b0b66b35-918e-4799-9bde-b3d4bb9972ea", + "type": "box-selector", + "state": { + "x": 316, + "y": 2574, + "width": 290, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "22882ecc-c2cb-4036-a3f2-a0ed083c7569", + "type": "text-field", + "label": "Text of line", + "value": "wird.\"", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "f48ef83d-36a5-4e7b-aed8-371edf438566", + "type": "box-selector", + "state": { + "x": 647, + "y": 2575, + "width": 149, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "67099757-ce9a-4f70-a70b-a248f7d96648", + "type": "text-field", + "label": "Text of line", + "value": "Schwäbische", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "07d27981-dc67-4e9f-b9f6-201647c8898f", + "type": "box-selector", + "state": { + "x": 1580, + "y": 2572, + "width": 307, + "height": 40 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9338c880-c525-42a1-bc70-45f56c9a2ece", + "type": "text-field", + "label": "Text of line", + "value": "Post", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "5468aa01-8144-4754-b4df-9425ca61df7a", + "type": "box-selector", + "state": { + "x": 1906, + "y": 2572, + "width": 103, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "180e1b2c-c514-4c0f-a7ea-c4ca28c532cd", + "type": "box-selector", + "state": { + "x": 316, + "y": 2572, + "width": 1693, + "height": 41 + } + } + }, + { + "id": "ab604e92-7d78-41fb-93ca-b1501fb00e8a", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "01867639-a6e6-41ef-a13b-7138dab91ae2", + "type": "text-field", + "label": "Text of line", + "value": "Unsere", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "ba88b7a0-1529-48f1-b707-e4a93da955d9", + "type": "box-selector", + "state": { + "x": 452, + "y": 2682, + "width": 171, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "7dbc8d21-9188-43d8-85ef-a460d3e80006", + "type": "text-field", + "label": "Text of line", + "value": "Prospekte", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "208934ec-03d6-45a8-941e-b869a62a92db", + "type": "box-selector", + "state": { + "x": 638, + "y": 2682, + "width": 244, + "height": 46 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "67a3a37b-0c6c-4792-ab88-c87951b650fa", + "type": "text-field", + "label": "Text of line", + "value": "„Schuhmacher,", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b900c1b4-67f5-422a-9a98-0d92e0f37677", + "type": "box-selector", + "state": { + "x": 895, + "y": 2680, + "width": 361, + "height": 45 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "617b76f5-0965-4cc3-9a2c-5d22e05b13c3", + "type": "text-field", + "label": "Text of line", + "value": "Filmtiere\"", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "57d0bd7c-7153-4ae5-aba6-edbc0861cafd", + "type": "box-selector", + "state": { + "x": 1272, + "y": 2680, + "width": 246, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "316f4942-b3ac-41cf-a6e8-5a1511ec7995", + "type": "text-field", + "label": "Text of line", + "value": "sowie", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "56d5c377-dd2c-452d-b38c-5e9389c7d8c7", + "type": "box-selector", + "state": { + "x": 1538, + "y": 2681, + "width": 140, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "c31c6ff0-baba-40be-9093-85de6394f617", + "type": "text-field", + "label": "Text of line", + "value": "„Natur-", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "ace14eb8-b958-4ddf-aea6-bfd0004100fe", + "type": "box-selector", + "state": { + "x": 1693, + "y": 2679, + "width": 185, + "height": 43 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "2e8cd415-4479-48e3-9c1b-bde9105b74c4", + "type": "box-selector", + "state": { + "x": 452, + "y": 2679, + "width": 1426, + "height": 49 + } + } + }, + { + "id": "30732c44-b0cb-41de-b2dc-922edc2e3a73", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "4359db2a-7214-4d4d-bc5c-2f3ed8ff011a", + "type": "text-field", + "label": "Text of line", + "value": "und", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d4f0f385-f63f-4c2b-90ae-96d0e0cf7b3e", + "type": "box-selector", + "state": { + "x": 451, + "y": 2734, + "width": 92, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "0e0e0f1c-333c-44bc-9b7d-5f4ef6e5d24e", + "type": "text-field", + "label": "Text of line", + "value": "Heimatbücher", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "383dfced-ef3b-47b2-adf8-e81ce7bc3e4b", + "type": "box-selector", + "state": { + "x": 559, + "y": 2734, + "width": 346, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9e7329ad-7cc3-4992-86d0-24824e9da77b", + "type": "text-field", + "label": "Text of line", + "value": "bei", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "54897375-9c1f-4e6c-b584-213d223e5f51", + "type": "box-selector", + "state": { + "x": 922, + "y": 2732, + "width": 70, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "b3150904-8ae4-4ea0-b738-c4397d97cb60", + "type": "text-field", + "label": "Text of line", + "value": "Bruckmann\"", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "1fda1596-6578-4fc4-aa5b-277d6a241faa", + "type": "box-selector", + "state": { + "x": 1010, + "y": 2732, + "width": 298, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "18aa3d60-54bd-4740-ac42-6ee5b3c3979e", + "type": "text-field", + "label": "Text of line", + "value": "liegen", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b0879345-c86b-4a14-83cb-cdcb2e3979ce", + "type": "box-selector", + "state": { + "x": 1328, + "y": 2733, + "width": 150, + "height": 47 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "468fff96-6847-4c21-a795-e1e3a109cb01", + "type": "text-field", + "label": "Text of line", + "value": "bei", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d86a1a2d-0e80-4c52-9545-6436acd65ba6", + "type": "box-selector", + "state": { + "x": 1497, + "y": 2732, + "width": 71, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9a29022d-b3f5-4d6d-827f-db2cee1f9683", + "type": "text-field", + "label": "Text of line", + "value": "Ihrem", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3a5a479a-cc81-4d0c-b99d-c57032166ce4", + "type": "box-selector", + "state": { + "x": 1584, + "y": 2732, + "width": 143, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "9ce299ce-f2ec-4ac6-90bc-b33c476eb751", + "type": "text-field", + "label": "Text of line", + "value": "Buch¬", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "e7a701dc-a6fb-430e-a46b-4d0d15ab925a", + "type": "box-selector", + "state": { + "x": 1744, + "y": 2732, + "width": 134, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "a171b80c-a54a-4335-a232-f7ba942f084d", + "type": "box-selector", + "state": { + "x": 451, + "y": 2732, + "width": 1427, + "height": 48 + } + } + }, + { + "id": "c5dc6101-1969-473f-a84c-9cc6167b4dd4", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "3121c3c4-deb0-46f0-8e94-327dbe6bad31", + "type": "text-field", + "label": "Text of line", + "value": "händlerbereit.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "ac1e7208-1923-4403-b599-b2fc73e7aad5", + "type": "box-selector", + "state": { + "x": 455, + "y": 2787, + "width": 356, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "016d82a4-7370-4cd4-aaf9-a9589dcc121c", + "type": "text-field", + "label": "Text of line", + "value": "Sollten", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "6163159d-a102-43c2-a315-6881ed445d5c", + "type": "box-selector", + "state": { + "x": 829, + "y": 2786, + "width": 167, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "5e9b5e54-2eaa-4a81-99e6-fb7eb7320686", + "type": "text-field", + "label": "Text of line", + "value": "sie", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "fef57891-1dd0-4d7f-91c0-d77c404078de", + "type": "box-selector", + "state": { + "x": 1016, + "y": 2787, + "width": 69, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "e532ec14-1e63-4435-a820-8d03762da072", + "type": "text-field", + "label": "Text of line", + "value": "dort", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "91b15d23-fa1e-43e2-9675-03ae4e1b0e11", + "type": "box-selector", + "state": { + "x": 1101, + "y": 2786, + "width": 97, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "0c805291-fb50-443e-87e7-de8df73645c7", + "type": "text-field", + "label": "Text of line", + "value": "nicht", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "86060f45-619a-4375-abcd-bf337155564d", + "type": "box-selector", + "state": { + "x": 1211, + "y": 2785, + "width": 117, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "59a2c9c6-6904-44c1-aa2a-837d82801d2e", + "type": "text-field", + "label": "Text of line", + "value": "veriügbar", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3238ecfb-ff83-4628-9d72-7aa7492bd510", + "type": "box-selector", + "state": { + "x": 1344, + "y": 2786, + "width": 241, + "height": 48 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "faf1eeb6-16fb-41a1-922e-5839f883b53f", + "type": "text-field", + "label": "Text of line", + "value": "sein,", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "6a6c0dc8-b63c-468e-ae34-5aba320b380e", + "type": "box-selector", + "state": { + "x": 1601, + "y": 2786, + "width": 117, + "height": 43 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "e598bc72-b9e0-4896-baca-b8e25566f35d", + "type": "text-field", + "label": "Text of line", + "value": "gehen", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "97df10be-0ec6-44f4-a95f-858529423923", + "type": "box-selector", + "state": { + "x": 1731, + "y": 2784, + "width": 148, + "height": 49 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "3ff01d33-e683-441d-913b-5b9b9c4bb8d7", + "type": "box-selector", + "state": { + "x": 455, + "y": 2784, + "width": 1424, + "height": 50 + } + } + }, + { + "id": "18384c9e-d765-4201-a068-9d972cd722d5", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "7807ddc7-3815-4f2a-bfc3-404ff799720f", + "type": "text-field", + "label": "Text of line", + "value": "sie", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "959e6dec-0497-4865-9437-01d0e012118a", + "type": "box-selector", + "state": { + "x": 450, + "y": 2841, + "width": 68, + "height": 36 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "3564426f-7976-4974-8456-fe5b154d198c", + "type": "text-field", + "label": "Text of line", + "value": "Ihnen", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "fc150758-21c0-4874-a426-edb8b5fdb998", + "type": "box-selector", + "state": { + "x": 537, + "y": 2839, + "width": 141, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "3e6a91af-79de-4df8-93ca-3351eacad492", + "type": "text-field", + "label": "Text of line", + "value": "aui", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "76f09727-db53-44fa-a090-2e2341a07dae", + "type": "box-selector", + "state": { + "x": 701, + "y": 2840, + "width": 77, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "27dcc7b3-7a91-4fe6-843c-223edee81a14", + "type": "text-field", + "label": "Text of line", + "value": "Verlangen", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "dc313d33-40de-49da-bbe1-87802d8766ce", + "type": "box-selector", + "state": { + "x": 799, + "y": 2839, + "width": 253, + "height": 48 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "6629e6fe-7fa1-4a51-acee-122c7291e5a9", + "type": "text-field", + "label": "Text of line", + "value": "kostenlos", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "6e1f093d-c120-4147-ace6-c05cf1426517", + "type": "box-selector", + "state": { + "x": 1075, + "y": 2839, + "width": 235, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "28f84498-3e99-4344-ad7a-f19e4355fe8d", + "type": "text-field", + "label": "Text of line", + "value": "direkt", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "2f1fdacf-ae7b-464f-9e13-50300161abe1", + "type": "box-selector", + "state": { + "x": 1332, + "y": 2838, + "width": 144, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "17af623e-7e6c-4bf7-a9e8-bd3f5e7fe533", + "type": "text-field", + "label": "Text of line", + "value": "vom", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "d0195a7a-b75a-4746-8ea5-675daa4d6eae", + "type": "box-selector", + "state": { + "x": 1498, + "y": 2848, + "width": 104, + "height": 28 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "22bf505a-c8f6-41a2-af95-a3dc2e9049c0", + "type": "text-field", + "label": "Text of line", + "value": "Verlag", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "ea3ccb63-e3b3-4f58-a15f-1ca33d395a30", + "type": "box-selector", + "state": { + "x": 1621, + "y": 2837, + "width": 164, + "height": 47 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "4101db98-bd35-4970-a870-1632276b61eb", + "type": "text-field", + "label": "Text of line", + "value": "zu.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "99a1ebae-38c3-41ba-87bf-d90100d8eca8", + "type": "box-selector", + "state": { + "x": 1805, + "y": 2847, + "width": 68, + "height": 27 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "3e30df7a-5d76-4b90-b045-698179f868a6", + "type": "box-selector", + "state": { + "x": 450, + "y": 2837, + "width": 1423, + "height": 50 + } + } + } + ] + }, + "selector": { + "id": "750603ef-bf14-464a-b812-6540f53497dd", + "type": "box-selector", + "state": { + "x": 296, + "y": 310, + "width": 1730, + "height": 2582 + } + } + }, + { + "id": "551e683f-367c-41f6-bd63-4e9b0eedcb73", + "type": "entity", + "label": "Paragraph", + "pluralLabel": "Paragraphs", + "description": "Region of the page denoting a single paragraph", + "allowMultiple": true, + "labelledBy": "lines", + "properties": { + "lines": [ + { + "id": "97ecb8ff-84ec-4341-af25-aa222dab8128", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "7beb6728-54ad-4a1a-9d36-af8e8e3f6458", + "type": "text-field", + "label": "Text of line", + "value": "VEELAG", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3a517d06-d144-4fb0-aee0-8589091d8e35", + "type": "box-selector", + "state": { + "x": 318, + "y": 2986, + "width": 378, + "height": 62 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "066abf69-9007-41bd-bc77-877934e9cba9", + "type": "text-field", + "label": "Text of line", + "value": "F.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "f42ce9da-2efc-4b4d-877d-c4d1d981fb7a", + "type": "box-selector", + "state": { + "x": 732, + "y": 2986, + "width": 72, + "height": 60 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "1fa43532-b397-4d45-a2ea-b7a82543ffd2", + "type": "text-field", + "label": "Text of line", + "value": "BRUCKMANN", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "e958670a-53b9-4ec3-afcc-60f421dae727", + "type": "box-selector", + "state": { + "x": 842, + "y": 2986, + "width": 596, + "height": 62 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "2d5a0353-9973-4ede-a1ec-89d7142678c5", + "type": "text-field", + "label": "Text of line", + "value": "•", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "542a5c2a-aaf3-48ef-bae5-471ece38506a", + "type": "box-selector", + "state": { + "x": 1486, + "y": 3014, + "width": 16, + "height": 16 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "fbaa50df-d74e-4ef7-8dbd-7bb25d2fe483", + "type": "text-field", + "label": "Text of line", + "value": "MÜNCHEN", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "0117a007-70cf-4e2f-b1bb-d997d80099f6", + "type": "box-selector", + "state": { + "x": 1550, + "y": 2976, + "width": 464, + "height": 70 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "5e9906e7-3770-4d91-8cef-77fd83868e7d", + "type": "box-selector", + "state": { + "x": 318, + "y": 2976, + "width": 1696, + "height": 72 + } + } + }, + { + "id": "97a702dc-9884-4687-ba8e-430047956e77", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "1f90ff08-c109-428f-ad54-aecdb124cb27", + "type": "text-field", + "label": "Text of line", + "value": "Postanschrift:", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "967051f9-7c7d-48a7-aa71-229da08d340b", + "type": "box-selector", + "state": { + "x": 452, + "y": 3085, + "width": 518, + "height": 40 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "2b7bcd38-7480-44a5-ae31-50bf3bb59c8b", + "type": "text-field", + "label": "Text of line", + "value": "München", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "668a38cd-f208-495b-b653-ff3d45e8bb21", + "type": "box-selector", + "state": { + "x": 1023, + "y": 3085, + "width": 314, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "7e7dd4d3-ac59-4ce6-a28b-0c82a633d266", + "type": "text-field", + "label": "Text of line", + "value": "20,", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b11d2b75-b392-4203-8bf4-7f85a3113a41", + "type": "box-selector", + "state": { + "x": 1381, + "y": 3084, + "width": 87, + "height": 42 + } + }, + "pluralField": "Text of lines", + "previewInline": true + }, + { + "id": "267ed104-ed49-4a15-b3ec-4c11c5d1f6b3", + "type": "text-field", + "label": "Text of line", + "value": "Abholfach", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "87c9914e-e837-42dd-b347-c9300b417f4e", + "type": "box-selector", + "state": { + "x": 1520, + "y": 3082, + "width": 362, + "height": 40 + } + }, + "pluralField": "Text of lines", + "previewInline": true + } + ] + }, + "selector": { + "id": "ad077dcd-7880-4f7c-8a6d-d599c6eeb9cb", + "type": "box-selector", + "state": { + "x": 452, + "y": 3082, + "width": 1430, + "height": 44 + } + } + } + ] + }, + "selector": { + "id": "60471d9b-7538-4b23-b078-80a5cd69cc6b", + "type": "box-selector", + "state": { + "x": 296, + "y": 310, + "width": 1734, + "height": 2820 + } + } + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:collection:21206", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:15267", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:15302", + "type": "Canvas" + } + ], + "derivedFrom": "3366a8c7-644d-4457-966b-3fd5f6a529be", + "contributors": { + "4d13f32e-97f2-493b-9069-1042490cd79b": { + "id": "4d13f32e-97f2-493b-9069-1042490cd79b", + "type": "Person", + "name": "Madoc TS" + } + } +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/01-single-field-with-revision.json b/services/madoc-ts/fixtures/03-revisions/01-single-field-with-revision.json new file mode 100644 index 000000000..28b07a664 --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/01-single-field-with-revision.json @@ -0,0 +1,60 @@ +{ + "structure": { + "id": "2416e3ed-90cc-4160-8d7b-ceefeb6d2adc", + "type": "choice", + "label": "revisions - single field with revision", + "items": [ + { + "id": "31b27c9b-2388-47df-b6f4-73fb4878c1fa", + "label": "Only choice", + "type": "model", + "fields": [ + "name" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "type": "entity", + "properties": { + "name": [ + { + "id": "aca844a3-836a-45c3-bb16-dc28bcdce46f", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + }, + { + "id": "eafb62d7-71b7-47bd-b887-def8655d8d2a", + "type": "text-field", + "label": "Name", + "revision": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "description": "The name of the thing", + "value": "Some value that was submitted" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "contributors": { + "7c26cf57-5950-4849-b533-11e0ee4afa4b": { + "id": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "type": "Person", + "name": "Test person" + } + }, + "revisions": [ + { + "id": "7c26cf57-5950-4849-b533-11e0ee4afa4b", + "structureId": "31b27c9b-2388-47df-b6f4-73fb4878c1fa", + "fields": [ + "name" + ] + } + ], + "id": "b329e009-1c8a-4bed-bfde-c2a587a22f97" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/02-single-field-with-multiple-revisions.json b/services/madoc-ts/fixtures/03-revisions/02-single-field-with-multiple-revisions.json new file mode 100644 index 000000000..4efaee736 --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/02-single-field-with-multiple-revisions.json @@ -0,0 +1,78 @@ +{ + "structure": { + "id": "11f4882a-b246-4415-b90b-d3bb91598835", + "type": "choice", + "label": "revisions - single field with multiple revisions", + "items": [ + { + "id": "b321af45-bac1-4f5e-b069-f4bef4fda215", + "label": "Only choice", + "type": "model", + "fields": [ + "name" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "b3f53013-23cc-45db-825a-12500bf3c20e", + "type": "entity", + "properties": { + "name": [ + { + "id": "06b23c90-b4a4-437c-ade2-fb84bbefaa6e", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + }, + { + "id": "baf51d8c-ce99-4bf4-afd0-0ca2092a7784", + "type": "text-field", + "label": "Name", + "revision": "514c8d52-80b0-49c1-ab97-24a67f29d194", + "value": "Person A wrote this", + "description": "The name of the thing" + }, + { + "id": "205c9b62-48e3-43ff-8853-222dcd357710", + "type": "text-field", + "label": "Name", + "revision": "b4077dff-3bea-4783-9712-32b52a1146e3", + "value": "Person B wrote this", + "description": "The name of the thing" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "contributors": { + "514c8d52-80b0-49c1-ab97-24a67f29d194": { + "id": "514c8d52-80b0-49c1-ab97-24a67f29d194", + "type": "Person", + "name": "Test person A" + }, + "b4077dff-3bea-4783-9712-32b52a1146e3": { + "id": "b4077dff-3bea-4783-9712-32b52a1146e3", + "type": "Person", + "name": "Test person B" + } + }, + "revisions": [ + { + "id": "514c8d52-80b0-49c1-ab97-24a67f29d194", + "fields": [ + "name" + ] + }, + { + "id": "b4077dff-3bea-4783-9712-32b52a1146e3", + "fields": [ + "name" + ] + } + ], + "id": "93d09b85-9332-4b71-8e27-1294c8a963f3" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/03-nested-revision.json b/services/madoc-ts/fixtures/03-revisions/03-nested-revision.json new file mode 100644 index 000000000..ba43cd242 --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/03-nested-revision.json @@ -0,0 +1,109 @@ +{ + "structure": { + "id": "a980bf48-5572-423f-8eac-ac1d491b8e4a", + "type": "choice", + "label": "revisions - nested revision", + "items": [ + { + "id": "d4000be7-1407-43a4-b5e7-75479fb96a0d", + "label": "Person + data", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ], + "name", + "description" + ], + "description": "The only choice in the list" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "a8d5ff43-adb2-456a-a615-3d24fbfa05f7", + "type": "entity", + "properties": { + "name": [ + { + "id": "a1ed84d2-c44c-4877-ac3d-10559acd7fce", + "type": "text-field", + "label": "Name", + "description": "The name of the thing", + "value": "" + } + ], + "description": [ + { + "id": "5d610b23-3b03-400b-a9cf-6923563639eb", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing", + "value": "" + } + ], + "person": [ + { + "id": "5c8a5874-8bca-422c-be71-300612d67c72", + "type": "entity", + "label": "Person", + "properties": { + "firstName": [ + { + "id": "7b45ec25-15a6-40dd-9a1d-0fd1d673df15", + "type": "text-field", + "label": "First name", + "value": "" + }, + { + "id": "dda6d8bc-ca6d-48e0-8bcc-a24537586346", + "type": "text-field", + "label": "First name", + "value": "Some value", + "revision": "fa500021-7408-4318-ab05-ac6e4d4a3096" + } + ], + "lastName": [ + { + "id": "f5e7480c-411e-486d-a91e-0bf24f146ab5", + "type": "text-field", + "label": "Last name", + "value": "" + } + ] + }, + "description": "Describe a person" + } + ] + }, + "label": "Nested choices", + "description": "" + }, + "contributors": { + "fa500021-7408-4318-ab05-ac6e4d4a3096": { + "id": "fa500021-7408-4318-ab05-ac6e4d4a3096", + "type": "Person", + "name": "Test person" + } + }, + "revisions": [ + { + "id": "fa500021-7408-4318-ab05-ac6e4d4a3096", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ], + "name" + ] + } + ], + "id": "2cc4131d-4f8d-4ceb-b140-48cd513b5e4f" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/04-dual-transcription.json b/services/madoc-ts/fixtures/03-revisions/04-dual-transcription.json new file mode 100644 index 000000000..9e9e13f72 --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/04-dual-transcription.json @@ -0,0 +1,77 @@ +{ + "structure": { + "id": "3ad67408-a442-4214-9b25-1e9818a90a2c", + "type": "choice", + "label": "revisions - dual transcription", + "items": [ + { + "id": "49625e01-cc02-445a-8db7-c5e3899661f8", + "label": "Transcriptions", + "type": "model", + "fields": [ + "transcription" + ] + } + ], + "description": "" + }, + "document": { + "id": "279e8fb2-13c3-43cc-aff2-2b41d9025828", + "type": "entity", + "label": "Name of entity", + "properties": { + "transcription": [ + { + "id": "762eff26-1590-4194-b93b-5a337ae40ad2", + "type": "text-field", + "label": "Transcription", + "allowMultiple": false, + "value": "Canonical transcription, maybe OCR" + }, + { + "id": "c0ac6fd6-9146-4eac-a2b3-0067bc689cb6", + "type": "text-field", + "label": "Transcription", + "allowMultiple": false, + "revision": "04267f75-bb8d-4321-8046-12db3f9d6ceb", + "value": "Person A created this one" + }, + { + "id": "c2b68f02-cce4-4a12-940b-d1359d89e807", + "type": "text-field", + "label": "Transcription", + "allowMultiple": false, + "revision": "81ab315e-200e-4649-bb11-99db766a5f66", + "value": "Person B created this one" + } + ] + } + }, + "contributors": { + "04267f75-bb8d-4321-8046-12db3f9d6ceb": { + "id": "04267f75-bb8d-4321-8046-12db3f9d6ceb", + "type": "Person", + "name": "Test person A" + }, + "81ab315e-200e-4649-bb11-99db766a5f66": { + "id": "81ab315e-200e-4649-bb11-99db766a5f66", + "type": "Person", + "name": "Test person B" + } + }, + "revisions": [ + { + "id": "04267f75-bb8d-4321-8046-12db3f9d6ceb", + "fields": [ + "transcription" + ] + }, + { + "id": "81ab315e-200e-4649-bb11-99db766a5f66", + "fields": [ + "transcription" + ] + } + ], + "id": "737150d3-2c72-4eeb-9fd4-42ad085cdf7f" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/05-allow-multiple-transcriptions.json b/services/madoc-ts/fixtures/03-revisions/05-allow-multiple-transcriptions.json new file mode 100644 index 000000000..5fae501de --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/05-allow-multiple-transcriptions.json @@ -0,0 +1,128 @@ +{ + "structure": { + "id": "8d36f25f-649b-47b4-b7fe-02043f8cfe7a", + "type": "choice", + "label": "revisions - allow multiple transcriptions", + "items": [ + { + "id": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + "label": "Transcriptions", + "type": "model", + "fields": [ + "transcription" + ] + } + ], + "description": "" + }, + "document": { + "id": "47e8a9d8-76f8-422b-91af-b457d1c624a0", + "type": "entity", + "label": "Name of entity", + "properties": { + "transcription": [ + { + "id": "1615a172-b2c5-4192-bcc1-606a871b6230", + "type": "text-field", + "label": "Transcription", + "allowMultiple": true, + "value": "" + }, + { + "id": "2666cf79-ef2f-419f-a3f4-038216a89783", + "type": "text-field", + "label": "Transcription", + "revises": "1615a172-b2c5-4192-bcc1-606a871b6230", + "allowMultiple": true, + "revision": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "value": "Person A created this one, which revises the original" + }, + { + "id": "1efd5946-a3a1-484f-a862-710741a3b682", + "type": "text-field", + "label": "Transcription", + "allowMultiple": true, + "revision": "daf3f9d9-2a16-4c1f-8657-3560775bd9eb", + "value": "Person B created this one, which is completely new" + }, + { + "id": "892f3abe-bbbe-4b1e-9167-a52ec76ea5c1", + "type": "text-field", + "label": "Transcription", + "allowMultiple": true, + "revision": "bb5d55b1-6c38-4bb9-a6e6-ed236347671b", + "value": "Person C created this one" + }, + { + "id": "912683e3-fd6e-4599-bb0f-acd232c9cf87", + "type": "text-field", + "label": "Transcription", + "allowMultiple": true, + "revises": "2666cf79-ef2f-419f-a3f4-038216a89783", + "revision": "896c6278-655a-4a39-965c-08abbffb0bf2", + "value": "Person D created this one, which overrides Person As one" + } + ] + } + }, + "contributors": { + "f496a9aa-25eb-4b1d-9d94-9cdcef03e527": { + "id": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "type": "Person", + "name": "Test person A" + }, + "daf3f9d9-2a16-4c1f-8657-3560775bd9eb": { + "id": "daf3f9d9-2a16-4c1f-8657-3560775bd9eb", + "type": "Person", + "name": "Test person B" + }, + "bb5d55b1-6c38-4bb9-a6e6-ed236347671b": { + "id": "bb5d55b1-6c38-4bb9-a6e6-ed236347671b", + "type": "Person", + "name": "Test person C" + }, + "896c6278-655a-4a39-965c-08abbffb0bf2": { + "id": "896c6278-655a-4a39-965c-08abbffb0bf2", + "type": "Person", + "name": "Test person D" + } + }, + "revisions": [ + { + "id": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "approved": true, + "status": "accepted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + "fields": [ + "transcription" + ] + }, + { + "id": "daf3f9d9-2a16-4c1f-8657-3560775bd9eb", + "status": "accepted", + "approved": true, + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + "fields": [ + "transcription" + ] + }, + { + "id": "bb5d55b1-6c38-4bb9-a6e6-ed236347671b", + "status": "submitted", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + "fields": [ + "transcription" + ] + }, + { + "id": "896c6278-655a-4a39-965c-08abbffb0bf2", + "status": "submitted", + "revises": "f496a9aa-25eb-4b1d-9d94-9cdcef03e527", + "structureId": "fd847948-11bf-42ca-bfdd-cab85ea818f3", + "fields": [ + "transcription" + ] + } + ], + "id": "143d9c4a-5d4e-4ca2-89f3-dc4bd7b45e3e" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/06-model-root.json b/services/madoc-ts/fixtures/03-revisions/06-model-root.json new file mode 100644 index 000000000..6e0d1814a --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/06-model-root.json @@ -0,0 +1,709 @@ +{ + "structure": { + "id": "2416e3ed-90cc-4160-8d7b-ceefeb6d2adc", + "type": "choice", + "label": "revisions - model root", + "items": [ + { + "id": "e5c95a41-2544-4cb6-aa69-376c60f617cc", + "label": "Edit field A", + "type": "model", + "fields": [ + "field-a" + ] + }, + { + "id": "9d4236af-a38d-4fbe-ac32-200fd07b5b08", + "label": "Edit model A - no model root", + "description": "This should allow you to create new 'model-a' instances", + "type": "model", + "fields": [ + [ + "model-a", + [ + "field-b" + ] + ] + ] + }, + { + "id": "e31b1e2c-7362-45f6-86f4-52c1ff637f45", + "label": "Edit model A - with model root", + "description": "This should allow you to edit all of the 'field-b' fields. If model-a id is specified then it should allow you to only edit that one documents field, since the field has allowMultiple false", + "type": "model", + "modelRoot": [ + "model-a" + ], + "fields": [ + [ + "model-a", + [ + "field-b" + ] + ] + ] + }, + { + "id": "2741b3c3-3bc3-4793-b766-7888c34970f6", + "label": "Edit model A - model root and field above root", + "description": "This should throw an error", + "type": "model", + "modelRoot": [ + "model-a" + ], + "fields": [ + "field-a", + [ + "model-a", + [ + "field-b" + ] + ] + ] + }, + { + "id": "475fa8bc-607d-4d88-b0ef-1e1e08e4edc2", + "label": "Edit model C - no model root", + "description": "This should [???]", + "type": "model", + "fields": [ + [ + "model-a", + [ + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "0c94ac8e-04ca-47ac-87de-3789fa1f7adc", + "label": "Edit model C - model root depth 1", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a" + ], + "fields": [ + [ + "model-a", + [ + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "a6781d80-331a-45d5-8b2e-b544cd8bd6d8", + "label": "Edit model C - model root depth 2", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a", + "model-c" + ], + "fields": [ + [ + "model-a", + [ + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "c7950199-2910-474c-8551-bee1c5838c5b", + "label": "Edit model C - model root depth 1 + field under", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a" + ], + "fields": [ + [ + "model-a", + [ + "field-b", + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "abe68dc4-7326-421d-9854-b97330eca026", + "label": "Edit model C - model root depth 1 + field over", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a" + ], + "fields": [ + "field-a", + [ + "model-a", + [ + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "b36f45cb-0d14-4fd9-bd19-6335b72d7174", + "label": "Edit model C - model root depth 2 + field over", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a", + "model-c" + ], + "fields": [ + "field-a", + [ + "model-a", + [ + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "09524831-d748-4469-ab1f-fb9e69f56427", + "label": "Edit model C - model root depth 2 + field over (x2)", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-a", + "model-c" + ], + "fields": [ + "field-a", + [ + "model-a", + [ + "field-b", + [ + "model-c", + [ + "field-c" + ] + ] + ] + ] + ] + }, + { + "id": "0577d939-4a66-4c9f-ac8b-b858483a8f1f", + "label": "Edit model B - edit nested field (no duplicate allow)", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-b" + ], + "fields": [ + [ + "model-b", + [ + "field-d", + [ + "model-c", + [ + "field-e", + [ + "model-d", + [ + "field-f" + ] + ] + ] + ] + ] + ] + ] + }, + { + "id": "0f426597-f4c1-4ce3-8f51-1d1964fc28f5", + "label": "Edit model B - edit nested field (root on allowMultiple=false)", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-b", + "model-c" + ], + "fields": [ + [ + "model-b", + [ + "field-d", + [ + "model-c", + [ + "field-e", + [ + "model-d", + [ + "field-f" + ] + ] + ] + ] + ] + ] + ] + }, + { + "id": "0b619955-21d7-4e01-8907-b540a18528ce", + "label": "Edit model B - depth 3", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-b", + "model-c", + "model-d" + ], + "fields": [ + [ + "model-b", + [ + "field-d", + [ + "model-c", + [ + "field-e", + [ + "model-d", + [ + "field-f" + ] + ] + ] + ] + ] + ] + ] + }, + { + "id": "dba590fc-78b6-4def-beee-20447977a845", + "label": "Edit model F - depth 1", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-f" + ], + "fields": [ + [ + "model-f", + [ + [ + "model-g", + [ + "field-f" + ] + ] + ] + ] + ] + }, + { + "id": "205fac90-1659-41fa-abf4-199bf5f9b092", + "label": "Edit model F - depth 2", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-f", + "model-g" + ], + "fields": [ + [ + "model-f", + [ + [ + "model-g", + [ + "field-f" + ] + ] + ] + ] + ] + }, + { + "id": "7e5da7c6-ec21-4277-bf5c-37c5044b450f", + "label": "Edit model H - depth 0", + "description": "This should [???]", + "type": "model", + "fields": [ + [ + "model-h", + [ + [ + "model-i", + [ + "field-g" + ] + ] + ] + ] + ] + }, + { + "id": "5fed695d-2815-41c4-9a3b-fb2b27a929ee", + "label": "Edit model H - depth 1", + "description": "This should [???]", + "modelRoot": [ + "model-h" + ], + "type": "model", + "fields": [ + [ + "model-h", + [ + [ + "model-i", + [ + "field-g" + ] + ] + ] + ] + ] + }, + { + "id": "0e65ee6c-4ac9-4112-b291-42998845e30c", + "label": "Edit model H - depth 2", + "description": "This should [???]", + "modelRoot": [ + "model-h", + "model-i" + ], + "type": "model", + "fields": [ + [ + "model-h", + [ + [ + "model-i", + [ + "field-g" + ] + ] + ] + ] + ] + }, + { + "id": "45e13064-887e-4dc2-aebf-4742f4beea29", + "label": "Edit model B - one field", + "description": "This should [???]", + "type": "model", + "modelRoot": [ + "model-b" + ], + "fields": [ + [ + "model-b", + [ + [ + "model-c", + [ + [ + "model-d", + [ + "field-f" + ] + ] + ] + ] + ] + ] + ] + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "3353dc03-9f35-49e7-9b81-4090fa533c64", + "type": "entity", + "properties": { + "field-a": [ + { + "id": "5f0e4511-957d-429c-8151-396ec4585a2d", + "type": "text-field", + "value": "value for field", + "label": "Top level text field" + } + ], + "model-a": [ + { + "id": "d58f5f48-28bf-44ca-8396-c30ed4ae779b", + "type": "entity", + "label": "Model A.1: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-b": [ + { + "id": "58a09b28-219c-4609-b24f-c0ed6e5cfa42", + "type": "text-field", + "value": "value for field", + "label": "First level text field" + } + ], + "model-c": [ + { + "id": "435017ff-5bc0-4559-ac49-5aa1e1058121", + "type": "entity", + "label": "Model C.1: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-c": [ + { + "id": "53cdccf0-ef2b-432c-85dc-f9f6fb177297", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ] + } + } + ] + } + } + ], + "model-b": [ + { + "id": "d948d756-14ad-4554-92ce-0296e5ce8735", + "type": "entity", + "label": "Model B.1: Allow multiple = false", + "allowMultiple": false, + "properties": { + "field-d": [ + { + "id": "7ff9cbd8-a393-4b82-ab2b-fd3814541722", + "type": "text-field", + "value": "value for field", + "label": "First level text field" + } + ], + "model-c": [ + { + "id": "b51eae75-e76f-496f-b048-f0b5b7f6cacd", + "type": "entity", + "label": "Model B.2: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-e": [ + { + "id": "7c19f5f0-75ba-4153-9f96-35055bc2a286", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ], + "model-d": [ + { + "id": "4e167b01-6c0c-4e2f-bec6-d0f4dc4126eb", + "type": "entity", + "label": "Model B.3: Allow multiple = false", + "allowMultiple": false, + "properties": { + "field-f": [ + { + "id": "23fb8e8b-6ae4-4503-b688-80116b5df127", + "type": "text-field", + "value": "value for field", + "label": "Third level text field" + } + ] + } + } + ] + } + } + ] + } + } + ], + "model-f": [ + { + "id": "602df1a8-9071-409a-ba6d-43b8dfc97683", + "type": "entity", + "label": "Model F.1: Allow multiple = false", + "allowMultiple": false, + "properties": { + "field-e": [ + { + "id": "64e0aa6d-3288-4b6a-9cea-8e40373861d9", + "type": "text-field", + "value": "value for field", + "label": "First level text field" + } + ], + "model-g": [ + { + "id": "1a7e07f0-f111-406e-972e-71976258a47f", + "type": "entity", + "label": "Model F.2: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-f": [ + { + "id": "1ecda588-c846-48c1-813c-2fd4ccfd6700", + "type": "text-field", + "value": "value for field (first)", + "label": "Second level text field (first)" + } + ] + } + }, + { + "id": "20254fca-fdad-4c70-b748-590f206e51d8", + "type": "entity", + "label": "Model F.3: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-f": [ + { + "id": "5019c619-a64c-4893-bf39-857df15f1e92", + "type": "text-field", + "value": "value for field (second)", + "label": "Second level text field (second)" + } + ] + } + } + ] + } + } + ], + "model-h": [ + { + "id": "ab70224e-4cb8-4d8d-a379-9769ad3e06e5", + "type": "entity", + "label": "Model H.1: Allow multiple = true", + "allowMultiple": true, + "properties": { + "model-i": [ + { + "id": "ecbd3446-6da3-4125-934f-1a3b766b8afe", + "type": "entity", + "label": "Model I.1: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-g": [ + { + "id": "e72d1003-5b50-42ed-9495-f4d80cc53961", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ] + } + }, + { + "id": "7de07c37-6157-4d63-8097-d2d4a78b778f", + "type": "entity", + "label": "Model I.2: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-g": [ + { + "id": "8d4e3122-0f15-42db-864d-52c1a2843a90", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ] + } + } + ] + } + }, + { + "id": "aa6fe309-deef-4211-802c-dbc0f2ba16ed", + "type": "entity", + "label": "Model H.2: Allow multiple = true", + "allowMultiple": true, + "properties": { + "model-i": [ + { + "id": "3c5abf69-5258-4b0f-a082-a7e53ec15c47", + "type": "entity", + "label": "Model I.3: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-g": [ + { + "id": "8fed8e4d-fa2f-4279-a145-ea88b1193bae", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ] + } + }, + { + "id": "915106fd-f663-4e55-a727-8253e251c62d", + "type": "entity", + "label": "Model I.4: Allow multiple = true", + "allowMultiple": true, + "properties": { + "field-g": [ + { + "id": "bb6ec6db-eaab-4271-b69e-34bf5501684b", + "type": "text-field", + "value": "value for field", + "label": "Second level text field" + } + ] + } + } + ] + } + } + ] + }, + "label": "Simple document", + "description": "" + }, + "target": [ + { + "type": "manifest", + "id": "https://view.nls.uk/manifest/7446/74464117/manifest.json" + }, + { + "type": "canvas", + "id": "https://view.nls.uk/iiif/7446/74464117/canvas/3" + } + ], + "contributors": {}, + "revisions": [], + "id": "998f9673-61aa-4979-a6e6-258306917119" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/03-revisions/07-single-field-with-values.json b/services/madoc-ts/fixtures/03-revisions/07-single-field-with-values.json new file mode 100644 index 000000000..4941f415b --- /dev/null +++ b/services/madoc-ts/fixtures/03-revisions/07-single-field-with-values.json @@ -0,0 +1,71 @@ +{ + "structure": { + "id": "0215bbe1-27b3-4783-a9b7-def91578b445", + "type": "choice", + "label": "revisions - single field with values", + "items": [ + { + "id": "d14f911a-051c-4dac-b96e-b0ff951e7754", + "label": "Only choice", + "type": "model", + "fields": [ + "name" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "5e64ba01-05d0-424b-b083-dde979b7fa46", + "type": "entity", + "properties": { + "name": [ + { + "id": "4f655b57-9eb3-4b21-8a0b-0652ae16df42", + "type": "text-field", + "label": "Name", + "allowMultiple": true, + "description": "The name of the thing" + }, + { + "id": "1e0017ea-0553-4973-89a5-44583a9a1e4f", + "type": "text-field", + "label": "Name", + "allowMultiple": true, + "revision": "2badfa7f-369c-488c-bdde-df236b70eb36", + "description": "The name of the thing", + "value": "FIRST VALUE" + }, + { + "id": "c9470cec-05f4-4987-80ca-04eb7d835055", + "type": "text-field", + "label": "Name", + "allowMultiple": true, + "revision": "2badfa7f-369c-488c-bdde-df236b70eb36", + "description": "The name of the thing", + "value": "SECOND VALUE" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "contributors": { + "613da999-e851-4867-8439-d5cf9e054da0": { + "id": "613da999-e851-4867-8439-d5cf9e054da0", + "type": "Person", + "name": "Test person" + } + }, + "revisions": [ + { + "id": "2badfa7f-369c-488c-bdde-df236b70eb36", + "structureId": "d14f911a-051c-4dac-b96e-b0ff951e7754", + "fields": [ + "name" + ] + } + ], + "id": "4bb58cd0-8586-44c6-85b3-d11a698cd720" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/01-simple-selector.json b/services/madoc-ts/fixtures/04-selectors/01-simple-selector.json new file mode 100644 index 000000000..182df276c --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/01-simple-selector.json @@ -0,0 +1,41 @@ +{ + "structure": { + "id": "0b190e61-6870-41ed-9ef2-e921811683bd", + "type": "choice", + "label": "selectors - simple selector", + "items": [ + { + "id": "34083298-1792-4790-b909-e0166ea20a5d", + "label": "Only choice", + "type": "model", + "fields": [ + "name" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "d2820942-a460-41fb-94a8-878d952189c3", + "type": "entity", + "properties": { + "name": [ + { + "id": "76e7ea6c-fcd1-4b71-aa8f-be5e100e755d", + "type": "text-field", + "selector": { + "id": "0c15c2b8-48e9-4c83-b77e-054cd8215f93", + "type": "box-selector", + "state": null + }, + "label": "Name", + "description": "The name of the thing" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "id": "ca100c44-6593-48cf-80a5-96d8e440aa0b" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/02-multiple-selectors.json b/services/madoc-ts/fixtures/04-selectors/02-multiple-selectors.json new file mode 100644 index 000000000..9390b016f --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/02-multiple-selectors.json @@ -0,0 +1,55 @@ +{ + "structure": { + "id": "32777457-064d-42c5-866a-678e6c95c722", + "type": "choice", + "label": "selectors - multiple selectors", + "items": [ + { + "id": "bc28bc85-2fc5-4371-916a-c23779a88b43", + "label": "Only choice", + "type": "model", + "fields": [ + "firstName", + "lastName" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "document": { + "id": "bc5e10b9-1ee2-4318-a46c-36c61b4e58a2", + "type": "entity", + "properties": { + "firstName": [ + { + "id": "060dd509-d167-49f5-bd9e-8da5080583e4", + "type": "text-field", + "selector": { + "id": "6ffcfd34-4141-4f38-918d-5348e9d42ab5", + "type": "box-selector", + "state": null + }, + "label": "First name", + "description": "The name of the thing" + } + ], + "lastName": [ + { + "id": "9d81a5d1-c626-41a6-9f6f-e11bda2fbdb3", + "type": "text-field", + "selector": { + "id": "9b25da35-f1fb-4b58-92e4-0eef3d929029", + "type": "box-selector", + "state": null + }, + "label": "Last name", + "description": "The last name of the thing" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "id": "63257644-8196-43fc-ad6f-633631f20824" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/03-nested-selector.json b/services/madoc-ts/fixtures/04-selectors/03-nested-selector.json new file mode 100644 index 000000000..ce058dbdb --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/03-nested-selector.json @@ -0,0 +1,144 @@ +{ + "structure": { + "id": "86dafca3-ae73-4d61-b655-a2919fe4993c", + "type": "choice", + "label": "selectors - nested selector", + "items": [ + { + "id": "7b9703b9-22f5-40ab-8bfa-bdaf6c1cfc67", + "label": "Choice 1", + "type": "model", + "fields": [ + "name", + "description" + ], + "description": "The first choice in the list" + }, + { + "id": "18e0d089-e87c-448f-9ef2-3282a44cf6d2", + "label": "Person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "98cea2df-824f-46d7-ae50-c6d1340b26e2", + "label": "All fields", + "type": "model", + "fields": [ + "name", + "description", + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "c2ec0d72-6ac8-4bb8-a037-1516ffc3dd9a", + "type": "entity", + "properties": { + "name": [ + { + "id": "7cdfa7ab-f3b2-466d-a802-a2090e68723d", + "selector": { + "id": "74830725-7279-4a91-aaab-35f69a825a03", + "type": "box-selector", + "state": null + }, + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "03d2b79f-7efc-4df5-b78f-32bb49a9d650", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ], + "person": [ + { + "id": "cc2570c7-b3a5-4e5b-8f3d-3b3693769969", + "type": "entity", + "label": "Person", + "allowMultiple": true, + "properties": { + "firstName": [ + { + "id": "c3434721-19f7-4402-9bfb-1e11875a0de0", + "selector": { + "id": "05a99c13-eedd-4a14-940a-f774fc461ca4", + "type": "box-selector", + "state": null + }, + "type": "text-field", + "label": "First name", + "value": "first first name" + } + ], + "lastName": [ + { + "id": "f55be233-4a97-4e14-8863-04706799b2d8", + "type": "text-field", + "label": "Last name", + "value": "first last name" + } + ] + }, + "description": "Describe a person" + }, + { + "id": "56405dc7-b910-45e0-8ade-898594276795", + "type": "entity", + "label": "Person", + "allowMultiple": true, + "properties": { + "firstName": [ + { + "id": "9a55a096-4a79-46b0-8111-d9775d074a14", + "selector": { + "id": "007a5ace-b1a4-49ca-8dd3-c78aee2d5409", + "type": "box-selector", + "state": null + }, + "type": "text-field", + "label": "First name", + "value": "second first name" + } + ], + "lastName": [ + { + "id": "4772f6cc-9ee3-48b2-9c5b-bb19ef48aeaf", + "type": "text-field", + "label": "Last name", + "value": "second last name" + } + ] + }, + "description": "Describe a person" + } + ] + }, + "label": "Nested choices", + "description": "" + }, + "id": "fdcd413f-fa2a-424b-a8c5-ae53080f77b5" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/04-mulitple-nested-selectors.json b/services/madoc-ts/fixtures/04-selectors/04-mulitple-nested-selectors.json new file mode 100644 index 000000000..ee7cc898d --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/04-mulitple-nested-selectors.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "f78cb77f-ff5d-4c95-afd0-34df8a967b0f", + "type": "choice", + "label": "selectors - mulitple nested selectors", + "items": [] + }, + "document": { + "id": "c01518ad-a79c-43a4-a301-22b959faa089", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "109a102e-9c0b-4253-bb48-a75730a6030e" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/05-wunder-selector.json b/services/madoc-ts/fixtures/04-selectors/05-wunder-selector.json new file mode 100644 index 000000000..e69e78373 --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/05-wunder-selector.json @@ -0,0 +1,56 @@ +{ + "structure": { + "id": "892f0228-53d2-4f79-986d-712c122bd33f", + "type": "choice", + "label": "selectors - wunder selector", + "items": [ + { + "id": "e801f905-5afc-4612-9e59-2b78cf407b9d", + "label": "Book cover", + "type": "model", + "fields": [ + "title" + ], + "description": "Describe the cover of a book" + } + ] + }, + "target": [ + { + "type": "manifest", + "id": "https://wellcomelibrary.org/iiif/b18035723/manifest" + }, + { + "type": "canvas", + "id": "https://wellcomelibrary.org/iiif/b18035723/canvas/c0" + } + ], + "document": { + "id": "12396c9f-0706-49c1-9559-1ab853040be1", + "type": "entity", + "properties": { + "title": [ + { + "id": "35ffb3e1-76ef-437e-acce-3523db75b54a", + "type": "text-field", + "selector": { + "id": "d35b5a96-e653-4b9b-8f6c-a93ea590dbd4", + "type": "box-selector", + "state": { + "height": 771, + "width": 2199, + "x": 164, + "y": 235 + } + }, + "label": "Title", + "description": "The title of the book as it is printed on the cover.", + "value": "WUNDER DER VERERBUNG" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "id": "31fd2fae-7fd5-40bd-9300-007c5a7f28f0" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/06-entity-selector.json b/services/madoc-ts/fixtures/04-selectors/06-entity-selector.json new file mode 100644 index 000000000..339f2a240 --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/06-entity-selector.json @@ -0,0 +1,119 @@ +{ + "structure": { + "id": "78412b11-77d9-489f-9e5f-e29a7b8427ed", + "type": "choice", + "label": "selectors - entity selector", + "items": [ + { + "id": "e8258a74-eafa-4e24-9548-cf23ea8d03bb", + "label": "All fields", + "type": "model", + "fields": [ + "name", + "description", + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + } + ], + "description": "Contains 3 choices with multiple text fields nested" + }, + "document": { + "id": "2229b369-9b8c-461f-b931-1a2d07a359e3", + "type": "entity", + "properties": { + "name": [ + { + "id": "20c0caad-9083-4348-a02b-2cec80a6adec", + "selector": { + "id": "52c6e7a7-c27c-4f6d-9404-07d30bc886b3", + "type": "box-selector", + "state": null + }, + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ], + "description": [ + { + "id": "8dfe562b-bdba-46b8-8517-971acf48fd51", + "type": "text-field", + "label": "Description", + "description": "Tell us something about the thing" + } + ], + "person": [ + { + "id": "985daf4f-da3e-4948-8e7d-5513acb2cd2f", + "type": "entity", + "label": "Person", + "selector": { + "id": "ecee8404-11b1-4c44-b7a9-4c800b2ffd42", + "type": "box-selector", + "state": null + }, + "allowMultiple": true, + "properties": { + "firstName": [ + { + "id": "221824be-4566-45f1-bc15-fed59a2d83fe", + "type": "text-field", + "label": "First name", + "value": "first first name" + } + ], + "lastName": [ + { + "id": "1234162e-a5d3-4357-8cad-5a3001b9a3a6", + "type": "text-field", + "label": "Last name", + "value": "first last name" + } + ] + }, + "description": "Describe a person" + }, + { + "id": "a58c6080-4ecb-4580-a7c5-730cd9f72580", + "type": "entity", + "label": "Person", + "allowMultiple": true, + "selector": { + "id": "3020f977-2304-43ee-9e0e-c7b9abf216b6", + "type": "box-selector", + "state": null + }, + "properties": { + "firstName": [ + { + "id": "4b3440c4-6443-46d7-bdfd-e85736a1e254", + "type": "text-field", + "label": "First name", + "value": "second first name" + } + ], + "lastName": [ + { + "id": "252468e6-8d78-4984-95af-549038ec6a57", + "type": "text-field", + "label": "Last name", + "value": "second last name" + } + ] + }, + "description": "Describe a person" + } + ] + }, + "label": "Nested choices", + "description": "" + }, + "id": "4c1c9a86-617e-4380-8bd4-eb4706ac9eb3" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/07-top-level-selector.json b/services/madoc-ts/fixtures/04-selectors/07-top-level-selector.json new file mode 100644 index 000000000..0338fae73 --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/07-top-level-selector.json @@ -0,0 +1,56 @@ +{ + "structure": { + "id": "ce61be72-aed8-4f5f-ba07-c42829eba7d0", + "type": "choice", + "label": "selectors - top level selector", + "items": [ + { + "id": "7f244335-6501-4faf-a089-c3f2bb18ffad", + "label": "Only choice", + "type": "model", + "fields": [ + "name" + ], + "description": "This could be a top level item without a choice." + } + ], + "description": "Contains single choice with single text field" + }, + "target": [ + { + "type": "manifest", + "id": "https://wellcomelibrary.org/iiif/b18035723/manifest" + }, + { + "type": "canvas", + "id": "https://wellcomelibrary.org/iiif/b18035723/canvas/c0" + } + ], + "document": { + "id": "5e2bbb8a-d351-476b-8925-43a40c51a808", + "type": "entity", + "selector": { + "id": "b3c438a0-6926-4622-ad72-2973f674a649", + "type": "box-selector", + "state": { + "height": 771, + "width": 2199, + "x": 164, + "y": 235 + } + }, + "properties": { + "name": [ + { + "id": "cbbea03e-22f3-406c-8f9d-b3ab5676d998", + "type": "text-field", + "label": "Name", + "description": "The name of the thing" + } + ] + }, + "label": "Simple document", + "description": "" + }, + "id": "fa843fe4-c8d3-4005-a5eb-e0765377d48d" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/04-selectors/08-hocr-output.json b/services/madoc-ts/fixtures/04-selectors/08-hocr-output.json new file mode 100644 index 000000000..5f1c389e0 --- /dev/null +++ b/services/madoc-ts/fixtures/04-selectors/08-hocr-output.json @@ -0,0 +1,188 @@ +{ + "id": "c800d47f-6053-4ad3-9c2f-f5d88756f47e", + "structure": { + "id": "d09d9b0c-c465-4777-b102-f54398982b78", + "type": "choice", + "description": "test", + "label": "selectors - hocr output", + "items": [ + { + "id": "b65179f7-08fa-46b0-95dd-ceec9387de4c", + "type": "model", + "description": "test", + "label": "Transcribe", + "fields": [ + "label", + "description", + "html-test", + "dropdown" + ], + "instructions": "Fill in the fields" + }, + { + "id": "c8bb939a-7a76-4b15-9f77-81375519128c", + "type": "model", + "label": "OCR Example", + "fields": [ + [ + "paragraph", + [ + [ + "lines", + [ + "text" + ] + ] + ] + ] + ] + } + ] + }, + "document": { + "id": "5b8b5649-b7c4-47eb-911e-329556b26f5f", + "type": "entity", + "label": "Untitled document", + "properties": { + "paragraph": [ + { + "id": "159621fb-4f93-4cd7-a394-5a1141fc1091", + "type": "entity", + "label": "Paragraph", + "pluralLabel": "Paragraphs", + "description": "Region of the page denoting a single paragraph", + "allowMultiple": true, + "labelledBy": "lines", + "properties": { + "lines": [ + { + "id": "64e82cb7-16f8-432e-b2b7-3828233a134c", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": true, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "eb122262-fab3-43c8-9432-ac93dad3abf8", + "type": "text-field", + "label": "Text of line", + "value": "", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "da7e26f8-9797-423e-a0cf-276df7b859ea", + "type": "box-selector", + "state": { + "x": 10, + "y": 20, + "width": 30, + "height": 40 + } + }, + "multiline": false, + "pluralField": "Text of lines" + } + ] + }, + "selector": { + "id": "0d0a0325-6d9a-407a-93c2-db0f55bb7209", + "type": "box-selector", + "state": { + "x": 100, + "y": 200, + "width": 300, + "height": 400 + } + } + } + ] + }, + "selector": { + "id": "2f32a9f6-525e-4cba-9bef-115129680fce", + "type": "box-selector", + "state": { + "x": 200, + "y": 400, + "width": 600, + "height": 800 + } + } + } + ], + "label": [ + { + "id": "289f768b-c904-4bcf-b621-85dbd8b6ecf9", + "type": "html-field", + "label": "label", + "value": "" + } + ], + "html-test": [ + { + "id": "c0eebc2d-553f-4c8f-8a92-e66ecea6819d", + "type": "html-field", + "label": "html-test", + "value": "" + } + ], + "description": [ + { + "id": "6a62a11f-171d-41e9-ab41-747d7748a99b", + "type": "text-field", + "label": "description", + "value": "", + "multiline": true, + "enableLinks": false, + "enableExternalImages": false + } + ], + "dropdown": [ + { + "id": "3bef2d0d-bd96-4955-ab7e-f410df217b60", + "type": "dropdown-field", + "label": "dropdown test", + "value": "d", + "options": [ + { + "text": "Choice A", + "value": "a" + }, + { + "text": "Choice B", + "value": "b" + }, + { + "text": "Choice C", + "value": "c" + }, + { + "text": "Choice D", + "value": "d" + } + ], + "clearable": true + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:canvas:6095", + "type": "Canvas" + }, + { + "id": "urn:madoc:manifest:6058", + "type": "Manifest" + } + ], + "contributors": { + "8ec9ed91-22a3-4e5c-bc67-0684173c4a7c": { + "id": "8ec9ed91-22a3-4e5c-bc67-0684173c4a7c", + "type": "Person", + "name": "Madoc TS" + } + } +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/05-profiles/01-comments.json b/services/madoc-ts/fixtures/05-profiles/01-comments.json new file mode 100644 index 000000000..4de9754e2 --- /dev/null +++ b/services/madoc-ts/fixtures/05-profiles/01-comments.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "feefb384-db35-4b23-a60b-979680bc548c", + "type": "choice", + "label": "profiles - comments", + "items": [] + }, + "document": { + "id": "3a0e2700-ad58-4468-9127-f9c5be6ca87f", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "d0e513e0-0d05-406c-b52e-e63eac54e2cb" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/05-profiles/02-tabs.json b/services/madoc-ts/fixtures/05-profiles/02-tabs.json new file mode 100644 index 000000000..a511e5a8f --- /dev/null +++ b/services/madoc-ts/fixtures/05-profiles/02-tabs.json @@ -0,0 +1,69 @@ +{ + "id": "0d32980f-8964-49ff-9dea-47fc7510c409", + "structure": { + "id": "006d634e-e8c4-4edd-9d37-b4beff3da7f2", + "type": "choice", + "label": "profiles - tabs", + "profile": [ + "tabs" + ], + "items": [ + { + "id": "6c42bb4e-05db-4c3c-b423-20e4972dc11f", + "type": "model", + "label": "Tab A", + "fields": [ + "name" + ] + }, + { + "id": "2f612ee8-8756-4ebf-98bb-aa25b7585481", + "type": "model", + "label": "Tab B", + "fields": [ + "something" + ] + }, + { + "id": "15f5306e-9acc-4d35-accc-e7cb9608f302", + "type": "model", + "label": "Tab C", + "fields": [ + "something_else" + ] + } + ] + }, + "document": { + "id": "af6480bd-1805-4229-a11a-b4e939032549", + "type": "entity", + "label": "Untitled document", + "properties": { + "something_else": [ + { + "id": "d18fbed3-a74e-402a-95b0-1ae03ed8f760", + "type": "text-field", + "label": "something_else", + "value": "" + } + ], + "something": [ + { + "id": "34b88eed-52ae-4eec-b6e9-b4a404e0f0cd", + "type": "text-field", + "label": "something", + "value": "" + } + ], + "name": [ + { + "id": "7daa9087-7934-400d-922b-d245a4b73f27", + "type": "text-field", + "label": "name", + "value": "" + } + ] + } + }, + "target": null +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/05-profiles/03-listing.json b/services/madoc-ts/fixtures/05-profiles/03-listing.json new file mode 100644 index 000000000..4c62a5875 --- /dev/null +++ b/services/madoc-ts/fixtures/05-profiles/03-listing.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "aaea4581-96a2-4e7f-b66c-004a7ba882d6", + "type": "choice", + "label": "profiles - listing", + "items": [] + }, + "document": { + "id": "1800bc5d-60f3-4064-8d9d-131e379981f5", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "5cc7a821-5fbe-4d34-acdf-0c3a1f0a2d65" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/05-profiles/04-transcription.json b/services/madoc-ts/fixtures/05-profiles/04-transcription.json new file mode 100644 index 000000000..3312c6da3 --- /dev/null +++ b/services/madoc-ts/fixtures/05-profiles/04-transcription.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "d89acd33-c56d-4dfd-ba90-fc0b1cb31c12", + "type": "choice", + "label": "profiles - transcription", + "items": [] + }, + "document": { + "id": "28b43424-5efe-40d5-9c9d-771770db4319", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "87e91625-80ae-4808-a914-223ffab54179" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/05-profiles/05-tagging.json b/services/madoc-ts/fixtures/05-profiles/05-tagging.json new file mode 100644 index 000000000..07328054b --- /dev/null +++ b/services/madoc-ts/fixtures/05-profiles/05-tagging.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "7223ccb9-27f2-44cf-843c-9a21b333764e", + "type": "choice", + "label": "profiles - tagging", + "items": [] + }, + "document": { + "id": "08c07ec0-d553-4e8c-a4f8-ace08d6c0298", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "5bf7b5f8-980d-44de-bc41-8594abf22647" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/06-invalid/01-empty-structure.json b/services/madoc-ts/fixtures/06-invalid/01-empty-structure.json new file mode 100644 index 000000000..d4234bd2c --- /dev/null +++ b/services/madoc-ts/fixtures/06-invalid/01-empty-structure.json @@ -0,0 +1,241 @@ +{ + "structure": { + "id": "cdee3083-5f74-43c1-9d80-d97349f5fa69", + "type": "choice", + "label": "invalid - empty structure", + "description": "Contains single choice with single text field", + "items": [ + { + "id": "f0c7f99c-9e4b-4211-8dc5-f94c1287690b", + "type": "model", + "label": "Completely valid", + "fields": [ + "label" + ] + }, + { + "id": "c9db4483-9421-4370-abab-2c24ee656de2", + "type": "model", + "label": "Partially valid", + "fields": [ + "label", + "description" + ] + }, + { + "id": "d4e96195-e6ea-47f7-8bd1-69fa366be166", + "type": "model", + "label": "Completely invalid", + "fields": [ + "description" + ] + }, + { + "id": "983b5776-4a2a-4107-a50d-eab94f0d2c0e", + "label": "Nested choice - partially valid", + "type": "choice", + "items": [ + { + "label": "Nested - partially valid", + "id": "c40a1d4b-6bcc-4dd0-89f3-f8146a0837d0", + "type": "model", + "fields": [ + "label", + "description" + ] + }, + { + "label": "Nested - completely valid", + "id": "6961fa68-0886-4bbe-b680-9f6004a2fe12", + "type": "model", + "fields": [ + "label" + ] + }, + { + "label": "Nested - completely invalid", + "id": "b229fe32-684b-420b-8287-d26fa3569cd2", + "type": "model", + "fields": [ + "description" + ] + } + ], + "description": "This is a nested choice" + }, + { + "id": "8dee1d18-6570-4e75-a18d-73acb40a11ad", + "label": "Completely invalid choice", + "type": "choice", + "items": [ + { + "label": "Nested - completely invalid", + "id": "c68d65d1-a493-4c25-acb5-2588440ee953", + "type": "model", + "fields": [ + "description" + ] + }, + { + "label": "Nested - completely invalid", + "id": "e27e35b8-9882-443a-8453-2e418d77f613", + "type": "model", + "fields": [ + "description" + ] + } + ], + "description": "This is a nested choice" + }, + { + "id": "3cb849fe-e2fe-4d0d-87ab-dff1519c6c3c", + "label": "Valid Person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "bd34d28a-0299-4407-af0a-113f027bd3d0", + "label": "Partially valid person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "NOT_EXIST" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "1d6c1829-58c7-4c23-8f99-813edad87517", + "label": "Invalid person", + "type": "model", + "fields": [ + [ + "person", + [ + "NOT_EXIST", + "ALSO_NOT_EXIST" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "0c1675fc-96d0-42f3-bfb9-f9f8fa56ffa0", + "label": "Partially valid choice", + "type": "choice", + "items": [ + { + "id": "3404e64c-dae4-470a-a1b0-3f1d453c80c6", + "label": "Valid Person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "lastName" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "77a0b28d-d072-4862-a0d1-cb23d17f9fe5", + "label": "Partially valid person", + "type": "model", + "fields": [ + [ + "person", + [ + "firstName", + "NOT_EXIST" + ] + ] + ], + "description": "The second choice in the list" + }, + { + "id": "ff7acdea-c7ed-4158-bcf8-81e1559ae004", + "label": "Invalid person", + "type": "model", + "fields": [ + [ + "person", + [ + "NOT_EXIST", + "ALSO_NOT_EXIST" + ] + ] + ], + "description": "The second choice in the list" + } + ], + "description": "This is a nested choice" + } + ] + }, + "target": [ + { + "type": "manifest", + "id": "https://view.nls.uk/manifest/7446/74464117/manifest.json" + }, + { + "type": "canvas", + "id": "https://view.nls.uk/iiif/7446/74464117/canvas/3" + } + ], + "document": { + "id": "e03c5f63-d7d4-4f87-b3a5-16f98c8737e1", + "type": "entity", + "label": "Label", + "labelledBy": "label", + "properties": { + "label": [ + { + "id": "11590355-556c-4c4c-a9dd-7f0656d028c6", + "type": "text-field", + "label": "Name", + "value": "Forth Road Bridge" + } + ], + "person": [ + { + "id": "1ad53177-c2c6-45c0-a202-f485db1e57df", + "type": "entity", + "label": "Person", + "properties": { + "firstName": [ + { + "id": "4e79f032-e8da-4619-91e1-0e9716d7c2d8", + "type": "text-field", + "label": "First name" + } + ], + "lastName": [ + { + "id": "7fcbe69a-c9b2-4519-a9bb-cf5b05c6a87a", + "type": "text-field", + "label": "Last name" + } + ] + }, + "description": "Describe a person" + } + ] + } + }, + "id": "22949fda-79d6-4a31-a693-a7b66fdfaa94" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/97-bugs/01-approve-revision.json b/services/madoc-ts/fixtures/97-bugs/01-approve-revision.json new file mode 100644 index 000000000..aa019debf --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/01-approve-revision.json @@ -0,0 +1,89 @@ +{ + "id": "5b49767b-f79b-4039-b933-9c917498dc42", + "structure": { + "id": "16e4789c-6866-4271-bdfd-ee5220e9ffeb", + "type": "choice", + "label": "bugs - approve revision", + "items": [ + { + "id": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "type": "model", + "label": "Default", + "fields": [ + "regionOfInterest" + ] + } + ] + }, + "document": { + "id": "4f6d0447-b3fa-4ed9-b137-7f6e68f4849c", + "type": "entity", + "label": "Untitled document", + "properties": { + "regionOfInterest": [ + { + "id": "48304e90-4257-4cbd-b3ef-4c8fcfb45b96", + "type": "text-field", + "label": "regionOfInterest", + "value": "", + "selector": { + "id": "6353eac6-518d-4a28-a598-b251f754db9c", + "type": "box-selector", + "state": null + } + }, + { + "id": "3930eefd-50bf-40f1-9ed6-f7c59aea693c", + "type": "text-field", + "label": "regionOfInterest", + "value": "tests", + "revises": "48304e90-4257-4cbd-b3ef-4c8fcfb45b96", + "selector": { + "id": "01a9a2fd-bb23-4cfa-b3b4-7be643c66a15", + "type": "box-selector", + "state": null + }, + "revision": "7b0052c9-ff2c-4463-b198-c6bcc6d18606" + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:collection:1", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:3", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:46", + "type": "Canvas" + } + ], + "profile": null, + "revisions": [ + { + "structureId": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "approved": false, + "label": "Default", + "id": "7b0052c9-ff2c-4463-b198-c6bcc6d18606", + "fields": [ + "regionOfInterest" + ], + "status": "submitted", + "revises": null, + "authors": [ + "ca7c97bb-78ab-44ec-82be-e559eaf6c7df" + ] + } + ], + "contributors": { + "ca7c97bb-78ab-44ec-82be-e559eaf6c7df": { + "id": "ca7c97bb-78ab-44ec-82be-e559eaf6c7df", + "type": "Person", + "name": "admin" + } + } +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-bad-revision.json b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-bad-revision.json new file mode 100644 index 000000000..b57688e21 --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-bad-revision.json @@ -0,0 +1,56 @@ +{ + "captureModelId": "d3ab792c-5bad-4634-aeb5-7beaa3266e9d", + "revision": { + "structureId": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "approved": false, + "label": "Default", + "id": "c0d80087-7ded-40a2-bc8d-ea014fd96b7c", + "fields": [ + "regionOfInterest" + ], + "status": "draft", + "revises": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "authors": [ + "urn:madoc:user:1" + ], + "deletedFields": null + }, + "document": { + "id": "9342aea3-cf76-4787-b779-3445e9dece79", + "type": "entity", + "label": "Untitled document", + "properties": { + "regionOfInterest": [ + { + "id": "09b103ae-2701-4eb0-99c9-acef6314c9ff", + "type": "text-field", + "label": "regionOfInterest test test", + "value": "test", + "revises": "1490ec4f-b36f-44bc-8ae0-3cf10bf1b7e3", + "description": "test", + "selector": { + "id": "59b4e34b-cfaa-4a29-9bd3-b476992b9846", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "b9d01f98-b16f-4d10-bd31-c945232a68c3", + "type": "box-selector", + "state": { + "x": 1716, + "y": 304, + "width": 476, + "height": 252 + }, + "revisionId": "c0d80087-7ded-40a2-bc8d-ea014fd96b7c", + "revises": "59b4e34b-cfaa-4a29-9bd3-b476992b9846" + } + ] + }, + "revision": "c0d80087-7ded-40a2-bc8d-ea014fd96b7c" + } + ] + } + }, + "source": "structure" +} diff --git a/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-revision.json b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-revision.json new file mode 100644 index 000000000..565711ead --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking-revision.json @@ -0,0 +1,55 @@ +{ + "captureModelId": "5b3b131d-51fc-46cb-9c05-aaa884144d82", + "source": "structure", + "document": { + "id": "988664d3-1d51-44b4-9a77-5956a800cdfa", + "type": "entity", + "label": "Untitled document", + "properties": { + "regionOfInterest": [ + { + "id": "cd6534b2-0236-4ac2-83d7-1d1743bb06d6", + "type": "text-field", + "label": "regionOfInterest test test", + "value": "", + "description": "test", + "selector": { + "id": "1affad96-4823-479e-a49f-b35d476cfc25", + "type": "box-selector", + "state": null + } + }, + { + "id": "d04c340b-1845-4432-90ce-20ed1a1273ad", + "type": "text-field", + "label": "regionOfInterest test test", + "value": "test region", + "revises": "cd6534b2-0236-4ac2-83d7-1d1743bb06d6", + "description": "test", + "selector": { + "id": "ecd25628-84ed-4b3b-8a12-57aaa33fbc45", + "type": "box-selector", + "state": null + }, + "revision": "e2beba84-bdd1-4ae1-bcb0-cd01ba95a450" + } + ] + }, + "immutable": true + }, + "revision": { + "structureId": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "approved": false, + "label": "Default", + "id": "e2beba84-bdd1-4ae1-bcb0-cd01ba95a450", + "fields": [ + "regionOfInterest" + ], + "status": "submitted", + "revises": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "authors": [ + "urn:madoc:user:1" + ], + "deletedFields": null + } +} diff --git a/services/madoc-ts/fixtures/97-bugs/02-selector-nuking.json b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking.json new file mode 100644 index 000000000..d9ace0129 --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/02-selector-nuking.json @@ -0,0 +1,65 @@ +{ + "id": "5b3b131d-51fc-46cb-9c05-aaa884144d82", + "structure": { + "id": "16e4789c-6866-4271-bdfd-ee5220e9ffeb", + "type": "choice", + "description": "test", + "label": "First project", + "items": [ + { + "id": "9c2c6558-703d-4276-ac44-01c78e66ecef", + "type": "model", + "description": "test test", + "label": "Default", + "fields": [ + "regionOfInterest" + ], + "instructions": "test test test test" + } + ] + }, + "document": { + "id": "988664d3-1d51-44b4-9a77-5956a800cdfa", + "type": "entity", + "label": "Untitled document", + "properties": { + "regionOfInterest": [ + { + "id": "cd6534b2-0236-4ac2-83d7-1d1743bb06d6", + "type": "text-field", + "label": "regionOfInterest test test", + "value": "", + "description": "test", + "selector": { + "id": "1affad96-4823-479e-a49f-b35d476cfc25", + "type": "box-selector", + "state": null + } + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:collection:1", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:3", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:55", + "type": "Canvas" + } + ], + "profile": null, + "derivedFrom": "f5302b73-bbcd-4453-8c1a-b3750eec6878", + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "admin" + } + } +} diff --git a/services/madoc-ts/fixtures/97-bugs/03-delete-entity-req.json b/services/madoc-ts/fixtures/97-bugs/03-delete-entity-req.json new file mode 100644 index 000000000..35b8e70ce --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/03-delete-entity-req.json @@ -0,0 +1,142 @@ +{ + "id": "a57d168a-77f8-4125-b317-11abecd460b2", + "structure": { + "id": "259797ed-bc96-480e-a6ed-4a81280790e6", + "type": "choice", + "label": "Multiple entities and fields", + "items": [ + { + "id": "7d172208-7d3e-4e73-9874-a85fd24b33d6", + "type": "model", + "label": "Default", + "fields": [ + ["entity-multiple-selector", ["test"]], + ["entity-multiple", ["test"]], + "field-multiple", + "field-multiple-selector" + ] + } + ] + }, + "document": { + "id": "97f13c42-045f-4b83-875b-1bda76cee77f", + "type": "entity", + "label": "Multiple entities and fields", + "properties": { + "entity-multiple-selector": [ + { + "id": "42165553-16ff-4910-bf6f-2bd924ed94ad", + "type": "entity", + "label": "entity-multiple-selector", + "allowMultiple": true, + "properties": { + "test": [ + { "id": "f7c3b5b8-52f8-466f-927e-6e46564fd231", "type": "text-field", "label": "test", "value": "" } + ] + }, + "selector": { + "id": "27893b54-e46a-4dab-a5ac-93d0c422deaf", + "type": "box-selector", + "state": { "x": 1465, "y": 216, "width": 235, "height": 151 } + } + }, + { + "id": "c91bd07e-4d5c-4708-9e6c-3a4145afc02a", + "type": "entity", + "label": "entity-multiple-selector", + "allowMultiple": true, + "properties": { + "test": [ + { "id": "f3474412-7eae-4166-b0c8-629a7810ecbb", "type": "text-field", "label": "test", "value": "" } + ] + }, + "selector": { + "id": "ac2b7586-b308-4968-9218-39419418e8ca", + "type": "box-selector", + "state": { "x": 1797, "y": 448, "width": 190, "height": 151 } + } + } + ], + "entity-multiple": [ + { + "id": "0fddf46f-26f4-498e-9454-8ac7f27736e4", + "type": "entity", + "label": "entity-multiple", + "allowMultiple": true, + "properties": { + "test": [ + { "id": "1520fd2c-90a0-47ab-816f-9a967f32aa1d", "type": "text-field", "label": "test", "value": "" } + ] + } + }, + { + "id": "b019e80d-ee0d-4aa6-98e9-d1874a81ecd9", + "type": "entity", + "label": "entity-multiple", + "allowMultiple": true, + "properties": { + "test": [ + { "id": "5af75e4e-2d3e-4266-a3e0-99bc1ac634b1", "type": "text-field", "label": "test", "value": "" } + ] + } + } + ], + "field-multiple": [ + { + "id": "7b3cfc39-60c4-4162-b9cb-db261f9f5a92", + "type": "text-field", + "label": "field-multiple", + "value": "1", + "allowMultiple": true + }, + { + "id": "63ebf562-ce9c-4f98-b7df-1addbce0c83f", + "type": "text-field", + "label": "field-multiple", + "value": "2", + "allowMultiple": true + } + ], + "field-multiple-selector": [ + { + "id": "c99ea6d2-36d4-4d44-9135-b78a65b63993", + "type": "text-field", + "label": "field-multiple-selector", + "value": "1", + "allowMultiple": true, + "selector": { + "id": "ed37a3c7-b950-445b-92f5-96f8b048d468", + "type": "box-selector", + "state": { "x": 1403, "y": 881, "width": 287, "height": 134 } + } + }, + { + "id": "044c6407-2c61-4d17-9c7f-616670a8de46", + "type": "text-field", + "label": "field-multiple-selector", + "value": "2", + "allowMultiple": true, + "selector": { + "id": "4505db9b-25d0-41fd-a52e-14b5e50b7190", + "type": "box-selector", + "state": { "x": 1938, "y": 896, "width": 180, "height": 154 } + } + }, + { + "id": "11beb6d9-d7f3-4516-8e44-917a61be72c1", + "type": "text-field", + "label": "field-multiple-selector", + "value": "3", + "allowMultiple": true, + "selector": { "id": "0650ae1a-be98-40df-8616-70726d686c9f", "type": "box-selector", "state": null } + } + ] + } + }, + "target": [ + { "id": "urn:madoc:manifest:3", "type": "Manifest" }, + { "id": "urn:madoc:canvas:44", "type": "Canvas" } + ], + "profile": null, + "contributors": { "urn:madoc:user:1": { "id": "urn:madoc:user:1", "type": "Person", "name": "Madoc TS" } } +} diff --git a/services/madoc-ts/fixtures/97-bugs/03-delete-entity.json b/services/madoc-ts/fixtures/97-bugs/03-delete-entity.json new file mode 100644 index 000000000..7b99f30eb --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/03-delete-entity.json @@ -0,0 +1,259 @@ +{ + "id": "a57d168a-77f8-4125-b317-11abecd460b2", + "structure": { + "id": "259797ed-bc96-480e-a6ed-4a81280790e6", + "type": "choice", + "label": "Multiple entities and fields", + "items": [ + { + "id": "7d172208-7d3e-4e73-9874-a85fd24b33d6", + "type": "model", + "label": "Default", + "fields": [ + ["entity-multiple-selector", ["test"]], + ["entity-multiple", ["test"]], + "field-multiple", + "field-multiple-selector" + ] + } + ] + }, + "document": { + "id": "97f13c42-045f-4b83-875b-1bda76cee77f", + "type": "entity", + "label": "Multiple entities and fields", + "properties": { + "entity-multiple-selector": [ + { + "id": "8c94c5ec-79c2-4799-93d3-ee2c71e1ea1a", + "type": "entity", + "label": "entity-multiple-selector", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "a5f1b235-83e5-4490-8e4d-59003b4ec8d7", + "type": "text-field", + "label": "test", + "value": "DELETE THIS ONE" + } + ] + }, + "selector": { + "id": "8862eaa7-025b-4559-8737-37efdf5a454d", + "type": "box-selector", + "state": { + "x": 2148, + "y": 139, + "width": 265, + "height": 200 + } + } + }, + { + "id": "42165553-16ff-4910-bf6f-2bd924ed94ad", + "type": "entity", + "label": "entity-multiple-selector", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "f7c3b5b8-52f8-466f-927e-6e46564fd231", + "type": "text-field", + "label": "test", + "value": "" + } + ] + }, + "selector": { + "id": "27893b54-e46a-4dab-a5ac-93d0c422deaf", + "type": "box-selector", + "state": { + "x": 1465, + "y": 216, + "width": 235, + "height": 151 + } + } + }, + { + "id": "c91bd07e-4d5c-4708-9e6c-3a4145afc02a", + "type": "entity", + "label": "entity-multiple-selector", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "f3474412-7eae-4166-b0c8-629a7810ecbb", + "type": "text-field", + "label": "test", + "value": "" + } + ] + }, + "selector": { + "id": "ac2b7586-b308-4968-9218-39419418e8ca", + "type": "box-selector", + "state": { + "x": 1797, + "y": 448, + "width": 190, + "height": 151 + } + } + } + ], + "entity-multiple": [ + { + "id": "0fddf46f-26f4-498e-9454-8ac7f27736e4", + "type": "entity", + "label": "entity-multiple", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "1520fd2c-90a0-47ab-816f-9a967f32aa1d", + "type": "text-field", + "label": "test", + "value": "" + } + ] + } + }, + { + "id": "b019e80d-ee0d-4aa6-98e9-d1874a81ecd9", + "type": "entity", + "label": "entity-multiple", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "5af75e4e-2d3e-4266-a3e0-99bc1ac634b1", + "type": "text-field", + "label": "test", + "value": "" + } + ] + } + }, + { + "id": "69d503de-57a6-49c8-8b88-ca4fc5710bb9", + "type": "entity", + "label": "entity-multiple", + "allowMultiple": true, + "properties": { + "test": [ + { + "id": "afef840c-aaeb-471f-83eb-1e19fdb599e9", + "type": "text-field", + "label": "test", + "value": "DELETE THIS" + } + ] + } + } + ], + "field-multiple": [ + { + "id": "7b3cfc39-60c4-4162-b9cb-db261f9f5a92", + "type": "text-field", + "label": "field-multiple", + "value": "1", + "allowMultiple": true + }, + { + "id": "63ebf562-ce9c-4f98-b7df-1addbce0c83f", + "type": "text-field", + "label": "field-multiple", + "value": "2", + "allowMultiple": true + }, + { + "id": "b5228da9-d767-4e3a-9db8-36ccdbc04660", + "type": "text-field", + "label": "field-multiple", + "value": "DELETE THIS", + "allowMultiple": true + } + ], + "field-multiple-selector": [ + { + "id": "c99ea6d2-36d4-4d44-9135-b78a65b63993", + "type": "text-field", + "label": "field-multiple-selector", + "value": "1", + "allowMultiple": true, + "selector": { + "id": "ed37a3c7-b950-445b-92f5-96f8b048d468", + "type": "box-selector", + "state": { + "x": 1403, + "y": 881, + "width": 287, + "height": 134 + } + } + }, + { + "id": "044c6407-2c61-4d17-9c7f-616670a8de46", + "type": "text-field", + "label": "field-multiple-selector", + "value": "2", + "allowMultiple": true, + "selector": { + "id": "4505db9b-25d0-41fd-a52e-14b5e50b7190", + "type": "box-selector", + "state": { + "x": 1938, + "y": 896, + "width": 180, + "height": 154 + } + } + }, + { + "id": "11beb6d9-d7f3-4516-8e44-917a61be72c1", + "type": "text-field", + "label": "field-multiple-selector", + "value": "3", + "allowMultiple": true, + "selector": { + "id": "0650ae1a-be98-40df-8616-70726d686c9f", + "type": "box-selector", + "state": null + } + }, + { + "id": "d81f12dc-5664-4638-8ce9-7f7a447aa882", + "type": "text-field", + "label": "field-multiple-selector", + "value": "DELETE THIS", + "allowMultiple": true, + "selector": { + "id": "b9310295-7dc6-41cb-af8e-810854072150", + "type": "box-selector", + "state": null + } + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:manifest:3", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:44", + "type": "Canvas" + } + ], + "profile": null, + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Madoc TS" + } + } +} diff --git a/services/madoc-ts/fixtures/97-bugs/04-missing-selector.json b/services/madoc-ts/fixtures/97-bugs/04-missing-selector.json new file mode 100644 index 000000000..1a23f7347 --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/04-missing-selector.json @@ -0,0 +1,104 @@ +{ + "id": "5a51cdec-ea69-473c-a0ec-674b3e43b51f", + "structure": { + "id": "bbf9c022-3cc5-4ced-b836-652ddf16fcd4", + "type": "choice", + "label": "Multiple entities and fields", + "items": [ + { + "id": "bbf9c022-3cc5-4ced-b836-652ddf16fcd3", + "type": "model", + "label": "Default", + "fields": [ + [ + "place", + [ + "place_name" + ] + ] + ] + } + ] + }, + "revisions": [ + { + "structureId": "bbf9c022-3cc5-4ced-b836-652ddf16fcd3", + "approved": false, + "label": "Default", + "id": "3fde8b6b-a31a-413c-a6f7-9747c8ad3268", + "fields": [ + [ + "place", + [ + "place_name" + ] + ] + ], + "status": "submitted", + "revises": null, + "authors": [] + }, + { + "structureId": "bbf9c022-3cc5-4ced-b836-652ddf16fcd3", + "approved": false, + "label": "Default", + "id": "8d95f27b-613a-40f7-8f4e-118c666a2272", + "fields": [ + [ + "place", + [ + "place_name" + ] + ] + ], + "status": "submitted", + "revises": null, + "authors": [] + } + ], + "document": { + "id": "c4b51b4d-6bad-4eab-845e-bf64085f3d03", + "type": "entity", + "label": "Test", + "properties": { + "place": [ + { + "id": "2d39427f-3c30-4013-b3b4-d0833574fe1d", + "type": "entity", + "label": "place", + "properties": { + "place_name": [ + { + "id": "41e8fd6e-9265-498a-8a9a-5f0ad2a597be", + "type": "text-field", + "label": "place_name", + "value": "" + } + ] + }, + "selector": { + "id": "c5e8c546-d529-484c-b59c-913b3259bb74", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "032b7148-8491-45c5-ac5f-fa7714d1eb44", + "type": "box-selector", + "state": { + "x": 1391, + "y": 1374, + "width": 474, + "height": 140 + }, + "revises": "c5e8c546-d529-484c-b59c-913b3259bb74", + "revisionId": "3fde8b6b-a31a-413c-a6f7-9747c8ad3268" + } + ] + } + } + ] + } + }, + "source": "structure", + "status": "accepted" +} diff --git a/services/madoc-ts/fixtures/97-bugs/04-source.json b/services/madoc-ts/fixtures/97-bugs/04-source.json new file mode 100644 index 000000000..6b5b8bb9e --- /dev/null +++ b/services/madoc-ts/fixtures/97-bugs/04-source.json @@ -0,0 +1,89 @@ +{ + "captureModelId": "5a51cdec-ea69-473c-a0ec-674b3e43b51f", + "revision": { + "structureId": "bbf9c022-3cc5-4ced-b836-652ddf16fcd3", + "approved": false, + "label": "Place", + "id": "8d95f27b-613a-40f7-8f4e-118c666a2272", + "fields": [ + [ + "place", + [ + "place_name" + ] + ] + ], + "status": "accepted", + "revises": "bbf9c022-3cc5-4ced-b836-652ddf16fcd3", + "authors": [ + "urn:madoc:user:2" + ], + "deletedFields": null, + "accepted": true + }, + "document": { + "id": "c4b51b4d-6bad-4eab-845e-bf64085f3d03", + "type": "entity", + "label": "Test", + "properties": { + "place": [ + { + "id": "2d39427f-3c30-4013-b3b4-d0833574fe1d", + "type": "entity", + "label": "place", + "properties": { + "place_name": [ + { + "id": "41e8fd6e-9265-498a-8a9a-5f0ad2a597be", + "type": "text-field", + "label": "place_name", + "value": "" + }, + { + "id": "ac0e89da-d448-4637-a771-c47e11236bc4", + "type": "text-field", + "label": "place_name", + "value": "Nowhere near infinity", + "revises": "41e8fd6e-9265-498a-8a9a-5f0ad2a597be", + "revision": "8d95f27b-613a-40f7-8f4e-118c666a2272" + } + ] + }, + "selector": { + "id": "c5e8c546-d529-484c-b59c-913b3259bb74", + "type": "box-selector", + "state": null, + "revisedBy": [ + { + "id": "032b7148-8491-45c5-ac5f-fa7714d1eb44", + "type": "box-selector", + "state": { + "x": 1391, + "y": 1374, + "width": 474, + "height": 140 + }, + "revises": "c5e8c546-d529-484c-b59c-913b3259bb74", + "revisionId": "3fde8b6b-a31a-413c-a6f7-9747c8ad3268" + }, + { + "id": "9a046095-8ee7-4353-bc4d-54e000eda9f4", + "type": "box-selector", + "state": { + "x": 1355, + "y": 1916, + "width": 1304, + "height": 304 + }, + "revises": "c5e8c546-d529-484c-b59c-913b3259bb74", + "revisionId": "8d95f27b-613a-40f7-8f4e-118c666a2272" + } + ] + } + } + ] + } + }, + "source": "structure", + "status": "accepted" +} diff --git a/services/madoc-ts/fixtures/98-revision-requests/add-ocr-line.json b/services/madoc-ts/fixtures/98-revision-requests/add-ocr-line.json new file mode 100644 index 000000000..9c350c57a --- /dev/null +++ b/services/madoc-ts/fixtures/98-revision-requests/add-ocr-line.json @@ -0,0 +1,249 @@ +{ + "id": "aa2344b4-a70b-41cf-aa62-5e211e76ee2c", + "type": "entity", + "label": "Untitled document", + "properties": { + "transcription": [ + { + "id": "b2867260-78e3-484f-8549-40a89a55d51c", + "type": "entity", + "label": "Paragraph", + "pluralLabel": "Paragraphs", + "description": "Region of the page denoting a single paragraph", + "allowMultiple": false, + "labelledBy": "lines", + "properties": { + "lines": [ + { + "id": "95b51378-ef27-480e-a380-b9673100f497", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": false, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "72c8175b-7684-4d67-8987-5b4d53623025", + "type": "text-field", + "label": "Text of line", + "value": "bereidiern", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "b4ad337b-5c11-4639-999c-6c818e5362d6", + "type": "box-selector", + "state": { + "x": 312, + "y": 2503, + "width": 262, + "height": 39 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "bc8bb1c6-9e02-4655-8b01-fcf8e23254ba", + "type": "text-field", + "label": "Text of line", + "value": "und", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "2dc4f63d-ab34-49cc-bdee-7223c57ca42e", + "type": "box-selector", + "state": { + "x": 617, + "y": 2504, + "width": 92, + "height": 38 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "597938a0-194e-4076-8351-9bf562d125a4", + "type": "text-field", + "label": "Text of line", + "value": "beglücken.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "3dd7df09-9bc9-4d95-a2f3-f44c16eeb09f", + "type": "box-selector", + "state": { + "x": 746, + "y": 2504, + "width": 265, + "height": 48 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "d5bd1fcb-bc2a-4b9c-badd-689f4334faee", + "type": "text-field", + "label": "Text of line", + "value": "Man", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "60148ad3-2069-40f1-9ff6-9613ea7fb405", + "type": "box-selector", + "state": { + "x": 1065, + "y": 2504, + "width": 111, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "9984408b-d29a-4338-98a0-10e667726afc", + "type": "text-field", + "label": "Text of line", + "value": "lernt,", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "727aab1f-6640-4207-9e9f-b32eae018362", + "type": "box-selector", + "state": { + "x": 1216, + "y": 2504, + "width": 126, + "height": 43 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "272dbf0c-1e20-4294-a2d0-c35a7f4b1fb2", + "type": "text-field", + "label": "Text of line", + "value": "indem", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "7a9dcb1a-6796-4926-84f9-4cd4b93d455c", + "type": "box-selector", + "state": { + "x": 1394, + "y": 2503, + "width": 153, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "0997c9ea-bb34-4ae9-a063-2cafd8a4bd7f", + "type": "text-field", + "label": "Text of line", + "value": "man", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "2a069825-b397-404f-8aa6-f35b0b6ca3f4", + "type": "box-selector", + "state": { + "x": 1595, + "y": 2512, + "width": 105, + "height": 27 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "23859747-7bb2-403d-83f7-e36df634024a", + "type": "text-field", + "label": "Text of line", + "value": "aufs", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "7daa348c-45ad-4f6e-b900-042eb3910046", + "type": "box-selector", + "state": { + "x": 1739, + "y": 2499, + "width": 99, + "height": 40 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + }, + { + "id": "aa8389e8-e639-423d-8f1c-ffbe498ae25e", + "type": "text-field", + "label": "Text of line", + "value": "beste", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "61484374-e52c-4717-b5ca-e752fc1feab0", + "type": "box-selector", + "state": { + "x": 1876, + "y": 2500, + "width": 131, + "height": 37 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + } + ] + }, + "selector": { + "id": "793fa89c-82ee-4f6f-b105-28ec8b9760b9", + "type": "box-selector", + "state": { + "x": 312, + "y": 2499, + "width": 1695, + "height": 53 + } + }, + "revises": "a9d8cae8-db25-4880-bc33-e4c0ff192939", + "immutable": false, + "revision": "98751c14-a342-4958-9803-5e0bd5d11b6a" + } + ] + }, + "selector": { + "id": "750603ef-bf14-464a-b812-6540f53497dd", + "type": "box-selector", + "state": { + "x": 296, + "y": 310, + "width": 1730, + "height": 2582 + } + }, + "immutable": true + } + ] + }, + "immutable": true +} diff --git a/services/madoc-ts/fixtures/98-revision-requests/add-ocr-text.json b/services/madoc-ts/fixtures/98-revision-requests/add-ocr-text.json new file mode 100644 index 000000000..1efab4160 --- /dev/null +++ b/services/madoc-ts/fixtures/98-revision-requests/add-ocr-text.json @@ -0,0 +1,118 @@ +{ + "captureModelId": "ece903d1-9e17-41b0-af83-5f417df2a22c", + "source": "canonical", + "document": { + "id": "aa2344b4-a70b-41cf-aa62-5e211e76ee2c", + "type": "entity", + "label": "Untitled document", + "properties": { + "transcription": [ + { + "id": "b2867260-78e3-484f-8549-40a89a55d51c", + "type": "entity", + "label": "Paragraph", + "pluralLabel": "Paragraphs", + "description": "Region of the page denoting a single paragraph", + "allowMultiple": false, + "labelledBy": "lines", + "properties": { + "lines": [ + { + "id": "a9d8cae8-db25-4880-bc33-e4c0ff192939", + "type": "entity", + "label": "Line", + "pluralLabel": "Lines", + "description": "All of the lines inside of a paragraph", + "allowMultiple": false, + "labelledBy": "text", + "properties": { + "text": [ + { + "id": "bd56ecd1-d010-4899-896c-748deddeda0d", + "type": "text-field", + "label": "Text of line", + "value": "beglücken.", + "description": "Single word, phrase or the whole line", + "allowMultiple": true, + "selector": { + "id": "e255f928-6445-4f58-93c6-1b4a7fd7019d", + "type": "box-selector", + "state": { + "x": 746, + "y": 2504, + "width": 265, + "height": 48 + } + }, + "pluralField": "Text of lines", + "previewInline": true, + "revision": "95e40449-7e93-4441-a916-ec3915fcd217", + "revises": "a29eb861-a893-4f2d-a3d2-e420ea405e4a" + } + ] + }, + "selector": { + "id": "98751c14-a342-4958-9803-5e0bd5d11b6a", + "type": "box-selector", + "state": { + "x": 312, + "y": 2499, + "width": 1695, + "height": 53 + } + }, + "immutable": true + } + ] + }, + "selector": { + "id": "750603ef-bf14-464a-b812-6540f53497dd", + "type": "box-selector", + "state": { + "x": 296, + "y": 310, + "width": 1730, + "height": 2582 + } + }, + "immutable": true + } + ] + }, + "immutable": true + }, + "revision": { + "id": "95e40449-7e93-4441-a916-ec3915fcd217", + "fields": [ + [ + "transcription", + [ + [ + "lines", + [ + "text" + ] + ] + ] + ] + ] + }, + "author": { + "id": "urn:madoc:user:1", + "name": "Admin" + }, + "target": [ + { + "id": "urn:madoc:collection:21206", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:15267", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:15302", + "type": "Canvas" + } + ] +} diff --git a/services/madoc-ts/fixtures/99-unrealistic/01-kitchen-sink.json b/services/madoc-ts/fixtures/99-unrealistic/01-kitchen-sink.json new file mode 100644 index 000000000..815a89681 --- /dev/null +++ b/services/madoc-ts/fixtures/99-unrealistic/01-kitchen-sink.json @@ -0,0 +1,16 @@ +{ + "structure": { + "id": "f48bf990-c8ef-4b03-8c91-262cd5ecaa1a", + "type": "choice", + "label": "unrealistic - kitchen sink", + "items": [] + }, + "document": { + "id": "00737ca3-ddcd-4a4f-846a-16c9e21ae2d8", + "type": "entity", + "label": "TODO", + "labelledBy": "label", + "properties": {} + }, + "id": "da06bdab-7cc2-426b-bcb5-7f3bb52e5d1a" +} \ No newline at end of file diff --git a/services/madoc-ts/fixtures/99-unrealistic/02-ames.json b/services/madoc-ts/fixtures/99-unrealistic/02-ames.json new file mode 100644 index 000000000..eb3ba9408 --- /dev/null +++ b/services/madoc-ts/fixtures/99-unrealistic/02-ames.json @@ -0,0 +1,767 @@ +{ + "id": "5aff4557-b331-4181-9030-1097f5220b67", + "structure": { + "id": "abf9e833-09a6-4f2a-a30e-c42da2527ae1", + "type": "choice", + "description": "This structure provides the fields that the annotator will use to capture information related to a specific court proceeding, a case, a swearing in of attorneys, a naturalization ceremony, etc.", + "profile": [], + "label": "Court Proceeding Annotation", + "items": [ + { + "id": "dfcf67af-5026-4a71-880a-861d399132c5", + "type": "choice", + "label": "Court Proceeding", + "items": [] + }, + { + "id": "4f8d8d46-1e27-4d77-a33e-56a37992487d", + "type": "model", + "description": "Unique identifier of a court proceeding in the format of XXYYY-ZZZZZZZZZ", + "label": "Court Proceeding ID", + "fields": [ + [ + "Nation", + [ + [ + "Early America", + [ + [ + "Massachusetts", + [ + [ + "CourtProceeding", + [ + "ProceedingID", + "ColonyName", + "TribunalName", + "VolumeName", + "VolumePage", + "CourtProceedingBeginMonth", + "CourtTermBeginYear", + "CourtTermCity", + "CaseName", + "VariantCaseName", + "CourtProceedingType", + "OtherCourtProceedingType" + ] + ] + ] + ] + ] + ] + ] + ] + ], + "instructions": "XX=colony abbreviation\nYYY=court abbreviation (may be more than 3 characters)\nZZZZZZZZZ=a unique sequential number beginning with 000000001" + } + ] + }, + "document": { + "id": "a539ffda-aaec-4885-bc6c-a01cebeed1b4", + "type": "entity", + "label": "Early Court Records", + "description": "Entities and fields that can be used to annotate court proceeding documents", + "properties": { + "Nation": [ + { + "id": "2ea4f20c-86ff-4da6-8b81-8e38abd532e3", + "type": "entity", + "label": "Nation", + "properties": { + "Early America": [ + { + "id": "172d059a-ffe9-4568-a35f-b5de60538a59", + "type": "entity", + "label": "Early America", + "properties": { + "Massachusetts": [ + { + "id": "9cf9ca8e-2e2f-4f25-9c91-4cf714067d03", + "type": "entity", + "label": "Massachusetts", + "properties": { + "Court Term": [ + { + "id": "7ae00473-e513-4033-a8c2-d7e5e845a704", + "type": "entity", + "label": "Court Term", + "properties": { + "TermEndYear": [ + { + "id": "98172f8e-9d06-4734-9568-68b14d3e37aa", + "type": "text-field", + "label": "Term End Year", + "value": "", + "description": "The year in which a court proceeding was concluded." + } + ], + "TermNote": [ + { + "id": "3ff15fcb-dbaa-4654-9b05-63a6cd11d6be", + "type": "text-field", + "label": "Term Note", + "value": "", + "description": "Additional information regarding the court term, such as reason for delay, missing personnel, change of venue, etc." + } + ], + "City": [ + { + "id": "553aea5d-44b7-4242-9687-73c9e51a37ce", + "type": "text-field", + "label": "City", + "value": "", + "description": "The name of a city or community in which court business may be conducted." + } + ], + "County": [ + { + "id": "7db9ae75-2917-48c6-9e15-3b4f9a94b0d9", + "type": "text-field", + "label": "County", + "value": "", + "description": "The county in which court business may be conducted." + } + ], + "TermBeginDay": [ + { + "id": "970547e0-9a71-4993-a4a5-c04d79f03d01", + "type": "dropdown-field", + "label": "Term Begin Day", + "value": null, + "description": "The day of the month on which a court proceeding was started.", + "options": [ + { + "text": "1", + "value": "1" + }, + { + "text": "2", + "value": "2" + }, + { + "text": "3", + "value": "3" + }, + { + "text": "4", + "value": "4" + }, + { + "text": "5", + "value": "5" + }, + { + "text": "6", + "value": "6" + }, + { + "text": "7", + "value": "7" + }, + { + "text": "8", + "value": "8" + }, + { + "text": "9", + "value": "9" + }, + { + "text": "10", + "value": "10" + }, + { + "text": "11", + "value": "11" + }, + { + "text": "12", + "value": "12" + }, + { + "text": "13", + "value": "13" + }, + { + "text": "14", + "value": "14" + }, + { + "text": "15", + "value": "15" + }, + { + "text": "16", + "value": "16" + }, + { + "text": "17", + "value": "17" + }, + { + "text": "18", + "value": "18" + }, + { + "text": "19", + "value": "19" + }, + { + "text": "20", + "value": "20" + }, + { + "text": "21", + "value": "21" + }, + { + "text": "22", + "value": "22" + }, + { + "text": "23", + "value": "23" + }, + { + "text": "24", + "value": "24" + }, + { + "text": "25", + "value": "25" + }, + { + "text": "26", + "value": "26" + }, + { + "text": "27", + "value": "27" + }, + { + "text": "28", + "value": "28" + }, + { + "text": "29", + "value": "29" + }, + { + "text": "30", + "value": "30" + }, + { + "text": "31", + "value": "31" + } + ], + "clearable": true + } + ], + "TermBeginMonth": [ + { + "id": "2596870e-f6b1-4fed-a51e-c697286e3da1", + "type": "dropdown-field", + "label": "Term Begin Month", + "value": null, + "description": "The month of the year in which a court proceeding was started.", + "options": [ + { + "text": "January", + "value": "January" + }, + { + "text": "February", + "value": "February" + }, + { + "text": "March", + "value": "March" + }, + { + "text": "April", + "value": "April" + }, + { + "text": "May", + "value": "May" + }, + { + "text": "June", + "value": "June" + }, + { + "text": "July", + "value": "July" + }, + { + "text": "August", + "value": "August" + }, + { + "text": "September", + "value": "September" + }, + { + "text": "October", + "value": "October" + }, + { + "text": "November", + "value": "November" + }, + { + "text": "December", + "value": "December" + } + ], + "clearable": true + } + ], + "TermBeginYear": [ + { + "id": "3d135e1f-91c5-4f9e-b77f-12409e2e6722", + "type": "text-field", + "label": "Term Begin Year", + "value": "", + "description": "The year in which a court proceeding was started." + } + ], + "TermEndDay": [ + { + "id": "9a090c08-7c01-48f1-b511-ec8daf0b2985", + "type": "dropdown-field", + "label": "Term End Day", + "value": null, + "description": "The day of the month on which a court proceeding was concluded.", + "options": [ + { + "text": "1", + "value": "1" + }, + { + "text": "2", + "value": "2" + }, + { + "text": "3", + "value": "3" + }, + { + "text": "4", + "value": "4" + }, + { + "text": "5", + "value": "5" + }, + { + "text": "6", + "value": "6" + }, + { + "text": "7", + "value": "7" + }, + { + "text": "8", + "value": "8" + }, + { + "text": "9", + "value": "9" + }, + { + "text": "10", + "value": "10" + }, + { + "text": "11", + "value": "11" + }, + { + "text": "12", + "value": "12" + }, + { + "text": "13", + "value": "13" + }, + { + "text": "14", + "value": "14" + }, + { + "text": "15", + "value": "15" + }, + { + "text": "16", + "value": "16" + }, + { + "text": "17", + "value": "17" + }, + { + "text": "18", + "value": "18" + }, + { + "text": "19", + "value": "19" + }, + { + "text": "20", + "value": "20" + }, + { + "text": "21", + "value": "21" + }, + { + "text": "22", + "value": "22" + }, + { + "text": "23", + "value": "23" + }, + { + "text": "24", + "value": "24" + }, + { + "text": "25", + "value": "25" + }, + { + "text": "26", + "value": "26" + }, + { + "text": "27", + "value": "27" + }, + { + "text": "28", + "value": "28" + }, + { + "text": "29", + "value": "29" + }, + { + "text": "30", + "value": "30" + }, + { + "text": "31", + "value": "31" + } + ], + "clearable": true + } + ], + "TermEndMonth": [ + { + "id": "2df8094a-632b-482a-a864-ea1d22b32022", + "type": "dropdown-field", + "label": "Term End Month", + "value": null, + "description": "The month of the year in which a court proceeding was concluded.", + "options": [ + { + "text": "January", + "value": "January" + }, + { + "text": "February", + "value": "February" + }, + { + "text": "March", + "value": "March" + }, + { + "text": "April", + "value": "April" + }, + { + "text": "May", + "value": "May" + }, + { + "text": "June", + "value": "June" + }, + { + "text": "July", + "value": "July" + }, + { + "text": "August", + "value": "August" + }, + { + "text": "September", + "value": "September" + }, + { + "text": "October", + "value": "October" + }, + { + "text": "November", + "value": "November" + } + ], + "clearable": true + } + ] + } + } + ], + "CourtProceeding": [ + { + "id": "4368c4a8-ef38-4a25-95e4-3420af5ca2ee", + "type": "entity", + "label": "CourtProceeding", + "properties": { + "ColonyName": [ + { + "id": "a7680c67-b616-45f2-b925-b016c644e4a0", + "type": "text-field", + "label": "Colony Name", + "value": "", + "description": "The name of the colony in which the court proceeding took place." + } + ], + "TribunalName": [ + { + "id": "a86ac9fc-031a-4424-b86d-1d7642800122", + "type": "text-field", + "label": "Tribunal Name", + "value": "", + "description": "The name of the court in which the proceeding occurred." + } + ], + "VolumePage": [ + { + "id": "45565ffa-211a-4dba-be4e-95995be9f87d", + "type": "text-field", + "label": "Volume Page", + "value": "", + "description": "Identifies the specific page within a volume of court proceedings, i.e., 31 recto for front side of page 31 or 31 verso for backside of page 31." + } + ], + "ProceedingID": [ + { + "id": "e8bc6b2a-0853-445b-b592-1eb1df038ad3", + "type": "text-field", + "label": "Court Proceeding ID", + "value": "", + "description": "The unique identifier of the court proceeding in the format XXYYY-zzzzzzzzz, where\nXX = colony abbreviation (i.e., MA for Massachusetts), \nYYY = tribunal (i.e., SCJ for Superior Court of Judicature) (may be more or less that 3 characters),\nzzzzzzzzz = incremental number beginning with 000000001" + } + ], + "VolumeName": [ + { + "id": "27fd80ed-971e-4220-b4a4-4d75d44c36a1", + "type": "text-field", + "label": "VolumeName", + "value": "", + "description": "The name of the volume containing the record of the court proceeding." + } + ], + "OriginalTrialCourtName": [ + { + "id": "49f2aab9-1495-4fdd-b291-8735465b6953", + "type": "dropdown-field", + "label": "Original Trial Court Name", + "value": null, + "description": "The name of the lower court in which the case was originally tried.", + "options": [ + { + "text": "County - civil", + "value": "County - civil" + }, + { + "text": "County - criminal", + "value": "County - criminal" + }, + { + "text": "Other Trial Court", + "value": "Other Trial Court" + } + ], + "clearable": true + }, + { + "id": "1c778b6a-b593-45ed-9cf7-5a311c53163e", + "type": "text-field", + "label": "Original Trial Court Name", + "value": "", + "description": "The name of the court that first heard this case. Most commonly, this will be either the Inferior Court of Common Pleas (county-civil) or the Court of Gaol Delivery & General Assize (county-criminal)." + } + ], + "CourtProceedingBeginMonth": [ + { + "id": "0baa18ce-231f-4122-83e5-fe5e9478e2b2", + "type": "text-field", + "label": "CourtTermBeginMonth", + "value": "", + "description": "The month of the year in which the court began its session." + } + ], + "VariantCaseName": [ + { + "id": "5a21cf64-b799-4370-b356-fe3305b5fcb8", + "type": "text-field", + "label": "Variant Case Name", + "value": "", + "description": "An alternate name for the case, as found in the court record.", + "selector": { + "id": "887f2af0-912f-499a-ac03-46f486ff27db", + "type": "box-selector", + "state": null + } + } + ], + "CourtTermBeginYear": [ + { + "id": "6545da7e-1bad-4b9e-afbf-221d3e17320d", + "type": "text-field", + "label": "Court Term Begin Year", + "value": "", + "description": "The year in which the court began a session." + } + ], + "CourtTermCity": [ + { + "id": "80cbcf7c-c89c-400b-a971-8b3c897a3fb8", + "type": "text-field", + "label": "Court Term City", + "value": "", + "description": "The city in which the court held a session." + } + ], + "CaseName": [ + { + "id": "c19add98-ea9a-4033-8e47-d71d377ddddf", + "type": "text-field", + "label": "Case Name", + "value": "", + "description": "The name that a case would be commonly called, such as Jones v. Smith", + "selector": { + "id": "951fe3ba-b39d-4c16-93b6-ea1069bd60a5", + "type": "box-selector", + "state": null + } + } + ], + "CourtProceedingType": [ + { + "id": "d28140bf-238f-48d0-af77-02c5bc41e223", + "type": "dropdown-field", + "label": "Court Proceeding Type", + "value": null, + "description": "Indicates what kind of proceeding is reflected in the court record, what exactly the court is doing.", + "options": [ + { + "text": "Admission of Attorneys", + "value": "Admission of Attorneys" + }, + { + "text": "Appeal [judges only]", + "value": "Appeal [judges only]" + }, + { + "text": "Appeal by Review", + "value": "Appeal by Review" + }, + { + "text": "Appeal in Chancery", + "value": "Appeal in Chancery" + }, + { + "text": "Appeal with Jury", + "value": "Appeal with Jury" + }, + { + "text": "Appeal with Referee", + "value": "Appeal with Referee" + }, + { + "text": "Hearing - Petition", + "value": "Hearing - Petition" + }, + { + "text": "Hearing - Petition for sale of real estate", + "value": "Hearing - Petition for sale of real estate" + }, + { + "text": "Hearing - Petition for division of real estate", + "value": "Hearing - Petition for division of real estate" + }, + { + "text": "Naturalization", + "value": "Naturalization" + }, + { + "text": "Trial of First Instance", + "value": "Trial of First Instance" + }, + { + "text": "Other", + "value": "Other" + } + ], + "clearable": true + } + ], + "OtherCourtProceedingType": [ + { + "id": "d4e53036-be4e-4b3c-9d1f-7ca6dc1b3962", + "type": "text-field", + "label": "Other Court Proceeding Type", + "value": "", + "description": "A court proceeding type that is no a common proceeding, but has been noted in the court record." + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "target": [ + { + "id": "urn:madoc:collection:53", + "type": "Collection" + }, + { + "id": "urn:madoc:manifest:1", + "type": "Manifest" + }, + { + "id": "urn:madoc:canvas:9", + "type": "Canvas" + } + ], + "derivedFrom": "70d2bd06-1744-4512-b55c-bcea4dfa1ef0", + "contributors": { + "urn:madoc:user:1": { + "id": "urn:madoc:user:1", + "type": "Person", + "name": "Madoc TS" + } + } +} diff --git a/services/madoc-ts/fixtures/simple.json b/services/madoc-ts/fixtures/simple.json new file mode 100644 index 000000000..79ce56ac0 --- /dev/null +++ b/services/madoc-ts/fixtures/simple.json @@ -0,0 +1,243 @@ +{ + "structure": { + "id": "c1", + "type": "choice", + "label": "Annotate this book", + "description": "Add some details on this book, or leave a review", + "items": [ + { + "id": "c2", + "type": "choice", + "label": "Describe this book", + "items": [ + { + "id": "c3", + "type": "model", + "label": "Basic information", + "fields": [ + "name", + "description", + "author" + ] + }, + { + "id": "c4", + "type": "model", + "label": "Add an ISBN number", + "fields": [ + "isbn" + ] + } + ] + }, + { + "id": "c6", + "type": "model", + "label": "Rate this book", + "fields": [ + [ + "review", + [ + "author", + "datePublished", + "reviewRating" + ] + ] + ] + } + ] + }, + "document": { + "@context": "http://schema.org", + "conformsTo": "Book", + "type": "entity", + "properties": { + "name": [ + { + "type": "text-field", + "term": "name", + "label": "Enter the name of the book", + "value": "The Hitchhiker's Guide to the Galaxy", + "selector": { + "type": "box-selector", + "state": null + } + } + ], + "description": [ + { + "type": "text-field", + "term": "description", + "label": "Enter a description of the book", + "value": "The Hitchhiker's Guide to the Galaxy is the first of five books in the Hitchhiker's Guide to the Galaxy comedy science fiction \"trilogy\" by Douglas Adams. The novel is an adaptation of the first four parts of Adams' radio series of the same name." + } + ], + "isbn": [ + { + "type": "text-field", + "term": "isbn", + "label": "Enter the ISBN", + "value": "0345391802" + } + ], + "author": [ + { + "type": "text-field", + "term": "author", + "label": "Author", + "value": "http://viaf.org/viaf/113230702" + } + ], + "review": [ + { + "type": "entity", + "label": "Review", + "conformsTo": "Review", + "term": "review", + "properties": { + "author": [ + { + "type": "text-field", + "term": "author", + "label": "Enter your name", + "value": "John Doe" + } + ], + "datePublished": [ + { + "type": "text-field", + "label": "Current date", + "term": "datePublished", + "format": "YYYY-MM-DD", + "value": "2019-10-10" + } + ], + "name": [ + { + "type": "text-field", + "term": "name", + "label": "Short name of your review", + "value": "A masterpiece of literature" + } + ], + "reviewBody": [ + { + "type": "text-field", + "term": "reviewBody", + "label": "Write your review", + "value": "Very simply, the book is one of the funniest SF spoofs ever written, with hyperbolic ideas folding in on themselves" + } + ], + "reviewRating": [ + { + "term": "reviewRating", + "label": "Star rating", + "type": "text-field", + "max": 5, + "value": 5 + } + ] + } + }, + { + "type": "entity", + "label": "Review", + "conformsTo": "Review", + "term": "review", + "properties": { + "author": [ + { + "term": "author", + "type": "text-field", + "label": "Enter your name", + "value": "John Smith" + } + ], + "datePublished": [ + { + "term": "datePublished", + "type": "text-field", + "label": "Current date", + "format": "YYYY-MM-DD", + "value": "2019-10-11" + } + ], + "name": [ + { + "term": "name", + "type": "text-field", + "label": "Short name of your review", + "value": "" + } + ], + "reviewBody": [ + { + "term": "reviewBody", + "type": "text-field", + "label": "Write your review", + "value": "" + } + ], + "reviewRating": [ + { + "type": "text-field", + "max": 5, + "value": 4, + "term": "reviewRating" + } + ] + } + }, + { + "type": "entity", + "label": "Review", + "conformsTo": "Review", + "term": "review", + "properties": { + "author": [ + { + "term": "author", + "type": "text-field", + "label": "Enter your name", + "value": "Jane Doe" + } + ], + "datePublished": [ + { + "term": "datePublished", + "type": "text-field", + "label": "Current date", + "format": "YYYY-MM-DD", + "value": "2019-10-12" + } + ], + "name": [ + { + "term": "name", + "type": "text-field", + "label": "Short name of your review", + "value": "A great book" + } + ], + "reviewBody": [ + { + "term": "reviewBody", + "type": "text-field", + "label": "Write your review", + "value": "It's very great" + } + ], + "reviewRating": [ + { + "term": "reviewRating", + "type": "text-field", + "max": 5, + "value": 5 + } + ] + } + } + ] + } + } +} diff --git a/services/madoc-ts/package.json b/services/madoc-ts/package.json index e744b1af0..eed87b5b5 100644 --- a/services/madoc-ts/package.json +++ b/services/madoc-ts/package.json @@ -43,6 +43,7 @@ "deepmerge": "4.2.2", "dnd-multi-backend": "6.0.0", "html-entities": "1.4.0", + "formik": "^2.2.9", "react": ">=17", "react-dom": ">=17", "@types/react": ">=17", @@ -55,9 +56,6 @@ "@atlas-viewer/atlas": "1.6.9", "@atlas-viewer/iiif-image-api": "^1.2.6", "@babel/runtime": "^7.16.0", - "@capture-models/editor": "^0.13.23", - "@capture-models/helpers": "^0.13.14", - "@capture-models/types": "^0.13.0", "@hyperion-framework/parser": "^1.1.0", "@hyperion-framework/presentation-2": "^1.1.0", "@hyperion-framework/presentation-2-parser": "^1.1.0", @@ -68,6 +66,7 @@ "@koa/router": "^10.0.0", "@madoc.io/types": "./npm/madoc-types", "@slonik/migrator": "^0.2.0", + "@styled-icons/entypo": "^10.34.0", "ajv": "^6.12.0", "babel-preset-react": "^6.24.1", "bcrypt": "^5.0.1", @@ -84,10 +83,16 @@ "css-loader": "^4.2.2", "deepmerge": "^4.2.2", "del": "^6.0.0", + "draft-js": "^0.11.7", + "draft-js-export-html": "^1.4.1", + "draft-js-import-element": "^1.4.0", + "draft-js-import-html": "^1.4.1", + "draft-js-utils": "^1.4.0", "easy-peasy": "^3.3.0", "email-validator": "^2.0.4", + "fast-copy": "^2.1.1", "form-data": "^4.0.0", - "formik": "^2.1.4", + "formik": "^2.2.9", "framer-motion": "^2.0.1", "i18next": "^19.7.0", "i18next-browser-languagedetector": "^6.0.1", @@ -147,10 +152,13 @@ "react-query": "^2.16.0", "react-router-config": "^5.1.1", "react-router-dom": "^5.1.2", + "react-rte": "0.16.1", "react-stack-grid": "^0.7.1", "react-syntax-highlighter": "^15.4.3", + "react-textarea-autosize": "^8.3.1", "react-timeago": "^4.4.0", - "react-tooltip": "^4.2.15", + "react-tooltip": "^4.2.21", + "redux-batched-subscribe": "^0.1.6", "rich-markdown-editor": "^11.17.8", "semver": "^7.3.5", "sharp": "^0.25.2", @@ -185,6 +193,7 @@ "@types/color-hash": "^1.0.0", "@types/content-type": "^1.1.3", "@types/cookie-parser": "^1.4.2", + "@types/draft-js": "^0.10.43", "@types/jest": "^27.0.2", "@types/js-cookie": "^2.2.6", "@types/json-schema": "^7.0.7", @@ -211,8 +220,10 @@ "@types/react-helmet": "^6.1.0", "@types/react-router-config": "^5.0.1", "@types/react-router-dom": "^5.1.4", + "@types/react-rte": "^0.16.1", "@types/react-stack-grid": "^0.7.0", "@types/react-timeago": "^4.1.1", + "@types/redux-batched-subscribe": "^0.1.4", "@types/semver": "^7.3.6", "@types/sharp": "^0.24.0", "@types/slonik": "^22.1.4", diff --git a/services/madoc-ts/schemas/CreateCanvas.json b/services/madoc-ts/schemas/CreateCanvas.json index e0f909de0..0d1edb0fb 100644 --- a/services/madoc-ts/schemas/CreateCanvas.json +++ b/services/madoc-ts/schemas/CreateCanvas.json @@ -4,9 +4,6 @@ "canvas": {}, "thumbnail": { "type": "string" - }, - "local_source": { - "type": "string" } }, "required": [ diff --git a/services/madoc-ts/schemas/CreateManifest.json b/services/madoc-ts/schemas/CreateManifest.json index 5d927fdf9..c47b7cc22 100644 --- a/services/madoc-ts/schemas/CreateManifest.json +++ b/services/madoc-ts/schemas/CreateManifest.json @@ -2,9 +2,6 @@ "type": "object", "properties": { "manifest": {}, - "local_source": { - "type": "string" - }, "taskId": { "type": "string" } diff --git a/services/madoc-ts/schemas/ManifestFull.json b/services/madoc-ts/schemas/ManifestFull.json index e5452d281..e76bcf540 100644 --- a/services/madoc-ts/schemas/ManifestFull.json +++ b/services/madoc-ts/schemas/ManifestFull.json @@ -88,6 +88,12 @@ "published": { "type": "boolean" }, + "viewingDirection": { + "$ref": "#/definitions/ViewingDirection" + }, + "source": { + "type": "string" + }, "items": { "type": "array", "items": { @@ -121,7 +127,8 @@ "id", "items", "label", - "thumbnail" + "thumbnail", + "viewingDirection" ] }, "subjects": { diff --git a/services/madoc-ts/schemas/ProjectConfiguration.json b/services/madoc-ts/schemas/ProjectConfiguration.json index 4a9b0d4ac..b0d0f803f 100644 --- a/services/madoc-ts/schemas/ProjectConfiguration.json +++ b/services/madoc-ts/schemas/ProjectConfiguration.json @@ -110,6 +110,9 @@ "miradorCanvasPage": { "type": "boolean" }, + "universalViewerCanvasPage": { + "type": "boolean" + }, "contributionMode": { "enum": [ "annotation", diff --git a/services/madoc-ts/src/extensions/capture-models/ConfigInjection/ConfigInjection.extension.ts b/services/madoc-ts/src/extensions/capture-models/ConfigInjection/ConfigInjection.extension.ts index 8048fe475..c91066429 100644 --- a/services/madoc-ts/src/extensions/capture-models/ConfigInjection/ConfigInjection.extension.ts +++ b/services/madoc-ts/src/extensions/capture-models/ConfigInjection/ConfigInjection.extension.ts @@ -1,4 +1,4 @@ -import { CaptureModel } from '@capture-models/types'; +import { CaptureModel } from '../../../frontend/shared/capture-models/types/capture-model'; import { ApiClient } from '../../../gateway/api'; import { parseUrn } from '../../../utility/parse-urn'; import { defaultDispose } from '../../extension-manager'; diff --git a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/DynamicDataSources.extension.ts b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/DynamicDataSources.extension.ts index d42c46805..6949c1c29 100644 --- a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/DynamicDataSources.extension.ts +++ b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/DynamicDataSources.extension.ts @@ -1,5 +1,6 @@ -import { traverseDocument } from '@capture-models/helpers'; -import { BaseField, CaptureModel } from '@capture-models/types'; +import { traverseDocument } from '../../../frontend/shared/capture-models/helpers/traverse-document'; +import { CaptureModel } from '../../../frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../../frontend/shared/capture-models/types/field-types'; import { ApiClient } from '../../../gateway/api'; import { parseModelTarget } from '../../../utility/parse-model-target'; import { defaultDispose } from '../../extension-manager'; diff --git a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/sources/Plaintext.source.ts b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/sources/Plaintext.source.ts index cc9b9d31a..1aad1f459 100644 --- a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/sources/Plaintext.source.ts +++ b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/sources/Plaintext.source.ts @@ -1,4 +1,4 @@ -import { FieldSource } from '@capture-models/editor'; +import { FieldSource } from '../../../../frontend/shared/capture-models/editor/components/FieldEditor/FieldEditor'; import { DynamicData, DynamicDataLoader } from '../types'; const plaintextSourceDefinition: FieldSource = { diff --git a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/types.ts b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/types.ts index 6770b595a..3209b3fe0 100644 --- a/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/types.ts +++ b/services/madoc-ts/src/extensions/capture-models/DynamicDataSources/types.ts @@ -1,5 +1,5 @@ -import { FieldSource } from '@capture-models/editor'; -import { BaseField } from '@capture-models/types'; +import { FieldSource } from '../../../frontend/shared/capture-models/editor/components/FieldEditor/FieldEditor'; +import { BaseField } from '../../../frontend/shared/capture-models/types/field-types'; import { ApiClient } from '../../../gateway/api'; export type DynamicDataLoader = ( diff --git a/services/madoc-ts/src/extensions/capture-models/MediaExplorer/index.ts b/services/madoc-ts/src/extensions/capture-models/MediaExplorer/index.ts index dac781658..4a30286cd 100644 --- a/services/madoc-ts/src/extensions/capture-models/MediaExplorer/index.ts +++ b/services/madoc-ts/src/extensions/capture-models/MediaExplorer/index.ts @@ -1,9 +1,9 @@ -import { FieldSpecification } from '@capture-models/types'; import React from 'react'; +import { FieldSpecification } from '../../../frontend/shared/capture-models/types/field-types'; import { MediaExplorer, MediaExplorerProps } from './MediaExplorer'; import { MediaExplorerPreview } from './MediaExplorer.preview'; -declare module '@capture-models/types' { +declare module '../../../frontend/shared/capture-models/types/field-types' { export interface FieldTypeMap { 'madoc-media-explorer': MediaExplorerProps; } diff --git a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.extension.ts b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.extension.ts index 1a0f5b2f6..fc6b8112e 100644 --- a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.extension.ts +++ b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.extension.ts @@ -1,5 +1,7 @@ -import { traverseDocument, traverseStructure } from '@capture-models/helpers'; -import { BaseField, CaptureModel, NestedModelFields } from '@capture-models/types'; +import { traverseDocument } from '../../../frontend/shared/capture-models/helpers/traverse-document'; +import { traverseStructure } from '../../../frontend/shared/capture-models/helpers/traverse-structure'; +import { CaptureModel, NestedModelFields } from '../../../frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../../frontend/shared/capture-models/types/field-types'; import { ApiClient } from '../../../gateway/api'; import { parseModelTarget } from '../../../utility/parse-model-target'; import { defaultDispose } from '../../extension-manager'; diff --git a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.helpers.ts b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.helpers.ts index de3f8154d..a12d43693 100644 --- a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.helpers.ts +++ b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.helpers.ts @@ -1,5 +1,9 @@ -import { generateId, isEntity, isEntityList, traverseDocument } from '@capture-models/helpers'; -import { BaseField, BaseSelector, CaptureModel } from '@capture-models/types'; +import { generateId } from '../../../frontend/shared/capture-models/helpers/generate-id'; +import { isEntity } from '../../../frontend/shared/capture-models/helpers/is-entity'; +import { traverseDocument } from '../../../frontend/shared/capture-models/helpers/traverse-document'; +import { CaptureModel } from '../../../frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../../frontend/shared/capture-models/types/field-types'; +import { BaseSelector } from '../../../frontend/shared/capture-models/types/selector-types'; export const PARAGRAPHS_PROFILE = 'http://madoc.io/profiles/capture-model-fields/paragraphs'; diff --git a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.slots.tsx b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.slots.tsx index de59ab3f4..1c4452035 100644 --- a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.slots.tsx +++ b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.slots.tsx @@ -1,14 +1,14 @@ import React from 'react'; import styled, { css } from 'styled-components'; -import { InlineReadonlyValue } from '../../../frontend/shared/caputre-models/new/components/DefaultInlineField'; -import { ProfileConfig, useSlotContext } from '../../../frontend/shared/caputre-models/new/components/EditorSlots'; -import { ModifiedStatus } from '../../../frontend/shared/caputre-models/new/features/ModifiedStatus'; -import { useCurrentEntity } from '../../../frontend/shared/caputre-models/new/hooks/use-current-entity'; -import { useEntityDetails } from '../../../frontend/shared/caputre-models/new/hooks/use-entity-details'; -import { useFieldDetails } from '../../../frontend/shared/caputre-models/new/hooks/use-field-details'; -import { mapProperties } from '../../../frontend/shared/caputre-models/new/utility/map-properties'; -import { getEntityLabel } from '../../../frontend/shared/caputre-models/utility/get-entity-label'; -import { DocumentPreview } from '../../../frontend/shared/caputre-models/DocumentPreview'; +import { InlineReadonlyValue } from '../../../frontend/shared/capture-models/new/components/DefaultInlineField'; +import { ProfileConfig, useSlotContext } from '../../../frontend/shared/capture-models/new/components/EditorSlots'; +import { ModifiedStatus } from '../../../frontend/shared/capture-models/new/features/ModifiedStatus'; +import { useCurrentEntity } from '../../../frontend/shared/capture-models/new/hooks/use-current-entity'; +import { useEntityDetails } from '../../../frontend/shared/capture-models/new/hooks/use-entity-details'; +import { useFieldDetails } from '../../../frontend/shared/capture-models/new/hooks/use-field-details'; +import { mapProperties } from '../../../frontend/shared/capture-models/new/utility/map-properties'; +import { getEntityLabel } from '../../../frontend/shared/capture-models/utility/get-entity-label'; +import { DocumentPreview } from '../../../frontend/shared/capture-models/DocumentPreview'; const InlineLine = styled.div<{ $isModified?: boolean }>` border-left: 3px solid transparent; diff --git a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.tsx b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.tsx index 855514cdb..1deb86789 100644 --- a/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.tsx +++ b/services/madoc-ts/src/extensions/capture-models/Paragraphs/Paragraphs.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { BaseField } from '@capture-models/types'; +import { BaseField } from '../../../frontend/shared/capture-models/types/field-types'; import { TableEmpty } from '../../../frontend/shared/layout/Table'; export interface ParagraphsProps extends BaseField { diff --git a/services/madoc-ts/src/extensions/capture-models/Paragraphs/index.ts b/services/madoc-ts/src/extensions/capture-models/Paragraphs/index.ts index 138e38c3a..b46087253 100644 --- a/services/madoc-ts/src/extensions/capture-models/Paragraphs/index.ts +++ b/services/madoc-ts/src/extensions/capture-models/Paragraphs/index.ts @@ -1,11 +1,12 @@ import React from 'react'; -import { registerField } from '@capture-models/plugin-api'; +import { registerField } from '../../../frontend/shared/capture-models/plugin-api/global-store'; +import { FieldSpecification } from '../../../frontend/shared/capture-models/types/field-types'; + import { Paragraphs, ParagraphsProps } from './Paragraphs'; -import { FieldSpecification } from '@capture-models/types'; import { ParagraphsPreview } from './Paragraphs.preview'; -declare module '@capture-models/types' { - export interface FieldTypeMap { +declare module '../../../frontend/shared/capture-models/types/field-types' { + interface FieldTypeMap { 'madoc-paragraphs': ParagraphsProps; } } diff --git a/services/madoc-ts/src/extensions/capture-models/crowdsourcing-api.ts b/services/madoc-ts/src/extensions/capture-models/crowdsourcing-api.ts index 49b622c3c..be106ded6 100644 --- a/services/madoc-ts/src/extensions/capture-models/crowdsourcing-api.ts +++ b/services/madoc-ts/src/extensions/capture-models/crowdsourcing-api.ts @@ -1,7 +1,11 @@ -import { createChoice, createDocument, generateId, traverseDocument, traverseStructure } from '@capture-models/helpers'; -import { CaptureModel, RevisionRequest } from '@capture-models/types'; import deepmerge from 'deepmerge'; import { stringify } from 'query-string'; +import { createChoice } from '../../frontend/shared/capture-models/helpers/create-choice'; +import { createDocument } from '../../frontend/shared/capture-models/helpers/create-document'; +import { generateId } from '../../frontend/shared/capture-models/helpers/generate-id'; +import { traverseDocument } from '../../frontend/shared/capture-models/helpers/traverse-document'; +import { CaptureModel } from '../../frontend/shared/capture-models/types/capture-model'; +import { RevisionRequest } from '../../frontend/shared/capture-models/types/revision-request'; import { ApiClient } from '../../gateway/api'; import { CrowdsourcingReview, @@ -12,6 +16,7 @@ import { CrowdsourcingTask } from '../../gateway/tasks/crowdsourcing-task'; import { CaptureModelSnippet } from '../../types/schemas/capture-model-snippet'; import { ModelSearch } from '../../types/schemas/search'; import { generateModelFields } from '../../utility/generate-model-fields'; +import { traverseStructure } from "../../utility/traverse-structure"; import { BaseExtension, defaultDispose, ExtensionManager } from '../extension-manager'; import { DynamicData } from './DynamicDataSources/types'; import { CaptureModelExtension } from './extension'; diff --git a/services/madoc-ts/src/extensions/capture-models/extension.ts b/services/madoc-ts/src/extensions/capture-models/extension.ts index c2e1651f4..411c60529 100644 --- a/services/madoc-ts/src/extensions/capture-models/extension.ts +++ b/services/madoc-ts/src/extensions/capture-models/extension.ts @@ -1,4 +1,4 @@ -import { CaptureModel } from '@capture-models/types'; +import { CaptureModel } from '../../frontend/shared/capture-models/types/capture-model'; import { BaseExtension } from '../extension-manager'; export interface CaptureModelExtension extends BaseExtension { diff --git a/services/madoc-ts/src/extensions/notifications/extension.ts b/services/madoc-ts/src/extensions/notifications/extension.ts index f2ef12343..5e5cef9b2 100644 --- a/services/madoc-ts/src/extensions/notifications/extension.ts +++ b/services/madoc-ts/src/extensions/notifications/extension.ts @@ -1,5 +1,5 @@ -import { generateId } from '@capture-models/helpers'; import { stringify } from 'query-string'; +import { generateId } from '../../frontend/shared/capture-models/helpers/generate-id'; import { ApiClient } from '../../gateway/api'; import { BaseTask } from '../../gateway/tasks/base-task'; import { NotificationList, NotificationRequest } from '../../types/notifications'; diff --git a/services/madoc-ts/src/extensions/page-blocks/block-editor-react.ts b/services/madoc-ts/src/extensions/page-blocks/block-editor-react.ts index 72a254d5b..f9f82f673 100644 --- a/services/madoc-ts/src/extensions/page-blocks/block-editor-react.ts +++ b/services/madoc-ts/src/extensions/page-blocks/block-editor-react.ts @@ -1,5 +1,5 @@ -import { BaseField } from '@capture-models/types'; import React, { JSXElementConstructor } from 'react'; +import { BaseField } from '../../frontend/shared/capture-models/types/field-types'; import { blockConfigFor } from '../../frontend/shared/plugins/external/block-config-for'; import { EditorialContext } from '../../types/schemas/site-page'; import { PageBlockDefinition, PageBlockEditor, PageBlockExtension } from './extension'; diff --git a/services/madoc-ts/src/extensions/page-blocks/current-manifest-snippet-block.tsx b/services/madoc-ts/src/extensions/page-blocks/current-manifest-snippet-block.tsx index e7bf08a1b..49325ab98 100644 --- a/services/madoc-ts/src/extensions/page-blocks/current-manifest-snippet-block.tsx +++ b/services/madoc-ts/src/extensions/page-blocks/current-manifest-snippet-block.tsx @@ -1,5 +1,5 @@ -import { captureModelShorthand } from '@capture-models/helpers'; import React from 'react'; +import { captureModelShorthand } from '../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { ManifestSnippet } from '../../frontend/shared/components/ManifestSnippet'; import { ReactPageBlockDefinition } from './extension'; diff --git a/services/madoc-ts/src/extensions/page-blocks/extension.ts b/services/madoc-ts/src/extensions/page-blocks/extension.ts index 50038e5dc..90c6cdec3 100644 --- a/services/madoc-ts/src/extensions/page-blocks/extension.ts +++ b/services/madoc-ts/src/extensions/page-blocks/extension.ts @@ -1,5 +1,5 @@ -import { CaptureModel } from '@capture-models/types'; -import { InternationalString } from '@hyperion-framework/types/iiif/descriptive'; +import { CaptureModel } from '../../frontend/shared/capture-models/types/capture-model'; +import { InternationalString } from '@hyperion-framework/types'; import React, { JSXElementConstructor } from 'react'; import ReactDOM from 'react-dom'; import { ApiClient } from '../../gateway/api'; diff --git a/services/madoc-ts/src/extensions/page-blocks/simple-html-block/simple-html-block.ts b/services/madoc-ts/src/extensions/page-blocks/simple-html-block/simple-html-block.ts index 0e361b060..419c5fcf1 100644 --- a/services/madoc-ts/src/extensions/page-blocks/simple-html-block/simple-html-block.ts +++ b/services/madoc-ts/src/extensions/page-blocks/simple-html-block/simple-html-block.ts @@ -1,4 +1,4 @@ -import { captureModelShorthand } from '@capture-models/helpers'; +import { captureModelShorthand } from '../../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { HTMLPageBlockDefinition } from '../extension'; const definition: HTMLPageBlockDefinition<{ html: string }> = { diff --git a/services/madoc-ts/src/extensions/page-blocks/simple-markdown-block/simple-markdown-block.tsx b/services/madoc-ts/src/extensions/page-blocks/simple-markdown-block/simple-markdown-block.tsx index f93c91d00..a7b500d0c 100644 --- a/services/madoc-ts/src/extensions/page-blocks/simple-markdown-block/simple-markdown-block.tsx +++ b/services/madoc-ts/src/extensions/page-blocks/simple-markdown-block/simple-markdown-block.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import Editor, { theme } from 'rich-markdown-editor'; -import { captureModelShorthand } from '@capture-models/helpers'; import styled from 'styled-components'; +import { captureModelShorthand } from '../../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { useApi } from '../../../frontend/shared/hooks/use-api'; import { Button } from '../../../frontend/shared/navigation/Button'; import { ModalFooter } from '../../../frontend/shared/layout/Modal'; diff --git a/services/madoc-ts/src/extensions/projects/templates/crowdsourced-transcription.ts b/services/madoc-ts/src/extensions/projects/templates/crowdsourced-transcription.ts index 4007be197..1aa306617 100644 --- a/services/madoc-ts/src/extensions/projects/templates/crowdsourced-transcription.ts +++ b/services/madoc-ts/src/extensions/projects/templates/crowdsourced-transcription.ts @@ -1,10 +1,8 @@ -import { DropdownFieldProps } from '@capture-models/editor/lib/input-types/DropdownField/DropdownField'; -import { captureModelShorthand } from '@capture-models/helpers'; +import { DropdownFieldProps } from '../../../frontend/shared/capture-models/editor/input-types/DropdownField/DropdownField'; +import { captureModelShorthand } from '../../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { ProjectTemplate } from '../types'; type CrowdsourcedTranscriptionOptions = { - // submissionsPerImage: number; - // enablePersonalNotes: boolean; modelLabel: string; modelDescription: string; crowdsourcingInstructions: string; diff --git a/services/madoc-ts/src/extensions/projects/templates/kitchen-sink.ts b/services/madoc-ts/src/extensions/projects/templates/kitchen-sink.ts index 161ed0d31..c92a3bbe7 100644 --- a/services/madoc-ts/src/extensions/projects/templates/kitchen-sink.ts +++ b/services/madoc-ts/src/extensions/projects/templates/kitchen-sink.ts @@ -1,4 +1,4 @@ -import { captureModelShorthand } from '@capture-models/helpers'; +import { captureModelShorthand } from '../../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { ProjectTemplate } from '../types'; export const kitchenSinkTemplate: ProjectTemplate = { diff --git a/services/madoc-ts/src/extensions/projects/templates/ocr-correction.ts b/services/madoc-ts/src/extensions/projects/templates/ocr-correction.ts index 476e19a41..7231ac9cc 100644 --- a/services/madoc-ts/src/extensions/projects/templates/ocr-correction.ts +++ b/services/madoc-ts/src/extensions/projects/templates/ocr-correction.ts @@ -1,5 +1,5 @@ -import { DropdownFieldProps } from '@capture-models/editor/lib/input-types/DropdownField/DropdownField'; -import { captureModelShorthand } from '@capture-models/helpers'; +import { DropdownFieldProps } from '../../../frontend/shared/capture-models/editor/input-types/DropdownField/DropdownField'; +import { captureModelShorthand } from '../../../frontend/shared/capture-models/helpers/capture-model-shorthand'; import { ProjectTemplate } from '../types'; type OCRCorrectionOptions = { diff --git a/services/madoc-ts/src/extensions/projects/types.ts b/services/madoc-ts/src/extensions/projects/types.ts index 77c65f644..0a3c6913b 100644 --- a/services/madoc-ts/src/extensions/projects/types.ts +++ b/services/madoc-ts/src/extensions/projects/types.ts @@ -1,8 +1,8 @@ -import { CaptureModel } from '@capture-models/types'; -import { BaseField } from '@capture-models/types/src/field-types'; import React from 'react'; import { ModelEditorConfig } from '../../frontend/admin/pages/crowdsourcing/model-editor/use-model-editor-config'; import { ProjectStatusMap } from '../../frontend/shared/atoms/ProjectStatus'; +import { CaptureModel } from '../../frontend/shared/capture-models/types/capture-model'; +import { BaseField } from '../../frontend/shared/capture-models/types/field-types'; import { MadocTheme } from '../../frontend/themes/definitions/types'; import { ApiClient } from '../../gateway/api'; import { ProjectRow } from '../../types/projects'; diff --git a/services/madoc-ts/src/frontend/admin/index.tsx b/services/madoc-ts/src/frontend/admin/index.tsx index 84f99e618..b5498d2d3 100644 --- a/services/madoc-ts/src/frontend/admin/index.tsx +++ b/services/madoc-ts/src/frontend/admin/index.tsx @@ -9,7 +9,7 @@ import { renderUniversalRoutes } from '../shared/utility/server-utils'; import { ApiContext, useIsApiRestarting } from '../shared/hooks/use-api'; import { ErrorMessage } from '../shared/callouts/ErrorMessage'; import { UniversalRoute } from '../types'; -import '../shared/caputre-models/plugins'; +import '../shared/capture-models/plugins'; import { SiteProvider } from '../shared/hooks/use-site'; import { AdminSidebar } from './molecules/AdminSidebar'; diff --git a/services/madoc-ts/src/frontend/admin/pages/content/linking/view-ocr-model.tsx b/services/madoc-ts/src/frontend/admin/pages/content/linking/view-ocr-model.tsx index 69c392329..5914fcab6 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/linking/view-ocr-model.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/linking/view-ocr-model.tsx @@ -1,14 +1,14 @@ -import { Revisions } from '@capture-models/editor'; -import { CaptureModel } from '@capture-models/types'; import * as React from 'react'; import { Suspense, useMemo } from 'react'; import { useQuery } from 'react-query'; import { preprocessCaptureModel } from '../../../../../extensions/capture-models/Paragraphs/Paragraphs.helpers'; import { ResourceLinkResponse } from '../../../../../types/schemas/linking'; -import { RevisionNavigation } from '../../../../shared/caputre-models/RevisionNavigation'; -import { documentFragmentWrapper } from '../../../../shared/caputre-models/utility/document-fragment-wrapper'; +import { RevisionNavigation } from '../../../../shared/capture-models/RevisionNavigation'; +import { CaptureModel } from '../../../../shared/capture-models/types/capture-model'; +import { documentFragmentWrapper } from '../../../../shared/capture-models/utility/document-fragment-wrapper'; import { useApi } from '../../../../shared/hooks/use-api'; import { ViewContentFetch } from '../../../molecules/ViewContentFetch'; +import { Revisions } from '../../../../shared/capture-models/editor/stores/revisions/index'; export const ViewOCRModel: React.FC<{ canvasId: number; link: ResourceLinkResponse }> = ({ canvasId, link }) => { const api = useApi(); diff --git a/services/madoc-ts/src/frontend/admin/pages/content/project-configuration.tsx b/services/madoc-ts/src/frontend/admin/pages/content/project-configuration.tsx index fa6fdabcf..28ab3fde4 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/project-configuration.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/project-configuration.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useMutation } from 'react-query'; import { useHistory } from 'react-router-dom'; import { WidePage } from '../../../shared/layout/WidePage'; -import { EditShorthandCaptureModel } from '../../../shared/caputre-models/EditorShorthandCaptureModel'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; import { siteConfigurationModel } from '../../../shared/configuration/site-config'; import { useApi } from '../../../shared/hooks/use-api'; import { apiHooks } from '../../../shared/hooks/use-api-query'; diff --git a/services/madoc-ts/src/frontend/admin/pages/content/site-configuration.tsx b/services/madoc-ts/src/frontend/admin/pages/content/site-configuration.tsx index 45b864643..66130b30e 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/site-configuration.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/site-configuration.tsx @@ -1,8 +1,8 @@ -import { RoundedCard } from '@capture-models/editor'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { SuccessMessage } from '../../../shared/callouts/SuccessMessage'; +import { RoundedCard } from '../../../shared/capture-models/editor/components/RoundedCard/RoundedCard'; import { WidePage } from '../../../shared/layout/WidePage'; import { useLocationQuery } from '../../../shared/hooks/use-location-query'; import { HrefLink } from '../../../shared/utility/href-link'; diff --git a/services/madoc-ts/src/frontend/admin/pages/content/system-configuration.tsx b/services/madoc-ts/src/frontend/admin/pages/content/system-configuration.tsx index 3786889c5..8ece8aa55 100644 --- a/services/madoc-ts/src/frontend/admin/pages/content/system-configuration.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/content/system-configuration.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useMutation } from 'react-query'; import { useHistory } from 'react-router-dom'; -import { EditShorthandCaptureModel } from '../../../shared/caputre-models/EditorShorthandCaptureModel'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; import { useApi } from '../../../shared/hooks/use-api'; import { useSite, useSystemConfig, useUpdateSystemConfig } from '../../../shared/hooks/use-site'; import { AdminHeader } from '../../molecules/AdminHeader'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/capture-model-list.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/capture-model-list.tsx index 5452c6478..922e9e7a6 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/capture-model-list.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/capture-model-list.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { CaptureModelSnippet } from '../../../../../types/schemas/capture-model-snippet'; +import { Card, CardContent } from '../../../../shared/capture-models/editor/atoms/Card'; import { UniversalComponent } from '../../../../types'; import { Link, useHistory } from 'react-router-dom'; import { Button } from '../../../../shared/navigation/Button'; import { useMutation } from 'react-query'; import { useApi } from '../../../../shared/hooks/use-api'; -import { Card, CardContent } from '@capture-models/editor'; import { useData } from '../../../../shared/hooks/use-data'; import { createUniversalComponent } from '../../../../shared/utility/create-universal-component'; import { AdminHeader } from '../../../molecules/AdminHeader'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/view-capture-model.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/view-capture-model.tsx index b3b3700d3..e3462d13e 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/view-capture-model.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/capture-models/view-capture-model.tsx @@ -1,11 +1,11 @@ +import { EditorContext } from '../../../../shared/capture-models/editor/components/EditorContext/EditorContext'; +import { defaultTheme } from '../../../../shared/capture-models/editor/themes'; +import { CaptureModel } from '../../../../shared/capture-models/types/capture-model'; import { UniversalComponent } from '../../../../types'; -import { EditorContext } from '@capture-models/editor'; import React, { useState } from 'react'; import { renderUniversalRoutes } from '../../../../shared/utility/server-utils'; import { Link, useParams } from 'react-router-dom'; -import { defaultTheme } from '@capture-models/editor'; import { ThemeProvider } from 'styled-components'; -import { CaptureModel } from '@capture-models/types'; import { useMutation } from 'react-query'; import { useApi } from '../../../../shared/hooks/use-api'; import { Button } from '../../../../shared/navigation/Button'; @@ -15,7 +15,7 @@ import { LightNavigation, LightNavigationItem } from '../../../../shared/navigat type ViewCaptureModelType = { params: { id: string; captureModelId: string }; - query: {}; + query: any; variables: { id: string }; data: CaptureModel; }; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/auto-structure.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/auto-structure.tsx index 495fd8c05..01858baec 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/auto-structure.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/auto-structure.tsx @@ -1,7 +1,8 @@ -import { DocumentStore, StructureStore } from '@capture-models/editor'; -import { generateId } from '@capture-models/helpers'; import React, { useEffect, useMemo } from 'react'; import { generateModelFields } from '../../../../../utility/generate-model-fields'; +import { DocumentStore } from '../../../../shared/capture-models/editor/stores/document/document-store'; +import { StructureStore } from '../../../../shared/capture-models/editor/stores/structure/structure-store'; +import { generateId } from '../../../../shared/capture-models/helpers/generate-id'; export const AutoStructure: React.FC = () => { const newDocument = DocumentStore.useStoreState(s => s.document); diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-document-editor.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-document-editor.tsx index fcedd7115..71814adfb 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-document-editor.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-document-editor.tsx @@ -1,15 +1,13 @@ import React, { useMemo } from 'react'; -import { - DocumentEditor, - DocumentStore, - FieldEditor, - FieldWrapper, - Segment, - StructureStore, -} from '@capture-models/editor'; -import { BaseField } from '@capture-models/types'; import { Header } from '../../../../shared/atoms/Header'; -import { isEntityList } from '@capture-models/helpers'; +import { Segment } from '../../../../shared/capture-models/editor/atoms/Segment'; +import { DocumentEditor } from '../../../../shared/capture-models/editor/components/DocumentEditor/DocumentEditor'; +import { FieldEditor } from '../../../../shared/capture-models/editor/components/FieldEditor/FieldEditor'; +import { FieldWrapper } from '../../../../shared/capture-models/editor/components/FieldWrapper/FieldWrapper'; +import { DocumentStore } from '../../../../shared/capture-models/editor/stores/document/document-store'; +import { StructureStore } from '../../../../shared/capture-models/editor/stores/structure/structure-store'; +import { isEntityList } from '../../../../shared/capture-models/helpers/is-entity'; +import { BaseField } from '../../../../shared/capture-models/types/field-types'; import { useApi } from '../../../../shared/hooks/use-api'; import { useModelEditorConfig } from './use-model-editor-config'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-structure-editor.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-structure-editor.tsx index 459f7053c..5723c7336 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-structure-editor.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/full-structure-editor.tsx @@ -1,6 +1,9 @@ -import { DocumentStore, StructureEditor, StructureStore, useFocusedStructureEditor } from '@capture-models/editor'; -import { CaptureModel } from '@capture-models/types'; import React from 'react'; +import { StructureEditor } from '../../../../shared/capture-models/editor/components/StructureEditor/StructureEditor'; +import { DocumentStore } from '../../../../shared/capture-models/editor/stores/document/document-store'; +import { StructureStore } from '../../../../shared/capture-models/editor/stores/structure/structure-store'; +import { useFocusedStructureEditor } from '../../../../shared/capture-models/editor/stores/structure/use-focused-structure-editor'; +import { CaptureModel } from '../../../../shared/capture-models/types/capture-model'; import { useApi } from '../../../../shared/hooks/use-api'; import { useModelEditorConfig } from './use-model-editor-config'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/preview-capture-model.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/preview-capture-model.tsx index bbd3b53c6..1a67512b5 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/preview-capture-model.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/model-editor/preview-capture-model.tsx @@ -1,15 +1,15 @@ import { VaultProvider } from '@hyperion-framework/react-vault'; import React, { Suspense, useState } from 'react'; -import { useCaptureModel } from '@capture-models/editor'; import { useParams } from 'react-router-dom'; -import { BackToChoicesButton } from '../../../../shared/caputre-models/new/components/BackToChoicesButton'; -import { EditorSlots } from '../../../../shared/caputre-models/new/components/EditorSlots'; -import { RevisionProviderWithFeatures } from '../../../../shared/caputre-models/new/components/RevisionProviderWithFeatures'; +import { useCaptureModel } from '../../../../shared/capture-models/editor/components/EditorContext/EditorContext'; +import { BackToChoicesButton } from '../../../../shared/capture-models/new/components/BackToChoicesButton'; +import { EditorSlots } from '../../../../shared/capture-models/new/components/EditorSlots'; +import { RevisionProviderWithFeatures } from '../../../../shared/capture-models/new/components/RevisionProviderWithFeatures'; +import { CaptureModel } from '../../../../shared/capture-models/types/capture-model'; import { useApi } from '../../../../shared/hooks/use-api'; import { ContentExplorer } from '../../../../shared/components/ContentExplorer'; import { Button, ButtonRow, TinyButton } from '../../../../shared/navigation/Button'; -import '../../../../shared/caputre-models/refinements'; -import { CaptureModel } from '@capture-models/types'; +import '../../../../shared/capture-models/refinements'; import { ViewContentFetch } from '../../../molecules/ViewContentFetch'; export const PreviewCaptureModel: React.FC<{ diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/new-project-from-template.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/new-project-from-template.tsx index c6f43ee15..c300a8b39 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/new-project-from-template.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/new-project-from-template.tsx @@ -6,7 +6,7 @@ import { useHistory, useParams } from 'react-router-dom'; import slugify from 'slugify'; import { CreateProject } from '../../../../../types/schemas/create-project'; import { ErrorMessage } from '../../../../shared/callouts/ErrorMessage'; -import { EditShorthandCaptureModel } from '../../../../shared/caputre-models/EditorShorthandCaptureModel'; +import { EditShorthandCaptureModel } from '../../../../shared/capture-models/EditorShorthandCaptureModel'; import { ProjectBanner } from '../../../../shared/components/ProjectBanner'; import { Stepper, StepperContainer } from '../../../../shared/components/Stepper'; import { Input, InputContainer, InputLabel } from '../../../../shared/form/Input'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-configuration.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-configuration.tsx index 73f7dcd80..c79fca90c 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-configuration.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-configuration.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { ProjectFull } from '../../../../../types/project-full'; import { EmptyState } from '../../../../shared/layout/EmptyState'; import { SuccessMessage } from '../../../../shared/callouts/SuccessMessage'; -import { EditShorthandCaptureModel } from '../../../../shared/caputre-models/EditorShorthandCaptureModel'; +import { EditShorthandCaptureModel } from '../../../../shared/capture-models/EditorShorthandCaptureModel'; import { useAdminLayout } from '../../../../shared/components/AdminMenu'; import { siteConfigurationModel } from '../../../../shared/configuration/site-config'; import { useApi } from '../../../../shared/hooks/use-api'; diff --git a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-model-editor.tsx b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-model-editor.tsx index 215011b26..9aff2e1fa 100644 --- a/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-model-editor.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/crowdsourcing/projects/project-model-editor.tsx @@ -1,14 +1,15 @@ import { useTranslation } from 'react-i18next'; +import { EditorContext } from '../../../../shared/capture-models/editor/components/EditorContext/EditorContext'; +import { defaultTheme } from '../../../../shared/capture-models/editor/themes'; +import { CaptureModel } from '../../../../shared/capture-models/types/capture-model'; import { EmptyState } from '../../../../shared/layout/EmptyState'; import { DashboardTab, DashboardTabs } from '../../../../shared/components/DashboardTabs'; import { useProjectTemplate } from '../../../../shared/hooks/use-project-template'; import { UniversalComponent } from '../../../../types'; -import { EditorContext, defaultTheme } from '@capture-models/editor'; import React, { useState } from 'react'; import { renderUniversalRoutes } from '../../../../shared/utility/server-utils'; import { Link, useHistory, useParams } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; -import { CaptureModel } from '@capture-models/types'; import { useMutation } from 'react-query'; import { useApi } from '../../../../shared/hooks/use-api'; import { useData } from '../../../../shared/hooks/use-data'; diff --git a/services/madoc-ts/src/frontend/admin/pages/global/global-system-config.tsx b/services/madoc-ts/src/frontend/admin/pages/global/global-system-config.tsx index cadb3f9e2..2ed030320 100644 --- a/services/madoc-ts/src/frontend/admin/pages/global/global-system-config.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/global/global-system-config.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { useMutation } from 'react-query'; import { Site, SystemConfig } from '../../../../extensions/site-manager/types'; import { SuccessMessage } from '../../../shared/callouts/SuccessMessage'; -import { EditShorthandCaptureModel } from '../../../shared/caputre-models/EditorShorthandCaptureModel'; +import { EditShorthandCaptureModel } from '../../../shared/capture-models/EditorShorthandCaptureModel'; import { useApi } from '../../../shared/hooks/use-api'; import { useData } from '../../../shared/hooks/use-data'; import { serverRendererFor } from '../../../shared/plugins/external/server-renderer-for'; diff --git a/services/madoc-ts/src/frontend/admin/pages/global/list-users.tsx b/services/madoc-ts/src/frontend/admin/pages/global/list-users.tsx index 2c89b4a61..816e520a3 100644 --- a/services/madoc-ts/src/frontend/admin/pages/global/list-users.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/global/list-users.tsx @@ -1,8 +1,8 @@ -import { Tag } from '@capture-models/editor'; import React, { useEffect, useState } from 'react'; import { Redirect, useHistory } from 'react-router-dom'; import ReactTimeago from 'react-timeago'; import { User } from '../../../../extensions/site-manager/types'; +import { Tag } from '../../../shared/capture-models/editor/atoms/Tag'; import { Button, ButtonRow } from '../../../shared/navigation/Button'; import { SimpleTable } from '../../../shared/layout/SimpleTable'; import { SuccessMessage } from '../../../shared/callouts/SuccessMessage'; diff --git a/services/madoc-ts/src/frontend/admin/pages/sites/site-permissions.tsx b/services/madoc-ts/src/frontend/admin/pages/sites/site-permissions.tsx index c4d84c3a0..09542744d 100644 --- a/services/madoc-ts/src/frontend/admin/pages/sites/site-permissions.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/sites/site-permissions.tsx @@ -1,10 +1,10 @@ -import { Tag } from '@capture-models/editor'; import React, { useEffect, useRef, useState } from 'react'; import { SelectRef } from 'react-functional-select/dist/Select'; import { useTranslation } from 'react-i18next'; import { useMutation } from 'react-query'; import { SiteUser } from '../../../../extensions/site-manager/types'; import { siteRoles } from '../../../config'; +import { Tag } from '../../../shared/capture-models/editor/atoms/Tag'; import { Button, ButtonRow, SmallButton } from '../../../shared/navigation/Button'; import { DefaultSelect } from '../../../shared/form/DefaulSelect'; import { ErrorMessage } from '../../../shared/callouts/ErrorMessage'; diff --git a/services/madoc-ts/src/frontend/admin/pages/tasks/crowdsourcing-canvas-task.tsx b/services/madoc-ts/src/frontend/admin/pages/tasks/crowdsourcing-canvas-task.tsx index 9eaf28fb1..5e6b95daa 100644 --- a/services/madoc-ts/src/frontend/admin/pages/tasks/crowdsourcing-canvas-task.tsx +++ b/services/madoc-ts/src/frontend/admin/pages/tasks/crowdsourcing-canvas-task.tsx @@ -1,8 +1,8 @@ import React, { useMemo, useState } from 'react'; import { CrowdsourcingCanvasTask } from '../../../../gateway/tasks/crowdsourcing-canvas-task'; import { parseUrn } from '../../../../utility/parse-urn'; -import { Inspector } from '../../../shared/caputre-models/inspector/Inspector'; -import { Editor, EditorProps } from '../../../shared/caputre-models/new/Editor'; +import { Inspector } from '../../../shared/capture-models/inspector/Inspector'; +import { Editor, EditorProps } from '../../../shared/capture-models/new/Editor'; import { apiHooks } from '../../../shared/hooks/use-api-query'; import { GenericTask } from './generic-task'; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/CaptureModelEditor.tsx similarity index 85% rename from services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelEditor.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/CaptureModelEditor.tsx index f0689b324..b1d9ded52 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelEditor.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/CaptureModelEditor.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { CaptureModel, RevisionRequest } from '@capture-models/types'; import { RevisionNavigation } from './RevisionNavigation'; import { useViewerSaving } from '../hooks/use-viewer-saving'; +import { CaptureModel } from './types/capture-model'; +import { RevisionRequest } from './types/revision-request'; export const CaptureModelEditor: React.FC<{ captureModel: CaptureModel; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelHeader.tsx b/services/madoc-ts/src/frontend/shared/capture-models/CaptureModelHeader.tsx similarity index 96% rename from services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelHeader.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/CaptureModelHeader.tsx index 4d8a9036e..6480dfb28 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/CaptureModelHeader.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/CaptureModelHeader.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { Revisions } from '@capture-models/editor'; +import { Revisions } from './editor/stores/revisions/index'; const HeaderBackground = styled.div``; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/Choice.tsx b/services/madoc-ts/src/frontend/shared/capture-models/Choice.tsx similarity index 100% rename from services/madoc-ts/src/frontend/shared/caputre-models/Choice.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/Choice.tsx diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/DocumentPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/DocumentPreview.tsx similarity index 74% rename from services/madoc-ts/src/frontend/shared/caputre-models/DocumentPreview.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/DocumentPreview.tsx index f0bc138f3..54c6d32ce 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/DocumentPreview.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/DocumentPreview.tsx @@ -1,7 +1,8 @@ import React, { ComponentClass, FunctionComponent, useMemo } from 'react'; -import { FieldPreview } from '@capture-models/editor'; -import { isEntity } from '@capture-models/helpers'; -import { BaseField, CaptureModel } from '@capture-models/types'; +import { FieldPreview } from './editor/components/FieldPreview/FieldPreview'; +import { isEntity } from './helpers/is-entity'; +import { CaptureModel } from './types/capture-model'; +import { BaseField } from './types/field-types'; import { getEntityLabel } from './utility/get-entity-label'; export const DocumentPreview: React.FC<{ diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/EditorShorthandCaptureModel.tsx b/services/madoc-ts/src/frontend/shared/capture-models/EditorShorthandCaptureModel.tsx similarity index 87% rename from services/madoc-ts/src/frontend/shared/caputre-models/EditorShorthandCaptureModel.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/EditorShorthandCaptureModel.tsx index e879844a2..be3e72042 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/EditorShorthandCaptureModel.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/EditorShorthandCaptureModel.tsx @@ -1,18 +1,14 @@ -import { - captureModelShorthand, - generateId, - hydrateCaptureModel, - hydrateCompressedModel, - serialiseCaptureModel, -} from '@capture-models/helpers'; -import { CaptureModel, RevisionRequest } from '@capture-models/types'; import React, { useCallback, useMemo } from 'react'; -import { slotConfig } from '../../../extensions/capture-models/Paragraphs/Paragraphs.slots'; +import { captureModelShorthand } from './helpers/capture-model-shorthand'; +import { generateId } from './helpers/generate-id'; +import { hydrateCaptureModel } from './helpers/hydrate-capture-model'; +import { serialiseCaptureModel } from './helpers/serialise-capture-model'; import { CustomSubmitButton } from './new/components/CustomSubmitButton'; import { EditorSlots, ProfileConfig } from './new/components/EditorSlots'; import { RevisionProviderWithFeatures } from './new/components/RevisionProviderWithFeatures'; import { createRevisionFromDocument } from '../utility/create-revision-from-document'; -import { ButtonRow } from '../navigation/Button'; +import { CaptureModel } from './types/capture-model'; +import { RevisionRequest } from './types/revision-request'; export const EditShorthandCaptureModel: React.FC<{ data: any | undefined; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/EntityInstance.tsx b/services/madoc-ts/src/frontend/shared/capture-models/EntityInstance.tsx similarity index 91% rename from services/madoc-ts/src/frontend/shared/caputre-models/EntityInstance.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/EntityInstance.tsx index 015ecb3fd..ed7b87138 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/EntityInstance.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/EntityInstance.tsx @@ -1,6 +1,6 @@ -import { Revisions } from '@capture-models/editor'; -import { useSelectorStatus } from '@capture-models/plugin-api'; import React, { useCallback, useEffect } from 'react'; +import { Revisions } from './editor/stores/revisions/index'; +import { useSelectorStatus } from './plugin-api/hooks/use-selector-status'; type EntityInstanceProps = { selectorId: string; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/EntityInstanceList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/EntityInstanceList.tsx similarity index 81% rename from services/madoc-ts/src/frontend/shared/caputre-models/EntityInstanceList.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/EntityInstanceList.tsx index faa5f3ad6..5196f49e0 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/EntityInstanceList.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/EntityInstanceList.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import { Revisions, RoundedCard } from '@capture-models/editor'; -import { getLabel } from '@capture-models/helpers'; -import { useRefinement } from '@capture-models/plugin-api'; -import { CaptureModel, EntityInstanceListRefinement } from '@capture-models/types'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; +import { Revisions } from './editor/stores/revisions/index'; +import { getLabel } from './helpers/get-label'; import { NewEntityInstanceButton } from './NewEntityInstanceButton'; import { DocumentPreview } from './DocumentPreview'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; +import { CaptureModel } from './types/capture-model'; +import { EntityInstanceListRefinement } from './types/refinements'; export const EntityInstanceList: React.FC<{ entities: Array; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/EntityTopLevel.tsx b/services/madoc-ts/src/frontend/shared/capture-models/EntityTopLevel.tsx similarity index 90% rename from services/madoc-ts/src/frontend/shared/caputre-models/EntityTopLevel.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/EntityTopLevel.tsx index 40e907701..84c82333c 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/EntityTopLevel.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/EntityTopLevel.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { useRefinement } from '@capture-models/plugin-api'; -import { BaseField, CaptureModel, EntityRefinement } from '@capture-models/types'; import { FieldList } from './FieldList'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; import { RevisionActions } from './RevisionActions'; +import { CaptureModel } from './types/capture-model'; +import { BaseField } from './types/field-types'; +import { EntityRefinement } from './types/refinements'; import { VerboseSelector } from './VerboseSelector'; import { RevisionBreadcrumbs } from './RevisionBreadcrumbs'; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/FieldInstanceList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/FieldInstanceList.tsx similarity index 79% rename from services/madoc-ts/src/frontend/shared/caputre-models/FieldInstanceList.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/FieldInstanceList.tsx index cf2c6ca9e..62ad05101 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/FieldInstanceList.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/FieldInstanceList.tsx @@ -1,8 +1,12 @@ -import { FieldHeader, FieldPreview, Revisions, RoundedCard } from '@capture-models/editor'; -import { useRefinement } from '@capture-models/plugin-api'; -import { BaseField, FieldInstanceListRefinement } from '@capture-models/types'; import React from 'react'; +import { FieldHeader } from './editor/components/FieldHeader/FieldHeader'; +import { FieldPreview } from './editor/components/FieldPreview/FieldPreview'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; +import { Revisions } from './editor/stores/revisions/index'; import { NewFieldButtonInstance } from './NewFieldInstanceButton'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; +import { BaseField } from './types/field-types'; +import { FieldInstanceListRefinement } from './types/refinements'; export const FieldInstanceList: React.FC<{ fields: Array; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/FieldList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/FieldList.tsx similarity index 82% rename from services/madoc-ts/src/frontend/shared/caputre-models/FieldList.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/FieldList.tsx index 84263f568..55f8f9cd2 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/FieldList.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/FieldList.tsx @@ -1,10 +1,13 @@ -import { useRefinement } from '@capture-models/plugin-api'; import React from 'react'; -import { FieldHeader, RoundedCard } from '@capture-models/editor'; -import { BaseField, CaptureModel, FieldListRefinement } from '@capture-models/types'; -import { isEntityList } from '@capture-models/helpers'; +import { FieldHeader } from './editor/components/FieldHeader/FieldHeader'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; import { EntityInstanceList } from './EntityInstanceList'; import { FieldInstanceList } from './FieldInstanceList'; +import { isEntityList } from './helpers/is-entity'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; +import { CaptureModel } from './types/capture-model'; +import { BaseField } from './types/field-types'; +import { FieldListRefinement } from './types/refinements'; export const FieldList: React.FC<{ entity: { property: string; instance: CaptureModel['document'] }; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/NewEntityInstanceButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/NewEntityInstanceButton.tsx similarity index 86% rename from services/madoc-ts/src/frontend/shared/caputre-models/NewEntityInstanceButton.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/NewEntityInstanceButton.tsx index d183c80fe..f60e67bd9 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/NewEntityInstanceButton.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/NewEntityInstanceButton.tsx @@ -1,8 +1,8 @@ import { TinyButton } from '../navigation/Button'; import React from 'react'; -import { Revisions } from '@capture-models/editor'; -import { CaptureModel } from '@capture-models/types'; import styled from 'styled-components'; +import { Revisions } from './editor/stores/revisions/index'; +import { CaptureModel } from './types/capture-model'; const NewInstanceContainer = styled.div` text-align: center; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/NewFieldInstanceButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/NewFieldInstanceButton.tsx similarity index 86% rename from services/madoc-ts/src/frontend/shared/caputre-models/NewFieldInstanceButton.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/NewFieldInstanceButton.tsx index 3e16dd223..617f5b988 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/NewFieldInstanceButton.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/NewFieldInstanceButton.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { Revisions } from '@capture-models/editor'; import styled from 'styled-components'; import { TinyButton } from '../navigation/Button'; -import { BaseField } from '@capture-models/types'; +import { Revisions } from './editor/stores/revisions/index'; +import { BaseField } from './types/field-types'; const NewInstanceContainer = styled.div` text-align: center; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionActions.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionActions.tsx similarity index 95% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionActions.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionActions.tsx index 1d841d09c..7e2b8417b 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionActions.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionActions.tsx @@ -1,6 +1,8 @@ -import { CardButton, CardButtonGroup, Revisions } from '@capture-models/editor'; -import { isEntity } from '@capture-models/helpers'; import React, { useMemo } from 'react'; +import { CardButton } from './editor/components/CardButton/CardButton'; +import { CardButtonGroup } from './editor/components/CardButtonGroup/CardButtonGroup'; +import { Revisions } from './editor/stores/revisions/index'; +import { isEntity } from './helpers/is-entity'; function isTopLevel(path: [string, string, boolean][]): boolean { return path.filter(p => !p[2]).length === 0; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionBreadcrumbs.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionBreadcrumbs.tsx similarity index 97% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionBreadcrumbs.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionBreadcrumbs.tsx index c4e798414..6bfa24558 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionBreadcrumbs.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionBreadcrumbs.tsx @@ -1,6 +1,6 @@ -import { Revisions } from '@capture-models/editor'; import React, { useMemo } from 'react'; import { BreadcrumbDivider, BreadcrumbItem, BreadcrumbList } from '../components/Breadcrumbs'; +import { Revisions } from './editor/stores/revisions/index'; export function useBreads(): { breads: Array<{ id: string; name: string; profile?: string }>; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionChoicePage.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionChoicePage.tsx similarity index 81% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionChoicePage.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionChoicePage.tsx index 39f5cfa1e..20ea6367a 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionChoicePage.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionChoicePage.tsx @@ -1,7 +1,8 @@ -import { Revisions, useChoiceRevisions } from '@capture-models/editor'; +import { useChoiceRevisions } from './editor/hooks/useChoiceRevisions'; +import { Revisions } from './editor/stores/revisions/index'; import { RevisionList } from './RevisionList'; -import { StructureType } from '@capture-models/types'; import React from 'react'; +import { StructureType } from './types/utility'; export const RevisionChoicePage: React.FC<{ model: StructureType<'model'>; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionList.tsx similarity index 85% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionList.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionList.tsx index e17566caf..7c57a9987 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionList.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionList.tsx @@ -1,11 +1,15 @@ -import { BackgroundSplash, CardButton, CardButtonGroup, RoundedCard } from '@capture-models/editor'; -import { isEmptyRevision } from '@capture-models/helpers'; -import { useRefinement } from '@capture-models/plugin-api'; -import { RevisionListRefinement, RevisionRequest, StructureType } from '@capture-models/types'; import React from 'react'; +import { CardButton } from './editor/components/CardButton/CardButton'; +import { CardButtonGroup } from './editor/components/CardButtonGroup/CardButtonGroup'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; +import { isEmptyRevision } from './helpers/is-empty-revision'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; import { SingleRevision } from './SingleRevision'; import { SubmissionList } from './SubmissionList'; import { useCurrentUser } from '../hooks/use-current-user'; +import { RevisionListRefinement } from './types/refinements'; +import { RevisionRequest } from './types/revision-request'; +import { StructureType } from './types/utility'; export type RevisionListProps = { model: StructureType<'model'>; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionNavigation.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionNavigation.tsx similarity index 87% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionNavigation.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionNavigation.tsx index 1861f242b..5961b771e 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionNavigation.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionNavigation.tsx @@ -1,12 +1,15 @@ -import { Revisions, useNavigation } from '@capture-models/editor'; -import { useRefinement } from '@capture-models/plugin-api'; -import { CaptureModel, ChoiceRefinement, RevisionRequest } from '@capture-models/types'; import React, { useMemo } from 'react'; +import { useNavigation } from './editor/hooks/useNavigation'; +import { Revisions } from './editor/stores/revisions/index'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; import { RevisionChoicePage } from './RevisionChoicePage'; import { Choice } from './Choice'; import { RevisionTopLevel } from './RevisionTopLevel'; import { SubmissionList } from './SubmissionList'; import { useCurrentUser } from '../hooks/use-current-user'; +import { CaptureModel } from './types/capture-model'; +import { ChoiceRefinement } from './types/refinements'; +import { RevisionRequest } from './types/revision-request'; export const RevisionNavigation: React.FC<{ structure: CaptureModel['structure']; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionPreview.tsx similarity index 89% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionPreview.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionPreview.tsx index 9d95c4763..b3ca03f2e 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionPreview.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionPreview.tsx @@ -1,6 +1,7 @@ -import { CardButton, CardButtonGroup, RoundedCard } from '@capture-models/editor'; -import { CaptureModel } from '@capture-models/types'; import React, { useEffect, useState } from 'react'; +import { CardButton } from './editor/components/CardButton/CardButton'; +import { CardButtonGroup } from './editor/components/CardButtonGroup/CardButtonGroup'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; import { VerboseEntityPage } from './VerboseEntityPage'; import { useDebouncedCallback } from 'use-debounce'; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionTopLevel.tsx b/services/madoc-ts/src/frontend/shared/capture-models/RevisionTopLevel.tsx similarity index 96% rename from services/madoc-ts/src/frontend/shared/caputre-models/RevisionTopLevel.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/RevisionTopLevel.tsx index 810b305f0..2ed1f33c6 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/RevisionTopLevel.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/RevisionTopLevel.tsx @@ -1,6 +1,6 @@ -import { Revisions } from '@capture-models/editor'; -import { RevisionRequest } from '@capture-models/types'; import React, { useCallback, useState } from 'react'; +import { Revisions } from './editor/stores/revisions/index'; +import { RevisionRequest } from './types/revision-request'; import { VerboseEntityPage } from './VerboseEntityPage'; import { RevisionPreview } from './RevisionPreview'; import { ThankYouPage } from './ThankYouPage'; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/SingleFieldInstance.tsx b/services/madoc-ts/src/frontend/shared/capture-models/SingleFieldInstance.tsx similarity index 81% rename from services/madoc-ts/src/frontend/shared/caputre-models/SingleFieldInstance.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/SingleFieldInstance.tsx index e45d87564..480df1c2c 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/SingleFieldInstance.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/SingleFieldInstance.tsx @@ -1,7 +1,9 @@ -import { FieldWrapper, Revisions, useFieldSelector } from '@capture-models/editor'; -import { BaseField } from '@capture-models/types'; import React from 'react'; import { useDebouncedCallback } from 'use-debounce'; +import { FieldWrapper } from './editor/components/FieldWrapper/FieldWrapper'; +import { Revisions } from './editor/stores/revisions/index'; +import { useFieldSelector } from './editor/stores/selectors/selector-hooks'; +import { BaseField } from './types/field-types'; export const SingleFieldInstance: React.FC<{ field: BaseField; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/SingleRevision.tsx b/services/madoc-ts/src/frontend/shared/capture-models/SingleRevision.tsx similarity index 90% rename from services/madoc-ts/src/frontend/shared/caputre-models/SingleRevision.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/SingleRevision.tsx index 09498217a..8d115958c 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/SingleRevision.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/SingleRevision.tsx @@ -1,7 +1,7 @@ -import { RoundedCard } from '@capture-models/editor'; -import { RevisionRequest } from '@capture-models/types'; import React from 'react'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; import { EntityTopLevel } from './EntityTopLevel'; +import { RevisionRequest } from './types/revision-request'; type SingleRevisionProps = { request: RevisionRequest; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/SubmissionList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/SubmissionList.tsx similarity index 90% rename from services/madoc-ts/src/frontend/shared/caputre-models/SubmissionList.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/SubmissionList.tsx index bf1c195e9..d8dcb0ae6 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/SubmissionList.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/SubmissionList.tsx @@ -1,7 +1,8 @@ -import { Revisions, RoundedCard } from '@capture-models/editor'; -import { RevisionRequest } from '@capture-models/types'; import React, { useMemo, useState } from 'react'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; +import { Revisions } from './editor/stores/revisions/index'; import { SingleRevision } from './SingleRevision'; +import { RevisionRequest } from './types/revision-request'; export const SubmissionList: React.FC<{ submissions: RevisionRequest[] }> = ({ submissions }) => { const myUnpublished = useMemo(() => submissions.filter(rev => rev.revision.status === 'draft'), [submissions]); diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/TabNavigation.tsx b/services/madoc-ts/src/frontend/shared/capture-models/TabNavigation.tsx similarity index 93% rename from services/madoc-ts/src/frontend/shared/caputre-models/TabNavigation.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/TabNavigation.tsx index a0a56ea7a..03b514bd8 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/TabNavigation.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/TabNavigation.tsx @@ -1,5 +1,5 @@ -import { StructureType } from '@capture-models/types'; import React from 'react'; +import { StructureType } from './types/utility'; export const TabNavigation: React.FC<{ currentId: string; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/ThankYouPage.tsx b/services/madoc-ts/src/frontend/shared/capture-models/ThankYouPage.tsx similarity index 65% rename from services/madoc-ts/src/frontend/shared/caputre-models/ThankYouPage.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/ThankYouPage.tsx index 69ad4f09d..321398830 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/ThankYouPage.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/ThankYouPage.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { CardButton, RoundedCard } from '@capture-models/editor'; +import { CardButton } from './editor/components/CardButton/CardButton'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; export const ThankYouPage: React.FC<{ onContinue: () => void }> = ({ onContinue }) => { return ( diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseEntityPage.tsx b/services/madoc-ts/src/frontend/shared/capture-models/VerboseEntityPage.tsx similarity index 93% rename from services/madoc-ts/src/frontend/shared/caputre-models/VerboseEntityPage.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/VerboseEntityPage.tsx index 1bf20599b..7d798679e 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseEntityPage.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/VerboseEntityPage.tsx @@ -1,9 +1,10 @@ -import { BaseField, CaptureModel } from '@capture-models/types'; import React, { useCallback } from 'react'; +import { Revisions } from './editor/stores/revisions/index'; import { EntityTopLevel } from './EntityTopLevel'; +import { isEntity } from './helpers/is-entity'; +import { CaptureModel } from './types/capture-model'; +import { BaseField } from './types/field-types'; import { VerboseFieldPage } from './VerboseFieldPage'; -import { Revisions } from '@capture-models/editor'; -import { isEntity } from '@capture-models/helpers'; function isTopLevel(path: [string, string, boolean][]): boolean { return path.filter(p => !p[2]).length === 0; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseFieldPage.tsx b/services/madoc-ts/src/frontend/shared/capture-models/VerboseFieldPage.tsx similarity index 71% rename from services/madoc-ts/src/frontend/shared/caputre-models/VerboseFieldPage.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/VerboseFieldPage.tsx index 8209a9fa6..735249dd6 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseFieldPage.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/VerboseFieldPage.tsx @@ -1,9 +1,12 @@ -import { FieldInstance, FieldInstanceReadOnly, RoundedCard } from '@capture-models/editor'; -import { useRefinement } from '@capture-models/plugin-api'; -import { BaseField, FieldRefinement } from '@capture-models/types'; import React from 'react'; +import { FieldInstanceReadOnly } from './editor/components/FieldInstanceReadOnly/FieldInstanceReadOnly'; +import { RoundedCard } from './editor/components/RoundedCard/RoundedCard'; +import { FieldInstance } from './editor/connected-components/FieldInstance'; +import { useRefinement } from './plugin-api/hooks/use-refinement'; import { RevisionActions } from './RevisionActions'; import { RevisionBreadcrumbs } from './RevisionBreadcrumbs'; +import { BaseField } from './types/field-types'; +import { FieldRefinement } from './types/refinements'; export const VerboseFieldPage: React.FC<{ field: { property: string; instance: BaseField }; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseSelector.tsx b/services/madoc-ts/src/frontend/shared/capture-models/VerboseSelector.tsx similarity index 85% rename from services/madoc-ts/src/frontend/shared/caputre-models/VerboseSelector.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/VerboseSelector.tsx index c20b87b15..74ddd0da0 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/VerboseSelector.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/VerboseSelector.tsx @@ -1,8 +1,7 @@ -import { RoundedCard } from '@capture-models/editor'; -import { BaseSelector } from '@capture-models/types'; import React from 'react'; import styled from 'styled-components'; import { EntityInstance } from './EntityInstance'; +import { BaseSelector } from './types/selector-types'; type VerboseSelectorProps = { selector: BaseSelector; diff --git a/services/madoc-ts/src/frontend/shared/caputre-models/ViewDocument.tsx b/services/madoc-ts/src/frontend/shared/capture-models/ViewDocument.tsx similarity index 88% rename from services/madoc-ts/src/frontend/shared/caputre-models/ViewDocument.tsx rename to services/madoc-ts/src/frontend/shared/capture-models/ViewDocument.tsx index 0ce17b451..f85b32c4d 100644 --- a/services/madoc-ts/src/frontend/shared/caputre-models/ViewDocument.tsx +++ b/services/madoc-ts/src/frontend/shared/capture-models/ViewDocument.tsx @@ -1,6 +1,6 @@ -import { Revisions } from '@capture-models/editor'; -import { serialiseCaptureModel } from '@capture-models/helpers'; import React, { useEffect } from 'react'; +import { Revisions } from './editor/stores/revisions/index'; +import { serialiseCaptureModel } from './helpers/serialise-capture-model'; import { RevisionTopLevel } from './RevisionTopLevel'; import { useApi } from '../hooks/use-api'; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Breadcrumb.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Breadcrumb.tsx new file mode 100644 index 000000000..ada6c9e5c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Breadcrumb.tsx @@ -0,0 +1,26 @@ +import styled from 'styled-components'; + +export const Breadcrumb = styled.div` + line-height: 1; + display: inline-block; + margin: 0 0; + vertical-align: middle; + font-size: 1em; + cursor: pointer; + &:last-child { + color: #333; + } +`; +export const BreadcrumbSection = styled.div` + display: inline-block; + margin: 0; + padding: 0; +`; +export const BreadcrumbDivider = styled.div` + display: inline-block; + opacity: 0.7; + margin: 0 0.21428571rem 0; + font-size: 0.92857143em; + color: rgba(0, 0, 0, 0.4); + vertical-align: baseline; +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Button.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Button.tsx new file mode 100644 index 000000000..b227562b0 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Button.tsx @@ -0,0 +1,87 @@ +import styled, { css } from 'styled-components'; + +export const Button = styled.button<{ + primary?: boolean; + fluid?: boolean; + alert?: boolean; + selected?: boolean; + size?: 'tiny' | 'mini' | 'small'; +}>` + + background: rgba(5,42,68,0.05); + color: rgba(5,42,68,0.7); + font-size: 1em; + padding: .5em .8em; + border: 1px solid rgba(5,42,68,0.2); + border-radius: 3px; + cursor: pointer; + display: inline-block; + margin: .14285714em; + + &:hover { + background: rgba(5,42,68,0.2); + } + + & > & { + margin-left: 1em; + } + + + &:disabled { + opacity: .5; + } + + ${props => + props.fluid && + css` + width: 100%; + `} + + ${props => + props.primary && + css` + border: 1px solid #034590; + background: #005cc5; + &:hover { + background: #1468cb; + } + color: #fff; + `} + + ${props => + props.selected && + css` + background: rgba(5, 42, 68, 0.2); + &:hover { + border-color: rgba(5, 42, 68, 0.6); + } + `} + + ${props => + props.alert && + css` + background: #ba321c; + border-color: #610000; + &:hover { + background: #a71e08; + } + color: #fff; + `} + + + ${props => + props.size === 'small' && + css` + font-size: 0.9em; + `} + ${props => + props.size === 'tiny' && + css` + font-size: 0.8em; + `} + ${props => + props.size === 'mini' && + css` + font-size: 0.7em; + `} +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Card.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Card.tsx new file mode 100644 index 000000000..f33b37a2a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Card.tsx @@ -0,0 +1,73 @@ +import styled, { css } from 'styled-components'; +import { Button } from './Button'; + +export const Card = styled.div<{ fluid?: boolean }>` + max-width: 100%; + position: relative; + display: flex; + flex-direction: column; + min-height: 0; + background: #fff; + padding: 0; + border: none; + border-radius: 5px; + box-shadow: 0 1px 3px 0 #d4d4d5, 0 0 0 1px #d4d4d5; + + width: 290px; + ${props => + props.fluid && + css` + width: 100%; + `} + + & > ${Button} { + border-radius: 0 0 4px 4px; + border: none; + } +`; + +export const CardHeader = styled.div` + font-size: 1.2em; + line-height: 1.4em; + font-weight: bold; +`; + +export const CardMeta = styled.div` + color: #999; + font-size: 0.9em; + line-height: 1.3em; +`; + +export const CardContent = styled.div<{ extra?: boolean; textAlign?: 'left' | 'right' }>` + flex-grow: 1; + border: none; + background: 0 0; + margin: 0; + padding: 0 1em; + box-shadow: none; + font-size: 1em; + border-radius: 0; + + ${props => + props.textAlign === 'right' && + css` + text-align: right; + `} + + ${props => + props.extra && + css` + max-width: 100%; + min-height: 0; + flex-grow: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + position: static; + background: 0 0; + width: auto; + margin: 0 0; + padding: 0.75em 1em; + top: 0; + left: 0; + box-shadow: none; + `} +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/ConfirmButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/ConfirmButton.tsx new file mode 100644 index 000000000..207c53d36 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/ConfirmButton.tsx @@ -0,0 +1,127 @@ +import React, { useLayoutEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { Button } from './Button'; +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; + +const Portal = styled.div` + position: fixed; + z-index: 100; + background: rgba(0, 0, 0, 0.5); + top: 0; + bottom: 0; + left: 0; + right: 0; +`; + +const ModalContainer = styled.div` + position: fixed; + display: flex; + z-index: 101; + top: 0; + bottom: 0; + left: 0; + right: 0; +`; + +const Modal = styled.div` + background: #fff; + margin: auto; + width: 400px; + min-height: 100px; + padding: 20px; + border-radius: 4px; +`; + +export const ConfirmButton: React.FC<{ message: string; defaultButton?: boolean; onClick: () => void }> = ({ + message, + defaultButton, + onClick, + children, +}) => { + const { t } = useTranslation(); + const portalEl = useRef(); + const [ready, setIsReady] = useState(false); + const containerRef = useRef(); + + useLayoutEffect(() => { + const element = document.createElement('div'); + document.body.appendChild(element); + portalEl.current = element; + + return () => { + portalEl.current = undefined; + document.body.removeChild(element); + }; + }, []); + + return ( + <> + {ready && portalEl.current + ? createPortal( + <> + + { + e.stopPropagation(); + e.preventDefault(); + if (e.target !== containerRef.current) { + return; + } + setIsReady(false); + }} + > + +

{message}

+ {defaultButton ? ( + + ) : ( + { + e.stopPropagation(); + e.preventDefault(); + onClick(); + setIsReady(false); + }} + > + {children} + + )} + +
+
+ , + portalEl.current + ) + : null} + { + e.stopPropagation(); + e.preventDefault(); + setIsReady(true); + }} + > + {children} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Dropdown.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Dropdown.tsx new file mode 100644 index 000000000..2111143b8 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Dropdown.tsx @@ -0,0 +1,88 @@ +import { useCallback, useMemo } from 'react'; +import React from 'react'; +import { Select } from 'react-functional-select'; +import { Tag } from './Tag'; + +export type DropdownOption = { + key?: string; + value: string; + text: string; + label?: string; +}; + +export type DropdownProps = { + id?: string; + placeholder?: string; + fluid?: boolean; + disabled?: boolean; + selection?: boolean; + isClearable?: boolean; + value?: string; + options: Array; + onChange: (value?: string) => void; +}; + +function getValue(option: DropdownOption) { + return option.value; +} + +export function renderOptionLabel(option: DropdownOption) { + return ( + <> + {option.text} + {option.label ? {option.label} : null} + + ); +} + +function getLabel(option: DropdownOption) { + return option.text; +} + +export const Dropdown: React.FC = ({ + id, + placeholder, + isClearable, + value, + disabled, + options, + onChange, +}) => { + const onOptionChange = useCallback((option: DropdownOption | null): void => { + if (option) { + onChange(option ? option.value : undefined); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const initialValue = useMemo(() => { + return options.find(item => item.value === value); + }, [options, value]); + + return ( + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Segment.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Segment.tsx new file mode 100644 index 000000000..2541c34bf --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Segment.tsx @@ -0,0 +1,14 @@ +import styled from 'styled-components'; + +export const Segment = styled.div` + background: #f9fafb; + border-color: rgba(34, 36, 38, 0.15); + border-top: 2px solid lightcoral; + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + max-width: initial; + box-shadow: 0 2px 25px 0 rgba(34, 36, 38, 0.05) inset; + padding: 1em; +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/StyledForm.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/StyledForm.tsx new file mode 100644 index 000000000..962ec3250 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/StyledForm.tsx @@ -0,0 +1,101 @@ +import styled, { css } from 'styled-components'; +import React, { forwardRef, useState } from 'react'; + +const Textarea = React.lazy(() => /* webpackChunkName: "browser" */ import('react-textarea-autosize')); + +export const StyledForm = styled.form` + margin-bottom: 1em; +`; + +export const StyledFormLabel = styled.label` + font-weight: bold; + font-size: 1em; + line-height: 1.6em; + display: block; + > * { + font-weight: normal; + } +`; +export const StyledFormField = styled.div` + display: block; + margin-bottom: 1em; +`; + +export const inputCss = css` + display: block; + width: 100%; + margin: 0; + outline: 0; + font-family: inherit; + -webkit-appearance: none; + border-radius: 3px; + tap-highlight-color: rgba(255, 255, 255, 0); + line-height: 1.2em; + padding: 0.7em 0.9em; + font-size: 1em; + background: #fff; + border: 1px solid rgba(5, 42, 68, 0.2); + color: rgba(0, 0, 0, 0.87); + box-shadow: 0 0 0 0 transparent inset; + &:focus { + border-color: #005cc5; + } +`; + +const _StyledCheckbox = styled.input` + margin: 1em; +`; +export const StyledCheckbox: typeof _StyledCheckbox = forwardRef(function StyledCheckbox(props: any, ref) { + return <_StyledCheckbox ref={ref} type="checkbox" {...props} />; +}) as any; + +export const StyledFormInputElement = styled.input` + ${inputCss} +`; + +export const StyledFormMultilineInputElement = styled(Textarea)` + ${inputCss} +`; + +export const StyledFormInput: React.FC, + HTMLInputElement +> & { multiline?: boolean }> = forwardRef(function StyledFormInput({ multiline, ...props }: any, ref) { + const [value, setValue] = useState(props.value as string); + + if (multiline) { + return ( + + { + setValue(e.currentTarget.value); + if (props.onChange) { + props.onChange(e as any); + } + }} + /> + + ); + } + + return ( + { + setValue(e.currentTarget.value); + if (props.onChange) { + props.onChange(e); + } + }} + /> + ); +}) as any; + +export const StyledFormTextarea = styled.textarea` + ${inputCss} +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Tag.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Tag.tsx new file mode 100644 index 000000000..a02c65249 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/atoms/Tag.tsx @@ -0,0 +1,29 @@ +import styled, { css } from 'styled-components'; + +export const Tag = styled.div<{ size?: 'tiny'; blue?: boolean }>` + font-size: 0.8em; + display: inline-block; + line-height: 1; + margin: 0 0.14285714em; + vertical-align: center; + background-color: #fff; + border: 1px solid #ccc; + padding: 0.5em 0.8em; + color: rgba(0, 0, 0, 0.7); + text-transform: none; + //font-weight: 700; + //border: 0 solid transparent; + border-radius: 3px; + ${props => + props.size === 'tiny' && + css` + font-size: 0.55em; + `} + ${props => + props.blue && + css` + background: #005cc5; + color: #fff; + border-color: #004ea7; + `} +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/bundle.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/bundle.ts new file mode 100644 index 000000000..44d9dffb3 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/bundle.ts @@ -0,0 +1,15 @@ +// Default content types. +import './content-types/CanvasPanel'; +import './content-types/Atlas'; + +// Default input types. +import './input-types/AutocompleteField'; +import './input-types/CheckboxField'; +import './input-types/CheckboxListField'; +import './input-types/DropdownField'; +import './input-types/HTMLField'; +import './input-types/TaggedTextField'; +import './input-types/TextField'; + +// Default selectors +import './selector-types/BoxSelector'; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/AutoSaveFormik/AutoSaveFormik.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/AutoSaveFormik/AutoSaveFormik.tsx new file mode 100644 index 000000000..a27f6b692 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/AutoSaveFormik/AutoSaveFormik.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useFormikContext } from 'formik'; +import { useUnmount } from '../../hooks/useUnmount'; + +export const AutoSaveFormik: React.FC = () => { + const { submitForm, dirty } = useFormikContext(); + + useUnmount(() => { + if (dirty) { + submitForm(); + } + }, [dirty, submitForm]); + + return null; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackBanner/BackBanner.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackBanner/BackBanner.tsx new file mode 100644 index 000000000..7912b4f4f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackBanner/BackBanner.tsx @@ -0,0 +1,8 @@ +import styled from 'styled-components'; +import { getTheme } from '../../themes'; + +export const BackBanner = styled.div` + background: ${props => getTheme(props).colors.mutedPrimary}; + color: ${props => getTheme(props).colors.textOnMutedPrimary}; + padding: 0.3em 30px; +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.stories.tsx new file mode 100644 index 000000000..ceba7e7c9 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.stories.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { BackgroundSplash } from './BackgroundSplash'; +import { defaultTheme } from '../../themes'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { CardButton } from '../CardButton/CardButton'; +import { BackBanner } from '../BackBanner/BackBanner'; + +export default { title: 'Legacy/Background Splash' }; + +const Container: React.FC = ({ children }) => { + return ( + +
+
+
+
- viewer -
+
+
{children}
+
+
+
+ ); +}; + +export const Default: React.FC = () => ( + +
+ + + Move the red box to highlight one person, and enter information about them below. You can repeat this for as + many people as you recognise. + + Create new + +
+
+); + +export const WithDescription: React.FC = () => ( + +
+ + + Move the red box to highlight one person, and enter information about them below. You can repeat this for as + many people as you recognise. + + Create new + +
+
+); + +export const WithInteractiveCards: React.FC = () => ( + +
+ Some text at the top. + + + Enter whatever information you have about this photograph + + + Move the red box to highlight one person, and enter information about them below. You can repeat this for as + many people as you recognise. + + + Tagging photographs will help people to search the collection. Please use tags/suggestions from the + autocomplete list whenever possible. If you cannot find a matching suggestion, then enter your own tags in the + additional tags box. + + +
+
+); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.tsx new file mode 100644 index 000000000..0268e7c11 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/BackgroundSplash/BackgroundSplash.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import styled from 'styled-components'; +import { getTheme } from '../../themes'; + +const Background = styled.div` + background: ${props => getTheme(props).colors.primary}; + padding: 1em 2em 1em 2em; + margin-bottom: 1em; +`; + +const BackgroundHeader = styled.h1` + font-size: ${props => getTheme(props).sizes.headingLg}; + margin-top: 0.3em; + margin-bottom: 0.3em; + font-weight: 500; + color: ${props => getTheme(props).colors.textOnPrimary}; +`; + +const BackgroundDescription = styled.p` + font-size: ${props => getTheme(props).sizes.text}; + color: ${props => getTheme(props).colors.textOnPrimary}; + opacity: 0.9; +`; +const BackgroundBody = styled.div` + margin: 0 20px; +`; + +type BackgroundSplashProps = { + header: string; + description?: string; +}; + +export const BackgroundSplash: React.FC = ({ header, description, children }) => { + return ( + <> + + {header} + {description ? {description} : null} + + {children} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.stories.tsx new file mode 100644 index 000000000..d974f056b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.stories.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Card, CardContent } from '../../atoms/Card'; +import { CaptureModelList } from './CaptureModelList'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; + +export default { title: 'Capture model editor components/Capture Model List' }; + +export const Simple: React.FC = () => { + const models = [ + { label: 'Model A', id: '1' }, + { label: 'Model B', id: '2' }, + ]; + + return ( + + + + console.log('onclick', e)} + onDelete={e => console.log('remove', e)} + /> + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.tsx new file mode 100644 index 000000000..5e1db9d29 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CaptureModelList/CaptureModelList.tsx @@ -0,0 +1,31 @@ +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import React, { useMemo } from 'react'; +import { Heading } from '../Heading/Heading'; +import { useTranslation } from 'react-i18next'; + +type Props = { + captureModels: Array<{ id: string; label: string }>; + onDelete: (model: string) => void; + onClick: (model: string) => void; +}; + +export const CaptureModelList: React.FC = ({ captureModels, onClick, onDelete }) => { + const { t } = useTranslation(); + const orderedList = useMemo(() => { + return captureModels.sort((a, b) => { + return (a.label || t('Untitled')).localeCompare(b.label || t('Untitled')); + }); + }, [t, captureModels]); + + return ( + <> +
+ {orderedList.map(model => ( + onClick(model.id)}> + {model.label} + + ))} +
+ + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.stories.tsx new file mode 100644 index 000000000..3ed02273b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.stories.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; +import { BackgroundSplash } from '../BackgroundSplash/BackgroundSplash'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { CardButton } from './CardButton'; + +export default { title: 'Legacy/Card button' }; + +export const Simple: React.FC = () => { + return ( + + + + Testing card button + + Testing card button + + Testing card button + + + + Testing card button + + + + + Testing card button + + + + + Testing card button + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.tsx new file mode 100644 index 000000000..86378a2e1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButton/CardButton.tsx @@ -0,0 +1,54 @@ +import styled from 'styled-components'; +import { getTheme } from '../../themes'; + +export const CardButton = styled.button<{ size?: 'large' | 'medium' | 'small'; shadow?: boolean; inline?: boolean }>` + box-sizing: border-box; + background: ${props => getTheme(props).colors.primary}; + color: ${props => getTheme(props).colors.textOnPrimary}; + margin-bottom: ${props => { + const size = getTheme(props).card[props.size === 'large' ? 'large' : 'small']; + if (size && size.margin) { + return size.margin; + } + return 0; + }}; + padding: ${props => (props.size === 'large' ? '0.75em 1.2em' : '.6em 1.2em')}; + width: ${props => (props.inline ? 'auto' : '100%')}; + box-shadow: ${props => (props.shadow ? getTheme(props).card.shadow : 'none')}; + border-radius: 4px; + font-weight: 500; + border: none; + text-align: center; + font-size: ${props => { + switch (props.size) { + case 'large': + return getTheme(props).sizes.buttonLg; + case 'medium': + return getTheme(props).sizes.buttonMd; + case 'small': + return getTheme(props).sizes.buttonSm; + default: + return getTheme(props).sizes.buttonMd; + } + }}; + cursor: pointer; + transition: transform 0.2s; + box-shadow: ${props => getTheme(props).card.shadow}; + &:disabled { + opacity: 0.5; + cursor: initial; + } + &:hover { + transform: translateY(-2px); + } + &:active { + transform: translateY(1px); + } + &:focus { + outline: 2px solid rgba(255, 255, 255, 0.5); + outline-offset: -4px; + &:active { + outline: none; + } + } +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.stories.tsx new file mode 100644 index 000000000..bd6756056 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.stories.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; +import { BackgroundSplash } from '../BackgroundSplash/BackgroundSplash'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { CardButtonGroup } from './CardButtonGroup'; +import { CardButton } from '../CardButton/CardButton'; + +export default { title: 'Legacy/Card button group' }; + +export const Simple: React.FC = () => { + return ( + + + + + Use as template + Suggest edit + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.tsx new file mode 100644 index 000000000..83c3779d8 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardButtonGroup/CardButtonGroup.tsx @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +export const CardButtonGroup = styled.div<{ spacing?: string }>` + display: flex; + flex-direction: row; + & > * + * { + margin-left: ${props => (props.spacing ? props.spacing : '10px')}; + } +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.stories.tsx new file mode 100644 index 000000000..24932ab2c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.stories.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { CardDropdown } from './CardDropdown'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; +import { BackgroundSplash } from '../BackgroundSplash/BackgroundSplash'; + +export default { title: 'Legacy/Card dropdown' }; + +export const Simple: React.FC = () => { + return ( + + + + This is a card dropdown. + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.tsx new file mode 100644 index 000000000..87385ff27 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/CardDropdown/CardDropdown.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { RoundedCard, RoundedCardProps } from '../RoundedCard/RoundedCard'; +import styled from 'styled-components'; + +type CardDropdownProps = RoundedCardProps & { + openInitially?: boolean; + cards: Array; + hideCount?: boolean; +}; + +const CardDropdownContainer = styled.div``; + +const CardListWrapper = styled.div<{ isOpen: boolean }>` + padding: 0 1em; + max-height: ${props => (props.isOpen ? '500px' : '0')}; + transition: max-height 0.3s; + overflow: hidden; + margin-top: -1em; +`; + +export const CardDropdown: React.FC = ({ + cards, + hideCount, + onClick, + openInitially = false, + ...props +}) => { + const [isOpen, setIsOpen] = useState(openInitially); + return ( + + { + setIsOpen(o => !o); + if (onClick) { + onClick(); + } + }} + {...props} + /> + + {cards.map((card, key) => ( + + ))} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.stories.tsx new file mode 100644 index 000000000..3e6c2b47a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.stories.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { Button } from '../../atoms/Button'; +import { CaptureModelProvider } from '../../core/capture-model-provider'; +import { useNavigation } from '../../hooks/useNavigation'; +import { defaultTheme } from '../../themes'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { Choice } from './Choice'; + +const simple = require('../../../../../../../fixtures/simple.json'); + +const withSimpleCaptureModel = (Component: React.FC): React.FC => () => ( + + + + + +); + +export default { title: 'Legacy/Choice' }; + +export const StaticExample: React.FC = () => ( + +
+ console.log('choice', chose)} + /> +
+
+); + +export const UsingHook: React.FC = withSimpleCaptureModel(() => { + const [currentView, { pop, push, idStack }] = useNavigation(); + + if (currentView.type !== 'choice') { + return ( + +

+ We are on {currentView.label} +

+ +
+ ); + } + + return ; +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.tsx new file mode 100644 index 000000000..0b44c6b62 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Choice/Choice.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { StructureType } from '../../../types/utility'; +import { BackgroundSplash } from '../BackgroundSplash/BackgroundSplash'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; + +export const Choice: React.FC<{ + showBackButton?: boolean; + onBackButton?: () => void; + onChoice: (id: string) => void; + choice: StructureType<'choice'>; +}> = ({ choice, onChoice, showBackButton, onBackButton, children }) => { + return ( + <> + {showBackButton && onBackButton ? : null} + + {choice.items.map((item, idx) => ( + onChoice(item.id)}> + {item.description} + + ))} + {children} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.stories.tsx new file mode 100644 index 000000000..db165209a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.stories.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { StructureType } from '../../../types/utility'; +import { StructureStore } from '../../stores/structure/structure-store'; +import { useFocusedStructureEditor } from '../../stores/structure/use-focused-structure-editor'; +import { ChoiceEditor } from './ChoiceEditor'; + +export default { title: 'Capture model editor components/Choice Editor' }; + +const model: CaptureModel = require('../../../../../../../fixtures/simple.json'); + +const SimpleInner: React.FC = () => { + const focus = StructureStore.useStoreActions(act => act.focus); + const current = StructureStore.useStoreState(state => state.focus.structure); + const currentPath = StructureStore.useStoreState(state => state.focus.index); + const { + setLabel, + setDescription, + setProfile, + addStructureToChoice, + removeStructureFromChoice, + reorderChoices, + } = useFocusedStructureEditor(); + + return ( + } + onAddChoice={addStructureToChoice} + onAddModel={addStructureToChoice} + pushFocus={focus.pushFocus} + popFocus={focus.popFocus} + onRemove={removeStructureFromChoice} + initialPath={currentPath} + /> + ); +}; + +export const Simple: React.FC = () => ( + +
+ +
+
+); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.tsx new file mode 100644 index 000000000..f3330fbcc --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceEditor/ChoiceEditor.tsx @@ -0,0 +1,152 @@ +import React, { useEffect } from 'react'; +import { StructureType } from '../../../types/utility'; +import { Button } from '../../atoms/Button'; +import { Grid, GridColumn } from '../../atoms/Grid'; +import { Card, CardContent, CardHeader, CardMeta } from '../../atoms/Card'; +import { NewChoiceForm } from '../NewChoiceForm/NewChoiceForm'; +import { NewModelForm } from '../NewModelForm/NewModelForm'; +import { useMiniRouter } from '../../hooks/useMiniRouter'; +import { StructureMetadataEditor } from '../StructureMetadataEditor/StructureMetadataEditor'; +import { useTranslation } from 'react-i18next'; + +type Props = { + choice: StructureType<'choice'>; + initialPath?: number[]; + setPath?: (ids: number[]) => void; + setLabel: (value: string) => void; + setProfile: (profile: string[]) => void; + setDescription: (value: string) => void; + onAddChoice: (choice: StructureType<'choice'>) => void; + onAddModel: (model: StructureType<'model'>) => void; + reorderChoices: (startIndex: number, endIndex: number) => void; + onRemove: (id: number) => void; + pushFocus: (idx: number) => void; + popFocus: (payload?: any) => void; +}; + +export const ChoiceList = React.lazy(() => import(/* webpackChunkName: "choice-list" */ '../ChoiceList/ChoiceList')); + +export const ChoiceEditor: React.FC = ({ + choice, + onAddChoice, + onAddModel, + onRemove, + setLabel, + setDescription, + initialPath = [], + setPath, + setProfile, + reorderChoices, + pushFocus, + popFocus, +}) => { + const { t } = useTranslation(); + const [route, router] = useMiniRouter(['list', 'newChoice', 'newModel'], 'list'); + + useEffect(() => { + router.list(); + }, [choice, router]); + + if (choice.type !== 'choice') { + return null; + } + + return ( + + + + {initialPath.length ? ( + + + + ) : null} + + {choice.label} + {t('Choice')} + {/**/} + {/*{subtree.description ? {subtree.description} : null}*/} + + + + + { + setLabel(values.label); + setDescription(values.description || ''); + if (values.profile) { + setProfile(values.profile); + } + }} + /> + + + {t('loading...')}}> + + + + {route === 'list' ? ( + <> + + + + + + + + + + + + ) : route === 'newChoice' ? ( + <> + + + + + + + {t('Create new choice')} + + + { + // Add choice. + onAddChoice(newChoice); + // Navigate back. + router.list(); + }} + /> + + + ) : route === 'newModel' ? ( + <> + + + + + + + {t('Create new model')} + + + { + // Add model. + onAddModel(newModel); + // Navigate back. + router.list(); + }} + /> + + + ) : null} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceList/ChoiceList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceList/ChoiceList.tsx new file mode 100644 index 000000000..3ca0fccba --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChoiceList/ChoiceList.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { StructureType } from '../../../types/utility'; +import { Button } from '../../atoms/Button'; +import { List, ListContent, ListDescription, ListHeader, ListItem } from '../../atoms/List'; +import { Folder } from '@styled-icons/entypo/Folder'; +import { List as ListIcon } from '@styled-icons/entypo/List'; +import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; +import { Tag } from '../../atoms/Tag'; +import { useTranslation } from 'react-i18next'; + +type Props = { + choice: StructureType<'choice'>; + pushFocus: (key: number) => void; + onRemove: (key: number) => void; + onReorder: (startIndex: number, endIndex: number) => void; +}; + +export const ChoiceList: React.FC = ({ onRemove, choice, onReorder, pushFocus }) => { + const { t } = useTranslation(); + const onDragEnd = (result: DropResult) => { + if (!result.destination) { + return; + } + + onReorder(result.source.index, result.destination.index); + }; + + return ( + + + {provided => ( +
+ + {choice.items.map((item, key) => ( + + {innerProvided => ( + { + pushFocus(key); + }} + > + {item.type === 'model' ? : } + + {item.label} + {item.description ? {item.description} : null} + + + + {item.type} + + + )} + + ))} + {provided.placeholder} + +
+ )} +
+
+ ); +}; + +export default ChoiceList; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.stories.tsx new file mode 100644 index 000000000..77fcfa18d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.stories.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { PluginProvider } from '../../../plugin-api/context'; +import { ChooseField } from './ChooseField'; +// Import some fields. +import '../../input-types/TextField/index'; +import '../../input-types/HTMLField'; + +export default { title: 'Capture model editor components/Choose field' }; + +export const Simple: React.FC = () => { + return ( + + console.log(choice)} /> + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.tsx new file mode 100644 index 000000000..c65a1437c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseField/ChooseField.tsx @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { BaseField } from '../../../types/field-types'; +import { Button } from '../../atoms/Button'; +import { Card, CardContent } from '../../atoms/Card'; +import { Grid, GridColumn, GridRow } from '../../atoms/Grid'; +import { useTranslation } from 'react-i18next'; + +export const ChooseField: React.FC<{ + handleChoice: (choice: BaseField) => void; +}> = ({ handleChoice }) => { + const { t } = useTranslation(); + const { fields } = useContext(PluginContext); + + return ( +
+

Choose field

+ + + {Object.values(fields).map(field => + field ? ( + + + +

{field.label}

+

{field.description}

+
+ +
+
+ ) : null + )} +
+
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.stories.tsx new file mode 100644 index 000000000..149294897 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.stories.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { ChooseFieldButton } from './ChooseFieldButton'; +import { Card, CardContent } from '../../atoms/Card'; + +export default { title: 'Capture model editor components/Choose Field Button' }; + +export const Simple: React.FC = () => ( + + + console.log(t)} /> + + +); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.tsx new file mode 100644 index 000000000..9dedb51f1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseFieldButton/ChooseFieldButton.tsx @@ -0,0 +1,44 @@ +import React, { useContext, useState } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { Dropdown } from '../../atoms/Dropdown'; + +type Props = { + onChange: (term?: string) => void; + fieldType?: string; +}; + +// - Choose type select +// - Choose label +// - Choose term / JSON property + +export const ChooseFieldButton: React.FC = ({ onChange, fieldType }) => { + const { fields } = useContext(PluginContext); + const [value, setValue] = useState(fieldType || fields[0]?.type); + + return ( + { + onChange(v); + setValue(v); + }} + options={ + Object.values(fields) + .map(field => + field + ? { + key: field.type, + value: field.type, + text: field.label, + label: field.type, + } + : null + ) + .filter(e => e !== null) as any[] + } + /> + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.stories.tsx new file mode 100644 index 000000000..cc75ad529 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.stories.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { PluginProvider } from '../../../plugin-api/context'; +import { ChooseSelector } from './ChooseSelector'; +// Import some fields. +import '../../selector-types/BoxSelector/index'; + +export default { title: 'Capture model editor components/Choose selector' }; + +export const Simple: React.FC = () => { + return ( + + console.log(choice)} /> + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.tsx new file mode 100644 index 000000000..1effef62b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelector/ChooseSelector.tsx @@ -0,0 +1,43 @@ +import React, { useContext } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { SelectorSpecification } from '../../../types/selector-types'; +import { Button } from '../../atoms/Button'; +import { Card, CardContent, CardHeader } from '../../atoms/Card'; +import { Tag } from '../../atoms/Tag'; +import { useTranslation } from 'react-i18next'; + +export const ChooseSelector: React.FC<{ + handleChoice: (choice: SelectorSpecification) => void; +}> = ({ handleChoice }) => { + const { t } = useTranslation(); + const { selectors } = useContext(PluginContext); + + return ( +
+

{t('Choose selector')}

+
    + {Object.values(selectors).map(field => + field ? ( + + + {field.label} +

    {field.description}

    +
    + + {t('Supported content types')} +
    + {field.supportedContentTypes.map(type => ( + {type} + ))} +
    +
    + +
    + ) : null + )} +
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.stories.tsx new file mode 100644 index 000000000..a52fb9b2f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.stories.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { ChooseSelectorButton } from './ChooseSelectorButton'; + +export default { title: 'Capture model editor components/Choose Selector Button' }; + +export const Simple: React.FC = () => console.log(t)} />; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.tsx new file mode 100644 index 000000000..449c6200a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ChooseSelectorButton/ChooseSelectorButton.tsx @@ -0,0 +1,51 @@ +import React, { useContext, useState } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { Dropdown, DropdownOption } from '../../atoms/Dropdown'; +// Pull in the build-in selectors. +import '../../selector-types/BoxSelector/index'; +import { useTranslation } from 'react-i18next'; + +type Props = { + value?: string; + onChange: (term?: string) => void; +}; + +export const ChooseSelectorButton: React.FC = ({ value: initialValue, onChange }) => { + const { t } = useTranslation(); + const { selectors } = useContext(PluginContext); + const [value, setValue] = useState(initialValue); + + return ( +
+ { + onChange(val); + setValue(val); + }} + options={[ + { + key: '', + value: '', + text: 'none', + }, + ...(Object.values(selectors) + .map(field => + field + ? { + key: field.type, + value: field.type, + text: field.label, + label: field.type, + } + : null + ) + .filter(e => e !== null) as DropdownOption[]), + ]} + /> +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.stories.tsx new file mode 100644 index 000000000..1ad565768 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.stories.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { DocumentCreator } from './DocumentCreator'; + +export default { title: 'Capture model editor components/Document Creator' }; + +export const Simple: React.FC = () => ; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.tsx new file mode 100644 index 000000000..15bc2813c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentCreator/DocumentCreator.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +type Props = {}; + +export const DocumentCreator: React.FC = () => { + return
DocumentCreator
; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.stories.tsx new file mode 100644 index 000000000..f5848431d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.stories.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { DocumentEditor } from './DocumentEditor'; +import { DocumentStore } from '../../stores/document/document-store'; + +const model: CaptureModel = require('../../../../../../../fixtures/simple.json'); + +export default { + title: 'Capture model editor components/Document editor', + component: DocumentEditor, +}; + +const Inner = () => { + const state = DocumentStore.useStoreState(s => ({ + subtree: s.subtree, + subtreePath: s.subtreePath, + subtreeFields: s.subtreeFields, + })); + const actions = DocumentStore.useStoreActions(a => ({ + setLabel: a.setLabel, + setDescription: a.setDescription, + popSubtree: a.popSubtree, + pushSubtree: a.pushSubtree, + selectField: a.selectField, + deselectField: a.deselectField, + addField: a.addField, + setSelector: a.setSelector, + setAllowMultiple: a.setAllowMultiple, + setLabelledBy: a.setLabelledBy, + setPluralLabel: a.setPluralLabel, + })); + + return ( +
+ +
+ ); +}; + +export const Simple: React.FC = () => { + return ( + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.tsx new file mode 100644 index 000000000..5006384bb --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentEditor/DocumentEditor.tsx @@ -0,0 +1,383 @@ +import copy from 'fast-copy'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { CaptureModel } from '../../../types/capture-model'; +import { BaseField } from '../../../types/field-types'; +import { BaseSelector, SelectorTypeMap } from '../../../types/selector-types'; +import { Button } from '../../atoms/Button'; +import { Card, CardHeader, CardContent, CardMeta } from '../../atoms/Card'; +import { Grid, GridColumn } from '../../atoms/Grid'; +import { List, ListItem, ListHeader, ListContent, ListDescription } from '../../atoms/List'; +import { Dropdown } from '../../atoms/Dropdown'; +import { useMiniRouter } from '../../hooks/useMiniRouter'; +import { ChooseSelectorButton } from '../ChooseSelectorButton/ChooseSelectorButton'; +import { NewDocumentForm } from '../NewDocumentForm/NewDocumentForm'; +import { NewFieldForm } from '../NewFieldForm/NewFieldForm'; +import { SubtreeBreadcrumb } from '../SubtreeBreadcrumb/SubtreeBreadcrumb'; +import { Box } from '@styled-icons/entypo/Box'; +import { Edit } from '@styled-icons/entypo/Edit'; +import { Tag } from '../../atoms/Tag'; +import { StyledForm, StyledFormField, StyledFormInput, StyledFormLabel, StyledCheckbox } from '../../atoms/StyledForm'; +import { CardButton } from '../CardButton/CardButton'; +import { CardButtonGroup } from '../CardButtonGroup/CardButtonGroup'; +import { ConfirmButton } from '../../atoms/ConfirmButton'; +import { useTranslation } from 'react-i18next'; + +export type DocumentEditorProps = { + setLabel: (label: string) => void; + setDescription: (label: string) => void; + setAllowMultiple: (allow: boolean) => void; + setLabelledBy: (label: string) => void; + setPluralLabel: (label: string) => void; + selectField: (term: string) => void; + popSubtree: (payload?: { count: number }) => void; + pushSubtree: (term: string) => void; + deselectField: (payload?: any) => void; + addField: (payload?: any) => void; + selectedField?: string | null; + subtreePath: string[]; + subtree: CaptureModel['document']; + subtreeFields: Array<{ term: string; value: CaptureModel['document'] | BaseField }>; + setSelector: (payload: { term?: string; selector: BaseSelector | undefined }) => void; + onDelete?: () => void; +}; + +export const DocumentEditor: React.FC = ({ + setLabel, + setDescription, + selectField, + deselectField, + setAllowMultiple, + setPluralLabel, + setLabelledBy, + addField, + popSubtree, + selectedField, + subtreePath, + subtree, + subtreeFields, + pushSubtree, + setSelector, + onDelete, +}) => { + const { t } = useTranslation(); + const [route, router] = useMiniRouter(['list', 'newField', 'newDocument'], 'list'); + const { selectors } = useContext(PluginContext); + const isRoot = subtreePath.length === 0; + const [metadataOpen, setMetadataOpen] = useState(false); + const [customLabelledBy, setCustomLabelBy] = useState(false); + + const subtreeFieldOptions = useMemo( + () => [ + { + key: '', + value: '', + text: 'none', + }, + ...subtreeFields.map(item => ({ + key: item.term, + value: item.term, + text: item.value.label === item.term ? item.term : `${item.value.label} (${item.term})`, + })), + ], + [subtreeFields] + ); + + useEffect(() => { + if (subtree.labelledBy && !subtreeFieldOptions.find(opt => opt.value === subtree.labelledBy) && !customLabelledBy) { + setCustomLabelBy(true); + } + }, [subtreeFieldOptions, subtree.labelledBy, customLabelledBy]); + + useEffect(() => { + if (route !== 'list') { + deselectField(); + } + }, [deselectField, route]); + + return ( +
+ + {route === 'list' ? ( + <> + + + {subtreePath.length ? ( + + + + ) : null} + + + + + {subtree.description ? {subtree.description} : null} + + + + {metadataOpen ? ( + + + + + {t('Label')} + setLabel(e.currentTarget.value)} + /> + + + + + {t('Description')} + setDescription(e.currentTarget.value)} + /> + + + {!isRoot && ( + <> + + + setAllowMultiple(e.currentTarget.checked)} + /> + {t('Allow multiple instances')} + + + {subtree.allowMultiple ? ( + + + {t('Plural label (used when referring to lists of this document)')} + setPluralLabel(e.currentTarget.value)} + /> + + + ) : null} + + )} + {customLabelledBy ? ( + + + {t('Entity labelled by property')} + setLabelledBy(e.currentTarget.value)} + /> + + + ) : ( + <> + + + {t('Entity labelled by property')} + { + setLabelledBy(val || ''); + }} + options={subtreeFieldOptions} + /> + + + + )} + + + setCustomLabelBy(e.currentTarget.checked)} + /> + {t('Advanced labelled by property')} + + + + + {t('Choose selector (optional)')} + { + if (t) { + const selector = selectors[t as keyof SelectorTypeMap]; + if (selector) { + setSelector({ + selector: { + type: selector.type, + state: copy(selector.defaultState), + } as any, + }); + } + } else { + setSelector({ selector: undefined }); + } + }} + /> + + + + + + ) : ( + + + + )} + + + {subtreeFields.map(({ value: item, term }, key) => ( + { + if (item.type === 'entity') { + pushSubtree(term); + } else { + selectField(term); + } + }} + > + {item.type === 'entity' ? : } + + {item.label === term ? term : `${item.label} (${term})`} + {item.description ? {item.description} : null} + + + {item.type} + + + ))} + + + + + + {t('Add field')} + + + {t('Add nested entity')} + + + + {onDelete ? ( + + + onDelete()} + > + + + + + ) : null} + + ) : route === 'newField' ? ( + <> + + + + + + + {t('Create new field')} + + + + + { + // Use term to get plugin. + addField({ + term: newField.term, + field: { + type: newField.fieldType, + label: newField.term, + value: newField.field.defaultValue, + selector: newField.selector + ? { + type: newField.selector.type, + state: copy(newField.selector.defaultState), + } + : undefined, + ...newField.field.defaultProps, + }, + select: true, + }); + router.list(); + }} + /> + + + ) : ( + <> + + + + + + + {t('Create new document')} + + + + + + { + // Use term to get plugin. + addField({ + term: newDoc.term, + field: { + type: 'entity', + label: newDoc.term, + selector: newDoc.selector + ? { + type: newDoc.selector.type, + state: copy(newDoc.selector.defaultState), + } + : undefined, + properties: {}, + }, + select: true, + }); + router.list(); + }} + /> + + + )} + +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentPreview/DocumentPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentPreview/DocumentPreview.tsx new file mode 100644 index 000000000..974ab11a1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/DocumentPreview/DocumentPreview.tsx @@ -0,0 +1,43 @@ +import React, { ComponentClass, FunctionComponent, useMemo } from 'react'; +import { filterRevises } from '../../../helpers/filter-revises'; +import { isEntity } from '../../../helpers/is-entity'; +import { CaptureModel } from '../../../types/capture-model'; +import { BaseField } from '../../../types/field-types'; +import { FieldPreview } from '../FieldPreview/FieldPreview'; + +export const DocumentPreview: React.FC<{ + entity: CaptureModel['document'] | BaseField; + as?: FunctionComponent | ComponentClass | string; +}> = ({ entity, as, children }) => { + const filteredLabeledBy = useMemo(() => { + if (isEntity(entity)) { + if (entity.labelledBy) { + const properties = filterRevises(entity.properties[entity.labelledBy]); + + if (!properties || properties.length === 0) { + return undefined; + } + + return properties as Array; + } + } + + return undefined; + }, [entity]); + + if (isEntity(entity)) { + if (!filteredLabeledBy) { + return <>{children}; + } + + return ( + <> + {filteredLabeledBy.map(labelFieldOrEntity => ( + + ))} + + ); + } + + return ; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/EditorContext/EditorContext.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/EditorContext/EditorContext.tsx new file mode 100644 index 000000000..655cb2a71 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/EditorContext/EditorContext.tsx @@ -0,0 +1,68 @@ +import React, { useEffect, useRef } from 'react'; +import { createContext } from '../../../helpers/create-context'; +import { CaptureModel } from '../../../types/capture-model'; +import { DocumentStore } from '../../stores/document/document-store'; +import { StructureStore } from '../../stores/structure/structure-store'; +import { useDebouncedCallback } from 'use-debounce'; + +export const [useCaptureModel, CaptureModelProvider] = createContext(); + +const ChangeObserver: React.FC<{ + onDocumentChange: (doc: CaptureModel['document']) => void; + onStructureChange: (structure: CaptureModel['structure']) => void; +}> = props => { + const doc = DocumentStore.useStore(); + const struct = StructureStore.useStore(); + + const lastStructure = useRef(); + const lastDocument = useRef(); + + const [onDocumentChange] = useDebouncedCallback(props.onDocumentChange, 300, { + trailing: true, + }); + const [onStructureChange] = useDebouncedCallback(props.onStructureChange, 300, { + trailing: true, + }); + + useEffect(() => { + return doc.subscribe(() => { + const state = doc.getState(); + + if (state.document !== lastDocument.current) { + lastDocument.current = state.document; + onDocumentChange(state.document); + } + }); + }); + useEffect(() => { + return struct.subscribe(() => { + const state = struct.getState(); + + if (state.structure !== lastStructure.current) { + lastStructure.current = state.structure; + onStructureChange(state.structure); + } + }); + }); + + return null; +}; + +export const EditorContext: React.FC<{ + captureModel: CaptureModel; + onDocumentChange?: (doc: CaptureModel['document']) => void; + onStructureChange?: (structure: CaptureModel['structure']) => void; +}> = ({ captureModel, onDocumentChange, onStructureChange, children }) => { + return ( + + + + {onDocumentChange && onStructureChange ? ( + + ) : null} + {children} + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldEditor/FieldEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldEditor/FieldEditor.tsx new file mode 100644 index 000000000..4ae1b17da --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldEditor/FieldEditor.tsx @@ -0,0 +1,202 @@ +import copy from 'fast-copy'; +import React, { useContext, useState } from 'react'; +import { Field, Form, Formik } from 'formik'; +import { generateId } from '../../../helpers/generate-id'; +import { PluginContext } from '../../../plugin-api/context'; +import { BaseField } from '../../../types/field-types'; +import { BaseSelector, SelectorTypeMap } from '../../../types/selector-types'; +import { Button } from '../../atoms/Button'; +import { Segment } from '../../atoms/Segment'; +import { ConfirmButton } from '../../atoms/ConfirmButton'; +import { ChooseSelectorButton } from '../ChooseSelectorButton/ChooseSelectorButton'; +import { ChooseFieldButton } from '../ChooseFieldButton/ChooseFieldButton'; +import { FormPreview } from '../FormPreview/FormPreview'; +import { + StyledCheckbox, + StyledFormField, + StyledFormInputElement, + StyledFormLabel, + StyledFormTextarea, +} from '../../atoms/StyledForm'; +import { AutoSaveFormik } from '../AutoSaveFormik/AutoSaveFormik'; +import { MultiDropdown } from '../../atoms/MultiDropdown'; +import { useTranslation } from 'react-i18next'; + +export type FieldSource = { + id: string; + name: string; + description?: string; + defaultProps?: any; + fieldTypes: string[]; +}; + +export const FieldEditor: React.FC<{ + field: BaseField; + term?: string; + onSubmit: (newProps: BaseField, term?: string) => void; + onDelete?: (term?: string) => void; + onChangeFieldType?: (type: string, defaults: any, term?: string) => void; + setSaveHandler?: (handler: () => void) => void; + sourceTypes?: Array; +}> = ({ onSubmit, onDelete, onChangeFieldType, sourceTypes, field: props, term }) => { + const { t } = useTranslation(); + const ctx = useContext(PluginContext); + const { fields, selectors } = useContext(PluginContext); + const [selector, setSelector] = useState(props.selector); + const field = ctx.fields[props.type]; + const [defaultValue, setDefaultValue] = useState(props.value); + + if (!field) { + throw new Error(`Plugin ${props.type} does not exist`); + } + + const editorProps = field.mapEditorProps ? field.mapEditorProps(props) : props; + const editor = React.createElement(field.Editor, editorProps as any); + const dataSources = (sourceTypes || []).filter(sourceType => { + return sourceType.fieldTypes.indexOf(props.type) !== -1; + }); + const [dataSource, setDataSource] = useState(props.dataSources || []); + + return ( + + { + if (field.onEditorSubmit) { + onSubmit( + field.onEditorSubmit({ + ...newProps, + type: props.type, + selector, + dataSources: dataSource && dataSource.length ? dataSource : undefined, + value: defaultValue, + }), + term + ); + } else { + onSubmit( + { + ...newProps, + type: props.type, + selector, + dataSources: dataSource && dataSource.length ? dataSource : undefined, + value: defaultValue, + }, + term + ); + } + }} + > +
+ + +

{t('Preview')}

+ +
+ + + {t('Label')} + + + + + + {t('Description')} + + + + {onChangeFieldType ? ( + + + {t('Field type')} + + type && fields[type] ? onChangeFieldType(t as any, (fields[type] as any).defaultProps, term) : null + } + /> + + + ) : null} + {dataSources ? ( + + + {t('Dynamic data sources')} + { + setDataSource(val || []); + }} + options={dataSources.map(source => { + return { + key: source.id, + text: source.name || '', + value: source.id, + }; + })} + /> + + + ) : null} + + + {t('Choose selector (optional)')} + { + if (v) { + const chosenSelector = selectors[v as keyof SelectorTypeMap]; + if (chosenSelector) { + setSelector({ + id: generateId(), + type: chosenSelector.type, + state: copy(chosenSelector.defaultState), + }); + } + } else { + setSelector(undefined); + } + }} + /> + + + + + + {t('Allow multiple instances')} + + + + + {t('Plural label (used when referring to lists of this document)')} + + + + {editor} +
+ + {onDelete ? ( + onDelete(term)}> + + + ) : null} +
+ +
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldHeader/FieldHeader.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldHeader/FieldHeader.tsx new file mode 100644 index 000000000..621a0560c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldHeader/FieldHeader.tsx @@ -0,0 +1,143 @@ +import React, { useCallback, useState } from 'react'; +import styled, { css } from 'styled-components'; +import { Tag } from '../../atoms/Tag'; +import { useTranslation } from 'react-i18next'; + +type FieldHeaderProps = { + labelFor?: string; + label: string; + selectorLabel?: string; + showTerm?: boolean; + term?: string; + description?: string; + selectorComponent?: any; + onSelectorClose?: () => void; + onSelectorOpen?: () => void; +}; + +export const FieldHeaderWrapper = styled.div` + font-family: -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', + 'Helvetica Neue', 'Icons16', sans-serif; + line-height: 1.8em; + margin: 0.5em 0; +`; + +const FieldHeaderTop = styled.div` + display: flex; +`; + +const FieldHeaderLeft = styled.div` + flex: 1 1 0px; +`; + +export const FieldHeaderTitle = styled.label` + letter-spacing: -0.3px; + font-weight: 500; + font-size: 1.3em; + .ui.tiny.label { + margin-left: 0.5em; + } +`; + +const FieldHeaderSubtitle = styled.label` + letter-spacing: -0.25px; + font-size: 1em; + padding-bottom: 0.3em; + display: block; +`; + +const FieldHeaderRight = styled.div` + display: flex; + flex-direction: column; +`; + +const FieldHeaderIcon = styled.div<{ open?: boolean }>` + padding: 0.5em 1em; + cursor: pointer; + margin-top: auto; + background: transparent; + color: #6041e2; + transition: transform 0.5s, background-color 0.5s, color 0.5s; + margin-bottom: 0.5em; + transform: translateY(0); + &:hover { + background: #eee; + } + ${props => + props.open + ? css` + color: #fff; + background: #aaa7de; + transform: translateY(0.5em); + &:hover { + background: #aaa7de; + } + ` + : ``} +`; + +const FieldHeaderBottom = styled.div<{ open?: boolean }>` + transition: max-height 0.3s; + overflow: hidden; + height: auto; + ${props => (props.open ? `max-height: 500px;` : `max-height: 0;`)} +`; + +const FieldHeaderBottomInner = styled.div` + background: #aaa7de; + color: #fff; + padding: 0.7em; + margin-bottom: 0.5em; +`; + +export const FieldHeader: React.FC = ({ + description, + term, + selectorComponent, + showTerm, + labelFor, + label, + onSelectorClose, + onSelectorOpen, + selectorLabel, +}) => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + + const toggleSelector = useCallback(() => { + if (open) { + setOpen(false); + if (onSelectorClose) { + onSelectorClose(); + } + } else { + setOpen(true); + if (onSelectorOpen) { + onSelectorOpen(); + } + } + }, [onSelectorClose, onSelectorOpen, open]); + + return ( + + + + + {label} {showTerm && term ? {term} : null} + + {description ? {description} : null} + + {selectorComponent ? ( + + {selectorLabel || t('Define region')} + + ) : null} + + {selectorComponent ? ( + + {selectorComponent ? selectorComponent : null} + + ) : null} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.stories.tsx new file mode 100644 index 000000000..c9506b9ca --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.stories.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { defaultTheme } from '../../themes'; +import { FieldInstanceList } from './FieldInstanceList'; +import { ThemeProvider } from 'styled-components'; + +export default { title: 'Legacy/Field instance list' }; + +const simple = require('../../../../../../../fixtures/01-basic/05-multiple-fields-multiple-values.json'); + +export const SimpleExample: React.FC = () => { + return ( + +
+ console.log('choose field', field)} + fields={simple.document.properties.name} + property={'name'} + /> +
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.tsx new file mode 100644 index 000000000..610206a1d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceList/FieldInstanceList.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { BaseField } from '../../../types/field-types'; +import { FieldHeader } from '../FieldHeader/FieldHeader'; +import { FieldPreview } from '../FieldPreview/FieldPreview'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; + +type FieldInstanceListProps = { + fields: Array; + property: string; + fallbackLabel?: string; + chooseField?: (field: { property: string; instance: BaseField }) => void; +}; + +export const FieldInstanceList: React.FC = ({ + fields, + property, + chooseField, + fallbackLabel = 'Untitled', +}) => { + const label = fields[0] ? fields[0].label : fallbackLabel; + const pluralLabel = fields[0] ? fields[0].pluralLabel || label : label; + + return ( + <> + 1 ? pluralLabel : label} /> + {fields.map((field, idx) => { + return ( + chooseField({ instance: field, property }) : undefined} + > + + + ); + })} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceReadOnly/FieldInstanceReadOnly.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceReadOnly/FieldInstanceReadOnly.tsx new file mode 100644 index 000000000..771d63dd0 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldInstanceReadOnly/FieldInstanceReadOnly.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import styled from 'styled-components'; +import { BaseField } from '../../../types/field-types'; +import { FieldPreview } from '../FieldPreview/FieldPreview'; +import { Revisions } from '../../stores/revisions/index'; +import { SelectorPreview } from '../SelectorPreview/SelectorPreview'; +import ReactTooltip from 'react-tooltip'; + +const PreviewListContainer = styled.div` + & ~ & { + margin-top: 1.5em; + } +`; + +const PreviewList = styled.div` + background: rgba(5, 42, 68, 0.1); + padding: 0.5em; + border-radius: 3px; +`; + +const PreviewLabel = styled.div` + font-size: 1em; + font-weight: 600; + margin-bottom: 0.5em; +`; + +export const FieldInstanceReadOnly: React.FC<{ + fields: Array; + showSelectorPreview?: boolean; +}> = ({ fields, showSelectorPreview }) => { + const chooseSelector = Revisions.useStoreActions(a => a.chooseSelector); + const currentSelectorId = Revisions.useStoreState(s => s.selector.currentSelectorId); + const previewData = Revisions.useStoreState(s => s.selector.selectorPreviewData); + + return ( + + {fields[0].label} + + {fields.map(field => ( + + + + + {showSelectorPreview && field.selector && field.selector.state ? ( + + + + ) : null} + + ))} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldPreview/FieldPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldPreview/FieldPreview.tsx new file mode 100644 index 000000000..cbbb86a44 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldPreview/FieldPreview.tsx @@ -0,0 +1,12 @@ +import React, { ComponentClass, FunctionComponent } from 'react'; +import { useFieldPreview } from '../../../plugin-api/hooks/use-field-preview'; +import { BaseField } from '../../../types/field-types'; + +export const FieldPreview: React.FC<{ + field: BaseField; + as?: FunctionComponent | ComponentClass | string; +}> = ({ field }) => { + const preview = useFieldPreview(field); + + return React.createElement(React.Fragment, {}, preview); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.stories.tsx new file mode 100644 index 000000000..a3a0a68c9 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.stories.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import { BaseField } from '../../../types/field-types'; +import { createFormFieldReducer } from '../../core/current-form'; +import { FieldWrapper } from '../FieldWrapper/FieldWrapper'; +import { FieldSet, FieldSetRenderField } from './FieldSet'; + +import '../../input-types/TextField'; + +export default { title: 'Capture model editor components/Fieldset' }; + +const model0 = require('../../../../../../../fixtures/01-basic/01-single-field.json'); +const model1 = require('../../../../../../../fixtures/01-basic/02-multiple-fields.json'); +const model2 = require('../../../../../../../fixtures/02-nesting/03-deeply-nested-subset.json'); +const model = require('../../../../../../../fixtures/02-nesting/04-deeply-nested-mixed-instance.json'); + +const firstOption = model1.structure.items[0].fields.reduce(createFormFieldReducer(model1.document), []); + +const simpleRenderField: FieldSetRenderField = (field: BaseField, config) => { + return ( +
+ console.log('field updated value => ', value)} /> +
+ ); +}; + +export const Simple: React.FC = () => ( +
+); + +const secondOption = model0.structure.items[0].fields.reduce(createFormFieldReducer(model0.document), []); + +export const SingleField: React.FC = () => ( +
+); + +const nestedModel = model.structure.items[0].fields.reduce(createFormFieldReducer(model.document), []); + +export const NestedModel: React.FC = () => ( +
+); + +export const CustomNestedModel: React.FC = () => ( +
( +
( + console.log('field updated value => ', value)} /> + )} + /> + )} + /> +); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.tsx new file mode 100644 index 000000000..ddcecb3fc --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldSet/FieldSet.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { BaseField, NestedField } from '../../../types/field-types'; +import { Heading } from '../Heading/Heading'; + +export type FieldSetRenderField = ( + field: BaseField, + config: { + path: Array<[string, number]>; + depth: number; + key: number; + count: number; + } +) => React.ReactElement | null; + +export type FieldSetRenderFieldset = ( + props: FieldSetProps, + config: { key: number; count: number } +) => React.ReactElement | null; + +export type FieldSetProps = { + label?: string; + description?: string; + fields: NestedField; + renderField: FieldSetRenderField; + renderNestedFieldset?: FieldSetRenderFieldset; + path?: Array<[string, number]>; + depth?: number; +} & React.DetailedHTMLProps, HTMLFieldSetElement>; + +export const FieldSet: React.FC = ({ + label, + description, + fields, + renderField, + renderNestedFieldset, + depth = 0, + path = [], + ...props +}) => ( +
+ {label && ( + {label} + )} + {description &&

{description}

} + {fields.map((field, fieldKey) => ( + + {field.type === 'documents' + ? field.list.map((doc, key) => + renderNestedFieldset ? ( + renderNestedFieldset( + { + label: doc.label, + description: doc.description, + fields: doc.fields, + renderField, + renderNestedFieldset, + depth: depth + 1, + path: [...path, [doc.id, key]], + }, + { key, count: field.list.length } + ) + ) : ( +
+ ) + ) + : field.list.map((nestedField, key) => + renderField(nestedField, { + depth: depth + 1, + key, + path: [...path, [nestedField.id, key]], + count: field.list.length, + }) + )} + + ))} +
+); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.stories.tsx new file mode 100644 index 000000000..12f581042 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.stories.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { PluginProvider } from '../../../plugin-api/context'; +import { defaultTheme } from '../../themes'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { FieldWrapper } from './FieldWrapper'; +// Import some plugins +import '../../input-types/TextField/index'; +import '../../selector-types/BoxSelector'; +import '../../content-types/Atlas'; + +export default { title: 'Legacy/Field Wrapper' }; + +export const Simple: React.FC = () => { + return ( +
+ + + + console.log(val)} + /> + { + console.log('chose selector', selector); + }} + selector={{ + id: '123', + type: 'box-selector', + state: { + x: 0, + y: 0, + height: 100, + width: 100, + }, + }} + showTerm={true} + term="description" + onUpdateValue={val => console.log(val)} + /> + { + console.log('chose selector', selector); + }} + selector={{ + id: '456', + type: 'box-selector', + state: null, + }} + showTerm={true} + term="description" + onUpdateValue={val => console.log(val)} + /> + + + +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.tsx new file mode 100644 index 000000000..5fdb86235 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FieldWrapper/FieldWrapper.tsx @@ -0,0 +1,101 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useField } from '../../../plugin-api/hooks/use-field'; +import { useSelectorStatus } from '../../../plugin-api/hooks/use-selector-status'; +import { BaseField } from '../../../types/field-types'; +import { BaseSelector } from '../../../types/selector-types'; +import { FieldHeader } from '../FieldHeader/FieldHeader'; + +type Props = { + field: T; + selector?: BaseSelector; + term?: string; + showTerm?: boolean; + onUpdateValue: (value: T['value']) => void; + hideHeader?: boolean; + fallback?: any; + chooseSelector?: (payload: { selectorId: string }) => void; + currentSelectorId?: string | null; + onUpdateSelector?: (state: any) => void; + clearSelector?: () => void; + selectorPreview?: any; + selectorLabel?: string; + + // @todo other things for the selector. + // onChooseSelector() + // onClearSelector() + // onUpdateSelector() + // onDisplaySelector() + // onHideSelector() +}; + +export const FieldWrapper: React.FC = ({ + field, + term, + onUpdateValue, + showTerm, + selector, + hideHeader, + fallback, + chooseSelector, + currentSelectorId, + clearSelector, + selectorPreview, + onUpdateSelector, + selectorLabel, +}) => { + const [value, setValue] = useState(field.value); + + const updateValue = useCallback( + newValue => { + setValue(newValue); + onUpdateValue(newValue); + }, + [onUpdateValue] + ); + + const fieldComponent = useField(field, value, updateValue); + + // @todo pass a lot of (optional) things from props to this selector status for actions on selectors. + const selectorComponent = useSelectorStatus(selector, { + updateSelector: onUpdateSelector, + chooseSelector: chooseSelector ? (selectorId: string) => chooseSelector({ selectorId }) : undefined, + clearSelector, + currentSelectorId: currentSelectorId ? currentSelectorId : undefined, + selectorPreview, + }); + + const componentWillUnmount = useCallback(() => { + if (selector && clearSelector && currentSelectorId === selector.id) { + clearSelector(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentSelectorId]); + + // On unmount. + useEffect(() => componentWillUnmount, [componentWillUnmount]); + + return ( + +
+ {hideHeader ? null : ( + { + if (chooseSelector && selector) { + chooseSelector({ selectorId: selector.id }); + } + }} + onSelectorClose={clearSelector} + term={term} + /> + )} +
{fieldComponent || ''}
+
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FormPreview/FormPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FormPreview/FormPreview.tsx new file mode 100644 index 000000000..da926fba5 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/FormPreview/FormPreview.tsx @@ -0,0 +1,37 @@ +import { useFormikContext } from 'formik'; +import React, { useState } from 'react'; +import { BaseField } from '../../../types/field-types'; +import { Button } from '../../atoms/Button'; +import { FieldWrapper } from '../FieldWrapper/FieldWrapper'; +import { useTranslation } from 'react-i18next'; + +export const FormPreview: React.FC<{ + term?: string; + type: string; + defaultValue?: any; + setDefaultValue?: (value: any) => void; + mapValues?: (props: any) => any; +}> = ({ type, term, mapValues, defaultValue, setDefaultValue }) => { + const { t } = useTranslation(); + const { values } = useFormikContext(); + const [value, setValue] = useState(values.value); + + // const [field] = useField(values, value, setValue); + + return ( + <> + + {setDefaultValue && value !== defaultValue ? ( + <> + + + ) : null} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.stories.tsx new file mode 100644 index 000000000..ef3e13e65 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.stories.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { Heading } from './Heading'; + +export default { title: 'Capture model editor components/Heading' }; + +export const Large: React.FC = () => ( +
+ Large heading +

With paragraph text under the heading

+
+); + +export const Medium: React.FC = () => ( +
+ Medium heading +

With paragraph text under the heading

+
+); + +export const Small: React.FC = () => ( +
+ Small heading +

With paragraph text under the heading

+
+); + +export const Document: React.FC = () => ( +
+ Main heading +

+ With the paragraph text under the heading. With the paragraph text under the heading. With the paragraph text + under the heading. With the paragraph text under the heading. With the paragraph text under the heading. With the + paragraph text under the heading. With the paragraph text under the heading +

+ First medium heading +

+ With the paragraph text under the heading. With the paragraph text under the heading. With the paragraph text + under the heading. With the paragraph text under the heading. With the paragraph text under the heading. With the + paragraph text under the heading. With the paragraph text under the heading +

+ Second heading +

+ With the paragraph text under the heading. With the paragraph text under the heading. With the paragraph text + under the heading. With the paragraph text under the heading. With the paragraph text under the heading. With the + paragraph text under the heading. With the paragraph text under the heading +

+ First small heading +

+ With the paragraph text under the heading. With the paragraph text under the heading. With the paragraph text + under the heading. With the paragraph text under the heading. With the paragraph text under the heading. With the + paragraph text under the heading. With the paragraph text under the heading +

+ Last medium heading +

+ With the paragraph text under the heading. With the paragraph text under the heading. With the paragraph text + under the heading. With the paragraph text under the heading. With the paragraph text under the heading. With the + paragraph text under the heading. With paragraph text under the heading +

+
+); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.tsx new file mode 100644 index 000000000..9bba508d7 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Heading/Heading.tsx @@ -0,0 +1,28 @@ +import styled, { css } from 'styled-components'; + +export const Heading = styled.header<{ size: 'large' | 'medium' | 'small' }>` + font-family: -apple-system, "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Open Sans", "Helvetica Neue", "Icons16", sans-serif; + line-height: 1.4em; + margin: .3em 0; + ${props => + props.size === 'large' && + css` + font-weight: 700; + letter-spacing: -0.8px; + font-size: 2.4em; + `} + ${props => + props.size === 'medium' && + css` + letter-spacing: -0.6px; + font-weight: 500; + font-size: 1.8em; + `} + ${props => + props.size === 'small' && + css` + letter-spacing: -0.3px; + font-size: 1.2em; + font-weight: 600; + `} +`; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelEditor/ModelEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelEditor/ModelEditor.tsx new file mode 100644 index 000000000..bfdc576b7 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelEditor/ModelEditor.tsx @@ -0,0 +1,136 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { CaptureModel, ModelFields } from '../../../types/capture-model'; +import { StructureType } from '../../../types/utility'; +import { Button } from '../../atoms/Button'; +import { Card, CardContent, CardMeta, CardHeader } from '../../atoms/Card'; +import { Grid, GridColumn } from '../../atoms/Grid'; +import { List, ListHeader, ListContent, ListItem } from '../../atoms/List'; +import { expandModelFields, mergeFlatKeys, structureToFlatStructureDefinition } from '../../core/structure-editor'; +import { SelectModelFields } from '../SelectModelFields/SelectModelFields'; +import { StructureMetadataEditor } from '../StructureMetadataEditor/StructureMetadataEditor'; +import { Box } from '@styled-icons/entypo/Box'; +import { Edit } from '@styled-icons/entypo/Edit'; +import { Tag } from '../../atoms/Tag'; +import { useTranslation } from 'react-i18next'; + +type Props = { + document: CaptureModel['document']; + setLabel: (label: string) => void; + model: StructureType<'model'>; + modelFields: ModelFields; + setDescription: (description: string) => void; + setInstructions: (instructions: string) => void; + setModelFields: (fields: ModelFields) => void; + + initialPath?: number[]; + popFocus: () => void; +}; + +// Set label +// Set description +// Add field +// Remove field +// @todo later +// - Reorder fields +// - Profile value + +export const ModelEditor: React.FC = ({ + document, + model, + popFocus, + modelFields, + setLabel, + setDescription, + setInstructions, + initialPath = [], + setModelFields, +}) => { + const { t } = useTranslation(); + const [isSelecting, setIsSelecting] = useState(false); + const [selected, setSelected] = useState(() => expandModelFields(modelFields)); + + const flatKeys = useMemo(() => mergeFlatKeys(selected), [selected]); + + useEffect(() => { + if (flatKeys) { + setModelFields(flatKeys); + } + }, [flatKeys, setModelFields]); + + return ( + + + + {initialPath.length ? ( + + + + ) : null} + + {model.label} + {t('Model')} + + + + + { + setLabel(values.label); + setDescription(values.description || ''); + if (values.type === 'model') { + setInstructions(values.instructions || ''); + } + }} + /> + + + + {structureToFlatStructureDefinition(document, mergeFlatKeys(selected)).map((item, key) => ( + + {item.type === 'entity' ? : } + + {item.label} + + + {item.type} + + + + ))} + + + + {isSelecting ? ( + + { + setIsSelecting(false); + setSelected(s => (s ? [...s, m] : [m])); + }} + /> +
+ +
+ ) : ( + + )} +
+ +
{JSON.stringify(mergeFlatKeys(selected), null, 2)}
+
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelMetadataEditor/ModelMetadataEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelMetadataEditor/ModelMetadataEditor.tsx new file mode 100644 index 000000000..015a4fd7d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/ModelMetadataEditor/ModelMetadataEditor.tsx @@ -0,0 +1,59 @@ +import { useFormik } from 'formik'; +import React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { Button } from '../../atoms/Button'; +import { StyledForm, StyledFormField, StyledFormInput } from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + structure: CaptureModel['structure']; + onSave: (structure: CaptureModel['structure']) => void; +}; + +export const ModelMetadataEditor: React.FC = ({ structure, onSave }) => { + const { t } = useTranslation(); + const formik = useFormik({ + initialValues: structure, + onSubmit: values => { + onSave(values); + }, + }); + + return ( +
+ + + + + + + + + + + {formik.dirty ? ( + + ) : null} +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.stories.tsx new file mode 100644 index 000000000..50ed88c53 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.stories.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { NewChoiceForm } from './NewChoiceForm'; + +export default { title: 'Capture model editor components/New Choice Form' }; + +export const Simple: React.FC = () => console.log(choice)} />; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.tsx new file mode 100644 index 000000000..983d7050b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewChoiceForm/NewChoiceForm.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { createChoice } from '../../../helpers/create-choice'; +import { StructureType } from '../../../types/utility'; +import { Button } from '../../atoms/Button'; +import { StyledForm, StyledFormField, StyledFormInput } from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + onSave: (choice: StructureType<'choice'>) => void; +}; + +export const NewChoiceForm: React.FC = ({ onSave }) => { + const { t } = useTranslation(); + const [label, setLabel] = useState(''); + const onSubmit = (e: any) => { + e.preventDefault(); + if (!label) return; + onSave(createChoice({ label })); + }; + + return ( + + + + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.stories.tsx new file mode 100644 index 000000000..b99157005 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.stories.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { NewDocumentForm } from './NewDocumentForm'; + +export default { title: 'Capture model editor components/New Document Form' }; + +export const Simple: React.FC = () => console.log(t)} />; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.tsx new file mode 100644 index 000000000..df17ab9cf --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewDocumentForm/NewDocumentForm.tsx @@ -0,0 +1,66 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { SelectorSpecification } from '../../../types/selector-types'; +import { Button } from '../../atoms/Button'; +import { ChooseSelectorButton } from '../ChooseSelectorButton/ChooseSelectorButton'; +import { ErrorMessage } from '../../atoms/Message'; +import { StyledForm, StyledFormField, StyledFormInput } from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + existingTerms: string[]; + onSave: (t: { term: string; selectorType?: string; selector?: SelectorSpecification }) => void; +}; + +export const NewDocumentForm: React.FC = ({ existingTerms, onSave }) => { + const { t } = useTranslation(); + const [term, setTerm] = useState(''); + const [error, setError] = useState(''); + const { selectors } = useContext(PluginContext); + const [selectorType, setSelectorType] = useState(''); + + const onSubmit = (e: any) => { + e.preventDefault(); + const selector = selectorType ? selectors[selectorType] : undefined; + onSave({ + term, + selectorType, + selector, + }); + }; + + useEffect(() => { + if (existingTerms.indexOf(term) !== -1) { + setError(t('The key "{{term}}" already exists in this item', { term })); + } else { + setError(''); + } + }, [existingTerms, t, term]); + + return ( + + + + + + + + {error ? {error} : null} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.stories.tsx new file mode 100644 index 000000000..dc75c821a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.stories.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { NewFieldForm } from './NewFieldForm'; + +export default { title: 'Capture model editor components/New Field Form' }; + +export const Simple: React.FC = () => console.log(t)} />; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.tsx new file mode 100644 index 000000000..c49c87754 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewFieldForm/NewFieldForm.tsx @@ -0,0 +1,86 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { PluginContext } from '../../../plugin-api/context'; +import { FieldSpecification } from '../../../types/field-types'; +import { SelectorSpecification } from '../../../types/selector-types'; +import { Button } from '../../atoms/Button'; +import { ChooseFieldButton } from '../ChooseFieldButton/ChooseFieldButton'; +import { ChooseSelectorButton } from '../ChooseSelectorButton/ChooseSelectorButton'; +import { ErrorMessage } from '../../atoms/Message'; +import { StyledForm, StyledFormField, StyledFormInput, StyledFormLabel } from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + existingTerms: string[]; + onSave: (t: { + fieldType: string; + selectorType?: string; + term: string; + field: FieldSpecification; + selector?: SelectorSpecification; + }) => void; +}; + +export const NewFieldForm: React.FC = ({ existingTerms, onSave }) => { + const { t } = useTranslation(); + const [term, setTerm] = useState(''); + const { fields, selectors } = useContext(PluginContext); + const [fieldType, setFieldType] = useState(''); + const [selectorType, setSelectorType] = useState(''); + const [error, setError] = useState(''); + + const onSubmit = (e: any) => { + e.preventDefault(); + if (fieldType === '') return; + const field = fields[fieldType]; + if (!field) return; + const selector = selectorType ? selectors[selectorType] : undefined; + onSave({ + term, + fieldType, + field: field, + selectorType, + selector, + }); + }; + + useEffect(() => { + if (existingTerms.indexOf(term) !== -1) { + setError(t('The key "{{term}}" already exists in this item', { term })); + } else { + setError(''); + } + }, [existingTerms, term]); + + return ( + + + + {t('Choose field type')} + setFieldType(type as any)} /> + + + + + {t('Choose selector (optional)')} + setSelectorType(type as any)} /> + + + + + {t('JSON Key / Term')} + setTerm(e.currentTarget.value)} + /> + + + {error ? {error} : null} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewModelForm/NewModelForm.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewModelForm/NewModelForm.tsx new file mode 100644 index 000000000..567463a0a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/NewModelForm/NewModelForm.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { createModel } from '../../../helpers/create-model'; +import { StructureType } from '../../../types/utility'; +import { Button } from '../../atoms/Button'; +import { StyledForm, StyledFormField, StyledFormInput } from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + onSave: (choice: StructureType<'model'>) => void; +}; + +export const NewModelForm: React.FC = ({ onSave }) => { + const { t } = useTranslation(); + const [label, setLabel] = useState(''); + + const onSubmit = (e: any) => { + e.preventDefault(); + if (!label) return; + onSave( + createModel({ + label, + }) + ); + }; + + return ( + + + + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.stories.tsx new file mode 100644 index 000000000..9899aef20 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.stories.tsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; +import { RevisionSummary } from './RevisionSummary'; + +export default { title: 'Legacy/ Revision Summary' }; + +export const SaveOption = () => { + const [text, setText] = useState(''); + + return ( +
+ + console.log('saved')} + descriptionOfChange={text} + setDescriptionOfChange={setText} + /> + +
+ ); +}; + +export const EditOption = () => { + const [text, setText] = useState(''); + + return ( +
+ + console.log('saved')} + descriptionOfChange={text} + setDescriptionOfChange={setText} + /> + +
+ ); +}; + +export const SaveAndEditOption = () => { + const [text, setText] = useState(''); + + return ( +
+ + console.log('saved')} + onEdit={() => console.log('edited')} + descriptionOfChange={text} + setDescriptionOfChange={setText} + /> + +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.tsx new file mode 100644 index 000000000..cac9c7d06 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RevisionSummary/RevisionSummary.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { useDebouncedCallback } from 'use-debounce'; +import { ErrorMessage } from '../../atoms/Message'; +import { TextField } from '../../input-types/TextField/TextField'; +import { CardButton } from '../CardButton/CardButton'; +import { CardButtonGroup } from '../CardButtonGroup/CardButtonGroup'; +import { FieldHeaderWrapper, FieldHeaderTitle } from '../FieldHeader/FieldHeader'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { useTranslation } from 'react-i18next'; + +export const RevisionSummary: React.FC<{ + descriptionOfChange: string; + error?: string; + onSave?: () => void; + onPublish?: () => void; + onEdit?: () => void; + setDescriptionOfChange?: (change: string) => void; + isSaving?: boolean; +}> = ({ onSave, onPublish, onEdit, isSaving, error, descriptionOfChange, setDescriptionOfChange }) => { + const { t } = useTranslation(); + const [label, setLabel] = useState(descriptionOfChange); + + const [updateValue] = useDebouncedCallback(v => { + if (setDescriptionOfChange) { + setDescriptionOfChange(v as any); + } + }, 200); + + useEffect(() => { + updateValue(label); + }, [label, setDescriptionOfChange, updateValue]); + + return ( + <> + {error ? {t('Something went wrong while saving your submission')} : null} + {setDescriptionOfChange ? ( + + + + ) : null} + {onEdit || onSave ? ( + + {onEdit ? ( + + {t('Edit')} + + ) : null} + {onSave ? ( + + {isSaving ? t('Saving...') : t('Save changes')} + + ) : null} + + ) : null} + {onPublish ? ( + + {isSaving ? t('Saving...') : t('Submit for review')} + + ) : null} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.stories.tsx new file mode 100644 index 000000000..ee63b53fd --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.stories.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../../themes'; +import { RoundedCard } from './RoundedCard'; +import { CardButton } from '../CardButton/CardButton'; + +export default { title: 'Legacy/Rounded Card' }; + +export const Default: React.FC = () => ( +
+ + Move the red box to highlight one person, and enter information about them below. You can repeat this for as many + people as you recognise. + + Create new +
+); + +export const Interactive: React.FC = () => ( + +
+ +
+
+); + +export const InteractiveRemove: React.FC = () => ( + alert('clicked')} + onRemove={() => alert('remove')} + /> +); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.tsx new file mode 100644 index 000000000..7f9bab5de --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/RoundedCard/RoundedCard.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import styled, { css } from 'styled-components'; +import { ConfirmButton } from '../../atoms/ConfirmButton'; +import { getCard, getTheme } from '../../themes'; + +export type CardSize = 'large' | 'medium' | 'small'; + +export type RoundedCardProps = { + size?: CardSize; + count?: number; + label?: string; + labelFor?: string; + image?: string; + interactive?: boolean; + onClick?: any; + onRemove?: () => void; + removeMessage?: string; +}; + +const CardLabel = styled.label` + display: block; + font-size: ${props => getTheme(props).sizes.headingMd}; + color: #000; + flex: 1 1 0px; + align-self: center; +`; + +const CardBody = styled.div` + font-size: ${props => getTheme(props).sizes.text}; + line-height: 1.4em; + color: #000; +`; + +const CardWrapper = styled.article<{ size: CardSize; interactive: boolean }>` + position: relative; + box-sizing: border-box; + background: #fff; + padding: ${props => getCard(props, 'padding')}; + border-radius: ${props => getCard(props, 'radius')}; + margin-bottom: ${props => getCard(props, 'margin')}; + box-shadow: ${props => getTheme(props).card.shadow}; + border: 2px solid transparent; + z-index: 2; + ${props => + props.interactive && + css` + cursor: pointer; + & label { + cursor: pointer; + } + `} + &:hover { + border: 2px solid ${props => (props.interactive ? getTheme(props).colors.primary : 'transparent')}; + } +`; + +const DeleteIcon = () => ( + + + + +); + +const CardCount = styled.div` + background: ${props => getTheme(props).colors.mutedPrimary}; + color: ${props => getTheme(props).colors.textOnMutedPrimary}; + border-radius: 12px; + border: 1px solid ${props => getTheme(props).colors.textOnMutedPrimary}; + font-size: 12px; + padding: 0 8px; + font-weight: 600; + height: 24px; + line-height: 24px; + align-self: center; +`; + +const CardLayout = styled.div<{ showMargin: boolean }>` + display: flex; + flex-direction: row; + margin-bottom: ${props => (props.showMargin ? '0.6em' : '0')}; +`; + +const CardImage = styled.img` + height: 30px; + width: 30px; + contain: content; + margin-right: 10px; +`; + +const RemoveIcon = styled.div<{ size: CardSize }>` + position: absolute; + ${props => + props.size === 'small' + ? css` + top: 0.4em; + right: 0.4em; + ` + : css` + top: 1em; + right: 1em; + `}; + width: 30px; + height: 30px; + text-align: center; + display: flex; + align-content: center; + justify-content: center; + border-radius: 50%; + svg { + margin: auto; + } + box-shadow: none; + transition: box-shadow 0.4s, background-color 0.4s; + &:hover { + background: #ffac9d; + box-shadow: 0px 4px 10px 0 rgba(0, 0, 0, 0.3); + } +`; + +export const RoundedCard: React.FC = ({ + size = 'large', + onClick, + onRemove, + removeMessage, + count, + label, + labelFor, + interactive = !!onClick, + children, + image, +}) => { + return ( + + {onRemove ? ( + { + onRemove(); + }} + > + + + + + ) : null} + {image || label || count ? ( + + {image ? : null} + {label ? {label} : null} + {count ? {count} : null} + + ) : null} + {children ? {children} : null} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.stories.tsx new file mode 100644 index 000000000..ad001a159 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.stories.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { Button } from '../../atoms/Button'; +import { Card, CardContent } from '../../atoms/Card'; +import { mergeFlatKeys, structureToFlatStructureDefinition } from '../../core/structure-editor'; +import { SelectModelFields } from './SelectModelFields'; +import { Tag } from '../../atoms/Tag'; + +export default { title: 'Capture model editor components/Select Model Fields' }; + +const model: CaptureModel = require('../../../../../../../fixtures/simple.json'); + +export const Simple: React.FC = () => { + const [isSelecting, setIsSelecting] = useState(false); + const [selected, setSelected] = useState([]); + + return ( +
+ {isSelecting ? ( + + { + setIsSelecting(false); + setSelected(s => [...s, m]); + }} + /> +
+ +
+ ) : ( + + )} + + + {structureToFlatStructureDefinition(model.document, mergeFlatKeys(selected)).map((struct, key) => ( +
+ {struct.label} + + {struct.type} + + {struct.key.map((s: string) => ( + + {s} + + ))} +
+ ))} +
+ +
{JSON.stringify(mergeFlatKeys(selected), null, 2)}
+
+
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.tsx new file mode 100644 index 000000000..55be28e96 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectModelFields/SelectModelFields.tsx @@ -0,0 +1,90 @@ +import produce, { Draft } from 'immer'; +import React, { useState } from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { Tree } from '../Tree/Tree'; +import { useTranslation } from 'react-i18next'; + +type Props = { + document: CaptureModel['document']; + selected: string[][]; + onSave: (newFields: string[]) => void; +}; + +export const SelectModelFields: React.FC = ({ document, selected = [], onSave }) => { + const { t } = useTranslation(); + + const processDoc = (doc: CaptureModel['document'], keyAcc: string[]): any[] => { + const idx = selected.map(s => s.join('--HASH--')); + return Object.keys(doc.properties) + .map(key => { + const props = doc.properties[key]; + if (props.length === 0) return null; + const prop = props[0]; + if (!prop) return null; + if (prop.type === 'entity') { + return { + id: 0, + hasCaret: true, + icon: 'layers', + label: prop.label, + nodeData: [...keyAcc, key], + secondaryLabel:
{t('entity')}
, + childNodes: processDoc(prop as CaptureModel['document'], [...keyAcc, key]), + }; + } + return { + id: key, + icon: 'cube', + label: prop.label, + secondaryLabel:
{prop.type}
, + disabled: idx.indexOf([...keyAcc, key].join('--HASH--')) !== -1, + nodeData: [...keyAcc, key], + }; + }) + .filter(Boolean) as any[]; + }; + + const [nodes, setNodes] = useState(() => processDoc(document, [])); + + const mutatePoint = ([i, ...path]: number[], mutation: (node: Draft) => void) => { + setNodes( + produce(nodesDraft => { + mutation( + path.reduce((acc: any, next: number) => { + if (!acc.childNodes) return acc; + return acc.childNodes[next]; + }, nodesDraft[i]) + ); + })(nodes) + ); + }; + + const onNodeClick = (node: any, tree: any) => { + if (node) { + if (tree.childNodes) { + for (const childNode of tree.childNodes) { + onNodeClick(childNode.nodeData as string[], childNode); + } + } else { + // @todo change this to accept ALL fields in the model. + onSave(node as string[]); + } + } + }; + + const handleNodeExpand = (_: any, path: number[]) => { + mutatePoint(path, node => { + node.isExpanded = true; + }); + }; + + const handleNodeCollapse = (_: any, path: number[]) => { + mutatePoint(path, node => { + node.isExpanded = false; + }); + }; + + return ( + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectorPreview/SelectorPreview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectorPreview/SelectorPreview.tsx new file mode 100644 index 000000000..66fb1f1ad --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SelectorPreview/SelectorPreview.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useSelectorStatus } from '../../../plugin-api/hooks/use-selector-status'; +import { BaseSelector } from '../../../types/selector-types'; + +export const SelectorPreview: React.FC<{ + selector?: BaseSelector; + chooseSelector?: (payload: { selectorId: string }) => void; + currentSelectorId?: string | null; + selectorPreview?: any; +}> = ({ chooseSelector, selectorPreview, currentSelectorId, selector }) => { + return useSelectorStatus(selector, { + currentSelectorId: currentSelectorId ? currentSelectorId : undefined, + chooseSelector: selectorId => (chooseSelector ? chooseSelector({ selectorId }) : undefined), + selectorPreview, + readOnly: true, + }); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.stories.tsx new file mode 100644 index 000000000..ca4e1e49f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.stories.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { DocumentStore } from '../../stores/document/document-store'; +import { useFocusedStructureEditor } from '../../stores/structure/use-focused-structure-editor'; +import { StructureEditor } from './StructureEditor'; +import { StructureStore } from '../../stores/structure/structure-store'; + +export default { title: 'Capture model editor components/Structure Editor' }; + +const model: CaptureModel = require('../../../../../../../fixtures/simple.json'); + +const withStructure = (Component: React.FC): React.FC => () => ( + + + + + +); + +export const Simple: React.FC = withStructure(() => { + const document = DocumentStore.useStoreState(state => state.document); + const tree = StructureStore.useStoreState(state => state.tree); + const focus = StructureStore.useStoreActions(act => act.focus); + const current = StructureStore.useStoreState(state => state.focus.structure); + const currentPath = StructureStore.useStoreState(state => state.focus.index); + const { + setLabel, + setDescription, + addStructureToChoice, + setModelFields, + removeStructureFromChoice, + setProfile, + setInstructions, + reorderChoices, + } = useFocusedStructureEditor(); + + return ( + + ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.tsx new file mode 100644 index 000000000..4b242857c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureEditor/StructureEditor.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { CaptureModel, ModelFields } from '../../../types/capture-model'; +import { StructureType } from '../../../types/utility'; +import { Grid, GridColumn } from '../../atoms/Grid'; +import { ChoiceEditor } from '../ChoiceEditor/ChoiceEditor'; +import { ModelEditor } from '../ModelEditor/ModelEditor'; +import { Tree } from '../Tree/Tree'; +import { useTranslation } from 'react-i18next'; + +type Props = { + tree: any[]; + structure: CaptureModel['structure']; + currentPath?: number[]; + document: CaptureModel['document']; + setPath?: (ids: number[]) => void; + setLabel: (value: string) => void; + setDescription: (value: string) => void; + setProfile: (value: string[]) => void; + setInstructions: (value: string) => void; + onAddChoice: (choice: StructureType<'choice'>) => void; + onAddModel: (model: StructureType<'model'>) => void; + onRemove: (id: number) => void; + pushFocus: (idx: number) => void; + setFocus: (idx: number[]) => void; + popFocus: (payload?: any) => void; + setModelFields: (fields: ModelFields) => void; + reorderChoices: (startIndex: number, endIndex: number) => void; +}; + +export const StructureEditor: React.FC = ({ + tree, + document, + structure, + setFocus, + currentPath, + setDescription, + setInstructions, + setLabel, + setProfile, + setPath, + popFocus, + reorderChoices, + onRemove, + pushFocus, + onAddChoice, + onAddModel, + setModelFields, +}) => { + const { t } = useTranslation(); + return ( + + + setFocus(key)} /> + + + {structure ? ( + structure.type === 'choice' ? ( + + ) : structure.type === 'model' ? ( + `${r}`).join('-')} + setLabel={setLabel} + setDescription={setDescription} + setInstructions={setInstructions} + document={document} + modelFields={structure.fields} + setModelFields={setModelFields} + /> + ) : ( +
{t('Unknown type')}
+ ) + ) : ( +
{t('empty')}
+ )} +
+
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.stories.tsx new file mode 100644 index 000000000..957e4bc27 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.stories.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { createChoice } from '../../../helpers/create-choice'; +import { CaptureModel } from '../../../types/capture-model'; +import { StructureMetadataEditor } from './StructureMetadataEditor'; + +export default { title: 'Capture model editor components/Edit structure' }; + +export const EditChoice: React.FC = () => { + const [choice, setChoice] = useState( + createChoice({ + label: 'Some choice', + description: 'With a description', + }) + ); + + return ; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.tsx new file mode 100644 index 000000000..2bd86e321 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/StructureMetadataEditor/StructureMetadataEditor.tsx @@ -0,0 +1,115 @@ +import { useFormik } from 'formik'; +import React from 'react'; +import { CaptureModel } from '../../../types/capture-model'; +import { Button } from '../../atoms/Button'; +import { + StyledForm, + StyledFormField, + StyledFormInputElement, + StyledFormLabel, + StyledFormTextarea, +} from '../../atoms/StyledForm'; +import { useUnmount } from '../../hooks/useUnmount'; +import { useTranslation } from 'react-i18next'; + +type Props = { + profiles?: string[]; + structure: CaptureModel['structure']; + onSave: (structure: CaptureModel['structure']) => void; +}; + +export const StructureMetadataEditor: React.FC = ({ profiles = [], structure, onSave }) => { + const { t } = useTranslation(); + const formik = useFormik({ + initialValues: structure, + onSubmit: values => { + onSave(values); + }, + }); + + const dirty = formik.dirty; + const submitForm = formik.submitForm; + + useUnmount(() => { + if (dirty) { + submitForm(); + } + }, [dirty, submitForm]); + + return ( + + + + {t('Label')} + + + + + + + {t('Description')} + + + + + {profiles.length ? ( +
+

{t('Profiles')}

+ {profiles.map((prof, idx) => { + return ( +
+ {prof}{' '} + {(formik.values.profile || []).indexOf(prof) === -1 ? ( + + ) : ( + + )} +
+ ); + })} +
+ ) : null} + + {formik.values.type === 'model' ? ( + + + {t('Crowdsourcing Instructions')} + + + + ) : null} + {formik.dirty ? ( + + ) : null} +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubmissionList/SubmissionList.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubmissionList/SubmissionList.tsx new file mode 100644 index 000000000..f27563181 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubmissionList/SubmissionList.tsx @@ -0,0 +1,44 @@ +import React, { useMemo, useState } from 'react'; +import { RevisionRequest } from '../../../types/revision-request'; +import { RoundedCard } from '../RoundedCard/RoundedCard'; +import { useTranslation } from 'react-i18next'; + +export const SubmissionList: React.FC<{ + submissions: RevisionRequest[]; + renderRevisions: (revisions: RevisionRequest[]) => React.ReactNode; +}> = ({ submissions, renderRevisions }) => { + const { t } = useTranslation(); + const myUnpublished = useMemo(() => submissions.filter(rev => rev.revision.status === 'draft'), [submissions]); + const mySubmitted = useMemo(() => submissions.filter(rev => rev.revision.status === 'submitted'), [submissions]); + const [showPending, setShowPending] = useState(false); + + if (submissions.length === 0 || (mySubmitted.length === 0 && myUnpublished.length === 0)) { + return null; + } + + return ( + +

{t('Your submissions')}

+ {myUnpublished.length > 0 ? ( +

{t('These are the submissions that you have created but not yet submitted for review or others to see.')}

+ ) : null} + {renderRevisions(myUnpublished)} + {mySubmitted.length && !showPending ? ( +

+ setShowPending(true)}> + {t('You have {{count}} pending submissions', { count: mySubmitted.length })} + +

+ ) : null} + {showPending ? ( + <> + setShowPending(false)}> + {t('close')} + +

{t('Awaiting review')}

+ {renderRevisions(mySubmitted)} + + ) : null} +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubtreeBreadcrumb/SubtreeBreadcrumb.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubtreeBreadcrumb/SubtreeBreadcrumb.tsx new file mode 100644 index 000000000..b42175ae6 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/SubtreeBreadcrumb/SubtreeBreadcrumb.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Breadcrumb, BreadcrumbDivider, BreadcrumbSection } from '../../atoms/Breadcrumb'; +import { useTranslation } from 'react-i18next'; + +type Props = { + subtreePath: string[]; + popSubtree: (payload?: { count: number }) => void; +}; + +export const SubtreeBreadcrumb: React.FC = ({ popSubtree, subtreePath }) => { + const { t } = useTranslation(); + return ( + + popSubtree({ count: subtreePath.length }) : undefined} + > + {t('Document root')} + + {subtreePath.map((path, n) => ( + + {n !== subtreePath.length ? / : null} + popSubtree({ count: subtreePath.length - n - 1 }) : undefined} + > + {path} + + + ))} + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Tree/Tree.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Tree/Tree.tsx new file mode 100644 index 000000000..1fbf61763 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/components/Tree/Tree.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +type TreeProps = { + id: string; + label: string; + nodeData: any; + childNodes?: TreeProps[]; + disabled?: boolean; +}; + +export const Tree: React.FC<{ tree: TreeProps; onClick: (t: any, tree: TreeProps) => void }> = ({ tree, onClick }) => { + return ( +
+ {tree.nodeData && !tree.disabled ? ( + onClick(tree.nodeData, tree)}> + {tree.label} + + ) : ( +
{tree.label}
+ )} + {tree.childNodes ? ( +
+ {tree.childNodes.map(node => ( + + ))} +
+ ) : null} +
+ ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/EntityInstance.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/EntityInstance.tsx new file mode 100644 index 000000000..8d8eee85c --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/EntityInstance.tsx @@ -0,0 +1,44 @@ +import { useSelectorStatus } from '../../plugin-api/hooks/use-selector-status'; +import { Revisions } from '../stores/revisions'; +import React, { useCallback, useEffect } from 'react'; +import { useSelectorWithId } from '../stores/selectors/selector-hooks'; + +type EntityInstanceProps = { + selectorId: string; + readOnly?: boolean; + isTopLevel?: boolean; +}; + +export const EntityInstance: React.FC = ({ selectorId, readOnly, isTopLevel }) => { + const selector = useSelectorWithId(selectorId); + const updateSelectorValue = Revisions.useStoreActions(a => a.updateSelector); + const chooseSelector = Revisions.useStoreActions(a => a.chooseSelector); + const clearSelector = Revisions.useStoreActions(a => a.clearSelector) as any; + const currentSelectorId = Revisions.useStoreState(s => s.selector.currentSelectorId); + const previewData = Revisions.useStoreState(s => s.selector.selectorPreviewData); + + const componentWillUnmount = useCallback(() => { + clearSelector(); + }, [clearSelector]); + + useEffect(() => componentWillUnmount, [componentWillUnmount]); + + const updateSelector = useCallback( + state => { + if (selector) { + updateSelectorValue({ selectorId: selector.id, state }); + } + }, + [selector, updateSelectorValue] + ); + + return useSelectorStatus(selector, { + updateSelector: updateSelector, + chooseSelector: chooseSelector ? (_selectorId: string) => chooseSelector({ selectorId: _selectorId }) : undefined, + clearSelector, + currentSelectorId: currentSelectorId ? currentSelectorId : undefined, + selectorPreview: selector ? previewData[selector.id] : undefined, + readOnly, + isTopLevel, + }); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/FieldInstance.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/FieldInstance.tsx new file mode 100644 index 000000000..e0d12d02a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/connected-components/FieldInstance.tsx @@ -0,0 +1,49 @@ +import React, { useCallback } from 'react'; +import { useDebouncedCallback } from 'use-debounce'; +import { BaseField } from '../../types/field-types'; +import { FieldWrapper } from '../components/FieldWrapper/FieldWrapper'; +import { Revisions } from '../stores/revisions'; +import { useFieldSelector } from '../stores/selectors/selector-hooks'; + +export const FieldInstance: React.FC<{ + field: BaseField; + property: string; + path: Array<[string, string]>; + hideHeader?: boolean; +}> = ({ field, property, path, hideHeader }) => { + const updateFieldValue = Revisions.useStoreActions(a => a.updateFieldValue); + const updateSelectorValue = Revisions.useStoreActions(a => a.updateSelector); + const chooseSelector = Revisions.useStoreActions(a => a.chooseSelector); + const currentSelectorId = Revisions.useStoreState(s => s.selector.currentSelectorId); + const clearSelector = Revisions.useStoreActions(a => a.clearSelector) as any; + const previewData = Revisions.useStoreState(s => s.selector.selectorPreviewData); + + const selector = useFieldSelector(field); + + const [updateValue] = useDebouncedCallback(newValue => { + updateFieldValue({ value: newValue, path: [...path, [property, field.id]] }); + }, 100); + + const updateSelector = useCallback( + state => { + if (field && field.selector) { + updateSelectorValue({ selectorId: field.selector.id, state }); + } + }, + [field, updateSelectorValue] + ); + + return ( + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.helpers.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.helpers.ts new file mode 100644 index 000000000..0cd203663 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.helpers.ts @@ -0,0 +1,29 @@ +import { ImageService } from '@hyperion-framework/types'; +import { useCallback } from 'react'; +import { createContext } from '../../../helpers/create-context'; + +export const [useImageServiceContext, ImageServiceContext] = createContext(); + +export function useCroppedRegion() { + const imageService = useImageServiceContext(); + + return useCallback( + (props?: { x: number; y: number; width: number; height: number } | null) => { + if (!imageService.tiles || !props) { + return undefined; + } + + // For this small preview, use the first tile. + const tile = imageService.tiles[0]; + + if (!tile) { + return undefined; + } + + return `${imageService.id}/${Math.floor(props.x)},${Math.floor(props.y)},${Math.floor(props.width)},${Math.floor( + props.height + )}/${tile.width},/0/default.jpg`; + }, + [imageService.id, imageService.tiles] + ); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.tsx new file mode 100644 index 000000000..1b440f713 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/Atlas.tsx @@ -0,0 +1,163 @@ +import { ImageService } from '@hyperion-framework/types'; +import React, { Suspense, useMemo, useState } from 'react'; +import { BaseContent, ContentOptions } from '../../../types/content-types'; +import { useAllSelectors, useCurrentSelector, useSelectorActions } from '../../stores/selectors/selector-hooks'; +import { + useExternalManifest, + CanvasContext, + useCanvas, + useImageService, + VaultProvider, + useVaultEffect, +} from '@hyperion-framework/react-vault'; +import { AtlasAuto, getId, GetTile, TileSet, AtlasContextType, PopmotionControllerConfig } from '@atlas-viewer/atlas'; +import { ImageServiceContext } from './Atlas.helpers'; + +export type AtlasCustomOptions = { + unstable_webglRenderer?: boolean; + customFetcher?: (url: string, options: T) => unknown | Promise; + onCreateAtlas?: (context: AtlasContextType) => void; + controllerConfig?: PopmotionControllerConfig; +}; + +export interface AtlasViewerProps extends BaseContent { + id: string; + type: string; + state: { + canvasId: string; + manifestId: string; + imageService?: string; + }; + options: ContentOptions; +} + +const Canvas: React.FC<{ + isEditing?: boolean; + onDeselect?: () => void; + onCreated?: (ctx: any) => void; + unstable_webglRenderer?: boolean; + controllerConfig?: PopmotionControllerConfig; + style?: any; +}> = ({ isEditing, onDeselect, children, onCreated, unstable_webglRenderer, controllerConfig, style }) => { + const canvas = useCanvas(); + const { data: service } = useImageService() as { data?: ImageService }; + const [thumbnail, setThumbnail] = useState(undefined); + + useVaultEffect( + v => { + if (canvas) { + v.getThumbnail(canvas, { minWidth: 100 }, false).then(thumb => { + if (thumb.best) { + setThumbnail(thumb.best); + } + }); + } else { + setThumbnail(undefined); + } + }, + [canvas] + ); + + const tiles: GetTile | undefined = useMemo(() => { + if (canvas && service) { + return { + id: getId(service), + width: canvas.width, + height: canvas.height, + imageService: service, + thumbnail: thumbnail?.type === 'fixed' ? thumbnail : undefined, + }; + } + return undefined; + }, [canvas, service, thumbnail]); + + if (!service || !canvas) { + return null; + } + + return ( + + + + {tiles ? : null} + + {children} + + + + + ); +}; + +export const AtlasViewer: React.FC = props => { + const { isLoaded } = useExternalManifest(props.state.manifestId); + const currentSelector = useCurrentSelector('atlas', undefined); + const selectorVisibility = { + adjacentSelectors: true, + topLevelSelectors: true, + displaySelectors: true, + currentSelector: true, + ...(props.options && props.options.selectorVisibility ? props.options.selectorVisibility : {}), + }; + const selectors = useAllSelectors('atlas', selectorVisibility); + const [actions] = useSelectorActions(); + + if (!isLoaded) { + return null; + } + + const { height = 500, width = '100%', maxHeight, maxWidth } = props.options || { height: 500 }; + + const styleProps = { + minWidth: 100, + minHeight: 100, + height, + width, + maxHeight, + maxWidth, + }; + + return ( +
+ + { + if (currentSelector) { + actions.clearSelector(); + } + }} + > + {selectors} + {currentSelector} + {props.children} + + +
+ ); +}; + +const WrappedViewer: React.FC = props => { + const customFetcher = + props.options && props.options.custom && props.options.custom.customFetcher + ? props.options.custom.customFetcher + : undefined; + + return ( + + {props.children} + + ); +}; + +export default WrappedViewer; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/index.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/index.ts new file mode 100644 index 000000000..1199f8c20 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/Atlas/index.ts @@ -0,0 +1,50 @@ +import React from 'react'; +import { registerContent } from '../../../plugin-api/global-store'; +import { ContentSpecification } from '../../../types/content-types'; +import { AtlasViewerProps } from './Atlas'; + +declare module '../../../types/content-types' { + export interface ContentTypeMap { + atlas: AtlasViewerProps; + } +} + +const specification: ContentSpecification = { + label: 'Atlas Viewer', + type: 'atlas', + supports: target => { + if (target.length < 2) { + return false; + } + const canvas = target[target.length - 1]; + const manifest = target[target.length - 2]; + + return ( + manifest && + (manifest.type || '').toLowerCase() === 'manifest' && + canvas && + (canvas.type || '').toLowerCase() === 'canvas' + ); + }, + targetToState: (target, options) => { + if (options.targetOverride) { + return options.targetOverride; + } + const canvas = target[target.length - 1]; + const manifest = target[target.length - 2]; + return { + manifestId: manifest.id, + canvasId: canvas.id, + }; + }, + description: 'Supports viewing IIIF Canvases inside of a Manifest.', + defaultState: { + canvasId: '', + manifestId: '', + }, + DefaultComponent: React.lazy(() => import(/* webpackChunkName: "atlas" */ './Atlas')), +}; + +registerContent(specification); + +export default specification; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.stories.tsx new file mode 100644 index 000000000..f1102de78 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.stories.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { Revisions as RevisionStore } from '../../stores/revisions'; +import CanvasPanel from './CanvasPanel'; +import './index'; +import { useState } from 'react'; +import { useSelectorHelper } from '../../stores/selectors/selector-helper'; +const model = require('../../../../../../../fixtures/04-selectors/05-wunder-selector.json'); +const ocrModel = require('../../../../../../../fixtures/02-nesting/06-ocr.json'); + +export default { title: 'Content Types/Canvas Panel' }; + +export const Simple: React.FC = () => { + return ( + + + + ); +}; + +export const OCRCanvas: React.FC = () => { + const helper = useSelectorHelper(); + const [highlighted, setHighlighted] = useState(false); + + return ( + + + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.tsx new file mode 100644 index 000000000..a16429999 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/CanvasPanel.tsx @@ -0,0 +1,70 @@ +import { + CanvasProvider, + CanvasRepresentation, + Manifest, + OpenSeadragonViewer, + OpenSeadragonViewport, + SingleTileSource, + Viewport, +} from 'canvas-panel-beta'; +import React, { Suspense, useState } from 'react'; +import { createContext } from '../../../helpers/create-context'; +import { BaseContent } from '../../../types/content-types'; +import { useAllSelectors, useCurrentSelector } from '../../stores/selectors/selector-hooks'; + +export interface CanvasPanelProps extends BaseContent { + id: string; + type: string; + state: { + maxHeight?: number; + canvasId: string; + manifestId: string; + }; +} + +export const [useViewer, ViewerProvider] = createContext(); + +export const CanvasPanel: React.FC = ({ canvasId, manifestId, maxHeight }) => { + const [viewer, setViewer] = useState(); + // Starting with display selectors. I need the selector context, BUT it should + // work without the context too. + const currentSelector = useCurrentSelector('canvas-panel', { + width: 400, + height: 400, + x: 500, + y: 500, + }); + + const allSelectors = useAllSelectors('canvas-panel', { + topLevelSelectors: true, + displaySelectors: true, + currentSelector: true, + adjacentSelectors: false, + }); + + return ( + null}> + + + + + + + + + {allSelectors} + {currentSelector} + + + + + + + ); +}; + +const WrappedCanvasPanel: React.FC = ({ id, state }) => { + return ; +}; + +export default WrappedCanvasPanel; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/index.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/index.ts new file mode 100644 index 000000000..e06ae96b8 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/content-types/CanvasPanel/index.ts @@ -0,0 +1,61 @@ +import React from 'react'; +import { registerContent } from '../../../plugin-api/global-store'; +import { ContentSpecification } from '../../../types/content-types'; + +declare module '../../../types/content-types' { + export interface ContentTypeMap { + 'canvas-panel': import('./CanvasPanel').CanvasPanelProps; + } +} + +/** + * Supports: + * [ + * { type: 'manifest', id: '...' }, + * { type: 'canvas', id: '...' } + * ] + * + * or: + * [ + * { type: 'anything', id: '...' }, + * { type: 'manifest', id: '...' }, + * { type: 'canvas', id: '...' } + * ] + * + * So long as the last 2 items are a manifest and then a canvas (most specific) + */ +const specification: ContentSpecification = { + label: 'Canvas Panel', + type: 'canvas-panel', + supports: (target, options) => { + if (!options.legacy) { + return false; + } + if (target.length < 2) { + return false; + } + const canvas = target[target.length - 1]; + const manifest = target[target.length - 2]; + + return manifest && manifest.type === 'manifest' && canvas && canvas.type === 'canvas'; + }, + targetToState: target => { + const canvas = target[target.length - 1]; + const manifest = target[target.length - 2]; + return { + manifestId: manifest.id, + canvasId: canvas.id, + }; + }, + description: 'Supports viewing IIIF Canvases inside of a Manifest.', + defaultState: { + canvasId: '', + manifestId: '', + }, + DefaultComponent: React.lazy(() => import(/* webpackChunkName: "canvas-panel" */ './CanvasPanel')), + // DefaultComponent: CanvasPanel, +}; + +registerContent(specification); + +export default specification; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model-provider.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model-provider.tsx new file mode 100644 index 000000000..4a32a763a --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model-provider.tsx @@ -0,0 +1,68 @@ +import React, { useMemo, useState } from 'react'; +import { CaptureModel } from '../../types/capture-model'; +import { CaptureModelContext } from '../../types/to-be-removed'; +import { useInternalNavigationState } from './navigation'; +import { useInternalCurrentFormState } from './current-form'; +import { useInternalCurrentSelectorState } from './current-selector'; +import { InternalProvider } from './context'; + +export const CaptureModelProvider: React.FC<{ + captureModel: CaptureModel; +}> = ({ captureModel: originalCaptureModel, children }) => { + const [captureModel, setCaptureModel] = useState(() => originalCaptureModel); + + const { currentView, currentPath, replacePath } = useInternalNavigationState(captureModel); + + const { + currentFields, + updateFieldValue, + createUpdateFieldValue, + updateInternalFieldValue, + } = useInternalCurrentFormState( + captureModel, + setCaptureModel, + currentView.type === 'model' ? currentView.fields : null + ); + + const { + currentSelectorPath, + currentSelector, + currentSelectorOriginalState, + setCurrentSelector, + updateCustomSelector, + } = useInternalCurrentSelectorState(captureModel, updateInternalFieldValue); + + const state: CaptureModelContext = useMemo( + () => ({ + captureModel, + currentView, + currentPath, + replacePath, + currentFields, + updateFieldValue, + createUpdateFieldValue, + currentSelectorPath, + currentSelector, + currentSelectorOriginalState, + setCurrentSelector, + updateCustomSelector, + }), + [ + captureModel, + createUpdateFieldValue, + currentFields, + currentPath, + currentSelector, + currentSelectorOriginalState, + currentSelectorPath, + currentView, + // @todo, these probably shouldn't need to be in here. + replacePath, + setCurrentSelector, + updateCustomSelector, + updateFieldValue, + ] + ); + + return ; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.md b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.md new file mode 100644 index 000000000..6d1f7ef74 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.md @@ -0,0 +1,23 @@ +# Capture model +You can use the simple `CaptureModel` provider and hooks to make it easier to write components that depend on the state of a particular capture model. + +To use the provider, you will need to wrap all or some of your component tree in the capture model provider, passing it your capture model. If this is managed by state in the component rendering, when it changes, everything using the hook below will update automatically. + +```jsx +function App() { + return ( + + + + ); +} +``` + +Then in your components you will have the `useCaptureModel()` hook available: + +```jsx +function TestComponent() { + const { captureModel } = useCaptureModel(); + return
{captureModel.structure.label}
+} +``` diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.stories.tsx new file mode 100644 index 000000000..aab00a6e1 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.stories.tsx @@ -0,0 +1,30 @@ +import { CaptureModel } from '../../types/capture-model'; +import { Card } from '../atoms/Card'; +import { CaptureModelProvider } from './capture-model-provider'; +import * as React from 'react'; +import { useCaptureModel } from './capture-model'; +// @ts-ignore +import notes from './capture-model.md'; + +const withSimpleCaptureModel = (Component: React.FC): React.FC => () => ( + + + +); + +export default { + title: 'Core/Capture models', + parameters: { notes }, +}; + +const model: CaptureModel = require('../../../../../../fixtures/simple.json'); + +export const Label: React.FC = withSimpleCaptureModel(() => { + const { captureModel } = useCaptureModel(); + + return ( + +

{captureModel.structure.label}

+
+ ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.ts new file mode 100644 index 000000000..1434bc2ae --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/capture-model.ts @@ -0,0 +1,9 @@ +import { UseCaptureModel } from '../../types/to-be-removed'; +import { useContext } from './context'; + +/** + * @deprecated + */ +export function useCaptureModel(): UseCaptureModel { + return useContext(); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context-editor.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context-editor.ts new file mode 100644 index 000000000..1c836c4f2 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context-editor.ts @@ -0,0 +1,94 @@ +// @todo Compact using context (immer) +// @todo un-Compact using context (immer) + +import { CaptureModel } from '../../types/capture-model'; + +export function addDefaultContext(model: CaptureModel, context: string): CaptureModel { + if ( + (typeof model.document['@context'] === 'string' && context !== model.document['@context']) || + (typeof model.document['@context'] !== 'string' && + model.document['@context'] && + model.document['@context']['@vocab'] !== context) + ) { + throw new Error('Cannot add default context, context already exists'); + } + + const { ['@context']: atContext, ...document } = model.document; + + return { + ...model, + document: { + '@context': atContext + ? { + '@vocab': context, + ...(!atContext || typeof atContext === 'string' ? {} : atContext), + } + : context, + ...document, + }, + }; +} + +export function addContext(model: CaptureModel, context: string, alias: string): CaptureModel { + const { ['@context']: atContext, ...document } = model.document; + const fullContext: { [vocab: string]: string } = + typeof atContext === 'string' ? { '@vocab': atContext } : atContext || {}; + + if (fullContext[alias] && fullContext[alias] !== context) { + throw new Error(`Cannot add context ${alias}, context already exists (${fullContext[alias]})`); + } + + return { + ...model, + document: { + '@context': { + ...fullContext, + [alias]: context, + }, + ...document, + }, + }; +} + +export function removeContext(model: CaptureModel, alias: string): CaptureModel { + if ( + !model.document['@context'] || + typeof model.document['@context'] === 'string' || + !model.document['@context'][alias] + ) { + // Unchanged. + return model; + } + + const { ['@context']: atContext, ...document } = model.document; + const { [alias]: _, ...ctx } = atContext; + + return { + ...model, + document: { + '@context': ctx, + ...document, + }, + }; +} + +export function removeDefaultContext(model: CaptureModel): CaptureModel { + if ( + !model.document['@context'] || + (typeof model.document['@context'] !== 'string' && !model.document['@context']['@vocab']) + ) { + return model; + } + + const { ['@context']: atContext, ...document } = model.document; + + if (typeof atContext === 'string') { + // Return without the context at all. + return { + ...model, + document, + }; + } + + return removeContext(model, '@vocab'); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context.ts new file mode 100644 index 000000000..b3c3b9761 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/context.ts @@ -0,0 +1,6 @@ +import { createContext } from '../../helpers/create-context'; +import { CaptureModelContext } from '../../types/to-be-removed'; + +const [useContext, InternalProvider] = createContext(); + +export { useContext, InternalProvider }; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.stories.tsx new file mode 100644 index 000000000..faf19d45e --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.stories.tsx @@ -0,0 +1,48 @@ +import { FieldSet } from '../components/FieldSet/FieldSet'; +import { CaptureModelProvider } from './capture-model-provider'; +import React, { useEffect } from 'react'; +import { useCurrentForm } from './current-form'; +import { useNavigation } from './navigation'; +import '../input-types/TextField/index'; + +const withSimpleCaptureModel = (Component: React.FC): React.FC => () => ( + + + +); + +export default { + title: 'Core/Current form', +}; + +export const SimpleForm: React.FC = withSimpleCaptureModel(() => { + const { currentFields, updateFieldValue } = useCurrentForm(); + const { replacePath } = useNavigation(); + + useEffect(() => { + replacePath([0, 0]); + }, [replacePath]); + + return ( +
( +
    +
  • + Label: {field.label} +
  • +
  • + Description: {field.description} +
  • +
  • + Type: {field.type} +
  • +
  • + Value: {typeof field.value === 'string' ? field.value : 'n/a'}{' '} + +
  • +
+ )} + /> + ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.ts new file mode 100644 index 000000000..e1c72b900 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-form.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import produce, { Draft, original } from 'immer'; +import { isEntity, isEntityList } from '../../helpers/is-entity'; +import { CaptureModel, ModelFields, NestedModelFields } from '../../types/capture-model'; +import { BaseField, NestedField } from '../../types/field-types'; +import { UseCurrentForm } from '../../types/to-be-removed'; +import { useContext } from './context'; + +/** + * @deprecated use revisions instead + */ +export function useCurrentForm(): UseCurrentForm { + const { currentFields, updateFieldValue, createUpdateFieldValue } = useContext(); + + return useMemo(() => ({ currentFields, updateFieldValue, createUpdateFieldValue }), [ + currentFields, + updateFieldValue, + createUpdateFieldValue, + ]); +} + +/** + * @deprecated use revisions instead + */ +export const createFormFieldReducer = (document: Doc) => ( + acc: NestedField, + next: string | NestedModelFields +): NestedField => { + if (typeof next === 'string') { + const nextItem = document.properties[next]; + + if (!nextItem) { + console.log(next, document); + throw new Error(`Invalid structure, ${next} does not exist in document`); + } + + if (nextItem.length > 0) { + if (isEntityList(nextItem)) { + // @ts-ignore + acc.push({ + type: 'documents', + // @ts-ignore + list: nextItem.map((singleDoc: CaptureModel['document']) => { + const { properties: _, ...doc } = singleDoc; + return { + ...doc, + fields: Object.keys(singleDoc.properties).reduce(createFormFieldReducer(singleDoc), []), + }; + }), + }); + } else { + acc.push({ + type: 'fields', + list: document.properties[next] as BaseField[], + }); + } + } + return acc; + } + const [key, fields] = next; + + if (typeof (key as unknown) !== 'string' || !Array.isArray(fields)) { + throw new Error('Invalid capture model. Expected: [string, [string, string]]'); + } + + // @todo verify this is an array of documents, otherwise invalid capture model. + const nestedDocs = document.properties[key] as any; + + acc.push({ + type: 'documents', + list: nestedDocs.map((singleDoc: CaptureModel['document']) => { + const { properties: _, ...doc } = singleDoc; + return { + ...doc, + fields: fields.reduce(createFormFieldReducer(singleDoc), []), + }; + }), + }); + + return acc; +}; + +/** + * @deprecated use revisions instead + */ +export function useInternalCurrentFormState( + captureModel: CaptureModel, + updateCaptureModel: (newModel: CaptureModel) => void, + currentFieldIds: ModelFields | null +): UseCurrentForm & { + updateInternalFieldValue: ( + path: Array<[string, number]>, + cb: (field: Draft, draft: Draft) => void + ) => void; +} { + const [currentFields, setCurrentFields] = useState>([]); + + useEffect(() => { + if (currentFieldIds) { + setCurrentFields(currentFieldIds.reduce(createFormFieldReducer(captureModel.document), [])); + } + }, [captureModel, captureModel.document, currentFieldIds]); + + const fieldValueProducer = useCallback( + ( + model: CaptureModel, + path: Array<[string, number]>, + cb: (field: Draft, draft: Draft) => void + ) => { + updateCaptureModel( + produce((draft: Draft) => { + let cursor: CaptureModel['document'] | BaseField = draft.document; + for (const [term, idx] of path) { + if (isEntity(cursor)) { + cursor = cursor.properties[term][idx]; + } else { + throw Error(`Invalid update path ${path.map(([a, b]) => `${a}:${b}`).join('/')}`); + } + } + const field = cursor as BaseField; + + if (!original(field)) { + throw new Error(`Invalid update path ${path.map(([a, b]) => `${a}:${b}`).join('/')}`); + } + + cb(field, draft); + // draft. + })(model) + ); + }, + // @todo updateCaptureModel is from a useState call, and is usually excluded from + // this deps array. In here it is passed in, which is bad. This could be made + // better. + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const updateInternalFieldValue = useCallback( + (path: Array<[string, number]>, cb: (field: Draft, draft: Draft) => void) => { + if (currentFieldIds === null) { + throw new Error('No form selected'); + } + fieldValueProducer(captureModel, path, cb); + }, + [captureModel, currentFieldIds, fieldValueProducer] + ); + + const updateFieldValue = useCallback( + (path: Array<[string, number]>, value: any) => + updateInternalFieldValue(path, field => { + field.value = value; + }), + [updateInternalFieldValue] + ); + + const createUpdateFieldValue = useCallback( + (path: Array<[string, number]>) => { + return (innerPath: Array<[string, number]>, value: any) => { + return updateFieldValue([...path, ...innerPath], value); + }; + }, + [updateFieldValue] + ); + + return { + currentFields, + updateFieldValue, + createUpdateFieldValue, + updateInternalFieldValue, + }; +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.stories.tsx new file mode 100644 index 000000000..ac7d2d2b8 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.stories.tsx @@ -0,0 +1,76 @@ +import { CaptureModelProvider } from './capture-model-provider'; +import * as React from 'react'; +import { useEffect } from 'react'; +import { useNavigation } from './navigation'; +import { useCurrentSelector } from './current-selector'; +import { BoxSelectorProps } from '../selector-types/BoxSelector/BoxSelector'; + +const withSimpleCaptureModel = (Component: React.FC): React.FC => () => ( + + + +); + +export default { + title: 'Core/Current selector', +}; + +export const SimpleSelector: React.FC = withSimpleCaptureModel(() => { + const { + currentSelectorPath, + currentSelector, + updateSelector, + confirmSelector, + resetSelector, + setCurrentSelector, + } = useCurrentSelector(); + + const { replacePath } = useNavigation(); + + useEffect(() => { + replacePath([0, 0]); + }, [replacePath, setCurrentSelector]); + + if (!currentSelectorPath) { + return ( +
+ +
+ ); + } + + // @todo this is a general problem with the @types. + const selectorState = currentSelector ? (currentSelector as BoxSelectorProps['state']) : null; + + return ( +
+
    +
  • + Current path: + {currentSelectorPath && currentSelectorPath.map((r: any) => `${r[0]}[${r[1]}]`)} +
  • +
  • + Current selector: + {selectorState && + `${selectorState.x}, ${selectorState.y}, ${selectorState.width}, + ${selectorState.height}`} +
  • + + + + +
+
+ ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.ts new file mode 100644 index 000000000..f324b8385 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/current-selector.ts @@ -0,0 +1,131 @@ +import { Draft } from 'immer'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { CaptureModel } from '../../types/capture-model'; +import { BaseField } from '../../types/field-types'; +import { BaseSelector } from '../../types/selector-types'; +import { CurrentSelectorState, UseCurrentSelector } from '../../types/to-be-removed'; +import { useContext } from './context'; + +/** + * @deprecated use revisions instead + */ +export function useCurrentSelector(): UseCurrentSelector { + const { + currentSelectorPath, + currentSelector, + setCurrentSelector, + updateCustomSelector, + currentSelectorOriginalState, + } = useContext(); + + const confirmSelector = useCallback(() => { + // sets the current selector to null + setCurrentSelector(null); + }, [setCurrentSelector]); + + const updateSelector = useCallback( + (state: BaseSelector['state'], confirm = false) => { + if (currentSelectorPath) { + updateCustomSelector(currentSelectorPath, state); + if (confirm) { + confirmSelector(); + } + } + }, + [confirmSelector, currentSelectorPath, updateCustomSelector] + ); + + const resetSelector = useCallback(() => { + // sets the selector to `currentSelectorOriginalState` + updateSelector(currentSelectorOriginalState, true); + }, [currentSelectorOriginalState, updateSelector]); + + return useMemo( + () => ({ + currentSelectorPath, + confirmSelector, + currentSelector, + currentSelectorOriginalState, + resetSelector, + setCurrentSelector, + updateCustomSelector, + updateSelector, + }), + [ + currentSelectorPath, + confirmSelector, + currentSelector, + currentSelectorOriginalState, + resetSelector, + setCurrentSelector, + updateCustomSelector, + updateSelector, + ] + ); +} + +/** + * @deprecated use revisions instead + */ +export function useInternalCurrentSelectorState( + captureModel: CaptureModel, + updateField: ( + path: Array<[string, number]>, + cb: (field: Draft, draft: Draft) => void + ) => void +): CurrentSelectorState { + const [currentSelectorPath, setCurrentSelector] = useState | null>(null); + + const [currentSelectorOriginalState, setCurrentSelectorOriginalState] = useState(null); + + const [currentSelector, setCurrentSelectorObj] = useState(); + + // @todo there is a gap for fields that want to provide selectors. We need the + // "currentSelector" to be the source of truth for if there is currently a + // selector, and then a wrapper to set the selector object to whatever a field + // may want. The API will provide a callback for when a selector is saved, + // or could be updated through props. Either a push or pull interaction. + + const fieldSelector = useMemo(() => { + if (!currentSelectorPath) { + return null; + } + return (currentSelectorPath.reduce((acc: CaptureModel['document'], [path, idx]) => { + return acc.properties[path][idx] as CaptureModel['document']; + }, captureModel.document) as CaptureModel['document'] | BaseField).selector; + }, [captureModel.document, currentSelectorPath]); + + useEffect(() => { + setCurrentSelectorObj(fieldSelector ? fieldSelector.state : null); + }, [fieldSelector]); + + useEffect(() => { + if (currentSelectorPath && fieldSelector) { + setCurrentSelectorOriginalState(fieldSelector ? fieldSelector.state : null); + } + // This shouldn't change if the document changes. The doc will + // change quite often, but this only needs to track the original + // state when the selector path changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentSelectorPath]); + + const updateCustomSelector = useCallback( + (path: Array<[string, number]>, value: Selector['state']) => { + // Updates the capture model. + updateField(path, field => { + if (field.selector) { + field.selector.state = value; + } + }); + }, + [updateField] + ); + + return { + currentSelectorPath, + currentSelector, + currentSelectorOriginalState, + updateCustomSelector, + setCurrentSelector, + }; +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.stories.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.stories.tsx new file mode 100644 index 000000000..53dfb1ed5 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.stories.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Button } from '../atoms/Button'; +import { Choice } from '../components/Choice/Choice'; +import { CaptureModelProvider } from './capture-model-provider'; +import { useNavigation as legacyUseNavigation } from './navigation'; +import { ThemeProvider } from 'styled-components'; +import { defaultTheme } from '../themes'; +import { BackgroundSplash } from '../components/BackgroundSplash/BackgroundSplash'; +import { RoundedCard } from '../components/RoundedCard/RoundedCard'; +import { useNavigation } from '../hooks/useNavigation'; + +const simple = require('../../../../../../fixtures/simple.json'); + +const withSimpleCaptureModel = (Component: React.FC): React.FC => () => ( + + + + + +); + +export default { + title: 'Core/Navigation', +}; + +export const UsingComponent: React.FC = withSimpleCaptureModel(() => { + const [currentView, { pop, push, idStack }] = useNavigation(simple.structure); + + if (currentView.type !== 'choice') { + return ( + +

+ We are on {currentView.label} +

+ +
+ ); + } + + return ; +}); + +export const LegacyHook: React.FC = withSimpleCaptureModel(() => { + const { currentView, pushPath, currentPath, popPath } = legacyUseNavigation(); + + if (!currentView) { + return null; + } + + return ( + + {currentPath.length ? : null} + + {currentView.type === 'choice' ? ( + currentView.items.map((item: any, idx: number) => ( + pushPath(idx)}> + {item.description} + + )) + ) : ( + We are on a form! + )} + + + ); +}); diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.ts new file mode 100644 index 000000000..5156d89ec --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/navigation.ts @@ -0,0 +1,69 @@ +import { useCallback, useMemo, useState } from 'react'; +import { CaptureModel } from '../../types/capture-model'; +import { UseNavigation } from '../../types/to-be-removed'; +import { useContext } from './context'; + +/** + * @deprecated use self-contained ./hooks/use-navigation.ts + */ +export function useNavigation(): UseNavigation { + const { currentView, currentPath, replacePath } = useContext(); + + const pushPath = useCallback( + (index: number) => { + replacePath([...currentPath, index]); + }, + [currentPath, replacePath] + ); + + const popPath = useCallback(() => { + replacePath(currentPath.slice(0, currentPath.length - 1)); + }, [currentPath, replacePath]); + + const resetPath = useCallback(() => { + replacePath([]); + }, [replacePath]); + + return useMemo( + () => ({ + currentView, + currentPath, + pushPath, + popPath, + replacePath, + resetPath, + }), + [currentView, currentPath, pushPath, popPath, replacePath, resetPath] + ); +} + +/** + * @deprecated use self-contained ./hooks/use-navigation.ts + */ +export function useInternalNavigationState(captureModel: CaptureModel) { + // Navigation actions. + const [currentView, setCurrentView] = useState(() => captureModel.structure); + const [currentPath, replacePathRaw] = useState([]); + + const replacePath = useCallback( + (path: number[]) => { + setCurrentView( + path.reduce((level, next) => { + if (level.type === 'choice') { + return level.items[next]; + } + return level; + }, captureModel.structure) + ); + replacePathRaw(path); + }, + // @todo The structure should never change. Check if this will cause any issues. + [captureModel.structure] + ); + + return { + currentPath, + replacePath, + currentView, + }; +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/core/structure-editor.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/structure-editor.ts new file mode 100644 index 000000000..c2e44f9ec --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/core/structure-editor.ts @@ -0,0 +1,187 @@ +import { isEntity } from '../../helpers/is-entity'; +import { CaptureModel, ModelFields, NestedModelFields } from '../../types/capture-model'; + +type FlatStructureDefinition = + | { + label?: string; + type: 'model'; + key: string[]; + fields: FlatStructureDefinition[]; + } + | { + label?: string; + type: Exclude; + key: string[]; + }; + +export function getDocumentFields( + document: CaptureModel['document'], + rootKeys: string[] = [] +): FlatStructureDefinition { + const structure: FlatStructureDefinition = { + label: document.label, + type: 'model', + key: rootKeys, + fields: [], + }; + for (const key of Object.keys(document.properties)) { + // @todo validation of values to make sure they are all the same type. + const values = document.properties[key]; + if (!values || values.length === 0) continue; + const value = values[0]; + + // we have a key. + structure.fields.push( + isEntity(value) + ? getDocumentFields(value, [...rootKeys, key]) + : { + label: value.label, + type: value.type, + key: [...rootKeys, key], + } + ); + } + + return structure; +} + +export function structureToFlatStructureDefinition( + document: CaptureModel['document'], + modelFields: ModelFields, + structures: FlatStructureDefinition[] = [], + keyScope: string[] = [] +): FlatStructureDefinition[] { + modelFields.reduce((acc, field) => { + if (typeof field === 'string') { + const fullFieldList = document.properties[field]; + // @todo validation? + if (!fullFieldList || !fullFieldList.length) return acc; + const fullField = fullFieldList[0]; + acc.push({ + key: [...keyScope, field], + type: fullField.type, + label: fullField.label, + }); + } + + const [modelKey, fields] = field as [string, ModelFields]; + const fullFieldList = document.properties[modelKey]; + // @todo validation? + if (!fullFieldList || !fullFieldList.length) return acc; + + const nestedModel = fullFieldList[0] as CaptureModel['document']; + + // for [a, [b, c]] + // We want to get [a, b] and [a, c] extracted + return structureToFlatStructureDefinition(nestedModel, fields, structures, [...keyScope, modelKey]); + }, structures); + + return structures; +} + +export function expandModelFields(fields: ModelFields): string[][] { + const reducer = (prefix: string[]) => (acc: string[][], next: string | NestedModelFields): string[][] => { + if (typeof next === 'string') { + acc.push([...prefix, next]); + return acc; + } + const [name, nextFields] = next; + + if (typeof nextFields === 'undefined') { + throw new Error(`Invalid model fields at level ${name} (${JSON.stringify(fields, null, 2)})`); + } + + return nextFields.reduce(reducer([...prefix, name]), acc); + }; + + return fields.reduce(reducer([]), []); +} + +export function mergeFlatKeys(inputKeys: string[][]): ModelFields { + const keyHash = inputKeys.map(k => k.join('--HASH--')); + const keys = inputKeys.filter((k, i) => keyHash.indexOf(k.join('--HASH--')) === i); + const array: ModelFields = []; + const uniqueKeys = []; + const entityBuffer: { key: string; values: string[][] } = { + key: '', + values: [], + }; + const entityMap: { [key: string]: number } = {}; + const flushBuffer = () => { + // Flush last. + if (entityBuffer.key) { + const existing = entityMap[entityBuffer.key]; + // flush the buffer. + if (typeof existing !== 'undefined') { + // Existing entity + const item = array[existing] as [string, ModelFields]; + item[1].push(...mergeFlatKeys(entityBuffer.values)); + } else { + // new entity. + array.push([entityBuffer.key, mergeFlatKeys(entityBuffer.values)]); + entityMap[entityBuffer.key] = array.length - 1; + } + // reset the buffer. + entityBuffer.key = ''; + entityBuffer.values = []; + } + }; + + for (const key of keys) { + if (key.length === 0) continue; + // Flush + if (entityBuffer.key !== key[0]) { + flushBuffer(); + } + // For top level fields. + if (key.length === 1) { + if (uniqueKeys.indexOf(key[0]) !== -1) continue; + uniqueKeys.push(key[0]); + array.push(key[0]); + continue; + } + const [entity, ...path] = key; + entityBuffer.key = entity; + entityBuffer.values.push(path); + } + // Flush last. + flushBuffer(); + + return array; +} + +export function documentFieldOptionsToStructure(definitions: FlatStructureDefinition[]): ModelFields { + const flatKeys = []; + for (const def of definitions) { + flatKeys.push(def.key); + } + + return mergeFlatKeys(flatKeys); +} + +export function structureToTree(level: CaptureModel['structure'], keyAcc: number[] = []): any | null { + switch (level.type) { + case 'choice': + return { + id: keyAcc.length ? keyAcc.join('--') : 'root', + icon: 'folder-close', + label: level.label, + nodeData: { ...level, key: keyAcc }, + isExpanded: true, + childNodes: level.items + .map((choiceItem, choiceKey) => structureToTree(choiceItem, [...keyAcc, choiceKey])) + .filter(e => e) as any[], + }; + + case 'model': + return { + id: keyAcc.length ? keyAcc.join('--') : 'root', + icon: 'form', + label: level.label, + nodeData: { ...level, key: keyAcc }, + }; + + default: + return null; + } +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useChoiceRevisions.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useChoiceRevisions.ts new file mode 100644 index 000000000..821d4e220 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useChoiceRevisions.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; +import { Revisions } from '../stores/revisions'; + +export const useChoiceRevisions = (choiceId: string) => { + const revisions = Revisions.useStoreState(s => s.revisions); + + return useMemo( + () => + Object.keys(revisions) + .map(revId => revisions[revId]) + .filter(rev => rev.revision.structureId === choiceId), + [choiceId, revisions] + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useMiniRouter.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useMiniRouter.ts new file mode 100644 index 000000000..115c44c7d --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useMiniRouter.ts @@ -0,0 +1,14 @@ +import { useMemo, useState } from 'react'; + +export function useMiniRouter(routes: T[], defaultRoute: T) { + const [current, setCurrent] = useState(defaultRoute); + const router = useMemo(() => { + return routes.reduce((acc, next: T) => { + acc[next] = () => setCurrent(next); + return acc; + }, {} as any) as { [key in T]: () => void }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return [current, router, setCurrent] as const; +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useNavigation.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useNavigation.ts new file mode 100644 index 000000000..73f5b8fe2 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useNavigation.ts @@ -0,0 +1,34 @@ +import { Revisions } from '../stores/revisions/index'; + +export function useNavigation() { + const { currentStructure, currentId, structureMap, idStack, choiceStack } = Revisions.useStoreState(state => { + return { + currentStructure: state.currentStructure, + currentId: state.currentStructureId, + idStack: state.idStack, + structureMap: state.structureMap, + choiceStack: state.choiceStack, + }; + }); + + const { goTo, pop, push } = Revisions.useStoreActions(actions => { + return { + goTo: actions.goToStructure, + push: actions.pushStructure, + pop: actions.popStructure, + }; + }); + + return [ + currentStructure, + { + currentId, + goTo, + push, + pop, + idStack, + choiceStack, + structureMap, + }, + ] as const; +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useRevisionSubtree.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useRevisionSubtree.ts new file mode 100644 index 000000000..2f6eb7ad8 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useRevisionSubtree.ts @@ -0,0 +1,7 @@ +import { Revisions } from '../stores/revisions/index'; + +function useRevisionSubtree() { + return Revisions.useStoreState(s => { + return { currentEntity: s.revisionSubtree, currentField: s.revisionSubtreeField }; + }); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useTreeNode.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useTreeNode.ts new file mode 100644 index 000000000..39ade6e78 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useTreeNode.ts @@ -0,0 +1,39 @@ +import produce, { Draft } from 'immer'; +import { useState } from 'react'; + +type ITreeNode = any; + +export const useTreeNode = (createInitial: () => ITreeNode[]) => { + const [nodes, setNodes] = useState(createInitial); + + const mutateAllPoints = (mutation: (node: Draft) => void) => { + setNodes( + produce(nodesDraft => { + const handleNode = (node: Draft) => { + mutation(node); + if (node.childNodes) { + node.childNodes.forEach((childNode: any) => { + handleNode(childNode); + }); + } + }; + handleNode(nodesDraft); + }) + ); + }; + + const mutatePoint = ([i, ...path]: number[], mutation: (node: Draft) => void) => { + setNodes( + produce(nodesDraft => { + mutation( + path.reduce((acc: ITreeNode, next: number) => { + if (!acc.childNodes) return acc; + return acc.childNodes[next]; + }, nodesDraft[i]) + ); + })(nodes) + ); + }; + + return { nodes, setNodes, mutatePoint, mutateAllPoints }; +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useUnmount.ts b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useUnmount.ts new file mode 100644 index 000000000..5d55ef6e4 --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/hooks/useUnmount.ts @@ -0,0 +1,15 @@ +import { useCallback, useEffect, useRef } from 'react'; + +export function useUnmount(callback: () => void, deps: any[] = []) { + const unmount = useRef(); + + unmount.current = useCallback(callback, deps); + + useEffect(() => { + return () => { + if (unmount.current) { + unmount.current(); + } + }; + }, []); +} diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.editor.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.editor.tsx new file mode 100644 index 000000000..7d4747e0f --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.editor.tsx @@ -0,0 +1,63 @@ +import { Field } from 'formik'; +import React from 'react'; +import { + StyledFormField, + StyledFormLabel, + StyledFormInputElement, + StyledCheckbox, + StyledFormInput, +} from '../../atoms/StyledForm'; +import { useTranslation } from 'react-i18next'; + +type Props = { + dataSource: string; + placeholder?: string; + clearable: boolean; + requestInitial: boolean; +}; + +const AutocompleteFieldEditor: React.FC = props => { + const { t } = useTranslation(); + return ( + <> + + + {t('Data source')} + + + + + + {t('Placeholder')} + + + + + + + {t('Allow clearing of selection')} + + + + + + {t('Make initial search')} + + + + ); +}; + +export default AutocompleteFieldEditor; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.preview.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.preview.tsx new file mode 100644 index 000000000..f964d32ec --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.preview.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Tag } from '../../atoms/Tag'; +import { AutocompleteFieldProps } from './AutocompleteField'; + +export const AutocompleteFieldPreview: React.FC = ({ value }) => { + if (!value) { + return No value; + } + + return ( + <> + {value.label} + {value.resource_class ? {value.resource_class} : null} + + + ); +}; diff --git a/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx new file mode 100644 index 000000000..30b7d307b --- /dev/null +++ b/services/madoc-ts/src/frontend/shared/capture-models/editor/input-types/AutocompleteField/AutocompleteField.tsx @@ -0,0 +1,121 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { Select } from 'react-functional-select'; +import { BaseField, FieldComponent } from '../../../types/field-types'; +import { ErrorMessage } from '../../atoms/Message'; +import { Tag } from '../../atoms/Tag'; +import { useTranslation } from 'react-i18next'; + +export interface AutocompleteFieldProps extends BaseField { + type: 'autocomplete-field'; + value: { uri: string; label: string; resource_class?: string } | undefined; + placeholder?: string; + clearable?: boolean; + requestInitial?: boolean; + dataSource: string; +} + +export type CompletionItem = { + uri: string; + label: string; + resource_class?: string; + score?: number; +}; + +function renderOptionLabel(option: CompletionItem) { + return ( + <> + {option.label} + {option.resource_class ? {option.resource_class} : null} + + ); +} + +export const AutocompleteField: FieldComponent = props => { + const { t } = useTranslation(); + const [options, setOptions] = useState(props.value ? [props.value] : []); + const [isLoading, setIsLoading] = useState(false); + const [hasFetched, setHasFetched] = useState(false); + const [error, setError] = useState(''); + + const onOptionChange = (option: CompletionItem | undefined) => { + if (!option) { + props.updateValue(undefined); + return; + } + + if (!props.value || option.uri !== props.value.uri) { + props.updateValue( + option ? { label: option.label, resource_class: option.resource_class, uri: option.uri } : undefined + ); + } + }; + + const onInputChange = () => { + setIsLoading(true); + }; + + const onSearchChange = useCallback( + (value: string | undefined) => { + if (value || props.requestInitial) { + if (hasFetched && props.dataSource.indexOf('%') === -1) { + setIsLoading(false); + return; + } + // Make API Request. + fetch(`${props.dataSource}`.replace(/%/, value || '')) + .then(r => r.json() as Promise<{ completions: CompletionItem[] }>) + .then(items => { + setOptions(items.completions); + setIsLoading(false); + setHasFetched(true); + setError(''); + }) + .catch(() => { + setError(t('There was a problem fetching results')); + }); + } + }, + [props.requestInitial, props.dataSource, t] + ); + + useEffect(() => { + if (props.requestInitial) { + onSearchChange(props.value?.uri || ''); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.requestInitial]); + + return ( + <> +