Skip to content

Code example generation for search page #381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
"bootstrap-vue": "^2.21.2",
"bs58": "^4.0.1",
"chart.js": "^4.3.0",
"clipboard": "^2.0.11",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use v-clipboard, can you re-use that instead of a new/this dependency?

"commonmark": "^0.29.3",
"core-js": "^3.6.5",
"highlight.js": "^11.9.0",
"leaflet": "^1.8.0",
"node-polyfill-webpack-plugin": "^2.0.0",
"remove-markdown": "^0.5.0",
Expand All @@ -51,6 +53,7 @@
"v-clipboard": "^3.0.0-next.1",
"vue": "^2.6.12",
"vue-chartjs": "^5.2.0",
"vue-highlightjs": "^1.3.3",
"vue-i18n": "^8.28.2",
"vue-multiselect": "^2.1.6",
"vue-read-more-smooth": "^0.1.8",
Expand Down
8 changes: 7 additions & 1 deletion src/StacBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ import {
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

import VueHighlightJS from "vue-highlightjs";
import highlight from 'highlight.js';
import 'highlight.js/lib/languages/python';
import 'highlight.js/styles/github.css';

import ErrorAlert from './components/ErrorAlert.vue';
import StacHeader from './components/StacHeader.vue';

Expand All @@ -49,8 +54,9 @@ import { getBest, prepareSupported } from './locale-id';
Vue.use(AlertPlugin);
Vue.use(ButtonGroupPlugin);
Vue.use(ButtonPlugin);
Vue.use(BadgePlugin);
Vue.use(BadgePlugin)
Vue.use(CardPlugin);
Vue.use(VueHighlightJS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be registered so that it doesn't import into the global bundle? Ideally this would only be loaded if needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could say a bit more - I'm new to Vue, so some of the concepts here require me to squint a bit more than others might

Copy link
Collaborator

@m-mohr m-mohr Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be imported in the CodeBox component and not use Vue.use. I'm not sure whether this library supports this though.

Similarly, the Bootstrap Badges should also be imported individually where the are used. I think there should be examples around in the code if you search for the BBadge component.

I'm a bit busy right now so can't elaborate further at this point.

Copy link
Author

@moradology moradology Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I follow you - just been using python and jvm lingo for long enough that 'global bundle' tripped me up. Also, I think the badge plugin is from prior work and I accidentally shifted its order to the diff's confusion

Copy link
Collaborator

@m-mohr m-mohr Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global Bundle is probably also not the right terminology. I think better is "app bundle" or "initially loaded files". I pretty much just want that it is lazily loaded when needed, not everytime the page loads. :-)

Vue.use(LayoutPlugin);
Vue.use(SpinnerPlugin);

Expand Down
56 changes: 56 additions & 0 deletions src/components/CodeBox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<div class="codebox">
<pre v-highlightjs>
<code :class="language" :id="componentId">
{{ code }}
</code>
</pre>
<button ref="copyButton" :data-clipboard-target="'#' + componentId">Copy</button>
</div>
</template>

<script>
import Clipboard from 'clipboard';

export default {
name: "CodeBox",
props: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both props should probably be required.

code: String,
language: String
},
data() {
return {
componentId: `${this.language}Content`
}
},
mounted() {
new Clipboard(this.$refs.copyButton);
},
};
</script>

<style scoped>
.codebox {
border: 1px solid #ccc;
padding: 16px;
border-radius: 8px;
}
.codebox button {
background-color: #007BFF;
color: #FFF;
padding: 5px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}

.codebox button:hover {
background-color: #0056b3;
}
.codebox pre, .codebox code {
padding: 0;
margin: 0;
}

</style>
122 changes: 122 additions & 0 deletions src/components/SearchCode.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<template>
<b-tabs>
<b-tab title="Python">
<CodeBox :code="pythonCode" language="python" />
</b-tab>
<b-tab title="Javascript">
<CodeBox :code="javascriptCode" language="javascript" />
</b-tab>
<b-tab title="R">
<CodeBox :code="rCode" language="r" />
</b-tab>
</b-tabs>
</template>

