Task lists WIP:

* front-end
This commit is contained in:
John Livingston 2024-05-06 15:57:42 +02:00
parent 9d5d59e9bc
commit 964b8854f6
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
11 changed files with 155 additions and 19 deletions

View File

@ -8,13 +8,15 @@ export default class MUCTaskListView extends CustomElement {
return { return {
model: { type: Object, attribute: true }, model: { type: Object, attribute: true },
collapsed: { type: Boolean, attribute: false }, collapsed: { type: Boolean, attribute: false },
edit: { type: Boolean, attribute: false } edit: { type: Boolean, attribute: false },
add_task_form_opened: { type: Boolean, attribute: false }
} }
} }
async initialize () { async initialize () {
this.collapsed = false this.collapsed = false
this.edit = false this.edit = false
this.add_task_form_opened = false
if (!this.model) { if (!this.model) {
return return
} }
@ -82,7 +84,7 @@ export default class MUCTaskListView extends CustomElement {
this.edit = !this.edit this.edit = !this.edit
if (this.edit) { if (this.edit) {
await this.updateComplete await this.updateComplete
const input = this.querySelector('input[name="name"]') const input = this.querySelector('.task-list-name input[name="name"]')
if (input) { if (input) {
input.focus() input.focus()
// Placing cursor at the end: // Placing cursor at the end:
@ -91,6 +93,46 @@ export default class MUCTaskListView extends CustomElement {
} }
} }
} }
async openAddTaskForm () {
this.add_task_form_opened = true
await this.updateComplete
const input = this.querySelector('.task-list-add-task input[name="name"]')
if (input) {
input.focus()
}
}
closeAddTaskForm () {
this.add_task_form_opened = false
}
async submitAddTask (ev) {
ev.preventDefault()
const name = ev.target.name.value.trim()
if ((name ?? '') === '') { return }
try {
this.querySelectorAll('input[type=submit]').forEach(el => {
el.setAttribute('disabled', true)
el.classList.add('disabled')
})
await this.model.createTask({
name
})
this.closeAddTaskForm()
} catch (err) {
console.error(err)
} finally {
this.querySelectorAll('input[type=submit]').forEach(el => {
el.removeAttribute('disabled')
el.classList.remove('disabled')
})
}
}
} }
api.elements.define('livechat-converse-muc-task-list', MUCTaskListView) api.elements.define('livechat-converse-muc-task-list', MUCTaskListView)

View File

@ -1,6 +1,6 @@
import { CustomElement } from 'shared/components/element.js' import { CustomElement } from 'shared/components/element.js'
import { api } from '@converse/headless/core' import { api } from '@converse/headless/core'
import tplMucTask from './templates/muc-task' import { tplMucTask } from './templates/muc-task'
export default class MUCTaskView extends CustomElement { export default class MUCTaskView extends CustomElement {
static get properties () { static get properties () {

View File

@ -16,12 +16,25 @@
column-gap: 0.25em; column-gap: 0.25em;
width: 100%; width: 100%;
button { .task-list-toggle-tasks,
.task-list-action {
border: 0; border: 0;
padding-left: 0.25em;
padding-right: 0.25em;
} }
.task-list-name { .task-list-name {
flex-grow: 2; flex-grow: 2;
form {
display: flex;
flex-flow: row nowrap;
column-gap: 0.25em;
input[type="text"] {
flex-grow: 2;
}
}
} }
} }
@ -29,5 +42,10 @@
padding-left: 2em; padding-left: 2em;
width: 100%; width: 100%;
} }
.task-list-add-task {
padding: 0.25em 0.25em 0.25em 2em;
width: 100%;
}
} }
} }

View File

