From a87f025ea0b90c6f895bbf92c7ff0562fa734d12 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Wed, 24 Jul 2024 15:57:02 +0200 Subject: [PATCH] implement task version history --- dist/index.js | 73 ++++++++++++++++++++++----- dist/styles.css | 6 ++- src/Model/Files/boardModel.ts | 22 +++++++- src/View/Components/option.tsx | 9 ++++ src/View/Modals/taskSettingsModal.tsx | 16 ++++++ src/View/translations.ts | 8 +-- src/ViewModel/Pages/boardViewModel.ts | 2 +- src/ViewModel/Pages/taskViewModel.ts | 36 ++++++++++++- 8 files changed, 152 insertions(+), 20 deletions(-) diff --git a/dist/index.js b/dist/index.js index b44c1247..e4786bbc 100644 --- a/dist/index.js +++ b/dist/index.js @@ -686,10 +686,22 @@ const fileIds = this.storageModel.list(taskContainerPath); return fileIds; }; - getTaskFileContent = (taskId) => { + listTaskVersionIds = (taskId) => { + const versionIds = this.fileModel.listFileContentIds(taskId); + return versionIds; + }; + getLatestTaskFileContent = (taskId) => { const taskFileContentOrNull = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); return taskFileContentOrNull; }; + getSpecificTaskFileContent = (taskId, versionId) => { + const taskFileContentOrNull = this.fileModel.getFileContent( + taskId, + versionId, + TaskFileContentReference + ); + return taskFileContentOrNull; + }; deleteTask = (boardId, taskId) => { const taskFilePath = this.getTaskFilePath(taskId); const taskReferencePath = this.getTaskReferencePath( @@ -1468,6 +1480,8 @@ priority = new State(""); date = new State(""); time = new State(""); + selectedVersionId = new State(""); + versionIds = new ListState(); // view open = () => { this.boardViewModel.selectTask(this); @@ -1506,7 +1520,25 @@ this.boardViewModel.removeTaskFromList(this.task.fileId); }; // load + loadVersionIds = () => { + const versionIds = this.boardModel.listTaskVersionIds( + this.task.fileId + ); + const sortedVersionIds = versionIds.sort(localeCompare).reverse(); + this.versionIds.clear(); + this.versionIds.add(...sortedVersionIds); + }; + switchVersion = (versionId) => { + const taskFileContent = this.boardModel.getSpecificTaskFileContent(this.task.fileId, versionId); + if (taskFileContent == null) return; + this.task = taskFileContent; + this.loadTaskData(); + }; loadAllData = () => { + this.loadTaskData(); + this.loadVersionIds(); + }; + loadTaskData = () => { this.name.value = this.task.name; this.description.value = this.task.description ?? ""; this.category.value = this.task.category ?? ""; @@ -1514,6 +1546,7 @@ this.priority.value = this.task.priority ?? ""; this.date.value = this.task.date ?? ""; this.time.value = this.task.time ?? ""; + this.selectedVersionId.value = this.task.fileContentId; }; // init constructor(boardModel, boardViewModel, taskFileContent) { @@ -1521,6 +1554,9 @@ this.boardViewModel = boardViewModel; this.task = taskFileContent; this.loadAllData(); + this.selectedVersionId.subscribeSilent((selectedVersionId) => { + this.switchVersion(selectedVersionId); + }); } }; @@ -1653,7 +1689,7 @@ this.boardInfo.fileId ); for (const taskId of taskIds) { - const taskFileContent = this.boardModel.getTaskFileContent(taskId); + const taskFileContent = this.boardModel.getLatestTaskFileContent(taskId); if (taskFileContent == null) continue; const taskViewModel = new TaskViewModel( this.boardModel, @@ -1992,7 +2028,8 @@ closeButton: "Close", confirmButton: "Confirm", saveButton: "Save", - setButton: "Set" + setButton: "Set", + fileVersionLabel: "Version" }, regional: { weekdays: { @@ -2394,9 +2431,29 @@ return RibbonButton(label, icon, isSelected, select); } + // src/View/Components/option.tsx + function Option(text, value, selectedOnCreate) { + return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text); + } + var StringToOption = (string) => { + return Option(string, string, false); + }; + var VersionIdToOption = (versionId) => { + const [date, rest] = versionId.split("T"); + const [time] = rest.split("."); + const readableName = `${date} ${time}`; + return Option(readableName, versionId, false); + }; + // src/View/Modals/taskSettingsModal.tsx function TaskSettingsModal(taskViewModel) { - return /* @__PURE__ */ createElement("div", { class: "modal", open: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDescriptionLabel), /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement("div", { class: "modal", open: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.general.fileVersionLabel), /* @__PURE__ */ createElement( + "select", + { + "bind:value": taskViewModel.selectedVersionId, + "children:append": [taskViewModel.versionIds, VersionIdToOption] + } + ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDescriptionLabel), /* @__PURE__ */ createElement( "textarea", { rows: "10", @@ -3104,14 +3161,6 @@ } }; - // src/View/Components/option.tsx - function Option(text, value, selectedOnCreate) { - return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text); - } - var StringToOption = (string) => { - return Option(string, string, false); - }; - // src/View/Components/chatEntry.tsx function ChatEntry(chatViewModel) { const view = /* @__PURE__ */ createElement( diff --git a/dist/styles.css b/dist/styles.css index a1c3a2c8..22bdc72a 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -677,6 +677,10 @@ label.tile:has(> span.icon) :is(input, select, textarea) { padding-left: 0px; } +select { + padding-right: 2.5rem; +} + .padding-right { padding-right: var(--control-padding) !important; } @@ -1230,4 +1234,4 @@ input[type="checkbox"], font-style: normal; font-weight: 300; src: url("Icons/material-icons.woff") format("woff"); -} \ No newline at end of file +} diff --git a/src/Model/Files/boardModel.ts b/src/Model/Files/boardModel.ts index 28bbb1d9..64c40c4e 100644 --- a/src/Model/Files/boardModel.ts +++ b/src/Model/Files/boardModel.ts @@ -5,12 +5,12 @@ import { checkMatchesObjectStructure, } from "../Utility/typeSafety"; import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; +import { v4, version } from "uuid"; import ChatModel from "../Chat/chatModel"; import { Color } from "../../colors"; import { HandlerManager } from "../Utility/utility"; import StorageModel from "../Global/storageModel"; -import { v4 } from "uuid"; export default class BoardModel { storageModel: StorageModel; @@ -169,12 +169,30 @@ export default class BoardModel { return fileIds; }; - getTaskFileContent = (taskId: string): TaskFileContent | null => { + listTaskVersionIds = (taskId: string): string[] => { + const versionIds: string[] = this.fileModel.listFileContentIds(taskId); + return versionIds; + }; + + getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { const taskFileContentOrNull: TaskFileContent | null = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); return taskFileContentOrNull; }; + getSpecificTaskFileContent = ( + taskId: string, + versionId: string + ): TaskFileContent | null => { + const taskFileContentOrNull: TaskFileContent | null = + this.fileModel.getFileContent( + taskId, + versionId, + TaskFileContentReference + ); + return taskFileContentOrNull; + }; + deleteTask = (boardId: string, taskId: string): void => { const taskFilePath: string[] = this.getTaskFilePath(taskId); const taskReferencePath: string[] = this.getTaskReferencePath( diff --git a/src/View/Components/option.tsx b/src/View/Components/option.tsx index 4a98ad00..5410e0ee 100644 --- a/src/View/Components/option.tsx +++ b/src/View/Components/option.tsx @@ -13,3 +13,12 @@ export const StringToOption: React.StateItemConverter = ( ) => { return Option(string, string, false); }; + +export const VersionIdToOption: React.StateItemConverter = ( + versionId: string +) => { + const [date, rest] = versionId.split("T"); + const [time] = rest.split("."); + const readableName = `${date} ${time}`; + return Option(readableName, versionId, false); +}; diff --git a/src/View/Modals/taskSettingsModal.tsx b/src/View/Modals/taskSettingsModal.tsx index 5b164d82..f9b48b6c 100644 --- a/src/View/Modals/taskSettingsModal.tsx +++ b/src/View/Modals/taskSettingsModal.tsx @@ -1,5 +1,7 @@ import * as React from "bloatless-react"; +import { StringToOption, VersionIdToOption } from "../Components/option"; + import { DangerousActionButton } from "../Components/dangerousActionButton"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import { translations } from "../translations"; @@ -11,6 +13,20 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {

{translations.chatPage.task.taskSettingsHeadline}

+ + +
+