-
Notifications
You must be signed in to change notification settings - Fork 137
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
Code example generation for search page #381
base: main
Are you sure you want to change the base?
Changes from 1 commit
b973c18
36e86dc
d1cdb0a
2598ae2
904cb09
7942f01
9f02d73
33d7c2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
|
||
|
@@ -49,8 +54,9 @@ import { getBest, prepareSupported } from './locale-id'; | |
Vue.use(AlertPlugin); | ||
Vue.use(ButtonGroupPlugin); | ||
Vue.use(ButtonPlugin); | ||
Vue.use(BadgePlugin); | ||
Vue.use(CardPlugin); | ||
Vue.use(BadgePlugin) | ||
Vue.use(VueHighlightJS); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
||
|
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: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,40 +17,58 @@ | |
@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"> | ||
<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> | ||
</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"> | ||
|
@@ -78,6 +96,7 @@ export default { | |
BTab, | ||
BTabs, | ||
Catalogs: () => import('../components/Catalogs.vue'), | ||
CodeBox: () => import('../components/CodeBox.vue'), | ||
Loading, | ||
Items: () => import('../components/Items.vue'), | ||
Map: () => import('../components/Map.vue'), | ||
|
@@ -102,7 +121,11 @@ export default { | |
itemFilters: {}, | ||
collectionFilters: {}, | ||
activeSearch: 0, | ||
selectedCollections: {} | ||
selectedCollections: {}, | ||
|
||
pythonCode: null, | ||
javascriptCode: null, | ||
rCode: null | ||
}; | ||
}, | ||
computed: { | ||
|
@@ -237,13 +260,90 @@ export default { | |
} | ||
return false; | ||
}, | ||
filterString() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally all the code related to the code generation etc. should be in a separate component, not in the Search component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, even better would be to have model classes for code generation. One method per use case and one class per language. Similarly to what I did here for another project: https://github.com/Open-EO/openeo-web-editor/tree/master/src/export All UI related stuff should still be in the Vue Component, of course. So really just the code generation in classes. |
||
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.searchLink.href}' | ||
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.searchLink.href}'; | ||
|
||
// 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() { | ||
let filterString = JSON.stringify(this.filters || {}) | ||
return ` | ||
from pystac_client import Client | ||
|
||
# Connect to STAC API | ||
stac_api_url = '${this.searchLink}' | ||
client = Client.open(stac_api_url) | ||
|
||
# Build query | ||
query = ${filterString} | ||
|
||
# Perform search | ||
search_result = client.search(query) | ||
` | ||
}, | ||
updateCode() { | ||
this.pythonCode = this.generatePython() | ||
this.javascriptCode = this.generateJavascript() | ||
this.rCode = this.generateR() | ||
}, | ||
async loadResults(link) { | ||
this.error = null; | ||
this.loading = true; | ||
try { | ||
this.link = Utils.addFiltersToLink(link, this.filters, this.itemsPerPage); | ||
console.log("link", this.link) | ||
this.pythonCode = this.generatePython(this.link) | ||
this.javascriptCode = this.generateJavascript(this.link) | ||
this.rCode = this.generateR(this.link) | ||
|
||
let key = this.isCollectionSearch ? 'collections' : 'features'; | ||
// console.log(1, this.$store) | ||
// console.log(2, this.link) | ||
// console.log(3, key) | ||
let response = await stacRequest(this.$store, this.link); | ||
if (response) { | ||
this.showPage(response.config.url); | ||
|
@@ -274,6 +374,7 @@ export default { | |
} | ||
else { | ||
await this.loadResults(this.searchLink); | ||
this.updateCode(this.searchLink) | ||
} | ||
}, | ||
showPage(url) { | ||
|
There was a problem hiding this comment.
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?