@ -23,7 +23,27 @@ class ChatRoomTaskList extends Model {
} }
async deleteItem () { async deleteItem () {
return this.collection.chatroom.taskManager.deleteItem(this) const tasks = this.getTasks()
return this.collection.chatroom.taskManager.deleteItems([...tasks, this])
}
async createTask (data) {
// Cloning data to avoid side effects:
data = Object.assign({}, data)
const name = data?.name
if (!name) { throw new Error('Missing name') }
data.list = this.get('id')
if (!data.order) {
data.order = 1 + Math.max(...this.getTasks().map(t => t.get('order') ?? 0))
}
console.log('Creating task ' + name + '...')
const chatroom = this.collection.chatroom
const tasksCollection = chatroom.tasks
await chatroom.taskManager.createItem(tasksCollection, data)
console.log('Task list ' + name + ' created.')
} }
} }

View File

@ -1,23 +1,28 @@
import { html } from 'lit' import { html } from 'lit'
import { repeat } from 'lit/directives/repeat.js' import { repeat } from 'lit/directives/repeat.js'
import { __ } from 'i18n' import { __ } from 'i18n'
import { tplMucAddTaskForm } from './muc-task'
export default function tplMucTaskList (el, tasklist) { export default function tplMucTaskList (el, tasklist) {
const tasks = tasklist.getTasks() const tasks = tasklist.getTasks()
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const i18nDelete = __(LOC_task_list_delete) const i18nDelete = __(LOC_task_list_delete)
// eslint-disable-next-line no-undef
const i18nCreateTask = __(LOC_task_create)
// eslint-disable-next-line no-undef
const i18nTaskListName = __(LOC_task_list_name)
return html` return html`
<div class="task-list-description"> <div class="task-list-description">
${el.collapsed ${el.collapsed
? html` ? html`
<button @click=${el.toggleTasks}> <button @click=${el.toggleTasks} class="task-list-toggle-tasks">
<converse-icon <converse-icon
color="var(--muc-toolbar-btn-color)" color="var(--muc-toolbar-btn-color)"
class="fa fa-angle-double-up" class="fa fa-angle-double-up"
size="1em"></converse-icon> size="1em"></converse-icon>
</button>` </button>`
: html` : html`
<button @click=${el.toggleTasks}> <button @click=${el.toggleTasks} class="task-list-toggle-tasks">
<converse-icon <converse-icon
color="var(--muc-toolbar-btn-color)" color="var(--muc-toolbar-btn-color)"
class="fa fa-angle-double-down" class="fa fa-angle-double-down"
@ -29,22 +34,31 @@ export default function tplMucTaskList (el, tasklist) {
<div class="task-list-name"> <div class="task-list-name">
${tasklist.get('name')} ${tasklist.get('name')}
</div> </div>
<a title="${__('Edit')}" <button class="task-list-action" title="${i18nCreateTask}" @click=${el.openAddTaskForm}>
<converse-icon class="fa fa-plus" size="1em"></converse-icon>
</button>
<button class="task-list-action" title="${__('Edit')}"
@click=${el.toggleEdit} @click=${el.toggleEdit}
> >
<converse-icon class="fa fa-edit" size="1em"></converse-icon> <converse-icon class="fa fa-edit" size="1em"></converse-icon>
</a> </button>
<a title="${i18nDelete}" <button class="task-list-action" title="${i18nDelete}"
@click=${el.deleteTaskList} @click=${el.deleteTaskList}
> >
<converse-icon class="fa fa-trash-alt" size="1em"></converse-icon> <converse-icon class="fa fa-trash-alt" size="1em"></converse-icon>
</a>` </button>`
: html` : html`
<div class="task-list-name"> <div class="task-list-name">
<form @submit=${el.saveTaskList}> <form @submit=${el.saveTaskList} class="converse-form">
<input type="text" name="name" autofocus value=${tasklist.get('name')} /> <input type="text" name="name"
placeholder="${__(i18nTaskListName)}"
class="form-control"
value="${tasklist.get('name')}"
/>
<input type="submit" class="btn btn-primary" value="${__('Ok')}" /> <input type="submit" class="btn btn-primary" value="${__('Ok')}" />
<input type="reset" class="btn btn-secondary" value="${__('Cancel')}" @click=${el.toggleEdit} /> <input type="button" class="btn btn-secondary button-cancel"
value="${__('Cancel')}" @click=${el.toggleEdit}
/>
</form> </form>
</div>` </div>`
} }
@ -56,5 +70,9 @@ export default function tplMucTaskList (el, tasklist) {
return html`<livechat-converse-muc-task .model=${task}></livechat-converse-muc-task>` return html`<livechat-converse-muc-task .model=${task}></livechat-converse-muc-task>`
}) })
} }
</div>` </div>
${!el.add_task_form_opened
? ''
: tplMucAddTaskForm(el, tasklist)
}`
} }

