@rightkit/workspace-ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Tasks view (Google Tasks). Extracted from App.tsx (M5) — a prop-less,
3
+ // memoized component that reads its state from the store and owns its handlers
4
+ // + load-on-mount, so it re-renders only when its own slices change.
5
+ import { memo, useEffect } from "react";
6
+ import Icon from "@rightkit/mail-ui/components/Icons";
7
+ import { Button, IconButton, SkeletonRows, Input, Select, Checkbox } from "@rightkit/react-shell/react";
8
+ import { useAppStore } from "@rightkit/mail-ui/store/index";
9
+ import { showDialog } from "@rightkit/mail-ui/hooks/dialog";
10
+ import { listTasks, listTaskLists, createTaskList, renameTaskList, deleteTaskList, clearCompletedTasks, setTaskStatus, createTaskIn, deleteTask, } from "@rightkit/mail-ui/lib/ipc";
11
+ function TasksViewInner() {
12
+ const activeAccount = useAppStore((s) => s.activeAccount);
13
+ const setErr = useAppStore((s) => s.setErr);
14
+ const showUndo = useAppStore((s) => s.showUndo);
15
+ const gtasks = useAppStore((s) => s.gtasks);
16
+ const setGtasks = useAppStore((s) => s.setGtasks);
17
+ const taskLists = useAppStore((s) => s.taskLists);
18
+ const setTaskLists = useAppStore((s) => s.setTaskLists);
19
+ const taskListId = useAppStore((s) => s.taskListId);
20
+ const setTaskListId = useAppStore((s) => s.setTaskListId);
21
+ const taskDue = useAppStore((s) => s.taskDue);
22
+ const setTaskDue = useAppStore((s) => s.setTaskDue);
23
+ const showCompletedTasks = useAppStore((s) => s.showCompletedTasks);
24
+ const setShowCompletedTasks = useAppStore((s) => s.setShowCompletedTasks);
25
+ const listMgrOpen = useAppStore((s) => s.listMgrOpen);
26
+ const setListMgrOpen = useAppStore((s) => s.setListMgrOpen);
27
+ const taskFormOpen = useAppStore((s) => s.taskFormOpen);
28
+ const setTaskFormOpen = useAppStore((s) => s.setTaskFormOpen);
29
+ const taskCreate = useAppStore((s) => s.taskCreate);
30
+ const setTaskCreate = useAppStore((s) => s.setTaskCreate);
31
+ const tasksBusy = useAppStore((s) => s.tasksBusy);
32
+ const setTasksBusy = useAppStore((s) => s.setTasksBusy);
33
+ async function reloadTasks(showCompleted = showCompletedTasks) {
34
+ if (!activeAccount)
35
+ return;
36
+ try {
37
+ setGtasks(await listTasks(activeAccount, showCompleted));
38
+ }
39
+ catch (e) {
40
+ setErr(String(e));
41
+ }
42
+ }
43
+ /** Create / rename / delete task lists (Tasks list manager). */
44
+ async function newTaskList() {
45
+ if (!activeAccount)
46
+ return;
47
+ const title = await showDialog({ kind: "prompt", message: "New list name" });
48
+ if (!title || !title.trim())
49
+ return;
50
+ try {
51
+ await createTaskList(activeAccount, title.trim());
52
+ setTaskLists(await listTaskLists(activeAccount));
53
+ showUndo("List created", [], "");
54
+ }
55
+ catch (e) {
56
+ setErr(String(e));
57
+ }
58
+ }
59
+ async function renameTaskListRow(l) {
60
+ if (!activeAccount)
61
+ return;
62
+ const title = await showDialog({ kind: "prompt", message: "Rename list", initial: l.title });
63
+ if (!title || !title.trim() || title.trim() === l.title)
64
+ return;
65
+ try {
66
+ await renameTaskList(activeAccount, l.id, title.trim());
67
+ setTaskLists(await listTaskLists(activeAccount));
68
+ reloadTasks();
69
+ }
70
+ catch (e) {
71
+ setErr(String(e));
72
+ }
73
+ }
74
+ async function deleteTaskListRow(l) {
75
+ if (!activeAccount)
76
+ return;
77
+ if ((await showDialog({ kind: "confirm", message: `Delete the list "${l.title}" and all its tasks? This cannot be undone.` })) !== "ok")
78
+ return;
79
+ try {
80
+ await deleteTaskList(activeAccount, l.id);
81
+ setTaskLists(await listTaskLists(activeAccount));
82
+ reloadTasks();
83
+ showUndo("List deleted", [], "");
84
+ }
85
+ catch (e) {
86
+ setErr(String(e));
87
+ }
88
+ }
89
+ async function clearCompletedRow(l) {
90
+ if (!activeAccount)
91
+ return;
92
+ try {
93
+ await clearCompletedTasks(activeAccount, l.id);
94
+ reloadTasks();
95
+ showUndo(`Cleared completed in ${l.title}`, [], "");
96
+ }
97
+ catch (e) {
98
+ setErr(String(e));
99
+ }
100
+ }
101
+ function toggleTask(t) {
102
+ if (!activeAccount)
103
+ return;
104
+ const done = t.status === "completed";
105
+ setGtasks((prev) => prev.map((x) => (x.id === t.id ? { ...x, status: done ? "needsAction" : "completed" } : x)));
106
+ setTaskStatus(activeAccount, t.list_id, t.id, !done).catch((e) => setErr(String(e)));
107
+ }
108
+ function deleteTaskRow(t) {
109
+ if (!activeAccount)
110
+ return;
111
+ deleteTask(activeAccount, t.list_id, t.id)
112
+ .then(() => setGtasks((prev) => prev.filter((x) => x.id !== t.id)))
113
+ .catch((e) => setErr(String(e)));
114
+ }
115
+ async function submitTask() {
116
+ if (!activeAccount || !taskCreate.title.trim()) {
117
+ setErr("Task needs a title.");
118
+ return;
119
+ }
120
+ const list = taskListId || taskLists[0]?.id || "@default";
121
+ const due = taskDue ? new Date(taskDue).toISOString() : "";
122
+ try {
123
+ await createTaskIn(activeAccount, list, taskCreate.title, taskCreate.notes, due);
124
+ setTaskFormOpen(false);
125
+ setTaskCreate({ title: "", notes: "" });
126
+ setTaskDue("");
127
+ await reloadTasks();
128
+ }
129
+ catch (e) {
130
+ setErr(String(e));
131
+ }
132
+ }
133
+ // Load lists + tasks on mount / account change (was the `view === "tasks"`
134
+ // branch of App's workspace effect).
135
+ useEffect(() => {
136
+ setTaskFormOpen(false);
137
+ if (!activeAccount)
138
+ return;
139
+ setTasksBusy(true);
140
+ listTaskLists(activeAccount).then(setTaskLists).catch(() => { });
141
+ listTasks(activeAccount)
142
+ .then(setGtasks)
143
+ .catch((e) => setErr(String(e)))
144
+ .finally(() => setTasksBusy(false));
145
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- setTaskFormOpen/setTasksBusy/setGtasks/setErr are store setters with unstable identity; tracking them would reload task lists on every render instead of only on account change.
146
+ }, [activeAccount]);
147
+ return (_jsxs("div", { className: "ws-panel", children: [_jsxs("div", { className: "ws-head", children: [_jsx("h2", { children: "Tasks" }), _jsx("div", { className: "ws-inline-toggle", title: "Include completed tasks", children: _jsx(Checkbox, { label: "Show completed", checked: showCompletedTasks, onChange: (on) => {
148
+ setShowCompletedTasks(on);
149
+ reloadTasks(on);
150
+ } }) }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => setListMgrOpen((o) => !o), children: "Lists" }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
151
+ setTaskCreate({ title: "", notes: "" });
152
+ setTaskDue("");
153
+ setTaskFormOpen((o) => !o);
154
+ }, children: "+ New task" })] }), listMgrOpen && (_jsxs("div", { className: "ws-form list-mgr", children: [_jsxs("div", { className: "list-mgr-head", children: [_jsx("span", { children: "Task lists" }), _jsx(Button, { variant: "primary", size: "sm", onClick: newTaskList, children: "+ New list" })] }), taskLists.map((l) => (_jsxs("div", { className: "list-mgr-row", children: [_jsx("span", { className: "ws-title", children: l.title }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => clearCompletedRow(l), children: "Clear completed" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => renameTaskListRow(l), children: "Rename" }), _jsx(Button, { variant: "danger", size: "sm", onClick: () => deleteTaskListRow(l), children: "Delete" })] }, l.id))), taskLists.length === 0 && _jsx("p", { className: "hint", children: "No lists yet." })] })), taskFormOpen && (_jsxs("div", { className: "ws-form", children: [_jsx(Input, { placeholder: "Task title", value: taskCreate.title, onChange: (e) => setTaskCreate({ ...taskCreate, title: e.target.value }) }), _jsx("textarea", { placeholder: "Notes (optional)", value: taskCreate.notes, onChange: (e) => setTaskCreate({ ...taskCreate, notes: e.target.value }) }), _jsxs("label", { className: "ws-form-row", children: [_jsx("span", { children: "Due" }), _jsx(Input, { type: "date", value: taskDue, onChange: (e) => setTaskDue(e.target.value) })] }), taskLists.length > 1 && (_jsx(Select, { label: "Task list", value: taskListId || taskLists[0]?.id || "", options: taskLists.map((l) => ({ value: l.id, label: l.title })), onChange: setTaskListId })), _jsxs("div", { className: "ws-form-foot", children: [_jsx(Button, { variant: "primary", onClick: submitTask, children: "Create" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setTaskFormOpen(false), children: "Cancel" })] })] })), tasksBusy ? (_jsx(SkeletonRows, {})) : gtasks.length === 0 ? (_jsx("p", { className: "hint", children: "No open tasks." })) : (_jsx("div", { className: "ws-list", children: gtasks.map((t) => (_jsxs("div", { className: "ws-row task-row", children: [_jsx(Checkbox, { label: _jsx("span", { className: "sr-only", children: "Complete" }), checked: t.status === "completed", onChange: () => toggleTask(t) }), _jsx("span", { className: `ws-title ${t.status === "completed" ? "task-done" : ""}`, children: t.title || "(untitled)" }), _jsxs("span", { className: "ws-sub", children: [t.list_title, t.due ? ` · due ${t.due.slice(0, 10)}` : ""] }), _jsx(IconButton, { label: "Delete task", onClick: () => deleteTaskRow(t), children: _jsx(Icon, { name: "trash", size: 14 }) })] }, t.id))) }))] }));
155
+ }
156
+ export const TasksView = memo(TasksViewInner);
@@ -0,0 +1,6 @@
1
+ import type { DriveFile } from "@rightkit/mail-ui/lib/ipc";
2
+ /** File-type to recognizable glyph plus Google's semantic color, or null for themed. */
3
+ export declare function fileGlyph(f: DriveFile): {
4
+ icon: string;
5
+ color: string | null;
6
+ };
@@ -0,0 +1,19 @@
1
+ /** File-type to recognizable glyph plus Google's semantic color, or null for themed. */
2
+ export function fileGlyph(f) {
3
+ if (f.is_folder)
4
+ return { icon: "folder", color: null };
5
+ const m = f.mime_type;
6
+ if (m.includes("application/vnd.google-apps.document"))
7
+ return { icon: "doc", color: "#4285F4" };
8
+ if (m.includes("spreadsheet"))
9
+ return { icon: "doc", color: "#0F9D58" };
10
+ if (m.includes("presentation"))
11
+ return { icon: "doc", color: "#F4B400" };
12
+ if (m === "application/pdf")
13
+ return { icon: "doc", color: "#DB4437" };
14
+ if (m.startsWith("image/"))
15
+ return { icon: "image", color: null };
16
+ if (m.startsWith("video/"))
17
+ return { icon: "video", color: null };
18
+ return { icon: "draft", color: null };
19
+ }
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@rightkit/workspace-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Calendar, contacts, drive, tasks and meeting views for workspace-style apps.",
5
+ "license": "MIT OR Apache-2.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "LICENSE-MIT",
21
+ "LICENSE-APACHE"
22
+ ],
23
+ "sideEffects": [
24
+ "*.css"
25
+ ],
26
+ "dependencies": {
27
+ "@rightkit/mail-ui": "^0.1.0",
28
+ "@rightkit/react-shell": "^0.7.2",
29
+ "@rightkit/settings-ui": "^0.7.0"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=18 <21"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^26.6.6",
36
+ "@types/react": "^19.3.0",
37
+ "@types/react-dom": "^19.3.0",
38
+ "react": "^19.3.1",
39
+ "react-dom": "^19.3.1",
40
+ "tsx": "^4.23.15",
41
+ "typescript": "^7.0.2",
42
+ "vite": "^8.3.4",
43
+ "vitest": "^5.0.5"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org",
47
+ "access": "public"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/bogusyogi/rightkit.git",
52
+ "directory": "packages/workspace-ui"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
56
+ "typecheck": "tsc -p tsconfig.json --noEmit",
57
+ "test": "pnpm --filter \"@rightkit/workspace-ui^...\" run build && node --import tsx --test \"test/*.test.ts\""
58
+ }
6
59
  }