<script>
import { BTabs, BTab } from 'bootstrap-vue';
export default {
name: "SearchCode",
props: {
catalogHref: String,
filters: Object,
},
components: {
BTab,
BTabs,
CodeBox: () => import('./CodeBox.vue'),
},
data() {
return {
componentId: `${this.language}Content`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unsused

pythonCode: null,
javascriptCode: null,
rCode: null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unsused

}
},
methods: {
filterString() {
let obj = this.filters || {}
for (let key in obj) {
if (obj[key] === null || (Array.isArray(obj[key]) && obj[key].length === 0)) {
delete obj[key];
}
}
return JSON.stringify(obj)
},
generatePython() {
return `
from pystac_client import Client

# Connect to STAC API
stac_endpoint = '${this.catalogHref}'
client = Client.open(stac_endpoint)

# Build query
query = ${this.filterString()}

# Perform search
search_result = client.search(query )
`
},
generateJavascript() {
return `
// Define the STAC API endpoint
const STAC_ENDPOINT = '${this.catalogHref}';

// Define your search parameters
const searchParams = ${this.filterString()};

// Perform the search
fetch(STAC_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(searchParams)
})
.then(response => response.json())
.then(data => {
console.log("STAC search results:", data);
})
.catch(error => {
console.error("Error fetching STAC data:", error);
});
`
},
generateR() {
return `
from pystac_client import Client

# Connect to STAC API
stac_api_url = '${this.catalogHref}'
client = Client.open(stac_api_url)

# Build query
query = ${this.filterString()}

# Perform search
search_result = client.search(query)
`
},
updateCode() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a bit better to only generate what's shown, not everything upfront, especially if we extend this.

this.pythonCode = this.generatePython()
this.javascriptCode = this.generateJavascript()
this.rCode = this.generateR()
}
},
watch: {
filters: {
deep: true,
handler() {
this.updateCode();
}
}
},
mounted() {
this.updateCode();
},
};
</script>

<style scoped>
</style>
74 changes: 41 additions & 33 deletions src/views/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,48 @@
@input="setFilters"
/>
</b-tab>

</b-tabs>
</b-col>
<b-col class="right">
<b-alert v-if="error" variant="error" show>{{ error }}</b-alert>
<Loading v-else-if="!data && loading" fill top />
<b-alert v-else-if="data === null" variant="info" show>{{ $t('search.modifyCriteria') }}</b-alert>
<b-alert v-else-if="results.length === 0" variant="warning" show>{{ $t('search.noItemsFound') }}</b-alert>
<template v-else>
<div id="search-map" v-if="itemCollection">
<Map :stac="stac" :stacLayerData="itemCollection" scrollWheelZoom popover />
</div>
<Catalogs
v-if="isCollectionSearch" :catalogs="results" collectionsOnly
:pagination="pagination" :loading="loading" @paginate="loadResults"
:count="totalCount"
>
<template #catalogFooter="slot">
<b-button-group v-if="itemSearch || canFilterItems(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-group>
</template>
</Catalogs>
<Items
v-else
:stac="stac" :items="results" :api="true" :allowFilter="false"
:pagination="pagination" :loading="loading" @paginate="loadResults"
:count="totalCount"
/>
</template>
<b-alert v-if="error" variant="error" show>{{ error }}</b-alert>
<Loading v-else-if="!data && loading" fill top />
<b-alert v-else-if="data === null" variant="info" show>{{ $t('search.modifyCriteria') }}</b-alert>
<b-alert v-else-if="results.length === 0" variant="warning" show>{{ $t('search.noItemsFound') }}</b-alert>
<template v-else>
<b-row>
<b-col class="left">
<SearchCode :filters="filters" :catalogHref="searchLink.href"></SearchCode>
</b-col>
<b-col class="right">
<div id="search-map" v-if="itemCollection">
<Map :stac="stac" :stacLayerData="itemCollection" scrollWheelZoom popover />
</div>
</b-col>
</b-row>
<Catalogs
v-if="isCollectionSearch" :catalogs="results" collectionsOnly
:pagination="pagination" :loading="loading" @paginate="loadResults"
:count="totalCount"
>
<template #catalogFooter="slot">
<b-button-group v-if="itemSearch || canFilterItems(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-group>
</template>
</Catalogs>
<Items
v-else
:stac="stac" :items="results" :api="true" :allowFilter="false"
:pagination="pagination" :loading="loading" @paginate="loadResults"
:count="totalCount"
/>
</template>
</b-col>
</b-row>
<b-alert v-if="selectedCollectionCount > 0" show variant="dark" class="selected-collections-action">
Expand Down Expand Up @@ -78,6 +86,7 @@ export default {
BTab,
BTabs,
Catalogs: () => import('../components/Catalogs.vue'),
SearchCode: () => import('../components/SearchCode.vue'),
Loading,
Items: () => import('../components/Items.vue'),
Map: () => import('../components/Map.vue'),
Expand All @@ -102,7 +111,7 @@ export default {
itemFilters: {},
collectionFilters: {},
activeSearch: 0,
selectedCollections: {}
selectedCollections: {},
};
},
computed: {
Expand Down Expand Up @@ -242,7 +251,6 @@ export default {
this.loading = true;
try {
this.link = Utils.addFiltersToLink(link, this.filters, this.itemsPerPage);

let key = this.isCollectionSearch ? 'collections' : 'features';
let response = await stacRequest(this.$store, this.link);
if (response) {
Expand Down