Skip to content

Commit 9cbbaf4

Browse files
committed
begin adding anonymous repls
1 parent b005cdc commit 9cbbaf4

File tree

5 files changed

+134
-110
lines changed

5 files changed

+134
-110
lines changed

playground/pages/edit.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,14 @@ export const Edit = (props: { dark: boolean; horizontal: boolean }) => {
6464
context.setTabs(tabs);
6565
const [current, setCurrent] = createSignal<string>();
6666
const [resource, { mutate }] = createResource<APIRepl, string>(params.repl, async (repl) => {
67-
let output: APIRepl = await fetch(`${API}/repl/${repl}`, {
68-
headers: { authorization: context.token ? `Bearer ${context.token}` : '' },
69-
}).then((r) => r.json());
67+
let output: APIRepl;
68+
if (params.user == 'local') {
69+
output = JSON.parse(localStorage.getItem(repl)!);
70+
} else {
71+
output = await fetch(`${API}/repl/${repl}`, {
72+
headers: { authorization: context.token ? `Bearer ${context.token}` : '' },
73+
}).then((r) => r.json());
74+
}
7075

7176
batch(() => {
7277
setTabs(
@@ -84,8 +89,22 @@ export const Edit = (props: { dark: boolean; horizontal: boolean }) => {
8489
const tabMapper = (tabs: Tab[]) => tabs.map((x) => ({ name: x.name, content: x.source.split('\n') }));
8590
const updateRepl = debounce(() => {
8691
const repl = resource.latest;
87-
if (!repl || !context.token || context.user()?.display != params.user) return;
92+
if (!repl) return;
8893
const files = tabMapper(tabs());
94+
if (params.user == 'local') {
95+
localStorage.setItem(
96+
params.repl,
97+
JSON.stringify({
98+
title: repl.title,
99+
version: repl.version,
100+
public: repl.public,
101+
labels: repl.labels,
102+
files,
103+
}),
104+
);
105+
return;
106+
}
107+
if (!context.token || context.user()?.display != params.user) return;
89108
fetch(`${API}/repl/${params.repl}`, {
90109
method: 'PUT',
91110
headers: {
@@ -104,6 +123,7 @@ export const Edit = (props: { dark: boolean; horizontal: boolean }) => {
104123

105124
createEffect(() => {
106125
tabMapper(tabs()); // use the latest value on debounce, and just throw this value away (but use it to track)
126+
resource();
107127
if (loaded) updateRepl();
108128
});
109129

@@ -142,22 +162,6 @@ export const Edit = (props: { dark: boolean; horizontal: boolean }) => {
142162
value={resource()?.title || ''}
143163
onChange={(e) => {
144164
mutate((x) => x && { ...x, title: e.currentTarget.value });
145-
const repl = resource.latest!;
146-
const files = tabMapper(tabs());
147-
fetch(`${API}/repl/${params.repl}`, {
148-
method: 'PUT',
149-
headers: {
150-
'authorization': `Bearer ${context.token}`,
151-
'Content-Type': 'application/json',
152-
},
153-
body: JSON.stringify({
154-
title: e.currentTarget.value,
155-
version: repl.version,
156-
public: repl.public,
157-
labels: repl.labels,
158-
files: files,
159-
}),
160-
});
161165
}}
162166
/>
163167
</RenderHeader>

playground/pages/home.tsx

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useNavigate, useParams } from 'solid-app-router';
22
import { Icon } from 'solid-heroicons';
33
import { eye, eyeOff, plus, x } from 'solid-heroicons/outline';
4-
import { createResource, For, Suspense } from 'solid-js';
4+
import { createResource, For, Show, Suspense } from 'solid-js';
55
import { createStore, produce } from 'solid-js/store';
66
import { defaultTabs } from '../../src';
77
import { API, useAppContext } from '../context';
@@ -55,6 +55,21 @@ export const Home = () => {
5555
<button
5656
class="bg-solid-lightgray shadow-md dark:bg-solid-darkLighterBg rounded-xl p-4 text-3xl flex mx-auto"
5757
onClick={async () => {
58+
if (!params.user && !context.user()?.display) {
59+
let id = Math.floor(Date.now() / 1000).toString();
60+
localStorage.setItem(
61+
id,
62+
JSON.stringify({
63+
title: 'Counter Example',
64+
public: true,
65+
labels: [],
66+
version: '1.0',
67+
files: defaultTabs.map((x) => ({ name: x.name, content: x.source.split('\n') })),
68+
}),
69+
);
70+
navigate(`/local/${id}`);
71+
return;
72+
}
5873
const result = await fetch(`${API}/repl`, {
5974
method: 'POST',
6075
headers: {
@@ -73,7 +88,8 @@ export const Home = () => {
7388
navigate(`/${context.user()?.display}/${id}`);
7489
}}
7590
>
76-
<Icon path={plus} class="w-8" /> Create new REPL
91+
<Icon path={plus} class="w-8" />{' '}
92+
{params.user || context.user()?.display ? 'Create new REPL' : 'Create Anonymous REPL'}
7793
</button>
7894

7995
<table class="w-128 mx-auto my-8">
@@ -106,56 +122,67 @@ export const Home = () => {
106122
</tr>
107123
}
108124
>
109-
<For each={get(repls.list)}>
110-
{(repl, i) => (
125+
<Show
126+
when={params.user || context.user()?.display}
127+
fallback={
111128
<tr>
112-
<td>
113-
<a href={`${params.user || context.user()?.display}/${repl.id}`}>{repl.title}</a>
114-
</td>
115-
<td>{new Date(repl.created_at).toLocaleString()}</td>
116-
<td>
117-
<Icon
118-
path={repl.public ? eye : eyeOff}
119-
class="w-6 inline m-2 ml-0 cursor-pointer"
120-
onClick={async () => {
121-
fetch(`${API}/repl/${repl.id}`, {
122-
method: 'PUT',
123-
headers: {
124-
'authorization': `Bearer ${context.token}`,
125-
'Content-Type': 'application/json',
126-
},
127-
body: JSON.stringify({
128-
...repl,
129-
public: !repl.public,
130-
}),
131-
});
132-
setRepls(
133-
produce((x) => {
134-
x!.list[i()].public = !repl.public;
135-
}),
136-
);
137-
}}
138-
/>
139-
<Icon
140-
path={x}
141-
class="w-6 inline m-2 mr-0 text-red-700 cursor-pointer"
142-
onClick={async () => {
143-
fetch(`${API}/repl/${repl.id}`, {
144-
method: 'DELETE',
145-
headers: {
146-
authorization: `Bearer ${context.token}`,
147-
},
148-
});
149-
setRepls({
150-
total: repls.total - 1,
151-
list: repls.list.filter((x) => x.id !== repl.id),
152-
});
153-
}}
154-
/>
129+
<td colspan="3" class="text-center">
130+
Not logged in. Please login to see your repls.
155131
</td>
156132
</tr>
157-
)}
158-
</For>
133+
}
134+
>
135+
<For each={get(repls.list)}>
136+
{(repl, i) => (
137+
<tr>
138+
<td>
139+
<a href={`${params.user || context.user()?.display}/${repl.id}`}>{repl.title}</a>
140+
</td>
141+
<td>{new Date(repl.created_at).toLocaleString()}</td>
142+
<td>
143+
<Icon
144+
path={repl.public ? eye : eyeOff}
145+
class="w-6 inline m-2 ml-0 cursor-pointer"
146+
onClick={async () => {
147+
fetch(`${API}/repl/${repl.id}`, {
148+
method: 'PUT',
149+
headers: {
150+
'authorization': `Bearer ${context.token}`,
151+
'Content-Type': 'application/json',
152+
},
153+
body: JSON.stringify({
154+
...repl,
155+
public: !repl.public,
156+
}),
157+
});
158+
setRepls(
159+
produce((x) => {
160+
x!.list[i()].public = !repl.public;
161+
}),
162+
);
163+
}}
164+
/>
165+
<Icon
166+
path={x}
167+
class="w-6 inline m-2 mr-0 text-red-700 cursor-pointer"
168+
onClick={async () => {
169+
fetch(`${API}/repl/${repl.id}`, {
170+
method: 'DELETE',
171+
headers: {
172+
authorization: `Bearer ${context.token}`,
173+
},
174+
});
175+
setRepls({
176+
total: repls.total - 1,
177+
list: repls.list.filter((x) => x.id !== repl.id),
178+
});
179+
}}
180+
/>
181+
</td>
182+
</tr>
183+
)}
184+
</For>
185+
</Show>
159186
</Suspense>
160187
</tbody>
161188
</table>

src/components/editor/monacoTabs.tsx

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,31 @@
1-
import { Component, createEffect, onCleanup } from 'solid-js';
1+
import { Component, createEffect, on, onCleanup } from 'solid-js';
22
import type { Tab } from '../..';
33
import { Uri, editor } from 'monaco-editor';
4-
import { keyedMap } from '../../utils/keyedMap';
4+
import { KeyedMap } from '../../utils/keyedMap';
55

66
const MonacoTabs: Component<{ folder: string; tabs: Tab[]; compiled: string }> = (props) => {
7-
const fileUri = Uri.parse(`file:///${props.folder}/output_dont_import.tsx`);
7+
const syncTab = (name: string, source: () => string) => {
8+
const uri = Uri.parse(`file:///${props.folder}/${name}`);
9+
const model = editor.createModel(source(), undefined, uri);
810

9-
const oldModel = editor.getModels().find((model) => model.uri.path === fileUri.path);
10-
if (oldModel) oldModel.dispose();
11+
let first = true;
12+
createEffect(
13+
on(source, (mysource) => {
14+
if (first) return (first = false);
15+
if (model.getValue() !== mysource) model.setValue(mysource);
16+
}),
17+
);
18+
onCleanup(() => model.dispose());
19+
};
1120

12-
const model = editor.createModel('', 'typescript', fileUri);
21+
syncTab('output_dont_import.tsx', () => props.compiled);
1322

14-
createEffect(() => {
15-
model.setValue(props.compiled);
16-
});
17-
onCleanup(() => model.dispose());
18-
19-
keyedMap<Tab>({
20-
by: (tab) => tab.name,
21-
get each() {
22-
return props.tabs;
23-
},
24-
children: (tab) => {
25-
const uri = Uri.parse(`file:///${props.folder}/${tab().name}`);
26-
27-
const model = editor.createModel(tab().source, undefined, uri);
28-
29-
let first = true;
30-
createEffect(() => {
31-
const source = tab().source;
32-
if (!first && model.getValue() !== source) model.setValue(source);
33-
else first = false;
34-
});
35-
onCleanup(() => model.dispose());
36-
},
37-
});
38-
return <></>;
23+
return (
24+
<KeyedMap by={(tab) => tab.name} each={props.tabs}>
25+
{(tab) => {
26+
syncTab(tab().name, () => tab().source);
27+
}}
28+
</KeyedMap>
29+
);
3930
};
4031
export default MonacoTabs;

src/components/repl.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ const Repl: ReplProps = (props) => {
3636
if (idx < 0) return;
3737
props.setCurrent(current);
3838
}
39-
function setTabName(id1: string, name: string) {
40-
const idx = props.tabs.findIndex((tab) => tab.name === id1);
39+
function setTabName(name: string, newName: string) {
40+
const idx = props.tabs.findIndex((tab) => tab.name === name);
4141
if (idx < 0) return;
4242

4343
const tabs = props.tabs;
44-
tabs[idx] = { ...tabs[idx], name };
44+
tabs[idx] = { ...tabs[idx], name: newName };
4545
batch(() => {
4646
props.setTabs(tabs);
47-
if (props.current === id1) {
48-
props.setCurrent(tabs[idx].name);
47+
if (props.current === name) {
48+
props.setCurrent(newName);
4949
}
5050
});
5151
}
52-
function removeTab(id1: string) {
52+
function removeTab(name: string) {
5353
const tabs = props.tabs;
54-
const idx = tabs.findIndex((tab) => tab.name === id1);
54+
const idx = tabs.findIndex((tab) => tab.name === name);
5555
const tab = tabs[idx];
5656

5757
if (!tab) return;
@@ -62,7 +62,7 @@ const Repl: ReplProps = (props) => {
6262
batch(() => {
6363
props.setTabs([...tabs.slice(0, idx), ...tabs.slice(idx + 1)]);
6464
// We want to redirect to another tab if we are deleting the current one
65-
if (props.current === id1) {
65+
if (props.current === name) {
6666
props.setCurrent(tabs[idx - 1].name);
6767
}
6868
});

src/utils/keyedMap.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { onCleanup, createEffect, createSignal, untrack, createRoot, Signal } fr
44
// This version is designed to call a function on creation and onCleanup on removal
55
// for a certain keying function (the by parameter)
66
// this does not return any values, and is designed as more of a hook
7-
export const keyedMap = <T>(props: { by: (a: T) => string; children: (a: () => T) => void; each: T[] }): void => {
7+
export const KeyedMap = <T>(props: { by: (a: T) => string; children: (a: () => T) => void; each: T[] }) => {
88
const key = props.by;
99
const mapFn = props.children;
1010
const disposers = new Map<string, () => void>();
@@ -13,7 +13,7 @@ export const keyedMap = <T>(props: { by: (a: T) => string; children: (a: () => T
1313
for (const disposer of disposers.values()) disposer();
1414
});
1515

16-
return createEffect(() => {
16+
createEffect(() => {
1717
const list = props.each || [];
1818
const newNodes = new Map<string, Signal<T>>();
1919
return untrack(() => {
@@ -40,4 +40,6 @@ export const keyedMap = <T>(props: { by: (a: T) => string; children: (a: () => T
4040
prev = newNodes;
4141
});
4242
});
43+
44+
return undefined;
4345
};

0 commit comments

Comments
 (0)