App.vue 10.8 KB
Newer Older
Mathias Goebel's avatar
Mathias Goebel committed
1
<template>
2
3
  <q-layout
    id="q-app"
4
    class="root viewport"
5
6
    view="hHh Lpr fFf"
  >
7
8
9
10
11
12
13
14
15
    <Header
      v-if="config.headers.all"
      :collectiontitle="collectiontitle"
      :config="config"
      :imageurl="imageurl"
      :item="item"
      :itemurls="itemurls"
      :manifests="manifests"
      :panels="panels"
16
17
      :projectcolors="config.colors"
      :standalone="config.standalone"
18
    />
19

20
21
    <q-page-container class="root">
      <router-view
22
        :annotations="annotations"
dindigala's avatar
dindigala committed
23
        :annotation-loading="annotationLoading"
24
25
26
        :collection="collection"
        :config="config"
        :contenttypes="contentTypes"
27
        :contenturls="contentUrls"
28
29
30
31
32
33
34
35
36
37
38
        :fontsize="fontsize"
        :imageurl="imageurl"
        :item="item"
        :labels="config.labels"
        :manifests="manifests"
        :panels="panels"
        :request="request"
        :tree="tree"
      />
    </q-page-container>
  </q-layout>
Mathias Goebel's avatar
Mathias Goebel committed
39
40
41
</template>

<script>
schneider210's avatar
schneider210 committed
42
import { colors } from 'quasar';
43
import Header from '@/components/header.vue';
44
import Panels from '@/mixins/panels';
45

Mathias Goebel's avatar
Mathias Goebel committed
46
export default {
47
  name: 'TIDO',
48
49
50
  components: {
    Header,
  },
51
  mixins: [Panels],
52
53
  data() {
    return {
54
      annotations: [],
dindigala's avatar
dindigala committed
55
      annotationLoading: false,
56
      collection: {},
57
      collectiontitle: '',
58
      config: {},
59
      contentTypes: [],
60
      contentUrls: [],
schneider210's avatar
schneider210 committed
61
      fontsize: 14,
62
      imageurl: '',
63
      isCollection: false,
64
      item: {},
65
66
67
68
69
70
      itemurl: '',
      itemurls: [],
      manifests: [],
      tree: [],
    };
  },
nwindis's avatar
nwindis committed
71
72
73
74
75
  created() {
    this.getConfig();
    this.init();
    this.itemurls.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));

schneider210's avatar
schneider210 committed
76
77
    this.$q.dark.set('auto');

nwindis's avatar
nwindis committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
    if (this.config.colors.primary && this.config.colors.secondary && this.config.colors.accent) {
      colors.setBrand('primary', this.config.colors.primary);
      colors.setBrand('secondary', this.config.colors.secondary);
      colors.setBrand('accent', this.config.colors.accent);
    }
  },
  mounted() {
    /**
      * listen to fontsize change (user interaction). emitted in @/components/content.vue
      * in- or rather decrease fontsize of the text by 1px
      * default fontsize: 14px
      *
      * @param number fontsize
      */
    this.$root.$on('update-fontsize', (fontsize) => {
      this.fontsize = fontsize;
    });
    this.$root.$on('panels-position', (newPanels) => {
      this.panels = newPanels;
    });
    /**
      * listen to item change (user interaction).
      * emitted in: *getItemurls*; handler for tree nodes. fired on user interaction
      *
      * @param string url
      */
    this.$root.$on('update-item', (url) => {
      this.itemurl = url;
      this.$router.push({ query: { itemurl: url } });
      // NOTE: Set imageurl to an empty string. Otherwise, if there is no corresponding image,
      // the "preceding" image according to the "preceding" item will be shown.
      this.imageurl = '';
      this.getItemData(url);
    });
  },
