forked from bluerobotics/BlueOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
frontend: Add extension manifest settings
- Loading branch information
1 parent
830bdec
commit d7458a2
Showing
2 changed files
with
405 additions
and
2 deletions.
There are no files selected for viewing
381 changes: 381 additions & 0 deletions
381
core/frontend/src/components/kraken/ExtensionSettings.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,381 @@ | ||
<template> | ||
<v-dialog v-model="settings_open" width="500px"> | ||
<v-card min-width="100%"> | ||
<v-app-bar> | ||
<v-toolbar-title>Extensions Manifest</v-toolbar-title> | ||
<v-spacer /> | ||
</v-app-bar> | ||
<v-card class="px-3 pb-3 mx-2" elevation="0"> | ||
<v-list> | ||
<v-card-subtitle class="text-md-center" max-width="30"> | ||
Drag manifest sources to set priority. <br /><b>Top ones have higher priority.</b> | ||
</v-card-subtitle> | ||
<draggable v-model="manifests"> | ||
<v-card | ||
v-for="(element, index) in manifests" | ||
:key="index" | ||
class="pl-3 ma-2 pa-1 d-flex align-center justify-center" | ||
style="cursor: pointer;" | ||
> | ||
{{ order(index + 1) }} | ||
<v-spacer /> | ||
{{ element.name }} | ||
<v-spacer /> | ||
<v-btn | ||
icon | ||
:disabled="element.factory" | ||
tooltip="Delete Source" | ||
@click="removeSource(element.identifier)" | ||
> | ||
<v-icon>mdi-delete</v-icon> | ||
</v-btn> | ||
<v-btn | ||
icon | ||
@click="setEditingModal(element)" | ||
> | ||
<v-icon>{{ element.factory ? 'mdi-pencil-lock' : 'mdi-pencil' }}</v-icon> | ||
</v-btn> | ||
<v-icon v-text="'mdi-drag'" /> | ||
</v-card> | ||
</draggable> | ||
<v-card | ||
class="ma-2 py-1 mt-4 d-flex align-center justify-center" | ||
@click.prevent="setCreateModal" | ||
> | ||
<v-spacer /> | ||
<v-icon>mdi-plus</v-icon> | ||
<v-spacer /> | ||
</v-card> | ||
</v-list> | ||
<v-alert | ||
v-if="has_operation_error" | ||
class="mx-2" | ||
type="error" | ||
> | ||
{{ operation_error }} | ||
</v-alert> | ||
<v-progress-linear | ||
v-if="operation_loading" | ||
indeterminate | ||
min-width="300" | ||
/> | ||
<v-divider /> | ||
<v-card-actions class="justify-center mt-1"> | ||
<v-btn | ||
color="primary" | ||
@click="settings_open = false" | ||
> | ||
Cancel | ||
</v-btn> | ||
<v-spacer /> | ||
<v-btn | ||
color="success" | ||
@click="reorderSources" | ||
> | ||
Apply | ||
</v-btn> | ||
</v-card-actions> | ||
</v-card> | ||
</v-card> | ||
<v-dialog v-model="operation_open" width="600px"> | ||
<v-card elevation="0"> | ||
<v-card-title class="mb-7"> | ||
{{ is_editing ? 'Update Source' : 'Add a new source' }} | ||
</v-card-title> | ||
<v-card-subtitle class="text-md-center" max-width="30"> | ||
<b>Factory sources can only be enabled/disabled.</b> | ||
</v-card-subtitle> | ||
<v-text-field | ||
v-model="source_url" | ||
:disabled="is_factory" | ||
class="mx-6" | ||
label="URL" | ||
outlined | ||
dense | ||
@input="deductNameFromURL" | ||
/> | ||
<v-text-field | ||
v-model="source_name" | ||
:disabled="is_factory" | ||
class="mx-6 pr-8" | ||
label="Name" | ||
outlined | ||
dense | ||
@input="blockNameAutoComplete" | ||
/> | ||
<v-checkbox | ||
v-model="source_enabled" | ||
class="mx-6 mt-0" | ||
label="Source Enabled" | ||
/> | ||
<v-divider /> | ||
<v-card-actions class="justify-center pb-4 mt-3 mx-1"> | ||
<v-btn | ||
color="primary" | ||
@click="operation_open = false" | ||
> | ||
Cancel | ||
</v-btn> | ||
<v-spacer /> | ||
<v-btn | ||
color="success" | ||
@click="applySubModalAction" | ||
> | ||
Apply | ||
</v-btn> | ||
</v-card-actions> | ||
</v-card> | ||
</v-dialog> | ||
</v-dialog> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import Vue from 'vue' | ||
import { Manifest, ManifestSource } from '@/types/kraken' | ||
import back_axios from '@/utils/api' | ||
const KRAKEN_API_V2_URL = '/kraken/v2.0' | ||
export default Vue.extend({ | ||
name: 'ExtensionSettings', | ||
props: { | ||
value: { | ||
type: Boolean, | ||
required: true, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
settings_open: false, | ||
operation_open: false, | ||
operation_loading: false, | ||
operation_error: undefined as string | undefined, | ||
source_name: '', | ||
source_url: '', | ||
source_enabled: true, | ||
allow_deduct_name: true, | ||
source_url_copy_icon: 'mdi-content-copy', | ||
editing_source: undefined as Manifest | undefined, | ||
manifests: [] as Manifest[], | ||
} | ||
}, | ||
computed: { | ||
is_editing(): boolean { | ||
return this.editing_source !== undefined | ||
}, | ||
is_factory(): boolean { | ||
return this.editing_source?.factory ?? false | ||
}, | ||
has_operation_error(): boolean { | ||
return this.operation_error !== undefined | ||
}, | ||
}, | ||
watch: { | ||
value(val) { | ||
this.settings_open = val | ||
if (val) { | ||
this.fetchManifestsSources() | ||
} | ||
}, | ||
settings_open(val) { | ||
this.$emit('input', val) | ||
if (!val) { | ||
this.$emit('refresh') | ||
} | ||
}, | ||
}, | ||
methods: { | ||
order(index: number): string { | ||
// Based over: https://stackoverflow.com/a/39466341 | ||
return `${index}${['st', 'nd', 'rd'][((index + 90) % 100 - 10) % 10 - 1] || 'th'}` | ||
}, | ||
copyURLToClipboard() { | ||
navigator.clipboard.writeText(this.source_url) | ||
this.source_url_copy_icon = 'mdi-check' | ||
setTimeout(() => { | ||
this.source_url_copy_icon = 'mdi-content-copy' | ||
}, 500) | ||
}, | ||
blockNameAutoComplete() { | ||
this.allow_deduct_name = false | ||
}, | ||
deductNameFromURL() { | ||
if (!(this.allow_deduct_name && URL.canParse(this.source_url))) { | ||
return | ||
} | ||
const cut_path = new URL(this.source_url).pathname.slice(1) | ||
if (cut_path === '') { | ||
return | ||
} | ||
const parts = cut_path.split('/').filter((part) => !part.endsWith('.json') && !part.endsWith('.txt')) | ||
this.source_name = `${parts[0] ?? ''} ${parts[1] ?? ''}`.trim() | ||
}, | ||
prepareRequest() { | ||
this.operation_loading = true | ||
this.operation_error = undefined | ||
}, | ||
setDefaultModal() { | ||
this.source_name = '' | ||
this.source_url = '' | ||
this.source_enabled = true | ||
this.editing_source = undefined | ||
this.allow_deduct_name = true | ||
}, | ||
setEditingModal(manifest: Manifest) { | ||
this.source_name = manifest.name | ||
this.source_url = manifest.url | ||
this.source_enabled = manifest.enabled | ||
this.editing_source = manifest | ||
this.allow_deduct_name = false | ||
this.operation_open = true | ||
}, | ||
setCreateModal() { | ||
this.setDefaultModal() | ||
this.operation_open = true | ||
}, | ||
async applySubModalAction() { | ||
// Close the sub modal as soon as applied since errors and loading are performed on main modal | ||
this.operation_open = false | ||
if (this.is_editing) { | ||
if (this.is_factory) { | ||
await this.setSourceEnable(this.editing_source!.identifier, this.source_enabled) | ||
} else { | ||
await this.updateSource( | ||
this.editing_source!.identifier, | ||
this.source_name, | ||
this.source_url, | ||
this.source_enabled, | ||
) | ||
} | ||
} else { | ||
await this.addSource(this.source_name, this.source_url, this.source_enabled) | ||
} | ||
}, | ||
async fetchManifestsSources() { | ||
this.prepareRequest() | ||
try { | ||
const sources = await back_axios({ | ||
method: 'get', | ||
url: `${KRAKEN_API_V2_URL}/manifest/?data=false`, | ||
timeout: 20000, | ||
}) | ||
this.manifests = sources.data as Manifest[] | ||
} catch (error) { | ||
this.operation_error = `Unable to fetch manifest sources: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
async addSource(name: string, url: string, enabled: boolean) { | ||
this.prepareRequest() | ||
try { | ||
const source = { name, url, enabled } as ManifestSource | ||
await back_axios({ | ||
method: 'post', | ||
url: `${KRAKEN_API_V2_URL}/manifest/`, | ||
data: source, | ||
timeout: 20000, | ||
}) | ||
this.fetchManifestsSources() | ||
} catch (error) { | ||
this.operation_error = `Unable to add manifest source: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
async removeSource(identifier: string) { | ||
this.prepareRequest() | ||
try { | ||
await back_axios({ | ||
method: 'delete', | ||
url: `${KRAKEN_API_V2_URL}/manifest/${identifier}`, | ||
timeout: 15000, | ||
}) | ||
this.fetchManifestsSources() | ||
} catch (error) { | ||
this.operation_error = `Unable to remove manifest source: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
async updateSource(identifier: string, name: string, url: string, enabled: boolean) { | ||
this.prepareRequest() | ||
try { | ||
const source = { name, url, enabled } as ManifestSource | ||
await back_axios({ | ||
method: 'put', | ||
url: `${KRAKEN_API_V2_URL}/manifest/${identifier}/details`, | ||
data: source, | ||
timeout: 20000, | ||
}) | ||
this.fetchManifestsSources() | ||
} catch (error) { | ||
this.operation_error = `Unable to update manifest source: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
async reorderSources() { | ||
this.prepareRequest() | ||
const ids = this.manifests.map((manifest) => manifest.identifier) | ||
try { | ||
await back_axios({ | ||
method: 'put', | ||
url: `${KRAKEN_API_V2_URL}/manifest/orders`, | ||
data: ids, | ||
timeout: 20000, | ||
}) | ||
// After some successful apply we should close the main modal to keep consistency | ||
this.settings_open = false | ||
} catch (error) { | ||
this.operation_error = `Unable to update order: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
async setSourceEnable(identifier: string, enable: boolean) { | ||
this.prepareRequest() | ||
try { | ||
const postfix = enable ? 'enable' : 'disable' | ||
await back_axios({ | ||
method: 'post', | ||
url: `${KRAKEN_API_V2_URL}/manifest/${identifier}/${postfix}`, | ||
timeout: 10000, | ||
}) | ||
this.fetchManifestsSources() | ||
} catch (error) { | ||
this.operation_error = `Unable to ${enable ? 'enable' : 'disable'} source: ${error}` | ||
} finally { | ||
this.operation_loading = false | ||
} | ||
}, | ||
}, | ||
}) | ||
</script> |
Oops, something went wrong.