App.vue 10.7 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: [],
61
      fontsize: 1,
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
      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 = [];
157
      } finally {
dindigala's avatar
dindigala committed
158
        this.annotationLoading = true;
159
      }
160
    },
schneider210's avatar
schneider210 committed
161
162
163
164
165
166
167
168
    /**
      * 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
      */
169
    getCollection(url) {
170
171
      this.isCollection = true;

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

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

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

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

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

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

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

          if (data.annotationCollection) {
            this.getAnnotations(data.annotationCollection);
          }
242
243
        });
    },
schneider210's avatar
schneider210 committed
244
245
246
247
248
249
250
    /**
      * caller: *getItemUrls()*
      *
      * @param string nodelabel
      *
      * @return number idx
      */
251
252
253
254
255
256
257
258
259
    getItemIndex(nodelabel) {
      let idx = 0;
      this.itemurls.forEach((item, index) => {
        if (item === nodelabel) {
          idx = index;
        }
      });
      return idx;
    },
260
261
262
263
264
265
266
267
268
269
270
    /**
      * 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
271
272
273
274
275
276
277
278
279
    /**
      * 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
      */
280
281
    getItemUrls(sequence, label) {
      const urls = [];
282

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

286
287
        urls.push(
          {
288
            label: item.id,
289
290
            'label-key': `${itemLabel}`,
            labelSheet: true,
291
292
293
294
            handler: (node) => {
              if (this.itemurl === node.label) {
                return;
              }
schneider210's avatar
schneider210 committed
295
296
297
              // node.label === itemurl
              // @param label === manifest label; passed by getManifest()
              this.$root.$emit('update-item', node.label, this.getSequenceIndex(label));
298
              this.$root.$emit('update-item-index', this.getItemIndex(node.label));
299
              this.$root.$emit('update-sequence-index', this.getSequenceIndex(label));
300
301
302
303
304
305
            },
          },
        );
      });
      return urls;
    },
schneider210's avatar
schneider210 committed
306
307
308
309
310
311
312
313
    /**
      * get the collection label, if provided; otherwise get the manifest label
      * caller: *getCollection()*, *getManifest()*
      *
      * @param object data
      *
      * @return string 'label'
      */
314
315
316
317
318
319
    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
320
321
322
323
324
325
    /**
      * get all the data provided on 'manifest level'
      * caller: *init()*, *getCollection()*
      *
      * @param string url
      */
326
327
328
    getManifest(url) {
      this.request(url)
        .then((data) => {
329
330
331
332
333
          // 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: [] });
          }

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

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

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