feat(link shares): allows switching the initial view by passing a query parameter (#2335)

Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2335
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
konrad 2022-09-14 16:37:54 +00:00
commit a6e9b36bd6
4 changed files with 90 additions and 48 deletions

View file

@ -79,30 +79,59 @@
> >
<thead> <thead>
<tr> <tr>
<th>{{ $t('list.share.attributes.link') }}</th> <th></th>
<th>{{ $t('list.share.attributes.name') }}</th> <th>{{ $t('list.share.links.view') }}</th>
<th>{{ $t('list.share.attributes.sharedBy') }}</th>
<th>{{ $t('list.share.attributes.right') }}</th>
<th>{{ $t('list.share.attributes.delete') }}</th> <th>{{ $t('list.share.attributes.delete') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr :key="s.id" v-for="s in linkShares"> <tr :key="s.id" v-for="s in linkShares">
<td> <td>
<p class="mb-2 is-italic" v-if="s.name !== ''">
{{ s.name }}
</p>
<p class="mb-2">
<i18n-t keypath="list.share.links.sharedBy">
<strong>{{ s.sharedBy.getDisplayName() }}</strong>
</i18n-t>
</p>
<p class="mb-2">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('list.share.right.read') }}
</template>
</p>
<div class="field has-addons no-input-mobile"> <div class="field has-addons no-input-mobile">
<div class="control"> <div class="control">
<input <input
:value="getShareLink(s.hash)" :value="getShareLink(s.hash, selectedView[s.id])"
class="input" class="input"
readonly readonly
type="text" type="text"
/> />
</div> </div>
<div class="control"> <div class="control">
<x-button <x-button
@click="copy(getShareLink(s.hash))" @click="copy(getShareLink(s.hash, selectedView[s.id]))"
:shadow="false" :shadow="false"
v-tooltip="$t('misc.copy')" v-tooltip="$t('misc.copy')"
> >
<span class="icon"> <span class="icon">
<icon icon="paste"/> <icon icon="paste"/>
@ -112,33 +141,16 @@
</div> </div>
</td> </td>
<td> <td>
<template v-if="s.name !== ''"> <div class="select">
{{ s.name }} <select v-model="selectedView[s.id]">
</template> <option
<i v-else>{{ $t('list.share.links.noName') }}</i> v-for="(title, key) in availableViews"
</td> :value="key"
<td> :key="key">
{{ s.sharedBy.getDisplayName() }} {{ title }}
</td> </option>
<td class="type"> </select>
<template v-if="s.right === RIGHTS.ADMIN"> </div>
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('list.share.right.read') }}
</template>
</td> </td>
<td class="actions"> <td class="actions">
<x-button <x-button
@ -166,7 +178,7 @@
<template #header> <template #header>
<span>{{ $t('list.share.links.remove') }}</span> <span>{{ $t('list.share.links.remove') }}</span>
</template> </template>
<template #text> <template #text>
<p>{{ $t('list.share.links.removeText') }}</p> <p>{{ $t('list.share.links.removeText') }}</p>
</template> </template>
@ -190,6 +202,8 @@ import LinkShareService from '@/services/linkShare'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {success} from '@/message' import {success} from '@/message'
import type {ListView} from '@/types/ListView'
import {LIST_VIEWS} from '@/types/ListView'
const props = defineProps({ const props = defineProps({
listId: { listId: {
@ -209,6 +223,17 @@ const showDeleteModal = ref(false)
const linkIdToDelete = ref(0) const linkIdToDelete = ref(0)
const showNewForm = ref(false) const showNewForm = ref(false)
type SelectedViewMapper = Record<IList['id'], ListView>
const selectedView = ref<SelectedViewMapper>({})
const availableViews = computed<Record<ListView, string>>(() => ({
list: t('list.list.title'),
gantt: t('list.gantt.title'),
table: t('list.table.title'),
kanban: t('list.kanban.title'),
}))
const copy = useCopyToClipboard() const copy = useCopyToClipboard()
watch( watch(
() => props.listId, () => props.listId,
@ -225,7 +250,11 @@ async function load(listId: IList['id']) {
return return
} }
linkShares.value = await linkShareService.getAll({listId}) const links = await linkShareService.getAll({listId})
links.forEach((l: ILinkShare) => {
selectedView.value[l.id] = 'list'
})
linkShares.value = links
} }
async function add(listId: IList['id']) { async function add(listId: IList['id']) {
@ -257,15 +286,15 @@ async function remove(listId: IList['id']) {
} }
} }
function getShareLink(hash: string) { function getShareLink(hash: string, view: ListView = LIST_VIEWS.LIST) {
return frontendUrl.value + 'share/' + hash + '/auth' return frontendUrl.value + 'share/' + hash + '/auth?view=' + view
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// FIXME: I think this is not needed // FIXME: I think this is not needed
.sharables-list:not(.card-content) { .sharables-list:not(.card-content) {
overflow-y: auto overflow-y: auto
} }
@include modal-transition(); @include modal-transition();

View file

@ -242,7 +242,9 @@
"remove": "Remove a link share", "remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!", "removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.", "createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted" "deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
}, },
"userTeam": { "userTeam": {
"typeUser": "user | users", "typeUser": "user | users",
@ -264,9 +266,6 @@
}, },
"attributes": { "attributes": {
"link": "Link", "link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete" "delete": "Delete"
} }
}, },

8
src/types/ListView.ts Normal file
View file

@ -0,0 +1,8 @@
export const LIST_VIEWS = {
LIST: 'list',
GANTT: 'gantt',
TABLE: 'table',
KANBAN: 'kanban',
} as const
export type ListView = typeof LIST_VIEWS[keyof typeof LIST_VIEWS]

View file

@ -41,6 +41,7 @@ import {useTitle} from '@vueuse/core'
import Message from '@/components/misc/message.vue' import Message from '@/components/misc/message.vue'
import {LOGO_VISIBLE} from '@/store/mutation-types' import {LOGO_VISIBLE} from '@/store/mutation-types'
import {LIST_VIEWS, type ListView} from '@/types/ListView'
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
useTitle(t('sharing.authenticating')) useTitle(t('sharing.authenticating'))
@ -79,7 +80,12 @@ function useAuth() {
? route.query.logoVisible === 'true' ? route.query.logoVisible === 'true'
: true : true
store.commit(LOGO_VISIBLE, logoVisible) store.commit(LOGO_VISIBLE, logoVisible)
router.push({name: 'list.list', params: {listId}})
const view = route.query.view && Object.values(LIST_VIEWS).includes(route.query.view as ListView)
? route.query.view
: 'list'
router.push({name: `list.${view}`, params: {listId}})
} catch (e: any) { } catch (e: any) {
if (e.response?.data?.code === 13001) { if (e.response?.data?.code === 13001) {
authenticateWithPassword.value = true authenticateWithPassword.value = true