Skip to content

Commit 1ce5d87

Browse files
committed
feat: local storage
1 parent f282abe commit 1ce5d87

File tree

3 files changed

+156
-18
lines changed

3 files changed

+156
-18
lines changed

src/app.js

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Store from "./store.js";
2+
13
const DEFAULT_SOURCE = `# Welcome to Ruby Next playground!
24
# Here you can write Ruby code and see how it will be transformed by Ruby Next.
35
# You can also execute it and see the result.
@@ -41,6 +43,9 @@ export default class App {
4143

4244
this.onSelectEditor = this.onSelectEditor.bind(this);
4345
this.invalidatePreview = this.invalidatePreview.bind(this);
46+
this.openSavedExamples = this.openSavedExamples.bind(this);
47+
48+
this.store = new Store();
4449
}
4550

4651
bootstrap() {
@@ -102,11 +107,61 @@ export default class App {
102107

103108
this.versionSelect = document.getElementById("versionSelect");
104109

105-
if (theme === "dark") this.versionSelect.classList.add("sl-theme-dark");
106-
107110
this.versionSelect.addEventListener("sl-change", this.invalidatePreview);
108111

112+
const saveDialog = document.getElementById("saveDialog");
113+
114+
if (saveDialog) {
115+
this.el
116+
.querySelector('[target="save-btn"]')
117+
.addEventListener("click", () => saveDialog.show());
118+
119+
saveDialog.addEventListener("submit", (e) => {
120+
e.preventDefault();
121+
122+
const input = e.target.querySelector('[name="name"]');
123+
const name = input.value;
124+
const code = this.codeEditor.getValue();
125+
const config = this.configEditor.getValue();
126+
127+
this.store.save(name, { code, config });
128+
129+
input.value = "";
130+
saveDialog.hide();
131+
});
132+
}
133+
134+
const openDialog = document.getElementById("openDialog");
135+
136+
if (openDialog) {
137+
this.el
138+
.querySelector('[target="open-btn"]')
139+
.addEventListener("click", this.openSavedExamples);
140+
141+
openDialog.addEventListener("click", (e) => {
142+
if (e.target.tagName !== "A") return;
143+
144+
e.preventDefault();
145+
146+
const key = e.target.dataset.key;
147+
148+
const example = this.store.fetch(key);
149+
150+
if (example) {
151+
const { code, config } = example;
152+
153+
this.codeEditor.setValue(code);
154+
this.configEditor.setValue(config);
155+
}
156+
157+
openDialog.hide();
158+
});
159+
}
160+
109161
this.setCurrentVMVersion();
162+
163+
if (theme === "dark")
164+
document.documentElement.classList.add("sl-theme-dark");
110165
}
111166

112167
transpile(code, opts = {}) {
@@ -201,4 +256,27 @@ export default class App {
201256

202257
this.showEditor(e.target.value);
203258
}
259+
260+
openSavedExamples() {
261+
const dialog = document.getElementById("openDialog");
262+
263+
if (!dialog) return;
264+
265+
const examples = this.store.all();
266+
267+
const content = dialog.querySelector('[target="list"]');
268+
269+
if (!examples.length) {
270+
content.innerHTML = `<p>No saved examples yet</p>`;
271+
} else {
272+
content.innerHTML = examples
273+
.map(
274+
(key) =>
275+
`<a class="text-blue-600 dark:text-blue-200 hover:text-blue-500 dark:text-blue-100 cursor-pointer py-2 inline-block" href="#" data-key="${key}">${key}</a>`
276+
)
277+
.join("");
278+
}
279+
280+
dialog.show();
281+
}
204282
}

src/index.html

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,38 @@ <h1 class="text-2xl">Playground</h1>
8686
</nav>
8787
<div class="flex flex-row flex-grow relative">
8888
<div id="editor_left_pane" class="flex flex-col w-1/2 relative">
89-
<div class="flex flex-row flex-grow text-sm bg-editor-light dark:bg-editor-dark">
90-
<label for="editor_left_pane_config" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
91-
<input type="radio" id="editor_left_pane_config" name="editor_left_pane" value="configEditor" class="hidden peer">
92-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
93-
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
94-
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
95-
</svg>
96-
<span class="ml-1">Config</span>
97-
</label>
98-
<label for="editor_left_pane_code" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
99-
<input type="radio" id="editor_left_pane_code" name="editor_left_pane" value="codeEditor" class="hidden peer" checked>
100-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
101-
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z" />
102-
</svg>
103-
<span class="ml-1">Code</span>
104-
</label>
89+
<div class="flex flex-row justify-between flex-grow text-sm bg-editor-light dark:bg-editor-dark">
90+
<div class="flex flex-row">
91+
<label for="editor_left_pane_config" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
92+
<input type="radio" id="editor_left_pane_config" name="editor_left_pane" value="configEditor" class="hidden peer">
93+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
94+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
95+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
96+
</svg>
97+
<span class="ml-1">Config</span>
98+
</label>
99+
<label for="editor_left_pane_code" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
100+
<input type="radio" id="editor_left_pane_code" name="editor_left_pane" value="codeEditor" class="hidden peer" checked>
101+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
102+
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z" />
103+
</svg>
104+
<span class="ml-1">Code</span>
105+
</label>
106+
</div>
107+
<div class="flex flex-row mr-4 space-x-2">
108+
<span target="open-btn" class="mb-1 p-1 flex flex-row items-center cursor-pointer border dark:border-editor-light border-editor-dark">
109+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
110+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776" />
111+
</svg>
112+
<span class="ml-1">Open</span>
113+
</span>
114+
<span target="save-btn" class="mb-1 p-1 flex flex-row items-center cursor-pointer border dark:border-editor-light border-editor-dark">
115+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
116+
<path stroke-linecap="round" stroke-linejoin="round" d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z" />
117+
</svg>
118+
<span class="ml-1">Save</span>
119+
</span>
120+
</div>
105121
</div>
106122
<div id="configEditor" data-pane="editor_left_pane" class="w-full h-full hidden"></div>
107123
<div id="codeEditor" data-pane="editor_left_pane" class="w-full h-full"></div>
@@ -128,6 +144,18 @@ <h1 class="text-2xl">Playground</h1>
128144
<div id="outputEditor" data-pane="editor_right_pane" class="w-full h-full hidden"></div>
129145
</div>
130146
</div>
147+
<sl-dialog id="saveDialog" label="Save example" class="dialog-overview">
148+
You can save your example locally in your browser. It will be available only on this device.
149+
<form class="mt-2 flex flex-col space-y-2">
150+
<sl-input name="name" placeholder="Example name" required></sl-input>
151+
<sl-button type="submit" submit>Save</sl-button>
152+
</form>
153+
</sl-dialog>
154+
<sl-dialog id="openDialog" label="Open example" class="dialog-overview">
155+
<div target="list" class="flex flex-col space-y-2">
156+
<a class="text-blue-600 dark:text-blue-200 hover:text-blue-500 dark:text-blue-100 cursor-pointer" href="#" data-key="${key}">${key}</a>
157+
</div>
158+
</sl-dialog>
131159
</div>
132160
</main>
133161
<script type="module" src="index.js" disabled></script>

src/store.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export default class Store {
2+
constructor(prefix = "next/v1") {
3+
this.prefix = prefix;
4+
}
5+
6+
all() {
7+
const val = localStorage.getItem(`${this.prefix}/$all`);
8+
if (val) {
9+
return JSON.parse(val);
10+
} else {
11+
return [];
12+
}
13+
}
14+
15+
fetch(key) {
16+
const val = localStorage.getItem(`${this.prefix}/${key}`);
17+
if (val) {
18+
return JSON.parse(val);
19+
} else {
20+
return null;
21+
}
22+
}
23+
24+
save(key, value) {
25+
localStorage.setItem(`${this.prefix}/${key}`, JSON.stringify(value));
26+
27+
const all = this.all();
28+
all.push(key);
29+
30+
localStorage.setItem(`${this.prefix}/$all`, JSON.stringify(all));
31+
}
32+
}

0 commit comments

Comments
 (0)