113
  methods: {
schneider210's avatar
schneider210 committed
114
115
116
117
118
119
120
121
122
123
    /**
      * get resources using JavaScript's native fetch api
      * caller: *getCollection()*, *getItemData()*, *getManifest()*
      *         *@/components/content.vue::getSupport()*, *@/components/content.vue::created-hook*
      *
      * @param string url
      * @param string responsetype
      *
      * @return promise data
      */
124
125
126
127
128
129
    async request(url, responsetype = 'json') {
      const response = await fetch(url);
      const data = await (responsetype === 'text' ? response.text() : response.json());

      return data;
    },
130
131
132
133
134
135

    /**
      * get annotations of the current item
      * caller: *getItemData()*
      * @param string url
      */
136
    async getAnnotations(url) {
137
      this.annotations = [];
dindigala's avatar
dindigala committed
138
      this.annotationLoading = false;
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
      try {
        const annotations = await this.request(url);

        if (!annotations.annotationCollection.first) {
          this.annotations = [];
          return;
        }

        const current = await this.request(annotations.annotationCollection.first);

        if (current.annotationPage.items.length) {
          this.annotations = current.annotationPage.items;
        } else {
          this.annotations = [];
        }
      } catch (err) {
        this.annotations = [];
        this.$q.notify({ message: 'No annotations available' });
158
      } finally {
dindigala's avatar
dindigala committed
159
        this.annotationLoading = true;
160
      }
161
    },
schneider210's avatar
schneider210 committed
162
163
164
165
166
167
168
169
    /**
      * get collection data according to 'entrypoint'
      * (number of requests equal the number of manifests contained within a collection)
      * initialize the tree's root node
      * caller: *init()*
      *
      * @param string url
      */
170
    getCollection(url) {
171
172
      this.isCollection = true;

173
174
175
      this.request(url)
        .then((data) => {
          this.collection = data;
176
          this.collectiontitle = this.getLabel(data);
177

178
179
180
181
182
183
          this.tree.push(
            {
              children: [],
              handler: (node) => {
                this.$root.$emit('update-tree-knots', node.label);
              },
184
185
              label: this.collectiontitle,
              'label-key': this.collectiontitle,
186
187
188
              selectable: false,
            },
          );
189
190
191
192
193
194

          if (Array.isArray(data.sequence)) {
            data.sequence.forEach((seq) => this.getManifest(seq.id));
          }
        });
    },
schneider210's avatar
schneider210 committed
195
196
197
198
    /**
      * get config object (JSON) from index.html
      * caller: *created-hook*
      */
199
    getConfig() {
200
      this.config = JSON.parse(document.getElementById('tido-config').text);
201
    },
202
    /**
203
      * filter all urls that match either of the MIME types "application/xhtml+xml" and "text/html"
204
205
206
207
208
209
      * caller: *getItemData()*
      *
      * @param string array
      *
      * @return array
      */
210
    getContentUrls(content) {
211
212
213
      const urls = [];

      if (Array.isArray(content) && content.length) {
214
215
        this.contentTypes = [];

216
217
218
        content.forEach((c) => {
          if (c.type.match(/(application\/xhtml\+xml|text\/html)/)) {
            urls.push(c.url);
219
220

            this.contentTypes.push(c.type.split('type=')[1]);
221
222
223
224
          }
        });
      }
      return urls;
225
    },
schneider210's avatar
schneider210 committed
226
227
228
229
230
231
    /**
      * fetch all data provided on 'item level'
      * caller: *mounted-hook*, *getManifest()*
      *
      * @param string url
      */
232
    getItemData(url) {
233
234
      this.request(url)
        .then((data) => {
235
          this.item = data;
236

237
          this.contentUrls = this.getContentUrls(data.content);
238
          this.imageurl = data.image.id || '';
239
240
241
242

          if (data.annotationCollection) {
            this.getAnnotations(data.annotationCollection);
          }
243
244
        });
    },
schneider210's avatar
schneider210 committed
245
246
247
248
249
250
251
    /**
      * caller: *getItemUrls()*
      *
      * @param string nodelabel
      *
      * @return number idx
      */
252
253
254
255
256
257
258
259
260
    getItemIndex(nodelabel) {
      let idx = 0;
      this.itemurls.forEach((item, index) => {
        if (item === nodelabel) {
          idx = index;
        }
      });
      return idx;
    },
261
262
263
264
265
266
267
268
269
270
271
    /**
      * extract the 'label part' of the itemurl
      * caller: *getItemUrls()*
      *
      * @param string itemurl
      *
      * @return string 'label part'
      */
    getItemLabel(itemurl) {
      return itemurl.replace(/.*-(.*)\/latest.*$/, '$1');
    },
schneider210's avatar
schneider210 committed
272
273
274
275
276
277
278
279
280
    /**
      * get all itemurls hosted by each manifest's sequence to populate the aprropriate tree node
      * caller: *getManifest()*
      *
      * @param array sequence
      * @param string label
      *
      * @return array urls
      */
281
282
    getItemUrls(sequence, label) {
      const urls = [];
283

284
285
      sequence.forEach((item) => {
        const itemLabel = this.getItemLabel(item.id);
286

287
288
        urls.push(
          {
289
            label: item.id,
290
291
            'label-key': `${itemLabel}`,
            labelSheet: true,
292
293
294
295
            handler: (node) => {
              if (this.itemurl === node.label) {
                return;
              }
schneider210's avatar
schneider210 committed
296
297
298
              // node.label === itemurl
              // @param label === manifest label; passed by getManifest()
              this.$root.$emit('update-item', node.label, this.getSequenceIndex(label));
299
              this.$root.$emit('update-item-index', this.getItemIndex(node.label));
300
              this.$root.$emit('update-sequence-index', this.getSequenceIndex(label));
301
302
303
304
305
306
            },
          },
        );
      });
      return urls;
    },
schneider210's avatar
schneider210 committed
307
308
309
310
311
312
313
314
    /**
      * get the collection label, if provided; otherwise get the manifest label
      * caller: *getCollection()*, *getManifest()*
      *
      * @param object data
      *
      * @return string 'label'
      */
315
316
317
318
319
320
    getLabel(data) {
      if (Object.keys(this.collection).length) {
        return data.title && data.title[0].title ? data.title[0].title : data.label;
      }
      return data.label ? data.label : 'Manifest <small>(No label available)</small>';
    },
schneider210's avatar
schneider210 committed
321
322
323
324
325
326
    /**
      * get all the data provided on 'manifest level'
      * caller: *init()*, *getCollection()*
      *
      * @param string url
      */
327
328
329
    getManifest(url) {
      this.request(url)
        .then((data) => {
330
331
332
333
334
          // if the entrypoint points to a single manifest, initialize the tree
          if (this.isCollection === false) {
            this.tree.push({ label: '', 'label-key': this.config.labels.manifest, children: [] });
          }

335
336
337
338
339
340
341
342
343
344
          if (!Array.isArray(data.sequence)) {
            data.sequence = [data.sequence];
          }

          if (data.sequence[0] !== 'undefined') {
            data.sequence.map((seq) => this.itemurls.push(seq.id));
          }
          this.manifests.push(data);

          this.tree[0].children.push(
345
346
            {
              children: this.getItemUrls(data.sequence, data.label),
347
348
349
350
351
352
              label: data.label,
              'label-key': data.label,
              handler: (node) => {
                this.$root.$emit('update-tree-knots', node.label);
              },
              selectable: false,
353
            },
354
355
356
357
          );
          // make sure that urls are set just once on init
          if (!this.itemurl && data.sequence[0]) {
            this.itemurl = data.sequence[0].id;
358
            this.getItemData(data.sequence[0].id);
359
360
361
          }
        });
    },
schneider210's avatar
schneider210 committed
362
363
364
365
366
367
368
    /**
      * caller: *getItemUrls()*
      *
      * @param string label
      *
      * @return number index
      */
369
    getSequenceIndex(label) {
370
371
      let index = 0;
      this.manifests.forEach((manifest, idx) => {
372
        if (manifest.label === label) {
373
374
375
376
377
          index = idx;
        }
      });
      return index;
    },
schneider210's avatar
schneider210 committed
378
379
380
381
382
383
    /**
      * decide whether to start with a collection or a single manifest
      * caller: *created-hook*
      *
      * @return function getCollection() | getManifest()
      */
384
385
386
387
388
389
390
    init() {
      return this.config.entrypoint.match(/collection.json\s?$/)
        ? this.getCollection(this.config.entrypoint)
        : this.getManifest(this.config.entrypoint);
    },
  },

Mathias Goebel's avatar
Mathias Goebel committed
391
392
};
</script>
393
394

<style scoped>
395
.root {
396
397
398
399
400
401
  display: flex;
  flex: 1;
  flex-direction: column;
  overflow: hidden;
}

Mathias Goebel's avatar
Mathias Goebel committed
402
.viewport {
403
  height: 100vh;
404
405
}
</style>