Skip to content

Commit b488e2a

Browse files
author
Mauve Signweaver
committed
feat: Add Dweb Scratchpad app
1 parent 21aaa4c commit b488e2a

3 files changed

Lines changed: 253 additions & 1 deletion

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
*.html
21
*.gmi
32
style.css
43
!docs/examples/**

apps/scratchpad.html

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<!DOCTYPE html>
2+
<meta lang="en">
3+
<meta charset="UTF-8">
4+
<title>DWeb Scratchpad</title>
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta name="description" content="Quickly put together a p2p app with live preview">
7+
<link rel="stylesheet" href="agregore://theme/style.css">
8+
<style>
9+
textarea {
10+
min-height: 25vh;
11+
}
12+
.closeButton {
13+
position: absolute;
14+
top: 2px;
15+
right: 2px;
16+
}
17+
form > *, label > * {
18+
display: block;
19+
margin: 0.5em;
20+
}
21+
h1 {
22+
font-size: 1rem;
23+
display: inline;
24+
}
25+
textarea, iframe {
26+
margin: 0px;
27+
}
28+
</style>
29+
<header>
30+
<h1>DWeb Scratchpad</h1>
31+
<button
32+
title="Save your scratchpad to the dweb"
33+
onclick="saveDialog.showModal()"
34+
>💾</button>
35+
<button
36+
title="Load and edit a site from a link"
37+
onclick="loadDialog.showModal()"
38+
>📂</button>
39+
<button
40+
title="Look at your saved sites"
41+
onclick="window.showAllSaved()"
42+
>📃</button>
43+
<button
44+
title="Help"
45+
onclick="helpDialog.showModal()"
46+
></button>
47+
</header>
48+
<main id="mainContents">
49+
50+
<iframe id="previewFrame"></iframe>
51+
<textarea autofocus id="htmlContent">
52+
<marquee>🧑‍💻HTML content here📃</marquee>
53+
</textarea>
54+
<textarea id="cssContent">
55+
/*CSS here*/
56+
marquee {
57+
color: var(--ag-theme-primary);
58+
font-weight: bold;
59+
font-size: 3em;
60+
}
61+
</textarea>
62+
<textarea id="jsContent">
63+
// JavaScript here
64+
console.log("Hello World!")
65+
</textarea>
66+
67+
</main>
68+
<dialog id="saveDialog">
69+
<h2>Save to the dweb</h2>
70+
<form id="saveForm">
71+
<label>
72+
Title
73+
<input id="appTitle" name="title" value="Scratchpad App">
74+
</label>
75+
<label>
76+
Filename (.html)
77+
<input id="appFilename" name="filename" value="example.html">
78+
</label>
79+
<label>
80+
Description
81+
<textarea id="appDescription" name="description">My quick scratch</textarea>
82+
</label>
83+
<button>Save and View</button>
84+
</form>
85+
<button class="closeButton" title="Close dialog" onclick="this.parentElement.close()"></button>
86+
</dialog>
87+
<dialog id="loadDialog">
88+
<form id="loadForm">
89+
<label>
90+
Page URL
91+
<input name="url" type="url">
92+
<button>Load 📂</button>
93+
</label>
94+
</form>
95+
<button class="closeButton" title="Close dialog" onclick="this.parentElement.close()"></button>
96+
</dialog>
97+
<dialog id="helpDialog">
98+
<h2>How does this work?</h2>
99+
<p>
100+
This is a scratchpad for making p2p apps. Enter some HTML/CSS/JavaScript and see a preview in real time. You can save the result to your collection or try to load a site somebody else published from their link.
101+
</p>
102+
<p>
103+
If you're new to Agregore, check out our <a href="hyper://agregore.mauve.moe/docs/">docs</a> and if you're new to web programming check out the <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core">Mozilla Developer Network</a> for tutorials and documentation on web development.
104+
</p>
105+
<p>
106+
Once you've set up Ollama you can use the <a href="hyper://agregore.mauve.moe/docs/examples/quickcode.html">Quickcode</a> AI example to help generate code snippets for you. This can help you learn new syntax like "Css for a red box that is half the screen" or "open the camera and render the video to a video tag".
107+
</p>
108+
<p>
109+
If you feel like this page should have more features try opening it within itself and tinkering with the code!
110+
</p>
111+
<button class="closeButton" title="Close dialog" onclick="this.parentElement.close()"></button>
112+
</dialog>
113+
<script type="module">
114+
window.showAllSaved = async () => {
115+
const url = await getDriveURL()
116+
window.open(url)
117+
}
118+
119+
const shouldLoad = new URL(location.href).searchParams.get('url')
120+
121+
// Triggered when we generate blobs for the page content
122+
// FileReader converts them to data URLs
123+
const previewReader = new FileReader()
124+
previewReader.onload = (e) => previewFrame.src = e.target.result
125+
126+
mainContents.addEventListener('input', updatePreview)
127+
128+
loadForm.onsubmit = (e) => {
129+
e.preventDefault(e)
130+
console.log(loadForm)
131+
const url = new FormData(loadForm).get('url')
132+
loadPage(url)
133+
loadDialog.close()
134+
}
135+
136+
saveForm.onsubmit = (e) => {
137+
e.preventDefault()
138+
const formData = new FormData(saveForm)
139+
saveDialog.close()
140+
let filename = formData.get('filename')
141+
if(!filename.endsWith('.html')) {
142+
filename+= '.html'
143+
}
144+
saveAndPublish(filename)
145+
.then((url) => window.open(url))
146+
}
147+
148+
if(shouldLoad) {
149+
console.log("Loading", shouldLoad)
150+
loadPage(shouldLoad)
151+
} else {
152+
setPreview(genPage())
153+
}
154+
155+
async function getDriveURL() {
156+
const name = "dweb_scratchpad"
157+
const response = await fetch(`hyper://localhost/?key=${name}`, { method: 'POST' });
158+
if (!response.ok) {
159+
throw new Error(`Failed to generate Hyperdrive key: ${response.statusText}`);
160+
}
161+
return await response.text()
162+
}
163+
164+
async function saveAndPublish(filename) {
165+
const driveURL = await getDriveURL()
166+
const url = new URL(filename, driveURL).href
167+
const content = genPage()
168+
const response = await fetch(url, {
169+
method: 'PUT',
170+
body: content
171+
})
172+
if(!response.ok) {
173+
throw new Error(`Failed to upload ${await response.text()}`)
174+
}
175+
await response.text()
176+
return url
177+
}
178+
179+
function updatePreview() {
180+
setPreview(genPage())
181+
}
182+
183+
async function setPreview(content) {
184+
const blob = new Blob([content], {type:"text/html"})
185+
previewReader.readAsDataURL(blob)
186+
}
187+
188+
async function loadPage(url) {
189+
console.log('loading page', url)
190+
const response = await fetch(url)
191+
if(!response.ok) throw new Error(`Unable to load page:\n${await response.text()}`)
192+
const text = await response.text()
193+
const {title, description, html, js, css} = extractPage(text)
194+
console.log({title, description, html, js, css})
195+
196+
const {pathname} = new URL(url)
197+
const filename = pathname.split('/').at(-1)
198+
199+
appTitle.value = title
200+
appDescription.value = description
201+
appFilename.value = filename
202+
htmlContent.value = html
203+
cssContent.value = css
204+
jsContent.value = js
205+
206+
setPreview(genPage())
207+
}
208+
209+
function extractPage(content) {
210+
console.log('Extracting', {content})
211+
const parser = new DOMParser()
212+
const doc = parser.parseFromString(content, 'text/html');
213+
const title = doc.title
214+
const description = doc.head.querySelector('meta[name=description]')
215+
const css = doc.head.querySelector('style').innerText.trim()
216+
const script = doc.head.querySelector('script') || doc.body.querySelector('script')
217+
const js = script.innerText.trim()
218+
219+
for(const script of doc.querySelectorAll('script')) {
220+
script.parentElement.removeChild(script)
221+
}
222+
223+
const html = doc.body.innerHTML.trim()
224+
225+
return {title, description, css, html, js}
226+
}
227+
228+
function genPage({
229+
title=appTitle.value,
230+
description=appDescription.value,
231+
html=htmlContent.value,
232+
css=cssContent.value,
233+
js=jsContent.value
234+
}={}) {
235+
return `
236+
<!DOCTYPE html>
237+
<meta lang="en">
238+
<meta charset="UTF-8">
239+
<title>${title}</title>
240+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
241+
<meta name="description" content="${description}">
242+
<link rel="stylesheet" href="agregore://theme/style.css">
243+
<style>
244+
${css}
245+
</style>
246+
${html}
247+
<script type="module">
248+
${js}
249+
</${"script"}>
250+
`
251+
}
252+
</script>

explore.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Feel free to [submit a pull request](https://github.com/AgregoreWeb/website/) on
66
### Demos and tools:
77

88
- [Agregore Docs](/docs/)
9+
- [DWeb Scratchpad](/apps/scratchpad.html)
910
- [Drag and Drop File Uploads](/docs/examples/drag-and-drop/)
1011
- [Theme Builder](/docs/examples/themebuilder)
1112
- [Sutty CMS, static site generator with dweb publishing](https://sutty.nl/en/)

0 commit comments

Comments
 (0)