diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be2c75..2c029fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.3.0 + +- **feat:** `wsPath` is now optional +- **refactor:** `stateManager` -> `sessionManager` +- **docs:** config + # 0.2.13 - **fix:** tabs height diff --git a/docs/README.md b/docs/README.md index 79857d6..d5a5142 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,11 +6,13 @@ LaranaJS is a powerfull fullstack web-development framework. ## [How to](./how-to/) +- [Preview](./how-to/README.md) - [Quick start](./how-to/quickstart.md) - [File structure](./how-to/file-structure.md) -- [Config](./how-to/config.md) - [Pages](./how-to/pages.md) - [Routing](./how-to/routing.md) +- [Config](./how-to/config.md) +- [Subsystems](./how-to/subsystems.md) - [Components](./how-to/components.md) - [UIKit](./ui-how-to/kit.md) - [Styles](./how-to/styles.md) @@ -25,24 +27,32 @@ LaranaJS is a powerfull fullstack web-development framework. ## [Tooling](./tooling/) +- [Preview](./tooling/README.md) - [Clirana](./tooling/clirana.md) - [Ligma](./tooling/ligma.md) - [Schemer](./tooling/schemer.md) ## [Testing](./testing/) +- [Preview](./testing/README.md) - [e2e](./testing/e2e.md) - [screenshots](./testing/sreenshots.md) -## [Extensions](./extensions/) +## [Subsystems](./subsystems/) -- [Rendering](./extensions/rendering.md) +- [Preview](./subsystems/README.md) +- [Renderer](./subsystems/renderer.md) +- [SessionManager](./subsystems/session-manager.md) +- [Networker](./subsystems/networker.md) +- [Router](./subsystems/router.md) ## [Architecture](./architecture/) +- [Preview](./architecture/README.md) - [Modules](./architecture/modules.md) - [Microservices](./architecture/microservices.md) ## [misc](./misc/) +- [Preview](./misc/README.md) - [FAQ](./misc/faq.md) diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..edba3da --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,8 @@ +# Architecture + +TODO + +## Table of contents + +- [Modules](./modules.md) +- [Microservices](./microservices.md) diff --git a/docs/code-style/README.md b/docs/code-style/README.md new file mode 100644 index 0000000..06bb374 --- /dev/null +++ b/docs/code-style/README.md @@ -0,0 +1,3 @@ +# Code style + +TODO diff --git a/docs/how-to/README.md b/docs/how-to/README.md new file mode 100644 index 0000000..74e1c75 --- /dev/null +++ b/docs/how-to/README.md @@ -0,0 +1,22 @@ +# How to + +TODO + +## Table of contents + +- [Quick start](./quickstart.md) +- [File structure](./file-structure.md) +- [Pages](./pages.md) +- [Routing](./routing.md) +- [Config](./config.md) +- [Components](./components.md) +- [UIKit](./ui-how-to/kit.md) +- [Styles](./styles.md) +- [Animations](./animations.md) +- [Hooks](./hooks.md) +- [State](./state.md) +- [Sessions](./sessions.md) +- [Storage](./storage.md) +- [Events](./events.md) +- [Figures](./figures.md) +- [Deploy](./deploy.md) diff --git a/docs/how-to/components.md b/docs/how-to/components.md index 9bb55a2..e264cc3 100644 --- a/docs/how-to/components.md +++ b/docs/how-to/components.md @@ -1,3 +1,5 @@ # Components TODO + +> Do not implement business logic in components. Use services! diff --git a/docs/how-to/config.md b/docs/how-to/config.md index 334df50..9c90d39 100644 --- a/docs/how-to/config.md +++ b/docs/how-to/config.md @@ -1,3 +1,175 @@ # Config -LaranaJS is a modular framework. It means that you can replace some of its parts if needed. +By default app config lies in `src/config.js`. Here is the minimal example: + +```js +const { defineConfig } = require('larana-js') + +const config = defineConfig({}) + +module.exports = { config } + +``` + +[In the next step](./app-modules.md) we will discuss including this config to your app, but now let's take a look at options. + +## options + +### `debug` + +**Type:** `boolean` +**Default:** `false` + +Is your app in debug mode. Enables `debugOptions` and also add a `DEBUG` overlay to your screen, so you don't forget to disable it before deploying. + +Also client files are hot reloaded only when debug is enabled. + +### `debugOptions` + +**Type:** `object` + +It is an object with debug options: + +#### `renderOutline` + +**Type:** `boolean` +**Default:** `false` + +If `true`, components will have an outline — a `1px` border with default color set to `rgba(255, 0, 0, 0.4)`. + +> [Components](./components.md) can have custom outline. All you need is to pass `outlineColor` and `outlineWidth` options to a constructor. + +#### `renderPaddings` + +**Type:** `boolean` +**Default:** `false` + +If `true`, app will render paddings with `rgba(255, 255, 0, 0.3)` color. + +> [Components](./components.md) can have custom padding color. All you need is to pass `paddingColor` option to a constructor. + +#### `renderGaps` + +**Type:** `boolean` +**Default:** `false` + +If `true`, app will render gaps with `rgba(255, 0, 255, 0.3)` color. + +> [Components](./components.md) can have custom gap color. All you need is to pass `gapColor` option to a constructor. + +#### `renderCursor` + +**Type:** `boolean` +**Default:** `false` + +Should the app render a circle on the current mouse position. + +#### `logMessages` + +**Type:** `boolean` +**Default:** `false` + +Should the app log every message to console. + +### `port` + +**Type:** `number` +**Default:** `1610` + +Port on which your app will be served. + +### `host` + +**Type:** `string` +**Default:** `http://localhost` + +Host on which + +### `wsPath` + +**Type:** `string`|`null` +**Default:** `null` + +WebSocket endpoint. If not set, will use app's default. + +### `maxFPS` + +**Type:** `number` +**Default:** `30` + +> In development + +### `maxBandwidth` + +**Type:** `number` +**Default:** `10 * 1024` (10 KB) + +> In development + +### `sessionLifetime` + +**Type:** `number` +**Default:** `5 * 60 * 1000` (5 minutes) + +> In development + +### `storePreviousRender` + +**Type:** `boolean` +**Default:** `true` + +Wheter the app should store previous rendered image or not. It is used to compute diff image and reduce package size. + +If `true`, the app will use more memory. + +If `false`, the app will use more CPU power. + +> `ServerRenderer` specific option. +> More info here: [Renderer](../app-modules/renderer.md) + +### `initialW` + +**Type:** `number` +**Default:** `512` + +Width of initialRoot. + +> More info here: [Life cycle](./life-cycle.md) + +### `initialH` + +**Type:** `number` +**Default:** `512` + +Height of initialRoot. + +> More info here: [Life cycle](./life-cycle.md) + +### `defaultTheme` + +**Type:** `string` +**Default:** `'dark'` + +Default UI theme. + +> More info here: [Styles](./styles.md) + +### `defaultLang` + +**Type:** `string` +**Default:** `'en'` + +Default session language. + +### `staticDir` + +**Type:** `string`|`null` +**Default:** `null` + +Directory with static files. + +> More info here: [Static files](./static.md). + +## Next step + +Now we are ready for the [subsystems](./subsystems.md). diff --git a/docs/how-to/file-structure.md b/docs/how-to/file-structure.md index 4711242..7a2c450 100644 --- a/docs/how-to/file-structure.md +++ b/docs/how-to/file-structure.md @@ -10,6 +10,7 @@ Your initial file structure will be something like this: - `home-page.js` - `404-page.js` - `index.js` + - `services` — a directory for your business logic. - `static` — a directory for [static files](./static.md) - `larana.svg` - `styles` — a directory for [styles](./styles.md) @@ -31,16 +32,19 @@ If your project is bigger than a few files, it is recomended to separete your co - `account` - `components` - `pages` + - `services` - `routes.js` - `index.js` - `auth` - `components` - `pages` + - `services` - `routes.js` - `index.js` - `cart` - `components` - `pages` + - `services` - `routes.js` - `index.js` - `routes.js` diff --git a/docs/extensions/rendering.md b/docs/how-to/life-cycle.md similarity index 100% rename from docs/extensions/rendering.md rename to docs/how-to/life-cycle.md diff --git a/docs/how-to/pages.md b/docs/how-to/pages.md index 9e8e100..be11cfc 100644 --- a/docs/how-to/pages.md +++ b/docs/how-to/pages.md @@ -29,7 +29,10 @@ class ArticlePage extends Page { styles() { // will be pasted inside of - return '' + return [ + '', + '', + ].join('\n') } content() { @@ -61,7 +64,7 @@ class ArticlePage extends Page { throbber: 0, }) - myAPI.fetchArticle(route.params.id).then((r) => { + myAPI.fetchArticle(route.params.slug).then((r) => { setState({ article: r.data }) }) } @@ -85,6 +88,9 @@ class ArticlePage extends Page { const { state } = this.useState() return layout({ + style: { + direction: w < 1280 ? 'column' : 'row', + }, children: [ state.article ? text({ value: article.title }) : throbber({ model: 'throbber' }), ], diff --git a/docs/how-to/quickstart.md b/docs/how-to/quickstart.md index 83dbaae..501081c 100644 --- a/docs/how-to/quickstart.md +++ b/docs/how-to/quickstart.md @@ -8,7 +8,7 @@ Run the following comand to create a project: npm init larana-js my-project ``` -You will be prompted a few questions and then your project will be ready. +> You will be prompted a few questions and then your project will be ready. ### Initialization options diff --git a/docs/how-to/routing.md b/docs/how-to/routing.md index ce56a18..ae19bed 100644 --- a/docs/how-to/routing.md +++ b/docs/how-to/routing.md @@ -8,6 +8,7 @@ If you initialized your project with `npm init larana-js` command, you should ha ```js const { + HomePage, ArticlePage, NotFoundPage, } = require('./pages') @@ -60,12 +61,12 @@ Here is an example of route: ```js const route = { - path: '/articles/:slug/', - name: 'article', - page: [class ArticlePage extends Page], - url: '/articles/how-to/?section=comments', - params: { slug: 'how-to' }, - queryParams: { section: 'comments' }, + path: '/articles/:slug/', + name: 'article', + page: [class ArticlePage extends Page], + url: '/articles/how-to/?section=comments', + params: { slug: 'how-to' }, + queryParams: { section: 'comments' }, } ``` diff --git a/docs/how-to/subsystems.md b/docs/how-to/subsystems.md new file mode 100644 index 0000000..17b9abb --- /dev/null +++ b/docs/how-to/subsystems.md @@ -0,0 +1,4 @@ +# Subsystem + +LaranaJS is a modular framework. It means that you can replace some of its parts if needed. + diff --git a/docs/misc/README.md b/docs/misc/README.md new file mode 100644 index 0000000..585f600 --- /dev/null +++ b/docs/misc/README.md @@ -0,0 +1,7 @@ +# Misc + +TODO + +## Table of contents + +- [F.A.Q.](./faq.md) diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md new file mode 100644 index 0000000..c1914d0 --- /dev/null +++ b/docs/subsystems/README.md @@ -0,0 +1,10 @@ +# Subsystems + +TODO + +## Table of contents + +- [Renderer](./renderer.md) +- [SessionManager](./session-manager.md) +- [Networker](./networker.md) +- [Router](./router.md) diff --git a/docs/subsystems/networker.md b/docs/subsystems/networker.md new file mode 100644 index 0000000..67e7765 --- /dev/null +++ b/docs/subsystems/networker.md @@ -0,0 +1,3 @@ +# Networker + +TODO diff --git a/docs/subsystems/renderer.md b/docs/subsystems/renderer.md new file mode 100644 index 0000000..5004711 --- /dev/null +++ b/docs/subsystems/renderer.md @@ -0,0 +1,3 @@ +# Renderer + +TODO diff --git a/docs/subsystems/router.md b/docs/subsystems/router.md new file mode 100644 index 0000000..4905a63 --- /dev/null +++ b/docs/subsystems/router.md @@ -0,0 +1,3 @@ +# Router + +TODO diff --git a/docs/subsystems/session-manager.md b/docs/subsystems/session-manager.md new file mode 100644 index 0000000..6663c92 --- /dev/null +++ b/docs/subsystems/session-manager.md @@ -0,0 +1,3 @@ +# SessionManager + +TODO diff --git a/docs/testing/README.md b/docs/testing/README.md new file mode 100644 index 0000000..88503e4 --- /dev/null +++ b/docs/testing/README.md @@ -0,0 +1,8 @@ +# Testing + +TODO + +## Table of contents + +- [E2E](./e2e.md) +- [Screenshot testing](./screenshots.md) diff --git a/docs/tooling/README.md b/docs/tooling/README.md new file mode 100644 index 0000000..5d49c53 --- /dev/null +++ b/docs/tooling/README.md @@ -0,0 +1,9 @@ +# Tooling + +TODO + +## Table of contents + +- [clirana](./clirana.md) +- [Schemer](./schemer.md) +- [Ligma](./ligma.md) diff --git a/example/config.js b/example/config.js index 06e54d0..e1e3ecd 100644 --- a/example/config.js +++ b/example/config.js @@ -4,7 +4,7 @@ const { defineConfig } = require('larana-js') module.exports = defineConfig({ port: 1610, defaultTheme: 'dark', - debug: false, + debug: true, debugOptions: { renderOutline: true, renderPaddings: true, diff --git a/example/index.js b/example/index.js index 26b5991..a2478e4 100644 --- a/example/index.js +++ b/example/index.js @@ -1,5 +1,5 @@ const { - MemoryStateManager, + MemorySessionManager, // ServerRenderer, ClientRenderer, LaranaApp, @@ -28,14 +28,14 @@ const renderer = new ClientRenderer({ ], }) -const stateManager = new MemoryStateManager({ +const sessionManager = new MemorySessionManager({ debug: config.debug, }) const app = new LaranaApp({ config, renderer, - stateManager, + sessionManager, router, }) diff --git a/example/pages/donate-page.js b/example/pages/donate-page.js new file mode 100644 index 0000000..c120b05 --- /dev/null +++ b/example/pages/donate-page.js @@ -0,0 +1,51 @@ +const { Page, text, layout, qr } = require('larana-js') + +const { header } = require('../components') + +class DonatePage extends Page { + title() { + return 'Donate' + } + + root() { + return layout({ + style: [ + 'body', + { direction: 'column' }, + ], + children: [ + header({}), + layout({ + style: { + size: 9, + padding: 'var:u5', + direction: 'column', + gap: 'var:u2', + }, + children: [ + layout({ + style: 'hug', + children: [ + layout({}), + qr({ + value: 'https://boosty.to/kucheriavyi/donate', + style: { + width: 512, + aspectRatio: 1, + }, + }), + layout({}), + ], + }), + + text({ + value: 'https://boosty.to/kucheriavyi/donate', + }), + ], + }), + ], + }) + } +} + +module.exports = { DonatePage } diff --git a/example/pages/index.js b/example/pages/index.js index 916e0cd..bc05179 100644 --- a/example/pages/index.js +++ b/example/pages/index.js @@ -10,5 +10,6 @@ module.exports = { ...require('./table-page.js'), ...require('./quiz-page.js'), ...require('./image-page.js'), + ...require('./donate-page.js'), ...require('./404-page.js'), } diff --git a/example/routes.js b/example/routes.js index e729067..bc9173a 100644 --- a/example/routes.js +++ b/example/routes.js @@ -11,6 +11,7 @@ const { TablePage, ImagePage, QuizPage, + DonatePage, } = require('./pages') module.exports = { @@ -26,6 +27,7 @@ module.exports = { { path: '/table', name: 'table', page: TablePage }, { path: '/quiz', name: 'quiz', page: QuizPage }, { path: '/image', name: 'image', page: ImagePage }, + { path: '/donate', name: 'donate', page: DonatePage }, { path: '404', name: 'not-found', page: NotFoundPage }, ], diff --git a/index.js b/index.js index d565b05..cc31690 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,8 @@ module.exports = { ...require('./src/app.js'), config: require('./src/config.js'), ...require('./src/config.js'), - state: require('./src/state'), - ...require('./src/state'), + state: require('./src/sessions/index.js'), + ...require('./src/sessions/index.js'), routing: require('./src/routing'), ...require('./src/routing'), ui: require('./src/ui'), diff --git a/package.json b/package.json index 6d55e97..f6218be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "larana-js", - "version": "0.2.13", + "version": "0.3.0", "description": "Real SSR framework", "main": "index.js", "scripts": { diff --git a/roadmap.md b/roadmap.md index 3d4db26..f117bb0 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,18 +1,12 @@ # Roadmap -# 0.2.x - -- fix: images on server -- fix: client fonts -- feat: `toggleTheme` -- feat: `router.push()` -- feat: initDefaultStyles inside LaranaApp -- docs: howto - # 0.3.x +- fix: debug cursor +- fix: images on server - fix: ssr - fix: remove blinking on resize +- feat: client code in every app component - feat: min/max dimensions - feat: `alignment`: `start|center|end|stretch` - feat: root style fix @@ -20,6 +14,13 @@ - feat: `scroll` - feat: `blur` event - feat: `wrap` +- fix: client fonts +- feat: `toggleTheme` +- feat: `router.push()` +- feat: initDefaultStyles inside LaranaApp +- feat: lifecycle hooks +- feat: more arrow directions +- docs: howto - demo: `/game` page # 0.4.x diff --git a/src/app.js b/src/app.js index 4d30555..3bd191c 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ const { WebSocketServer } = require('ws') const { createServer } = require('http') -const { MemoryStateManager, Session } = require('./state') +const { MemorySessionManager, Session } = require('./sessions/index.js') const { ServerRenderer } = require('./ui/rendering') const { defaultConfig } = require('./config.js') const { Response, Request } = require('./network') @@ -15,7 +15,7 @@ class LaranaApp { config = { ...defaultConfig } router = new DefaultRouter({}) renderer = new ServerRenderer({ debug: false }) - stateManager = new MemoryStateManager({}) + sessionManager = new MemorySessionManager({}) clients = new Set() onConnect = (data) => {} @@ -29,7 +29,7 @@ class LaranaApp { const { router, renderer, - stateManager, + sessionManager, onConnect = (data) => { if (!config.debug || !config.debugOptions.logMessages) { return @@ -68,8 +68,8 @@ class LaranaApp { if (renderer) { this.renderer = renderer } - if (stateManager) { - this.stateManager = stateManager + if (sessionManager) { + this.sessionManager = sessionManager } initDefaultStyleVars() @@ -118,7 +118,7 @@ class LaranaApp { return } const route = this.router.resolve(req.url) - const sessionId = this.stateManager.generateSessionId() + const sessionId = this.sessionManager.generateSessionId() console.log(route) const PageClass = route.page @@ -146,7 +146,7 @@ class LaranaApp { route, storage: { lang: this.config.defaultLang, theme: this.config.defaultTheme }, }) - this.stateManager.addSession(sessionId, session) + this.sessionManager.addSession(sessionId, session) page._setSession(session) page.init() @@ -161,6 +161,7 @@ class LaranaApp { const clientCode = prepareTemplate({ sessionId, + wsPath: this.config.wsPath, lang: session.storage.lang, title: page.title(), meta: page.meta(), @@ -196,7 +197,7 @@ class LaranaApp { _onMessage({ ws, message }) { const payload = JSON.parse(message.toString()) - const session = this.stateManager.getSession(payload.sessionId) + const session = this.sessionManager.getSession(payload.sessionId) session.update() const { w, h, data } = payload diff --git a/src/app/client.js b/src/app/client.js new file mode 100644 index 0000000..6bea17f --- /dev/null +++ b/src/app/client.js @@ -0,0 +1,19 @@ +class ClientApp { + subsystems = []; + + networker = null; + + init(networker) { + this.networker = networker; + networker.init(this); + + for (var i = 0; i < this.subsystems.length; i++) { + var s = this.subsystems[i]; + s.init(this); + } + } + + sendMessage(message) { + this.networker.sendMessage(message); + } +} diff --git a/src/app/server.js b/src/app/server.js new file mode 100644 index 0000000..4f6194d --- /dev/null +++ b/src/app/server.js @@ -0,0 +1,6 @@ +class ServerApp { // ServerApp | ClientApp ? + // initial response + // sockets +} + +module.exports = { ServerApp } diff --git a/src/config.js b/src/config.js index cc7c41b..4f6e253 100644 --- a/src/config.js +++ b/src/config.js @@ -14,6 +14,15 @@ const configSchemer = new Schemer({ ...common.positiveInt, required: false, }, + host: { + type: 'string', + required: false, + }, + wsPath: { + type: 'string', + nullable: true, + required: false, + }, maxFPS: { ...common.positiveInt, required: false, @@ -51,10 +60,6 @@ const configSchemer = new Schemer({ nullable: true, required: false, }, - host: { - type: 'string', - required: false, - }, }) const defaultConfig = { @@ -67,6 +72,8 @@ const defaultConfig = { logMessages: false, }, port: 1610, + host: 'http://localhost', + wsPath: null, maxFPS: 30, maxBandwidth: 10 * 1024, // TODO sessionLifetime: 5 * (60 * 1000), // TODO @@ -76,7 +83,6 @@ const defaultConfig = { defaultTheme: 'dark', defaultLang: 'en', staticDir: null, - host: 'http://localhost', } let config = { ...defaultConfig } @@ -86,6 +92,7 @@ let config = { ...defaultConfig } * debug: boolean; * debugOptions: *; * port: number; + * host: string; * wsPath: string; * maxFPS: number; * maxBandwidth: number; diff --git a/src/routing/base-router.js b/src/routing/base-router.js index 25aee37..7997d1c 100644 --- a/src/routing/base-router.js +++ b/src/routing/base-router.js @@ -10,6 +10,10 @@ class BaseRouter { resolve(url) { throw new Error('Not implemented') } + + get clientCode() { + return '' + } } module.exports = { diff --git a/src/routing/default-router.js b/src/routing/default-router.js index 8a7fe34..87ee95b 100644 --- a/src/routing/default-router.js +++ b/src/routing/default-router.js @@ -53,7 +53,6 @@ class DefaultRouter extends BaseRouter { const queryParams = this.extractQueryParams(url) - if (!route) { const notFoundRoute = this.routes.find((p) => p.name === 'not-found') return { @@ -88,8 +87,11 @@ class DefaultRouter extends BaseRouter { return result } -} + get clientCode() { + return '' + } +} module.exports = { DefaultRouter, diff --git a/src/state/base-state-manager.js b/src/sessions/base-session-manager.js similarity index 68% rename from src/state/base-state-manager.js rename to src/sessions/base-session-manager.js index ed0c3d3..3f2bb43 100644 --- a/src/state/base-state-manager.js +++ b/src/sessions/base-session-manager.js @@ -1,7 +1,7 @@ const crypto = require('crypto') -class BaseStateManager { +class BaseSessionManager { generateSessionId() { return crypto.randomUUID() } @@ -13,7 +13,10 @@ class BaseStateManager { getSession(id) { throw new Error('Not implemented') } -} + get clientCode() { + return '' + } +} -module.exports = { BaseStateManager } +module.exports = { BaseSessionManager } diff --git a/src/sessions/index.js b/src/sessions/index.js new file mode 100644 index 0000000..02b381b --- /dev/null +++ b/src/sessions/index.js @@ -0,0 +1,5 @@ +module.exports = { + ...require('./base-session-manager.js'), + ...require('./memory-session-manager.js'), + ...require('./session.js'), +} diff --git a/src/state/memory-state-manager.js b/src/sessions/memory-session-manager.js similarity index 59% rename from src/state/memory-state-manager.js rename to src/sessions/memory-session-manager.js index 33e0eee..5747844 100644 --- a/src/state/memory-state-manager.js +++ b/src/sessions/memory-session-manager.js @@ -1,6 +1,6 @@ -const { BaseStateManager } = require('./base-state-manager.js') +const { BaseSessionManager } = require('./base-session-manager.js') -class MemoryStateManager extends BaseStateManager { +class MemorySessionManager extends BaseSessionManager { sessions = new Map() addSession(id, session) { @@ -16,4 +16,4 @@ class MemoryStateManager extends BaseStateManager { } } -module.exports = { MemoryStateManager } +module.exports = { MemorySessionManager } diff --git a/src/state/session.js b/src/sessions/session.js similarity index 100% rename from src/state/session.js rename to src/sessions/session.js diff --git a/src/state/index.js b/src/state/index.js deleted file mode 100644 index da37ec1..0000000 --- a/src/state/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - ...require('./base-state-manager.js'), - ...require('./memory-state-manager.js'), - ...require('./session.js'), -} diff --git a/src/subsystems/app/client.js b/src/subsystems/app/client.js new file mode 100644 index 0000000..4d22284 --- /dev/null +++ b/src/subsystems/app/client.js @@ -0,0 +1,41 @@ +class ClientApp { + name = 'Base'; + + app = null; + + _init(app) { + this.app = app; + + app.addMessageHanler(this._handleMessage); + + var listeners = this.getEventListeners(); + for (var i = 0; i < listeners.length; i++) { + var l = listeners[i]; + app.addEventListener(l.event, l.callback); + } + } + + getEventListeners() { + return []; + } + + _handleMessage(message) { + if (message.target != this.name) { + return; + } + this.handleMessage(message); + } + + handleMessage(message) {} + + sendMessage(message) { + this._sendMessage(message); + } + + _sendMessage(message) { + if (!message.target) { + message.target = this.name; + } + this.app.sendMessage(message); + } +} diff --git a/src/subsystems/app/server.js b/src/subsystems/app/server.js new file mode 100644 index 0000000..e69de29 diff --git a/src/subsystems/base.js b/src/subsystems/base.js new file mode 100644 index 0000000..98a0382 --- /dev/null +++ b/src/subsystems/base.js @@ -0,0 +1,43 @@ +class Subsystem { + name = 'Base'; + + app = null; + + _init(app) { + this.app = app; + + app.addMessageHanler(this._handleMessage); + + var listeners = this.getEventListeners(); + for (var i = 0; i < listeners.length; i++) { + var l = listeners[i]; + app.addEventListener(l.event, l.callback); + } + } + + getEventListeners() { + return []; + } + + _handleMessage(message) { + if (message.target != this.name) { + return; + } + this.handleMessage(message); + } + + handleMessage(message) {} + + sendMessage(message) { + this._sendMessage(message); + } + + _sendMessage(message) { + if (!message.target) { + message.target = this.name; + } + this.app.sendMessage(message); + } +} + +module.exports = { Subsystem } diff --git a/src/subsystems/networker/message.js b/src/subsystems/networker/message.js new file mode 100644 index 0000000..912e2a8 --- /dev/null +++ b/src/subsystems/networker/message.js @@ -0,0 +1,29 @@ +class Message { + sessionId = '' + timestamp = 0 + target = '' + payload = null + + constructor(options) { + const { sessionId, target, payload } = options + + this.sessionId = sessionId + this.target = target + this.payload = payload + } + + json() { + return { + timestamp: this.timestamp, + sessionId: this.sessionId, + target: this.target, + payload: this.payload?.json() ?? this.payload, + } + } + + jsonString() { + return JSON.stringify(this.json()) + } +} + +module.exports = { Message } diff --git a/src/subsystems/sessioner/index.js b/src/subsystems/sessioner/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/subsystems/sessioner/memory/client-memory-session-manager.js b/src/subsystems/sessioner/memory/client-memory-session-manager.js new file mode 100644 index 0000000..e69de29 diff --git a/src/subsystems/sessioner/memory/index.js b/src/subsystems/sessioner/memory/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/subsystems/sessioner/memory/memory-session-manager.js b/src/subsystems/sessioner/memory/memory-session-manager.js new file mode 100644 index 0000000..8b6c420 --- /dev/null +++ b/src/subsystems/sessioner/memory/memory-session-manager.js @@ -0,0 +1,9 @@ +const { Subsystem } = require("../../base/base"); + +class MemorySessionManager extends Subsystem { + get clientCode() { + __dirname + '/client.js' + } +} + +module.exports = { MemorySessionManager } diff --git a/src/ui/client/js/network.js b/src/ui/client/js/network.js index b37ad0b..a6cba61 100644 --- a/src/ui/client/js/network.js +++ b/src/ui/client/js/network.js @@ -1,6 +1,7 @@ /* eslint-disable */ var SESSION_ID = '%SESSION_ID%'; +var WS_PATH = '%WS_PATH'; var ws = null; @@ -23,6 +24,9 @@ function throttle(mainFunction, delay) { function getMessage(e) { var data = JSON.parse(e.data); + responseListeners.forEach((l) => { + l.applyResponse(data); + }); applyResponse(data); } @@ -45,7 +49,6 @@ function sendMessage(data) { const throttleMessage = throttle(sendMessage, 100); function connect(endpoint) { - console.log(endpoint) ws = new WebSocket(endpoint); ws.onerror = function (_) { @@ -53,7 +56,6 @@ function connect(endpoint) { ws.close(); connect(endpoint); }; - ws.onmessage = function (e) { getMessage(e); }; @@ -75,5 +77,10 @@ document.addEventListener('DOMContentLoaded', function () { protocol = "wss://"; } var endpoint = protocol + host + "/ws"; + + if (WS_PATH !== 'null' && WS_PATH !== null) { + endpoint = WS_PATH; + } + connect(endpoint); }); diff --git a/src/ui/client/prepare-network.js b/src/ui/client/prepare-network.js index 47d2c39..c47d9dc 100644 --- a/src/ui/client/prepare-network.js +++ b/src/ui/client/prepare-network.js @@ -4,11 +4,12 @@ const path = ['js', 'network.js'] const networkCode = readFile(path) -const prepareNetwork = ({ debug, sessionId }) => { +const prepareNetwork = ({ debug, sessionId, wsPath }) => { const network = debug ? readFile(path) : networkCode return network .replace('%SESSION_ID%', sessionId) + .replace('%WS_PATH%', wsPath) } module.exports = { prepareNetwork } diff --git a/ssssss.md b/ssssss.md new file mode 100644 index 0000000..1008e46 --- /dev/null +++ b/ssssss.md @@ -0,0 +1,149 @@ +# Existing subsystems + +1. Router +2. SessionManager -> Sessioner +3. Renderer +4. (Networker) +5. (Pager) +6. (Filer) + +# Lifecycle + +1. [CLIENT] User opens `/article/:slug/` +2. [SERVER] Handles request +- resolves route [Router] +- resolve session [Sessioner] +- creates page [ServerCore] +- renders page [Renderer] +- creating client code (including rendered page) [Renderer] +- sending client code to client [Networker] +3. [CLIENT] recieves response +- recieving client code [ClientCore] +- [NEW] add subsystem listeners +- initializing canvas [Renderer] +- patching image [Renderer] +- adding event listeners [Networker?] +- connecting to websockets [Networker] + +4 [CLIENT-SERVER] +- [SERVER2CLIENT] sending rendering data [Renderer] +- [SERVER2CLIENT] sending routing event [Router] +- [SERVER2CLIENT] localStorage [SessionManager] +- [SERVER2CLIENT] listen for new message [Networker?] +- [CLIENT2SERVER] sending new event [click/scroll/etc] + +# Server code + + + +# Client code + +```js + +%SUBSYSTEM_CLASSES% + +class ClientCore { + subsystems = [ + %SUBSYSTEM_CLASSES_CONSTUCT% + ]; + + networker = new Networker(); + + init() { + this.networker.init(this); + subsystems.forEach((s) => { + s.init(this); + }); + } +} + +class Networker { + name = 'Networker'; + + messageHandlers = []; + eventListeners = []; + + init(core) { + connectToWebSocket(); + messageHandlers.push(); + } + + handleMessage(message) { + messageHandlers.forEach((listen) => { + listen(this, message); + }); + } + + addMessageHandlers() {} + removerMessageHandlers() {} + + addEventListener() {} + removeEventListener() {} +} + +class BaseSubsystem {} + +class Router extends BaseSubsystem { + name = 'Router'; + core = null; + + init(core) { + this.core = core; + + this.networker.addMessageHandlers(this.handleMessage); + this.networker.addEventListener('click', handleLinkClick); + } + + handleLinkClick() {} + + handleMessage(message) { + const m = { + sender: 'router', + data: {}, + }; + + if (message.sender !== 'router') { + return; + } + + if (message.data.type === 'push') { + location.push(message.data.url); + this.core.sendMessageToSubsystem(this.name, {...}); + } + + /// + } +} + +class Renderer extends BaseSubsystem { + name = 'Renderer' + handleMessage(message) { + const m = { + sender: 'router', + data: {} + } + + if (message.sender !== 'renderer') { + return; + } + + if (message.data.type === 'image') { + ctx.drawImage(...) + } + + /// + } +} + + +document.addEventListener('DOMContentLoaded', () => { + const core = new ClientCore(); + + core.init(); + // subsystems.forEach((s) => { + + // }); +}); + + +```