Skip to content

Commit

Permalink
Add support for injecting custom code-snippets in webapps
Browse files Browse the repository at this point in the history
  • Loading branch information
KinyaElGrande committed Aug 23, 2024
1 parent f5e6c45 commit 2e8b0e9
Show file tree
Hide file tree
Showing 25 changed files with 489 additions and 3 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/assets/client/vue.config-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ module.exports = ({ appFlavour, appLabel, version = process.env.BUILD_VERSION, t
hot: false,

proxy: {
'^/custom.css': {
'^/custom.css': {
target: fetchBaseUrl(),
},

'^/code-snippets.js': {
target: fetchBaseUrl(),
},
},
Expand Down
3 changes: 3 additions & 0 deletions client/web/admin/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="<%= BASE_URL %>config.js" type="text/javascript"></script>

<!-- contains cdns that will be injected at the head of the page -->
<script src="<%= BASE_URL %>code-snippets.js" type="text/javascript"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions client/web/admin/src/components/CTheMainNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export default {
icon: 'cloud',
can: ['system/', 'dal-connections.search'],
},
{
label: 'system.items.code-snippets',
route: 'system.codesnippets',
icon: 'file-code',
can: ['system/', 'settings.read'],
},
{
label: 'system.items.sensitivityLevel',
route: 'system.sensitivityLevel',
Expand Down
297 changes: 297 additions & 0 deletions client/web/admin/src/views/System/CodeSnippets/Index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
<template>
<b-container
class="pt-2 pb-3"
>
<c-content-header
:title="$t('title')"
/>
<b-card
body-class="p-0"
header-class="border-bottom"
footer-class="border-top d-flex flex-wrap flex-fill-child gap-1"
class="shadow-sm"
>
<div class="align-items-center gap-1 p-3">
<b-button
variant="primary"
size="lg"
@click="newCodeSnippet()"
>
{{ $t('code-snippets.add') }}
</b-button>
</div>

<b-table
:items="providerItems"
:fields="codeSnippetProviderFields"
head-variant="light"
show-empty
hover
class="mb-0"
style="min-height: 50px;"
>
<template #cell(provider)="{ item }">
{{ item.provider || item.tag }}
</template>

<template #empty>
<p
data-test-id="no-matches"
class="text-center text-dark"
style="margin-top: 1vh;"
>
{{ $t('code-snippets.empty') }}
</p>
</template>

<template #cell(editor)="{ item }">
<c-input-confirm
v-if="item.delete"
:icon="item.deleted ? ['fas', 'trash-restore'] : undefined"
@confirmed="item.delete()"
/>

<b-button
variant="link"
@click="openEditor(item.editor)"
>
<font-awesome-icon
:icon="['fas', 'wrench']"
/>
</b-button>
</template>
</b-table>

<b-modal
id="modal-codeSnippet"
v-model="modal.open"
:title="modal.title"
scrollable
size="lg"
title-class="text-capitalize"
@ok="modal.updater(modal.data)"
>
<b-form-group
:label="$t('code-snippets.form.provider.label')"
label-class="text-primary"
>
<b-input-group>
<b-form-input v-model="modal.data.name" />
</b-input-group>
</b-form-group>

<div>
<div class="mb-2">
<h5>
{{ $t('code-snippets.add') }}
</h5>
<span class="text-muted">
{{ $t('code-snippets.form.value.description') }}
</span>
</div>

<c-ace-editor
v-model="modal.data.script"
lang="javascript"
height="500px"
font-size="14px"
show-line-numbers
:border="false"
:show-popout="false"
/>
</div>

<template #modal-footer="{ ok, cancel}">
<c-input-confirm
size="md"
variant="danger"
@confirmed="deleteCodeSnippet(modal.index)"
>
{{ $t('general:label.delete') }}
</c-input-confirm>

<b-button
variant="light"
class="ml-auto"
@click="cancel()"
>
{{ $t('general:label.cancel') }}
</b-button>

<b-button
variant="primary"
@click="ok()"
>
{{ $t('general:label.saveAndClose') }}
</b-button>
</template>
</b-modal>

<template #footer>
<c-button-submit
:disabled="!canManage"
:processing="codeSnippet.processing"
:success="codeSnippet.success"
:text="$t('admin:general.label.submit')"
class="ml-auto"
@submit="onSubmit()"
/>
</template>
</b-card>
</b-container>
</template>

<script>
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
import { components } from '@cortezaproject/corteza-vue'
import { mapGetters } from 'vuex'
const { CAceEditor } = components
export default {
name: 'CSystemCodeSnippetEditor',
i18nOptions: {
namespaces: 'system.code-snippets',
keyPrefix: 'editor',
},
components: {
CAceEditor,
},
mixins: [
editorHelpers,
],
data () {
return {
codeSnippets: [],
modal: {
open: false,
editor: null,
title: null,
data: [],
index: null,
},
codeSnippet: {
processing: false,
success: false,
},
originalCodeSnippets: [],
}
},
computed: {
...mapGetters({
canManage: 'rbac/can',
}),
codeSnippetProviderFields () {
return [
{ key: 'provider', label: this.$t('code-snippets.table-headers.provider'), thStyle: { width: '200px' }, tdClass: 'text-capitalize' },
{ key: 'value', label: this.$t('code-snippets.table-headers.value'), tdClass: 'td-content-overflow' },
{ key: 'editor', label: '', thStyle: { width: '200px' }, tdClass: 'text-right' },
]
},
providerItems () {
return this.codeSnippets.map((s, i) => ({
provider: s.name,
value: s.script,
editor: {
data: s,
index: i,
title: s.name,
updater: (changed) => {
this.codeSnippets[i] = changed
},
},
}))
},
},
created () {
this.fetchSettings()
this.originalCodeSnippets = [...this.codeSnippets]
},
methods: {
openEditor ({ component, title, data, updater }) {
this.modal.open = true
this.modal.component = component
this.modal.title = title
this.modal.updater = updater
// deref
this.modal.data = data
},
newCodeSnippet () {
this.openEditor({
title: this.$t('code-snippets.add'),
data: {
name: '',
script: '<' + 'script> ' + '</' + 'script>',
},
updater: (changed) => {
this.codeSnippets.push(changed)
},
})
},
fetchSettings () {
this.incLoader()
this.$Settings.fetch()
return this.$SystemAPI.settingsList({ prefix: 'code-snippets' })
.then(settings => {
if (settings && settings[0]) {
this.codeSnippets = settings[0].value
} else {
this.codeSnippets = []
}
})
.catch(this.toastErrorHandler(this.$t('notification:settings.codeSnippet.fetch.error')))
.finally(() => {
this.decLoader()
})
},
settingsUpdate (action) {
this.codeSnippet.processing = true
this.$SystemAPI.settingsUpdate({ values: [{ name: 'code-snippets', value: this.codeSnippets }] })
.then(() => {
this.animateSuccess('codeSnippet')
if (action === 'delete') {
this.toastSuccess(this.$t('notification:settings.codeSnippet.delete.success'))
} else {
this.toastSuccess(this.$t('notification:settings.codeSnippet.update.success'))
}
})
.catch(this.toastErrorHandler(this.$t('notification:settings.codeSnippet.update.error')))
.finally(() => {
this.codeSnippet.processing = false
})
},
onSubmit () {
this.settingsUpdate('update')
},
deleteCodeSnippet (i) {
this.codeSnippets.splice(i, 1)
this.settingsUpdate('delete')
this.$bvModal.hide('modal-codeSnippet')
},
},
}
</script>

<style>
.td-content-overflow {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
1 change: 1 addition & 0 deletions client/web/admin/src/views/UI/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default {
processing: false,
success: false,
},
}
},
Expand Down
2 changes: 2 additions & 0 deletions client/web/admin/src/views/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export default [
r('system.connection.new', 'connection/new', 'System/Connection/Editor'),
r('system.connection.edit', 'connection/edit/:connectionID', 'System/Connection/Editor'),

r('system.codesnippets', 'codesnippets', 'System/CodeSnippets/Index'),

combo('system', 'sensitivityLevel'),

combo('system', 'queue', { pkey: 'queueID' }),
Expand Down
4 changes: 4 additions & 0 deletions client/web/admin/vue.config-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ module.exports = ({ appFlavour, appLabel, version = process.env.BUILD_VERSION, t
'^/custom.css': {
target: fetchBaseUrl(),
},

'^/code-snippets.js': {
target: fetchBaseUrl(),
},
},

watchOptions: {
Expand Down
3 changes: 3 additions & 0 deletions client/web/compose/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="<%= BASE_URL %>config.js" type="text/javascript"></script>

<!-- contains cdns that will be injected at the head of the page -->
<script src="<%= BASE_URL %>code-snippets.js" type="text/javascript"></script>
</body>
</html>
4 changes: 4 additions & 0 deletions client/web/compose/vue.config-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ module.exports = ({ appFlavour, appLabel, version = process.env.BUILD_VERSION, t
'^/custom.css': {
target: fetchBaseUrl(),
},

'^/code-snippets.js': {
target: fetchBaseUrl(),
},
},

watchOptions: {
Expand Down
3 changes: 3 additions & 0 deletions client/web/discovery/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="<%= BASE_URL %>config.js" type="text/javascript"></script>

<!-- contains cdns that will be injected at the head of the page -->
<script src="<%= BASE_URL %>code-snippets.js" type="text/javascript"></script>
</body>
</html>
Loading

0 comments on commit 2e8b0e9

Please sign in to comment.