implement task version history

This commit is contained in:
marlon-erler
2024-07-24 15:57:02 +02:00
parent 47fa0938e9
commit a87f025ea0
8 changed files with 152 additions and 20 deletions
+61 -12
View File
@@ -686,10 +686,22 @@
const fileIds = this.storageModel.list(taskContainerPath); const fileIds = this.storageModel.list(taskContainerPath);
return fileIds; return fileIds;
}; };
getTaskFileContent = (taskId) => { listTaskVersionIds = (taskId) => {
const versionIds = this.fileModel.listFileContentIds(taskId);
return versionIds;
};
getLatestTaskFileContent = (taskId) => {
const taskFileContentOrNull = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); const taskFileContentOrNull = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference);
return taskFileContentOrNull; return taskFileContentOrNull;
}; };
getSpecificTaskFileContent = (taskId, versionId) => {
const taskFileContentOrNull = this.fileModel.getFileContent(
taskId,
versionId,
TaskFileContentReference
);
return taskFileContentOrNull;
};
deleteTask = (boardId, taskId) => { deleteTask = (boardId, taskId) => {
const taskFilePath = this.getTaskFilePath(taskId); const taskFilePath = this.getTaskFilePath(taskId);
const taskReferencePath = this.getTaskReferencePath( const taskReferencePath = this.getTaskReferencePath(
@@ -1468,6 +1480,8 @@
priority = new State(""); priority = new State("");
date = new State(""); date = new State("");
time = new State(""); time = new State("");
selectedVersionId = new State("");
versionIds = new ListState();
// view // view
open = () => { open = () => {
this.boardViewModel.selectTask(this); this.boardViewModel.selectTask(this);
@@ -1506,7 +1520,25 @@
this.boardViewModel.removeTaskFromList(this.task.fileId); this.boardViewModel.removeTaskFromList(this.task.fileId);
}; };
// load // 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 = () => { loadAllData = () => {
this.loadTaskData();
this.loadVersionIds();
};
loadTaskData = () => {
this.name.value = this.task.name; this.name.value = this.task.name;
this.description.value = this.task.description ?? ""; this.description.value = this.task.description ?? "";
this.category.value = this.task.category ?? ""; this.category.value = this.task.category ?? "";
@@ -1514,6 +1546,7 @@
this.priority.value = this.task.priority ?? ""; this.priority.value = this.task.priority ?? "";
this.date.value = this.task.date ?? ""; this.date.value = this.task.date ?? "";
this.time.value = this.task.time ?? ""; this.time.value = this.task.time ?? "";
this.selectedVersionId.value = this.task.fileContentId;
}; };
// init // init
constructor(boardModel, boardViewModel, taskFileContent) { constructor(boardModel, boardViewModel, taskFileContent) {
@@ -1521,6 +1554,9 @@
this.boardViewModel = boardViewModel; this.boardViewModel = boardViewModel;
this.task = taskFileContent; this.task = taskFileContent;
this.loadAllData(); this.loadAllData();
this.selectedVersionId.subscribeSilent((selectedVersionId) => {
this.switchVersion(selectedVersionId);
});
} }
}; };
@@ -1653,7 +1689,7 @@
this.boardInfo.fileId this.boardInfo.fileId
); );
for (const taskId of taskIds) { for (const taskId of taskIds) {
const taskFileContent = this.boardModel.getTaskFileContent(taskId); const taskFileContent = this.boardModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue; if (taskFileContent == null) continue;
const taskViewModel = new TaskViewModel( const taskViewModel = new TaskViewModel(
this.boardModel, this.boardModel,
@@ -1992,7 +2028,8 @@
closeButton: "Close", closeButton: "Close",
confirmButton: "Confirm", confirmButton: "Confirm",
saveButton: "Save", saveButton: "Save",
setButton: "Set" setButton: "Set",
fileVersionLabel: "Version"
}, },
regional: { regional: {
weekdays: { weekdays: {
@@ -2394,9 +2431,29 @@
return RibbonButton(label, icon, isSelected, select); 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 // src/View/Modals/taskSettingsModal.tsx
function TaskSettingsModal(taskViewModel) { 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", "textarea",
{ {
rows: "10", 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 // src/View/Components/chatEntry.tsx
function ChatEntry(chatViewModel) { function ChatEntry(chatViewModel) {
const view = /* @__PURE__ */ createElement( const view = /* @__PURE__ */ createElement(
+4
View File
@@ -677,6 +677,10 @@ label.tile:has(> span.icon) :is(input, select, textarea) {
padding-left: 0px; padding-left: 0px;
} }
select {
padding-right: 2.5rem;
}
.padding-right { .padding-right {
padding-right: var(--control-padding) !important; padding-right: var(--control-padding) !important;
} }
+20 -2
View File
@@ -5,12 +5,12 @@ import {
checkMatchesObjectStructure, checkMatchesObjectStructure,
} from "../Utility/typeSafety"; } from "../Utility/typeSafety";
import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; import FileModel, { FileContent, FileModelSubPath } from "./fileModel";
import { v4, version } from "uuid";
import ChatModel from "../Chat/chatModel"; import ChatModel from "../Chat/chatModel";
import { Color } from "../../colors"; import { Color } from "../../colors";
import { HandlerManager } from "../Utility/utility"; import { HandlerManager } from "../Utility/utility";
import StorageModel from "../Global/storageModel"; import StorageModel from "../Global/storageModel";
import { v4 } from "uuid";
export default class BoardModel { export default class BoardModel {
storageModel: StorageModel; storageModel: StorageModel;
@@ -169,12 +169,30 @@ export default class BoardModel {
return fileIds; 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 = const taskFileContentOrNull: TaskFileContent | null =
this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); this.fileModel.getLatestFileContent(taskId, TaskFileContentReference);
return taskFileContentOrNull; 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 => { deleteTask = (boardId: string, taskId: string): void => {
const taskFilePath: string[] = this.getTaskFilePath(taskId); const taskFilePath: string[] = this.getTaskFilePath(taskId);
const taskReferencePath: string[] = this.getTaskReferencePath( const taskReferencePath: string[] = this.getTaskReferencePath(
+9
View File
@@ -13,3 +13,12 @@ export const StringToOption: React.StateItemConverter<string> = (
) => { ) => {
return Option(string, string, false); return Option(string, string, false);
}; };
export const VersionIdToOption: React.StateItemConverter<string> = (
versionId: string
) => {
const [date, rest] = versionId.split("T");
const [time] = rest.split(".");
const readableName = `${date} ${time}`;
return Option(readableName, versionId, false);
};
+16
View File
@@ -1,5 +1,7 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import { StringToOption, VersionIdToOption } from "../Components/option";
import { DangerousActionButton } from "../Components/dangerousActionButton"; import { DangerousActionButton } from "../Components/dangerousActionButton";
import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
@@ -11,6 +13,20 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<main> <main>
<h2>{translations.chatPage.task.taskSettingsHeadline}</h2> <h2>{translations.chatPage.task.taskSettingsHeadline}</h2>
<label class="tile flex-no">
<span class="icon">history</span>
<div>
<span>{translations.general.fileVersionLabel}</span>
<select
bind:value={taskViewModel.selectedVersionId}
children:append={[taskViewModel.versionIds, VersionIdToOption]}
></select>
<span class="icon">arrow_drop_down</span>
</div>
</label>
<hr></hr>
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">label</span> <span class="icon">label</span>
<div> <div>
+2
View File
@@ -8,6 +8,8 @@ const englishTranslations = {
confirmButton: "Confirm", confirmButton: "Confirm",
saveButton: "Save", saveButton: "Save",
setButton: "Set", setButton: "Set",
fileVersionLabel: "Version",
}, },
regional: { regional: {
+1 -1
View File
@@ -175,7 +175,7 @@ export default class BoardViewModel {
); );
for (const taskId of taskIds) { for (const taskId of taskIds) {
const taskFileContent: TaskFileContent | null = const taskFileContent: TaskFileContent | null =
this.boardModel.getTaskFileContent(taskId); this.boardModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue; if (taskFileContent == null) continue;
const taskViewModel: TaskViewModel = new TaskViewModel( const taskViewModel: TaskViewModel = new TaskViewModel(
+35 -1
View File
@@ -1,9 +1,9 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import BoardModel, { TaskFileContent } from "../../Model/Files/boardModel"; import BoardModel, { TaskFileContent } from "../../Model/Files/boardModel";
import { localeCompare, padZero } from "../../Model/Utility/utility";
import BoardViewModel from "./boardViewModel"; import BoardViewModel from "./boardViewModel";
import { padZero } from "../../Model/Utility/utility";
export default class TaskViewModel { export default class TaskViewModel {
boardModel: BoardModel; boardModel: BoardModel;
@@ -49,6 +49,9 @@ export default class TaskViewModel {
date: React.State<string> = new React.State(""); date: React.State<string> = new React.State("");
time: React.State<string> = new React.State(""); time: React.State<string> = new React.State("");
selectedVersionId: React.State<string> = new React.State("");
versionIds: React.ListState<string> = new React.ListState();
// view // view
open = (): void => { open = (): void => {
this.boardViewModel.selectTask(this); this.boardViewModel.selectTask(this);
@@ -96,7 +99,30 @@ export default class TaskViewModel {
}; };
// load // load
loadVersionIds = (): void => {
const versionIds: string[] = this.boardModel.listTaskVersionIds(
this.task.fileId
);
const sortedVersionIds = versionIds.sort(localeCompare).reverse();
this.versionIds.clear();
this.versionIds.add(...sortedVersionIds);
};
switchVersion = (versionId: string): void => {
const taskFileContent: TaskFileContent | null =
this.boardModel.getSpecificTaskFileContent(this.task.fileId, versionId);
if (taskFileContent == null) return;
this.task = taskFileContent;
this.loadTaskData();
};
loadAllData = (): void => { loadAllData = (): void => {
this.loadTaskData();
this.loadVersionIds();
};
loadTaskData = (): void => {
this.name.value = this.task.name; this.name.value = this.task.name;
this.description.value = this.task.description ?? ""; this.description.value = this.task.description ?? "";
@@ -106,6 +132,8 @@ export default class TaskViewModel {
this.date.value = this.task.date ?? ""; this.date.value = this.task.date ?? "";
this.time.value = this.task.time ?? ""; this.time.value = this.task.time ?? "";
this.selectedVersionId.value = this.task.fileContentId;
}; };
// init // init
@@ -119,6 +147,12 @@ export default class TaskViewModel {
this.task = taskFileContent; this.task = taskFileContent;
// load
this.loadAllData(); this.loadAllData();
// subscriptions
this.selectedVersionId.subscribeSilent((selectedVersionId) => {
this.switchVersion(selectedVersionId);
});
} }
} }