Skip to content

Commit 76069a5

Browse files
committed
Project screen improvements, date sort, delete
1 parent e525b63 commit 76069a5

File tree

8 files changed

+182
-116
lines changed

8 files changed

+182
-116
lines changed

src/ProjectPageRouting.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ReactNode } from "react"
22
import { useRouterState } from "./router-hooks"
3-
import ProjectBrowser from "./project-persistence/ProjectBrowser";
3+
import ProjectBrowser from "./project/ProjectBrowser";
44

55
interface ProjectPageRoutingProps {
66
children: ReactNode

src/editor/EditorContainer.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,26 @@ const EditorContainer = ({ selection }: EditorContainerProps) => {
2626
setSettings({ ...settings, warnForApiUnsupportedByDevice: false });
2727
}, [setSettings, settings]);
2828
// Note fileInfo is not updated for ordinary text edits.
29-
const [fileInfo, _] = useProjectFileText(selection.file);
29+
const [fileInfo, onFileChange] = useProjectFileText(selection.file);
3030
const { ydoc, getFile } = useProjectStorage();
3131

3232
const ytext = getFile(selection.file);
3333

3434
// TODO: Overengineered until we have sync in mind
3535
const awareness = useMemo(() => {
3636
if (!ydoc) return null;
37-
return new Awareness(ydoc)
37+
return new Awareness(ydoc);
3838
}, [ydoc]);
3939

4040
if (ytext === null) return null;
4141
if (fileInfo === undefined) {
4242
return null;
4343
}
4444

45-
awareness!.setLocalStateField("user", { name: "micro:bit tester", color: "yellow" });
45+
awareness!.setLocalStateField("user", {
46+
name: "micro:bit tester",
47+
color: "yellow",
48+
});
4649

4750
// TODO: represent fileInfo in project?
4851

