Skip to content

Commit 1fb340d

Browse files
committed
Template Release 1.0.0
0 parents  commit 1fb340d

19 files changed

+56910
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
public/build/

LICENSE

Lines changed: 663 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
[`pure | 📦`](https://github.com/telamon/create-pure)
2+
[`code style | standard`](https://standardjs.com/)
3+
# pico-seed
4+
5+
This is a [pico-stack](https://github.com/telamon/picostack) project template
6+
containing a minimal 'todo'-app using svelte
7+
as template engine.
8+
9+
(You can hack it to use any other framework/template engine if you want, svelte was just what i had lying around.)
10+
11+
[DEMO](https://pico-todo.surge.sh/)
12+
13+
## Getting started
14+
Create your project if you already haven't
15+
```bash
16+
npx degit telamon/picostack-template my-project
17+
```
18+
19+
Then start the local dev-server
20+
```bash
21+
yarn
22+
yarn dev
23+
```
24+
and navigate to [https://localhost:5000](https://localhost:5000)
25+
26+
Other included scripts:
27+
```bash
28+
yarn dev # Starts local dev-server
29+
yarn test # Blockend dev: runs unit tests
30+
yarn debug # Blockend dev: debugs unit tests
31+
yarn build # Builds production webapp
32+
yarn sinal # Starts a local swarm singaling server
33+
yarn surge # Publishes your app to surge.sh
34+
```
35+
36+
## Donations
37+
38+
```ad
39+
| __ \ Help Wanted! | | | | | |
40+
| | | | ___ ___ ___ _ __ | |_| | __ _| |__ ___ ___ ___
41+
| | | |/ _ \/ __/ _ \ '_ \| __| | / _` | '_ \/ __| / __|/ _ \
42+
| |__| | __/ (_| __/ | | | |_| |___| (_| | |_) \__ \_\__ \ __/
43+
|_____/ \___|\___\___|_| |_|\__|______\__,_|_.__/|___(_)___/\___|
44+
45+
If you're reading this it means that the docs are missing or in a bad state.
46+
47+
Writing and maintaining friendly and useful documentation takes
48+
effort and time.
49+
50+
51+
__How_to_Help____________________________________.
52+
| |
53+
| - Open an issue if you have questions! |
54+
| - Star this repo if you found it interesting |
55+
| - Fork off & help document <3 |
56+
| - Say Hi! :) https://discord.gg/8RMRUPZ9RS |
57+
|.________________________________________________|
58+
```
59+
60+
## License
61+
62+
[AGPL-3.0-or-later](./LICENSE)
63+
64+
2022 &#x1f12f; Tony Ivanov

blockend/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const { SimpleKernel } = require('picostack')
2+
const { mute } = require('piconuro')
3+
4+
const TasksSlice = require('./slices/tasks.js')
5+
6+
class Kernel extends SimpleKernel {
7+
constructor (db) {
8+
super(db) // Initialize superclass
9+
10+
// Register slices/reducers
11+
this.store.register(TasksSlice())
12+
}
13+
14+
async createTask (title = 'unnamed task') {
15+
const feed = await this.createBlock('task', {
16+
title,
17+
status: 'todo'
18+
})
19+
return feed.last.sig // Task id
20+
}
21+
22+
async setStatus (taskId, status) {
23+
const feed = await this.createBlock('update', {
24+
taskId, status
25+
})
26+
return feed.last.id // block id
27+
}
28+
29+
// Expose subscribable list of tasks
30+
$tasks () {
31+
const userId = this.pk
32+
33+
// Create neuron
34+
const $n = subscriber => this.store.on('tasks', subscriber)
35+
36+
// Enrich low-level state with high-level variables
37+
return mute(
38+
$n,
39+
tasks => tasks.map(task => ({
40+
...task,
41+
owner: userId?.equals(task.author),
42+
writable: true // TODO: !!task.accessList.find(k => k.equals(userId)
43+
}))
44+
// Sort by activity
45+
.sort((a, b) => b.updatedAt - a.updatedAt)
46+
)
47+
}
48+
}
49+
50+
module.exports = Kernel

blockend/slices/tasks.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const { decodeBlock } = require('picostack').SimpleKernel
2+
const ALLOWED_STATES = ['todo', 'in-progress', 'to-verify', 'done', 'wontfix']
3+
function TasksSlice () {
4+
return {
5+
name: 'tasks', // Sets the name of this slice/registry
6+
7+
initialValue: [], // Initial value slice value.
8+
9+
filter ({ block, state }) {
10+
const data = decodeBlock(block.body)
11+
const { type } = data
12+
13+
switch (type) {
14+
// Validate create task blocks
15+
case 'task': {
16+
const { title, date } = data
17+
// Reject task invalid blocks with a reason
18+
if (!title || title === '') return 'Task Must have a title'
19+
if (date > Date.now()) return 'Task from future'
20+
} break
21+
22+
// Validate update status blocks
23+
case 'update': {
24+
const { taskId, status } = data
25+
if (!ALLOWED_STATES.find(s => status)) return 'InvalidStatus'
26+
27+
// Validate block against existing state
28+
const task = state.find(t => t.id.equals(taskId))
29+
30+
if (!task) return 'TaskNotFound'
31+
if (task.status === status) return 'StatusNotNew'
32+
33+
// Give write access the author.
34+
// if (!task.author.equals(block.key)) return 'AccessDenied'
35+
// TODO: write access to friends
36+
} break
37+
38+
// Silently ignore unrelated blocks
39+
// to give other slices a chance to handle it.
40+
default:
41+
return true
42+
}
43+
44+
// Accept valid block
45+
return false
46+
},
47+
48+
// Apply block content to mutate state
49+
reducer ({ block, state }) {
50+
const tasks = [...state]
51+
const data = decodeBlock(block.body)
52+
const { type } = data
53+
54+
switch (type) {
55+
case 'task': {
56+
const task = data
57+
// Set task-id to the block-signature that created it.
58+
// this will be useful later when we attempt to update it's status.
59+
task.id = block.sig
60+
task.author = block.key
61+
task.updated = data.date
62+
63+
tasks.push(task) // Append task to list
64+
} break
65+
66+
case 'update': {
67+
const task = tasks.find(t => t.id.equals(data.taskId))
68+
task.updated = data.date
69+
task.status = data.status
70+
} break
71+
}
72+
return Object.freeze(tasks)
73+
}
74+
}
75+
}
76+
module.exports = TasksSlice

frontend/App.svelte

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script>
2+
import { boot } from './api'
3+
import Tasks from './Tasks.svelte'
4+
import Devbar from './DevelopersBar.svelte'
5+
6+
const bootloader = boot()
7+
</script>
8+
<main class="container">
9+
<!-- "Hello" section -->
10+
<section>
11+
<h1>PiCO<sub>stack<sub></h1>
12+
<h2 class="tagline"><small> 0% Backend, 10'000% Frontend</small></h2>
13+
<h2>Hello Web 3.0</h2>
14+
<p>
15+
This is the
16+
<bold><a href="https://github.com/telamon/picostack">pico-stack</a></bold>
17+
quickstart template, run:
18+
</p>
19+
<pre><code>
20+
&gt; npx degit <a href="https://github.com/telamon/pico-template">telamon/pico-template</a> my-project
21+
</code></pre>
22+
<p>
23+
and build your own <strong>fully decentralized</strong> web-app.
24+
</p>
25+
</section>
26+
27+
<!-- Wait for bootloader then show Tasks -->
28+
{#await bootloader}
29+
<h3>Loading kernel...</h3>
30+
<progress value="7" max="100" indeterminate="true"></progress>
31+
{:then}
32+
<Tasks/>
33+
{:catch error}
34+
<h3>Boot failure!</h3>
35+
<p>{error.name}</p>
36+
<pre>{error.stackTrace}</pre>
37+
{/await}
38+
39+
<!-- nice to have -->
40+
<Devbar/>
41+
42+
<!-- Remove/replace this line :) -->
43+
<footer>Pico ❤️ DecentLabs 2022</footer>
44+
</main>
45+
<style>
46+
h1 {
47+
text-align: center;
48+
font-size: 4em;
49+
color: var(--primary);
50+
margin-bottom: 0;
51+
}
52+
footer {
53+
text-align: center;
54+
margin-top: 3em;
55+
}
56+
.tagline { text-align: center; }
57+
.tagline small { color: gray; font-weight: normal; }
58+
59+
</style>

frontend/DevelopersBar.svelte

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script>
2+
/**
3+
It was fun writing this component, keep if useful.
4+
*/
5+
6+
// Keep this component or throw it away, It was fun
7+
import {
8+
purge,
9+
run,
10+
connections,
11+
kernel,
12+
svlt
13+
} from './api.js'
14+
import { mute, combine } from 'piconuro'
15+
16+
17+
// When reducer was changed rerun available blocks
18+
async function reloadStore () {
19+
await kernel.store.reload()
20+
.then(() => console.info('Reloaded store successfully'))
21+
.catch(error => {
22+
console.error('Reload failed:', error)
23+
})
24+
}
25+
26+
// https://stackoverflow.com/questions/1248302/how-to-get-the-size-of-a-javascript-object
27+
function roughSizeOf(object) {
28+
const objectList = []
29+
const stack = [ object ]
30+
let bytes = 0
31+
while (stack.length) {
32+
const value = stack.pop()
33+
if (typeof value === 'boolean') bytes += 4
34+
else if (typeof value === 'string') bytes += value.length * 2
35+
else if (typeof value === 'number') bytes += 8
36+
else if (
37+
typeof value === 'object'
38+
&& objectList.indexOf(value) === -1
39+
) {
40+
objectList.push(value)
41+
for(const i in value ) stack.push(value[i])
42+
}
43+
}
44+
return bytes
45+
}
46+
const sliceNames = Object.keys(kernel.store.state)
47+
const combinedStateNeuron = combine(
48+
...sliceNames.map(name => sub => kernel.store.on(name, sub))
49+
)
50+
51+
const stateMemory = svlt(
52+
mute(
53+
mute(
54+
combinedStateNeuron,
55+
states => states.reduce((sum, state) => sum + roughSizeOf(state), 0)
56+
),
57+
bytes => (bytes / 1024).toFixed(2) // kilos
58+
)
59+
)
60+
61+
/**
62+
* Wishlist let picostore keep track of bytes and blocks accepted
63+
* and expunged.
64+
*/
65+
const nBlocks = svlt(
66+
mute(
67+
combinedStateNeuron,
68+
() => kernel.store._stores.reduce((sum, s) => sum + s.version, 0)
69+
)
70+
)
71+
72+
</script>
73+
<code>
74+
<span class="ctrls">
75+
<btn class="inverse" on:click={reloadStore}>Reload</btn>
76+
<btn class="inverse" on:click={purge}>Purge</btn>
77+
</span>
78+
<span class="primary">
79+
[DevelBar]
80+
</span>
81+
Kernel: <strong>{$run}</strong>
82+
Peers: <strong>{$connections}</strong>
83+
Blocks: <strong>{$nBlocks}</strong>
84+
Memory: <strong>{$stateMemory}kB</strong>
85+
</code>
86+
<style>
87+
code { display: block; }
88+
.ctrls {
89+
float: right;
90+
}
91+
.primary { color: var(--primary); }
92+
</style>

0 commit comments

Comments
 (0)