From 999c2981a69ad6ee2f2acbcc39d455e370f6b5f9 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Wed, 22 Apr 2026 07:47:23 +0200 Subject: [PATCH] [ini] --- .gitignore | 2 + misc/example-1.frm.json | 24 ++++ readme.md | 3 + source/logic/base.ts | 1 + source/logic/form.ts | 75 ++++++++++++ source/logic/helpers/call.ts | 19 +++ source/logic/helpers/file.ts | 17 +++ source/logic/helpers/json.ts | 14 +++ source/logic/helpers/list.ts | 16 +++ source/logic/helpers/map.ts | 42 +++++++ source/logic/helpers/string.ts | 37 ++++++ source/logic/input/_factory.ts | 94 +++++++++++++++ source/logic/input/_interface.ts | 33 ++++++ source/logic/input/checkbox.ts | 113 ++++++++++++++++++ source/logic/input/color.ts | 40 +++++++ source/logic/input/group.ts | 98 ++++++++++++++++ source/logic/input/list.ts | 191 +++++++++++++++++++++++++++++++ source/logic/input/number.ts | 39 +++++++ source/logic/input/simple.ts | 136 ++++++++++++++++++++++ source/logic/input/text.ts | 39 +++++++ source/logic/main.ts | 84 ++++++++++++++ source/structure.html | 20 ++++ source/style.css | 50 ++++++++ tools/build | 35 ++++++ 24 files changed, 1222 insertions(+) create mode 100644 .gitignore create mode 100644 misc/example-1.frm.json create mode 100644 readme.md create mode 100644 source/logic/base.ts create mode 100644 source/logic/form.ts create mode 100644 source/logic/helpers/call.ts create mode 100644 source/logic/helpers/file.ts create mode 100644 source/logic/helpers/json.ts create mode 100644 source/logic/helpers/list.ts create mode 100644 source/logic/helpers/map.ts create mode 100644 source/logic/helpers/string.ts create mode 100644 source/logic/input/_factory.ts create mode 100644 source/logic/input/_interface.ts create mode 100644 source/logic/input/checkbox.ts create mode 100644 source/logic/input/color.ts create mode 100644 source/logic/input/group.ts create mode 100644 source/logic/input/list.ts create mode 100644 source/logic/input/number.ts create mode 100644 source/logic/input/simple.ts create mode 100644 source/logic/input/text.ts create mode 100644 source/logic/main.ts create mode 100644 source/structure.html create mode 100644 source/style.css create mode 100755 tools/build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba98a06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.geany +/build/ diff --git a/misc/example-1.frm.json b/misc/example-1.frm.json new file mode 100644 index 0000000..d1c4ba6 --- /dev/null +++ b/misc/example-1.frm.json @@ -0,0 +1,24 @@ +{ + "type": "group", + "members": { + "active": { + "type": "checkbox", + "label": "aktiv" + }, + "name": { + "type": "text", + "label": "Name" + }, + "ranks": { + "type": "list", + "element": { + "type": "number" + }, + "label": "Rang" + }, + "color": { + "type": "color", + "label": "Farbe" + } + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ea8efe1 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# formgen + + 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/form.ts b/source/logic/form.ts new file mode 100644 index 0000000..0fb001d --- /dev/null +++ b/source/logic/form.ts @@ -0,0 +1,75 @@ +namespace formgen +{ + + /** + */ + type type_action = { + label : string; + target : string; + }; + + + /** + */ + export class class_form + { + + /** + */ + private input : formgen.input.interface_input; + + + /** + */ + private actions : Array; + + + /** + */ + public constructor( + input : formgen.input.interface_input, + actions : Array + ) + { + this.input = input; + this.actions = actions; + } + + + /** + */ + public async setup( + element_target : Element + ) : Promise + { + const element_form : Element = document.createElement("form"); + element_form.classList.add("formgen-form"); + // element_form.setAttribute("method", "POST"); + // input + { + const element_input : Element = document.createElement("div"); + element_input.classList.add("formgen-form-input"); + await this.input.setup(element_input); + element_form.appendChild(element_input); + } + // actions + { + const element_actions : Element = document.createElement("div"); + element_actions.classList.add("formgen-form-actions"); + for (const action of this.actions) + { + const element_action : Element = document.createElement("button"); + element_action.classList.add("formgen-form-button"); + element_action.setAttribute("formaction", action.target); + element_action.textContent = action.label; + element_actions.appendChild(element_action); + } + element_form.appendChild(element_actions); + } + element_target.appendChild(element_form); + return Promise.resolve(undefined); + } + + } + +} diff --git a/source/logic/helpers/call.ts b/source/logic/helpers/call.ts new file mode 100644 index 0000000..2d472b4 --- /dev/null +++ b/source/logic/helpers/call.ts @@ -0,0 +1,19 @@ +namespace formgen.helpers.call +{ + + /** + */ + export function convey( + x : unknown, + fs : Array + ) : unknown + { + let y : unknown = x; + for (const f of fs) + { + y = f(y); + } + return y; + } + +} diff --git a/source/logic/helpers/file.ts b/source/logic/helpers/file.ts new file mode 100644 index 0000000..616483a --- /dev/null +++ b/source/logic/helpers/file.ts @@ -0,0 +1,17 @@ +namespace formgen.helpers.file +{ + + /** + * @todo handle errors + */ + export function read_text( + path : string + ) : Promise + { + return ( + fetch(path) + .then(x => x.text()) + ); + } + +} diff --git a/source/logic/helpers/json.ts b/source/logic/helpers/json.ts new file mode 100644 index 0000000..dd5e066 --- /dev/null +++ b/source/logic/helpers/json.ts @@ -0,0 +1,14 @@ +namespace formgen.helpers.json +{ + + /** + * @todo handle errors + */ + export function decode( + json : string + ) : unknown + { + return JSON.parse(json); + } + +} diff --git a/source/logic/helpers/list.ts b/source/logic/helpers/list.ts new file mode 100644 index 0000000..aea2661 --- /dev/null +++ b/source/logic/helpers/list.ts @@ -0,0 +1,16 @@ +namespace formgen.helpers.list +{ + + /** + */ + export function transform( + list : Array, + function_ : ((type_element_from) => type_element_to) + ) : Array + { + return list.map( + (element) => function_(element) + ); + } + +} diff --git a/source/logic/helpers/map.ts b/source/logic/helpers/map.ts new file mode 100644 index 0000000..264b923 --- /dev/null +++ b/source/logic/helpers/map.ts @@ -0,0 +1,42 @@ +namespace formgen.helpers.map +{ + + /** + */ + export function read( + map : Record, + key : string, + fallback : type_value + ) : type_value + { + return ((key in map) ? map[key] : fallback); + } + + + /** + */ + export function transform( + map : Record, + function_ : ((string, type_value_from) => type_value_to) + ) : Record + { + return Object.fromEntries( + Object.entries(map) + .map(([key, value]) => ([key, function_(key, value)])) + ); + } + + + /** + */ + export function to_pairs( + map : Record, + ) : Array<{key : string; value : type_value;}> + { + return ( + Object.entries(map) + .map(([key, value]) => ({"key": key, "value": value})) + ); + } + +} diff --git a/source/logic/helpers/string.ts b/source/logic/helpers/string.ts new file mode 100644 index 0000000..58a3965 --- /dev/null +++ b/source/logic/helpers/string.ts @@ -0,0 +1,37 @@ +namespace formgen.helpers.string +{ + + /** + */ + let _index : int = 0; + + + /** + */ + export function coin( + template : string, + arguments_ : Record + ) : string + { + let result = template; + for (const [key, value] of Object.entries(arguments_)) + { + result = result.replace( + new RegExp("{{" + key + "}}", "g"), + value + ); + } + return result; + } + + + /** + */ + export function generate( + ) : string + { + _index += 1; + return _index.toFixed(0); + } + +} diff --git a/source/logic/input/_factory.ts b/source/logic/input/_factory.ts new file mode 100644 index 0000000..b36aa04 --- /dev/null +++ b/source/logic/input/_factory.ts @@ -0,0 +1,94 @@ +namespace formgen.input +{ + + /** + */ + export function from_raw( + raw : any + ) : interface_input + { + switch (raw.type) + { + case "checkbox": + { + return ( + new class_input_checkbox( + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + case "number": + { + return ( + new class_input_number( + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + case "text": + { + return ( + new class_input_text( + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + case "color": + { + return ( + new class_input_color( + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + case "list": + { + return ( + new class_input_list( + () => from_raw(raw.element), + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + case "group": + { + return ( + new class_input_group( + formgen.helpers.list.transform( + formgen.helpers.map.to_pairs(raw.members), + (pair) => ({ + "name": pair.key, + "input": from_raw(pair.value), + }) + ), + { + "label": formgen.helpers.map.read<(null | string)>(raw, "label", null), + } + ) + ); + break; + } + default: + { + throw (new Error("unhandled type: " + raw.type)); + break; + } + } + } + +} diff --git a/source/logic/input/_interface.ts b/source/logic/input/_interface.ts new file mode 100644 index 0000000..8ee49e7 --- /dev/null +++ b/source/logic/input/_interface.ts @@ -0,0 +1,33 @@ +namespace formgen.input +{ + + /** + */ + export interface interface_input + { + + /** + */ + setup( + target : Element + ) : Promise + ; + + + /** + */ + write( + value : type_value + ) : Promise + ; + + + /** + */ + read( + ) : Promise + ; + + } + +} diff --git a/source/logic/input/checkbox.ts b/source/logic/input/checkbox.ts new file mode 100644 index 0000000..a2eff27 --- /dev/null +++ b/source/logic/input/checkbox.ts @@ -0,0 +1,113 @@ +namespace formgen.input +{ + + /** + */ + export class class_input_checkbox implements interface_input + { + + /** + */ + private additional_classes : Array; + + + /** + */ + private label : (null | string); + + + /** + */ + private element_input : (null | Element); + + + /** + */ + public constructor( + { + "additional_classes": additional_classes = [], + "label": label = null, + } + : + { + additional_classes ?: Array, + label ?: (null | string); + } + = + { + } + ) + { + this.additional_classes = ["formgen-input-checkbox"].concat(additional_classes); + this.label = label; + this.element_input = null; + } + + + /** + * [implementation] + */ + public setup( + target : Element + ) : Promise + { + const id : string = formgen.helpers.string.generate(); + + const element_container : Element = document.createElement("div"); + element_container.classList.add("formgen-input"); + for (const class_ of this.additional_classes) + { + element_container.classList.add(class_); + } + // label + { + if (this.label === null) + { + // do nothing + } + else + { + const element_label : Element = document.createElement("label"); + element_label.setAttribute("for", id); + element_label.textContent = this.label; + element_container.appendChild(element_label); + } + } + // input + { + const element_input : Element = document.createElement("input"); + element_input.setAttribute("type", "checkbox"); + element_input.setAttribute("id", id); + element_container.appendChild(element_input); + this.element_input = element_input; + } + target.appendChild(element_container); + return Promise.resolve(undefined); + } + + + /** + * [implementation] + */ + public read( + ) : Promise + { + const value : boolean = (this.element_input as HTMLInputElement).checked; + return Promise.resolve(value); + } + + + /** + * [implementation] + */ + public write( + value : boolean + ) : Promise + { + (this.element_input as HTMLInputElement).checked = value; + return Promise.resolve(undefined); + } + + } + +} diff --git a/source/logic/input/color.ts b/source/logic/input/color.ts new file mode 100644 index 0000000..2ae6eed --- /dev/null +++ b/source/logic/input/color.ts @@ -0,0 +1,40 @@ +namespace formgen.input +{ + + /** + * @todo dedicated color type + */ + export class class_input_color extends class_input_simple + { + + /** + */ + public constructor( + { + "additional_classes": additional_classes = [], + "label": label = null, + } + : + { + additional_classes ?: Array, + label ?: (null | string); + } + = + { + } + ) + { + super( + "color", + (value) => value, + (raw) => raw, + { + "label": label, + "additional_classes": ["formgen-input-color"].concat(additional_classes), + } + ); + } + + } + +} diff --git a/source/logic/input/group.ts b/source/logic/input/group.ts new file mode 100644 index 0000000..ead2fb3 --- /dev/null +++ b/source/logic/input/group.ts @@ -0,0 +1,98 @@ +namespace formgen.input +{ + + /** + */ + type type_field = { + name : string; + input : interface_input; + }; + + + /** + */ + export class class_input_group implements interface_input> + { + + /** + */ + private fields : Array>; + + + /** + */ + private label : (null | string); + + + /** + */ + public constructor( + fields : Array>, + { + "label": label = null, + } + : + { + label ?: (null | string); + } + = + { + } + ) + { + this.fields = fields; + this.label = label; + } + + + /** + * [implementation] + */ + public async setup( + element_target : Element + ) : Promise + { + const element_container : Element = document.createElement("div"); + element_container.classList.add("formgen-input"); + element_container.classList.add("formgen-input-group"); + for (const field of this.fields) + { + const element_field : Element = document.createElement("div"); + element_field.classList.add("formgen-input-group-field"); + element_field.setAttribute("rel", field.name); + await field.input.setup(element_field); + element_container.appendChild(element_field); + } + element_target.appendChild(element_container); + return Promise.resolve(undefined); + } + + + /** + * [implementation] + */ + public async read( + ) : Promise> + { + let result : Record = {}; + for (const field of this.fields) + { + result[field.name] = await field.input.read(); + } + return result; + } + + + /** + * [implementation] + */ + public write( + value : Record + ) : Promise + { + return Promise.reject(new Error("not implemented")); + } + + } + +} diff --git a/source/logic/input/list.ts b/source/logic/input/list.ts new file mode 100644 index 0000000..5d47acb --- /dev/null +++ b/source/logic/input/list.ts @@ -0,0 +1,191 @@ +namespace formgen.input +{ + + /** + */ + type type_entry = { + id : string; + input : interface_input; + element : Element; + }; + + + /** + */ + export class class_input_list implements interface_input> + { + + /** + */ + private element_input_factory : (() => interface_input); + + + /** + */ + private label : (null | string); + + + /** + */ + private element_entries : (null | Element); + + + /** + */ + private entries : Array>; + + + /** + */ + public constructor( + element_input_factory : (() => interface_input), + { + "label": label = null, + } + : + { + label ?: (null | string); + } + = + { + } + ) + { + this.element_input_factory = element_input_factory; + this.label = label; + this.element_entries = null; + this.entries = []; + } + + + /** + */ + private remove( + id : string + ) : void + { + const index : int = this.entries.findIndex(entry => (entry.id === id)); + this.entries[index].element.remove(); + this.entries.splice(index, 1); + } + + + /** + */ + private async add( + ) : Promise + { + const id : string = formgen.helpers.string.generate(); + const input : interface_input = this.element_input_factory(); + const element_entry : Element = document.createElement("div"); + element_entry.classList.add("formgen-input-list-element"); + element_entry.setAttribute("rel", id); + // remover + { + const element_remover : Element = document.createElement("button"); + element_remover.classList.add("formgen-input-list-remover"); + element_remover.textContent = "-"; + element_remover.addEventListener( + "click", + (event) => { + event.preventDefault(); + this.remove(id); + } + ); + element_entry.appendChild(element_remover); + } + // input + { + await input.setup(element_entry); + } + this.entries.push( + { + "id": id, + "input": input, + "element": element_entry, + } + ); + this.element_entries.appendChild(element_entry); + } + + + /** + * [implementation] + */ + public async setup( + element_target : Element + ) : Promise + { + const element_container : Element = document.createElement("div"); + { + // label + { + if (this.label === null) + { + // do nothing + } + else + { + const element_label : Element = document.createElement("label"); + element_label.textContent = this.label; + element_container.appendChild(element_label); + } + } + // entries + { + const element_entries : Element = document.createElement("div"); + element_entries.classList.add("formgen-input-list-entries"); + element_container.appendChild(element_entries); + this.element_entries = element_entries; + } + // adder + { + const element_adder : Element = document.createElement("div"); + element_adder.classList.add("formgen-input-list-adder"); + { + const element_adder_button : Element = document.createElement("button"); + element_adder_button.textContent = "+"; + element_adder.appendChild(element_adder_button); + element_adder.addEventListener( + "click", + (event) => { + event.preventDefault(); + this.add(); + } + ); + } + element_container.appendChild(element_adder); + } + } + element_target.appendChild(element_container); + } + + + /** + * [implementation] + */ + public async read( + ) : Promise> + { + let result : Array = []; + for (const entry of this.entries) + { + result.push(await entry.input.read()); + } + return result; + } + + + /** + * [implementation] + */ + public write( + value : Array + ) : Promise + { + return Promise.reject(new Error("not implemented")); + } + + } + +} diff --git a/source/logic/input/number.ts b/source/logic/input/number.ts new file mode 100644 index 0000000..48c2e14 --- /dev/null +++ b/source/logic/input/number.ts @@ -0,0 +1,39 @@ +namespace formgen.input +{ + + /** + */ + export class class_input_number extends class_input_simple + { + + /** + */ + public constructor( + { + "additional_classes": additional_classes = [], + "label": label = null, + } + : + { + additional_classes ?: Array, + label ?: (null | string); + } + = + { + } + ) + { + super( + "number", + (value) => value.toFixed(), + (raw) => parseInt(raw), + { + "label": label, + "additional_classes": ["formgen-input-number"].concat(additional_classes), + } + ); + } + + } + +} diff --git a/source/logic/input/simple.ts b/source/logic/input/simple.ts new file mode 100644 index 0000000..45396f6 --- /dev/null +++ b/source/logic/input/simple.ts @@ -0,0 +1,136 @@ +namespace formgen.input +{ + + /** + */ + export class class_input_simple implements interface_input + { + + /** + */ + private type : string; + + + /** + */ + private value_encode : ((type_value) => string); + + + /** + */ + private value_decode : ((string) => type_value); + + + /** + */ + private additional_classes : Array; + + + /** + */ + private label : (null | string); + + + /** + */ + private element_input : (null | Element); + + + /** + */ + public constructor( + type : string, + value_encode : ((type_value) => string), + value_decode : ((string) => type_value), + { + "additional_classes": additional_classes = [], + "label": label = null, + } + : + { + additional_classes ?: Array, + label ?: (null | string); + } + = + { + } + ) + { + this.type = type; + this.value_encode = value_encode; + this.value_decode = value_decode; + this.additional_classes = additional_classes; + this.label = label; + this.element_input = null; + } + + + /** + * [implementation] + */ + public setup( + element_target : Element + ) : Promise + { + const id : string = formgen.helpers.string.generate(); + + const element_container : Element = document.createElement("div"); + element_container.classList.add("formgen-input"); + for (const class_ of this.additional_classes) + { + element_container.classList.add(class_); + } + // label + { + if (this.label === null) + { + // do nothing + } + else + { + const element_label : Element = document.createElement("label"); + element_label.setAttribute("for", id); + element_label.textContent = this.label; + element_container.appendChild(element_label); + } + } + // input + { + const element_input : Element = document.createElement("input"); + element_input.setAttribute("type", this.type); + element_input.setAttribute("id", id); + element_container.appendChild(element_input); + this.element_input = element_input; + } + element_target.appendChild(element_container); + return Promise.resolve(undefined); + } + + + /** + * [implementation] + */ + public read( + ) : Promise + { + const raw : string = (this.element_input as HTMLInputElement).value; + const value : type_value = this.value_decode(raw); + return Promise.resolve(value); + } + + + /** + * [implementation] + */ + public write( + value : type_value + ) : Promise + { + const raw : string = this.value_encode(value); + (this.element_input as HTMLInputElement).value = raw; + return Promise.resolve(undefined); + } + + } + +} diff --git a/source/logic/input/text.ts b/source/logic/input/text.ts new file mode 100644 index 0000000..4d43546 --- /dev/null +++ b/source/logic/input/text.ts @@ -0,0 +1,39 @@ +namespace formgen.input +{ + + /** + */ + export class class_input_text extends class_input_simple + { + + /** + */ + public constructor( + { + "additional_classes": additional_classes = [], + "label": label = null, + } + : + { + additional_classes ?: Array, + label ?: (null | string); + } + = + { + } + ) + { + super( + "text", + (value) => value, + (raw) => raw, + { + "label": label, + "additional_classes": ["formgen-input-text"].concat(additional_classes), + } + ); + } + + } + +} diff --git a/source/logic/main.ts b/source/logic/main.ts new file mode 100644 index 0000000..aa7fbc0 --- /dev/null +++ b/source/logic/main.ts @@ -0,0 +1,84 @@ +namespace formgen +{ + + /** + */ + let _input : (null | formgen.input.interface_input) = null; + + + /** + */ + async function render( + raw : string, + element_target : Element + ) : Promise + { + element_target.innerHTML = ""; + try + { + const description : any = formgen.helpers.json.decode(raw); + const input : formgen.input.interface_input = formgen.input.from_raw(description); + /* + const form : formgen.class_form = new formgen.class_form( + input, + [ + {"target": "/", "label": "abschicken"}, + ] + ); + await form.setup(element_target) + */ + await input.setup(element_target); + _input = input; + } + catch (error) + { + console.error(error); + } + } + + + /** + */ + function main( + ) : void + { + document.querySelector("#render").addEventListener( + "click", + () => { + render( + (document.querySelector("#input") as HTMLInputElement).value, + document.querySelector("#output") + ); + } + ); + document.querySelector("#read").addEventListener( + "click", + async () => { + if (_input === null) + { + console.warn("no input present"); + } + else + { + const value = await _input.read(); + console.info(value); + } + } + ); + } + + + /** + */ + export function entry( + ) : void + { + document.addEventListener( + "DOMContentLoaded", + () => { + main(); + } + ); + } + +} diff --git a/source/structure.html b/source/structure.html new file mode 100644 index 0000000..53a4377 --- /dev/null +++ b/source/structure.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ +
+
+
+
+ + + diff --git a/source/style.css b/source/style.css new file mode 100644 index 0000000..85cfa6d --- /dev/null +++ b/source/style.css @@ -0,0 +1,50 @@ +html +{ + background-color: #000000; + color: #FFFFFF; +} + +body +{ + max-width: 960px; + margin: auto; + padding: 16px; + + background-color: #202020; + color: #D0D0D0; +} + +#input +{ + width: 100%; + min-height: 240px; +} + +#render +{ + display: block; +} + +#output +{ + width: 100%; + min-height: 240px; +} + +.formgen-input > label +{ + display: block; + font-size: 80%; + font-weight: bold; + text-transform: uppercase; +} + +.formgen-input-group-field +{ + margin-bottom: 8px; +} + +.formgen-form-button +{ + text-transform: uppercase; +} diff --git a/tools/build b/tools/build new file mode 100755 index 0000000..51309a8 --- /dev/null +++ b/tools/build @@ -0,0 +1,35 @@ +#!/usr/bin/env sh + +## consts + +dir_source=source +dir_build=build +cmd_tsc=tsc + + +## exec + +mkdir -p ${dir_build} +${cmd_tsc} \ + --lib es2020,dom \ + ${dir_source}/logic/base.ts \ + ${dir_source}/logic/helpers/call.ts \ + ${dir_source}/logic/helpers/file.ts \ + ${dir_source}/logic/helpers/string.ts \ + ${dir_source}/logic/helpers/json.ts \ + ${dir_source}/logic/helpers/list.ts \ + ${dir_source}/logic/helpers/map.ts \ + ${dir_source}/logic/input/_interface.ts \ + ${dir_source}/logic/input/simple.ts \ + ${dir_source}/logic/input/text.ts \ + ${dir_source}/logic/input/number.ts \ + ${dir_source}/logic/input/color.ts \ + ${dir_source}/logic/input/checkbox.ts \ + ${dir_source}/logic/input/list.ts \ + ${dir_source}/logic/input/group.ts \ + ${dir_source}/logic/input/_factory.ts \ + ${dir_source}/logic/form.ts \ + ${dir_source}/logic/main.ts \ + --outFile ${dir_build}/logic.js +cp ${dir_source}/structure.html ${dir_build}/index.html -u -v +cp ${dir_source}/style.css ${dir_build}/style.css -u -v