import { h, Component, createRef } from 'preact' import htm from 'htm' import { signal } from 'preact/signals' const html = htm.bind(h) export class ChecklistGroup { static sort(a, b) {//{{{ if (a.Order < b.Order) return -1 if (a.Order > b.Order) return 1 return 0 }//}}} constructor(data) {//{{{ Object.keys(data).forEach(key => { if (key == 'Items') this.items = data[key].map(itemData => { let item = new ChecklistItem(itemData) item.checklistGroup = this return item }) else this[key] = data[key] }) }//}}} addItem(label, okCallback) {//{{{ window._app.current.request('/node/checklist_group/item_add', { ChecklistGroupID: this.ID, Label: label, }) .then(json => { let item = new ChecklistItem(json.Item) item.checklistGroup = this this.items.push(item) okCallback() }) .catch(window._app.current.responseError) return }//}}} updateLabel(newLabel, okCallback, errCallback) {//{{{ window._app.current.request('/node/checklist_group/label', { ChecklistGroupID: this.ID, Label: newLabel, }) .then(okCallback) .catch(errCallback) }//}}} delete(okCallback, errCallback) {//{{{ window._app.current.request('/node/checklist_group/delete', { ChecklistGroupID: this.ID, }) .then(() => { okCallback() }) .catch(errCallback) }//}}} } export class ChecklistItem { static sort(a, b) {//{{{ if (a.Order < b.Order) return -1 if (a.Order > b.Order) return 1 return 0 }//}}} constructor(data) {//{{{ Object.keys(data).forEach(key => { this[key] = data[key] }) }//}}} updateState(newState, okCallback, errCallback) {//{{{ window._app.current.request('/node/checklist_item/state', { ChecklistItemID: this.ID, State: newState, }) .then(okCallback) .catch(errCallback) }//}}} updateLabel(newLabel, okCallback, errCallback) {//{{{ window._app.current.request('/node/checklist_item/label', { ChecklistItemID: this.ID, Label: newLabel, }) .then(okCallback) .catch(errCallback) }//}}} delete(okCallback, errCallback) {//{{{ window._app.current.request('/node/checklist_item/delete', { ChecklistItemID: this.ID, }) .then(() => { this.checklistGroup.items = this.checklistGroup.items.filter(item => item.ID != this.ID) okCallback() }) .catch(errCallback) }//}}} move(to, okCallback) {//{{{ window._app.current.request('/node/checklist_item/move', { ChecklistItemID: this.ID, AfterItemID: to.ID, }) .then(okCallback) .catch(_app.current.responseError) }//}}} } export class Checklist extends Component { constructor() {//{{{ super() this.edit = signal(false) this.dragItemSource = null this.dragItemTarget = null this.groupElements = {} this.state = { confirmDeletion: true, } window._checklist = this }//}}} render({ ui, groups }, { confirmDeletion }) {//{{{ this.groupElements = {} if (groups.length == 0 && !ui.node.value.ShowChecklist.value) return if (typeof groups.sort != 'function') groups = [] groups.sort(ChecklistGroup.sort) let groupElements = groups.map(group => { this.groupElements[group.ID] = createRef() return html`<${ChecklistGroupElement} ref=${this.groupElements[group.ID]} key="group-${group.ID}" ui=${this} group=${group} />` }) let edit = 'edit-list-gray.svg' let confirmDeletionEl = '' if (this.edit.value) { edit = 'edit-list.svg' confirmDeletionEl = html`
this.setState({ confirmDeletion: !confirmDeletion })} />
` } let addGroup = () => { if (this.edit.value) return html` this.addGroup()} />` } return html`

Checklist

