Skip to content

Commit e8af4b3

Browse files
committed
Copy button for Asset links #94
1 parent 7ce7027 commit e8af4b3

File tree

5 files changed

+93
-34
lines changed

5 files changed

+93
-34
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- Option to hide deprecated elements from lists by default (prop: `hideDeprecatedByDefault`)
12+
- Copy Asset URL
1213

1314
### Fixed
1415

components/SearchableList.vue

+6-17
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<script>
5353
import Utils from '../utils';
5454
import Loading from './internal/Loading.vue';
55+
import CopyMixin from './internal/CopyMixin';
5556
import Vue from 'vue';
5657
5758
export default {
@@ -60,6 +61,7 @@ export default {
6061
Loading,
6162
SearchBox: () => import('./SearchBox.vue')
6263
},
64+
mixins: [CopyMixin],
6365
props: {
6466
data: {
6567
type: [Array, Object],
@@ -137,8 +139,7 @@ export default {
137139
showDetails: {},
138140
showList: this.collapsed ? null : true,
139141
hideDeprecated: this.hideDeprecatedByDefault,
140-
summaries: [],
141-
canCopy: false
142+
summaries: []
142143
};
143144
},
144145
watch: {
@@ -201,9 +202,6 @@ export default {
201202
created() {
202203
this.filter();
203204
},
204-
mounted() {
205-
this.canCopy = navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function';
206-
},
207205
methods: {
208206
hasActiveFilter() {
209207
return this.searchTerm.length >= this.searchMinLength || this.hideDeprecated;
@@ -223,18 +221,9 @@ export default {
223221
this.$emit('summaries', this.summaries);
224222
},
225223
copyIdentifier(event, summary) {
226-
if (this.allowCopy && this.canCopy) {
227-
let elem = event.composedPath()[0]; // event.target doesn't work in web components
228-
navigator.clipboard.writeText(summary.identifier)
229-
.then(() => this.toggleIcon(elem, ''))
230-
.catch(() => this.toggleIcon(elem, ''))
231-
}
232-
},
233-
toggleIcon(elem, newIcon) {
234-
if (elem) {
235-
let oldIcon = elem.innerText;
236-
elem.innerText = newIcon;
237-
setTimeout(() => elem.innerText = oldIcon, 2000);
224+
if (this.allowCopy) {
225+
const elem = event.composedPath()[0]; // event.target doesn't work in web components
226+
this.copyText(summary.identifier, () => this.toggleIcon(elem, ''), () => this.toggleIcon(elem, ''));
238227
}
239228
},
240229
generateSummaries() {

components/base.scss

+21-4
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,20 @@
147147
}
148148
.vue-component .badges .badge a.badge-fill {
149149
margin: -0.35em -0.5em;
150-
padding: 0.35em 0.5em 0.25em 0.5em;
150+
padding: 0.35em 0.5em;
151151
display: block;
152152
border-bottom: 0px;
153153
}
154154
.vue-component .badges.small .badge a.badge-fill {
155155
margin: -0.2em -0.3em;
156-
padding: 0.25em 0.35em 0.2em 0.35em;
156+
padding: 0.25em 0.35em;
157157
}
158158
.vue-component .badges .badge a:hover {
159159
color: #fff;
160160
border-bottom-style: solid;
161161
}
162162
.vue-component .badges .default {
163-
background-color: black;
163+
background-color: #555;
164164
}
165165
.vue-component .badges .green {
166166
background-color: green;
@@ -177,17 +177,34 @@
177177
.vue-component .badges .option3 {
178178
background-color: #936;
179179
}
180+
.vue-component .badges .option4 {
181+
background-color: #963;
182+
}
180183
.vue-component .badges .deprecated {
181184
background-color: red;
182185
}
183186
.vue-component .badges .experimental {
184187
background-color: blueviolet;
185188
}
186189
.vue-component .badges .action {
187-
background-color: chocolate;
190+
border: 1px solid black;
191+
background-color: white;
192+
color: black;
193+
margin: 0.33em;
194+
cursor: pointer;
195+
text-transform: none;
188196

189197
&:hover {
190198
background-color: black;
199+
color: white;
200+
201+
> a {
202+
color: white;
203+
}
204+
}
205+
206+
> a {
207+
color: black;
191208
}
192209
}
193210
/* Badges for UDFs */

components/internal/CopyMixin.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export default {
2+
data() {
3+
return {
4+
canCopy: false
5+
}
6+
},
7+
mounted() {
8+
this.canCopy = navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function';
9+
},
10+
methods: {
11+
copyText(text, onSuccess = null, onError = null) {
12+
if (this.canCopy) {
13+
const promise = navigator.clipboard.writeText(text)
14+
if (onSuccess) {
15+
promise.then(onSuccess);
16+
}
17+
if (onError) {
18+
promise.catch(onError);
19+
}
20+
}
21+
},
22+
toggleIcon(elem, newIcon) {
23+
if (elem) {
24+
let oldIcon = elem.innerText;
25+
elem.innerText = newIcon;
26+
setTimeout(() => elem.innerText = oldIcon, 2000);
27+
}
28+
}
29+
}
30+
}

components/internal/StacAsset.vue

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
<template>
22
<li class="vue-component asset">
33
<h4>
4-
<ul class="badges actions">
5-
<li class="badge action download">
6-
<a class="badge-fill" :href="asset.href" target="_blank" download>
7-
Download '{{ asset.title || id }}'
8-
<template v-if="fileFormat">as {{ fileFormat }}</template>
9-
</a>
10-
</li>
11-
</ul>
12-
<ul v-if="Array.isArray(asset.roles)" class="badges">
4+
<span>{{ asset.title || id }}</span>
5+
<ul v-if="Array.isArray(asset.roles)" class="badges roles">
136
<li v-for="role in asset.roles" :key="role" class="badge" :class="role === 'data' ? 'green' : 'secondary'">{{ role }}</li>
147
</ul>
158
</h4>
9+
<ul class="badges actions primary">
10+
<li class="badge action download">
11+
<a class="badge-fill" :href="asset.href" target="_blank" download>
12+
<span class="icon">💾</span> Download <template v-if="fileFormat">{{ fileFormat }}</template>
13+
</a>
14+
</li>
15+
<li class="badge action copy" @click.prevent.stop="copyURL($event, asset.href)">
16+
<span class="icon">📋</span> Copy URL
17+
</li>
18+
</ul>
1619
<Description v-if="asset.description" :description="asset.description" :compact="true" />
1720
<StacFields type="Asset" :metadata="asset" :ignore="ignore" title="" :context="context" headingTag="h5" />
1821
</li>
@@ -22,13 +25,15 @@
2225
import { Formatters } from '@radiantearth/stac-fields';
2326
import Description from '../Description.vue';
2427
import StacFields from './StacFields.vue';
28+
import CopyMixin from './CopyMixin';
2529
2630
export default {
2731
name: 'Asset',
2832
components: {
2933
Description,
3034
StacFields
3135
},
36+
mixins: [CopyMixin],
3237
props: {
3338
asset: {
3439
type: Object,
@@ -55,25 +60,42 @@ export default {
5560
}
5661
return null;
5762
}
63+
},
64+
methods: {
65+
copyURL(event, url) {
66+
const elem = event.composedPath()[0].querySelector('.icon'); // event.target doesn't work in web components
67+
this.copyText(url, () => this.toggleIcon(elem, ''), () => this.toggleIcon(elem, ''));
68+
}
5869
}
5970
}
6071
</script>
6172

6273
<style lang="scss">
6374
.vue-component.asset {
75+
margin-bottom: 1.5em;
76+
77+
> * {
78+
margin: 0.25em 0;
79+
}
80+
81+
.actions > .action {
82+
padding: 0.5em;
83+
}
84+
6485
h4 {
6586
font-size: 1.1em;
66-
margin: 0;
6787
font-weight: normal;
6888
69-
.download {
70-
margin-right: 1em;
89+
> .roles {
90+
font-size: 0.95em;
91+
margin-left: 0.3em;
7192
}
7293
}
94+
7395
h5 {
7496
font-size: 1.1em;
7597
font-weight: bold;
76-
margin: 0.75em 0 0.5em;
98+
margin: 0.5em 0;
7799
}
78100
79101
.metadata {

0 commit comments

Comments
 (0)