@@ -52,6 +55,7 @@ const EditorContainer = ({ selection }: EditorContainerProps) => {
5255
) : (
5356
<Editor
5457
selection={selection}
58+
onChange={onFileChange}
5559
fontSize={settings.fontSize}
5660
codeStructureOption={settings.codeStructureHighlight}
5761
parameterHelpOption={settings.parameterHelp}

src/editor/codemirror/CodeMirror.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { yCollab } from "y-codemirror.next";
4747

4848
interface CodeMirrorProps {
4949
className?: string;
50+
onChange: (doc: string) => void;
5051
text: Y.Text;
5152
awareness: Awareness;
5253
selection: WorkbenchSelection;
@@ -67,6 +68,7 @@ interface CodeMirrorProps {
6768
*/
6869
const CodeMirror = ({
6970
className,
71+
onChange,
7072
text,
7173
awareness,
7274
selection,
@@ -121,6 +123,7 @@ const CodeMirror = ({
121123
textRef.current = text;
122124
const notify = EditorView.updateListener.of((update) => {
123125
if (update.docChanged) {
126+
onChange(update.state.sliceDoc(0));
124127
setEditorInfo({
125128
undo: undoDepth(view.state),
126129
redo: redoDepth(view.state),
@@ -182,6 +185,7 @@ const CodeMirror = ({
182185
client,
183186
intl,
184187
logging,
188+
onChange,
185189
options,
186190
setActiveEditor,
187191
setEditorInfo,
@@ -192,7 +196,7 @@ const CodeMirror = ({
192196
apiReferenceMap,
193197
device,
194198
disableV2OnlyFeaturesWarning,
195-
text
199+
text,
196200
]);
197201
useEffect(() => {
198202
// Do this separately as we don't want to destroy the view whenever options needed for initialization change.
@@ -242,7 +246,7 @@ const CodeMirror = ({
242246
uri,
243247
apiReferenceMap,
244248
device,
245-
disableV2OnlyFeaturesWarning
249+
disableV2OnlyFeaturesWarning,
246250
]);
247251

248252
const { location } = selection;

src/project-persistence/ProjectBrowser.tsx

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { CloseButton, GridItem, Heading, HStack, Text } from "@chakra-ui/react";
2+
import { ReactNode } from "react";
3+
import { ProjectEntry } from "./project-list-db";
4+
import { timeAgo } from "./utils";
5+
6+
interface ProjectItemProps {
7+
project: ProjectEntry;
8+
loadProject: (projectId: string) => void;
9+
deleteProject: (projectId: string) => void;
10+
}
11+
12+
interface ProjectItemBaseProps {
13+
children: ReactNode;
14+
onClick: () => void;
15+
}
16+
17+
const ProjectItemBase = ({ onClick, children }: ProjectItemBaseProps) => (
18+
<GridItem
19+
role="button"
20+
bgColor="white"
21+
display="block"
22+
position="relative"
23+
mx={[0, 2, 5]}
24+
my={[0, 2, 10]}
25+
borderRadius={[0, "20px"]}
26+
borderWidth={[null, 1]}
27+
borderBottomWidth={1}
28+
borderColor={[null, "gray.300"]}
29+
py={[5, 8]}
30+
px={[3, 5, 8]}
31+
w="21rem"
32+
h="15rem"
33+
alignItems="stretch"
34+
onClick={onClick}
35+
>
36+
{children}
37+
</GridItem>
38+
);
39+
40+
export const ProjectItem = ({project, loadProject, deleteProject}: ProjectItemProps) => (
41+
<ProjectItemBase onClick={() => loadProject(project.id)}>
42+
<HStack justifyContent="space-between" w="100%">
43+
<Heading as="h2" isTruncated>
44+
{project.projectName}
45+
</Heading>
46+
</HStack>
47+
<Text size="lg">{timeAgo(new Date(project.modifiedDate))}</Text>
48+
49+
<CloseButton
50+
position="absolute"
51+
right={4}
52+
top={4}
53+
onClick={(e) => {
54+
deleteProject(project.id);
55+
e.stopPropagation();
56+
e.preventDefault();
57+
}}
58+
/>
59+
</ProjectItemBase>
60+
)
61+
62+
interface AddProjectItemProps {
63+
newProject: () => void;
64+
}
65+
66+
export const AddProjectItem = ({newProject}: AddProjectItemProps) =>
67+
<ProjectItemBase
68+
onClick={newProject}
69+
>
70+
<HStack justifyContent="space-between" w="100%">
71+
<Heading as="h2">New project</Heading>
72+
</HStack>
73+
<Text size="lg">Click to create</Text>
74+
</ProjectItemBase>

src/project-persistence/ProjectStorageProvider.tsx

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface ProjectContextValue {
2626
projectList: ProjectList | null;
2727
newStoredProject: () => Promise<NewStoredDoc>;
2828
restoreStoredProject: (id: string) => Promise<RestoredStoredDoc>;
29+
deleteProject: (id: string) => Promise<void>;
2930
ydoc: Y.Doc | null;
3031
awareness: Awareness | null;
3132
getFile: (filename: string) => Y.Text | null;
@@ -54,7 +55,9 @@ export function ProjectStorageProvider({
5455
projectId: string
5556
) => Promise<RestoredStoredDoc> = useCallback(
5657
async (projectId: string) => {
57-
const newProjectStore = new ProjectStore(projectId);
58+
const newProjectStore = new ProjectStore(projectId, () =>
59+
modifyProject(projectId)
60+
);
5861
await newProjectStore.init();
5962
setProjectStore(newProjectStore);
6063
return {
@@ -77,33 +80,46 @@ export function ProjectStorageProvider({
7780
});
7881
return Promise.resolve();
7982
});
80-
const newProjectStore = new ProjectStore(newProjectId);
83+
const newProjectStore = new ProjectStore(newProjectId, () =>
84+
modifyProject(newProjectId)
85+
);
8186
await newProjectStore.init();
8287
setProjectStore(newProjectStore);
8388
return { ydoc: newProjectStore.ydoc, id: newProjectId };
8489
}, []);
8590

91+
const deleteProject: (id: string) => Promise<void> = useCallback(
92+
async (id) => {
93+
await withProjectDb("readwrite", async (store) => {
94+
store.delete(id);
95+
return refreshProjects();
96+
});
97+
},
98+
[]
99+
);
100+
86101
// TODO: Get rid of debug hooks
87102
(window as unknown as any).projectList = projectList;
88103
(window as unknown as any).newProjectStore = newStoredProject;
89104
(window as unknown as any).restoreProjectStore = restoreStoredProject;
105+
(window as unknown as any).deleteProject = deleteProject;
106+
107+
const refreshProjects = async () => {
108+
const projectList = await withProjectDb("readonly", async (store) => {
109+
const projectList = await new Promise((res, rej) => {
110+
const query = store.index("modifiedDate").getAll();
111+
query.onsuccess = () => res(query.result);
112+
});
113+
return projectList;
114+
});
115+
setProjectList((projectList as ProjectList).reverse());
116+
};
90117

91118
useEffect(() => {
92119
if (window.navigator.storage?.persist) {
93120
window.navigator.storage.persist();
94121
}
95-
const getProjectsAsync = async () => {
96-
const projectList = await withProjectDb("readonly", async (store) => {
97-
const projectList = await new Promise((res, rej) => {
98-
const query = store.index("modifiedDate").getAll();
99-
query.onsuccess = () => res(query.result);
100-
query.onerror = rej;
101-
});
102-
return projectList;
103-
});
104-
setProjectList((projectList as ProjectList).reverse());
105-
};
106-
void getProjectsAsync();
122+
void refreshProjects();
107123
}, []);
108124

109125
// Helper to access files
@@ -116,6 +132,24 @@ export function ProjectStorageProvider({
116132
return files.get(filename)!;
117133
};
118134

135+
const modifyProject = useCallback(
136+
async (id: string) => {
137+
await withProjectDb("readwrite", async (store) => {
138+
await new Promise((res, rej) => {
139+
const getQuery = store.get(id);
140+
getQuery.onsuccess = () => {
141+
const putQuery = store.put({
142+
...getQuery.result,
143+
modifiedDate: new Date().valueOf(),
144+
});
145+
putQuery.onsuccess = () => res(getQuery.result);
146+
};
147+
});
148+
});
149+
},
150+
[projectStore]
151+
);
152+
119153
const setProjectName = useCallback(
120154
async (id: string, projectName: string) => {
121155
await withProjectDb("readwrite", async (store) => {
@@ -126,7 +160,6 @@ export function ProjectStorageProvider({
126160
modifiedDate: new Date().valueOf(),
127161
});
128162
query.onsuccess = () => res(query.result);
129-
query.onerror = rej;
130163
});
131164
});
132165
},
@@ -143,6 +176,7 @@ export function ProjectStorageProvider({
143176
getFile,
144177
newStoredProject,
145178
restoreStoredProject,
179+
deleteProject,
146180
setProjectName,
147181
}}
148182
>

0 commit comments

Comments
 (0)