mirror of
https://github.com/akaswan/table-list.git
synced 2026-07-22 05:49:21 +00:00
Saving before major structural changes are made
This commit is contained in:
parent
529b9b9631
commit
e8debb4535
8 changed files with 296 additions and 55 deletions
|
|
@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||
import { useTableContext } from "../views/TableView";
|
||||
import Table from "./Table";
|
||||
import TopBar from "./TopBar";
|
||||
import { format, addDays, subDays, isSameDay } from "date-fns";
|
||||
import { addDays, subDays } from "date-fns";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { Project, TableData, Task, TaskStatus } from "../types";
|
||||
|
||||
|
|
@ -13,9 +13,27 @@ const supabaseAnonKey = process.env.SUPABASE_ANON_KEY ?? "";
|
|||
const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||
|
||||
/** Utility Functions **/
|
||||
const getWeekDates = (startDate: Date, numDates: number) =>
|
||||
export const toMidnight = (date: Date | string): Date => {
|
||||
const d = new Date(date);
|
||||
d.setHours(0, 0, 0, 0); // set time to 00:00 UTC
|
||||
return d;
|
||||
};
|
||||
|
||||
const normalizeHours = (d: Date) => {
|
||||
const copy = new Date(d);
|
||||
copy.setHours(0, 0, 0, 0);
|
||||
return copy;
|
||||
};
|
||||
|
||||
const isBeforeNormalizedDay = (a: Date, b: Date) =>
|
||||
normalizeHours(a).getTime() < normalizeHours(b).getTime();
|
||||
|
||||
const isSameNormalizedDay = (a: Date, b: Date) =>
|
||||
normalizeHours(a).getTime() === normalizeHours(b).getTime();
|
||||
|
||||
const getWeekDates = (startDate: Date, numDates: number): Date[] =>
|
||||
Array.from({ length: numDates }, (_, i) =>
|
||||
format(addDays(startDate, i), "yyyy-MM-dd")
|
||||
addDays(startDate, i)
|
||||
);
|
||||
|
||||
const getTaskStatuses = (): TaskStatus[] => [
|
||||
|
|
@ -85,6 +103,7 @@ const App: React.FC = () => {
|
|||
|
||||
const setDatesToThisWeek = () => {
|
||||
const today = new Date();
|
||||
console.log(today);
|
||||
setBaseDate(today);
|
||||
setDates(getWeekDates(today, datesShown));
|
||||
};
|
||||
|
|
@ -153,10 +172,11 @@ const App: React.FC = () => {
|
|||
};
|
||||
|
||||
const moveTask = (id: number, newDate: string) => {
|
||||
const normalizedDate = toMidnight(newDate);
|
||||
const newProjects = projects.map((p) => ({
|
||||
...p,
|
||||
tasks: p.tasks.map((t) =>
|
||||
t.id === id ? { ...t, date: new Date(newDate) } : t
|
||||
t.id === id ? { ...t, date: normalizedDate } : t
|
||||
),
|
||||
}));
|
||||
saveSpecificData("projects", newProjects);
|
||||
|
|
@ -182,11 +202,11 @@ const App: React.FC = () => {
|
|||
setTimeout(() => ref.current?.focus(), 0);
|
||||
};
|
||||
|
||||
const addTaskToProject = (project: Project, date: string) => {
|
||||
const addTaskToProject = (project: Project, date: Date) => {
|
||||
const newTask: Task = {
|
||||
id: nextTaskId,
|
||||
name: "",
|
||||
date: new Date(date),
|
||||
date: toMidnight(date),
|
||||
parentProjectId: project.id,
|
||||
status: taskStatuses[0],
|
||||
};
|
||||
|
|
@ -202,25 +222,15 @@ const App: React.FC = () => {
|
|||
setNextTaskId(newId);
|
||||
};
|
||||
|
||||
const normalizeDateUTC = (d: Date) => {
|
||||
const copy = new Date(d);
|
||||
copy.setUTCHours(0, 0, 0, 0);
|
||||
return copy;
|
||||
};
|
||||
|
||||
const isBeforeDayUTC = (a: Date, b: Date) =>
|
||||
normalizeDateUTC(a).getTime() < normalizeDateUTC(b).getTime();
|
||||
|
||||
const findPendingTasksLeftSide = () => {
|
||||
let number = 0;
|
||||
projects.forEach((project) => {
|
||||
project.tasks.forEach((task) => {
|
||||
if (
|
||||
isBeforeDayUTC(new Date(task.date), baseDate) &&
|
||||
isBeforeNormalizedDay(new Date(task.date), baseDate) &&
|
||||
task.status.id !== "completed"
|
||||
) {
|
||||
number++;
|
||||
console.log(new Date(task.date));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -311,6 +321,10 @@ const isBeforeDayUTC = (a: Date, b: Date) =>
|
|||
/** Effects **/
|
||||
useEffect(() => {
|
||||
dataRef.current = data;
|
||||
// dates.forEach((date) => {
|
||||
// console.log(new Date(date));
|
||||
// });
|
||||
console.log(toMidnight(new Date()));
|
||||
}, [data]);
|
||||
useEffect(() => {
|
||||
syncWithServer();
|
||||
|
|
@ -325,7 +339,7 @@ const isBeforeDayUTC = (a: Date, b: Date) =>
|
|||
projects.forEach((project) => {
|
||||
project.tasks.forEach((task) => {
|
||||
if (
|
||||
isSameDay(new Date(task.date), subDays(new Date(), 1)) &&
|
||||
isSameNormalizedDay(new Date(task.date), new Date()) &&
|
||||
task.status.id !== "completed"
|
||||
) {
|
||||
remainingTasks++;
|
||||
|
|
@ -384,6 +398,7 @@ const isBeforeDayUTC = (a: Date, b: Date) =>
|
|||
editTaskStatus={editTaskStatus}
|
||||
moveTask={moveTask}
|
||||
wrapperRef={wrapperRef}
|
||||
sharedState={tableContext!.sharedState}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { format, parseISO } from "date-fns";
|
||||
import { format, isSameDay } from "date-fns";
|
||||
import { useRef } from "react";
|
||||
import * as React from "react";
|
||||
import TaskCell from "./TaskCell";
|
||||
import { DndContext, useDroppable, DragEndEvent } from "@dnd-kit/core";
|
||||
import { Project, TaskStatus } from "../types";
|
||||
import { IntraViewData } from "../main";
|
||||
import { SharedState } from "../sharedState";
|
||||
|
||||
interface TableProps {
|
||||
projects: Project[];
|
||||
|
|
@ -13,8 +15,8 @@ interface TableProps {
|
|||
newName: string,
|
||||
newProjectInputRef: React.RefObject<HTMLInputElement | null>
|
||||
) => void;
|
||||
dates: string[];
|
||||
addTaskToProject: (project: Project, date: string) => void;
|
||||
dates: Date[];
|
||||
addTaskToProject: (project: Project, date: Date) => void;
|
||||
removeProject: (id: number) => void;
|
||||
removeTask: (id: number) => void;
|
||||
nextTaskId: number;
|
||||
|
|
@ -23,6 +25,7 @@ interface TableProps {
|
|||
editTaskStatus: (id: number, newStatusId: string) => void;
|
||||
moveTask: (taskId: number, newDate: string) => void;
|
||||
wrapperRef: React.RefObject<HTMLDivElement | null>;
|
||||
sharedState: SharedState<IntraViewData> | null;
|
||||
}
|
||||
|
||||
function DroppableCell({
|
||||
|
|
@ -62,11 +65,21 @@ const Table: React.FC<TableProps> = ({
|
|||
taskStatuses,
|
||||
editTaskStatus,
|
||||
moveTask,
|
||||
wrapperRef,
|
||||
sharedState,
|
||||
}) => {
|
||||
const newProjectInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const newTaskInputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const isTaskAutoFocused = (taskId: number, taskName: string) => {
|
||||
if (taskId === nextTaskId - 1 && taskName === "") {
|
||||
sharedState?.set({ selectedTaskId: taskId });
|
||||
console.log("Auto focusing task:", taskId);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
onDragEnd={(event: DragEndEvent) => {
|
||||
|
|
@ -86,18 +99,17 @@ const Table: React.FC<TableProps> = ({
|
|||
<tr>
|
||||
<th>Projects</th>
|
||||
{dates.map((date) => {
|
||||
const dateObj = parseISO(date);
|
||||
return (
|
||||
<th className="date" key={date}>
|
||||
<th
|
||||
className="date"
|
||||
key={format(date, "yyyy-MM-dd")}
|
||||
>
|
||||
<div className="date-header">
|
||||
<div>
|
||||
{format(dateObj, "EEEE")}
|
||||
{format(date, "EEEE")}
|
||||
</div>
|
||||
<div>
|
||||
{format(
|
||||
dateObj,
|
||||
"yyyy-MM-dd"
|
||||
)}
|
||||
{format(date, "yyyy-MM-dd")}
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
|
|
@ -134,7 +146,7 @@ const Table: React.FC<TableProps> = ({
|
|||
}
|
||||
/>
|
||||
</td>
|
||||
{dates.map((date, index) => (
|
||||
{dates.map((date) => (
|
||||
<DroppableCell
|
||||
id={`${project.id}::${date}`}
|
||||
onClick={(e) => {
|
||||
|
|
@ -150,23 +162,23 @@ const Table: React.FC<TableProps> = ({
|
|||
}, 0);
|
||||
}
|
||||
}}
|
||||
key={`${project.id}-${date}`} // ADD KEY HERE
|
||||
key={`${project.id}-${date}`}
|
||||
>
|
||||
{project.tasks
|
||||
.filter(
|
||||
(task) =>
|
||||
new Date(
|
||||
task.date
|
||||
).toISOString() ===
|
||||
new Date(
|
||||
date
|
||||
).toISOString()
|
||||
.filter((task) =>
|
||||
isSameDay(
|
||||
new Date(task.date),
|
||||
new Date(date)
|
||||
)
|
||||
)
|
||||
.map((task) => (
|
||||
<TaskCell
|
||||
key={task.id}
|
||||
task={task}
|
||||
autoFocus={task.id === nextTaskId - 1 && task.name === ""}
|
||||
autoFocus={isTaskAutoFocused(
|
||||
task.id,
|
||||
task.name
|
||||
)}
|
||||
projectName={
|
||||
project.name
|
||||
}
|
||||
|
|
@ -187,6 +199,11 @@ const Table: React.FC<TableProps> = ({
|
|||
editTaskStatus={
|
||||
editTaskStatus
|
||||
}
|
||||
onFocus={(taskId) => {
|
||||
sharedState?.set({
|
||||
selectedTaskId: taskId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DroppableCell>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ interface TaskCellProps {
|
|||
taskStatuses: TaskStatus[];
|
||||
editTaskStatus: (id: number, newStatusId: string) => void;
|
||||
autoFocus?: boolean;
|
||||
onFocus: (taskId: number) => void;
|
||||
}
|
||||
|
||||
const TaskCell: React.FC<TaskCellProps> = ({
|
||||
|
|
@ -59,6 +60,7 @@ const TaskCell: React.FC<TaskCellProps> = ({
|
|||
taskStatuses,
|
||||
editTaskStatus,
|
||||
autoFocus = false, // default to false if not provided
|
||||
onFocus,
|
||||
}) => {
|
||||
const taskBackground = `${status.color}33`;
|
||||
const lightTaskTextColor = transColor(status.color, 25);
|
||||
|
|
@ -175,6 +177,7 @@ const TaskCell: React.FC<TaskCellProps> = ({
|
|||
height: "auto",
|
||||
}}
|
||||
rows={1}
|
||||
onFocus={() => onFocus(task.id)}
|
||||
/>
|
||||
<style>{`
|
||||
.task-input-${task.id}::placeholder {
|
||||
|
|
|
|||
46
src/main/components/TaskEditor.tsx
Normal file
46
src/main/components/TaskEditor.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { useContext, useState } from "react";
|
||||
import { TaskEditorContext } from "../views/TaskEditorView";
|
||||
import { useSharedState } from "../sharedState";
|
||||
import { TableData } from "../types";
|
||||
|
||||
const TaskEditor: React.FC = () => {
|
||||
const ctx = useContext(TaskEditorContext);
|
||||
const shared = ctx?.sharedState;
|
||||
const state = shared ? useSharedState(shared) : null;
|
||||
|
||||
if (!state) return <div>Loading...</div>;
|
||||
|
||||
const getInitialData = (): TableData => {
|
||||
const loaded = ctx?.loadData?.();
|
||||
if (
|
||||
loaded &&
|
||||
typeof loaded === "object" &&
|
||||
"projects" in loaded &&
|
||||
"nextProjectId" in loaded &&
|
||||
"nextTaskId" in loaded &&
|
||||
"lastUpdated" in loaded
|
||||
) {
|
||||
return loaded as TableData;
|
||||
}
|
||||
return {
|
||||
projects: [],
|
||||
nextProjectId: 1,
|
||||
nextTaskId: 1,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
const [data, setData] = useState<TableData>(getInitialData);
|
||||
|
||||
const tasks = data.projects.flatMap(project => project.tasks);
|
||||
|
||||
const selectedTask = tasks.find(task => task.id === state.selectedTaskId);
|
||||
|
||||
return (
|
||||
<div className="task-editor">
|
||||
<p>{selectedTask?.name}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskEditor;
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
import { Plugin, WorkspaceLeaf } from "obsidian";
|
||||
import { TableView, TABLE_VIEW_TYPE } from "./views/TableView";
|
||||
import { TableListSettingsTab } from "./settings";
|
||||
import { TASK_EDITOR_VIEW_TYPE } from "./views/TaskEditorView";
|
||||
import { TaskEditorView } from "./views/TaskEditorView";
|
||||
import { createSharedState } from "./sharedState";
|
||||
|
||||
export interface IntraViewData {
|
||||
selectedTaskId: number | null;
|
||||
}
|
||||
|
||||
export interface TableListSettings {
|
||||
maxDates: string;
|
||||
|
|
@ -13,6 +20,10 @@ const DEFAULT_SETTINGS: Partial<TableListSettings> = {
|
|||
export default class TableList extends Plugin {
|
||||
settings: TableListSettings;
|
||||
|
||||
sharedState = createSharedState<IntraViewData>({
|
||||
selectedTaskId: null,
|
||||
});
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
|
|
@ -34,7 +45,15 @@ export default class TableList extends Plugin {
|
|||
|
||||
const statusBarNotifier = this.addStatusBarItem();
|
||||
|
||||
const statusBarText = statusBarNotifier.createEl('span');
|
||||
const statusBarText = statusBarNotifier.createEl("span");
|
||||
|
||||
this.addCommand({
|
||||
id: "open-table-list",
|
||||
name: "Open table list",
|
||||
callback: () => {
|
||||
this.activateTableView();
|
||||
},
|
||||
});
|
||||
|
||||
if (!(await data)) {
|
||||
data = {
|
||||
|
|
@ -62,24 +81,34 @@ export default class TableList extends Plugin {
|
|||
() => data,
|
||||
(data) => this.saveData(data),
|
||||
this.settings,
|
||||
statusBarText
|
||||
statusBarText,
|
||||
this.sharedState
|
||||
)
|
||||
);
|
||||
|
||||
this.addRibbonIcon("table-2", "Activate view", () => {
|
||||
this.activateView();
|
||||
this.addRibbonIcon("table-2", "Open table list", () => {
|
||||
this.activateTableView();
|
||||
});
|
||||
|
||||
// if (!this.app.vault.getFolderByPath("TableList")) {
|
||||
// this.app.vault.createFolder("TableList");
|
||||
// } else {
|
||||
// console.log("Folder already exists");
|
||||
// }
|
||||
this.addRibbonIcon("rectangle-horizontal", "Open task editor", () => {
|
||||
this.activateTaskEditView();
|
||||
});
|
||||
|
||||
this.registerView(
|
||||
TASK_EDITOR_VIEW_TYPE,
|
||||
(leaf) =>
|
||||
new TaskEditorView(
|
||||
leaf,
|
||||
this.sharedState,
|
||||
() => data,
|
||||
(data) => this.saveData(data)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async onunload() {}
|
||||
|
||||
async activateView() {
|
||||
async activateTableView() {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
|
|
@ -105,4 +134,31 @@ export default class TableList extends Plugin {
|
|||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
||||
|
||||
async activateTaskEditView() {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(TASK_EDITOR_VIEW_TYPE);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
// Our view could not be found in the workspace, create a new leaf
|
||||
// in the right sidebar for it
|
||||
leaf = workspace.getRightLeaf(false);
|
||||
if (leaf) {
|
||||
await leaf.setViewState({
|
||||
type: TASK_EDITOR_VIEW_TYPE,
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
if (leaf) {
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/main/sharedState.ts
Normal file
32
src/main/sharedState.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { useSyncExternalStore } from "react";
|
||||
|
||||
export interface SharedState<T> {
|
||||
get: () => T;
|
||||
set: (next: Partial<T>) => void;
|
||||
subscribe: (callback: () => void) => () => void;
|
||||
}
|
||||
|
||||
export function createSharedState<T extends object>(initialState: T): SharedState<T> {
|
||||
let current = { ...initialState };
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function get() {
|
||||
return current;
|
||||
}
|
||||
|
||||
function set(partial: Partial<T>) {
|
||||
current = { ...current, ...partial };
|
||||
for (const listener of listeners) listener();
|
||||
}
|
||||
|
||||
function subscribe(callback: () => void) {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
return { get, set, subscribe };
|
||||
}
|
||||
|
||||
export function useSharedState<T>(shared: SharedState<T>): T {
|
||||
return useSyncExternalStore(shared.subscribe, shared.get, shared.get);
|
||||
}
|
||||
|
|
@ -2,21 +2,23 @@ import { createContext, useContext } from "react";
|
|||
import { ItemView, WorkspaceLeaf, App } from "obsidian";
|
||||
import { Root, createRoot } from "react-dom/client";
|
||||
import AppComponent from "src/main/components/App";
|
||||
import { TableListSettings } from "../main";
|
||||
import { IntraViewData, TableListSettings } from "../main";
|
||||
import { SharedState } from "../sharedState";
|
||||
|
||||
export interface AppContext {
|
||||
export interface TableViewContext {
|
||||
app: App;
|
||||
loadData: () => unknown;
|
||||
saveData: (data: unknown) => Promise<void>;
|
||||
settings: TableListSettings;
|
||||
statusBarText: HTMLSpanElement;
|
||||
sharedState: SharedState<IntraViewData>;
|
||||
}
|
||||
|
||||
export const TABLE_VIEW_TYPE = "table-view";
|
||||
|
||||
export const TableContext = createContext<AppContext | undefined>(undefined);
|
||||
export const TableContext = createContext<TableViewContext | undefined>(undefined);
|
||||
|
||||
export const useTableContext = (): AppContext | undefined => {
|
||||
export const useTableContext = (): TableViewContext | undefined => {
|
||||
return useContext(TableContext);
|
||||
};
|
||||
|
||||
|
|
@ -27,19 +29,22 @@ export class TableView extends ItemView {
|
|||
saveData: (data: unknown) => Promise<void>;
|
||||
settings: TableListSettings;
|
||||
statusBarText: HTMLSpanElement;
|
||||
sharedState: SharedState<IntraViewData>;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
loadData: () => unknown,
|
||||
saveData: (data: unknown) => Promise<void>,
|
||||
settings: TableListSettings,
|
||||
statusBarText: HTMLSpanElement
|
||||
statusBarText: HTMLSpanElement,
|
||||
sharedState: SharedState<IntraViewData>
|
||||
) {
|
||||
super(leaf);
|
||||
this.loadData = loadData;
|
||||
this.saveData = saveData;
|
||||
this.settings = settings;
|
||||
this.statusBarText = statusBarText;
|
||||
this.sharedState = sharedState;
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
|
|
@ -64,6 +69,7 @@ export class TableView extends ItemView {
|
|||
loadData: this.loadData,
|
||||
settings: this.settings,
|
||||
statusBarText: this.statusBarText,
|
||||
sharedState: this.sharedState,
|
||||
}}
|
||||
>
|
||||
<AppComponent />
|
||||
|
|
|
|||
66
src/main/views/TaskEditorView.tsx
Normal file
66
src/main/views/TaskEditorView.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { createContext } from "react";
|
||||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import { Root, createRoot } from "react-dom/client";
|
||||
import TaskEditor from "../components/TaskEditor";
|
||||
import { SharedState } from "../sharedState";
|
||||
import { IntraViewData } from "../main";
|
||||
|
||||
export const TASK_EDITOR_VIEW_TYPE = "task-editor-sidebar-view";
|
||||
|
||||
// Context shared between React components
|
||||
export const TaskEditorContext = createContext<{
|
||||
loadData: () => unknown;
|
||||
saveData: (data: unknown) => Promise<void>;
|
||||
sharedState: SharedState<IntraViewData>;
|
||||
} | null>(null);
|
||||
|
||||
export class TaskEditorView extends ItemView {
|
||||
root: Root | null = null;
|
||||
|
||||
loadData: () => unknown;
|
||||
saveData: (data: unknown) => Promise<void>;
|
||||
sharedState: SharedState<IntraViewData>;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
sharedState: SharedState<IntraViewData>,
|
||||
loadData: () => unknown,
|
||||
saveData: (data: unknown) => Promise<void>
|
||||
) {
|
||||
super(leaf);
|
||||
this.loadData = loadData;
|
||||
this.saveData = saveData;
|
||||
this.sharedState = sharedState;
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return TASK_EDITOR_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "Task Editor";
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return "rectangle-horizontal";
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.root = createRoot(this.containerEl.children[1]);
|
||||
this.root.render(
|
||||
<TaskEditorContext.Provider
|
||||
value={{
|
||||
sharedState: this.sharedState,
|
||||
loadData: this.loadData,
|
||||
saveData: this.saveData,
|
||||
}}
|
||||
>
|
||||
<TaskEditor />
|
||||
</TaskEditorContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.root?.unmount();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue