diff --git a/dist/index.css b/dist/index.css index 220c1c6c..a9738601 100644 --- a/dist/index.css +++ b/dist/index.css @@ -293,6 +293,7 @@ body[showing-chat] #chat-page-wrapper { flex-direction: column; scroll-snap-align: center; min-width: 100%; + overflow: hidden; } #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side), #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side) .content { @@ -303,6 +304,8 @@ body[showing-chat] #chat-page-wrapper { min-width: unset; } #chat-page > div > #main > div > .pane-wrapper.side { + width: 18rem; + min-width: 18rem; max-width: 18rem; } } @@ -566,28 +569,42 @@ body[showing-chat] #chat-page-wrapper { } /* src/View/ChatPages/taskPage.css */ -#task-page .task-grid { +#chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { + width: 100%; + max-width: 100%; + padding: 0px; +} +#chat-page > div > #main > #task-page .task-grid { display: flex; flex-direction: column; gap: var(--gap-between-tiles); + padding: var(--content-padding); +} +#chat-page > div > #main > #task-page .kanban-board-wrapper { + display: flex; + gap: var(--large-gap); + width: 100%; + height: 100%; + overflow: auto; + padding: var(--content-padding); } @media (min-width: 640px) { - #task-page .task-grid { + #chat-page > div > #main > #task-page .task-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); } } @media (max-width: 1024px) { - #task-page #board-list { + #chat-page > div > #main > #task-page #board-list { display: false; } - #task-page #board-content { + #chat-page > div > #main > #task-page #board-content { display: none; } - #task-page[isshowingboard] #board-list { + #chat-page > div > #main > #task-page[isshowingboard] #board-list { display: none; } - #task-page[isshowingboard] #board-content { + #chat-page > div > #main > #task-page[isshowingboard] #board-content { display: flex; } } diff --git a/dist/index.js b/dist/index.js index c34f47ca..48bfda06 100644 --- a/dist/index.js +++ b/dist/index.js @@ -330,6 +330,45 @@ } }; }; + function checkDoesObjectMatchReference(reference, stringEntryObject) { + reference_entry_loop: for (const referenceEntry of Object.entries( + reference + )) { + const [referenceKey, referenceValue] = referenceEntry; + const stringEntryObjectValue = stringEntryObject[referenceKey]; + if (referenceValue == void 0) return false; + if (referenceValue[0] == "-") { + const strippedReferenceValue = referenceValue.substring(1); + if (strippedReferenceValue == "" && stringEntryObjectValue != void 0 && stringEntryObjectValue != "") { + return false; + } + if (stringEntryObjectValue == strippedReferenceValue) { + return false; + } + } else { + if (referenceValue == "" && (stringEntryObjectValue == void 0 || stringEntryObjectValue == "")) { + return false; + } else if (referenceValue == "") { + continue reference_entry_loop; + } + if (stringEntryObjectValue != referenceValue) { + return false; + } + } + } + return true; + } + function collectObjectValuesForKey(key, converter, objects) { + const values = /* @__PURE__ */ new Set(); + for (const object of objects) { + const stringEntryObject = converter(object); + const stringEntryObjectValue = stringEntryObject[key]; + if (stringEntryObjectValue == void 0 || stringEntryObjectValue == "") + continue; + values.add(stringEntryObjectValue); + } + return [...values.values()].sort(localeCompare); + } var IndexManager = class { itemToString; sortedStrings = []; @@ -2145,7 +2184,9 @@ taskDescriptionLabel: "Description", taskDateLabel: "Date", taskTimeLabel: "Time", - deleteTaskButton: "Delete task" + deleteTaskButton: "Delete task", + /// + renameCategoryInputPlaceholder: "Rename category," } } }; @@ -2404,9 +2445,166 @@ )))))); } + // src/View/Components/filteredList.tsx + function FilteredList(reference, stringEntryObjectConverter, objects, viewBuilder) { + const matchingObjects = new ListState(); + objects.handleAddition((newObject) => { + const doesMatch = checkDoesObjectMatchReference( + reference, + stringEntryObjectConverter(newObject) + ); + if (doesMatch == false) return; + matchingObjects.add(newObject); + objects.handleRemoval(newObject, () => { + matchingObjects.remove(newObject); + }); + }); + return viewBuilder(matchingObjects); + } + + // src/View/Components/propertyList.tsx + function PropertyValueList(propertyKey, stringEntryObjectConverter, objects, viewBuilder) { + const propertyValues = new ListState(); + function collectValues() { + propertyValues.clear(); + const values = collectObjectValuesForKey( + propertyKey, + stringEntryObjectConverter, + [...objects.value.values()] + ); + propertyValues.add(...values); + } + objects.subscribe(() => { + collectValues(); + }); + return viewBuilder(propertyValues); + } + + // src/ViewModel/Pages/taskPropertyBulkChangeViewModel.ts + var TaskPropertyBulkChangeViewModel = class { + taskViewModels; + // state + inputValue = new State(""); + // guards + cannotSet; + // methods + set = () => { + if (this.cannotSet.value == true) return; + this.taskViewModels.value.forEach((taskViewModel) => { + this.setValue(this.inputValue.value, taskViewModel); + }); + }; + setValue; + // init + constructor(taskViewModels, valueSetter, initialValue) { + this.taskViewModels = taskViewModels; + this.inputValue.value = initialValue; + this.setValue = valueSetter; + this.cannotSet = createProxyState( + [this.inputValue], + () => this.inputValue.value == "" || this.inputValue.value == initialValue + ); + } + }; + var TaskCategoryBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel { + constructor(taskViewModels, initialValue) { + super( + taskViewModels, + (newCategory, taskViewModel) => { + taskViewModel.category.value = newCategory; + taskViewModel.save(); + }, + initialValue + ); + } + }; + + // src/View/Components/taskEntry.tsx + function TaskEntry(taskViewModel) { + const details = { + description: taskViewModel.description.value || "---", + priority_high: taskViewModel.priority.value || "---", + category: taskViewModel.category.value || "---", + clock_loader_40: taskViewModel.status.value || "---", + calendar_month: taskViewModel.date.value || "---", + schedule: taskViewModel.time.value || "---" + }; + const view = /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": taskViewModel.open }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { "subscribe:innerText": taskViewModel.name }), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + "div", + { + class: "grid secondary", + style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem" + }, + ...Object.entries(details).map((entry) => /* @__PURE__ */ createElement( + "span", + { + class: "flex-row align-center width-100 flex-no clip", + style: "gap: 1rem" + }, + /* @__PURE__ */ createElement("span", { class: "icon", style: "font-size: 1.1rem" }, entry[0]), + /* @__PURE__ */ createElement("span", { class: "ellipsis" }, entry[1]) + )) + ))); + taskViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + } + var TaskViewModelToEntry = (taskViewModel) => { + return TaskEntry(taskViewModel); + }; + // src/View/ChatPages/boardKanbanPage.tsx function BoardKanbanPage(boardViewModel) { - return /* @__PURE__ */ createElement("div", null, "Kanban"); + const categoryNameConverter = (categoryName) => { + return KanbanBoard(categoryName, boardViewModel); + }; + return PropertyValueList( + "category", + (taskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + (categories) => { + return /* @__PURE__ */ createElement( + "div", + { + class: "kanban-board-wrapper", + "children:append": [categories, categoryNameConverter] + } + ); + } + ); + } + function KanbanBoard(categoryName, boardViewModel) { + return FilteredList( + { category: categoryName }, + (taskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + (taskViewModels) => { + const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + return /* @__PURE__ */ createElement("div", { class: "flex-column flex-no", style: "width: 18rem; min-width: 18rem" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, + "bind:value": viewModel.inputValue, + "on:enter": viewModel.set + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": viewModel.set, + "toggle:disabled": viewModel.cannotSet + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "check") + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap", + "children:append": [taskViewModels, TaskViewModelToEntry] + } + )); + } + ); } // src/View/Modals/boardSettingsModal.tsx @@ -2471,41 +2669,6 @@ ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": taskViewModel.close }, translations.general.closeButton), /* @__PURE__ */ createElement("button", { class: "flex primary", "on:click": taskViewModel.closeAndSave }, translations.general.saveButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); } - // src/View/Components/taskEntry.tsx - function TaskEntry(taskViewModel) { - const details = { - description: taskViewModel.description.value || "---", - priority_high: taskViewModel.priority.value || "---", - category: taskViewModel.category.value || "---", - clock_loader_40: taskViewModel.status.value || "---", - calendar_month: taskViewModel.date.value || "---", - schedule: taskViewModel.time.value || "---" - }; - const view = /* @__PURE__ */ createElement("button", { class: "tile", "on:click": taskViewModel.open }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { "subscribe:innerText": taskViewModel.name }), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "grid secondary", - style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem" - }, - ...Object.entries(details).map((entry) => /* @__PURE__ */ createElement( - "span", - { - class: "flex-row align-center width-100 flex-no clip", - style: "gap: 1rem" - }, - /* @__PURE__ */ createElement("span", { class: "icon", style: "font-size: 1.1rem" }, entry[0]), - /* @__PURE__ */ createElement("span", { class: "ellipsis" }, entry[1]) - )) - ))); - taskViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - var TaskViewModelToEntry = (taskViewModel) => { - return TaskEntry(taskViewModel); - }; - // src/View/ChatPages/boardPage.tsx function BoardPage(boardViewModel) { boardViewModel.loadData(); @@ -2588,7 +2751,7 @@ "on:click": boardViewModel.createTask }, /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__PURE__ */ createElement("div", { class: "content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); + ))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); } // src/View/Components/boardEntry.tsx diff --git a/src/View/ChatPages/boardKanbanPage.tsx b/src/View/ChatPages/boardKanbanPage.tsx index c0dfee26..f73e27c1 100644 --- a/src/View/ChatPages/boardKanbanPage.tsx +++ b/src/View/ChatPages/boardKanbanPage.tsx @@ -1,14 +1,71 @@ import * as React from "bloatless-react"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; +import { FilteredList } from "../Components/filteredList"; import { PropertyValueList } from "../Components/propertyList"; +import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Pages/taskPropertyBulkChangeViewModel"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; +import { TaskViewModelToEntry } from "../Components/taskEntry"; +import { translations } from "../translations"; export function BoardKanbanPage(boardViewModel: BoardViewModel) { + const categoryNameConverter: React.StateItemConverter = ( + categoryName: string + ) => { + return KanbanBoard(categoryName, boardViewModel); + }; + return PropertyValueList( "category", (taskViewModel: TaskViewModel) => taskViewModel.task, boardViewModel.taskViewModels, - () =>
+ (categories: React.ListState) => { + return ( +
+ ); + } + ); +} + +function KanbanBoard(categoryName: string, boardViewModel: BoardViewModel) { + return FilteredList( + { category: categoryName }, + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + (taskViewModels: React.ListState) => { + const viewModel: TaskCategoryBulkChangeViewModel = + new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + + return ( +
+
+ + +
+ +
+ +
+
+ ); + } ); } diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index db29ce78..c55bb12b 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -103,7 +103,7 @@ export function BoardPage(boardViewModel: BoardViewModel) { -
+
{BoardSettingsModal(boardViewModel)}
diff --git a/src/View/ChatPages/taskPage.css b/src/View/ChatPages/taskPage.css index 74332b8a..0c620180 100644 --- a/src/View/ChatPages/taskPage.css +++ b/src/View/ChatPages/taskPage.css @@ -1,30 +1,51 @@ -#task-page .task-grid { +#chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { + width: 100%; + max-width: 100%; + + padding: 0px; +} + +#chat-page > div > #main > #task-page .task-grid { display: flex; flex-direction: column; gap: var(--gap-between-tiles); + + padding: var(--content-padding); +} + +#chat-page > div > #main > #task-page .kanban-board-wrapper { + display: flex; + gap: var(--large-gap); + + width: 100%; + height: 100%; + + overflow: auto; + + padding: var(--content-padding); } @media (min-width: 640px) { - #task-page .task-grid { + #chat-page > div > #main > #task-page .task-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); } } @media (max-width: 1024px) { - #task-page #board-list { + #chat-page > div > #main > #task-page #board-list { display: false; } - #task-page #board-content { + #chat-page > div > #main > #task-page #board-content { display: none; } - #task-page[isshowingboard] #board-list { + #chat-page > div > #main > #task-page[isshowingboard] #board-list { display: none; } - #task-page[isshowingboard] #board-content { + #chat-page > div > #main > #task-page[isshowingboard] #board-content { display: flex; } } diff --git a/src/View/Components/taskEntry.tsx b/src/View/Components/taskEntry.tsx index 18b12f03..bc3a5316 100644 --- a/src/View/Components/taskEntry.tsx +++ b/src/View/Components/taskEntry.tsx @@ -13,7 +13,7 @@ export function TaskEntry(taskViewModel: TaskViewModel) { }; const view = ( -