annotations.vue 7.96 KB
Newer Older
1
<template>
2
3
4
5
6
7
8
9
  <div class="annotations">
    <q-tabs
      v-model="currentTab"
      active-color="$q.dark.isActive ? 'white' : 'accent'"
      align="justify"
      class="text-grey q-mb-sm"
      dense
      indicator-color="$q.dark.isActive ? 'white' : 'accent'"
Mathias Goebel's avatar
Mathias Goebel committed
10
    >
11
12
13
14
15
16
17
18
      <q-tab
        v-for="annotationTab in annotationTabs"
        :key="annotationTab.key"
        :label="annotationTab.collectionTitle"
        :name="annotationTab.key"
        @click="activeTab(annotationTab.key)"
      />
    </q-tabs>
19

20
    <div
21
      v-if="currentAnnotations.length"
22
23
      class="q-ma-sm"
    >
24
25
26
      <AnnotationToggles />

      <AnnotationList
27
        :hot-annotations="currentAnnotations"
28
29
30
31
32
33
34
35
36
37
        :get-icon="getIcon"
        :status-check="statusCheck"
        :toggle="toggle"
      />

      <AnnotationOptions
        :selected-all="selectedAll"
        :selected-none="selectedNone"
        :toggle-to="toggleTo"
      />
38
    </div>
39

40
41
42
43
44
45
    <div
      v-else
      class="q-pa-sm"
    >
      <Notification :message="$t(messages.none)" />
    </div>
46
  </div>
47
48
49
</template>

<script>
50
import * as Icons from '@quasar/extras/fontawesome-v5';
51

52
53
54
import AnnotationToggles from '@/components/annotations/toggles.vue';
import AnnotationList from '@/components/annotations/list.vue';
import AnnotationOptions from '@/components/annotations/options.vue';
55

56
import Notification from '@/components/notification.vue';
57

