finish object version handling

This commit is contained in:
marlon-erler
2024-07-11 15:08:25 +02:00
parent 19fe4274fc
commit f585729133
7 changed files with 166 additions and 39 deletions
BIN
View File
Binary file not shown.
+69 -12
View File
@@ -76,6 +76,9 @@
this._bindings.add(fn); this._bindings.add(fn);
fn(this._value); fn(this._value);
} }
subscribeSilent(fn) {
this._bindings.add(fn);
}
// stringify // stringify
toString() { toString() {
return JSON.stringify(this._value); return JSON.stringify(this._value);
@@ -171,6 +174,9 @@
); );
return proxyState; return proxyState;
} }
function bulkSubscribe(statesToSubscibe, fn) {
statesToSubscibe.forEach((state) => state.subscribeSilent(fn));
}
function persistState(localStorageKey, state) { function persistState(localStorageKey, state) {
state.subscribe(() => { state.subscribe(() => {
const stringifiedValue = state.toString(); const stringifiedValue = state.toString();
@@ -619,7 +625,7 @@
id: UUID(), id: UUID(),
title, title,
isoDateLastEdited: (/* @__PURE__ */ new Date()).toISOString(), isoDateLastEdited: (/* @__PURE__ */ new Date()).toISOString(),
contentVersions: [] contentVersions: [this.createObjectContent()]
}; };
}; };
addObject = (messageObject) => { addObject = (messageObject) => {
@@ -628,7 +634,12 @@
updateObject = (id, contents) => { updateObject = (id, contents) => {
const messageObject = this.objects.value.get(id); const messageObject = this.objects.value.get(id);
if (!messageObject) return; if (!messageObject) return;
messageObject.contentVersions.push(contents); if (contents) {
messageObject.contentVersions.push(contents);
messageObject.contentVersions.sort(
(a, b) => a.isoDateVersionCreated > b.isoDateVersionCreated ? 0 : 1
);
}
this.sendObject(messageObject); this.sendObject(messageObject);
}; };
addObjectAndSend = (messageObject) => { addObjectAndSend = (messageObject) => {
@@ -652,12 +663,17 @@
this.objects.clear(); this.objects.clear();
}; };
getLatestObjectContent = (messageObject) => { getLatestObjectContent = (messageObject) => {
return this.getObjectContentFromIndex(messageObject, 0);
};
getObjectContentFromIndex = (messageObject, index) => {
const versions = messageObject.contentVersions; const versions = messageObject.contentVersions;
if (versions.length == 0) return versions[index] ?? this.createObjectContent();
return { };
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString() createObjectContent = () => {
}; return {
return versions[versions.length - 1]; id: UUID(),
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString()
};
}; };
// channel // channel
setChannel = () => { setChannel = () => {
@@ -980,6 +996,8 @@
objectTitle: "Object title", objectTitle: "Object title",
objectTitlePlaceholder: "My object", objectTitlePlaceholder: "My object",
objectVersion: "Object version", objectVersion: "Object version",
noteContent: "Note",
noteContentPlaceholder: "Take a note...",
resendObjects: "Resend all objects", resendObjects: "Resend all objects",
deleteObject: "Delete object" deleteObject: "Delete object"
}; };
@@ -1192,27 +1210,66 @@
// src/Views/Objects/objectDetailModal.tsx // src/Views/Objects/objectDetailModal.tsx
function ObjectDetailModal(chat, messageObject, isPresented) { function ObjectDetailModal(chat, messageObject, isPresented) {
const editingTitle = new State(messageObject.title); const editingTitle = new State(messageObject.title);
const selectedMessageObjectIndex = new State(0);
selectedMessageObjectIndex.subscribe(console.log);
const selectedMessageObject = createProxyState(
[selectedMessageObjectIndex],
() => chat.getObjectContentFromIndex(
messageObject,
selectedMessageObjectIndex.value
)
);
const didEditContent = new State(false);
const editingNoteContent = createProxyState(
[selectedMessageObject],
() => selectedMessageObject.value.noteContent ?? ""
);
bulkSubscribe(
[editingNoteContent],
() => didEditContent.value = true
);
function handleKeyDown(e) {
if (!e.metaKey && !e.ctrlKey) return;
switch (e.key) {
case "s":
e.preventDefault();
saveAndClose();
break;
}
}
function closeModal() { function closeModal() {
isPresented.value = false; isPresented.value = false;
} }
function saveAndClose() { function saveAndClose() {
messageObject.title = editingTitle.value; messageObject.title = editingTitle.value;
chat.updateObject(messageObject.id, { chat.updateObject(
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString() messageObject.id,
}); didEditContent.value == true ? {
isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString(),
id: UUID(),
noteContent: editingNoteContent.value
} : void 0
);
closeModal(); closeModal();
} }
function deleteAndClose() { function deleteAndClose() {
chat.deleteObject(messageObject); chat.deleteObject(messageObject);
closeModal(); closeModal();
} }
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary" }, messageObject.id), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented, "on:keydown": handleKeyDown }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary" }, messageObject.id), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement(
"input", "input",
{ {
"bind:value": editingTitle, "bind:value": editingTitle,
placeholder: translation.objectTitlePlaceholder placeholder: translation.objectTitlePlaceholder
} }
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectVersion), /* @__PURE__ */ createElement("select", null, ...messageObject.contentVersions.map((content) => /* @__PURE__ */ createElement("option", null, new Date(content.isoDateVersionCreated).toLocaleString()))), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete"))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectVersion), /* @__PURE__ */ createElement("select", { "bind:value": selectedMessageObjectIndex }, ...messageObject.contentVersions.map((content, index) => /* @__PURE__ */ createElement("option", { value: index }, new Date(content.isoDateVersionCreated).toLocaleString()))), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "sticky_note_2"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.noteContent), /* @__PURE__ */ createElement(
"textarea",
{
rows: "5",
"bind:value": editingNoteContent,
placeholder: translation.noteContentPlaceholder
}
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger width-input", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete"))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save")))));
} }
// src/Views/Objects/objectListEntry.tsx // src/Views/Objects/objectListEntry.tsx
+6 -3
View File
@@ -602,7 +602,7 @@ label.tile:has(> span.icon) {
padding-left: var(--control-padding); padding-left: var(--control-padding);
} }
label.tile:has(> span.icon) :is(input, select) { label.tile:has(> span.icon) :is(input, select, textarea) {
padding-left: 0px; padding-left: 0px;
} }
@@ -735,8 +735,7 @@ button:not(.tile):not(header *),
footer, footer,
input:not([type="checkbox"]):not([type="radio"]), input:not([type="checkbox"]):not([type="radio"]),
select, select,
select + .icon, select + .icon {
textarea {
height: var(--control-height); height: var(--control-height);
} }
@@ -786,6 +785,10 @@ input[type="radio"] {
height: 16px; height: 16px;
} }
label.tile textarea {
resize: vertical;
}
/* /*
text text
*/ */
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"dependencies": { "dependencies": {
"bloatless-react": "^1.3.5", "bloatless-react": "^1.3.7",
"esbuild": "latest", "esbuild": "latest",
"udn-frontend": "^1.0.7-beta1" "udn-frontend": "^1.0.7-beta1"
}, },
+26 -8
View File
@@ -35,6 +35,9 @@ export interface MessageObject {
export interface MessageObjectContent { export interface MessageObjectContent {
isoDateVersionCreated: string; isoDateVersionCreated: string;
id: string;
noteContent?: string;
} }
// chat // chat
@@ -300,7 +303,7 @@ export class Chat {
id: React.UUID(), id: React.UUID(),
title, title,
isoDateLastEdited: new Date().toISOString(), isoDateLastEdited: new Date().toISOString(),
contentVersions: [], contentVersions: [this.createObjectContent()],
}; };
}; };
@@ -308,10 +311,15 @@ export class Chat {
this.objects.set(messageObject.id, messageObject); this.objects.set(messageObject.id, messageObject);
}; };
updateObject = (id: string, contents: MessageObjectContent): void => { updateObject = (id: string, contents?: MessageObjectContent): void => {
const messageObject = this.objects.value.get(id); const messageObject = this.objects.value.get(id);
if (!messageObject) return; if (!messageObject) return;
messageObject.contentVersions.push(contents); if (contents) {
messageObject.contentVersions.push(contents);
messageObject.contentVersions.sort((a, b) =>
a.isoDateVersionCreated > b.isoDateVersionCreated ? 0 : 1
);
}
this.sendObject(messageObject); this.sendObject(messageObject);
}; };
@@ -342,13 +350,23 @@ export class Chat {
getLatestObjectContent = ( getLatestObjectContent = (
messageObject: MessageObject messageObject: MessageObject
): MessageObjectContent => {
return this.getObjectContentFromIndex(messageObject, 0);
};
getObjectContentFromIndex = (
messageObject: MessageObject,
index: number
): MessageObjectContent => { ): MessageObjectContent => {
const versions = messageObject.contentVersions; const versions = messageObject.contentVersions;
if (versions.length == 0) return versions[index] ?? this.createObjectContent();
return { };
isoDateVersionCreated: new Date().toISOString(),
}; createObjectContent = () => {
return versions[versions.length - 1]; return {
id: React.UUID(),
isoDateVersionCreated: new Date().toISOString(),
};
}; };
// channel // channel
+61 -14
View File
@@ -1,10 +1,6 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import { import { Chat, MessageObject } from "../../Model/chatModel";
Chat,
MessageObject,
MessageObjectContent,
} from "../../Model/chatModel";
import { translation } from "../../translations"; import { translation } from "../../translations";
@@ -15,20 +11,59 @@ export function ObjectDetailModal(
) { ) {
// state // state
const editingTitle = new React.State(messageObject.title); const editingTitle = new React.State(messageObject.title);
const selectedMessageObject = new React.State<MessageObjectContent>(
chat.getLatestObjectContent(messageObject) const selectedMessageObjectIndex = new React.State(0);
selectedMessageObjectIndex.subscribe(console.log);
const selectedMessageObject = React.createProxyState(
[selectedMessageObjectIndex],
() =>
chat.getObjectContentFromIndex(
messageObject,
selectedMessageObjectIndex.value
)
);
const didEditContent = new React.State(false);
const editingNoteContent = React.createProxyState(
[selectedMessageObject],
() => selectedMessageObject.value.noteContent ?? ""
);
React.bulkSubscribe(
[editingNoteContent],
() => (didEditContent.value = true)
); );
// methods // methods
function handleKeyDown(e: KeyboardEvent) {
if (!e.metaKey && !e.ctrlKey) return;
switch (e.key) {
case "s":
e.preventDefault();
saveAndClose();
break;
}
}
function closeModal() { function closeModal() {
isPresented.value = false; isPresented.value = false;
} }
function saveAndClose() { function saveAndClose() {
messageObject.title = editingTitle.value; messageObject.title = editingTitle.value;
chat.updateObject(messageObject.id, {
isoDateVersionCreated: new Date().toISOString(), chat.updateObject(
}); messageObject.id,
didEditContent.value == true
? {
isoDateVersionCreated: new Date().toISOString(),
id: React.UUID(),
noteContent: editingNoteContent.value,
}
: undefined
);
closeModal(); closeModal();
} }
@@ -38,7 +73,7 @@ export function ObjectDetailModal(
} }
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented} on:keydown={handleKeyDown}>
<div> <div>
<main> <main>
<h2>{messageObject.title}</h2> <h2>{messageObject.title}</h2>
@@ -63,9 +98,9 @@ export function ObjectDetailModal(
<span class="icon">history</span> <span class="icon">history</span>
<div> <div>
<span>{translation.objectVersion}</span> <span>{translation.objectVersion}</span>
<select> <select bind:value={selectedMessageObjectIndex}>
{...messageObject.contentVersions.map((content) => ( {...messageObject.contentVersions.map((content, index) => (
<option> <option value={index}>
{new Date(content.isoDateVersionCreated).toLocaleString()} {new Date(content.isoDateVersionCreated).toLocaleString()}
</option> </option>
))} ))}
@@ -74,6 +109,18 @@ export function ObjectDetailModal(
</div> </div>
</label> </label>
<label class="tile">
<span class="icon">sticky_note_2</span>
<div>
<span>{translation.noteContent}</span>
<textarea
rows="5"
bind:value={editingNoteContent}
placeholder={translation.noteContentPlaceholder}
></textarea>
</div>
</label>
<hr></hr> <hr></hr>
<button class="danger width-input" on:click={deleteAndClose}> <button class="danger width-input" on:click={deleteAndClose}>
+2
View File
@@ -73,6 +73,8 @@ const englishTranslations = {
objectTitlePlaceholder: "My object", objectTitlePlaceholder: "My object",
objectVersion: "Object version", objectVersion: "Object version",
noteContent: "Note",
noteContentPlaceholder: "Take a note...",
resendObjects: "Resend all objects", resendObjects: "Resend all objects",
deleteObject: "Delete object", deleteObject: "Delete object",