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/vue": "6.19.3",
|
||||
"@types/is-touch-device": "1.0.0",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
"@vue/compat": "3.2.31",
|
||||
"@vueuse/core": "8.2.3",
|
||||
"@vueuse/router": "8.2.3",
|
||||
|
|
|
@ -156,95 +156,90 @@
|
|||
</aside>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
|
||||
import {mapState} from 'vuex'
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted, onBeforeMount} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import draggable from 'vuedraggable'
|
||||
import { SortableEvent } from 'sortablejs'
|
||||
|
||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
||||
import PoweredByLink from '@/components/home/PoweredByLink.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 {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import NamespaceModel from '@/models/namespace'
|
||||
import ListModel from '@/models/list'
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navigation',
|
||||
|
||||
components: {
|
||||
ListSettingsDropdown,
|
||||
NamespaceSettingsDropdown,
|
||||
draggable,
|
||||
Logo,
|
||||
PoweredByLink,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
listsVisible: {},
|
||||
drag: false,
|
||||
dragOptions: {
|
||||
const drag = ref(false)
|
||||
const dragOptions = {
|
||||
animation: 100,
|
||||
ghostClass: 'ghost',
|
||||
},
|
||||
listUpdating: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
menuActive: MENU_ACTIVE,
|
||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
|
||||
}),
|
||||
activeLists() {
|
||||
return this.namespaces.map(({lists}) => lists?.filter(item => 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
|
||||
}
|
||||
|
||||
const store = useStore()
|
||||
const currentList = computed(() => store.state.currentList)
|
||||
const menuActive = computed(() => store.state.menuActive)
|
||||
const loading = computed(() => store.state.loading && store.state.loadingModule === 'namespaces')
|
||||
|
||||
|
||||
const namespaces = computed(() => {
|
||||
return (store.state.namespaces.namespaces as NamespaceModel[]).filter(n => !n.isArchived)
|
||||
})
|
||||
const activeLists = computed(() => {
|
||||
return namespaces.value.map(({lists}) => {
|
||||
return lists?.filter(item => {
|
||||
return typeof item !== 'undefined' && !item.isArchived
|
||||
})
|
||||
})
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.resize)
|
||||
},
|
||||
mounted() {
|
||||
this.resize()
|
||||
},
|
||||
methods: {
|
||||
toggleFavoriteList(list) {
|
||||
})
|
||||
|
||||
const namespaceTitles = computed(() => {
|
||||
return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
|
||||
})
|
||||
|
||||
const namespaceListsCount = computed(() => {
|
||||
return namespaces.value.map((_, index) => activeLists.value[index]?.length ?? 0)
|
||||
})
|
||||
|
||||
|
||||
useEventListener('resize', resize)
|
||||
onMounted(() => resize())
|
||||
|
||||
|
||||
function toggleFavoriteList(list: ListModel) {
|
||||
// The favorites pseudo list is always favorite
|
||||
// Archived lists cannot be marked favorite
|
||||
if (list.id === -1 || list.isArchived) {
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('lists/toggleListFavorite', list)
|
||||
},
|
||||
resize() {
|
||||
store.dispatch('lists/toggleListFavorite', list)
|
||||
}
|
||||
|
||||
function resize() {
|
||||
// Hide the menu by default on mobile
|
||||
this.$store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||
},
|
||||
toggleLists(namespaceId) {
|
||||
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId]
|
||||
},
|
||||
updateActiveLists(namespace, activeLists) {
|
||||
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||
}
|
||||
|
||||
function toggleLists(namespaceId: number) {
|
||||
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
|
@ -255,39 +250,41 @@ export default defineComponent({
|
|||
...namespace.lists.filter(l => l.isArchived),
|
||||
]
|
||||
|
||||
const newNamespace = {
|
||||
store.commit('namespaces/setNamespaceById', {
|
||||
...namespace,
|
||||
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)
|
||||
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex)
|
||||
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
|
||||
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
|
||||
|
||||
const listsActive = this.activeLists[newNamespaceIndex]
|
||||
const listsActive = activeLists.value[newNamespaceIndex]
|
||||
const list = listsActive[e.newIndex]
|
||||
const listBefore = 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 {
|
||||
// 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,
|
||||
position,
|
||||
namespaceId,
|
||||
})
|
||||
} finally {
|
||||
this.listUpdating[list.id] = false
|
||||
listUpdating.value[list.id] = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {i18n} from '@/i18n'
|
||||
import NamespaceModel from '@/models/namespace'
|
||||
|
||||
export const getNamespaceTitle = (n) => {
|
||||
export const getNamespaceTitle = (n: NamespaceModel) => {
|
||||
if (n.id === -1) {
|
||||
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.file = new FileModel(this.file)
|
||||
this.created = new Date(this.created)
|
||||
|
||||
/** @type {number} */
|
||||
this.id
|
||||
}
|
||||
|
||||
defaults() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
|
||||
export default class ListModel extends AbstractModel {
|
||||
export default class LinkShareModel extends AbstractModel {
|
||||
|
||||
constructor(data) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
this.id
|
||||
|
||||
/** @type {boolean} */
|
||||
this.isArchived
|
||||
|
||||
/** @type {number} */
|
||||
this.position
|
||||
|
||||
this.created = new Date(this.created)
|
||||
this.updated = new Date(this.updated)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ export default class NamespaceModel extends AbstractModel {
|
|||
this.hexColor = '#' + this.hexColor
|
||||
}
|
||||
|
||||
/** @type {ListModel[]} */
|
||||
this.lists = this.lists.map(l => {
|
||||
return new ListModel(l)
|
||||
})
|
||||
|
@ -21,6 +22,15 @@ export default class NamespaceModel extends AbstractModel {
|
|||
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.updated = new Date(this.updated)
|
||||
}
|
||||
|
|
|
@ -3024,6 +3024,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
||||
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@*":
|
||||
version "0.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb"
|
||||
|
|
Loading…
Add table
Reference in a new issue