remodel views to update dynamically

This commit is contained in:
marlon-erler
2024-07-12 19:04:57 +02:00
parent bb5550a8fe
commit 8697e7ad8b
7 changed files with 357 additions and 245 deletions
+162 -98
View File
@@ -1464,11 +1464,6 @@
); );
} }
// src/Views/Objects/placeholderView.tsx
function PlaceholderView() {
return /* @__PURE__ */ createElement("div", { class: "flex-column width-100 height-100 align-center justify-center secondary" }, translation.noObjects);
}
// src/Views/Objects/objectGridView.tsx // src/Views/Objects/objectGridView.tsx
function ObjectGridView(chat, messageObjects, selectedObject, isShowingObjectModal) { function ObjectGridView(chat, messageObjects, selectedObject, isShowingObjectModal) {
const objectConverter = (messageObject) => { const objectConverter = (messageObject) => {
@@ -1479,7 +1474,7 @@
isShowingObjectModal isShowingObjectModal
); );
}; };
return messageObjects.value.size == 0 ? PlaceholderView() : /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "width-100 max-height-100 grid gap padding scroll-v", class: "width-100 max-height-100 grid gap padding scroll-v",
@@ -1491,16 +1486,7 @@
// src/Views/Objects/allObjectsView.tsx // src/Views/Objects/allObjectsView.tsx
function AllObjectsView(chat, selectedObject, isShowingObjectModal) { function AllObjectsView(chat, selectedObject, isShowingObjectModal) {
const content = createProxyState( return /* @__PURE__ */ createElement("div", { class: "width-100 height-100" }, ObjectGridView(chat, chat.objects, selectedObject, isShowingObjectModal));
[chat.objects],
() => ObjectGridView(
chat,
chat.objects,
selectedObject,
isShowingObjectModal
)
);
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100", "children:set": content });
} }
// src/Views/renameView.tsx // src/Views/renameView.tsx
@@ -1524,32 +1510,59 @@
// src/Views/Objects/kanbanView.tsx // src/Views/Objects/kanbanView.tsx
function KanbanView(chat, selectedObject, isShowingObjectModal) { function KanbanView(chat, selectedObject, isShowingObjectModal) {
const boards = new MapState(); const boards = new MapState();
chat.objects.subscribe(() => { const sortedBoardNames = createProxyState(
boards.clear(); [boards],
chat.objects.value.forEach((messageObject) => { () => [...boards.value.values()].map((board) => board.category).sort((a, b) => a.localeCompare(b))
const latest = chat.getMostRecentContent(messageObject); );
if (!latest.categoryName) return; chat.objects.handleAddition((messageObject) => {
const boardTitle = latest.categoryName; const latest = chat.getMostRecentContent(messageObject);
if (!boards.value.has(boardTitle)) if (!latest) return;
boards.set(boardTitle, { category: boardTitle, items: [] }); if (!latest.categoryName) return;
boards.value.get(boardTitle)?.items.push({ const categoryName = latest.categoryName;
priority: latest.priority ?? 0, if (!boards.value.has(categoryName)) {
messageObject const listState = new ListState();
listState.subscribeSilent(() => {
if (listState.value.size != 0) return;
boards.remove(categoryName);
}); });
boards.set(categoryName, {
category: categoryName,
items: listState
});
}
const boardItem = {
priority: latest.priority ?? 0,
messageObject
};
boards.value.get(categoryName)?.items.add(boardItem);
chat.objects.handleRemoval(messageObject, () => {
boards.value.get(categoryName)?.items.remove(boardItem);
}); });
}); });
const content = createProxyState( const boardToBoardView = (board) => {
[chat.objects], const view = KanbanBoardView(
() => boards.value.size == 0 ? PlaceholderView() : /* @__PURE__ */ createElement("div", { class: "flex-row large-gap width-100 height-100 scroll-v scroll-h padding" }, ...[...boards.value.values()].sort((a, b) => a.category.localeCompare(b.category)).map( chat,
(board) => KanbanBoardView(chat, board, selectedObject, isShowingObjectModal) board,
)) selectedObject,
isShowingObjectModal
);
sortedBoardNames.subscribe((sortedBoardNames2) => {
view.style.order = sortedBoardNames2.indexOf(board.category);
});
return view;
};
return /* @__PURE__ */ createElement(
"div",
{
class: "flex-row large-gap width-100 height-100 scroll-v scroll-h padding",
"children:append": [boards, boardToBoardView]
}
); );
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 scroll-no", "children:set": content });
} }
function KanbanBoardView(chat, kanbanBoard, selectedObject, isShowingObjectModal) { function KanbanBoardView(chat, kanbanBoard, selectedObject, isShowingObjectModal) {
const editingCategory = new State(kanbanBoard.category); const editingCategory = new State(kanbanBoard.category);
function renameBoard() { function renameBoard() {
kanbanBoard.items.forEach((kanbanBoardItem) => { kanbanBoard.items.value.forEach((kanbanBoardItem) => {
const { messageObject } = kanbanBoardItem; const { messageObject } = kanbanBoardItem;
const latest = chat.getMostRecentContent(messageObject); const latest = chat.getMostRecentContent(messageObject);
latest.categoryName = editingCategory.value; latest.categoryName = editingCategory.value;
@@ -1569,6 +1582,16 @@
latest.categoryName = kanbanBoard.category; latest.categoryName = kanbanBoard.category;
chat.addObjectAndSend(messageObject); chat.addObjectAndSend(messageObject);
} }
const itemToViewEntry = (kanbanBoardItem) => {
const view = ObjectEntryView(
chat,
kanbanBoardItem.messageObject,
selectedObject,
isShowingObjectModal
);
view.style.order = kanbanBoardItem.priority * -1;
return view;
};
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div", "div",
{ {
@@ -1578,38 +1601,30 @@
}, },
RenameView(editingCategory, kanbanBoard.category, renameBoard), RenameView(editingCategory, kanbanBoard.category, renameBoard),
/* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("hr", null),
/* @__PURE__ */ createElement("div", { class: "flex-column gap padding-bottom" }, ...kanbanBoard.items.sort((a, b) => b.priority - a.priority).map( /* @__PURE__ */ createElement(
(kanbanBoardItem) => ObjectEntryView( "div",
chat, {
kanbanBoardItem.messageObject, class: "flex-column gap padding-bottom",
selectedObject, "children:append": [kanbanBoard.items, itemToViewEntry]
isShowingObjectModal }
) )
))
); );
} }
// src/Views/Objects/noteObjectsView.tsx // src/Views/Objects/noteObjectsView.tsx
function NoteObjectsView(chat, selectedObject, isShowingObjectModal) { function NoteObjectsView(chat, selectedObject, isShowingObjectModal) {
const notes = new ListState(); const notes = new ListState();
chat.objects.subscribe(() => { chat.objects.handleAddition((messageObject) => {
notes.clear(); const latest = chat.getMostRecentContent(messageObject);
chat.objects.value.forEach((messageObject) => { if (!latest) return;
const latest = chat.getMostRecentContent(messageObject); if (!latest.noteContent) return;
if (!latest.noteContent) return; notes.add(messageObject);
notes.add(messageObject); chat.objects.handleRemoval(
}); messageObject,
() => notes.remove(messageObject)
);
}); });
const content = createProxyState( return /* @__PURE__ */ createElement("div", { class: "width-100 height-100" }, ObjectGridView(chat, notes, selectedObject, isShowingObjectModal));
[chat.objects],
() => ObjectGridView(
chat,
notes,
selectedObject,
isShowingObjectModal
)
);
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100", "children:set": content });
} }
// src/Views/Objects/objectDetailModal.tsx // src/Views/Objects/objectDetailModal.tsx
@@ -1750,7 +1765,15 @@
// src/Views/Objects/statusView.tsx // src/Views/Objects/statusView.tsx
function StatusView(chat, selectedObject, isShowingObjectModal) { function StatusView(chat, selectedObject, isShowingObjectModal) {
const rows = new MapState(); const rows = new MapState();
const statuses = /* @__PURE__ */ new Map(); const statuses = new MapState();
const sortedStatuses = createProxyState(
[statuses],
() => [...statuses.value.values()].map((statusData) => statusData.title).sort((a, b) => a.localeCompare(b))
);
const sortedCategoryNames = createProxyState(
[rows],
() => [...rows.value.values()].map((row) => row.category).sort((a, b) => a.localeCompare(b))
);
chat.objects.subscribe(() => { chat.objects.subscribe(() => {
rows.clear(); rows.clear();
statuses.clear(); statuses.clear();
@@ -1759,41 +1782,73 @@
if (!latest.categoryName || !latest.status) return; if (!latest.categoryName || !latest.status) return;
const category = latest.categoryName; const category = latest.categoryName;
const status = latest.status; const status = latest.status;
if (!statuses.has(status)) { if (!statuses.value.has(status)) {
statuses.set(status, { title: status, items: [] }); statuses.set(status, { title: status, items: [] });
} }
statuses.get(status).items.push(messageObject); statuses.value.get(status).items.push(messageObject);
if (!rows.value.has(category)) { if (!rows.value.has(category)) {
rows.set(category, { category, statusColumns: /* @__PURE__ */ new Map() }); rows.set(category, {
category,
statusColumns: new MapState()
});
} }
const row = rows.value.get(category); const row = rows.value.get(category);
if (!row.statusColumns.has(status)) { if (!row.statusColumns.value.has(status)) {
row.statusColumns.set(status, { title: status, items: [] }); row.statusColumns.set(status, {
status,
items: new ListState()
});
} }
const column = row.statusColumns.get(status); const column = row.statusColumns.value.get(status);
column.items.push({ column.items.add({
priority: latest.priority ?? 0, priority: latest.priority ?? 0,
status, status,
messageObject messageObject
}); });
}); });
statuses.forEach((statusItemList) => { statuses.value.forEach((statusItemList) => {
rows.value.forEach((row) => { rows.value.forEach((row) => {
if (row.statusColumns.has(statusItemList.title)) return; if (row.statusColumns.value.has(statusItemList.title)) return;
row.statusColumns.set(statusItemList.title, { row.statusColumns.set(statusItemList.title, {
title: statusItemList.title, status: statusItemList.title,
items: [] items: new ListState()
}); });
}); });
}); });
}); });
const content = createProxyState( const statusDataToHeaderView = (statusData) => {
[chat.objects], const view = StatusHeaderView(chat, statusData);
() => rows.value.size == 0 ? PlaceholderView() : /* @__PURE__ */ createElement("div", { class: "flex-column large-gap width-100 height-100 scroll-v scroll-h padding" }, /* @__PURE__ */ createElement("div", { class: "flex-row large-gap" }, /* @__PURE__ */ createElement("div", { class: "flex-column object-entry-wide" }), ...[...statuses.values()].sort((a, b) => a.title.localeCompare(b.title)).map((status) => StatusHeaderView(chat, status))), ...[...rows.value.values()].sort((a, b) => a.category.localeCompare(b.category)).map( sortedStatuses.subscribe(
(board) => KanbanRowView(chat, board, selectedObject, isShowingObjectModal) () => view.style.order = sortedStatuses.value.indexOf(statusData.title)
)) );
); return view;
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 scroll-no", "children:set": content }); };
const categoryRowToView = (row) => {
const view = KanbanRowView(
chat,
row,
sortedStatuses,
selectedObject,
isShowingObjectModal
);
sortedCategoryNames.subscribe(
() => view.style.order = sortedCategoryNames.value.indexOf(row.category)
);
return view;
};
return /* @__PURE__ */ createElement("div", { class: "flex-column large-gap width-100 height-100 scroll-v scroll-h padding" }, /* @__PURE__ */ createElement("div", { class: "flex-row large-gap" }, /* @__PURE__ */ createElement("div", { class: "flex-column object-entry-wide" }), /* @__PURE__ */ createElement(
"div",
{
class: "flex-row large-gap",
"children:append": [statuses, statusDataToHeaderView]
}
)), /* @__PURE__ */ createElement(
"div",
{
class: "flex-column large-gap",
"children:append": [rows, categoryRowToView]
}
));
} }
function StatusHeaderView(chat, status) { function StatusHeaderView(chat, status) {
const editingStatus = new State(status.title); const editingStatus = new State(status.title);
@@ -1807,12 +1862,11 @@
} }
return /* @__PURE__ */ createElement("div", { class: "object-entry-wide" }, RenameView(editingStatus, status.title, rename), /* @__PURE__ */ createElement("hr", null)); return /* @__PURE__ */ createElement("div", { class: "object-entry-wide" }, RenameView(editingStatus, status.title, rename), /* @__PURE__ */ createElement("hr", null));
} }
function KanbanRowView(chat, kanbanRow, selectedObject, isShowingObjectModal) { function KanbanRowView(chat, kanbanRow, sortedStatuses, selectedObject, isShowingObjectModal) {
const editingCategory = new State(kanbanRow.category); const editingCategory = new State(kanbanRow.category);
const cellItemEntries = [...kanbanRow.statusColumns.entries()];
function renameCategory() { function renameCategory() {
kanbanRow.statusColumns.forEach((statusColumns) => { kanbanRow.statusColumns.value.forEach((statusColumns) => {
statusColumns.items.forEach((statusCellItem) => { statusColumns.items.value.forEach((statusCellItem) => {
const { messageObject } = statusCellItem; const { messageObject } = statusCellItem;
const latest = chat.getMostRecentContent(messageObject); const latest = chat.getMostRecentContent(messageObject);
latest.categoryName = editingCategory.value; latest.categoryName = editingCategory.value;
@@ -1820,16 +1874,27 @@
}); });
}); });
} }
return /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "flex-row align-start" }, RenameView(editingCategory, kanbanRow.category, renameCategory)), ...cellItemEntries.sort((a, b) => a[0].localeCompare(b[0])).map( const statusColumnToEntryView = (statusColumn) => {
(entry) => StatusColumnView( const view = StatusColumnView(
chat, chat,
entry[1].items, statusColumn.items,
kanbanRow.category, kanbanRow.category,
entry[1].title, statusColumn.status,
selectedObject, selectedObject,
isShowingObjectModal isShowingObjectModal
) );
), /* @__PURE__ */ createElement("hr", null)); sortedStatuses.subscribe(
() => view.style.order = sortedStatuses.value.indexOf(statusColumn.status)
);
return view;
};
return /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "flex-row align-start" }, RenameView(editingCategory, kanbanRow.category, renameCategory)), /* @__PURE__ */ createElement(
"div",
{
class: "flex-row large-gap",
"children:append": [kanbanRow.statusColumns, statusColumnToEntryView]
}
));
} }
function StatusColumnView(chat, items, category, status, selectedObject, isShowingObjectModal) { function StatusColumnView(chat, items, category, status, selectedObject, isShowingObjectModal) {
function dragOver(event) { function dragOver(event) {
@@ -1846,21 +1911,20 @@
latest.categoryName = category; latest.categoryName = category;
chat.addObjectAndSend(messageObject); chat.addObjectAndSend(messageObject);
} }
const cellItemToEntryView = (cellItem) => ObjectEntryView(
chat,
cellItem.messageObject,
selectedObject,
isShowingObjectModal
);
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "flex-column gap object-entry-wide", class: "flex-column gap object-entry-wide",
"on:dragover": dragOver, "on:dragover": dragOver,
"on:drop": drop "on:drop": drop,
}, "children:append": [items, cellItemToEntryView]
...items.sort((a, b) => b.priority - a.priority).map( }
(kanbanBoardItem) => ObjectEntryView(
chat,
kanbanBoardItem.messageObject,
selectedObject,
isShowingObjectModal
)
)
); );
} }
+4 -10
View File
@@ -3,21 +3,15 @@ import * as React from "bloatless-react";
import { Chat, MessageObject } from "../../Model/chatModel"; import { Chat, MessageObject } from "../../Model/chatModel";
import { ObjectGridView } from "./objectGridView"; import { ObjectGridView } from "./objectGridView";
import { translation } from "../../translations";
export function AllObjectsView( export function AllObjectsView(
chat: Chat, chat: Chat,
selectedObject: React.State<MessageObject | undefined>, selectedObject: React.State<MessageObject | undefined>,
isShowingObjectModal: React.State<boolean> isShowingObjectModal: React.State<boolean>
) { ) {
const content = React.createProxyState([chat.objects], () => return (
ObjectGridView( <div class="width-100 height-100">
chat, {ObjectGridView(chat, chat.objects, selectedObject, isShowingObjectModal)}
chat.objects, </div>
selectedObject,
isShowingObjectModal
)
); );
return <div class="width-100 height-100" children:set={content}></div>;
} }
+65 -42
View File
@@ -5,11 +5,10 @@ import { Chat, MessageObject } from "../../Model/chatModel";
import { ObjectEntryView } from "./objectEntryView"; import { ObjectEntryView } from "./objectEntryView";
import { PlaceholderView } from "./placeholderView"; import { PlaceholderView } from "./placeholderView";
import { RenameView } from "../renameView"; import { RenameView } from "../renameView";
import { translation } from "../../translations";
interface KanbanBoard { interface KanbanBoard {
category: string; category: string;
items: KanbanBoardItem[]; items: React.ListState<KanbanBoardItem>;
} }
interface KanbanBoardItem { interface KanbanBoardItem {
@@ -23,41 +22,60 @@ export function KanbanView(
isShowingObjectModal: React.State<boolean> isShowingObjectModal: React.State<boolean>
) { ) {
const boards = new React.MapState<KanbanBoard>(); const boards = new React.MapState<KanbanBoard>();
const sortedBoardNames = React.createProxyState([boards], () =>
[...boards.value.values()]
.map((board) => board.category)
.sort((a, b) => a.localeCompare(b))
);
chat.objects.subscribe(() => { chat.objects.handleAddition((messageObject) => {
boards.clear(); const latest = chat.getMostRecentContent(messageObject);
chat.objects.value.forEach((messageObject) => { if (!latest) return;
const latest = chat.getMostRecentContent(messageObject); if (!latest.categoryName) return;
if (!latest.categoryName) return;
const boardTitle = latest.categoryName; const categoryName = latest.categoryName!;
if (!boards.value.has(categoryName)) {
if (!boards.value.has(boardTitle)) const listState = new React.ListState<KanbanBoardItem>();
boards.set(boardTitle, { category: boardTitle, items: [] }); listState.subscribeSilent(() => {
if (listState.value.size != 0) return;
boards.value.get(boardTitle)?.items.push({ boards.remove(categoryName);
priority: latest.priority ?? 0,
messageObject,
}); });
boards.set(categoryName, {
category: categoryName,
items: listState,
});
}
const boardItem: KanbanBoardItem = {
priority: latest.priority ?? 0,
messageObject,
};
boards.value.get(categoryName)?.items.add(boardItem);
chat.objects.handleRemoval(messageObject, () => {
boards.value.get(categoryName)?.items.remove(boardItem);
}); });
}); });
const content = React.createProxyState([chat.objects], () => const boardToBoardView: React.StateItemConverter<KanbanBoard> = (board) => {
boards.value.size == 0 ? ( const view = KanbanBoardView(
PlaceholderView() chat,
) : ( board,
<div class="flex-row large-gap width-100 height-100 scroll-v scroll-h padding"> selectedObject,
{...[...boards.value.values()] isShowingObjectModal
.sort((a, b) => a.category.localeCompare(b.category)) );
.map((board) => sortedBoardNames.subscribe((sortedBoardNames) => {
KanbanBoardView(chat, board, selectedObject, isShowingObjectModal) view.style.order = sortedBoardNames.indexOf(board.category);
)} });
</div> return view;
) };
);
return ( return (
<div class="width-100 height-100 scroll-no" children:set={content}></div> <div
class="flex-row large-gap width-100 height-100 scroll-v scroll-h padding"
children:append={[boards, boardToBoardView]}
></div>
); );
} }
@@ -70,7 +88,7 @@ function KanbanBoardView(
const editingCategory = new React.State(kanbanBoard.category); const editingCategory = new React.State(kanbanBoard.category);
function renameBoard() { function renameBoard() {
kanbanBoard.items.forEach((kanbanBoardItem) => { kanbanBoard.items.value.forEach((kanbanBoardItem) => {
const { messageObject } = kanbanBoardItem; const { messageObject } = kanbanBoardItem;
const latest = chat.getMostRecentContent(messageObject); const latest = chat.getMostRecentContent(messageObject);
latest.categoryName = editingCategory.value; latest.categoryName = editingCategory.value;
@@ -95,6 +113,19 @@ function KanbanBoardView(
chat.addObjectAndSend(messageObject); chat.addObjectAndSend(messageObject);
} }
const itemToViewEntry: React.StateItemConverter<KanbanBoardItem> = (
kanbanBoardItem
) => {
const view = ObjectEntryView(
chat,
kanbanBoardItem.messageObject,
selectedObject,
isShowingObjectModal
);
view.style.order = kanbanBoardItem.priority * -1;
return view;
};
return ( return (
<div <div
class="flex-column flex-no object-entry-wide" class="flex-column flex-no object-entry-wide"
@@ -103,18 +134,10 @@ function KanbanBoardView(
> >
{RenameView(editingCategory, kanbanBoard.category, renameBoard)} {RenameView(editingCategory, kanbanBoard.category, renameBoard)}
<hr></hr> <hr></hr>
<div class="flex-column gap padding-bottom"> <div
{...kanbanBoard.items class="flex-column gap padding-bottom"
.sort((a, b) => b.priority - a.priority) children:append={[kanbanBoard.items, itemToViewEntry]}
.map((kanbanBoardItem) => ></div>
ObjectEntryView(
chat,
kanbanBoardItem.messageObject,
selectedObject,
isShowingObjectModal
)
)}
</div>
</div> </div>
); );
} }
+11 -18
View File
@@ -3,7 +3,6 @@ import * as React from "bloatless-react";
import { Chat, MessageObject } from "../../Model/chatModel"; import { Chat, MessageObject } from "../../Model/chatModel";
import { ObjectGridView } from "./objectGridView"; import { ObjectGridView } from "./objectGridView";
import { translation } from "../../translations";
export function NoteObjectsView( export function NoteObjectsView(
chat: Chat, chat: Chat,
@@ -11,24 +10,18 @@ export function NoteObjectsView(
isShowingObjectModal: React.State<boolean> isShowingObjectModal: React.State<boolean>
) { ) {
const notes = new React.ListState<MessageObject>(); const notes = new React.ListState<MessageObject>();
chat.objects.subscribe(() => { chat.objects.handleAddition((messageObject) => {
notes.clear(); const latest = chat.getMostRecentContent(messageObject);
chat.objects.value.forEach((messageObject) => { if (!latest) return;
const latest = chat.getMostRecentContent(messageObject); if (!latest.noteContent) return;
if (!latest.noteContent) return;
notes.add(messageObject); notes.add(messageObject);
}); chat.objects.handleRemoval(messageObject, () =>
notes.remove(messageObject)
);
}); });
const content = React.createProxyState([chat.objects], () => return <div class="width-100 height-100">
ObjectGridView( {ObjectGridView(chat, notes, selectedObject, isShowingObjectModal)}
chat, </div>;
notes,
selectedObject,
isShowingObjectModal
)
);
return <div class="width-100 height-100" children:set={content}></div>;
} }
+1 -3
View File
@@ -24,9 +24,7 @@ export function ObjectGridView(
); );
}; };
return messageObjects.value.size == 0 ? ( return (
PlaceholderView()
) : (
<div <div
class="width-100 max-height-100 grid gap padding scroll-v" class="width-100 max-height-100 grid gap padding scroll-v"
style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));"
+110 -73
View File
@@ -3,18 +3,16 @@ import * as React from "bloatless-react";
import { Chat, MessageObject } from "../../Model/chatModel"; import { Chat, MessageObject } from "../../Model/chatModel";
import { ObjectEntryView } from "./objectEntryView"; import { ObjectEntryView } from "./objectEntryView";
import { PlaceholderView } from "./placeholderView";
import { RenameView } from "../renameView"; import { RenameView } from "../renameView";
import { translation } from "../../translations";
interface KanbanRow { interface CategoryRow {
category: string; category: string;
statusColumns: Map<string, StatusColumn>; statusColumns: React.MapState<StatusColumn>;
} }
interface StatusColumn { interface StatusColumn {
title: string; status: string;
items: StatusCellItem[]; items: React.ListState<StatusCellItem>;
} }
interface StatusCellItem { interface StatusCellItem {
@@ -23,7 +21,8 @@ interface StatusCellItem {
messageObject: MessageObject; messageObject: MessageObject;
} }
interface StatusItemList { // data only
interface StatusData {
title: string; title: string;
items: MessageObject[]; items: MessageObject[];
} }
@@ -33,8 +32,18 @@ export function StatusView(
selectedObject: React.State<MessageObject | undefined>, selectedObject: React.State<MessageObject | undefined>,
isShowingObjectModal: React.State<boolean> isShowingObjectModal: React.State<boolean>
) { ) {
const rows = new React.MapState<KanbanRow>(); const rows = new React.MapState<CategoryRow>();
const statuses = new Map<string, StatusItemList>(); const statuses = new React.MapState<StatusData>();
const sortedStatuses = React.createProxyState([statuses], () =>
[...statuses.value.values()]
.map((statusData) => statusData.title)
.sort((a, b) => a.localeCompare(b))
);
const sortedCategoryNames = React.createProxyState([rows], () =>
[...rows.value.values()]
.map((row) => row.category)
.sort((a, b) => a.localeCompare(b))
);
chat.objects.subscribe(() => { chat.objects.subscribe(() => {
rows.clear(); rows.clear();
@@ -48,22 +57,28 @@ export function StatusView(
const category = latest.categoryName; const category = latest.categoryName;
const status = latest.status; const status = latest.status;
if (!statuses.has(status)) { if (!statuses.value.has(status)) {
statuses.set(status, { title: status, items: [] }); statuses.set(status, { title: status, items: [] });
} }
statuses.get(status)!.items.push(messageObject); statuses.value.get(status)!.items.push(messageObject);
if (!rows.value.has(category)) { if (!rows.value.has(category)) {
rows.set(category, { category: category, statusColumns: new Map() }); rows.set(category, {
category: category,
statusColumns: new React.MapState(),
});
} }
const row = rows.value.get(category)!; const row = rows.value.get(category)!;
if (!row.statusColumns.has(status)) { if (!row.statusColumns.value.has(status)) {
row.statusColumns.set(status, { title: status, items: [] }); row.statusColumns.set(status, {
status: status,
items: new React.ListState(),
});
} }
const column = row.statusColumns.get(status)!; const column = row.statusColumns.value.get(status)!;
column.items.push({ column.items.add({
priority: latest.priority ?? 0, priority: latest.priority ?? 0,
status, status,
messageObject, messageObject,
@@ -71,43 +86,59 @@ export function StatusView(
}); });
// add status placeholders // add status placeholders
statuses.forEach((statusItemList) => { statuses.value.forEach((statusItemList) => {
rows.value.forEach((row) => { rows.value.forEach((row) => {
if (row.statusColumns.has(statusItemList.title)) return; if (row.statusColumns.value.has(statusItemList.title)) return;
row.statusColumns.set(statusItemList.title, { row.statusColumns.set(statusItemList.title, {
title: statusItemList.title, status: statusItemList.title,
items: [], items: new React.ListState(),
}); });
}); });
}); });
}); });
const content = React.createProxyState([chat.objects], () => const statusDataToHeaderView: React.StateItemConverter<StatusData> = (
rows.value.size == 0 ? ( statusData
PlaceholderView() ) => {
) : ( const view = StatusHeaderView(chat, statusData);
<div class="flex-column large-gap width-100 height-100 scroll-v scroll-h padding"> sortedStatuses.subscribe(
<div class="flex-row large-gap"> () => (view.style.order = sortedStatuses.value.indexOf(statusData.title))
<div class="flex-column object-entry-wide"></div> );
{...[...statuses.values()] return view;
.sort((a, b) => a.title.localeCompare(b.title)) };
.map((status) => StatusHeaderView(chat, status))}
</div> const categoryRowToView: React.StateItemConverter<CategoryRow> = (row) => {
{...[...rows.value.values()] const view = KanbanRowView(
.sort((a, b) => a.category.localeCompare(b.category)) chat,
.map((board) => row,
KanbanRowView(chat, board, selectedObject, isShowingObjectModal) sortedStatuses,
)} selectedObject,
</div> isShowingObjectModal
) );
); sortedCategoryNames.subscribe(
() => (view.style.order = sortedCategoryNames.value.indexOf(row.category))
);
return view;
};
return ( return (
<div class="width-100 height-100 scroll-no" children:set={content}></div> <div class="flex-column large-gap width-100 height-100 scroll-v scroll-h padding">
<div class="flex-row large-gap">
<div class="flex-column object-entry-wide"></div>
<div
class="flex-row large-gap"
children:append={[statuses, statusDataToHeaderView]}
></div>
</div>
<div
class="flex-column large-gap"
children:append={[rows, categoryRowToView]}
></div>
</div>
); );
} }
function StatusHeaderView(chat: Chat, status: StatusItemList) { function StatusHeaderView(chat: Chat, status: StatusData) {
const editingStatus = new React.State(status.title); const editingStatus = new React.State(status.title);
function rename() { function rename() {
@@ -130,17 +161,16 @@ function StatusHeaderView(chat: Chat, status: StatusItemList) {
function KanbanRowView( function KanbanRowView(
chat: Chat, chat: Chat,
kanbanRow: KanbanRow, kanbanRow: CategoryRow,
sortedStatuses: React.State<string[]>,
selectedObject: React.State<MessageObject | undefined>, selectedObject: React.State<MessageObject | undefined>,
isShowingObjectModal: React.State<boolean> isShowingObjectModal: React.State<boolean>
) { ) {
const editingCategory = new React.State(kanbanRow.category); const editingCategory = new React.State(kanbanRow.category);
const cellItemEntries = [...kanbanRow.statusColumns.entries()];
function renameCategory() { function renameCategory() {
kanbanRow.statusColumns.forEach((statusColumns) => { kanbanRow.statusColumns.value.forEach((statusColumns) => {
statusColumns.items.forEach((statusCellItem) => { statusColumns.items.value.forEach((statusCellItem) => {
const { messageObject } = statusCellItem; const { messageObject } = statusCellItem;
const latest = chat.getMostRecentContent(messageObject); const latest = chat.getMostRecentContent(messageObject);
latest.categoryName = editingCategory.value; latest.categoryName = editingCategory.value;
@@ -149,33 +179,40 @@ function KanbanRowView(
}); });
} }
const statusColumnToEntryView: React.StateItemConverter<StatusColumn> = (
statusColumn
) => {
const view = StatusColumnView(
chat,
statusColumn.items,
kanbanRow.category,
statusColumn.status,
selectedObject,
isShowingObjectModal
);
sortedStatuses.subscribe(
() =>
(view.style.order = sortedStatuses.value.indexOf(statusColumn.status))
);
return view;
};
return ( return (
<div class="flex-row flex-no large-gap"> <div class="flex-row flex-no large-gap">
<div class="flex-row align-start"> <div class="flex-row align-start">
{RenameView(editingCategory, kanbanRow.category, renameCategory)} {RenameView(editingCategory, kanbanRow.category, renameCategory)}
</div> </div>
{...cellItemEntries <div
.sort((a, b) => a[0].localeCompare(b[0])) class="flex-row large-gap"
.map((entry) => children:append={[kanbanRow.statusColumns, statusColumnToEntryView]}
StatusColumnView( ></div>
chat,
entry[1].items,
kanbanRow.category,
entry[1].title,
selectedObject,
isShowingObjectModal
)
)}
<hr></hr>
</div> </div>
); );
} }
function StatusColumnView( function StatusColumnView(
chat: Chat, chat: Chat,
items: StatusCellItem[], items: React.ListState<StatusCellItem>,
category: string, category: string,
status: string, status: string,
selectedObject: React.State<MessageObject | undefined>, selectedObject: React.State<MessageObject | undefined>,
@@ -199,22 +236,22 @@ function StatusColumnView(
chat.addObjectAndSend(messageObject); chat.addObjectAndSend(messageObject);
} }
const cellItemToEntryView: React.StateItemConverter<StatusCellItem> = (
cellItem
) =>
ObjectEntryView(
chat,
cellItem.messageObject,
selectedObject,
isShowingObjectModal
);
return ( return (
<div <div
class="flex-column gap object-entry-wide" class="flex-column gap object-entry-wide"
on:dragover={dragOver} on:dragover={dragOver}
on:drop={drop} on:drop={drop}
> children:append={[items, cellItemToEntryView]}
{...items ></div>
.sort((a, b) => b.priority - a.priority)
.map((kanbanBoardItem) =>
ObjectEntryView(
chat,
kanbanBoardItem.messageObject,
selectedObject,
isShowingObjectModal
)
)}
</div>
); );
} }
+3
View File
@@ -1,5 +1,8 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import { MessageObject } from "./Model/chatModel";
import { ObjectEntryView } from "./Views/Objects/objectEntryView";
export const storageKeys = { export const storageKeys = {
viewType(id: string): string { viewType(id: string): string {
return id + "view-type"; return id + "view-type";