Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions docs/actions.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Actions <!-- omit in toc -->

STAC Browser has a pluggable interface to share or open assets and links with other services, which we call "actions".
STAC Browser has a pluggable interface to share or open assets, catalogs, collections, items and links with other services, which we call "actions".

An action adds a button to an asset or link if certain requirements are met, which can then be executed by users.
An action adds a button to an asset, catalog, collection, item or link if certain requirements are met, which can then be executed by users.
For example, you could open COPC files in a dedicated COPC Viewer, which otherwise you could only download.

- [User Guide](#user-guide)
- [Assets](#assets)
- [Catalogs, Collections and Items](#catalogs-collections-and-items)
- [Links](#links)
- [Developer Guide](#developer-guide)

Expand Down Expand Up @@ -50,6 +51,37 @@ export default {

Save the file and restart / rebuild STAC Browser.

### Catalogs, Collections and Items

The following actions are available:

- `stac-map`: Allows to open items through stac-map at <https://developmentseed.org/stac-map/>.

All actions for catalogs, collections and items are stored in the folder [`src/actions/stac`](../src/actions/stac) if you want to inspect them.

The actions can be enabled by adding them to the [`stacActions.config.js`](../stacActions.config.js) file.
Open the file and you'll see a number of imports and exports.
Import the file for the action that you want to enable, e.g. for stac-map:

```js
import StacMap from './src/actions/stac/StacMap.js';
```

The path is fixed to `./src/actions/stac/`, the file extension is always `.js`.
In-between add the file name from the list above.
The import name should be the file name without extension (i.e. `StacMap` again).

Lastly, add the import name to the list of exports, e.g.

```js
export default {
OtherAction,
StacMap
};
```

Save the file and rebuild / restart STAC Browser.

### Links

The following actions are available:
Expand Down Expand Up @@ -86,16 +118,22 @@ Save the file and rebuild / restart STAC Browser.

## Developer Guide

Implementing actions for assets and links follows a very similar pattern.
The main difference is that assets implement the [`AssetActionPlugin` interface](../src/actions/AssetActionPlugin.js) while links implement the [`LinkActionPlugin` interface](../src/actions/LinkActionPlugin.js).
Similarly, actions for assets are stored in the folder links are stored in the folder [`src/actions/assets`](../src/actions/assets) while links are stored in the folder [`src/actions/links`](../src/actions/links).
Implementing actions for assets, items and links follows a very similar pattern.
The main difference is that each action implements its own interface:
- assets implement the [`AssetActionPlugin` interface](../src/actions/AssetActionPlugin.js)
- catalogs, collections and items implement the [`StacActionPlugin` interface](../src/actions/StacActionPlugin.js)
- links implement the [`LinkActionPlugin` interface](../src/actions/LinkActionPlugin.js)
Similarly, actions are stored in their own folder:
- assets are stored in the folder [`src/actions/assets`](../src/actions/assets)
- catalogs, collections and items are stored in the folder [`src/actions/stac`](../src/actions/stac)
- links are stored in the folder [`src/actions/links`](../src/actions/links)

The interfaces for both look as follows:
All interfaces look as follows:

- `constructor(data: object, component: Vue, id: string)`
- `data`: The asset or link object, it is available in the class through `this.asset` (for assets) and `this.link` (for links).
- `component`: The parent Asset/Link Vue component (available in the class through `this.component`)
- `id`: Internal ID of the asset or link, not meant to be used.
- `data`: The asset, catalog, collection, item or link object, it is available in the class through `this.asset` (for assets), `this.object` (for items) and `this.link` (for links).
- `component`: The parent Asset/Catalog/Collection/Item/Link Vue component (available in the class through `this.component`)
- `id`: Internal ID of the asset, catalog, collection, item or link, not meant to be used.
- `get btnOptions() : object`
- This should return an object of button options, see [VueBootstrap b-button](https://bootstrap-vue.org/docs/components/button/#component-reference) for details. Returns `href`, `rel` (only for links) and `target` (set to `_blank`) by default.
- `get onClick() : function(event: MouseEvent)`
Expand Down
9 changes: 9 additions & 0 deletions src/actions/StacActionPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import ActionPlugin from './ActionPlugin';

export default class StacActionPlugin extends ActionPlugin {

constructor(object, component, id) {
super(id, component);
this.object = object;
}
}
18 changes: 18 additions & 0 deletions src/actions/stac/StacMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import StacActionPlugin from "../StacActionPlugin";
import URI from 'urijs';
import i18n from "../../i18n";

export default class StacMap extends StacActionPlugin {

get show() {
return this.object.isItem();
}

get uri() {
return URI('https://developmentseed.org/stac-map/').addQuery('href', this.object.getAbsoluteUrl());
}

get text() {
return i18n.t('actions.openIn', {service: 'stac-map'});
}
}
7 changes: 7 additions & 0 deletions src/components/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
</small>
</b-card-text>
</b-card-body>
<StacActions :data="item" footer vertical size="sm" />
</b-card>
</template>

<script>
import { mapState, mapGetters } from 'vuex';
import FileFormatsMixin from './FileFormatsMixin';
import ThumbnailCardMixin from './ThumbnailCardMixin';
import StacActions from './StacActions.vue';
import StacLink from './StacLink.vue';
import { STAC } from 'stac-js';
import { formatTemporalExtent, formatTimestamp, formatMediaType } from '@radiantearth/stac-fields/formatters';
Expand All @@ -38,6 +40,7 @@ export default {
name: 'Item',
components: {
StacLink,
StacActions,
Keywords: () => import('./Keywords.vue')
},
filters: {
Expand Down Expand Up @@ -141,6 +144,10 @@ export default {
text-align: center;
position: relative;
}

&:has(.obj-actions) {
padding-bottom: 0.5rem;
}
}
}
</style>
66 changes: 66 additions & 0 deletions src/components/StacActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<!-- add card footer (for items search) -->
<b-card-footer v-if="footer && hasButtons">
<b-button-group class="obj-actions" :vertical="vertical" :size="size">
<b-button v-for="action of actions" v-bind="action.btnOptions" :key="action.id" variant="primary" @click="action.onClick">
<component v-if="action.icon" :is="action.icon" class="mr-1" />
{{ action.text }}
</b-button>
</b-button-group>
</b-card-footer>
<!-- add only button group (for item/collection vue) -->
<b-button-group v-else-if="hasButtons" class="obj-actions" :vertical="vertical" :size="size">
<b-button v-for="action of actions" v-bind="action.btnOptions" :key="action.id" variant="primary" @click="action.onClick">
<component v-if="action.icon" :is="action.icon" class="mr-1" />
{{ action.text }}
</b-button>
</b-button-group>
</template>


<script>
import { BIconBoxArrowUpRight } from 'bootstrap-vue';
import StacActions from '../../stacActions.config';

let i = 0;

export default {
name: 'StacActions',
components: {
BIconBoxArrowUpRight
},
props: {
data: {
type: Object,
required: true
},
footer: {
type: Boolean,
default: false
},
vertical: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'md'
}
},
data() {
return {
id: i++
};
},
computed: {
actions() {
return Object.entries(StacActions)
.map(([id, plugin]) => new plugin(this.data, this, id))
.filter(plugin => plugin.show);
},
hasButtons() {
return this.actions && this.actions.length > 0;
}
}
};
</script>
7 changes: 7 additions & 0 deletions src/views/Catalog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
</b-tabs>
</b-card>
</section>
<StacActions :data="data" />
<Assets v-if="hasAssets" :assets="assets" :context="data" :shown="selectedReferences" @showAsset="showAsset" />
<Assets v-if="hasItemAssets && !hasItems" :assets="itemAssets" :context="data" :definition="true" />
<Providers v-if="providers" :providers="providers" />
Expand Down Expand Up @@ -63,6 +64,7 @@
import { mapState, mapGetters } from 'vuex';
import Catalogs from '../components/Catalogs.vue';
import Description from '../components/Description.vue';
import StacActions from '../components/StacActions.vue';
import Items from '../components/Items.vue';
import ReadMore from "vue-read-more-smooth";
import ShowAssetLinkMixin from '../components/ShowAssetLinkMixin';
Expand All @@ -85,6 +87,7 @@ export default {
CollectionLink: () => import('../components/CollectionLink.vue'),
DeprecationNotice: () => import('../components/DeprecationNotice.vue'),
Description,
StacActions,
Items,
Keywords: () => import('../components/Keywords.vue'),
Links: () => import('../components/Links.vue'),
Expand Down Expand Up @@ -439,6 +442,10 @@ export default {
}
}

.obj-actions + .assets, .obj-actions + .providers, .obj-actions + .metadata {
margin-top: 1rem;
}

.metadata .card-columns {
column-count: 1;

Expand Down
7 changes: 7 additions & 0 deletions src/views/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</b-tabs>
</b-card>
</section>
<StacActions :data="data" />
<Assets v-if="hasAssets" :assets="assets" :context="data" :shown="selectedReferences" @showAsset="showAsset" />
<Links v-if="additionalLinks.length > 0" :title="$t('additionalResources')" :links="additionalLinks" :context="data" />
</b-col>
Expand All @@ -38,6 +39,7 @@
<script>
import { mapState, mapGetters } from 'vuex';
import Description from '../components/Description.vue';
import StacActions from '../components/StacActions.vue';
import ReadMore from "vue-read-more-smooth";
import ShowAssetLinkMixin from '../components/ShowAssetLinkMixin';
import DeprecationMixin from '../components/DeprecationMixin';
Expand All @@ -53,6 +55,7 @@ export default {
BTab,
CollectionLink: () => import('../components/CollectionLink.vue'),
Description,
StacActions,
DeprecationNotice: () => import('../components/DeprecationNotice.vue'),
Keywords: () => import('../components/Keywords.vue'),
Links: () => import('../components/Links.vue'),
Expand Down Expand Up @@ -122,6 +125,10 @@ export default {
align-self: center;
}

.obj-actions + .assets {
margin-top: 1rem;
}

.metadata .card-columns {
column-count: 1;

Expand Down
18 changes: 16 additions & 2 deletions src/views/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@
:count="totalCount" :apiFilters="collectionFilters"
>
<template #catalogFooter="slot">
<b-button-group v-if="itemSearch || canFilterItems(slot.data)" vertical size="sm">
<b-button-group v-if="itemSearch || canFilterItems(slot.data) || hasActions(slot.data)" vertical size="sm">
<b-button v-if="itemSearch" variant="outline-primary" :pressed="selectedCollections[slot.data.id]" @click="selectForItemSearch(slot.data)">
<b-icon-check-square v-if="selectedCollections[slot.data.id]" />
<b-icon-square v-else />
<span class="ml-2">{{ $t('search.selectForItemSearch') }}</span>
</b-button>
<StacLink :button="{variant: 'outline-primary', disabled: !canFilterItems(slot.data)}" :data="slot.data" :title="$t('search.filterCollection')" :state="{itemFilterOpen: 1}" />
<b-button v-for="action of actions(slot.data)" v-bind="action.btnOptions" :key="action.id" variant="outline-primary" @click="action.onClick">
<component v-if="action.icon" :is="action.icon" class="mr-1" />
{{ action.text }}
</b-button>
</b-button-group>
</template>
</Catalogs>
Expand All @@ -68,14 +72,16 @@ import Utils from '../utils';
import SearchFilter from '../components/SearchFilter.vue';
import Loading from '../components/Loading.vue';
import ErrorAlert from '../components/ErrorAlert.vue';
import StacActions from '../../stacActions.config';
import { getDisplayTitle, createSTAC, ItemCollection } from '../models/stac';
import { STAC } from 'stac-js';
import { BIconCheckSquare, BIconSquare, BTabs, BTab } from 'bootstrap-vue';
import { BIconBoxArrowUpRight, BIconCheckSquare, BIconSquare, BTabs, BTab } from 'bootstrap-vue';
import { getErrorCode, getErrorMessage, processSTAC, stacRequest } from '../store/utils';

export default {
name: "Search",
components: {
BIconBoxArrowUpRight,
BIconCheckSquare,
BIconSquare,
BTab,
Expand Down Expand Up @@ -242,6 +248,14 @@ export default {
}
return false;
},
actions(data) {
return Object.entries(StacActions)
.map(([id, plugin]) => new plugin(data, this, id))
.filter(plugin => plugin.show);
},
hasActions(data) {
return this.actions(data).length > 0;
},
async loadResults(link) {
this.error = null;
this.errorId = null;
Expand Down
5 changes: 5 additions & 0 deletions stacActions.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// import StacMap from './src/actions/stac/StacMap.js';

export default {
// StacMap,
};