You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Navigation.vue 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <!--
  2. - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
  3. -
  4. - @author Gary Kim <gary@garykim.dev>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <NcAppNavigation data-cy-files-navigation>
  24. <template #list>
  25. <NcAppNavigationItem v-for="view in parentViews"
  26. :key="view.id"
  27. :allow-collapse="true"
  28. :data-cy-files-navigation-item="view.id"
  29. :icon="view.iconClass"
  30. :open="view.expanded"
  31. :pinned="view.sticky"
  32. :title="view.name"
  33. :to="{name: 'filelist', params: { view: view.id }}"
  34. @update:open="onToggleExpand(view)">
  35. <NcAppNavigationItem v-for="child in childViews[view.id]"
  36. :key="child.id"
  37. :data-cy-files-navigation-item="child.id"
  38. :icon="child.iconClass"
  39. :title="child.name"
  40. :to="{name: 'filelist', params: { view: child.id }}" />
  41. </NcAppNavigationItem>
  42. </template>
  43. <!-- Settings toggle -->
  44. <template #footer>
  45. <ul class="app-navigation-entry__settings">
  46. <NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')"
  47. :title="t('files', 'Files settings')"
  48. data-cy-files-navigation-settings-button
  49. @click.prevent.stop="openSettings">
  50. <Cog slot="icon" :size="20" />
  51. </NcAppNavigationItem>
  52. </ul>
  53. </template>
  54. <!-- Settings modal-->
  55. <SettingsModal :open="settingsOpened"
  56. data-cy-files-navigation-settings
  57. @close="onSettingsClose" />
  58. </NcAppNavigation>
  59. </template>
  60. <script>
  61. import { emit, subscribe } from '@nextcloud/event-bus'
  62. import { generateUrl } from '@nextcloud/router'
  63. import axios from '@nextcloud/axios'
  64. import Cog from 'vue-material-design-icons/Cog.vue'
  65. import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
  66. import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
  67. import logger from '../logger.js'
  68. import Navigation from '../services/Navigation.ts'
  69. import SettingsModal from './Settings.vue'
  70. import { translate } from '@nextcloud/l10n'
  71. export default {
  72. name: 'Navigation',
  73. components: {
  74. Cog,
  75. NcAppNavigation,
  76. NcAppNavigationItem,
  77. SettingsModal,
  78. },
  79. props: {
  80. // eslint-disable-next-line vue/prop-name-casing
  81. Navigation: {
  82. type: Navigation,
  83. required: true,
  84. },
  85. },
  86. data() {
  87. return {
  88. settingsOpened: false,
  89. }
  90. },
  91. computed: {
  92. currentViewId() {
  93. return this.$route?.params?.view || 'files'
  94. },
  95. currentView() {
  96. return this.views.find(view => view.id === this.currentViewId)
  97. },
  98. /** @return {Navigation[]} */
  99. views() {
  100. return this.Navigation.views
  101. },
  102. parentViews() {
  103. return this.views
  104. // filter child views
  105. .filter(view => !view.parent)
  106. // sort views by order
  107. .sort((a, b) => {
  108. return a.order - b.order
  109. })
  110. },
  111. childViews() {
  112. return this.views
  113. // filter parent views
  114. .filter(view => !!view.parent)
  115. // create a map of parents and their children
  116. .reduce((list, view) => {
  117. list[view.parent] = [...(list[view.parent] || []), view]
  118. // Sort children by order
  119. list[view.parent].sort((a, b) => {
  120. return a.order - b.order
  121. })
  122. return list
  123. }, {})
  124. },
  125. },
  126. watch: {
  127. currentView(view, oldView) {
  128. logger.debug('View changed', { id: view.id, view })
  129. this.showView(view, oldView)
  130. },
  131. },
  132. beforeMount() {
  133. if (this.currentView) {
  134. logger.debug('Navigation mounted. Showing requested view', { view: this.currentView })
  135. this.showView(this.currentView)
  136. }
  137. subscribe('files:legacy-navigation:changed', this.onLegacyNavigationChanged)
  138. },
  139. methods: {
  140. /**
  141. * @param {Navigation} view the new active view
  142. * @param {Navigation} oldView the old active view
  143. */
  144. showView(view, oldView) {
  145. // Closing any opened sidebar
  146. window?.OCA?.Files?.Sidebar?.close?.()
  147. if (view.legacy) {
  148. const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer')
  149. document.querySelectorAll('#app-content .viewcontainer').forEach(el => {
  150. el.classList.add('hidden')
  151. })
  152. newAppContent.classList.remove('hidden')
  153. // Trigger init if not already done
  154. window.jQuery(newAppContent).trigger(new window.jQuery.Event('show'))
  155. // After show, properly send the right data
  156. this.$nextTick(() => {
  157. const { dir = '/' } = OC.Util.History.parseUrlQuery()
  158. const params = { itemId: view.id, dir }
  159. window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params))
  160. window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params))
  161. })
  162. }
  163. this.Navigation.setActive(view)
  164. emit('files:navigation:changed', view)
  165. },
  166. /**
  167. * Coming from the legacy files app.
  168. * TODO: remove when all views are migrated.
  169. *
  170. * @param {Navigation} view the new active view
  171. */
  172. onLegacyNavigationChanged({ id } = { id: 'files' }) {
  173. const view = this.Navigation.views.find(view => view.id === id)
  174. if (view && view.legacy && view.id !== this.currentView.id) {
  175. // Force update the current route as the request comes
  176. // from the legacy files app router
  177. this.$router.replace({ ...this.$route, params: { view: view.id } })
  178. this.Navigation.setActive(view)
  179. this.showView(view)
  180. }
  181. },
  182. /**
  183. * Expand/collapse a a view with children and permanently
  184. * save this setting in the server.
  185. *
  186. * @param {Navigation} view the view to toggle
  187. */
  188. onToggleExpand(view) {
  189. // Invert state
  190. view.expanded = !view.expanded
  191. axios.post(generateUrl(`/apps/files/api/v1/toggleShowFolder/${view.id}`), { show: view.expanded })
  192. },
  193. /**
  194. * Open the settings modal
  195. */
  196. openSettings() {
  197. this.settingsOpened = true
  198. },
  199. /**
  200. * Close the settings modal
  201. */
  202. onSettingsClose() {
  203. this.settingsOpened = false
  204. },
  205. t: translate,
  206. },
  207. }
  208. </script>
  209. <style scoped lang="scss">
  210. // TODO: remove when https://github.com/nextcloud/nextcloud-vue/pull/3539 is in
  211. .app-navigation::v-deep .app-navigation-entry-icon {
  212. background-repeat: no-repeat;
  213. background-position: center;
  214. }
  215. .app-navigation > ul.app-navigation__list {
  216. // Use flex gap value for more elegant spacing
  217. padding-bottom: var(--default-grid-baseline, 4px);
  218. }
  219. .app-navigation-entry__settings {
  220. height: auto !important;
  221. overflow: hidden !important;
  222. padding-top: 0 !important;
  223. // Prevent shrinking or growing
  224. flex: 0 0 auto;
  225. }
  226. </style>