feat(admin): migrate theme + api + utilities to vue 3 composition

parent 368493c3
......@@ -12,26 +12,26 @@
},
"dependencies": {
"@apollo/client": "3.6.8",
"@codemirror/autocomplete": "0.20.3",
"@codemirror/autocomplete": "6.0.2",
"@codemirror/basic-setup": "0.20.0",
"@codemirror/closebrackets": "0.19.2",
"@codemirror/commands": "0.20.0",
"@codemirror/commands": "6.0.0",
"@codemirror/comment": "0.19.1",
"@codemirror/fold": "0.19.4",
"@codemirror/gutter": "0.19.9",
"@codemirror/highlight": "0.19.8",
"@codemirror/history": "0.19.2",
"@codemirror/lang-css": "0.20.0",
"@codemirror/lang-html": "0.20.0",
"@codemirror/lang-javascript": "0.20.1",
"@codemirror/lang-json": "0.20.0",
"@codemirror/lang-markdown": "0.20.1",
"@codemirror/lang-css": "6.0.0",
"@codemirror/lang-html": "6.0.0",
"@codemirror/lang-javascript": "6.0.0",
"@codemirror/lang-json": "6.0.0",
"@codemirror/lang-markdown": "6.0.0",
"@codemirror/matchbrackets": "0.19.4",
"@codemirror/search": "0.20.1",
"@codemirror/state": "0.20.1",
"@codemirror/search": "6.0.0",
"@codemirror/state": "6.0.1",
"@codemirror/tooltip": "0.19.16",
"@codemirror/view": "0.20.7",
"@lezer/common": "0.16.1",
"@codemirror/view": "6.0.1",
"@lezer/common": "1.0.0",
"@quasar/extras": "1.14.0",
"@tiptap/core": "2.0.0-beta.176",
"@tiptap/extension-code-block": "2.0.0-beta.37",
......@@ -61,6 +61,7 @@
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.29.6",
"clipboard": "2.0.11",
"codemirror": "6.0.0",
"filesize": "9.0.9",
"filesize-parser": "1.5.0",
"graphql": "16.5.0",
......@@ -71,12 +72,12 @@
"luxon": "2.4.0",
"pinia": "2.0.14",
"pug": "3.0.2",
"quasar": "2.7.2",
"quasar": "2.7.3",
"tippy.js": "6.3.7",
"uuid": "8.3.2",
"v-network-graph": "0.5.19",
"vue": "3.2.37",
"vue-codemirror": "5.1.0",
"vue-codemirror": "6.0.0",
"vue-i18n": "9.1.10",
"vue-router": "4.0.16",
"vuedraggable": "4.1.0",
......
......@@ -178,7 +178,6 @@ defineEmits([
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
defineExpose({ $q })
// I18N
......
......@@ -510,7 +510,6 @@ import UtilCodeEditor from './UtilCodeEditor.vue'
// QUASAR
const $q = useQuasar()
defineExpose({ $q })
// STORES
......
......@@ -12,7 +12,6 @@ import { EditorState } from '@codemirror/state'
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'
import { ref, shallowRef, onBeforeMount, onMounted, watch } from 'vue'
import { json } from '@codemirror/lang-json'
// PROPS
......
......@@ -218,7 +218,6 @@ defineEmits([
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
defineExpose({ $q })
// I18N
......
......@@ -203,7 +203,6 @@ const overlays = {
// QUASAR
const $q = useQuasar()
defineExpose({ $q })
// STORES
......
......@@ -4,16 +4,16 @@ q-page.admin-api
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-rest-api.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.api.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.api.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.api.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.api.subtitle') }}
.col
.flex.items-center
template(v-if='enabled')
template(v-if='state.enabled')
q-spinner-rings.q-mr-sm(color='green')
.text-caption.text-green {{$t('admin.api.enabled')}}
.text-caption.text-green {{t('admin.api.enabled')}}
template(v-else)
q-spinner-rings.q-mr-sm(color='red', size='md')
.text-caption.text-red {{$t('admin.api.disabled')}}
.text-caption.text-red {{t('admin.api.disabled')}}
.col-auto
q-btn.q-mr-sm.q-ml-md.acrylic-btn(
icon='las la-question-circle'
......@@ -27,24 +27,24 @@ q-page.admin-api
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='state.loading > 0'
@click='load'
)
q-btn.q-mr-sm(
unelevated
icon='las la-power-off'
:label='!enabled ? $t(`admin.api.enableButton`) : $t(`admin.api.disableButton`)'
:color='!enabled ? `positive` : `negative`'
:label='!state.enabled ? t(`admin.api.enableButton`) : t(`admin.api.disableButton`)'
:color='!state.enabled ? `positive` : `negative`'
@click='globalSwitch'
:disabled='loading > 0'
:disabled='state.loading > 0'
)
q-btn(
unelevated
icon='las la-plus'
:label='$t(`admin.api.newKeyButton`)'
:label='t(`admin.api.newKeyButton`)'
color='primary'
@click='newKey'
:disabled='loading > 0'
:disabled='state.loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
......@@ -114,165 +114,166 @@ q-page.admin-api
//- v-btn(color='red', dark, @click='revokeConfirm', :loading='revokeLoading') {{$t('admin.api.revoke')}}
</template>
<script>
import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep'
<script setup>
import gql from 'graphql-tag'
import { createMetaMixin } from 'quasar'
// import { StatusIndicator } from 'vue-status-indicator'
import cloneDeep from 'lodash/cloneDeep'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, watch } from 'vue'
// QUASAR
const $q = useQuasar()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.api.title')
})
// DATA
// import CreateApiKey from './admin-api-create.vue'
const state = reactive({
enabled: false,
loading: 0,
keys: [],
isCreateDialogShown: false,
isRevokeConfirmDialogShown: false,
revokeLoading: false,
current: {}
})
export default {
components: {
// StatusIndicator,
// CreateApiKey
},
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.api.title')
// METHODS
async function load () {
state.loading++
$q.loading.show()
const resp = await APOLLO_CLIENT.query({
query: gql`
query getHooks {
apiKeys {
id
name
keyShort
expiration
isRevoked
createdAt
updatedAt
}
apiState
}
})
],
data () {
return {
enabled: false,
loading: 0,
keys: [],
isCreateDialogShown: false,
isRevokeConfirmDialogShown: false,
revokeLoading: false,
current: {}
}
},
mounted () {
this.load()
},
methods: {
async load () {
this.loading++
this.$q.loading.show()
const resp = await this.$apollo.query({
query: gql`
query getHooks {
apiKeys {
id
name
keyShort
expiration
isRevoked
createdAt
updatedAt
}
apiState
}
`,
fetchPolicy: 'network-only'
})
this.keys = cloneDeep(resp?.data?.apiKeys) ?? []
this.enabled = resp?.data?.apiState === true
this.$q.loading.hide()
this.loading--
},
async globalSwitch () {
this.isToggleLoading = true
try {
const resp = await this.$apollo.mutate({
mutation: gql`
mutation ($enabled: Boolean!) {
authentication {
setApiState (enabled: $enabled) {
responseResult {
succeeded
errorCode
slug
message
}
}
`,
fetchPolicy: 'network-only'
})
state.keys = cloneDeep(resp?.data?.apiKeys) ?? []
state.enabled = resp?.data?.apiState === true
$q.loading.hide()
state.loading--
}
async function globalSwitch () {
state.isToggleLoading = true
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation ($enabled: Boolean!) {
authentication {
setApiState (enabled: $enabled) {
responseResult {
succeeded
errorCode
slug
message
}
}
`,
variables: {
enabled: !this.enabled
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-api-toggle')
}
})
if (_get(resp, 'data.authentication.setApiState.responseResult.succeeded', false)) {
this.$store.commit('showNotification', {
style: 'success',
message: this.enabled ? this.$t('admin.api.toggleStateDisabledSuccess') : this.$t('admin.api.toggleStateEnabledSuccess'),
icon: 'check'
})
await this.load()
} else {
this.$store.commit('showNotification', {
style: 'red',
message: _get(resp, 'data.authentication.setApiState.responseResult.message', 'An unexpected error occurred.'),
icon: 'alert'
})
}
} catch (err) {
this.$store.commit('pushGraphError', err)
`,
variables: {
enabled: !state.enabled
}
this.isToggleLoading = false
},
async newKey () {
this.isCreateDialogShown = true
},
revoke (key) {
this.current = key
this.isRevokeConfirmDialogShown = true
},
async revokeConfirm () {
this.revokeLoading = true
try {
const resp = await this.$apollo.mutate({
mutation: gql`
mutation ($id: Int!) {
authentication {
revokeApiKey (id: $id) {
responseResult {
succeeded
errorCode
slug
message
}
}
})
if (resp?.data?.setApiState?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: state.enabled ? t('admin.api.toggleStateDisabledSuccess') : t('admin.api.toggleStateEnabledSuccess')
})
await load()
} else {
throw new Error(resp?.data?.setApiState?.operation.message || 'An unexpected error occurred.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to switch API state.',
caption: err.message
})
}
state.isToggleLoading = false
}
async function newKey () {
state.isCreateDialogShown = true
}
function revoke (key) {
state.current = key
state.isRevokeConfirmDialogShown = true
}
async function revokeConfirm () {
state.revokeLoading = true
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation ($id: Int!) {
authentication {
revokeApiKey (id: $id) {
responseResult {
succeeded
errorCode
slug
message
}
}
`,
variables: {
id: this.current.id
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-api-revoke')
}
})
if (_get(resp, 'data.authentication.revokeApiKey.responseResult.succeeded', false)) {
this.$store.commit('showNotification', {
style: 'success',
message: this.$t('admin.api.revokeSuccess'),
icon: 'check'
})
this.load()
} else {
this.$store.commit('showNotification', {
style: 'red',
message: _get(resp, 'data.authentication.revokeApiKey.responseResult.message', 'An unexpected error occurred.'),
icon: 'alert'
})
}
} catch (err) {
this.$store.commit('pushGraphError', err)
`,
variables: {
id: state.current.id
}
this.isRevokeConfirmDialogShown = false
this.revokeLoading = false
}
})
// if (_get(resp, 'data.authentication.revokeApiKey.responseResult.succeeded', false)) {
// this.$store.commit('showNotification', {
// style: 'success',
// message: this.$t('admin.api.revokeSuccess'),
// icon: 'check'
// })
// this.load()
// } else {
// this.$store.commit('showNotification', {
// style: 'red',
// message: _get(resp, 'data.authentication.revokeApiKey.responseResult.message', 'An unexpected error occurred.'),
// icon: 'alert'
// })
// }
} catch (err) {
// this.$store.commit('pushGraphError', err)
}
state.isRevokeConfirmDialogShown = false
state.revokeLoading = false
}
// MOUNTED
onMounted(() => {
load()
})
</script>
<style lang='scss'>
......
......@@ -4,8 +4,8 @@ q-page.admin-theme
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-paint-roller.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.theme.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.theme.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.theme.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.theme.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
......@@ -19,16 +19,16 @@ q-page.admin-theme
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='state.loading > 0'
@click='load'
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:loading='loading > 0'
:loading='state.loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
......@@ -38,11 +38,11 @@ q-page.admin-theme
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section.flex.items-center
.text-subtitle1 {{$t('admin.theme.options')}}
.text-subtitle1 {{t('admin.theme.options')}}
q-space
q-btn.acrylic-btn(
icon='las la-redo-alt'
:label='$t(`admin.theme.resetDefaults`)'
:label='t(`admin.theme.resetDefaults`)'
flat
size='sm'
color='pink'
......@@ -51,25 +51,25 @@ q-page.admin-theme
q-item(tag='label', v-ripple)
blueprint-icon(icon='light-on')
q-item-section
q-item-label {{$t(`admin.theme.darkMode`)}}
q-item-label(caption) {{$t(`admin.theme.darkModeHint`)}}
q-item-label {{t(`admin.theme.darkMode`)}}
q-item-label(caption) {{t(`admin.theme.darkModeHint`)}}
q-item-section(avatar)
q-toggle(
v-model='config.dark'
v-model='state.config.dark'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.theme.darkMode`)'
:aria-label='t(`admin.theme.darkMode`)'
)
template(v-for='(cl, idx) of colorKeys', :key='cl')
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='fill-color')
q-item-section
q-item-label {{$t(`admin.theme.` + cl + `Color`)}}
q-item-label(caption) {{$t(`admin.theme.` + cl + `ColorHint`)}}
q-item-label {{t(`admin.theme.` + cl + `Color`)}}
q-item-label(caption) {{t(`admin.theme.` + cl + `ColorHint`)}}
q-item-section(side)
.text-caption.text-grey-6 {{config[`color` + startCase(cl)]}}
.text-caption.text-grey-6 {{state.config[`color` + startCase(cl)]}}
q-item-section(side)
q-btn.q-mr-sm(
:key='`btnpick-` + cl'
......@@ -77,14 +77,14 @@ q-page.admin-theme
padding='xs md'
no-caps
size='sm'
:style='`background-color: ` + config[`color` + startCase(cl)] + `;`'
:style='`background-color: ` + state.config[`color` + startCase(cl)] + `;`'
text-color='white'
)
q-icon(name='las la-fill', size='xs', left)
span Pick...
q-menu
q-color(
v-model='config[`color` + startCase(cl)]'
v-model='state.config[`color` + startCase(cl)]'
)
//- -----------------------
......@@ -92,69 +92,63 @@ q-page.admin-theme
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{$t('admin.theme.layout')}}
.text-subtitle1 {{t('admin.theme.layout')}}
q-item
blueprint-icon(icon='right-navigation-toolbar')
q-item-section
q-item-label {{$t(`admin.theme.sidebarPosition`)}}
q-item-label(caption) {{$t(`admin.theme.sidebarPositionHint`)}}
q-item-label {{t(`admin.theme.sidebarPosition`)}}
q-item-label(caption) {{t(`admin.theme.sidebarPositionHint`)}}
q-item-section.col-auto
q-btn-toggle(
v-model='config.sidebarPosition'
v-model='state.config.sidebarPosition'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: 'Left', value: 'left' },
{ label: 'Right', value: 'right' }
]`
:options='rightLeftOptions'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='index')
q-item-section
q-item-label {{$t(`admin.theme.tocPosition`)}}
q-item-label(caption) {{$t(`admin.theme.tocPositionHint`)}}
q-item-label {{t(`admin.theme.tocPosition`)}}
q-item-label(caption) {{t(`admin.theme.tocPositionHint`)}}
q-item-section.col-auto
q-btn-toggle(
v-model='config.tocPosition'
v-model='state.config.tocPosition'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: 'Left', value: 'left' },
{ label: 'Right', value: 'right' }
]`
:options='rightLeftOptions'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='share')
q-item-section
q-item-label {{$t(`admin.theme.showSharingMenu`)}}
q-item-label(caption) {{$t(`admin.theme.showSharingMenuHint`)}}
q-item-label {{t(`admin.theme.showSharingMenu`)}}
q-item-label(caption) {{t(`admin.theme.showSharingMenuHint`)}}
q-item-section(avatar)
q-toggle(
v-model='config.showSharingMenu'
v-model='state.config.showSharingMenu'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.theme.showSharingMenu`)'
:aria-label='t(`admin.theme.showSharingMenu`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='print')
q-item-section
q-item-label {{$t(`admin.theme.showPrintBtn`)}}
q-item-label(caption) {{$t(`admin.theme.showPrintBtnHint`)}}
q-item-label {{t(`admin.theme.showPrintBtn`)}}
q-item-label(caption) {{t(`admin.theme.showPrintBtnHint`)}}
q-item-section(avatar)
q-toggle(
v-model='config.showPrintBtn'
v-model='state.config.showPrintBtn'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.theme.showPrintBtn`)'
:aria-label='t(`admin.theme.showPrintBtn`)'
)
.col-6
......@@ -163,234 +157,257 @@ q-page.admin-theme
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{$t('admin.theme.codeInjection')}}
.text-subtitle1 {{t('admin.theme.codeInjection')}}
q-item
blueprint-icon(icon='css')
q-item-section
q-item-label {{$t(`admin.theme.cssOverride`)}}
q-item-label(caption) {{$t(`admin.theme.cssOverrideHint`)}}
q-item-label {{t(`admin.theme.cssOverride`)}}
q-item-label(caption) {{t(`admin.theme.cssOverrideHint`)}}
q-item
q-item-section
q-no-ssr(:placeholder='$t(`common.loading`)')
q-no-ssr(:placeholder='t(`common.loading`)')
util-code-editor.admin-theme-cm(
ref='cmCSS'
v-model='config.injectCSS'
v-model='state.config.injectCSS'
language='css'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='html')
q-item-section
q-item-label {{$t(`admin.theme.headHtmlInjection`)}}
q-item-label(caption) {{$t(`admin.theme.headHtmlInjectionHint`)}}
q-item-label {{t(`admin.theme.headHtmlInjection`)}}
q-item-label(caption) {{t(`admin.theme.headHtmlInjectionHint`)}}
q-item
q-item-section
q-no-ssr(:placeholder='$t(`common.loading`)')
q-no-ssr(:placeholder='t(`common.loading`)')
util-code-editor.admin-theme-cm(
ref='cmHead'
v-model='config.injectHead'
v-model='state.config.injectHead'
language='html'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='html')
q-item-section
q-item-label {{$t(`admin.theme.bodyHtmlInjection`)}}
q-item-label(caption) {{$t(`admin.theme.bodyHtmlInjectionHint`)}}
q-item-label {{t(`admin.theme.bodyHtmlInjection`)}}
q-item-label(caption) {{t(`admin.theme.bodyHtmlInjectionHint`)}}
q-item
q-item-section
q-no-ssr(:placeholder='$t(`common.loading`)')
q-no-ssr(:placeholder='t(`common.loading`)')
util-code-editor.admin-theme-cm(
ref='cmBody'
v-model='config.injectBody'
v-model='state.config.injectBody'
language='html'
)
</template>
<script>
<script setup>
import gql from 'graphql-tag'
import { get } from 'vuex-pathify'
import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep'
import startCase from 'lodash/startCase'
import { createMetaMixin, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
import UtilCodeEditor from '../components/UtilCodeEditor.vue'
export default {
components: {
UtilCodeEditor
},
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.theme.title')
}
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.theme.title')
})
// DATA
const state = reactive({
loading: 0,
config: {
dark: false,
injectCSS: '',
injectHead: '',
injectBody: '',
colorPrimary: '#1976D2',
colorSecondary: '#02C39A',
colorAccent: '#f03a47',
colorHeader: '#000',
colorSidebar: '#1976D2',
sidebarPosition: 'left',
tocPosition: 'right',
showSharingMenu: true,
showPrintBtn: true
}
})
const colorKeys = [
'primary',
'secondary',
'accent',
'header',
'sidebar'
]
const rightLeftOptions = [
{ label: 'Left', value: 'left' },
{ label: 'Right', value: 'right' }
]
// WATCHERS
watch(() => adminStore.currentSiteId, (newValue) => {
load()
})
// METHODS
function resetColors () {
state.config.dark = false
state.config.colorPrimary = '#1976D2'
state.config.colorSecondary = '#02C39A'
state.config.colorAccent = '#f03a47'
state.config.colorHeader = '#000'
state.config.colorSidebar = '#1976D2'
}
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query fetchThemeConfig (
$id: UUID!
) {
siteById(
id: $id
) {
theme {
dark
colorPrimary
colorSecondary
colorAccent
colorHeader
colorSidebar
injectCSS
injectHead
injectBody
sidebarPosition
tocPosition
showSharingMenu
showPrintBtn
}
}
}
`,
variables: {
id: adminStore.currentSiteId
},
fetchPolicy: 'network-only'
})
],
data () {
return {
qsr: useQuasar(),
loading: 0,
colorKeys: [
'primary',
'secondary',
'accent',
'header',
'sidebar'
],
config: {
dark: false,
injectCSS: '',
injectHead: '',
injectBody: '',
colorPrimary: '#1976D2',
colorSecondary: '#02C39A',
colorAccent: '#f03a47',
colorHeader: '#000',
colorSidebar: '#1976D2',
sidebarPosition: 'left',
tocPosition: 'right',
showSharingMenu: true,
showPrintBtn: true
}
}
},
computed: {
currentSiteId: get('admin/currentSiteId', false)
},
watch: {
currentSiteId () {
this.load()
if (!resp?.data?.siteById?.theme) {
throw new Error('Failed to fetch theme config.')
}
},
mounted () {
this.load()
},
methods: {
startCase,
resetColors () {
this.config.dark = false
this.config.colorPrimary = '#1976D2'
this.config.colorSecondary = '#02C39A'
this.config.colorAccent = '#f03a47'
this.config.colorHeader = '#000'
this.config.colorSidebar = '#1976D2'
},
async load () {
if (!this.currentSiteId) { return }
state.config = cloneDeep(resp.data.siteById.theme)
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to fetch site theme config'
})
}
$q.loading.hide()
state.loading--
}
this.loading++
try {
const resp = await this.$apollo.query({
query: gql`
query fetchThemeConfig (
$id: UUID!
async function save () {
state.loading++
try {
const patchTheme = {
dark: state.config.dark,
colorPrimary: state.config.colorPrimary,
colorSecondary: state.config.colorSecondary,
colorAccent: state.config.colorAccent,
colorHeader: state.config.colorHeader,
colorSidebar: state.config.colorSidebar,
injectCSS: state.config.injectCSS,
injectHead: state.config.injectHead,
injectBody: state.config.injectBody,
sidebarPosition: state.config.sidebarPosition,
tocPosition: state.config.tocPosition,
showSharingMenu: state.config.showSharingMenu,
showPrintBtn: state.config.showPrintBtn
}
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveTheme (
$id: UUID!
$patch: SiteUpdateInput!
) {
updateSite (
id: $id,
patch: $patch
) {
siteById(
id: $id
) {
theme {
dark
colorPrimary
colorSecondary
colorAccent
colorHeader
colorSidebar
injectCSS
injectHead
injectBody
sidebarPosition
tocPosition
showSharingMenu
showPrintBtn
}
}
}
`,
variables: {
id: this.currentSiteId
},
fetchPolicy: 'network-only'
})
if (!resp?.data?.siteById?.theme) {
throw new Error('Failed to fetch theme config.')
}
this.config = cloneDeep(resp.data.siteById.theme)
} catch (err) {
this.$q.notify({
type: 'negative',
message: 'Failed to fetch site theme config'
})
}
this.loading--
},
async save () {
this.loading++
try {
const patchTheme = {
dark: this.config.dark,
colorPrimary: this.config.colorPrimary,
colorSecondary: this.config.colorSecondary,
colorAccent: this.config.colorAccent,
colorHeader: this.config.colorHeader,
colorSidebar: this.config.colorSidebar,
injectCSS: this.config.injectCSS,
injectHead: this.config.injectHead,
injectBody: this.config.injectBody,
sidebarPosition: this.config.sidebarPosition,
tocPosition: this.config.tocPosition,
showSharingMenu: this.config.showSharingMenu,
showPrintBtn: this.config.showPrintBtn
}
const respRaw = await this.$apollo.mutate({
mutation: gql`
mutation saveTheme (
$id: UUID!
$patch: SiteUpdateInput!
) {
updateSite (
id: $id,
patch: $patch
) {
status {
succeeded
slug
message
}
}
operation {
succeeded
slug
message
}
`,
variables: {
id: this.currentSiteId,
patch: {
theme: patchTheme
}
}
})
const resp = _get(respRaw, 'data.updateSite.status', {})
if (resp.succeeded) {
if (this.currentSiteId === this.$store.get('site/id')) {
this.$store.set('site/theme', patchTheme)
this.$q.dark.set(this.config.dark)
}
this.$q.notify({
type: 'positive',
message: this.$t('admin.theme.saveSuccess')
})
} else {
throw new Error(resp.message)
}
} catch (err) {
this.$q.notify({
type: 'negative',
message: 'Failed to save site theme config',
caption: err.message
`,
variables: {
id: adminStore.currentSiteId,
patch: {
theme: patchTheme
}
}
})
if (respRaw?.data?.updateSite?.operation?.succeeded) {
if (adminStore.currentSiteId === siteStore.id) {
siteStore.$patch({
theme: patchTheme
})
$q.dark.set(state.config.dark)
}
this.loading--
$q.notify({
type: 'positive',
message: t('admin.theme.saveSuccess')
})
} else {
throw new Error(respRaw?.data?.updateSite?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to save site theme config',
caption: err.message
})
}
state.loading--
}
// MOUNTED
onMounted(() => {
if (adminStore.currentSiteId) {
load()
}
})
</script>
<style lang='scss'>
......
......@@ -4,14 +4,14 @@ q-page.admin-utilities
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-swiss-army-knife.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.utilities.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.utilities.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.utilities.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.utilities.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
flat
color='grey'
href='https://docs.requarks.io/admin/utilities'
href='https://docs.js.wiki/admin/utilities'
target='_blank'
type='a'
)
......@@ -22,38 +22,31 @@ q-page.admin-utilities
q-item
blueprint-icon(icon='matches', :hue-rotate='45')
q-item-section
q-item-label {{$t(`admin.utilities.invalidAuthCertificates`)}}
q-item-label(caption) {{$t(`admin.utilities.invalidAuthCertificatesHint`)}}
q-item-label {{t(`admin.utilities.invalidAuthCertificates`)}}
q-item-label(caption) {{t(`admin.utilities.invalidAuthCertificatesHint`)}}
q-item-section(side)
q-btn.acrylic-btn(
flat
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='$t(`common.actions.proceed`)'
:label='t(`common.actions.proceed`)'
)
q-item
blueprint-icon(icon='historical', :hue-rotate='45')
q-item-section
q-item-label {{$t(`admin.utilities.purgeHistory`)}}
q-item-label(caption) {{$t(`admin.utilities.purgeHistoryHint`)}}
q-item-label {{t(`admin.utilities.purgeHistory`)}}
q-item-label(caption) {{t(`admin.utilities.purgeHistoryHint`)}}
q-item-section(side)
q-select(
outlined
:label='$t(`admin.utilities.purgeHistoryTimeframe`)'
v-model='purgeHistoryTimeframe'
:label='t(`admin.utilities.purgeHistoryTimeframe`)'
v-model='state.purgeHistoryTimeframe'
style='min-width: 175px;'
emit-value
map-options
dense
:options=`[
{ value: '24h', label: $t('admin.utitilies.purgeHistoryToday') },
{ value: '1m', label: $tc('admin.utitilies.purgeHistoryMonth', 1, { count: 1 }) },
{ value: '3m', label: $tc('admin.utitilies.purgeHistoryMonth', 3, { count: 3 }) },
{ value: '6m', label: $tc('admin.utitilies.purgeHistoryMonth', 6, { count: 6 }) },
{ value: '1y', label: $tc('admin.utitilies.purgeHistoryYear', 1, { count: 1 }) },
{ value: '2y', label: $tc('admin.utitilies.purgeHistoryYear', 2, { count: 2 }) }
]`
:options='purgeHistoryTimeframes'
)
q-separator.q-ml-sm(vertical)
q-item-section(side)
......@@ -62,20 +55,46 @@ q-page.admin-utilities
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='$t(`common.actions.proceed`)'
:label='t(`common.actions.proceed`)'
)
</template>
<script>
<script setup>
import { computed, reactive } from 'vue'
import { useMeta, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
export default {
data () {
return {
purgeHistoryTimeframe: '1y'
}
}
}
// QUASAR
const $q = useQuasar()
// STORES / ROUTERS / i18n
const { t } = useI18n()
// META
useMeta({
title: t('admin.utilities.title')
})
// DATA
const state = reactive({
purgeHistoryTimeframe: '1y'
})
// COMPUTED
const purgeHistoryTimeframes = computed(() => ([
{ value: '24h', label: t('admin.utitilies.purgeHistoryToday') },
{ value: '1m', label: t('admin.utitilies.purgeHistoryMonth', 1, { count: 1 }) },
{ value: '3m', label: t('admin.utitilies.purgeHistoryMonth', 3, { count: 3 }) },
{ value: '6m', label: t('admin.utitilies.purgeHistoryMonth', 6, { count: 6 }) },
{ value: '1y', label: t('admin.utitilies.purgeHistoryYear', 1, { count: 1 }) },
{ value: '2y', label: t('admin.utitilies.purgeHistoryYear', 2, { count: 2 }) }
]))
</script>
<style lang='scss'>
......
......@@ -36,20 +36,20 @@ const routes = [
{ path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
{ path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
// { path: ':siteid/navigation', component: () => import('../pages/AdminNavigation.vue') },
{ path: ':siteid/storage/:id?', component: () => import('../pages/AdminStorage.vue') },
// { path: ':siteid/rendering', component: () => import('../pages/AdminRendering.vue') },
// { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
{ path: ':siteid/storage/:id?', component: () => import('../pages/AdminStorage.vue') },
{ path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
// -> Users
// { path: 'auth', component: () => import('../pages/AdminAuth.vue') },
{ path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') },
{ path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') },
// -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') },
{ path: 'api', component: () => import('../pages/AdminApi.vue') },
{ path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
{ path: 'mail', component: () => import('../pages/AdminMail.vue') },
{ path: 'security', component: () => import('../pages/AdminSecurity.vue') },
{ path: 'system', component: () => import('../pages/AdminSystem.vue') },
// { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
{ path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
{ path: 'webhooks', component: () => import('../pages/AdminWebhooks.vue') },
{ path: 'flags', component: () => import('../pages/AdminFlags.vue') }
]
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment