19 |
20 |
21 |
40 |
--------------------------------------------------------------------------------
/ui/public/global.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | color: #333;
9 | margin: 0;
10 | padding: 8px;
11 | box-sizing: border-box;
12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
13 | }
14 |
15 | a {
16 | color: rgb(0,100,200);
17 | text-decoration: none;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 |
24 | a:visited {
25 | color: rgb(0,80,160);
26 | }
27 |
28 | label {
29 | display: block;
30 | }
31 |
32 | input, button, select, textarea {
33 | font-family: inherit;
34 | font-size: inherit;
35 | -webkit-padding: 0.4em 0;
36 | padding: 0.4em;
37 | margin: 0 0 0.5em 0;
38 | box-sizing: border-box;
39 | border: 1px solid #ccc;
40 | border-radius: 2px;
41 | }
42 |
43 | input:disabled {
44 | color: #ccc;
45 | }
46 |
47 | button {
48 | color: #333;
49 | background-color: #f4f4f4;
50 | outline: none;
51 | }
52 |
53 | button:disabled {
54 | color: #999;
55 | }
56 |
57 | button:not(:disabled):active {
58 | background-color: #ddd;
59 | }
60 |
61 | button:focus {
62 | border-color: #666;
63 | }
64 |
--------------------------------------------------------------------------------
/bindings/index.d.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 |
4 | /* auto-generated by NAPI-RS */
5 |
6 | export class ExternalObject {
7 | readonly '': {
8 | readonly '': unique symbol
9 | [K: symbol]: T
10 | }
11 | }
12 | export function sum(a: number, b: number): number
13 | export class Counter {
14 | constructor(value?: number | undefined | null)
15 | increment(): void
16 | set(value: number): void
17 | /**
18 | * Implement Svelte's Store Contract, defined as:
19 | *
20 | * store = {
21 | * subscribe: (
22 | * subscription: (value: any) => void
23 | * ) => (() => void), set?: (value: any) => void
24 | * }
25 | *
26 | * - A store must contain a .subscribe method, which must accept as its argument a subscription function.
27 | * This subscription function must be immediately and synchronously called with the store's current value
28 | * upon calling .subscribe. All of a store's active subscription functions must later be synchronously
29 | * called whenever the store's value changes.
30 | * - The .subscribe method MUST return an `unsubscribe` function.
31 | * Calling an `unsubscribe` function must stop its subscription, and its corresponding subscription
32 | * function must not be called again by the store.
33 | * - A store may optionally contain a `.set` method, which must accept as its argument a new value
34 | * for the store, and which synchronously calls all of the store's active subscription functions.
35 | * Such a store is called a writable store.
36 | */
37 | subscribe(callback: (...args: any[]) => any): (...args: any[]) => any
38 | }
39 |
--------------------------------------------------------------------------------
/ui/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 | import css from 'rollup-plugin-css-only';
7 |
8 | const production = !process.env.ROLLUP_WATCH;
9 |
10 | function serve() {
11 | let server;
12 |
13 | function toExit() {
14 | if (server) server.kill(0);
15 | }
16 |
17 | return {
18 | writeBundle() {
19 | if (server) return;
20 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
21 | stdio: ['ignore', 'inherit', 'inherit'],
22 | shell: true
23 | });
24 |
25 | process.on('SIGTERM', toExit);
26 | process.on('exit', toExit);
27 | }
28 | };
29 | }
30 |
31 | export default {
32 | input: 'src/main.js',
33 | output: {
34 | sourcemap: true,
35 | format: 'iife',
36 | name: 'app',
37 | file: 'public/build/bundle.js'
38 | },
39 | plugins: [
40 | svelte({
41 | compilerOptions: {
42 | // enable run-time checks when not in production
43 | dev: !production
44 | }
45 | }),
46 | // we'll extract any component CSS out into
47 | // a separate file - better for performance
48 | css({ output: 'bundle.css' }),
49 |
50 | // If you have external dependencies installed from
51 | // npm, you'll most likely need these plugins. In
52 | // some cases you'll need additional configuration -
53 | // consult the documentation for details:
54 | // https://github.com/rollup/plugins/tree/master/packages/commonjs
55 | resolve({
56 | browser: true,
57 | dedupe: ['svelte']
58 | }),
59 | commonjs(),
60 |
61 | // In dev mode, call `npm run start` once
62 | // the bundle has been generated
63 | !production && serve(),
64 |
65 | // Watch the `public` directory and refresh the
66 | // browser on changes when not in production
67 | !production && livereload('public'),
68 |
69 | // If we're building for production (npm run build
70 | // instead of npm run dev), minify
71 | //production && terser()
72 | ],
73 | watch: {
74 | clearScreen: false
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/bindings/src/lib.rs:
--------------------------------------------------------------------------------
1 | use napi::{
2 | bindgen_prelude::*,
3 | threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
4 | CallContext, JsUndefined,
5 | };
6 | use std::{cell::RefCell, collections::HashMap, rc::Rc};
7 |
8 | #[macro_use]
9 | extern crate napi_derive;
10 |
11 | #[napi]
12 | pub fn sum(a: i32, b: i32) -> i32 {
13 | a + b
14 | }
15 |
16 | #[napi]
17 | pub struct Counter {
18 | value: u32,
19 | subscribers: Rc>>>,
20 | next_subscriber: u64,
21 | }
22 |
23 | #[napi]
24 | impl Counter {
25 | #[napi(constructor)]
26 | pub fn new(value: Option) -> Counter {
27 | Counter {
28 | value: value.unwrap_or(0),
29 | subscribers: Rc::new(RefCell::new(HashMap::new())),
30 | next_subscriber: 0,
31 | }
32 | }
33 |
34 | #[napi]
35 | pub fn increment(&mut self) -> Result<()> {
36 | self.value += 1;
37 | self.notify_subscribers()
38 | }
39 |
40 | fn notify_subscribers(&mut self) -> Result<()> {
41 | for (_, cbref) in self.subscribers.borrow().iter() {
42 | cbref.call(self.value, ThreadsafeFunctionCallMode::Blocking);
43 | }
44 | Ok(())
45 | }
46 |
47 | #[napi]
48 | pub fn set(&mut self, value: u32) -> Result<()> {
49 | self.value = value;
50 | self.notify_subscribers()
51 | }
52 |
53 | /// Implement Svelte's Store Contract, defined as:
54 | ///
55 | /// store = {
56 | /// subscribe: (
57 | /// subscription: (value: any) => void
58 | /// ) => (() => void), set?: (value: any) => void
59 | /// }
60 | ///
61 | /// - A store must contain a .subscribe method, which must accept as its argument a subscription function.
62 | /// This subscription function must be immediately and synchronously called with the store's current value
63 | /// upon calling .subscribe. All of a store's active subscription functions must later be synchronously
64 | /// called whenever the store's value changes.
65 | /// - The .subscribe method MUST return an `unsubscribe` function.
66 | /// Calling an `unsubscribe` function must stop its subscription, and its corresponding subscription
67 | /// function must not be called again by the store.
68 | /// - A store may optionally contain a `.set` method, which must accept as its argument a new value
69 | /// for the store, and which synchronously calls all of the store's active subscription functions.
70 | /// Such a store is called a writable store.
71 | #[napi]
72 | pub fn subscribe(&mut self, env: Env, callback: JsFunction) -> Result {
73 | // Create a threadsafe wrapper
74 | //
75 | // The `Fatal` ErrorStrategy is best here because the argument we pass will come out as the first
76 | // one in JS. The `CalleeHandled` strategy, on the other hand, uses Node's calling convention,
77 | // where the first argument is the error (or null), and that doesn't follow the Svelte store contract.
78 | let tsfn: ThreadsafeFunction = callback
79 | .create_threadsafe_function(0, |ctx| ctx.env.create_uint32(ctx.value).map(|v| vec![v]))?;
80 |
81 | // Call once with the initial value
82 | tsfn.call(self.value, ThreadsafeFunctionCallMode::Blocking);
83 |
84 | // Save the callback so that we can call it later
85 | let key = self.next_subscriber;
86 | self.next_subscriber += 1;
87 | self.subscribers.borrow_mut().insert(key, tsfn);
88 |
89 | // Pass back an unsubscribe callback that will remove the subscription when called
90 | let subscribers = self.subscribers.clone();
91 | let unsubscribe = move |ctx: CallContext| -> Result {
92 | subscribers.borrow_mut().remove(&key);
93 | ctx.env.get_undefined()
94 | };
95 |
96 | env.create_function_from_closure("unsubscribe", unsubscribe)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | *Psst — looking for a more complete solution? Check out [SvelteKit](https://kit.svelte.dev), the official framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing.*
2 |
3 | *Looking for a shareable component template instead? You can [use SvelteKit for that as well](https://kit.svelte.dev/docs#packaging) or the older [sveltejs/component-template](https://github.com/sveltejs/component-template)*
4 |
5 | ---
6 |
7 | # svelte app
8 |
9 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
10 |
11 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
12 |
13 | ```bash
14 | npx degit sveltejs/template svelte-app
15 | cd svelte-app
16 | ```
17 |
18 | *Note that you will need to have [Node.js](https://nodejs.org) installed.*
19 |
20 |
21 | ## Get started
22 |
23 | Install the dependencies...
24 |
25 | ```bash
26 | cd svelte-app
27 | npm install
28 | ```
29 |
30 | ...then start [Rollup](https://rollupjs.org):
31 |
32 | ```bash
33 | npm run dev
34 | ```
35 |
36 | Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
37 |
38 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
39 |
40 | If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
41 |
42 | ## Building and running in production mode
43 |
44 | To create an optimised version of the app:
45 |
46 | ```bash
47 | npm run build
48 | ```
49 |
50 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
51 |
52 |
53 | ## Single-page app mode
54 |
55 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
56 |
57 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
58 |
59 | ```js
60 | "start": "sirv public --single"
61 | ```
62 |
63 | ## Using TypeScript
64 |
65 | This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
66 |
67 | ```bash
68 | node scripts/setupTypeScript.js
69 | ```
70 |
71 | Or remove the script via:
72 |
73 | ```bash
74 | rm scripts/setupTypeScript.js
75 | ```
76 |
77 | If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
78 |
79 | ## Deploying to the web
80 |
81 | ### With [Vercel](https://vercel.com)
82 |
83 | Install `vercel` if you haven't already:
84 |
85 | ```bash
86 | npm install -g vercel
87 | ```
88 |
89 | Then, from within your project folder:
90 |
91 | ```bash
92 | cd public
93 | vercel deploy --name my-project
94 | ```
95 |
96 | ### With [surge](https://surge.sh/)
97 |
98 | Install `surge` if you haven't already:
99 |
100 | ```bash
101 | npm install -g surge
102 | ```
103 |
104 | Then, from within your project folder:
105 |
106 | ```bash
107 | npm run build
108 | surge public my-project.surge.sh
109 | ```
110 |
--------------------------------------------------------------------------------
/ui/scripts/setupTypeScript.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /** This script modifies the project to support TS code in .svelte files like:
4 |
5 |
8 |
9 | As well as validating the code for CI.
10 | */
11 |
12 | /** To work on this script:
13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
14 | */
15 |
16 | const fs = require("fs")
17 | const path = require("path")
18 | const { argv } = require("process")
19 |
20 | const projectRoot = argv[2] || path.join(__dirname, "..")
21 |
22 | // Add deps to pkg.json
23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
25 | "svelte-check": "^2.0.0",
26 | "svelte-preprocess": "^4.0.0",
27 | "@rollup/plugin-typescript": "^8.0.0",
28 | "typescript": "^4.0.0",
29 | "tslib": "^2.0.0",
30 | "@tsconfig/svelte": "^2.0.0"
31 | })
32 |
33 | // Add script for checking
34 | packageJSON.scripts = Object.assign(packageJSON.scripts, {
35 | "check": "svelte-check --tsconfig ./tsconfig.json"
36 | })
37 |
38 | // Write the package JSON
39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
40 |
41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
44 | fs.renameSync(beforeMainJSPath, afterMainTSPath)
45 |
46 | // Switch the app.svelte file to use TS
47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte")
48 | let appFile = fs.readFileSync(appSveltePath, "utf8")
49 | appFile = appFile.replace("