feat: convert navigation to script setup and ts

This commit is contained in:
Dominik Pschenitschni 2022-02-13 18:11:26 +01:00 committed by kolaente
parent b5f867cc66
commit 658ca4c955
No known key found for this signature in database
GPG key ID: F40E70337AB24C9B
8 changed files with 149 additions and 123 deletions

View file

@ -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",

View file

@ -156,138 +156,135 @@
</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)
const dragOptions = {
animation: 100,
ghostClass: 'ghost',
}
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')
export default defineComponent({ const namespaces = computed(() => {
name: 'navigation', return (store.state.namespaces.namespaces as NamespaceModel[]).filter(n => !n.isArchived)
components: {
ListSettingsDropdown,
NamespaceSettingsDropdown,
draggable,
Logo,
PoweredByLink,
},
data() {
return {
listsVisible: {},
drag: false,
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
}
})
})
},
created() {
window.addEventListener('resize', this.resize)
},
mounted() {
this.resize()
},
methods: {
toggleFavoriteList(list) {
// 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() {
// 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) {
// 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
// because now all archived lists are sorted after the active ones. This is fine because they are sorted
// later when showing them anyway, and it makes the merging happening here a lot easier.
const lists = [
...activeLists,
...namespace.lists.filter(l => l.isArchived),
]
const newNamespace = {
...namespace,
lists,
}
this.$store.commit('namespaces/setNamespaceById', newNamespace)
},
async saveListPosition(e) {
const namespaceId = parseInt(e.to.dataset.namespaceId)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex)
const listsActive = this.activeLists[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
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', {
...list,
position,
namespaceId,
})
} finally {
this.listUpdating[list.id] = false
}
},
},
}) })
const activeLists = computed(() => {
return namespaces.value.map(({lists}) => {
return lists?.filter(item => {
return typeof item !== 'undefined' && !item.isArchived
})
})
})
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
}
store.dispatch('lists/toggleListFavorite', list)
}
function resize() {
// Hide the menu by default on mobile
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
// because now all archived lists are sorted after the active ones. This is fine because they are sorted
// later when showing them anyway, and it makes the merging happening here a lot easier.
const lists = [
...activeLists,
...namespace.lists.filter(l => l.isArchived),
]
store.commit('namespaces/setNamespaceById', {
...namespace,
lists,
})
}
const listUpdating = ref<{ [id: NamespaceModel['id']]: boolean}>({})
async function saveListPosition(e: SortableEvent) {
if (!e.newIndex) return
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
const listsActive = activeLists.value[newNamespaceIndex]
const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null
const listAfter = listsActive[e.newIndex + 1] ?? null
listUpdating.value[list.id] = true
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 store.dispatch('lists/updateList', {
...list,
position,
namespaceId,
})
} finally {
listUpdating.value[list.id] = false
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -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')
} }

View file

@ -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() {

View file

@ -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.

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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"