remodel views to update dynamically
This commit is contained in:
Vendored
+162
-98
@@ -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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));"
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user