From 2ec442c086cc8d949842f82ec7e749da2c2ce791 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Fri, 6 Mar 2026 08:37:53 +0100 Subject: [PATCH] [ini] --- .gitignore | 2 + source/data/conf.json | 5 + source/data/localization/de.json | 13 ++ source/data/localization/en.json | 13 ++ source/data/localization/eo.json | 13 ++ source/fonts/strogg.ttf | Bin 0 -> 16280 bytes source/fonts/strogg.woff | Bin 0 -> 7568 bytes source/fonts/strogg.woff2 | Bin 0 -> 5584 bytes source/logic/base.ts | 1 + source/logic/control/app.ts | 53 +++++++ source/logic/control/rental.ts | 68 ++++++++ source/logic/helpers/call.ts | 24 +++ source/logic/helpers/conf.ts | 32 ++++ source/logic/helpers/dom.ts | 68 ++++++++ source/logic/helpers/loc.ts | 168 ++++++++++++++++++++ source/logic/helpers/mvc.ts | 166 ++++++++++++++++++++ source/logic/helpers/nextbike.ts | 211 +++++++++++++++++++++++++ source/logic/helpers/storage.ts | 39 +++++ source/logic/helpers/string.ts | 29 ++++ source/logic/main.ts | 179 +++++++++++++++++++++ source/logic/model/app.ts | 231 ++++++++++++++++++++++++++++ source/logic/model/rental.ts | 120 +++++++++++++++ source/logic/platform/web/app.ts | 66 ++++++++ source/logic/platform/web/rental.ts | 54 +++++++ source/logic/view/console/app.ts | 40 +++++ source/logic/view/console/rental.ts | 40 +++++ source/logic/view/web/app.ts | 123 +++++++++++++++ source/logic/view/web/rental.ts | 151 ++++++++++++++++++ source/media/logo.svg | 74 +++++++++ source/structure/index.html | 57 +++++++ source/style/base.less | 154 +++++++++++++++++++ source/style/default.less | 40 +++++ source/style/strogg.less | 40 +++++ tools/build | 4 + tools/deploy | 11 ++ tools/makefile | 146 ++++++++++++++++++ tools/run | 4 + 37 files changed, 2439 insertions(+) create mode 100644 .gitignore create mode 100644 source/data/conf.json create mode 100644 source/data/localization/de.json create mode 100644 source/data/localization/en.json create mode 100644 source/data/localization/eo.json create mode 100644 source/fonts/strogg.ttf create mode 100644 source/fonts/strogg.woff create mode 100644 source/fonts/strogg.woff2 create mode 100644 source/logic/base.ts create mode 100644 source/logic/control/app.ts create mode 100644 source/logic/control/rental.ts create mode 100644 source/logic/helpers/call.ts create mode 100644 source/logic/helpers/conf.ts create mode 100644 source/logic/helpers/dom.ts create mode 100644 source/logic/helpers/loc.ts create mode 100644 source/logic/helpers/mvc.ts create mode 100644 source/logic/helpers/nextbike.ts create mode 100644 source/logic/helpers/storage.ts create mode 100644 source/logic/helpers/string.ts create mode 100644 source/logic/main.ts create mode 100644 source/logic/model/app.ts create mode 100644 source/logic/model/rental.ts create mode 100644 source/logic/platform/web/app.ts create mode 100644 source/logic/platform/web/rental.ts create mode 100644 source/logic/view/console/app.ts create mode 100644 source/logic/view/console/rental.ts create mode 100644 source/logic/view/web/app.ts create mode 100644 source/logic/view/web/rental.ts create mode 100644 source/media/logo.svg create mode 100644 source/structure/index.html create mode 100644 source/style/base.less create mode 100644 source/style/default.less create mode 100644 source/style/strogg.less create mode 100755 tools/build create mode 100755 tools/deploy create mode 100644 tools/makefile create mode 100755 tools/run diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e8147f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.geany +build/ diff --git a/source/data/conf.json b/source/data/conf.json new file mode 100644 index 0000000..87f9904 --- /dev/null +++ b/source/data/conf.json @@ -0,0 +1,5 @@ +{ + "baseurl": "https://api.nextbike.net/api/api.php", + "apikey": "xEsFhYZR5jpO1Ug1" +} + diff --git a/source/data/localization/de.json b/source/data/localization/de.json new file mode 100644 index 0000000..1946021 --- /dev/null +++ b/source/data/localization/de.json @@ -0,0 +1,13 @@ +{ + "username": "Telefon-Nummer", + "password": "Passwort", + "bikename": "Rad-Nummer", + "login": "Anmelden", + "logout": "Abmelden", + "rent": "Leihe hinzufügen", + "start": "starten", + "open": "Schloss öffnen", + "pause": "unterbrechen", + "finish": "beenden" +} + diff --git a/source/data/localization/en.json b/source/data/localization/en.json new file mode 100644 index 0000000..cf50ee8 --- /dev/null +++ b/source/data/localization/en.json @@ -0,0 +1,13 @@ +{ + "username": "phone number", + "password": "password", + "bikename": "bike number", + "login": "login", + "logout": "logout", + "rent": "add rental", + "start": "start", + "open": "open lock", + "pause": "break", + "finish": "finish" +} + diff --git a/source/data/localization/eo.json b/source/data/localization/eo.json new file mode 100644 index 0000000..5a70c5b --- /dev/null +++ b/source/data/localization/eo.json @@ -0,0 +1,13 @@ +{ + "username": "telefon-nombro", + "password": "pasvorto", + "bikename": "biciklo-nombro", + "login": "ensaluti", + "logout": "elsaluti", + "rent": "aldoni pruntajxon", + "start": "komenci", + "open": "malfermi sxlosilon", + "pause": "halti", + "finish": "finigi" +} + diff --git a/source/fonts/strogg.ttf b/source/fonts/strogg.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c28590b4e403db5c6a5f7afdf05ad08c3a833aea GIT binary patch literal 16280 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 literal 0 HcmV?d00001 diff --git a/source/fonts/strogg.woff2 b/source/fonts/strogg.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a1f3cabd362bdb352be30af6693226c6567ee293 GIT binary patch literal 5584 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 literal 0 HcmV?d00001 diff --git a/source/logic/base.ts b/source/logic/base.ts new file mode 100644 index 0000000..61b835c --- /dev/null +++ b/source/logic/base.ts @@ -0,0 +1 @@ +type int = number; diff --git a/source/logic/control/app.ts b/source/logic/control/app.ts new file mode 100644 index 0000000..eadbf39 --- /dev/null +++ b/source/logic/control/app.ts @@ -0,0 +1,53 @@ +namespace mod_control.app +{ + + /** + */ + export function setup + ( + platform : lib_mvc.type_model + ) : Promise + { + let context_dom : HTMLElement = platform.state.element_dom; + context_dom.querySelector(".app_login > button").addEventListener + ( + "click", + (event) => + { + const username : string = ((document.querySelector(".app_username > input") as HTMLInputElement)).value; + const password : string = ((document.querySelector(".app_password > input") as HTMLInputElement)).value; + mod_model.app.login(platform.state.model, username, password); + } + ); + context_dom.querySelector(".app_logout > button").addEventListener + ( + "click", + (event) => + { + mod_model.app.logout(platform.state.model); + } + ); + context_dom.querySelector(".app_rent > button").addEventListener + ( + "click", + (event) => + { + mod_model.app.prepare_rental(platform.state.model); + } + ); + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_control + ( + ) : lib_mvc.type_control + { + return { + "setup": (model) => setup(model), + }; + } + +} diff --git a/source/logic/control/rental.ts b/source/logic/control/rental.ts new file mode 100644 index 0000000..5103572 --- /dev/null +++ b/source/logic/control/rental.ts @@ -0,0 +1,68 @@ +namespace mod_control.rental +{ + + /** + */ + export function setup + ( + platform : lib_mvc.type_model + ) : Promise + { + let context_dom : HTMLElement = platform.state.element_dom; + context_dom.querySelector(".rental_start").addEventListener + ( + "click", + (event) => + { + const bike_name : string = lib_call.convey + ( + platform.state.model.state.id, + [ + x => lib_string.coin(".rental[rel=\"{{rel}}\"] > .rental_bike > input", {"rel": x}), + x => (document.querySelector(x) as HTMLInputElement), + x => x.value, + ] + ); + mod_model.rental.start(platform.state.model, bike_name); + } + ); + context_dom.querySelector(".rental_open > button").addEventListener + ( + "click", + (event) => + { + mod_model.rental.open(platform.state.model); + } + ); + context_dom.querySelector(".rental_pause > button").addEventListener + ( + "click", + (event) => + { + mod_model.rental.pause(platform.state.model); + } + ); + context_dom.querySelector(".rental_finish > button").addEventListener + ( + "click", + (event) => + { + mod_model.rental.end(platform.state.model); + } + ); + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_control + ( + ) : lib_mvc.type_control + { + return { + "setup": (model) => setup(model), + }; + } + +} diff --git a/source/logic/helpers/call.ts b/source/logic/helpers/call.ts new file mode 100644 index 0000000..bdf8e88 --- /dev/null +++ b/source/logic/helpers/call.ts @@ -0,0 +1,24 @@ +namespace lib_call +{ + + /** + */ + export function convey + ( + value : any, + functions : Array + ) : any + { + let result : any = value; + functions.forEach + ( + function_ => + { + result = function_(result); + } + ); + return result; + } + +} + diff --git a/source/logic/helpers/conf.ts b/source/logic/helpers/conf.ts new file mode 100644 index 0000000..71764d1 --- /dev/null +++ b/source/logic/helpers/conf.ts @@ -0,0 +1,32 @@ + +namespace lib_conf +{ + + /** + */ + var _data : any; + + + /** + */ + export async function load + ( + path : string + ) : Promise + { + _data = await fetch(path, {"method": "GET"}).then(x => x.json()) + } + + + /** + */ + export function get + ( + key : string + ) : any + { + return _data[key]; + } + +} + diff --git a/source/logic/helpers/dom.ts b/source/logic/helpers/dom.ts new file mode 100644 index 0000000..65a1b23 --- /dev/null +++ b/source/logic/helpers/dom.ts @@ -0,0 +1,68 @@ +namespace lib_dom +{ + + /** + * @author fenris + */ + export function clone( + element : Element, + options : { + context ?: Document; + } = {} + ) : Node + { + options = Object.assign( + { + "context": document, + }, + options + ); + return options.context.importNode(element, true); + } + + + /** + * @author fenris + */ + export function request( + id : string, + options : { + context ?: Document; + arguments ?: (null | Record); + } = {} + ) : DocumentFragment + { + options = Object.assign( + { + "context": document, + "arguments": null, + }, + options + ); + const template : (null | Element) = document.querySelector("template#" + id); + if (template === null) { + throw (new Error("template not found: " + id)); + } + else { + const fragment : DocumentFragment = ( + clone( + template["content"], + { + "context": options.context, + } + ) as DocumentFragment + ); + if (options.arguments === null) { + // do nothing + } + else { + for (let index : int = 0; index < fragment.children.length; index += 1) { + const element : Element = fragment.children[index]; + element.innerHTML = lib_string.coin(element.innerHTML, options.arguments); + } + } + return fragment; + } + } + +} diff --git a/source/logic/helpers/loc.ts b/source/logic/helpers/loc.ts new file mode 100644 index 0000000..7a06e60 --- /dev/null +++ b/source/logic/helpers/loc.ts @@ -0,0 +1,168 @@ +namespace lib_loc +{ + + /** + */ + var _data : Record> = {}; + + + /** + */ + var _language_order : Array = []; + + + /** + */ + var _resolve_path : ((language : string) => string); + + + /** + */ + async function load + ( + language : string + ) : Promise + { + if (_data.hasOwnProperty(language)) + { + // do nothing + } + else + { + try + { + _data[language] = ((await fetch(_resolve_path(language), {"method": "GET"}).then(x => x.json())) as Record); + } + catch (error) + { + console.warn + ( + lib_string.coin + ( + "could not load localization data for language '{{language}}': {{error}}", + { + "language": language, + "error": String(error), + } + ) + ); + } + } + // return Promise.resolve(undefined); + } + + + /** + */ + export function get + ( + key : string, + options : + { + language ?: (null | string); + } = {} + ) : string + { + options = Object.assign + ( + { + "language": null, + }, + options + ); + const language_list : Array = ( + [] + .concat((options.language === null) ? [] : [options.language]) + .concat(_language_order) + ); + for (const language of language_list) + { + if (_data.hasOwnProperty(language) && _data[language].hasOwnProperty(key)) + { + return _data[language][key]; + } + else + { + // do nothing + } + } + return ("{" + key + "}"); + } + + + /** + */ + export function translate_item + ( + selector : string, + key : string, + options : + { + kind ?: string; + parameters ?: Record; + language ?: (null | string); + context ?: (HTMLElement | DocumentFragment); + } = {} + ) : void + { + options = Object.assign + ( + { + "kind": "textcontent", + "parameters": {}, + "language": null, + "context": document, + }, + options + ); + const dom_element : Element = options.context.querySelector(selector); + const value : string = get(key, {"language": options.language}); + switch (options.kind) + { + default: + { + console.warn("unhandeld kind"); + break; + } + case "textcontent": + { + dom_element.textContent = value; + break; + } + case "attribute": + { + dom_element.setAttribute(options.parameters["key"], value); + break; + } + } + } + + + /** + */ + export async function setup + ( + language_order : Array, + options : + { + resolve_path ?: ((language : string) => string), + } = {} + ) : Promise + { + options = Object.assign + ( + { + "resolve_path": (language => lib_string.coin("localization/{{language}}.json", {"language": language})), + }, + options + ); + _resolve_path = options.resolve_path; + _language_order = language_order; + for await (const language of _language_order) + { + await load(language); + } + } + +} + diff --git a/source/logic/helpers/mvc.ts b/source/logic/helpers/mvc.ts new file mode 100644 index 0000000..b3723e3 --- /dev/null +++ b/source/logic/helpers/mvc.ts @@ -0,0 +1,166 @@ +namespace lib_mvc +{ + + /** + */ + export type type_event = { + type : string; + data : type_data; + }; + + + /** + */ + export type type_model = + { + listeners : Array<((event : type_event) => Promise)>; + state : type_state; + }; + + + /** + */ + export type type_view = + { + setup : ((model : type_model) => Promise); + update : ((model : type_model, event : type_event) => Promise); + }; + + + /** + */ + export type type_control = + { + setup : ((model : type_model) => Promise); + }; + + + /** + */ + export type type_complex = + { + model : type_model; + views : Array>; + controls : Array>; + }; + + + /** + */ + export function model_make + ( + state : type_state + ) : type_model + { + return { + "listeners": [], + "state": state, + }; + } + + + /** + */ + export function model_listen + ( + model : type_model, + action : ((event : type_event) => Promise) + ) : void + { + model.listeners.push(action); + } + + + /** + */ + export async function model_notify + ( + model : type_model, + event : type_event + ) : Promise + { + for await (const action of model.listeners) + { + action(event); + } + } + + + /** + */ + export async function view_setup + ( + view : type_view, + model : type_model, + options : + { + blocking ?: boolean; + } = {} + ) : Promise + { + options = Object.assign + ( + { + "blocking": true, + }, + options + ); + await view.setup(model); + model_listen + ( + model, + (info) => + { + if (options.blocking) + { + return view.update(model, info); + } + else + { + view.update(model, info); + return Promise.resolve(undefined); + } + } + ); + } + + + /** + */ + export async function control_setup + ( + control : type_control, + model : type_model, + options : + { + } = {} + ) : Promise + { + options = Object.assign + ( + { + }, + options + ); + await control.setup(model); + } + + + /** + */ + export async function complex_setup + ( + complex : type_complex + ) : Promise + { + for await (const view of complex.views) + { + await view_setup(view, complex.model); + } + for await (const control of complex.controls) + { + await control_setup(control, complex.model); + } + } + +} diff --git a/source/logic/helpers/nextbike.ts b/source/logic/helpers/nextbike.ts new file mode 100644 index 0000000..4c9f9c7 --- /dev/null +++ b/source/logic/helpers/nextbike.ts @@ -0,0 +1,211 @@ +namespace lib_nextbike +{ + + /** + */ + let _baseurl : (null | string) = null; + + + /** + */ + let _apikey : (null | string) = null; + + + /** + */ + let _testmode : boolean = false; + + + /** + */ + export function setup + ( + baseurl : string, + apikey : string, + options : + { + testmode ?: boolean; + } = {} + ) : void + { + options = Object.assign + ( + { + "testmode": false, + }, + options + ); + _baseurl = baseurl; + _apikey = apikey; + _testmode = options.testmode; + } + + + /** + */ + async function api_call + ( + action : string, + loginkey : (null | string), + input : Record + ) : Promise + { + if ((_baseurl === null) || (_apikey === null)) + { + throw (new Error("API not set up yet; execute 'setup' function")); + } + else + { + if (_testmode) + { + return { + "user": { + "loginkey": "", + }, + }; + } + else + { + return ( + fetch + ( + lib_string.coin + ( + "{{baseurl}}?{{query}}", + { + "baseurl": _baseurl, + "query": lib_call.convey + ( + { + "version": "v2", + "format": "json", + "apikey": _apikey, + "action": action, + "loginkey": loginkey, + }, + [ + Object.entries, + x => x.filter(([key, value]) => (value !== null)), + x => x.map(([key, value]) => lib_string.coin("{{key}}={{value}}", {"key": key, "value": value})), + x => x.join("&") + ] + ), + } + ), + { + "method": "POST", + "body": JSON.stringify(input), + } + ) + .then + ( + response => response.json() + ) + ); + } + } + } + + + /** + */ + export async function login + ( + mobile : string, + pin : string + ) : Promise + { + return api_call + ( + "login", + null, + {"mobile": mobile, "pin": pin} + ); + } + + + /** + */ + export async function logout + ( + loginkey : string + ) : Promise + { + return api_call + ( + "logout", + loginkey, + {} + ); + } + + + /** + */ + export async function open_lock + ( + loginkey : string, + bike : string + ) : Promise + { + return api_call + ( + "openLock", + loginkey, + {"bike": bike} + ); + } + + + /** + */ + export async function rental_begin + ( + loginkey : string, + bike : string + ) : Promise + { + return api_call + ( + "rent", + loginkey, + {"bike": bike} + ); + } + + + /** + */ + export async function rental_break + ( + loginkey : string, + bike : string + ) : Promise + { + return api_call + ( + "rentalBreak", + loginkey, + {"bike": bike} + ); + } + + + /** + */ + export async function rental_end + ( + loginkey : string, + bike : string + ) : Promise + { + return api_call + ( + "return", + loginkey, + {"bike": bike} + ); + } + +} + diff --git a/source/logic/helpers/storage.ts b/source/logic/helpers/storage.ts new file mode 100644 index 0000000..526c532 --- /dev/null +++ b/source/logic/helpers/storage.ts @@ -0,0 +1,39 @@ + +namespace lib_storage +{ + + /** + */ + export function write + ( + key : string, + value : any + ) : void + { + window.localStorage.setItem(key, JSON.stringify(value)); + } + + + /** + */ + export function read + ( + key : string + ) : any + { + return JSON.parse(window.localStorage.getItem(key)); + } + + + /** + */ + export function kill + ( + key : string + ) : any + { + return window.localStorage.removeItem(key); + } + +} + diff --git a/source/logic/helpers/string.ts b/source/logic/helpers/string.ts new file mode 100644 index 0000000..f45637c --- /dev/null +++ b/source/logic/helpers/string.ts @@ -0,0 +1,29 @@ + +namespace lib_string +{ + + /** + */ + export function coin + ( + template : string, + arguments_ : Record + ) : string + { + let result : string = template; + Object.entries(arguments_).forEach + ( + ([key, value]) => + { + result = result.replace + ( + new RegExp("{{" + key + "}}", "g"), + value + ); + } + ); + return result; + } + +} + diff --git a/source/logic/main.ts b/source/logic/main.ts new file mode 100644 index 0000000..397f141 --- /dev/null +++ b/source/logic/main.ts @@ -0,0 +1,179 @@ +/** + */ +async function do_rental +( + rental_model : lib_mvc.type_model, + target_dom : HTMLElement +) : Promise<{complex : lib_mvc.type_complex; element : HTMLElement;}> +{ + const platform : lib_mvc.type_model = lib_mvc.model_make + ( + mod_platform.rental.web.make(rental_model) + ); + + const rental_complex : lib_mvc.type_complex = + { + "model": platform, + "views": + [ + mod_view.rental.console_.implementation_view(), + mod_view.rental.web.implementation_view(), + ], + "controls": + [ + mod_control.rental.implementation_control(), + ], + }; + await mod_platform.rental.web.setup(platform, target_dom); + await lib_mvc.complex_setup(rental_complex); + + return Promise.resolve<{complex : lib_mvc.type_complex; element : HTMLElement;}> + ( + { + "complex": rental_complex, + "element": platform.state.element_dom, + } + ); +} + + +/** + */ +async function do_app +( + app_model : lib_mvc.type_model, + target_dom : HTMLElement +) : Promise<{complex : lib_mvc.type_complex; element : HTMLElement;}> +{ + const platform : lib_mvc.type_model = lib_mvc.model_make + ( + mod_platform.app.web.make(app_model) + ); + const app_complex : lib_mvc.type_complex = + { + "model": platform, + "views": + [ + mod_view.app.console_.implementation_view(), + mod_view.app.web.implementation_view(), + ], + "controls": + [ + mod_control.app.implementation_control(), + ], + }; + await mod_platform.app.web.setup(platform, target_dom); + await lib_mvc.complex_setup(app_complex); + + return Promise.resolve<{complex : lib_mvc.type_complex; element : HTMLElement;}> + ( + { + "complex": app_complex, + "element": platform.state.element_dom, + } + ); +} + + +/** + */ +async function main +( +) : Promise +{ + // consts + const styles : Record = + { + "default": {"sheet_name": "style/default.css", "force_language": null}, + "strogg": {"sheet_name": "style/strogg.css", "force_language": "en"}, + }; + + // args + const url : URL = new URL(window.location.href); + let parameters : Record = {}; + url.searchParams.forEach((value, key) => {parameters[key] = value;}); + const style_name : string = ( + (! ("style" in parameters)) + ? "default" + : ((! (parameters["style"] in styles)) ? "default" : parameters["style"]) + ); + const language : (null | string) = (parameters["language"] ?? null); + + // vars + const style : mod_view.app.web.type_style = styles[style_name]; + + // load conf + await lib_conf.load("conf.json"); + + // setup localization + await lib_loc.setup + ( + ( + [ + style.force_language, + language, + navigator.language, + ] + .filter(x => (x !== null)) + ), + { + "resolve_path": (language => lib_string.coin("localization/{{language}}.json", {"language": language})), + } + ); + + // setup nextbike api + lib_nextbike.setup(lib_conf.get("baseurl"), lib_conf.get("apikey"), {"testmode": true}); + + // place stylesheet + { + let link_dom : HTMLElement = document.createElement("link"); + link_dom.setAttribute("type", "text/css"); + link_dom.setAttribute("rel", "stylesheet"); + link_dom.setAttribute("href", style.sheet_name); + document.querySelector("head").appendChild(link_dom); + } + + // exec + const loginkey : (null | string) = lib_storage.read("nextbike_loginkey"); + + let app_model : lib_mvc.type_model = lib_mvc.model_make(mod_model.app.inital(loginkey)); + const app_deed = await do_app(app_model, document.querySelector("body")); + + // sub:rentals + { + lib_mvc.model_listen + ( + app_model, + async (event) => + { + switch (event.type) + { + default: + { + // do nothing + break; + } + case "new_rental": + { + let rental_model : lib_mvc.type_model = event.data["rental_model"]; + let rentals_dom : HTMLElement = app_deed.element.querySelector(".app_rentals"); + const rental_deed = await do_rental(rental_model, rentals_dom); + return Promise.resolve(undefined); + break; + } + case "end_rental": + { + let rentals_dom : HTMLElement = app_deed.element.querySelector(".app_rentals"); + let rental_dom : HTMLElement = document.querySelector + ( + lib_string.coin(".rental[rel=\"{{rel}}\"]", {"rel": event.data["id"]}) + ); + rentals_dom.removeChild(rental_dom); + return Promise.resolve(undefined); + break; + } + } + } + ); + } +} diff --git a/source/logic/model/app.ts b/source/logic/model/app.ts new file mode 100644 index 0000000..42666f4 --- /dev/null +++ b/source/logic/model/app.ts @@ -0,0 +1,231 @@ +namespace mod_model.app +{ + + /** + */ + export enum enum_condition + { + logged_out, + logged_in, + } + + + /** + */ + export type type_subject = + { + condition : (null | enum_condition); + loginkey : (null | string); + rental_counter : int; + rentals : + { + offer : Record>; + order : Array; + }; + }; + + + /** + */ + export function inital + ( + loginkey : (null | string) + ) : type_subject + { + return { + "condition": ((loginkey === null) ? enum_condition.logged_out : enum_condition.logged_in), + "loginkey": loginkey, + "rental_counter": 0, + "rentals": {"offer": {}, "order": []}, + }; + } + + + /** + */ + export async function login + ( + model : lib_mvc.type_model, + username : string, + password : string + ) : Promise + { + const result : any = await lib_nextbike.login(username, password); + model.state.loginkey = result["user"]["loginkey"]; + model.state.condition = enum_condition.logged_in; + lib_storage.write("nextbike_loginkey", model.state.loginkey); + lib_mvc.model_notify + ( + model, + { + "type": "login", + "data": {} + } + ); + } + + + /** + * @todo end rentals? + */ + export async function logout + ( + model : lib_mvc.type_model + ) : Promise + { + const result : any = await lib_nextbike.logout(model.state.loginkey); + lib_storage.kill("nextbike_loginkey"); + model.state.condition = enum_condition.logged_out; + model.state.rentals = {"offer": {}, "order": []}; + lib_mvc.model_notify + ( + model, + { + "type": "logout", + "data": {} + } + ); + } + + + /** + */ + export async function prepare_rental + ( + model : lib_mvc.type_model + ) : Promise + { + model.state.rental_counter += 1; + const rental_id : mod_model.rental.type_id = model.state.rental_counter.toFixed(0); + const rental_subject : mod_model.rental.type_subject = mod_model.rental.initial(rental_id); + const rental_model : lib_mvc.type_model = lib_mvc.model_make(rental_subject); + lib_mvc.model_listen + ( + rental_model, + async (event) => + { + switch (event.type) + { + default: + { + // do nothing + break; + } + case "start": + { + await start_rental(model, rental_subject.bike_name); + break; + } + case "pause": + { + await pause_rental(model, rental_subject.bike_name); + break; + } + case "open": + { + await open_lock(model, rental_subject.bike_name); + break; + } + case "end": + { + await end_rental(model, rental_subject.id); + break; + } + } + } + ); + model.state.rentals.offer[rental_id] = lib_mvc.model_make(rental_subject); + model.state.rentals.order.push(rental_id); + lib_mvc.model_notify}> + ( + model, + { + "type": "new_rental", + "data": {"rental_model": rental_model} + } + ); + } + + + /** + */ + async function start_rental + ( + model : lib_mvc.type_model, + bike_name : string + ) : Promise + { + const result : any = await lib_nextbike.rental_begin(model.state.loginkey, bike_name); + lib_mvc.model_notify + ( + model, + { + "type": "start_rental", + "data": {} + } + ); + } + + + /** + */ + async function pause_rental + ( + model : lib_mvc.type_model, + bike_name : string + ) : Promise + { + const result : any = await lib_nextbike.rental_break(model.state.loginkey, bike_name); + lib_mvc.model_notify + ( + model, + { + "type": "pause_rental", + "data": {} + } + ); + } + + + /** + */ + async function open_lock + ( + model : lib_mvc.type_model, + bike_name : string + ) : Promise + { + const result : any = await lib_nextbike.open_lock(model.state.loginkey, bike_name); + lib_mvc.model_notify + ( + model, + { + "type": "open_lock", + "data": {} + } + ); + } + + + /** + */ + function end_rental + ( + model : lib_mvc.type_model, + rental_id : mod_model.rental.type_id + ) : Promise + { + lib_mvc.model_notify + ( + model, + { + "type": "end_rental", + "data": {"id": rental_id} + } + ); + model.state.rentals.order = model.state.rentals.order.filter(x => (x !== rental_id)); + delete model.state.rentals.offer[rental_id]; + return Promise.resolve(undefined); + } + +} diff --git a/source/logic/model/rental.ts b/source/logic/model/rental.ts new file mode 100644 index 0000000..11c2360 --- /dev/null +++ b/source/logic/model/rental.ts @@ -0,0 +1,120 @@ +namespace mod_model.rental +{ + + /** + */ + export type type_id = string; + + + /** + */ + export enum enum_condition + { + prior, + running, + paused, + } + + + /** + */ + export type type_subject = + { + id : type_id; + condition : enum_condition; + bike_name : (null | string); + }; + + + /** + */ + export function initial + ( + id : type_id + ) : type_subject + { + return { + "id": id, + "condition": enum_condition.prior, + "bike_name": null, + }; + } + + + /** + */ + export async function start + ( + model : lib_mvc.type_model, + bike_name : string + ) : Promise + { + model.state.condition = enum_condition.running; + model.state.bike_name = bike_name; + await lib_mvc.model_notify + ( + model, + { + "type": "start", + "data": {"id": model.state.id} + } + ); + } + + + /** + */ + export async function pause + ( + model : lib_mvc.type_model + ) : Promise + { + model.state.condition = enum_condition.paused; + await lib_mvc.model_notify + ( + model, + { + "type": "pause", + "data": {"id": model.state.id} + } + ); + } + + + /** + */ + export async function open + ( + model : lib_mvc.type_model + ) : Promise + { + model.state.condition = enum_condition.running; + await lib_mvc.model_notify + ( + model, + { + "type": "open", + "data": {"id": model.state.id} + } + ); + } + + + /** + */ + export async function end + ( + model : lib_mvc.type_model + ) : Promise + { + await lib_mvc.model_notify + ( + model, + { + "type": "end", + "data": {"id": model.state.id} + } + ); + } + +} diff --git a/source/logic/platform/web/app.ts b/source/logic/platform/web/app.ts new file mode 100644 index 0000000..1b06d09 --- /dev/null +++ b/source/logic/platform/web/app.ts @@ -0,0 +1,66 @@ +namespace mod_platform.app.web +{ + + /** + */ + export type type_state = + { + model : lib_mvc.type_model; + element_dom : (null | HTMLElement); + } + + + /** + */ + export function make + ( + model : lib_mvc.type_model + ) : type_state + { + return { + "model": model, + "element_dom": null, + }; + } + + + /** + */ + export function setup + ( + platform : lib_mvc.type_model, + target_dom : HTMLElement + ) : Promise + { + let fragment : DocumentFragment = lib_dom.request("app"); + let app_dom : HTMLElement = fragment.querySelector(".app"); + target_dom.appendChild(fragment); + + app_dom.classList.add("empty"); + + platform.state.element_dom = app_dom; + + // propagate events from the model + lib_mvc.model_listen + ( + platform.state.model, + (event) => + { + return lib_mvc.model_notify(platform, event); + } + ); + + return Promise.resolve(undefined); + } + + + // imitate interface of model + // export function login(platform : lib_mvc.type_model, username : string, password : string) : Promise {return mod_model.app.login(platform.state.model, username, password);} + // export function logout(platform : lib_mvc.type_model) : Promise {return mod_model.app.logout(platform.state.model);} + // export function prepare_rental(platform : lib_mvc.type_model) : Promise {return mod_model.app.prepare_rental(platform.state.model);} + // function start_rental(platform : lib_mvc.type_model, bike_name : string) : Promise {return mod_model.app.start_rental(platform.model, bike_name);} + // function pause_rental(platform : lib_mvc.type_model, bike_name : string) : Promise {return mod_model.app.pause_rental(platform.model, bike_name);} + // function open_lock(platform : lib_mvc.type_model, bike_name : string) : Promise {return mod_model.app.open_lock(platform.model, bike_name);} + // function end_rental(platform : lib_mvc.type_model, rental_id : mod_model.rental.type_id) : Promise {return mod_model.app.end_rental(platform.model, rental_id);} + +} diff --git a/source/logic/platform/web/rental.ts b/source/logic/platform/web/rental.ts new file mode 100644 index 0000000..ec7c429 --- /dev/null +++ b/source/logic/platform/web/rental.ts @@ -0,0 +1,54 @@ +namespace mod_platform.rental.web +{ + + /** + */ + export type type_state = + { + model : lib_mvc.type_model; + element_dom : (null | HTMLElement); + } + + + /** + */ + export function make + ( + model : lib_mvc.type_model + ) : type_state + { + return { + "model": model, + "element_dom": null, + }; + } + + + /** + */ + export function setup + ( + platform : lib_mvc.type_model, + target_dom : HTMLElement + ) : Promise + { + let fragment : DocumentFragment = lib_dom.request("rental"); + let rental_dom : HTMLElement = fragment.querySelector(".rental"); + target_dom.appendChild(fragment); + + platform.state.element_dom = rental_dom; + + // propagate events from the model + lib_mvc.model_listen + ( + platform.state.model, + (event) => + { + return lib_mvc.model_notify(platform, event); + } + ); + + return Promise.resolve(undefined); + } + +} diff --git a/source/logic/view/console/app.ts b/source/logic/view/console/app.ts new file mode 100644 index 0000000..a3d9283 --- /dev/null +++ b/source/logic/view/console/app.ts @@ -0,0 +1,40 @@ +namespace mod_view.app.console_ +{ + + /** + */ + export function setup + ( + platform : lib_mvc.type_model, + ) : Promise + { + return Promise.resolve(undefined); + } + + + /** + */ + function update + ( + platform : lib_mvc.type_model, + event : lib_mvc.type_event + ) : Promise + { + console.info("app", event.type, platform.state.model, event.data); + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_view + ( + ) : lib_mvc.type_view + { + return { + "setup": (model) => setup(model), + "update": (model, event) => update(model, event), + }; + } + +} diff --git a/source/logic/view/console/rental.ts b/source/logic/view/console/rental.ts new file mode 100644 index 0000000..15a89d2 --- /dev/null +++ b/source/logic/view/console/rental.ts @@ -0,0 +1,40 @@ +namespace mod_view.rental.console_ +{ + + /** + */ + export function setup + ( + platform : lib_mvc.type_model + ) : Promise + { + return Promise.resolve(undefined); + } + + + /** + */ + function update + ( + platform : lib_mvc.type_model, + event : lib_mvc.type_event + ) : Promise + { + console.info("rental", event.type, platform.state.model, event.data); + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_view + ( + ) : lib_mvc.type_view + { + return { + "setup": (model) => setup(model), + "update": (model, event) => update(model, event), + }; + } + +} diff --git a/source/logic/view/web/app.ts b/source/logic/view/web/app.ts new file mode 100644 index 0000000..831950b --- /dev/null +++ b/source/logic/view/web/app.ts @@ -0,0 +1,123 @@ +namespace mod_view.app.web +{ + + /** + */ + export type type_style = + { + sheet_name : string; + force_language : (null | string); + }; + + + /** + */ + export type type_state = + { + context_dom : (null | HTMLElement); + }; + + + /** + */ + function set_condition + ( + state : type_state, + condition : mod_model.app.enum_condition + ) : void + { + state.context_dom.classList.toggle("empty", false); + state.context_dom.classList.toggle("logged_in", (condition === mod_model.app.enum_condition.logged_in)); + state.context_dom.classList.toggle("logged_out", (condition === mod_model.app.enum_condition.logged_out)); + } + + + /** + */ + export function make + ( + ) : type_state + { + return { + "context_dom": null, + }; + } + + + /** + */ + export function setup + ( + state : type_state, + platform : lib_mvc.type_model, + ) : Promise + { + let context_dom : HTMLElement = platform.state.element_dom; + state.context_dom = context_dom; + // translate stuff + { + lib_loc.translate_item(".app_username > input", "username", {"context": context_dom, "kind": "attribute", "parameters": {"key": "placeholder"}}); + lib_loc.translate_item(".app_password > input", "password", {"context": context_dom, "kind": "attribute", "parameters": {"key": "placeholder"}}); + lib_loc.translate_item(".app_login > button", "login", {"context": context_dom}); + lib_loc.translate_item(".app_logout > button", "logout", {"context": context_dom}); + lib_loc.translate_item(".app_rent > button", "rent", {"context": context_dom}); + } + // list rentals + { + /* + let rentals_dom : HTMLElement = document.querySelector("#rentals"); + rentals_dom.innerHTML = ""; + model.rentals.order.forEach + ( + (rental_id) => + { + } + ); + */ + } + set_condition(state, platform.state.model.state.condition); + return Promise.resolve(undefined); + } + + + /** + */ + function update + ( + state : type_state, + platform : lib_mvc.type_model, + event : lib_mvc.type_event + ) : Promise + { + switch (event.type) + { + default: + { + // do nothing + break; + } + case "login": + case "logout": + { + set_condition(state, platform.state.model.state.condition); + break; + } + } + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_view + ( + ) : lib_mvc.type_view + { + let state : type_state = make(); + return { + "setup": (model) => setup(state, model), + "update": (model, event) => update(state, model, event), + }; + } + +} diff --git a/source/logic/view/web/rental.ts b/source/logic/view/web/rental.ts new file mode 100644 index 0000000..6b44f57 --- /dev/null +++ b/source/logic/view/web/rental.ts @@ -0,0 +1,151 @@ +namespace mod_view.rental.web +{ + + /** + */ + export function setup + ( + platform : lib_mvc.type_model + ) : Promise + { + let context_dom : HTMLElement = platform.state.element_dom; + // container + { + context_dom.setAttribute("rel", platform.state.model.state.id); + context_dom.classList.add + ( + ( + (rental_state) => + { + switch (rental_state) + { + case mod_model.rental.enum_condition.prior: return "prior"; + case mod_model.rental.enum_condition.running: return "running"; + case mod_model.rental.enum_condition.paused: return "paused"; + } + } + ) (platform.state.model.state.condition), + ); + } + // bike + { + let bike_dom : HTMLInputElement = (context_dom.querySelector(".rental_bike > input") as HTMLInputElement) + lib_loc.translate_item + ( + ".rental_bike > input", + "bikename", + { + "context": context_dom, + "kind": "attribute", + "parameters": {"key": "placeholder"}, + } + ); + bike_dom.value = (platform.state.model.state.bike_name ?? ""); + const disabled : boolean = ( + ( + (rental_state) => + { + switch (rental_state) + { + case mod_model.rental.enum_condition.prior: return false; + case mod_model.rental.enum_condition.running: return true; + case mod_model.rental.enum_condition.paused: return true; + } + } + ) (platform.state.model.state.condition) + ); + if (! disabled) + { + // do nothing + } + else + { + bike_dom.setAttribute("disabled", "disabled"); + } + } + // start + { + lib_loc.translate_item(".rental_start > button", "start", {"context": context_dom}); + } + // open + { + lib_loc.translate_item(".rental_open > button", "open", {"context": context_dom}); + } + // pause + { + lib_loc.translate_item(".rental_pause > button", "pause", {"context": context_dom}); + } + // finish + { + lib_loc.translate_item(".rental_finish > button", "finish", {"context": context_dom}); + } + return Promise.resolve(undefined); + } + + + /** + */ + function update + ( + platform : lib_mvc.type_model, + event : lib_mvc.type_event + ) : Promise + { + switch (event.type) + { + default: + { + // do nothing + break; + } + case "start": + { + let container_dom : HTMLElement = document.querySelector + ( + lib_string.coin(".rental[rel=\"{{rel}}\"]", {"rel": event.data["id"]}) + ); + container_dom.classList.remove("prior"); + container_dom.classList.remove("paused"); + container_dom.classList.add("running"); + break; + } + case "pause": + { + let container_dom : HTMLElement = document.querySelector + ( + lib_string.coin(".rental[rel=\"{{rel}}\"]", {"rel": event.data["id"]}) + ); + container_dom.classList.remove("prior"); + container_dom.classList.remove("running"); + container_dom.classList.add("paused"); + break; + } + case "open": + { + let container_dom : HTMLElement = document.querySelector + ( + lib_string.coin(".rental[rel=\"{{rel}}\"]", {"rel": event.data["id"]}) + ); + container_dom.classList.remove("prior"); + container_dom.classList.remove("paused"); + container_dom.classList.add("running"); + break; + } + } + return Promise.resolve(undefined); + } + + + /** + */ + export function implementation_view + ( + ) : lib_mvc.type_view + { + return { + "setup": (model) => setup(model), + "update": (model, event) => update(model, event), + }; + } + +} diff --git a/source/media/logo.svg b/source/media/logo.svg new file mode 100644 index 0000000..77b1564 --- /dev/null +++ b/source/media/logo.svg @@ -0,0 +1,74 @@ + + + + + + image/svg+xml + + + + + + + + + + + + +