2
0

AdminNavigation.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <template lang='pug'>
  2. q-page.admin-navigation
  3. .row.q-pa-md.items-center
  4. .col-auto
  5. img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-tree-structure.svg')
  6. .col.q-pl-md
  7. .text-h5.text-primary.animated.fadeInLeft {{ t('admin.navigation.title') }}
  8. .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.navigation.subtitle') }}
  9. .col-auto
  10. q-btn.acrylic-btn.q-mr-sm(
  11. icon='las la-question-circle'
  12. flat
  13. color='grey'
  14. :aria-label='t(`common.actions.viewDocs`)'
  15. :href='siteStore.docsBase + `/admin/navigation`'
  16. target='_blank'
  17. type='a'
  18. )
  19. q-tooltip {{ t(`common.actions.viewDocs`) }}
  20. q-btn.q-mr-sm.acrylic-btn(
  21. icon='las la-redo-alt'
  22. flat
  23. color='secondary'
  24. :loading='state.loading > 0'
  25. :aria-label='t(`common.actions.refresh`)'
  26. @click='load'
  27. )
  28. q-tooltip {{ t(`common.actions.refresh`) }}
  29. q-btn(
  30. unelevated
  31. icon='mdi-check'
  32. :label='t(`common.actions.apply`)'
  33. color='secondary'
  34. @click='save'
  35. :disabled='state.loading > 0'
  36. )
  37. q-separator(inset)
  38. .row.q-pa-md.q-col-gutter-md
  39. .col-auto
  40. q-card.q-mt-sm {{t('admin.navigation.mode')}}
  41. q-card.bg-dark.q-mt-sm
  42. q-list(
  43. style='min-width: 350px;'
  44. padding
  45. dark
  46. )
  47. q-item
  48. q-item-section
  49. q-select(
  50. dark
  51. outlined
  52. option-value='value'
  53. option-label='text'
  54. emit-value
  55. map-options
  56. dense
  57. options-dense
  58. :label='t(`admin.navigation.mode`)'
  59. :aria-label='t(`admin.navigation.mode`)'
  60. )
  61. //- v-container.pa-0.mt-3(fluid, grid-list-lg)
  62. //- v-row(dense)
  63. //- v-col(cols='3')
  64. //- v-card.animated.fadeInUp
  65. //- v-toolbar(color='teal', dark, dense, flat, height='56')
  66. //- v-toolbar-title.subtitle-1 {{$t('admin.navigation.mode')}}
  67. //- v-list(nav, two-line)
  68. //- v-list-item-group(v-model='config.mode', mandatory, :color='$vuetify.theme.dark ? `teal lighten-3` : `teal`')
  69. //- v-list-item(value='TREE')
  70. //- v-list-item-avatar
  71. //- img(src='/_assets/svg/icon-tree-structure-dotted.svg', alt='Site Tree')
  72. //- v-list-item-content
  73. //- v-list-item-title {{$t('admin.navigation.modeSiteTree.title')}}
  74. //- v-list-item-subtitle {{$t('admin.navigation.modeSiteTree.description')}}
  75. //- v-list-item-avatar
  76. //- v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `TREE` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
  77. //- v-icon(v-else, :color='config.mode === `TREE` ? `teal` : `grey lighten-3`') mdi-check-circle
  78. //- v-list-item(value='STATIC')
  79. //- v-list-item-avatar
  80. //- img(src='/_assets/svg/icon-features-list.svg', alt='Static Navigation')
  81. //- v-list-item-content
  82. //- v-list-item-title {{$t('admin.navigation.modeStatic.title')}}
  83. //- v-list-item-subtitle {{$t('admin.navigation.modeStatic.description')}}
  84. //- v-list-item-avatar
  85. //- v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `STATIC` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
  86. //- v-icon(v-else, :color='config.mode === `STATIC` ? `teal` : `grey lighten-3`') mdi-check-circle
  87. //- v-list-item(value='MIXED')
  88. //- v-list-item-avatar
  89. //- img(src='/_assets/svg/icon-user-menu-male-dotted.svg', alt='Custom Navigation')
  90. //- v-list-item-content
  91. //- v-list-item-title {{$t('admin.navigation.modeCustom.title')}}
  92. //- v-list-item-subtitle {{$t('admin.navigation.modeCustom.description')}}
  93. //- v-list-item-avatar
  94. //- v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `MIXED` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
  95. //- v-icon(v-else, :color='config.mode === `MIXED` ? `teal` : `grey lighten-3`') mdi-check-circle
  96. //- v-list-item(value='NONE')
  97. //- v-list-item-avatar
  98. //- img(src='/_assets/svg/icon-cancel-dotted.svg', alt='None')
  99. //- v-list-item-content
  100. //- v-list-item-title {{$t('admin.navigation.modeNone.title')}}
  101. //- v-list-item-subtitle {{$t('admin.navigation.modeNone.description')}}
  102. //- v-list-item-avatar
  103. //- v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `none` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
  104. //- v-icon(v-else, :color='config.mode === `none` ? `teal` : `grey lighten-3`') mdi-check-circle
  105. //- v-col(cols='9', v-if='config.mode === `MIXED` || config.mode === `STATIC`')
  106. //- v-card.animated.fadeInUp.wait-p2s
  107. //- v-row(no-gutters, align='stretch')
  108. //- v-col(style='flex: 0 0 350px;')
  109. //- v-card.grey(flat, style='height: 100%; border-radius: 4px 0 0 4px;', :class='$vuetify.theme.dark ? `darken-4-l5` : `lighten-3`')
  110. //- .teal.lighten-1.pa-2.d-flex(style='margin-bottom: 1px; height:56px;')
  111. //- v-select(
  112. //- :disabled='locales.length < 2'
  113. //- label='Locale'
  114. //- hide-details
  115. //- solo
  116. //- flat
  117. //- background-color='teal darken-2'
  118. //- dark
  119. //- dense
  120. //- v-model='currentLang'
  121. //- :items='locales'
  122. //- item-text='nativeName'
  123. //- item-value='code'
  124. //- )
  125. //- v-tooltip(top)
  126. //- template(v-slot:activator='{ on }')
  127. //- v-btn.ml-2(icon, tile, color='white', v-on='on', @click='copyFromLocaleDialogIsShown = true')
  128. //- v-icon mdi-arrange-send-backward
  129. //- span {{$t('admin.navigation.copyFromLocale')}}
  130. //- v-list.py-2(dense, nav, dark, class='blue darken-2', style='border-radius: 0;')
  131. //- v-list-item(v-if='currentTree.length < 1')
  132. //- v-list-item-avatar(size='24'): v-icon(color='blue lighten-3') mdi-alert
  133. //- v-list-item-content
  134. //- em.caption.blue--text.text--lighten-4 {{$t('navigation.emptyList')}}
  135. //- draggable(v-model='currentTree')
  136. //- template(v-for='navItem in currentTree')
  137. //- v-list-item(
  138. //- v-if='navItem.kind === "link"'
  139. //- :key='navItem.id'
  140. //- :class='(navItem === current) ? "blue" : ""'
  141. //- @click='selectItem(navItem)'
  142. //- )
  143. //- v-list-item-avatar(size='24', tile)
  144. //- v-icon(v-if='navItem.icon.match(/fa[a-z] fa-/)', size='19') {{ navItem.icon }}
  145. //- v-icon(v-else) {{ navItem.icon }}
  146. //- v-list-item-title {{navItem.label}}
  147. //- .py-2.clickable(
  148. //- v-else-if='navItem.kind === "divider"'
  149. //- :key='navItem.id'
  150. //- :class='(navItem === current) ? "blue" : ""'
  151. //- @click='selectItem(navItem)'
  152. //- )
  153. //- v-divider
  154. //- v-subheader.pl-4.clickable(
  155. //- v-else-if='navItem.kind === "header"'
  156. //- :key='navItem.id'
  157. //- :class='(navItem === current) ? "blue" : ""'
  158. //- @click='selectItem(navItem)'
  159. //- ) {{navItem.label}}
  160. //- v-card-chin
  161. //- v-menu(offset-y, bottom, min-width='200px', style='flex: 1 1;')
  162. //- template(v-slot:activator='{ on }')
  163. //- v-btn(v-on='on', color='primary', depressed, block)
  164. //- v-icon(left) mdi-plus
  165. //- span {{$t('common.actions.add')}}
  166. //- v-list
  167. //- v-list-item(@click='addItem("link")')
  168. //- v-list-item-avatar(size='24'): v-icon mdi-link
  169. //- v-list-item-title {{$t('navigation.link')}}
  170. //- v-list-item(@click='addItem("header")')
  171. //- v-list-item-avatar(size='24'): v-icon mdi-format-title
  172. //- v-list-item-title {{$t('navigation.header')}}
  173. //- v-list-item(@click='addItem("divider")')
  174. //- v-list-item-avatar(size='24'): v-icon mdi-minus
  175. //- v-list-item-title {{$t('navigation.divider')}}
  176. //- v-col
  177. //- v-card(flat, style='border-radius: 0 4px 4px 0;')
  178. //- template(v-if='current.kind === "link"')
  179. //- v-toolbar(height='56', color='teal lighten-1', flat, dark)
  180. //- .subtitle-1 {{$t('navigation.edit', { kind: $t('navigation.link') })}}
  181. //- v-spacer
  182. //- v-btn.px-5(color='white', outlined, @click='deleteItem(current)')
  183. //- v-icon(left) mdi-delete
  184. //- span {{$t('navigation.delete', { kind: $t('navigation.link') })}}
  185. //- v-card-text
  186. //- v-text-field(
  187. //- outlined
  188. //- :label='$t("navigation.label")'
  189. //- prepend-icon='mdi-format-title'
  190. //- v-model='current.label'
  191. //- counter='255'
  192. //- )
  193. //- v-text-field(
  194. //- outlined
  195. //- :label='$t("navigation.icon")'
  196. //- prepend-icon='mdi-dice-5'
  197. //- v-model='current.icon'
  198. //- hide-details
  199. //- )
  200. //- .caption.pt-3.pl-5 The default icon set is #[strong Material Design Icons]. In order to use another icon set, you must first select it in the Theme administration section.
  201. //- .caption.pt-3.pl-5: strong Material Design Icons
  202. //- .caption.pl-5 Refer to the #[a(href='https://materialdesignicons.com/', target='_blank') Material Design Icons Reference] for the list of all possible values. You must prefix all values with #[code mdi-], e.g. #[code mdi-home]
  203. //- .caption.pt-3.pl-5: strong Font Awesome 5
  204. //- .caption.pl-5 Refer to the #[a(href='https://fontawesome.com/icons?d=gallery&m=free', target='_blank') Font Awesome 5 Reference] for the list of all possible values. You must prefix all values with #[code fas fa-], e.g. #[code fas fa-home]. Note that some icons use different prefixes (e.g. #[code fab], #[code fad], #[code fal], #[code far]).
  205. //- .caption.pt-3.pl-5: strong Font Awesome 4
  206. //- .caption.pl-5 Refer to the #[a(href='https://fontawesome.com/v4.7.0/icons/', target='_blank') Font Awesome 4 Reference] for the list of all possible values. You must prefix all values with #[code fa fa-], e.g. #[code fa fa-home]
  207. //- v-divider
  208. //- v-card-text
  209. //- v-select(
  210. //- outlined
  211. //- :label='$t("navigation.targetType")'
  212. //- prepend-icon='mdi-near-me'
  213. //- :items='navTypes'
  214. //- v-model='current.targetType'
  215. //- hide-details
  216. //- )
  217. //- v-text-field.mt-4(
  218. //- v-if='current.targetType === `external` || current.targetType === `externalblank`'
  219. //- outlined
  220. //- :label='$t("navigation.target")'
  221. //- prepend-icon='mdi-near-me'
  222. //- v-model='current.target'
  223. //- hide-details
  224. //- )
  225. //- .d-flex.align-center.mt-4(v-else-if='current.targetType === "page"')
  226. //- v-btn.ml-8(
  227. //- color='primary'
  228. //- dark
  229. //- @click='selectPage'
  230. //- )
  231. //- v-icon(left) mdi-magnify
  232. //- span {{$t('admin.navigation.selectPageButton')}}
  233. //- .caption.ml-4.primary--text {{current.target}}
  234. //- v-text-field(
  235. //- v-else-if='current.targetType === `search`'
  236. //- outlined
  237. //- :label='$t("navigation.navType.searchQuery")'
  238. //- prepend-icon='search'
  239. //- v-model='current.target'
  240. //- )
  241. //- v-divider
  242. //- template(v-else-if='current.kind === "header"')
  243. //- v-toolbar(height='56', color='teal lighten-1', flat, dark)
  244. //- .subtitle-1 {{$t('navigation.edit', { kind: $t('navigation.header') })}}
  245. //- v-spacer
  246. //- v-btn.px-5(color='white', outlined, @click='deleteItem(current)')
  247. //- v-icon(left) mdi-delete
  248. //- span {{$t('navigation.delete', { kind: $t('navigation.header') })}}
  249. //- v-card-text
  250. //- v-text-field(
  251. //- outlined
  252. //- :label='$t("navigation.label")'
  253. //- prepend-icon='mdi-format-title'
  254. //- v-model='current.label'
  255. //- )
  256. //- v-divider
  257. //- div(v-else-if='current.kind === "divider"')
  258. //- v-toolbar(height='56', color='teal lighten-1', flat, dark)
  259. //- .subtitle-1 {{$t('navigation.edit', { kind: $t('navigation.divider') })}}
  260. //- v-spacer
  261. //- v-btn.px-5(color='white', outlined, @click='deleteItem(current)')
  262. //- v-icon(left) mdi-delete
  263. //- span {{$t('navigation.delete', { kind: $t('navigation.divider') })}}
  264. //- v-card-text(v-if='current.kind')
  265. //- v-radio-group.pl-8(v-model='current.visibilityMode', mandatory, hide-details)
  266. //- v-radio(:label='$t("admin.navigation.visibilityMode.all")', value='all', color='primary')
  267. //- v-radio.mt-3(:label='$t("admin.navigation.visibilityMode.restricted")', value='restricted', color='primary')
  268. //- .pl-8
  269. //- v-select.pl-8.mt-3(
  270. //- item-text='name'
  271. //- item-value='id'
  272. //- outlined
  273. //- prepend-icon='mdi-account-group'
  274. //- label='Groups'
  275. //- :disabled='current.visibilityMode !== `restricted`'
  276. //- v-model='current.visibilityGroups'
  277. //- :items='groups'
  278. //- persistent-hint
  279. //- clearable
  280. //- multiple
  281. //- )
  282. //- template(v-else)
  283. //- v-toolbar(height='56', color='teal lighten-1', flat, dark)
  284. //- v-card-text.grey--text(v-if='currentTree.length > 0') {{$t('navigation.noSelectionText')}}
  285. //- v-card-text.grey--text(v-else) {{$t('navigation.noItemsText')}}
  286. //- v-dialog(v-model='copyFromLocaleDialogIsShown', max-width='650', persistent)
  287. //- v-card
  288. //- .dialog-header.is-short.is-teal
  289. //- v-icon.mr-3(color='white') mdi-arrange-send-backward
  290. //- span {{$t('admin.navigation.copyFromLocale')}}
  291. //- v-card-text.pt-5
  292. //- .body-2 {{$t('admin.navigation.copyFromLocaleInfoText')}}
  293. //- v-select.mt-3(
  294. //- :items='locales'
  295. //- item-text='nativeName'
  296. //- item-value='code'
  297. //- outlined
  298. //- prepend-icon='mdi-web'
  299. //- v-model='copyFromLocaleCode'
  300. //- :label='$t(`admin.navigation.sourceLocale`)'
  301. //- :hint='$t(`admin.navigation.sourceLocaleHint`)'
  302. //- persistent-hint
  303. //- )
  304. //- v-card-chin
  305. //- v-spacer
  306. //- v-btn(text, @click='copyFromLocaleDialogIsShown = false') {{$t('common.actions.cancel')}}
  307. //- v-btn.px-3(depressed, color='primary', @click='copyFromLocale')
  308. //- v-icon(left) mdi-chevron-right
  309. //- span {{$t('common.actions.copy')}}
  310. //- page-selector(mode='select', v-model='selectPageModal', :open-handler='selectPageHandle', path='home', :locale='currentLang')
  311. </template>
  312. <script setup>
  313. import gql from 'graphql-tag'
  314. import { find, intersectionBy, pull, unionBy } from 'lodash-es'
  315. import { v4 as uuid } from 'uuid'
  316. import { useI18n } from 'vue-i18n'
  317. import { useMeta, useQuasar } from 'quasar'
  318. import { computed, onMounted, reactive, watch, nextTick } from 'vue'
  319. import { useAdminStore } from '@/stores/admin'
  320. import { useSiteStore } from '@/stores/site'
  321. import draggable from 'vuedraggable'
  322. // QUASAR
  323. const $q = useQuasar()
  324. // STORES
  325. const adminStore = useAdminStore()
  326. const siteStore = useSiteStore()
  327. // I18N
  328. const { t } = useI18n()
  329. // META
  330. useMeta({
  331. title: t('admin.navigation.title')
  332. })
  333. // DATA
  334. const siteConfig = { lang: 'en' }
  335. const siteLangs = [{ code: 'en' }]
  336. const state = reactive({
  337. loading: 0,
  338. selectPageModal: false,
  339. trees: [],
  340. current: {},
  341. currentLang: siteConfig.lang,
  342. groups: [],
  343. copyFromLocaleDialogIsShown: false,
  344. config: {
  345. mode: 'NONE'
  346. },
  347. allLocales: [],
  348. copyFromLocaleCode: 'en'
  349. })
  350. // COMPUTED
  351. const navTypes = computed(() => ([
  352. { text: t('navigation.navType.external'), value: 'external' },
  353. { text: t('navigation.navType.externalblank'), value: 'externalblank' },
  354. { text: t('navigation.navType.home'), value: 'home' },
  355. { text: t('navigation.navType.page'), value: 'page' }
  356. // { text: t('navigation.navType.searchQuery'), value: 'search' }
  357. ]))
  358. const locales = computed(() => {
  359. return intersectionBy(state.allLocales, unionBy(siteLangs, [{ code: 'en' }, { code: siteConfig.lang }], 'code'), 'code')
  360. })
  361. const currentTree = computed({
  362. get () {
  363. return find(state.trees, ['locale', state.currentLang])?.items || []
  364. },
  365. set (val) {
  366. const tree = find(state.trees, ['locale', state.currentLang])
  367. if (tree) {
  368. tree.items = val
  369. } else {
  370. state.trees = [...state.trees, {
  371. locale: state.currentLang,
  372. items: val
  373. }]
  374. }
  375. }
  376. })
  377. // WATCHERS
  378. watch(() => state.currentLang, (newValue, oldValue) => {
  379. nextTick(() => {
  380. if (state.currentTree.length > 0) {
  381. state.current = state.currentTree[0]
  382. } else {
  383. state.current = {}
  384. }
  385. })
  386. })
  387. // METHODS
  388. async function load () {
  389. }
  390. function addItem (kind) {
  391. let newItem = {
  392. id: uuid(),
  393. kind,
  394. visibilityMode: 'all',
  395. visibilityGroups: []
  396. }
  397. switch (kind) {
  398. case 'link':
  399. newItem = {
  400. ...newItem,
  401. label: t('navigation.untitled', { kind: t('navigation.link') }),
  402. icon: 'mdi-chevron-right',
  403. targetType: 'home',
  404. target: ''
  405. }
  406. break
  407. case 'header':
  408. newItem.label = t('navigation.untitled', { kind: t('navigation.header') })
  409. break
  410. }
  411. state.currentTree = [...state.currentTree, newItem]
  412. state.current = newItem
  413. }
  414. function deleteItem (item) {
  415. state.currentTree = pull(state.currentTree, item)
  416. state.current = {}
  417. }
  418. function selectItem (item) {
  419. state.current = item
  420. }
  421. function selectPage () {
  422. state.selectPageModal = true
  423. }
  424. function selectPageHandle ({ path, locale }) {
  425. state.current.target = `/${locale}/${path}`
  426. }
  427. function copyFromLocale () {
  428. state.copyFromLocaleDialogIsShown = false
  429. state.currentTree = [...state.currentTree, ...find(state.trees, ['locale', state.copyFromLocaleCode])?.items || []]
  430. }
  431. async function save () {
  432. this.$store.commit('loadingStart', 'admin-navigation-save')
  433. try {
  434. const resp = await APOLLO_CLIENT.mutate({
  435. mutation: gql`
  436. mutation ($tree: [NavigationTreeInput]!, $mode: NavigationMode!) {
  437. navigation{
  438. updateTree(tree: $tree) {
  439. responseResult {
  440. succeeded
  441. errorCode
  442. slug
  443. message
  444. }
  445. },
  446. updateConfig(mode: $mode) {
  447. responseResult {
  448. succeeded
  449. errorCode
  450. slug
  451. message
  452. }
  453. }
  454. }
  455. }
  456. `,
  457. variables: {
  458. tree: state.trees,
  459. mode: state.config.mode
  460. }
  461. })
  462. if (resp?.data.navigation.updateTree.responseResult.succeeded && resp?.data.navigation.updateConfig.responseResult.succeeded) {
  463. this.$store.commit('showNotification', {
  464. message: t('navigation.saveSuccess'),
  465. style: 'success',
  466. icon: 'check'
  467. })
  468. } else {
  469. throw new Error(resp?.data.navigation.updateTree.operation.message || 'An unexpected error occurred.')
  470. }
  471. } catch (err) {
  472. this.$store.commit('pushGraphError', err)
  473. }
  474. this.$store.commit('loadingStop', 'admin-navigation-save')
  475. }
  476. async function refresh () {
  477. load()
  478. state.current = {}
  479. this.$store.commit('showNotification', {
  480. message: 'Navigation has been refreshed.',
  481. style: 'success',
  482. icon: 'cached'
  483. })
  484. }
  485. // apollo: {
  486. // config: {
  487. // query: gql`
  488. // {
  489. // navigation {
  490. // config {
  491. // mode
  492. // }
  493. // }
  494. // }
  495. // `,
  496. // fetchPolicy: 'network-only',
  497. // update: (data) => _.cloneDeep(data.navigation.config),
  498. // watchLoading (isLoading) {
  499. // this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-navigation-config')
  500. // }
  501. // },
  502. // trees: {
  503. // query: gql`
  504. // {
  505. // navigation {
  506. // tree {
  507. // locale
  508. // items {
  509. // id
  510. // kind
  511. // label
  512. // icon
  513. // targetType
  514. // target
  515. // visibilityMode
  516. // visibilityGroups
  517. // }
  518. // }
  519. // }
  520. // }
  521. // `,
  522. // fetchPolicy: 'network-only',
  523. // update: (data) => _.cloneDeep(data.navigation.tree),
  524. // watchLoading (isLoading) {
  525. // this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-navigation-tree')
  526. // }
  527. // },
  528. // groups: {
  529. // query: gql`
  530. // query {
  531. // groups {
  532. // list {
  533. // id
  534. // name
  535. // isSystem
  536. // userCount
  537. // createdAt
  538. // updatedAt
  539. // }
  540. // }
  541. // }
  542. // `,
  543. // fetchPolicy: 'network-only',
  544. // update: (data) => data.groups.list,
  545. // watchLoading (isLoading) {
  546. // this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-navigation-groups')
  547. // }
  548. // },
  549. // allLocales: {
  550. // query: gql`
  551. // {
  552. // localization {
  553. // locales {
  554. // code
  555. // name
  556. // nativeName
  557. // }
  558. // }
  559. // }
  560. // `,
  561. // fetchPolicy: 'network-only',
  562. // update: (data) => data.localization.locales,
  563. // watchLoading (isLoading) {
  564. // this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-navigation-locales')
  565. // }
  566. // }
  567. // }
  568. </script>
  569. <style lang='scss' scoped>
  570. .clickable {
  571. cursor: pointer;
  572. &:hover {
  573. background-color: rgba($blue-5, .25);
  574. }
  575. }
  576. </style>