123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- <template lang='pug'>
- q-page.admin-theme
- .row.q-pa-md.items-center
- .col-auto
- img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-paint-roller-animated.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') }}
- .col-auto
- q-btn.q-mr-sm.acrylic-btn(
- icon='las la-question-circle'
- flat
- color='grey'
- type='a'
- :href='siteStore.docsBase + `/admin/theme`'
- target='_blank'
- )
- q-btn.q-mr-sm.acrylic-btn(
- icon='las la-redo-alt'
- flat
- color='secondary'
- :loading='state.loading > 0'
- @click='load'
- )
- q-btn(
- unelevated
- icon='mdi-check'
- :label='t(`common.actions.apply`)'
- color='secondary'
- @click='save'
- :loading='state.loading > 0'
- )
- q-separator(inset)
- .row.q-pa-md.q-col-gutter-md
- .col-6
- //- -----------------------
- //- Theme Options
- //- -----------------------
- q-card.shadow-1.q-pb-sm
- q-card-section.flex.items-center
- .text-subtitle1 {{t('admin.theme.appearance')}}
- q-space
- q-btn.acrylic-btn(
- icon='las la-redo-alt'
- :label='t(`admin.theme.resetDefaults`)'
- flat
- size='sm'
- color='pink'
- @click='resetColors'
- )
- q-item(tag='label')
- 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-section(avatar)
- q-toggle(
- v-model='state.config.dark'
- color='primary'
- checked-icon='las la-check'
- unchecked-icon='las la-times'
- :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-section(side)
- .text-caption.text-grey-6 {{state.config[`color` + startCase(cl)]}}
- q-item-section(side)
- q-btn.q-mr-sm(
- :key='`btnpick-` + cl'
- glossy
- padding='xs md'
- no-caps
- size='sm'
- :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='state.config[`color` + startCase(cl)]'
- )
- //- -----------------------
- //- Theme Layout
- //- -----------------------
- q-card.shadow-1.q-pb-sm.q-mt-md
- q-card-section
- .text-subtitle1 {{t('admin.theme.layout')}}
- q-item
- blueprint-icon(icon='width')
- q-item-section
- q-item-label {{t(`admin.theme.contentWidth`)}}
- q-item-label(caption) {{t(`admin.theme.contentWidthHint`)}}
- q-item-section.col-auto
- q-btn-toggle(
- v-model='state.config.contentWidth'
- push
- glossy
- no-caps
- toggle-color='primary'
- :options='widthOptions'
- )
- q-separator.q-my-sm(inset)
- 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-section.col-auto
- q-btn-toggle(
- v-model='state.config.sidebarPosition'
- push
- glossy
- no-caps
- toggle-color='primary'
- :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-section.col-auto
- q-btn-toggle(
- v-model='state.config.tocPosition'
- push
- glossy
- no-caps
- toggle-color='primary'
- :options='rightLeftOptions'
- )
- q-separator.q-my-sm(inset)
- q-item(tag='label')
- blueprint-icon(icon='share')
- q-item-section
- q-item-label {{t(`admin.theme.showSharingMenu`)}}
- q-item-label(caption) {{t(`admin.theme.showSharingMenuHint`)}}
- q-item-section(avatar)
- q-toggle(
- v-model='state.config.showSharingMenu'
- color='primary'
- checked-icon='las la-check'
- unchecked-icon='las la-times'
- :aria-label='t(`admin.theme.showSharingMenu`)'
- )
- q-separator.q-my-sm(inset)
- q-item(tag='label')
- blueprint-icon(icon='print')
- q-item-section
- q-item-label {{t(`admin.theme.showPrintBtn`)}}
- q-item-label(caption) {{t(`admin.theme.showPrintBtnHint`)}}
- q-item-section(avatar)
- q-toggle(
- v-model='state.config.showPrintBtn'
- color='primary'
- checked-icon='las la-check'
- unchecked-icon='las la-times'
- :aria-label='t(`admin.theme.showPrintBtn`)'
- )
- .col-6
- //- -----------------------
- //- Fonts
- //- -----------------------
- q-card.shadow-1.q-pb-sm
- q-card-section.flex.items-center
- .text-subtitle1 {{t('admin.theme.fonts')}}
- q-space
- q-btn.acrylic-btn(
- icon='las la-redo-alt'
- :label='t(`admin.theme.resetDefaults`)'
- flat
- size='sm'
- color='pink'
- @click='resetFonts'
- )
- q-item
- blueprint-icon(icon='fonts-app')
- q-item-section
- q-item-label {{t(`admin.theme.baseFont`)}}
- q-item-label(caption) {{t(`admin.theme.baseFontHint`)}}
- q-item-section
- q-select(
- outlined
- v-model='state.config.baseFont'
- :options='fonts'
- emit-value
- map-options
- dense
- :aria-label='t(`admin.theme.baseFont`)'
- )
- q-item
- blueprint-icon(icon='fonts-app')
- q-item-section
- q-item-label {{t(`admin.theme.contentFont`)}}
- q-item-label(caption) {{t(`admin.theme.contentFontHint`)}}
- q-item-section
- q-select(
- outlined
- v-model='state.config.contentFont'
- :options='fonts'
- emit-value
- map-options
- dense
- :aria-label='t(`admin.theme.contentFont`)'
- )
- //- -----------------------
- //- Code Injection
- //- -----------------------
- q-card.shadow-1.q-pb-sm.q-mt-md
- q-card-section
- .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
- q-item-section
- q-no-ssr(:placeholder='t(`common.loading`)')
- util-code-editor.admin-theme-cm(
- ref='cmCSS'
- 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
- q-item-section
- q-no-ssr(:placeholder='t(`common.loading`)')
- util-code-editor.admin-theme-cm(
- ref='cmHead'
- 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
- q-item-section
- q-no-ssr(:placeholder='t(`common.loading`)')
- util-code-editor.admin-theme-cm(
- ref='cmBody'
- v-model='state.config.injectBody'
- language='html'
- )
- </template>
- <script setup>
- import gql from 'graphql-tag'
- import { cloneDeep, startCase } from 'lodash-es'
- import { useI18n } from 'vue-i18n'
- import { setCssVar, 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'
- // 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',
- contentWidth: 'full',
- sidebarPosition: 'left',
- tocPosition: 'right',
- showSharingMenu: true,
- showPrintBtn: true,
- baseFont: '',
- contentFont: ''
- }
- })
- const colorKeys = [
- 'primary',
- 'secondary',
- 'accent',
- 'header',
- 'sidebar'
- ]
- const widthOptions = [
- { label: 'Full Width', value: 'full' },
- { label: 'Centered', value: 'centered' }
- ]
- const rightLeftOptions = [
- { label: 'Hide', value: 'off' },
- { label: 'Left', value: 'left' },
- { label: 'Right', value: 'right' }
- ]
- const fonts = [
- { label: 'Inter', value: 'inter' },
- { label: 'Open Sans', value: 'opensans' },
- { label: 'Montserrat', value: 'montserrat' },
- { label: 'Roboto', value: 'roboto' },
- { label: 'Rubik', value: 'rubik' },
- { label: 'Tajawal', value: 'tajawal' },
- { label: 'User System Defaults', value: 'user' }
- ]
- // WATCHERS
- watch(() => adminStore.currentSiteId, (newValue) => {
- load()
- })
- // METHODS
- function resetColors () {
- state.config.dark = false
- state.config.colorPrimary = '#1976D2'
- state.config.colorSecondary = '#02C39A'
- state.config.colorAccent = '#FF9800'
- state.config.colorHeader = '#000'
- state.config.colorSidebar = '#1976D2'
- }
- function resetFonts () {
- state.config.baseFont = 'roboto'
- state.config.contentFont = 'roboto'
- }
- 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 {
- baseFont
- contentFont
- colorPrimary
- colorSecondary
- colorAccent
- colorHeader
- colorSidebar
- dark
- injectCSS
- injectHead
- injectBody
- contentWidth
- sidebarPosition
- tocPosition
- showSharingMenu
- showPrintBtn
- }
- }
- }
- `,
- variables: {
- id: adminStore.currentSiteId
- },
- fetchPolicy: 'network-only'
- })
- if (!resp?.data?.siteById?.theme) {
- throw new Error('Failed to fetch theme config.')
- }
- 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--
- }
- 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,
- contentWidth: state.config.contentWidth,
- sidebarPosition: state.config.sidebarPosition,
- tocPosition: state.config.tocPosition,
- showSharingMenu: state.config.showSharingMenu,
- showPrintBtn: state.config.showPrintBtn,
- baseFont: state.config.baseFont,
- contentFont: state.config.contentFont
- }
- const respRaw = await APOLLO_CLIENT.mutate({
- mutation: gql`
- mutation saveTheme (
- $id: UUID!
- $patch: SiteUpdateInput!
- ) {
- updateSite (
- id: $id,
- patch: $patch
- ) {
- operation {
- succeeded
- slug
- 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)
- setCssVar('primary', state.config.colorPrimary)
- setCssVar('secondary', state.config.colorSecondary)
- setCssVar('accent', state.config.colorAccent)
- setCssVar('header', state.config.colorHeader)
- setCssVar('sidebar', state.config.colorSidebar)
- }
- $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'>
- .admin-theme-cm {
- border: 1px solid #CCC;
- border-radius: 5px;
- overflow: hidden;
- > .CodeMirror {
- height: 150px;
- }
- }
- </style>
|