View File

@ -23,7 +23,7 @@ export default function tplMucTaskLists (el, tasklists) {
<div class="form-group"> <div class="form-group">
<label> <label>
${i18nCreateTaskList} ${i18nCreateTaskList}
<input type="text" value="" name="name" placeholder="${i18nTaskListName}" /> <input type="text" value="" class="form-control" name="name" placeholder="${i18nTaskListName}" />
</label> </label>
<input type="submit" value="${i18nAdd}" class="btn btn-primary" /> <input type="submit" value="${i18nAdd}" class="btn btn-primary" />
${!el.create_tasklist_error_message ${!el.create_tasklist_error_message

View File

@ -1,6 +1,7 @@
import { html } from 'lit' import { html } from 'lit'
import { __ } from 'i18n'
export default function tplMucTask (task) { export function tplMucTask (task) {
const done = task.get('done') const done = task.get('done')
return html` return html`
<div class=""> <div class="">
@ -15,3 +16,29 @@ export default function tplMucTask (task) {
${task.get('name')} ${task.get('name')}
</div>` </div>`
} }
export function tplMucAddTaskForm (tasklistEl, _tasklist) {
const i18nOk = __('Ok')
const i18nCancel = __('Cancel')
// eslint-disable-next-line no-undef
const i18nTaskName = __(LOC_task_name)
// eslint-disable-next-line no-undef
const i18nTaskDesc = __(LOC_task_description)
return html`
<form class="task-list-add-task converse-form" @submit=${tasklistEl.submitAddTask}>
<fieldset class="form-group">
<input type="text" name="name"
class="form-control" value=""
placeholder="${i18nTaskName}"
/>
<textarea class="form-control" name="description" placeholder="${i18nTaskDesc}"></textarea>
</fieldset>
<fieldset class="form-group">
<input type="submit" class="btn btn-primary" value="${i18nOk}" />
<input type="button" class="btn btn-secondary button-cancel"
value="${i18nCancel}" @click=${tasklistEl.closeAddTaskForm}
/>
</fieldset>
</form>`
}

View File

@ -61,7 +61,8 @@ function _initChatRoomTaskLists (mucModel) {
xmlns: XMLNS_TASK, xmlns: XMLNS_TASK,
collection: mucModel.tasks, collection: mucModel.tasks,
fields: { fields: {
name: String name: String,
description: String
}, },
attributes: { attributes: {
done: Boolean, done: Boolean,

View File

@ -120,6 +120,10 @@ export class PubSubManager {
console.log(`Node ${this.node} created on ${this.roomJID}.`) console.log(`Node ${this.node} created on ${this.roomJID}.`)
} }
async deleteItems (items) {
await Promise.all[items.map(item => this.deleteItem(item))]
}
async deleteItem (item) { async deleteItem (item) {
const id = item.get('id') const id = item.get('id')
if (!id) { if (!id) {

View File

@ -21,7 +21,10 @@ const locKeys = [
'task_list_create_error', 'task_list_create_error',
'task_list_name', 'task_list_name',
'task_list_delete', 'task_list_delete',
'task_list_delete_confirm' 'task_list_delete_confirm',
'task_create',
'task_name',
'task_description'
] ]
module.exports = locKeys module.exports = locKeys

View File

@ -441,3 +441,6 @@ task_list_create_error: 'Error when saving the task list'
task_list_name: 'Task list name' task_list_name: 'Task list name'
task_list_delete: 'Delete task list' task_list_delete: 'Delete task list'
task_list_delete_confirm: 'Are you sure you want to delete this task list?' task_list_delete_confirm: 'Are you sure you want to delete this task list?'
task_create: 'Create a new task'
task_name: 'Task name'
task_description: 'Description'