58
59
export default {
  name: 'Annotations',
60
  components: {
61
    AnnotationToggles,
62
    AnnotationList,
63
    AnnotationOptions,
64
    Notification,
65
  },
66
  props: {
schneider210's avatar
schneider210 committed
67
    annotations: {
68
69
      type: Array,
      default: () => [],
schneider210's avatar
schneider210 committed
70
    },
dindigala's avatar
dindigala committed
71
    annotationLoading: {
72
73
74
      type: Boolean,
      default: false,
    },
75
76
77
78
    config: {
      type: Object,
      default: () => {},
    },
79
80
  },
  data() {
81
    return {
82
83
      hotAnnotations: [],
      ids: [],
84
      messages: {
85
        none: 'noAnnotationMessage',
86
      },
87
88
      selectedAll: undefined,
      selectedNone: undefined,
89
      currentTab: 'editorial',
90
91
92
93
    };
  },
  computed: {
    configuredTypes() {
94
      return this.config.annotations.types.map((type) => type.contenttype);
95
    },
96
97
    currentAnnotations() {
      const contentType = this.annotationTabs.find((collection) => collection.key === this.currentTab);
98

99
      return this.hotAnnotations.filter((annotationCollection) => contentType.type.includes(annotationCollection.body['x-content-type']));
100
101
102
103
104
105
    },
    annotationTabs() {
      return [
        {
          collectionTitle: 'Editorial',
          key: 'editorial',
106
          type: ['Place', 'Person', 'Editorial Comment'],
107
108
109
110
        },
        {
          collectionTitle: 'Motifs',
          key: 'motifs',
111
          type: ['Motif'],
112
113
114
        },
      ];
    },
115
  },
116
  mounted() {
117
118
119
    this.$root.$on('update-annotations', (content) => {
      const parser = new DOMParser();
      const doc = parser.parseFromString(content, 'text/html');
120

121
      this.ids = [...doc.body.querySelectorAll('[id]')].map((el) => el.getAttribute('id'));
122

123
      const interval = setInterval(() => {
dindigala's avatar
dindigala committed
124
        if (this.annotationLoading) {
Mathias Goebel's avatar
Mathias Goebel committed
125
          this.hotAnnotations = this.filterAnnotationTypes();
126
          this.highlightActiveTabContent('editorial');
127
128
129
          clearInterval(interval);
        }
      }, 500);
130
131

      this.currentTab = 'editorial';
132
133
    });
  },
134
  methods: {
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
    highlightActiveTabContent(key) {
      const contentTypes = this.annotationTabs.find((content) => content.key === key).type;

      this.annotations.forEach((annotation) => {
        const id = this.stripAnnotationId(annotation.target.id);
        const textElement = document.getElementById(id);

        if (contentTypes.includes(annotation.body['x-content-type'])) {
          textElement.classList.add('annotation');
          textElement.classList.add('annotation-disabled');
        } else {
          textElement.classList.remove('annotation');
          textElement.classList.add('annotation-disabled');
        }
      });
    },
151
152
    activeTab(key) {
      this.currentTab = key;
153
154

      this.highlightActiveTabContent(key);
155
    },
156
157
158
    createSVG(name) {
      const [path, viewBox] = Icons[name].split('|');
      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
159

160
      svg.setAttribute('aria-hidden', 'true');
161
      svg.setAttribute('class', 'q-icon q-mx-xs');
162
163
164
165
166
167
168
169
      svg.setAttribute('focusable', 'false');
      svg.setAttribute('role', 'presentation');
      svg.setAttribute('viewBox', viewBox);

      const newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');

      newPath.setAttribute('d', path);
      svg.appendChild(newPath);
170

171
      return svg;
172
    },
173
174

    /**
175
    * filter for configured annotation types (index.html)
176
    *
177
    * @return array of annotations excluding unconfigured ones
178
    */
179
    filterAnnotationTypes() {
180
      return this.annotations.filter((annotation) => {
181
        this.$set(annotation, 'status', this.config.annotations.show);
182
183
184
185

        annotation.strippedId = this.stripAnnotationId(annotation.target.id);
        const annotationIds = this.ids.includes(annotation.strippedId);

186
        if (this.configuredTypes.find((type) => type === annotation.body['x-content-type']) && annotationIds) {
187
          this.setText(annotation);
188
189

          return true;
190
        }
191
        return false;
192
193
194
      });
    },

195
196
    getIcon(contentType) {
      return Icons[this.getIconName(contentType)];
197
    },
198

199
200
    getIconName(contentType) {
      return this.config.annotations.types.filter((annotation) => annotation.contenttype === contentType)[0].icon;
201
202
203
    },

    setText(annotation) {
204
      const contentType = annotation.body['x-content-type'];
205
206
      const id = this.stripAnnotationId(annotation.target.id);
      const textElement = document.getElementById(id);
207
208
209
210
      let svg = null;

      try {
        svg = this.createSVG(this.getIconName(contentType));
211
212

        svg.setAttribute('id', `annotation-icon-${id}`);
213
214
215
216
217
218
219
      } catch (err) {
        svg = null;
      }

      if (svg) {
        textElement.prepend(svg);
      }
220
221
    },

222
223
    statusCheck() {
      const num = this.hotAnnotations.length;
Mathias Goebel's avatar
Mathias Goebel committed
224
      const active = this.hotAnnotations.filter((annotation) => annotation.status === true).length;
225

226
227
228
229
230
231
232
233
234
235
236
237
      if (num === active) {
        this.selectedAll = false;
        this.selectedNone = true;
      } else if (active === 0) {
        this.selectedAll = true;
        this.selectedNone = false;
      } else {
        this.selectedAll = false;
        this.selectedNone = false;
      }
    },

238
239
240
241
242
243
244
245
246
    /**
    * get the annotation id of the current item
    *
    * @param string url
    * @return string
    */
    stripAnnotationId(url) {
      return url.split('/').pop();
    },
247

248
    toggle(annotation) {
249
      annotation.status = !annotation.status;
250
251
252

      const id = annotation.strippedId;

253
254
      document.getElementById(id).classList.toggle('annotation-disabled');
      document.getElementById(`list${id}`).classList.toggle('bg-grey-2');
255
256
    },

257
    toggleTo(bool) {
Mathias Goebel's avatar
Mathias Goebel committed
258
      this.hotAnnotations.filter((annotation) => annotation.status === bool).map((annotation) => this.toggle(annotation));
259
260
      this.selectedAll = bool;
      this.selectedNone = !bool;
261
    },
262
263
264
265
266
267
268
    toggleAnnotationList(id) {
      try {
        document.getElementById(`list${id}`).classList.toggle('bg-grey-2');
      } catch (err) {
        // Error display
      }
    },
269
  },
270
271
};
</script>
272

273
<style lang="scss">
274
275
/* not in scope to style the text */
.annotation {
276
277
  background-color: $grey-4;
  border-bottom: 1px solid;
278
279
280
281
282
  /**
  * adding a linting exception here,
  * because 1px is invalid, but needed here
  * adding a global rule for this would introduce unnecessary error proneness
  */
283
284
285
286
  /* stylelint-disable */
  margin: 0 1px;
  padding: 1px 1px 2px 1px;
  /* stylelint-enable */
287
  white-space: nowrap;
288
289
290
291

  @media (prefers-color-scheme: dark) {
    background-color: $grey-9;
  }
292
293
294
295
296
297
298
299
300
301
}

.annotation-disabled {
  border-bottom: 0;
  padding-bottom: inherit;
}

.annotation-disabled > svg {
  display: none;
}
302
</style>
303
304
305
306
307
308
309
310
311
312
313
314
315
316

<style lang="scss" scoped>
.q-item {
  min-height: unset;
}

.q-item__section--avatar {
  min-width: 24px;
}

.q-item__section--side {
  padding-right: unset;
}
</style>