Move list edit/namespace to separate pages and in a menu (#397)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/397 Co-authored-by: konrad <konrad@kola-entertainments.de> Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
52
src/views/list/settings/archive.vue
Normal file
52
src/views/list/settings/archive.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="archiveList()"
|
||||
>
|
||||
<span slot="header">{{ list.isArchived ? 'Un-' : '' }}Archive this list</span>
|
||||
<p slot="text" v-if="list.isArchived">
|
||||
You will be able to create new tasks or edit it.
|
||||
</p>
|
||||
<p slot="text" v-else>
|
||||
You won't be able to edit this list or create new tasks until you un-archive it.
|
||||
</p>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '@/services/list'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-archive',
|
||||
data() {
|
||||
return {
|
||||
listService: ListService,
|
||||
list: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.list = this.$store.getters['lists/getListById'](this.$route.params.listId)
|
||||
this.setTitle(`Archive "${this.list.title}"`)
|
||||
},
|
||||
methods: {
|
||||
archiveList() {
|
||||
|
||||
this.list.isArchived = !this.list.isArchived
|
||||
|
||||
this.listService.update(this.list)
|
||||
.then(r => {
|
||||
this.$store.commit('currentList', r)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', r)
|
||||
this.success({message: 'The list was successfully archived.'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.$router.back()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
166
src/views/list/settings/background.vue
Normal file
166
src/views/list/settings/background.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<create-edit
|
||||
title="Set list background"
|
||||
primary-label=""
|
||||
:loading="backgroundService.loading"
|
||||
class="list-background-setting"
|
||||
:wide="true"
|
||||
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
||||
>
|
||||
<div class="mb-4" v-if="uploadBackgroundEnabled">
|
||||
<input
|
||||
@change="uploadBackground"
|
||||
accept="image/*"
|
||||
class="is-hidden"
|
||||
ref="backgroundUploadInput"
|
||||
type="file"
|
||||
/>
|
||||
<x-button
|
||||
:loading="backgroundUploadService.loading"
|
||||
@click="$refs.backgroundUploadInput.click()"
|
||||
type="primary"
|
||||
>
|
||||
Choose a background from your pc
|
||||
</x-button>
|
||||
</div>
|
||||
<template v-if="unsplashBackgroundEnabled">
|
||||
<input
|
||||
:class="{'is-loading': backgroundService.loading}"
|
||||
@keyup="() => newBackgroundSearch()"
|
||||
class="input is-expanded"
|
||||
placeholder="Search for a background..."
|
||||
type="text"
|
||||
v-model="backgroundSearchTerm"
|
||||
/>
|
||||
<p class="unsplash-link"><a href="https://unsplash.com" target="_blank">Powered by Unsplash</a></p>
|
||||
<div class="image-search-result">
|
||||
<a
|
||||
:key="im.id"
|
||||
:style="{'background-image': `url(${backgroundThumbs[im.id]})`}"
|
||||
@click="() => setBackground(im.id)"
|
||||
class="image"
|
||||
v-for="im in backgroundSearchResult">
|
||||
<a :href="`https://unsplash.com/@${im.info.author}`" target="_blank" class="info">
|
||||
{{ im.info.authorName }}
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
<x-button
|
||||
:disabled="backgroundService.loading"
|
||||
@click="() => searchBackgrounds(currentPage + 1)"
|
||||
class="is-load-more-button mt-4"
|
||||
:shadow="false"
|
||||
type="secondary"
|
||||
v-if="backgroundSearchResult.length > 0"
|
||||
>
|
||||
{{ backgroundService.loading ? 'Loading...' : 'Load more photos'}}
|
||||
</x-button>
|
||||
</template>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BackgroundUnsplashService from '../../../services/backgroundUnsplash'
|
||||
import BackgroundUploadService from '../../../services/backgroundUpload'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
import CreateEdit from '@/components/misc/create-edit'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-background',
|
||||
components: {CreateEdit},
|
||||
data() {
|
||||
return {
|
||||
backgroundSearchTerm: '',
|
||||
backgroundSearchResult: [],
|
||||
backgroundService: null,
|
||||
backgroundThumbs: {},
|
||||
currentPage: 1,
|
||||
backgroundSearchTimeout: null,
|
||||
|
||||
backgroundUploadService: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
unsplashBackgroundEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders.includes('unsplash')
|
||||
},
|
||||
uploadBackgroundEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders.includes('upload')
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.backgroundService = new BackgroundUnsplashService()
|
||||
this.backgroundUploadService = new BackgroundUploadService()
|
||||
this.setTitle('Set a list background')
|
||||
// Show the default collection of backgrounds
|
||||
this.newBackgroundSearch()
|
||||
},
|
||||
methods: {
|
||||
newBackgroundSearch() {
|
||||
if (!this.unsplashBackgroundEnabled) {
|
||||
return
|
||||
}
|
||||
// This is an extra method to reset a few things when searching to not break loading more photos.
|
||||
this.$set(this, 'backgroundSearchResult', [])
|
||||
this.$set(this, 'backgroundThumbs', {})
|
||||
this.searchBackgrounds()
|
||||
},
|
||||
searchBackgrounds(page = 1) {
|
||||
|
||||
if (this.backgroundSearchTimeout !== null) {
|
||||
clearTimeout(this.backgroundSearchTimeout)
|
||||
}
|
||||
|
||||
// We're using the timeout to not search on every keypress but with a 300ms delay.
|
||||
// If another key is pressed within these 300ms, the last search request is dropped and a new one is scheduled.
|
||||
this.backgroundSearchTimeout = setTimeout(() => {
|
||||
this.currentPage = page
|
||||
this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
|
||||
.then(r => {
|
||||
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
|
||||
r.forEach(b => {
|
||||
this.backgroundService.thumb(b)
|
||||
.then(t => {
|
||||
this.$set(this.backgroundThumbs, b.id, t)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
setBackground(backgroundId) {
|
||||
// Don't set a background if we're in the process of setting one
|
||||
if (this.backgroundService.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
this.success({message: 'The background has been set successfully!'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
uploadBackground() {
|
||||
if (this.$refs.backgroundUploadInput.files.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
this.success({message: 'The background has been set successfully!'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
43
src/views/list/settings/delete.vue
Normal file
43
src/views/list/settings/delete.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteList()"
|
||||
>
|
||||
<span slot="header">Delete this list</span>
|
||||
<p slot="text">Are you sure you want to delete this list and all of its contents?
|
||||
<br/>This includes all tasks and <b>CANNOT BE UNDONE!</b></p>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '@/services/list'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-delete',
|
||||
data() {
|
||||
return {
|
||||
listService: ListService,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
const list = this.$store.getters['lists/getListById'](this.$route.params.listId)
|
||||
this.setTitle(`Delete "${list.title}"`)
|
||||
},
|
||||
methods: {
|
||||
deleteList() {
|
||||
const list = this.$store.getters['lists/getListById'](this.$route.params.listId)
|
||||
|
||||
this.listService.delete(list)
|
||||
.then(() => {
|
||||
this.$store.commit('namespaces/removeListFromNamespaceById', list)
|
||||
this.success({message: 'The list was successfully deleted.'}, this)
|
||||
this.$router.push({name: 'home'})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
58
src/views/list/settings/duplicate.vue
Normal file
58
src/views/list/settings/duplicate.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<create-edit
|
||||
title="Duplicate this list"
|
||||
primary-icon="paste"
|
||||
primary-label="Duplicate"
|
||||
@primary="duplicateList"
|
||||
:loading="listDuplicateService.loading"
|
||||
>
|
||||
<p>Select a namespace which should hold the duplicated list:</p>
|
||||
<namespace-search @selected="selectNamespace"/>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListDuplicateService from '@/services/listDuplicateService'
|
||||
import NamespaceSearch from '@/components/namespace/namespace-search'
|
||||
import ListDuplicateModel from '@/models/listDuplicateModel'
|
||||
import CreateEdit from '@/components/misc/create-edit'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-duplicate',
|
||||
data() {
|
||||
return {
|
||||
listDuplicateService: ListDuplicateService,
|
||||
selectedNamespace: null,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CreateEdit,
|
||||
NamespaceSearch,
|
||||
},
|
||||
created() {
|
||||
this.listDuplicateService = new ListDuplicateService()
|
||||
this.setTitle('Duplicate List')
|
||||
},
|
||||
methods: {
|
||||
selectNamespace(namespace) {
|
||||
this.selectedNamespace = namespace
|
||||
},
|
||||
duplicateList() {
|
||||
const listDuplicate = new ListDuplicateModel({
|
||||
listId: this.$route.params.listId,
|
||||
namespaceId: this.selectedNamespace.id,
|
||||
})
|
||||
this.listDuplicateService.create(listDuplicate)
|
||||
.then(r => {
|
||||
this.$store.commit('namespaces/addListToNamespace', r.list)
|
||||
this.$store.commit('lists/addList', r.list)
|
||||
this.success({message: 'The list was successfully duplicated.'}, this)
|
||||
this.$router.push({name: 'list.index', params: {listId: r.list.id}})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
127
src/views/list/settings/edit.vue
Normal file
127
src/views/list/settings/edit.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<create-edit
|
||||
title="Edit This List"
|
||||
primary-icon=""
|
||||
primary-label="Save"
|
||||
@primary="save"
|
||||
tertary="Delete"
|
||||
@tertary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="listtext">List Name</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': listService.loading}"
|
||||
:disabled="listService.loading"
|
||||
@keyup.enter="save"
|
||||
class="input"
|
||||
id="listtext"
|
||||
placeholder="The list title goes here..."
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="list.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="listtext"
|
||||
v-tooltip="'The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.'">
|
||||
List Identifier
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': listService.loading}"
|
||||
:disabled="listService.loading"
|
||||
@keyup.enter="save"
|
||||
class="input"
|
||||
id="listtext"
|
||||
placeholder="The list identifier goes here..."
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="list.identifier"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="listdescription">Description</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
:class="{ 'disabled': listService.loading}"
|
||||
:disabled="listService.loading"
|
||||
:preview-is-default="false"
|
||||
id="listdescription"
|
||||
placeholder="The lists description goes here..."
|
||||
v-model="list.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Color</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="list.hexColor"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListModel from '@/models/list'
|
||||
import ListService from '@/services/list'
|
||||
import ColorPicker from '@/components/input/colorPicker'
|
||||
import LoadingComponent from '@/components/misc/loading'
|
||||
import ErrorComponent from '@/components/misc/error'
|
||||
import ListDuplicateService from '@/services/listDuplicateService'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
import CreateEdit from '@/components/misc/create-edit'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-edit',
|
||||
data() {
|
||||
return {
|
||||
list: ListModel,
|
||||
listService: ListService,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CreateEdit,
|
||||
ColorPicker,
|
||||
editor: () => ({
|
||||
component: import(/* webpackChunkName: "editor" */ '@/components/input/editor'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.listDuplicateService = new ListDuplicateService()
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
const list = new ListModel({id: this.$route.params.listId})
|
||||
|
||||
this.listService.get(list)
|
||||
.then(r => {
|
||||
this.$set(this, 'list', r)
|
||||
this.$store.commit(CURRENT_LIST, r)
|
||||
this.setTitle(`Edit "${this.list.title}"`)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
save() {
|
||||
this.listService.update(this.list)
|
||||
.then(r => {
|
||||
this.$store.commit('namespaces/setListInNamespaceById', r)
|
||||
this.success({message: 'The list was successfully updated.'}, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
78
src/views/list/settings/share.vue
Normal file
78
src/views/list/settings/share.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<create-edit
|
||||
title="Share this list"
|
||||
primary-label=""
|
||||
>
|
||||
<component
|
||||
:id="list.id"
|
||||
:is="manageUsersComponent"
|
||||
:userIsAdmin="userIsAdmin"
|
||||
shareType="user"
|
||||
type="list"/>
|
||||
<component
|
||||
:id="list.id"
|
||||
:is="manageTeamsComponent"
|
||||
:userIsAdmin="userIsAdmin"
|
||||
shareType="team"
|
||||
type="list"/>
|
||||
|
||||
<link-sharing :list-id="$route.params.listId" v-if="linkSharingEnabled" class="mt-4"/>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '@/services/list'
|
||||
import ListModel from '@/models/list'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
|
||||
import CreateEdit from '@/components/misc/create-edit'
|
||||
import LinkSharing from '@/components/sharing/linkSharing'
|
||||
import userTeam from '@/components/sharing/userTeam'
|
||||
|
||||
export default {
|
||||
name: 'list-setting-share',
|
||||
data() {
|
||||
return {
|
||||
list: ListModel,
|
||||
listService: ListService,
|
||||
manageUsersComponent: '',
|
||||
manageTeamsComponent: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CreateEdit,
|
||||
LinkSharing,
|
||||
userTeam,
|
||||
},
|
||||
computed: {
|
||||
linkSharingEnabled() {
|
||||
return this.$store.state.config.linkSharingEnabled
|
||||
},
|
||||
userIsAdmin() {
|
||||
return this.list.owner && this.list.owner.id === this.$store.state.auth.info.id
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
const list = new ListModel({id: this.$route.params.listId})
|
||||
|
||||
this.listService.get(list)
|
||||
.then(r => {
|
||||
this.$set(this, 'list', r)
|
||||
this.$store.commit(CURRENT_LIST, r)
|
||||
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
|
||||
this.manageTeamsComponent = 'userTeam'
|
||||
this.manageUsersComponent = 'userTeam'
|
||||
this.setTitle(`Share "${this.list.title}"`)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
Reference in New Issue
Block a user