5
5
<Sidebar v-if =" sidebar" />
6
6
<!-- Header -->
7
7
<header >
8
- <div class =" logo" >{{ displayCatalogTitle }}</div >
9
- <StacHeader @enableSidebar =" sidebar = true" />
8
+ <b-row class =" site" >
9
+ <b-col md =" 12" >
10
+ <div class =" title" >
11
+ <img v-if =" catalogImageFromVueX" :src =" catalogImageFromVueX" class =" logo" >
12
+ <h2 >
13
+ <StacLink v-if =" root" :data =" root" />
14
+ <template v-else >{{ catalogTitle }}</template >
15
+ </h2 >
16
+ <b-button v-if =" root" size =" sm" variant =" outline-primary" id =" popover-root-btn" :title =" $t('server')" >
17
+ <b-icon-server /><span class =" button-label" >{{ $t('server') }}</span >
18
+ </b-button >
19
+ </div >
20
+ <nav class =" actions" >
21
+ <b-button-group >
22
+ <b-button variant =" primary" size =" sm" :title =" $t('browse')" v-b-toggle.sidebar @click =" sidebar = true" >
23
+ <b-icon-list /><span class =" button-label" >{{ $t('browse') }}</span >
24
+ </b-button >
25
+ <b-button v-if =" canSearch" variant =" primary" size =" sm" :to =" searchBrowserLink" :title =" $t('search.title')" :pressed =" isSearchPage()" >
26
+ <b-icon-search /><span class =" button-label" >{{ $t('search.title') }}</span >
27
+ </b-button >
28
+ </b-button-group >
29
+ </nav >
30
+ <nav class =" actions" >
31
+ <b-button-group >
32
+ <b-button v-if =" canAuthenticate" variant =" primary" size =" sm" @click =" logInOut" :title =" authTitle" >
33
+ <component :is =" authIcon" /><span class =" button-label" >{{ authLabel }}</span >
34
+ </b-button >
35
+ <b-dropdown size =" sm" variant =" primary" right :title =" $t('source.language.switch')" >
36
+ <template #button-content >
37
+ <b-icon-flag /><span class =" button-label" >{{ $t('source.language.label', {currentLanguage}) }}</span >
38
+ </template >
39
+ <b-dropdown-item
40
+ v-for =" l of languages" :key =" l.code" class =" lang-item"
41
+ @click =" switchLocale({locale: l.code, userSelected: true})"
42
+ >
43
+ <b-icon-check v-if =" localeFromVueX === l.code" />
44
+ <b-icon-blank v-else />
45
+ <span class =" title" >
46
+ <span :lang =" l.code" >{{ l.native }}</span >
47
+ <template v-if =" l .global && l .global !== l .native " > / <span lang =" en" >{{ l.global }}</span ></template >
48
+ </span >
49
+ <b-icon-exclamation-triangle v-if =" supportsLanguageExt && (!l.ui || !l.data)" :title =" l.ui ? $t('source.language.onlyUI') : $t('source.language.onlyData')" class =" ml-2" />
50
+ </b-dropdown-item >
51
+ </b-dropdown >
52
+ </b-button-group >
53
+ </nav >
54
+ </b-col >
55
+ </b-row >
56
+ <b-row class =" page" >
57
+ <b-col md =" 12" >
58
+ <div class =" title" >
59
+ <img v-if =" icon" :src =" icon.href" :alt =" icon.title" :title =" icon.title" class =" icon mr-2" >
60
+ <h1 >{{ title }}</h1 >
61
+ <Source class =" title-actions" :title =" title" :stacUrl =" url" :stac =" data" />
62
+ </div >
63
+ <nav class =" actions" >
64
+ <b-button-group class =" actions" >
65
+ <b-button v-if =" back" :to =" selfBrowserLink" :title =" $t('goBack.description', {type})" variant =" outline-primary" size =" sm" >
66
+ <b-icon-arrow-left /><span class =" button-label" >{{ $t('goBack.label') }}</span >
67
+ </b-button >
68
+ <b-button v-if =" collectionLink" :to =" toBrowserPath(collectionLink.href)" :title =" collectionLinkTitle" variant =" outline-primary" size =" sm" >
69
+ <b-icon-folder-symlink /><span class =" button-label" >{{ $t('goToCollection.label') }}</span >
70
+ </b-button >
71
+ <b-button v-if =" parentLink" :to =" toBrowserPath(parentLink.href)" :title =" parentLinkTitle" variant =" outline-primary" size =" sm" >
72
+ <b-icon-arrow-90deg-up /><span class =" button-label" >{{ $t('goToParent.label') }}</span >
73
+ </b-button >
74
+ </b-button-group >
75
+ </nav >
76
+ </b-col >
77
+ </b-row >
10
78
</header >
11
79
<!-- Content (Item / Catalog) -->
12
80
<router-view />
17
85
</template >
18
86
</i18n >
19
87
</footer >
88
+ <b-popover
89
+ v-if =" root" id =" popover-root" custom-class =" popover-large" target =" popover-root-btn"
90
+ triggers =" focus" placement =" bottom" container =" stac-browser"
91
+ >
92
+ <template #title >
93
+ {{ $t('server') }}
94
+ <b-badge v-if =" isApi" variant =" danger" >{{ $t('index.api') }}</b-badge >
95
+ <b-badge v-else variant =" success" >{{ $t('index.catalog') }}</b-badge >
96
+ </template >
97
+ <RootStats />
98
+ </b-popover >
20
99
</b-container >
21
100
</template >
22
101
23
102
<script >
24
103
import Vue from " vue" ;
25
104
import VueRouter from " vue-router" ;
26
- import Vuex , { mapActions , mapGetters , mapState } from ' vuex' ;
105
+ import Vuex , { mapMutations , mapActions , mapGetters , mapState } from ' vuex' ;
27
106
import CONFIG from ' ./config' ;
28
107
import getRoutes from " ./router" ;
29
108
import getStore from " ./store" ;
30
109
31
110
import {
32
- AlertPlugin , BadgePlugin , ButtonGroupPlugin , ButtonPlugin ,
33
- CardPlugin , LayoutPlugin , SpinnerPlugin ,
111
+ AlertPlugin , BadgePlugin , BDropdown , BDropdownItem , BPopover ,
112
+ BIconArrow90degUp , BIconArrowLeft , BIconBlank , BIconCheck , BIconExclamationTriangle ,
113
+ BIconFlag , BIconFolderSymlink , BIconInfoLg , BIconList , BIconSearch , BIconServer ,
114
+ ButtonGroupPlugin , ButtonPlugin , CardPlugin , LayoutPlugin , SpinnerPlugin ,
34
115
VBToggle , VBVisible } from " bootstrap-vue" ;
35
116
import " bootstrap/dist/css/bootstrap.css" ;
36
117
import " bootstrap-vue/dist/bootstrap-vue.css" ;
37
118
38
119
import ErrorAlert from ' ./components/ErrorAlert.vue' ;
39
- import StacHeader from ' ./components/StacHeader .vue' ;
120
+ import StacLink from ' ./components/StacLink .vue' ;
40
121
41
122
import STAC from ' ./models/stac' ;
42
123
import Utils from ' ./utils' ;
43
124
import URI from ' urijs' ;
44
125
45
- import { API_LANGUAGE_CONFORMANCE } from ' ./i18n' ;
126
+ import { API_LANGUAGE_CONFORMANCE , STAC_LANGUAGE_EXT } from ' ./i18n' ;
46
127
import { getBest , prepareSupported } from ' ./locale-id' ;
47
128
import BrowserStorage from " ./browser-store" ;
48
129
import Authentication from " ./components/Authentication.vue" ;
@@ -103,9 +184,25 @@ export default {
103
184
store,
104
185
components: {
105
186
Authentication,
187
+ BDropdown,
188
+ BDropdownItem,
189
+ BIconArrow90degUp,
190
+ BIconArrowLeft,
191
+ BIconBlank,
192
+ BIconCheck,
193
+ BIconExclamationTriangle,
194
+ BIconFlag,
195
+ BIconFolderSymlink,
196
+ BIconInfoLg,
197
+ BIconList,
198
+ BIconSearch,
199
+ BIconServer,
200
+ BPopover,
106
201
ErrorAlert,
202
+ RootStats : () => import (' ./components/RootStats.vue' ),
107
203
Sidebar : () => import (' ./components/Sidebar.vue' ),
108
- StacHeader
204
+ StacLink,
205
+ Source : () => import (' ./components/Source.vue' )
109
206
},
110
207
props: {
111
208
... Props
@@ -118,21 +215,157 @@ export default {
118
215
};
119
216
},
120
217
computed: {
121
- ... mapState ([' allowSelectCatalog' , ' data' , ' dataLanguage' , ' description' , ' globalError' , ' stateQueryParameters' , ' title' , ' uiLanguage' , ' url' ]),
218
+ ... mapState ([' allowSelectCatalog' , ' conformsTo ' , ' data' , ' dataLanguage' , ' dataLanguages ' , ' description' , ' globalError' , ' stateQueryParameters' , ' title' , ' uiLanguage' , ' url' ]),
122
219
... mapState ({
220
+ catalogImageFromVueX: ' catalogImage' ,
221
+ localeFromVueX: ' locale' ,
123
222
detectLocaleFromBrowserFromVueX: ' detectLocaleFromBrowser' ,
124
223
supportedLocalesFromVueX: ' supportedLocales' ,
125
224
storeLocaleFromVueX: ' storeLocale'
126
225
}),
127
- ... mapGetters ([' displayCatalogTitle' , ' fromBrowserPath' , ' isExternalUrl' , ' root' , ' supportsConformance' , ' toBrowserPath' ]),
128
- ... mapGetters (' auth' , [' showLogin' ]),
226
+ ... mapGetters ([' canSearch' , ' collectionLink' , ' fromBrowserPath' , ' isExternalUrl' , ' parentLink' , ' root' , ' rootLink' , ' supportsConformance' , ' supportsExtension' , ' toBrowserPath' ]),
227
+ ... mapGetters (' auth' , { authMethod: ' method' }),
228
+ ... mapGetters (' auth' , [' canAuthenticate' , ' isLoggedIn' , ' showLogin' ]),
129
229
browserVersion () {
130
230
if (typeof STAC_BROWSER_VERSION !== ' undefined' ) {
131
231
return STAC_BROWSER_VERSION ;
132
232
}
133
233
else {
134
234
return " " ;
135
235
}
236
+ },
237
+ authIcon () {
238
+ return this .isLoggedIn ? ' b-icon-unlock' : ' b-icon-lock' ;
239
+ },
240
+ authTitle () {
241
+ return this .authMethod .getButtonTitle ();
242
+ },
243
+ authLabel () {
244
+ return this .isLoggedIn ? this .authMethod .getLogoutLabel () : this .authMethod .getLoginLabel ();
245
+ },
246
+ searchBrowserLink () {
247
+ if (! this .canSearch ) {
248
+ return null ;
249
+ }
250
+ let searchLink;
251
+ if (this .data instanceof STAC && ! this .data .equals (this .root )) {
252
+ searchLink = this .data .getSearchLink ();
253
+ }
254
+ if (searchLink) {
255
+ return ` /search${ this .data .getBrowserPath ()} ` ;
256
+ }
257
+ else if (this .root && this .allowSelectCatalog ) {
258
+ return ` /search${ this .root .getBrowserPath ()} ` ;
259
+ }
260
+ return ' /search' ;
261
+ },
262
+ currentLanguage () {
263
+ let lang = this .languages .find (l => l .code === this .localeFromVueX );
264
+ if (lang) {
265
+ return lang .native ;
266
+ }
267
+ else {
268
+ return ' -' ;
269
+ }
270
+ },
271
+ supportsLanguageExt () {
272
+ return this .supportsExtension (STAC_LANGUAGE_EXT );
273
+ },
274
+ languages () {
275
+ let languages = [];
276
+
277
+ // Add all UI languages
278
+ for (let code of this .supportedLocalesFromVueX ) {
279
+ languages .push ({
280
+ code,
281
+ native: this .$t (` languages.${ code} .native` ),
282
+ global: this .$t (` languages.${ code} .global` ),
283
+ ui: true
284
+ });
285
+ }
286
+
287
+ // Add missing data languages
288
+ for (let lang of this .dataLanguages ) {
289
+ if (! Utils .isObject (lang) || ! lang .code || this .supportedLocalesFromVueX .includes (lang .code )) {
290
+ continue ;
291
+ }
292
+ let newLang = {
293
+ code: lang .code
294
+ };
295
+ newLang .native = lang .name || lang .alternate || lang .code ;
296
+ newLang .global = lang .alternate || lang .name || lang .code ;
297
+ newLang .data = true ;
298
+ languages .push (newLang);
299
+ }
300
+
301
+ if (this .supportsLanguageExt ) {
302
+ // Determine which languages are complete
303
+ const uiSupported = prepareSupported (this .supportedLocalesFromVueX );
304
+ const dataSupported = prepareSupported (this .dataLanguages .map (l => l .code ));
305
+ for (let l of languages) {
306
+ if (! l .ui ) {
307
+ l .ui = Boolean (getBest (uiSupported, l .code , null ));
308
+ }
309
+ if (! l .data ) {
310
+ l .data = Boolean (getBest (dataSupported, l .code , null ));
311
+ }
312
+ }
313
+ }
314
+
315
+ const collator = new Intl.Collator (this .uiLanguage );
316
+ return languages .sort ((a ,b ) => collator .compare (a .global , b .global ));
317
+ },
318
+ isApi () {
319
+ // todo: This gives false results for a statically hosted OGC API - Records, which may include conformance classes
320
+ return Array .isArray (this .conformsTo ) && this .conformsTo .length > 0 ;
321
+ },
322
+ back () {
323
+ return this .$route .name === ' validation' ;
324
+ },
325
+ selfBrowserLink () {
326
+ return this .toBrowserPath (this .url );
327
+ },
328
+ type () {
329
+ if (this .data instanceof STAC ) {
330
+ if (this .data .isItem ()) {
331
+ return this .$tc (' stacItem' );
332
+ }
333
+ else if (this .data .isCollection ()) {
334
+ return this .$tc (` stacCollection` );
335
+ }
336
+ else if (this .data .isCatalog ()) {
337
+ return this .$tc (` stacCatalog` );
338
+ }
339
+ else if (Utils .hasText (this .data .type )) {
340
+ return this .data .type ;
341
+ }
342
+ }
343
+ return null ;
344
+ },
345
+ collectionLinkTitle () {
346
+ if (this .collectionLink && Utils .hasText (this .collectionLink .title )) {
347
+ return this .$t (' goToCollection.descriptionWithTitle' , this .collectionLink );
348
+ }
349
+ else {
350
+ return this .$t (' goToCollection.description' );
351
+ }
352
+ },
353
+ parentLinkTitle () {
354
+ if (this .parentLink && Utils .hasText (this .parentLink .title )) {
355
+ return this .$t (' goToParent.descriptionWithTitle' , this .parentLink );
356
+ }
357
+ else {
358
+ return this .$t (' goToParent.description' );
359
+ }
360
+ },
361
+ icon () {
362
+ if (this .data instanceof STAC ) {
363
+ let icons = this .data .getIcons ();
364
+ if (icons .length > 0 ) {
365
+ return icons[0 ];
366
+ }
367
+ }
368
+ return null ;
136
369
}
137
370
},
138
371
watch: {
@@ -295,6 +528,24 @@ export default {
295
528
},
296
529
methods: {
297
530
... mapActions ([' switchLocale' ]),
531
+ ... mapMutations (' auth' , [' addAction' ]),
532
+ ... mapActions (' auth' , [' requestLogin' , ' requestLogout' ]),
533
+ async logInOut () {
534
+ if (this .url ) {
535
+ this .addAction (() => this .$store .dispatch (" load" , {
536
+ url: this .url ,
537
+ show: true ,
538
+ force: true ,
539
+ noRetry: true
540
+ }));
541
+ }
542
+ if (this .isLoggedIn ) {
543
+ await this .requestLogout ();
544
+ }
545
+ else {
546
+ await this .requestLogin ();
547
+ }
548
+ },
298
549
detectLocale () {
299
550
let locale;
300
551
if (this .storeLocaleFromVueX ) {
@@ -380,6 +631,9 @@ export default {
380
631
},
381
632
hideError () {
382
633
this .$store .commit (' showGlobalError' , null );
634
+ },
635
+ isSearchPage () {
636
+ return this .$router .currentRoute .name === ' search' ;
383
637
}
384
638
}
385
639
};
@@ -392,3 +646,12 @@ export default {
392
646
@import " ./theme/page.scss" ;
393
647
@import " ./theme/custom.scss" ;
394
648
< / style>
649
+ < style lang= " scss" scoped>
650
+ .lang - item > .dropdown - item {
651
+ display: flex;
652
+ > .title {
653
+ flex: 1 ;
654
+ }
655
+ }
656
+ < / style>
657
+
0 commit comments