this.toggleEdit()} /> <${addGroup} />
${confirmDeletionEl} ${groupElements}
` }//}}} toggleEdit() {//{{{ this.edit.value = !this.edit.value }//}}} addGroup() {//{{{ let label = prompt("Create a new group") if (label === null) return label = label.trim() if (label == '') return window._app.current.request('/node/checklist_group/add', { NodeID: window._app.current.nodeUI.current.node.value.ID, Label: label, }) .then(json => { let group = new ChecklistGroup(json.Group) this.props.groups.push(group) this.forceUpdate() }) .catch(window._app.current.responseError) return }//}}} dragTarget(target) {//{{{ if (this.dragItemTarget) this.dragItemTarget.setDragTarget(false) this.dragItemTarget = target target.setDragTarget(true) }//}}} dragReset() {//{{{ if (this.dragItemTarget) { this.dragItemTarget.setDragTarget(false) this.dragItemTarget = null } }//}}} } class ChecklistGroupElement extends Component { constructor() {//{{{ super() this.label = createRef() }//}}} render({ ui, group }) {//{{{ let items = ({ ui, group }) => group.items .sort(ChecklistItem.sort) .map(item => html`<${ChecklistItemElement} key="item-${item.ID}" ui=${ui} group=${this} item=${item} />`) let label = () => html`
this.editLabel()}>${group.Label}
` return html`
this.delete()} /> <${label} /> this.addItem()} />
<${items} ui=${ui} group=${group} />
` }//}}} addItem() {//{{{ let label = prompt("Create a new item") if (label === null) return label = label.trim() if (label == '') return this.props.group.addItem(label, () => { this.forceUpdate() }) }//}}} editLabel() {//{{{ let label = prompt('Edit label', this.props.group.Label) if (label === null) return label = label.trim() if (label == '') { alert(`A label can't be empty.`) return } this.label.current.classList.remove('error') this.props.group.updateLabel(label, () => { this.props.group.Label = label this.label.current.innerHTML = label this.label.current.classList.add('ok') this.forceUpdate() setTimeout(() => this.label.current.classList.remove('ok'), 500) }, () => { this.label.current.classList.add('error') }) }//}}} delete() {//{{{ if (this.props.ui.state.confirmDeletion) { if (!confirm(`Delete '${this.props.group.Label}'?`)) return } this.props.group.delete(() => { this.props.ui.props.groups = this.props.ui.props.groups.filter(g => g.ID != this.props.group.ID) this.props.ui.forceUpdate() }, err => { console.log(err) console.log('error') }) }//}}} } class ChecklistItemElement extends Component { constructor(props) {//{{{ super(props) this.state = { checked: props.item.Checked, dragTarget: false, } this.checkbox = createRef() this.label = createRef() }//}}} render({ ui, item }, { checked, dragTarget }) {//{{{ let checkbox = () => { if (ui.edit.value) return html`` else return html` this.update(evt.target.checked)} /> ` } return html`
this.delete()} /> <${checkbox} />
` }//}}} componentDidMount() {//{{{ this.base.addEventListener('dragstart', () => this.dragStart()) this.base.addEventListener('dragend', () => this.dragEnd()) this.base.addEventListener('dragenter', evt => this.dragEnter(evt)) }//}}} update(checked) {//{{{ this.setState({ checked }) this.checkbox.current.classList.remove('error') this.props.item.updateState(checked, () => { this.checkbox.current.classList.add('ok') setTimeout(() => this.checkbox.current.classList.remove('ok'), 500) }, () => { this.checkbox.current.classList.add('error') }) }//}}} editLabel() {//{{{ let label = prompt('Edit label', this.props.item.Label) if (label === null) return label = label.trim() if (label == '') { alert(`A label can't be empty.`) return } this.label.current.classList.remove('error') this.props.item.updateLabel(label, () => { this.props.item.Label = label this.label.current.innerHTML = label this.label.current.classList.add('ok') setTimeout(() => this.label.current.classList.remove('ok'), 500) }, () => { this.label.current.classList.add('error') }) }//}}} delete() {//{{{ if (this.props.ui.state.confirmDeletion) { if (!confirm(`Delete '${this.props.item.Label}'?`)) return } this.props.item.delete(() => { this.props.group.forceUpdate() }, err => { console.log(err) console.log('error') }) }//}}} setDragTarget(state) {//{{{ this.setState({ dragTarget: state }) }//}}} dragStart() {//{{{ // Shouldn't be needed, but in case the previous drag was bungled up, we reset. this.props.ui.dragReset() this.props.ui.dragItemSource = this }//}}} dragEnter(evt) {//{{{ evt.preventDefault() this.props.ui.dragTarget(this) }//}}} dragEnd() {//{{{ let groups = this.props.ui.props.groups let from = this.props.ui.dragItemSource.props.item let to = this.props.ui.dragItemTarget.props.item this.props.ui.dragReset() if (from.ID == to.ID) return let fromGroup = groups.find(g => g.ID == from.GroupID) let toGroup = groups.find(g => g.ID == to.GroupID) from.Order = to.Order from.GroupID = toGroup.ID toGroup.items.forEach(i => { if (i.ID == from.ID) return if (i.Order <= to.Order) i.Order-- }) if (fromGroup.ID != toGroup.ID) { fromGroup.items = fromGroup.items.filter(i => i.ID != from.ID) toGroup.items.push(from) } this.props.ui.groupElements[fromGroup.ID].current.forceUpdate() this.props.ui.groupElements[toGroup.ID].current.forceUpdate() from.move(to, ()=>console.log('ok')) }//}}} } // vim: foldmethod=marker