diff --git a/lxljs/vocab.js b/lxljs/vocab.js index b0fe7b396..e951140b2 100644 --- a/lxljs/vocab.js +++ b/lxljs/vocab.js @@ -172,6 +172,9 @@ export function getRecordType(mainEntityType, vocab, context) { // Mostly added as to not throw an error while the data is being picked up. return null; } + if (isSubClassOf(mainEntityType, 'SingleItem', vocab, context)) { + return 'SingleItem'; + } if (isSubClassOf(mainEntityType, 'Item', vocab, context)) { return 'Item'; } diff --git a/vue-client/src/App.vue b/vue-client/src/App.vue index 68b579bbd..e6604729d 100644 --- a/vue-client/src/App.vue +++ b/vue-client/src/App.vue @@ -847,7 +847,7 @@ body { } } } - input, textarea, img, select, .ItemEntity, .ItemLocal, .EntityAction, .ItemSibling, .icon, .icon i, li, i.fa, div { + input, textarea, img, select, .ItemEntity, .ItemLocal, .EntityAction, .icon, .icon i, li, i.fa, div { &:focus { .focus-mixin-border(); } diff --git a/vue-client/src/components/inspector/entity-adder.vue b/vue-client/src/components/inspector/entity-adder.vue index 42097892c..68b3ecf6d 100644 --- a/vue-client/src/components/inspector/entity-adder.vue +++ b/vue-client/src/components/inspector/entity-adder.vue @@ -383,29 +383,6 @@ export default { addToHistory: true, }); }, - addSibling(obj) { - const linkObj = { '@id': `${this.inspector.data.record['@id']}#work` }; - const workObj = obj; - workObj['@id'] = linkObj['@id']; - - this.$store.dispatch('setInspectorStatusValue', { - property: 'lastAdded', - value: 'work', - }); - this.$store.dispatch('updateInspectorData', { - changeList: [ - { - path: `${this.path}`, - value: linkObj, - }, - { - path: 'work', - value: workObj, - }, - ], - addToHistory: true, - }); - }, addEmpty(typeId) { this.hide(); const shortenedType = StringUtil.getCompactUri(typeId, this.resources.context); diff --git a/vue-client/src/components/inspector/field.vue b/vue-client/src/components/inspector/field.vue index d28633b2f..0b4c4ada9 100644 --- a/vue-client/src/components/inspector/field.vue +++ b/vue-client/src/components/inspector/field.vue @@ -19,7 +19,6 @@ import ItemLocal from './item-local.vue'; import ItemError from './item-error.vue'; import ItemVocab from './item-vocab.vue'; import ItemType from './item-type.vue'; -import ItemSibling from './item-sibling.vue'; import ItemBoolean from './item-boolean.vue'; import ItemNumeric from './item-numeric.vue'; import ItemGrouped from './item-grouped.vue'; @@ -131,7 +130,6 @@ export default { activeModal: false, removeHover: false, pasteHover: false, - foundChip: false, removed: false, uniqueIds: [], }; @@ -141,7 +139,6 @@ export default { 'item-entity': ItemEntity, 'item-value': ItemValue, 'item-local': ItemLocal, - 'item-sibling': ItemSibling, 'item-error': ItemError, 'item-vocab': ItemVocab, 'item-boolean': ItemBoolean, @@ -269,7 +266,8 @@ export default { this.resources.context, this.resources.vocabClasses, ).map((item) => StringUtil.getCompactUri(item, this.resources.context)); - return fetchedRange; + const unselectableTypes = Object.values(this.settings.extractableMappedTypes); + return fetchedRange.filter((t) => !unselectableTypes.includes(t)); }, allSearchTypes() { if (this.allValuesFrom.length > 0) { @@ -376,15 +374,6 @@ export default { } return `${this.parentPath}.${this.fieldKey}`; }, - isMarc() { - return this.fieldKey.startsWith('marc:'); - }, - isChild() { - if (this.parentPath !== 'mainEntity') { - return true; - } - return false; - }, isLangMapWithPartner() { return this.isLangMap && this.hasProp; }, @@ -399,12 +388,10 @@ export default { ); }, isCompositional() { - if (this.keyAsVocabProperty && this.keyAsVocabProperty.hasOwnProperty('category')) { - if (this.keyAsVocabProperty.category['@id'] === 'https://id.kb.se/vocab/compositional') { - return true; - } - } - return false; + return VocabUtil.hasCategory(this.keyAsVocabProperty, 'compositional', this.resources); + }, + isHistoryView() { + return this.diff !== null; }, hasSingleValue() { if (!isArray(this.fieldValue) || this.fieldValue.length === 1) { @@ -560,15 +547,12 @@ export default { if (this.fieldKey === '@type' || VocabUtil.getContextValue(this.fieldKey, '@type', this.resources.context) === '@vocab') { return 'vocab'; } + if (this.isPlainObject(o) && (!this.isLinked(o) || this.isInlined(o))) { + return 'local'; + } if (this.isPlainObject(o) && this.isLinked(o)) { return 'entity'; } - if (this.isPlainObject(o) && o.hasOwnProperty('@id') && this.isInGraph(o)) { - return 'sibling'; - } - if (this.isPlainObject(o) && !this.isLinked(o)) { - return 'local'; - } if (this.range && this.range.length > 0 && this.range.every((r) => Object.keys(VocabUtil.XSD_NUMERIC_TYPES).includes(r))) { return 'numeric'; } @@ -584,21 +568,11 @@ export default { if (typeof o === 'undefined') { throw new Error('Cannot check link status of undefined object.'); } - if (o.hasOwnProperty('@id') && !o.hasOwnProperty('@type')) { - return true; - } - return false; + return o.hasOwnProperty('@id'); }, - isInGraph(o) { - const data = this.inspector.data; - for (const point in data) { - if (data[point] !== null) { - if (data[point]['@id'] === o['@id']) { - return true; - } - } - } - return false; + isInlined(o) { + // FIXME + return this.isLinked(o) && Object.keys(o).length > 1 && this.isCompositional && !this.isHistoryView; }, isEmbedded(o) { const type = o['@type']; @@ -607,13 +581,6 @@ export default { } return VocabUtil.isEmbedded(type, this.resources.vocab, this.settings); }, - isChip(item) { - if (this.getDatatype(item) === 'entity') { - this.foundChip = true; - return true; - } - return false; - }, highLightLastAdded() { if (this.isLastAdded === true) { if (this.fieldValue === null || (isArray(this.fieldValue) && this.fieldValue.length === 0)) { @@ -948,7 +915,7 @@ export default { :diff="diff" :parent-path="path" /> - + - - diff --git a/vue-client/src/components/inspector/item-entity.vue b/vue-client/src/components/inspector/item-entity.vue index 419cdc5d6..71e904490 100644 --- a/vue-client/src/components/inspector/item-entity.vue +++ b/vue-client/src/components/inspector/item-entity.vue @@ -78,6 +78,9 @@ export default { isCardWithData() { return this.isCard && this.focusData && Object.keys(this.focusData).length > 1; }, + isHistoryView() { + return this.diff != null; + }, }, watch: { 'inspector.event'(val) { @@ -128,9 +131,6 @@ export default { removeFocus() { this.focused = false; }, - isHistoryView() { - return this.diff != null; - }, }, components: { Menu, @@ -138,7 +138,7 @@ export default { ReverseRelations, }, created() { - if (this.$store.state.settings.defaultExpandedProperties.includes(this.fieldKey)) { + if (this.$store.state.settings.defaultExpandedProperties.includes(this.fieldKey) && !this.isHistoryView) { this.expand(); } }, @@ -151,7 +151,7 @@ export default { } }).catch((error) => console.error(error)); } - if (this.isExpanded && !this.isHistoryView()) { + if (this.isExpanded && !this.isHistoryView) { this.expand(); } if (this.isNewlyAdded) { diff --git a/vue-client/src/components/inspector/item-local.vue b/vue-client/src/components/inspector/item-local.vue index 687af16be..5335dcb27 100644 --- a/vue-client/src/components/inspector/item-local.vue +++ b/vue-client/src/components/inspector/item-local.vue @@ -6,9 +6,12 @@ import * as VocabUtil from 'lxljs/vocab'; import * as DisplayUtil from 'lxljs/display'; import * as StringUtil from 'lxljs/string'; import * as LayoutUtil from '@/utils/layout'; +import * as HttpUtil from "@/utils/http"; +import { DELETE_ON_SAVE } from '@/store'; import { translatePhrase, labelByLang, capitalize } from '@/utils/filters'; import PropertyAdder from '@/components/inspector/property-adder.vue'; import EntityAction from '@/components/inspector/entity-action.vue'; +import IdPill from '@/components/shared/id-pill.vue'; import SearchWindow from './search-window.vue'; import ItemMixin from '../mixins/item-mixin.vue'; import LensMixin from '../mixins/lens-mixin.vue'; @@ -69,7 +72,6 @@ export default { }, data() { return { - inEdit: false, showCardInfo: false, extractDialogActive: false, propertyAdderOpened: false, @@ -150,6 +152,20 @@ export default { } return false; }, + canExtractMappedType() { + // TODO open up for all users at some point + if (this.user.getActiveLibraryUri() !== 'https://libris.kb.se/library/S') { + return false; + } + + return this.extractableMappedType && !this.isInlinedRecord; + }, + extractableMappedType() { + return this.settings.extractableMappedTypes[this.item['@type']]; + }, + isInlinedRecord() { + return this.item.hasOwnProperty('@id'); + }, getPath() { if (this.inArray) { return `${this.parentPath}[${this.index}]`; @@ -225,9 +241,6 @@ export default { this.expand(); } }, - isHolding() { - return this.inspector.data.mainEntity['@type'] === 'Item'; - }, isHistoryView() { return this.diff !== null; }, @@ -249,12 +262,6 @@ export default { closeExtractDialog() { this.extractDialogActive = false; }, - openForm() { - this.inEdit = true; - }, - closeForm() { - this.inEdit = false; - }, addFocus() { this.focused = true; }, @@ -296,7 +303,13 @@ export default { }, cloneThis() { const parentData = cloneDeep(get(this.inspector.data, this.parentPath)); - parentData.push(this.item); + if (this.isInlinedRecord) { + const o = cloneDeep(this.item); + delete o['@id']; + parentData.push(o); + } else { + parentData.push(this.item); + } this.$store.dispatch('setInspectorStatusValue', { property: 'lastAdded', @@ -328,6 +341,13 @@ export default { copyThis() { const userStorage = cloneDeep(this.userStorage); userStorage.copyClipboard = this.item; + if (this.isInlinedRecord) { + const o = cloneDeep(this.item); + delete o['@id']; + userStorage.copyClipboard = o; + } else { + userStorage.copyClipboard = this.item; + } this.$store.dispatch('setUserStorage', userStorage); this.$store.dispatch( 'pushNotification', @@ -352,6 +372,24 @@ export default { } }); }, + removeThisAndDeleteLinked() { + HttpUtil.getRelatedRecords({ o: this.item['@id'], _limit: 0 }, this.settings.apiPath) + .then((response) => { + // Before queuing the deletion of the linked record, check if there are any other + // records linking to it. If there are other links we won't be able to delete it. + // We assume that if there is exactly one link it is our own link. + if (response.totalItems === 1) { + const extraChangeList = [{ path: DELETE_ON_SAVE, id: this.item['@id'], type: this.item['@type'] }]; + this.removeThis(true, extraChangeList); + } else { + this.$store.dispatch('pushNotification', { type: 'danger', message: `${translatePhrase('Forbidden')} - ${translatePhrase('This entity may have active links')}` }); + } + }, (error) => { + const msg = 'Error checking for relations'; + this.$store.dispatch('pushNotification', { type: 'danger', message:`${translatePhrase(msg)} - ${error?.statusText || ''}` }); + console.log(msg, error); + }); + }, }, watch: { 'inspector.status.editing'(val) { @@ -455,6 +493,7 @@ export default { 'property-adder': PropertyAdder, 'search-window': SearchWindow, 'entity-action': EntityAction, + 'id-pill': IdPill, }, }; @@ -504,7 +543,7 @@ export default { :title="item['@type']"> - {{ translatePhrase("The work is extracted once the instance is saved") }} + {{ translatePhrase("Extracted when the record is saved") }} {{ capitalize(labelByLang(item['@type'])) }}: @@ -520,6 +559,11 @@ export default { +