Skip to content
Snippets Groups Projects
Select Git revision
  • master
1 result

903.nfa

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    WrapperInput.vue 20.40 KiB
    <template>
      <div
        v-if="componentDefinition !== null && values"
        :class="componentDefinition"
        :style="cssProps"
      >
        <b-form-group
          :class="{
            invisible: !fixedValueMode && invisible,
          }"
          label-cols-sm="3"
          label-align-sm="right"
          :label="label"
          :label-class="
            'application-profile-label' + (required ? ' mandatory' : '')
          "
        >
          <b-button-toolbar v-for="(entry, entryIndex) in values" :key="entryIndex">
            <component
              :is="componentDefinition"
              class="wrapper-input"
              :locked="locked"
              :required="required"
              :fixed-value-mode="fixedValueMode"
              :fixed-values="fixedValues"
              :disabled-mode="disabledMode"
              :language-code="languageLocale"
              :entry="entry"
              :data-type="dataType"
              :class-object="classObject"
              :list="list"
              :single-line="singleLine"
              :state="state"
              :attached-metadata="attachedMetadata[entryIndex]"
              :dataset="dataset"
              :node-import="nodeImport"
              :class-receiver="classReceiver"
              :user-receiver="userReceiver"
              @input="inputValue(entryIndex, $event)"
              @inputDataset="inputDataset"
              @triggerValidation="triggerValidation"
            />
            <b-button-group class="wrapper-input-button">
              <b-button
                v-if="fixedValueMode"
                :disabled="fixedValueDisabled[entryIndex]"
                class="innerButton"
                variant="outline-secondary"
                @click.prevent="changeLockable"
              >
                <LockIcon v-if="locked" />
                <LockOpenIcon v-else />
              </b-button>
              <b-button
                v-if="fixedValueMode"
                v-b-tooltip.hover.top="$t('invisibilityInfo')"
                :disabled="disabledMode || !fieldCanBeInvisible"
                class="innerButton"
                variant="outline-secondary"
                @click.prevent="changeVisibility"
              >
                <Invisible v-if="invisible" />
                <Visible v-else />
              </b-button>
              <b-button
                v-if="caseInsertField(entryIndex)"
                :disabled="disabledMode || insertNewFieldButtonDisabled"
                class="innerButton"
                variant="outline-secondary"
                @click.prevent="insertNewFields(1)"
              >
                <PlusIcon />
              </b-button>
              <b-button
                v-if="!caseInsertField(entryIndex)"
                :disabled="disabledMode || removeFieldButtonDisabled"
                class="innerButton"
                variant="outline-secondary"
                @click.prevent="removeField(entryIndex)"
              >
                <MinusIcon />
              </b-button>
            </b-button-group>
          </b-button-toolbar>
          <b-button
            v-if="values.length === 0"
            :disabled="disabledMode || insertNewFieldButtonDisabled"
            class="innerButton float-right"
            variant="outline-secondary"
            @click.prevent="insertNewFields(1)"
          >
            <PlusIcon />
          </b-button>
          <b-form-invalid-feedback :state="state">
            <div v-for="(message, index) in errorMessages[nodeName]" :key="index">
              {{ message.value }}
            </div>
          </b-form-invalid-feedback>
        </b-form-group>
      </div>
    </template>
    
    <script lang="ts">
    import { defineComponent, Vue2 as Vue } from 'vue-demi';
    import type { PropType } from 'vue-demi';
    import VueI18n from 'vue-i18n';
    
    import locale from '@/locale';
    
    import LockIcon from 'vue-material-design-icons/Lock.vue';
    import LockOpenIcon from 'vue-material-design-icons/LockOpen.vue';
    import MinusIcon from 'vue-material-design-icons/Minus.vue';
    import PlusIcon from 'vue-material-design-icons/Plus.vue';
    import Visible from 'vue-material-design-icons/Eye.vue';
    import Invisible from 'vue-material-design-icons/EyeOff.vue';
    
    import {
      BButton,
      BButtonGroup,
      BButtonToolbar,
      BFormInvalidFeedback,
      BFormGroup,
    } from 'bootstrap-vue';
    
    import InputCombobox from './InputCombobox.vue';
    import InputTextField from './InputTextField.vue';
    import InputDatePicker from './InputDatePicker.vue';
    import InputBooleanCombobox from './InputBooleanCombobox.vue';
    import InputList from './InputList.vue';
    import InputShape from './InputShape.vue';
    
    if (Vue) {
      Vue.use(VueI18n);
    } else {
      // TODO: Deal with Vue 3
    }
    
    const i18n = new VueI18n({
      locale: 'en',
      messages: locale,
      silentFallbackWarn: true,
    });
    
    import type { BilingualLabels } from '@coscine/api-client/dist/types/Coscine.Api.Metadata';
    import type { UserObject } from '@coscine/api-client/dist/types/Coscine.Api.User';
    import type { Dataset, NamedNode, Quad_Object, Quad_Subject } from 'rdf-js';
    import { prefixes } from '@zazuko/rdf-vocabularies';
    import factory from 'rdf-ext';
    import { getObject, getObjectStringList } from '@/util/linkedData';
    import type {
      FixedValueObject,
      FixedValues,
      ValueType,
    } from '@/types/fixedValues';
    
    export default defineComponent({
      i18n,
      name: 'WrapperInput',
      components: {
        BButton,
        BButtonGroup,
        BButtonToolbar,
        BFormInvalidFeedback,
        BFormGroup,
        LockIcon,
        LockOpenIcon,
        MinusIcon,
        PlusIcon,
        Visible,
        Invisible,
        InputTextField,
        InputCombobox,
        InputDatePicker,
        InputBooleanCombobox,
        InputList,
        InputShape,
      },
      props: {
        metadata: {
          required: true,
          type: Object as PropType<Dataset>,
        },
        metadataSubject: {
          default: () => factory.blankNode(),
          type: Object as PropType<Quad_Subject>,
        },
        fixedValues: {
          default: () => ({}),
          type: Object as PropType<FixedValues>,
        },
        fixedValueMode: {
          default: false,
          type: Boolean,
        },
        disabledMode: {
          default: false,
          type: Boolean,
        },
        languageLocale: {
          default: 'en',
          type: String,
        },
        errorMessages: {
          default: () => ({}),
          type: Object as PropType<{ [nodeName: string]: Quad_Object[] }>,
        },
        classReceiver: {
          default: () => {
            return async () => ({} as BilingualLabels);
          },
          type: Function as PropType<
            (classUrl: string) => Promise<BilingualLabels>
          >,
        },
        userReceiver: {
          default: () => {
            return async () => ({} as UserObject);
          },
          type: Function as PropType<() => Promise<UserObject>>,
        },
        dataset: {
          required: true,
          type: Object as PropType<Dataset>,
        },
        property: {
          required: true,
          type: Object as PropType<Quad_Subject>,
        },
      },
      data() {
        return {
          locked: false,
          invisible: false,
          values: [] as Quad_Object[],
        };
      },
      computed: {
        attachedMetadata(): Dataset[] {
          return this.values.map((entry) => {
            const collectedNodes = [entry];
            const toLookAtNodes = [entry];
            const attachedMetadata = factory.dataset() as unknown as Dataset;
            while (toLookAtNodes.length > 0) {
              const currentNode = toLookAtNodes.pop();
              const currentAttachedMetadata = Array.from(
                this.metadata.match(currentNode)
              );
              attachedMetadata.addAll(currentAttachedMetadata);
              toLookAtNodes.push(
                ...currentAttachedMetadata
                  .filter(
                    (currentOldValue) =>
                      (currentOldValue.object.termType === 'NamedNode' ||
                        currentOldValue.object.termType === 'BlankNode') &&
                      !collectedNodes.some(
                        (collectedNode) =>
                          collectedNode.value === currentOldValue.object.value
                      )
                  )
                  .map((quad) => quad.object)
              );
              collectedNodes.push(
                ...currentAttachedMetadata.map((quad) => quad.object)
              );
            }
            return attachedMetadata;
          });
        },
        classObject(): NamedNode<string> | null {
          const classes = this.dataset.match(
            this.property,
            factory.namedNode(prefixes.sh + 'class')
          );
          if (classes.size) {
            for (const classEntry of classes) {
              return classEntry.object as NamedNode<string>;
            }
          }
          return null;
        },
        componentDefinition(): string | null {
          if (this.dataType) {
            if (this.dataType.value === prefixes.xsd + 'date') {
              return 'InputDatePicker';
            } else if (this.dataType.value === prefixes.xsd + 'boolean') {
              return 'InputBooleanCombobox';
            }
            return 'InputTextField';
          } else {
            if (this.classObject) {
              return 'InputCombobox';
            }
            if (this.isList) {
              return 'InputList';
            }
            if (this.isNode) {
              return 'InputShape';
            }
          }
          return null;
        },
        cssProps(): {
          '--wrapperInputWidth': string;
          '--wrapperInputRightMargin': string;
        } {
          return {
            // Formula: 100% - (n.Buttons)*(Button Width) - (--wrapperInputRightMargin)
            '--wrapperInputWidth': this.fixedValueMode
              ? 'calc(100% - 181px - 1rem)'
              : 'calc(100% - 61px - 5px)',
            '--wrapperInputRightMargin': this.fixedValueMode ? '1rem' : '5px',
          };
        },
        dataType(): NamedNode | undefined {
          const dataTypes = this.dataset.match(
            this.property,
            factory.namedNode(prefixes.sh + 'datatype')
          );
          for (const dataType of dataTypes) {
            return dataType.object as NamedNode;
          }
          return undefined;
        },
        fieldCanBeInvisible(): boolean {
          return (
            (this.required &&
              this.values.some((entry) => entry.value !== '') &&
              this.locked) ||
            !this.required
          );
        },
        fixedValueDisabled(): Array<boolean> {
          return this.values.map(
            (entry) =>
              this.disabledMode || !entry || (this.required && this.invisible)
          );
        },
        insertNewFieldButtonDisabled(): boolean {
          if (
            this.values.length === 0 ||
            ((!this.maxCount || this.values.length < this.maxCount) &&
              this.values.every((entry) => entry.value !== ''))
          ) {
            return false;
          } else {
            return true;
          }
        },
        isList(): boolean {
          return (
            this.dataset.match(this.property, factory.namedNode(prefixes.sh + 'in'))
              .size > 0
          );
        },
        isNode(): boolean {
          return (
            this.dataset.match(
              this.property,
              factory.namedNode(prefixes.sh + 'node')
            ).size > 0
          );
        },
        label(): string {
          const labels = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'name'),
            this.dataset,
            this.languageLocale
          );
          if (!labels.length) {
            return this.nodeName;
          }
          return labels[0].object.value;
        },
        list(): string[] {
          if (this.isList) {
            return getObjectStringList(
              this.property,
              factory.namedNode(prefixes.sh + 'in'),
              this.dataset
            );
          }
          return [];
        },
        maxCount(): number | undefined {
          const maxCounts = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'maxCount'),
            this.dataset
          );
          if (!maxCounts.length) {
            return undefined;
          }
          return Number(maxCounts[0].object.value);
        },
        minCount(): number {
          const minCounts = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'minCount'),
            this.dataset
          );
          if (!minCounts.length) {
            return 0;
          }
          return Number(minCounts[0].object.value);
        },
        minLength(): number {
          const minLengths = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'minLength'),
            this.dataset
          );
          if (!minLengths.length) {
            return 0;
          }
          return Number(minLengths[0].object.value);
        },
        nodeName(): string {
          const names = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'path'),
            this.dataset
          );
          if (!names.length) {
            return '';
          }
          return names[0].object.value;
        },
        nodeImport(): string {
          const names = getObject(
            this.property,
            factory.namedNode(prefixes.sh + 'node'),
            this.dataset
          );
          if (!names.length) {
            return '';
          }
          return names[0].object.value;
        },
        removeFieldButtonDisabled(): boolean {
          if (this.values.length > this.minCount) {
            return false;
          } else {
            return true;
          }
        },
        required(): boolean {
          return this.minCount >= 1;
        },
        singleLine(): boolean {
          return !this.dataset.some(
            (quad) =>
              quad.subject === this.property &&
              quad.predicate.value === 'http://datashapes.org/dash#singleLine' &&
              quad.object.value === 'false'
          );
        },
        state(): boolean | null {
          if (
            this.errorMessages[this.nodeName] &&
            this.errorMessages[this.nodeName].length
          ) {
            return false;
          } else if (this.values.some((entry) => entry.value !== '')) {
            return true;
          }
          return null;
        },
        // TODO: Currently, then handling of multiple values is a bit weird
        // e.g. if you enter the same value, one disappears
        // This is due to a limitation that a dataset can only contain one ?s ?p ?o triple
        // Try to think how to deal with this limitation and resolve the weirdness
        metadataValues(): Quad_Object[] {
          return Array.from(
            this.metadata.match(
              this.metadataSubject,
              factory.namedNode(this.nodeName)
            )
          ).map((quad) => quad.object);
        },
      },
      watch: {
        fixedValues() {
          this.initFixedValues();
        },
        metadata() {
          this.initMetadata();
          this.setValuesFromMetadataValues();
        },
      },
      beforeMount() {
        i18n.locale = this.languageLocale;
    
        this.initMetadata();
        this.initFixedValues();
        this.initDefaultValues();
      },
      methods: {
        changeLockable() {
          this.locked = !this.locked;
          this.setFixedValues(this.values);
        },
        changeVisibility() {
          if (!this.invisible && this.fieldCanBeInvisible) {
            this.invisible = true;
          } else {
            this.invisible = false;
          }
          this.setFixedValues(this.values);
        },
        initDefaultValues() {
          if (
            !this.values.length ||
            this.values.every((entry) => entry.value === '')
          ) {
            const fixedValueObject = this.fixedValues[this.nodeName];
            const datasetMatches = this.dataset.match(
              this.property,
              factory.namedNode(prefixes.sh + 'defaultValue')
            );
            if (
              fixedValueObject &&
              fixedValueObject['https://purl.org/coscine/defaultValue'] &&
              fixedValueObject['https://purl.org/coscine/defaultValue'].some(
                (entry) => entry.value !== ''
              )
            ) {
              this.input(
                this.valueTypesToQuadObjects(
                  fixedValueObject['https://purl.org/coscine/defaultValue']
                )
              );
            } else if (datasetMatches.size) {
              this.input(Array.from(datasetMatches).map((quad) => quad.object));
            }
          }
        },
        initFixedValues() {
          const fixedValueObject = this.fixedValues[this.nodeName];
          if (fixedValueObject) {
            const fixedValueList =
              fixedValueObject['https://purl.org/coscine/fixedValue'];
            if (
              fixedValueList &&
              fixedValueList.some((entry) => entry.value !== '')
            ) {
              if (
                !this.values.length ||
                this.values.every((entry) => entry.value === '')
              ) {
                this.input(this.valueTypesToQuadObjects(fixedValueList));
              }
              this.locked = true;
            }
            const invisibleList =
              fixedValueObject['https://purl.org/coscine/invisible'];
            this.invisible =
              invisibleList !== undefined &&
              invisibleList.every((entry) => entry.value === '1');
          }
        },
        initMetadata() {
          this.values = [...this.metadataValues];
          // Set values if they don't exist
          if (!this.values.length) {
            const numberOfNewFields = this.isNode ? 0 : 1;
            // Setting more than one will not work since a dataset does not support multiple triples
            // which are the same ?s ?p ?o
            this.insertNewFields(numberOfNewFields);
          }
        },
        input(values: Quad_Object[], fixedValueTransfer = true) {
          this.$emit('input', this.nodeName, values);
          if (fixedValueTransfer) {
            this.updateFixedValues(values);
          }
        },
        inputFixedValues(object: FixedValueObject) {
          this.$emit('inputFixedValues', this.nodeName, object);
        },
        inputDataset(value: Quad_Object, dataset: Dataset) {
          this.$emit('inputDataset', value, dataset);
        },
        inputValue(entryKey: number, value: Quad_Object) {
          const newValues = [...this.values];
          // Clone for breaking reactivity
          if (value.termType === 'Literal') {
            newValues[entryKey] = factory.literal(value.value, value.datatype);
          } else if (value.termType === 'NamedNode') {
            newValues[entryKey] = factory.namedNode(value.value);
          } else {
            newValues[entryKey] = value;
          }
          this.input(newValues);
        },
        insertNewFields(numberOfNewFields = 1) {
          const newValues = [...this.values];
          for (let i = 0; i < numberOfNewFields; i++) {
            let entry: Quad_Object = factory.literal('', this.dataType);
            if (this.isNode) {
              entry = factory.blankNode();
            } else if (!this.dataType) {
              entry = factory.namedNode('');
            }
            newValues.push(entry);
          }
          this.input(newValues, false);
        },
        caseInsertField(entryKey: number) {
          return entryKey === 0 ? true : false;
        },
        removeField(entryKey: number) {
          const newValues = [...this.values];
          const removedEntries = newValues.splice(entryKey, 1);
          if (this.isNode) {
            for (const removedEntry of removedEntries) {
              this.inputDataset(
                removedEntry,
                factory.dataset() as unknown as Dataset
              );
            }
          }
          this.input(newValues);
        },
        setFixedValues(values: Quad_Object[]) {
          const currentFixedValueObject: FixedValueObject = {};
          const extractedFixedValue = this.extractForFixedValues(values);
          currentFixedValueObject['https://purl.org/coscine/invisible'] = [
            { value: this.invisible ? '1' : '0', type: 'literal' },
          ];
          if (this.locked) {
            currentFixedValueObject['https://purl.org/coscine/fixedValue'] =
              extractedFixedValue;
          } else {
            currentFixedValueObject['https://purl.org/coscine/defaultValue'] =
              extractedFixedValue;
          }
          this.inputFixedValues(currentFixedValueObject);
        },
        setValuesFromMetadataValues() {
          this.values = this.values.filter((entry) =>
            this.metadataValues.some(
              (metadataEntry) => entry.value === metadataEntry.value
            )
          );
          const newValues = this.metadataValues.filter(
            (metadataEntry) =>
              !this.values.some((entry) => entry.value === metadataEntry.value)
          );
          this.values = [...this.values, ...newValues];
        },
        triggerValidation() {
          this.$emit('triggerValidation');
        },
        updateFixedValues(values: Quad_Object[]) {
          if (this.fixedValueMode) {
            if (
              this.fixedValues[this.nodeName] &&
              this.fixedValues[this.nodeName]['https://purl.org/coscine/fixedValue']
            ) {
              this.locked = true;
            } else {
              this.locked = false;
            }
            this.setFixedValues(values);
          }
        },
        extractForFixedValues(values: Quad_Object[]) {
          const extractedValues: ValueType[] = [];
          for (const entry of values.filter((entry) => entry.value !== '')) {
            const valueObject: ValueType = {
              value: entry.value,
              type: entry.termType === 'Literal' ? 'literal' : 'uri',
              datatype:
                entry.termType === 'Literal' ? entry.datatype.value : undefined,
            };
            extractedValues.push(valueObject);
          }
          return extractedValues;
        },
        valueTypesToQuadObjects(valueTypes: ValueType[]) {
          return valueTypes.map((fixedValue) =>
            fixedValue.type === 'literal'
              ? factory.literal(
                  fixedValue.value,
                  fixedValue.datatype
                    ? factory.namedNode(fixedValue.datatype)
                    : undefined
                )
              : factory.namedNode(fixedValue.value)
          );
        },
      },
    });
    </script>
    
    <style scoped>
    .wrapper-input {
      width: var(--wrapperInputWidth);
      margin-right: var(--wrapperInputRightMargin);
    }
    .wrapper-input-button {
      width: 21px;
    }
    </style>