feat: convert navigation to script setup and ts
This commit is contained in:
parent
b5f867cc66
commit
658ca4c955
8 changed files with 149 additions and 123 deletions
|
@ -23,6 +23,7 @@
|
||||||
"@sentry/tracing": "6.19.3",
|
"@sentry/tracing": "6.19.3",
|
||||||
"@sentry/vue": "6.19.3",
|
"@sentry/vue": "6.19.3",
|
||||||
"@types/is-touch-device": "1.0.0",
|
"@types/is-touch-device": "1.0.0",
|
||||||
|
"@types/sortablejs": "^1.10.7",
|
||||||
"@vue/compat": "3.2.31",
|
"@vue/compat": "3.2.31",
|
||||||
"@vueuse/core": "8.2.3",
|
"@vueuse/core": "8.2.3",
|
||||||
"@vueuse/router": "8.2.3",
|
"@vueuse/router": "8.2.3",
|
||||||
|
|
|
@ -156,95 +156,90 @@
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineComponent} from 'vue'
|
import {ref, computed, onMounted, onBeforeMount} from 'vue'
|
||||||
|
import {useStore} from 'vuex'
|
||||||
import {mapState} from 'vuex'
|
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
|
import { SortableEvent } from 'sortablejs'
|
||||||
|
|
||||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||||
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
||||||
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
||||||
import Logo from '@/components/home/Logo.vue'
|
import Logo from '@/components/home/Logo.vue'
|
||||||
|
|
||||||
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
import {MENU_ACTIVE} from '@/store/mutation-types'
|
||||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||||
|
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||||
|
import { useEventListener } from '@vueuse/core'
|
||||||
|
import NamespaceModel from '@/models/namespace'
|
||||||
|
import ListModel from '@/models/list'
|
||||||
|
|
||||||
|
const drag = ref(false)
|
||||||
export default defineComponent({
|
const dragOptions = {
|
||||||
name: 'navigation',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
ListSettingsDropdown,
|
|
||||||
NamespaceSettingsDropdown,
|
|
||||||
draggable,
|
|
||||||
Logo,
|
|
||||||
PoweredByLink,
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
listsVisible: {},
|
|
||||||
drag: false,
|
|
||||||
dragOptions: {
|
|
||||||
animation: 100,
|
animation: 100,
|
||||||
ghostClass: 'ghost',
|
ghostClass: 'ghost',
|
||||||
},
|
}
|
||||||
listUpdating: {},
|
|
||||||
}
|
const store = useStore()
|
||||||
},
|
const currentList = computed(() => store.state.currentList)
|
||||||
computed: {
|
const menuActive = computed(() => store.state.menuActive)
|
||||||
...mapState({
|
const loading = computed(() => store.state.loading && store.state.loadingModule === 'namespaces')
|
||||||
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
|
||||||
currentList: CURRENT_LIST,
|
|
||||||
background: 'background',
|
const namespaces = computed(() => {
|
||||||
menuActive: MENU_ACTIVE,
|
return (store.state.namespaces.namespaces as NamespaceModel[]).filter(n => !n.isArchived)
|
||||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
|
})
|
||||||
}),
|
const activeLists = computed(() => {
|
||||||
activeLists() {
|
return namespaces.value.map(({lists}) => {
|
||||||
return this.namespaces.map(({lists}) => lists?.filter(item => typeof item !== 'undefined' && !item.isArchived))
|
return lists?.filter(item => {
|
||||||
},
|
return typeof item !== 'undefined' && !item.isArchived
|
||||||
namespaceTitles() {
|
|
||||||
return this.namespaces.map((namespace) => this.getNamespaceTitle(namespace))
|
|
||||||
},
|
|
||||||
namespaceListsCount() {
|
|
||||||
return this.namespaces.map((_, index) => this.activeLists[index]?.length ?? 0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
beforeCreate() {
|
|
||||||
// FIXME: async action in beforeCreate, might be unfinished when component mounts
|
|
||||||
this.$store.dispatch('namespaces/loadNamespaces')
|
|
||||||
.then(namespaces => {
|
|
||||||
namespaces.forEach(n => {
|
|
||||||
if (typeof this.listsVisible[n.id] === 'undefined') {
|
|
||||||
this.listsVisible[n.id] = true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
created() {
|
|
||||||
window.addEventListener('resize', this.resize)
|
const namespaceTitles = computed(() => {
|
||||||
},
|
return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
|
||||||
mounted() {
|
})
|
||||||
this.resize()
|
|
||||||
},
|
const namespaceListsCount = computed(() => {
|
||||||
methods: {
|
return namespaces.value.map((_, index) => activeLists.value[index]?.length ?? 0)
|
||||||
toggleFavoriteList(list) {
|
})
|
||||||
|
|
||||||
|
|
||||||
|
useEventListener('resize', resize)
|
||||||
|
onMounted(() => resize())
|
||||||
|
|
||||||
|
|
||||||
|
function toggleFavoriteList(list: ListModel) {
|
||||||
// The favorites pseudo list is always favorite
|
// The favorites pseudo list is always favorite
|
||||||
// Archived lists cannot be marked favorite
|
// Archived lists cannot be marked favorite
|
||||||
if (list.id === -1 || list.isArchived) {
|
if (list.id === -1 || list.isArchived) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.$store.dispatch('lists/toggleListFavorite', list)
|
store.dispatch('lists/toggleListFavorite', list)
|
||||||
},
|
}
|
||||||
resize() {
|
|
||||||
|
function resize() {
|
||||||
// Hide the menu by default on mobile
|
// Hide the menu by default on mobile
|
||||||
this.$store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||||
},
|
}
|
||||||
toggleLists(namespaceId) {
|
|
||||||
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId]
|
function toggleLists(namespaceId: number) {
|
||||||
},
|
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
|
||||||
updateActiveLists(namespace, activeLists) {
|
}
|
||||||
|
|
||||||
|
const listsVisible = ref<{ [id: NamespaceModel['id']]: boolean}>({})
|
||||||
|
// FIXME: async action will be unfinished when component mounts
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const namespaces = await store.dispatch('namespaces/loadNamespaces') as NamespaceModel[]
|
||||||
|
namespaces.forEach(n => {
|
||||||
|
if (typeof listsVisible.value[n.id] === 'undefined') {
|
||||||
|
listsVisible.value[n.id] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateActiveLists(namespace: NamespaceModel, activeLists: ListModel[]) {
|
||||||
// This is a bit hacky: since we do have to filter out the archived items from the list
|
// This is a bit hacky: since we do have to filter out the archived items from the list
|
||||||
// for vue draggable updating it is not as simple as replacing it.
|
// for vue draggable updating it is not as simple as replacing it.
|
||||||
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
|
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
|
||||||
|
@ -255,39 +250,41 @@ export default defineComponent({
|
||||||
...namespace.lists.filter(l => l.isArchived),
|
...namespace.lists.filter(l => l.isArchived),
|
||||||
]
|
]
|
||||||
|
|
||||||
const newNamespace = {
|
store.commit('namespaces/setNamespaceById', {
|
||||||
...namespace,
|
...namespace,
|
||||||
lists,
|
lists,
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.$store.commit('namespaces/setNamespaceById', newNamespace)
|
const listUpdating = ref<{ [id: NamespaceModel['id']]: boolean}>({})
|
||||||
},
|
async function saveListPosition(e: SortableEvent) {
|
||||||
|
if (!e.newIndex) return
|
||||||
|
|
||||||
async saveListPosition(e) {
|
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
|
||||||
const namespaceId = parseInt(e.to.dataset.namespaceId)
|
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
|
||||||
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex)
|
|
||||||
|
|
||||||
const listsActive = this.activeLists[newNamespaceIndex]
|
const listsActive = activeLists.value[newNamespaceIndex]
|
||||||
const list = listsActive[e.newIndex]
|
const list = listsActive[e.newIndex]
|
||||||
const listBefore = listsActive[e.newIndex - 1] ?? null
|
const listBefore = listsActive[e.newIndex - 1] ?? null
|
||||||
const listAfter = listsActive[e.newIndex + 1] ?? null
|
const listAfter = listsActive[e.newIndex + 1] ?? null
|
||||||
this.listUpdating[list.id] = true
|
listUpdating.value[list.id] = true
|
||||||
|
|
||||||
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
|
const position = calculateItemPosition(
|
||||||
|
listBefore !== null ? listBefore.position : null,
|
||||||
|
listAfter !== null ? listAfter.position : null,
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// create a copy of the list in order to not violate vuex mutations
|
// create a copy of the list in order to not violate vuex mutations
|
||||||
await this.$store.dispatch('lists/updateList', {
|
await store.dispatch('lists/updateList', {
|
||||||
...list,
|
...list,
|
||||||
position,
|
position,
|
||||||
namespaceId,
|
namespaceId,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
this.listUpdating[list.id] = false
|
listUpdating.value[list.id] = false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {i18n} from '@/i18n'
|
import {i18n} from '@/i18n'
|
||||||
|
import NamespaceModel from '@/models/namespace'
|
||||||
|
|
||||||
export const getNamespaceTitle = (n) => {
|
export const getNamespaceTitle = (n: NamespaceModel) => {
|
||||||
if (n.id === -1) {
|
if (n.id === -1) {
|
||||||
return i18n.global.t('namespace.pseudo.sharedLists.title')
|
return i18n.global.t('namespace.pseudo.sharedLists.title')
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ export default class AttachmentModel extends AbstractModel {
|
||||||
this.createdBy = new UserModel(this.createdBy)
|
this.createdBy = new UserModel(this.createdBy)
|
||||||
this.file = new FileModel(this.file)
|
this.file = new FileModel(this.file)
|
||||||
this.created = new Date(this.created)
|
this.created = new Date(this.created)
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
this.id
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel from './user'
|
||||||
|
|
||||||
export default class ListModel extends AbstractModel {
|
export default class LinkShareModel extends AbstractModel {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
// The constructor of AbstractModel handles all the default parsing.
|
// The constructor of AbstractModel handles all the default parsing.
|
||||||
|
|
|
@ -24,6 +24,15 @@ export default class ListModel extends AbstractModel {
|
||||||
this.subscription = new SubscriptionModel(this.subscription)
|
this.subscription = new SubscriptionModel(this.subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
this.id
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.isArchived
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
this.position
|
||||||
|
|
||||||
this.created = new Date(this.created)
|
this.created = new Date(this.created)
|
||||||
this.updated = new Date(this.updated)
|
this.updated = new Date(this.updated)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default class NamespaceModel extends AbstractModel {
|
||||||
this.hexColor = '#' + this.hexColor
|
this.hexColor = '#' + this.hexColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {ListModel[]} */
|
||||||
this.lists = this.lists.map(l => {
|
this.lists = this.lists.map(l => {
|
||||||
return new ListModel(l)
|
return new ListModel(l)
|
||||||
})
|
})
|
||||||
|
@ -21,6 +22,15 @@ export default class NamespaceModel extends AbstractModel {
|
||||||
this.subscription = new SubscriptionModel(this.subscription)
|
this.subscription = new SubscriptionModel(this.subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
this.id
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
this.title
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.isArchived
|
||||||
|
|
||||||
this.created = new Date(this.created)
|
this.created = new Date(this.created)
|
||||||
this.updated = new Date(this.updated)
|
this.updated = new Date(this.updated)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3024,6 +3024,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
||||||
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
|
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
|
||||||
|
|
||||||
|
"@types/sortablejs@^1.10.7":
|
||||||
|
version "1.10.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.10.7.tgz#ab9039c85429f0516955ec6dbc0bb20139417b15"
|
||||||
|
integrity sha512-lGCwwgpj8zW/ZmaueoPVSP7nnc9t8VqVWXS+ASX3eoUUENmiazv0rlXyTRludXzuX9ALjPsMqBu85TgJNWbTOg==
|
||||||
|
|
||||||
"@types/tern@*":
|
"@types/tern@*":
|
||||||
version "0.23.4"
|
version "0.23.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb"
|
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb"
|
||||||
|
|
Loading…
Reference in a new issue