Compare commits
131 Commits
Author | SHA1 | Date |
---|---|---|
|
c0171aa8d8 | 2 months ago |
|
6933a89111 | 2 months ago |
|
7a40c27712 | 2 months ago |
|
6de0aca195 | 2 months ago |
|
02203a5dd0 | 2 months ago |
|
a36190bde9 | 2 months ago |
|
41d71f1523 | 2 months ago |
|
1a51451e82 | 2 months ago |
|
9fb347421d | 2 months ago |
|
362d9f870c | 2 months ago |
|
cf6438d623 | 2 months ago |
|
c0b43e38d1 | 2 months ago |
|
d361a1bbee | 2 months ago |
|
c25f9bb93e | 2 months ago |
|
aa65dc1e54 | 2 months ago |
|
5dab23dc14 | 2 months ago |
|
46f12f517d | 2 months ago |
|
ab4d39d55d | 2 months ago |
|
0496bb0295 | 2 months ago |
|
d578a56269 | 2 months ago |
|
61a32a7bfe | 2 months ago |
|
27cc2d930d | 2 months ago |
|
73c85f4d2e | 2 months ago |
|
025573a16c | 2 months ago |
|
467fca6adf | 3 months ago |
|
819ad6bee0 | 3 months ago |
|
e65c2f96cd | 3 months ago |
|
3608ae5cf0 | 3 months ago |
|
2b50ed88d1 | 3 months ago |
|
60776d6fc3 | 3 months ago |
|
b9a92a4df2 | 3 months ago |
|
74974f6c80 | 3 months ago |
|
92a86aa880 | 3 months ago |
|
fc1dfd7e21 | 3 months ago |
|
9f374eeaa4 | 3 months ago |
|
20710fc461 | 3 months ago |
|
db5750ca16 | 3 months ago |
|
0110253876 | 3 months ago |
|
e495aa72ad | 3 months ago |
|
439a98eb33 | 3 months ago |
|
70e5d0d66f | 3 months ago |
|
60faa1a062 | 3 months ago |
|
e539ace5a0 | 3 months ago |
|
23e30b8ffb | 3 months ago |
|
8d6eb00ae4 | 3 months ago |
|
c2a34dd540 | 3 months ago |
|
12f52feca8 | 3 months ago |
|
89bc8106c9 | 3 months ago |
|
c34114c176 | 3 months ago |
|
6ff45892cb | 3 months ago |
|
6f186b64d8 | 3 months ago |
|
dd3d986485 | 3 months ago |
|
d9cef5210d | 3 months ago |
|
26becc9431 | 3 months ago |
|
3afeb8a5e6 | 3 months ago |
|
0209c1012f | 3 months ago |
|
1b81c4df0e | 3 months ago |
|
e4e4c60f27 | 3 months ago |
|
b53ffc3571 | 3 months ago |
|
eca487c2fe | 3 months ago |
|
e0fbe8bd96 | 3 months ago |
|
c228b251b2 | 3 months ago |
|
208481a867 | 3 months ago |
|
aada47b8e0 | 3 months ago |
|
512551c4f4 | 3 months ago |
|
578d26c2e5 | 3 months ago |
|
9f46eaba1a | 3 months ago |
|
40d2546b36 | 3 months ago |
|
6ccee1914c | 3 months ago |
|
8b81393131 | 3 months ago |
|
e88623a3f5 | 3 months ago |
|
614ce95a2b | 3 months ago |
|
65b3debb9e | 3 months ago |
|
653e82e6b6 | 3 months ago |
|
1dd90bd242 | 3 months ago |
|
a30941888f | 3 months ago |
|
ac7bb1e1ff | 3 months ago |
|
752ca8435b | 3 months ago |
|
df8ceb0bd8 | 3 months ago |
|
90d446adb2 | 3 months ago |
|
767893d9f9 | 3 months ago |
|
9557335694 | 3 months ago |
|
430411ff7f | 3 months ago |
|
42ad432bdb | 3 months ago |
|
d8170fad15 | 3 months ago |
|
d5c4865ecf | 3 months ago |
|
626f7ac1ac | 3 months ago |
|
51a4da6638 | 3 months ago |
|
7ff88c0e66 | 5 months ago |
|
76fac29dd6 | 5 months ago |
|
646afba4d3 | 5 months ago |
|
ea9261446d | 5 months ago |
|
e0a60593d2 | 5 months ago |
|
882303a5fa | 5 months ago |
|
eae24e4bd5 | 5 months ago |
|
a0c6f68e23 | 5 months ago |
|
27d0635119 | 5 months ago |
|
0ba13cca94 | 5 months ago |
|
e0046c6aa8 | 5 months ago |
|
12bf468e22 | 5 months ago |
|
89207c163f | 5 months ago |
|
170d00d852 | 5 months ago |
|
4479c92bb4 | 5 months ago |
|
b7fff8604e | 5 months ago |
|
a7fcb5a3ca | 5 months ago |
|
4b6f4006d9 | 5 months ago |
|
9447216e53 | 5 months ago |
|
f41e7f1384 | 5 months ago |
|
c4b0fc9162 | 5 months ago |
|
5e782912f7 | 5 months ago |
|
1632528322 | 5 months ago |
|
b60b89cbea | 5 months ago |
|
0cc60ad07c | 5 months ago |
|
771a05dba8 | 5 months ago |
|
f9e7e188cb | 5 months ago |
|
02f72d4648 | 5 months ago |
|
bb0ccb8904 | 5 months ago |
|
6f78fa1cfb | 5 months ago |
|
3782344e1f | 5 months ago |
|
5b74731cf1 | 5 months ago |
|
94ef6186cf | 5 months ago |
|
3b8efc9a6d | 5 months ago |
|
0c51679e04 | 5 months ago |
|
ec298757c2 | 5 months ago |
|
8121c9ec6a | 7 months ago |
|
9d073a8367 | 7 months ago |
|
cdc1188061 | 7 months ago |
|
ea1786f379 | 7 months ago |
|
4a0bd90799 | 7 months ago |
|
95d0649169 | 7 months ago |
|
fc2c6b15c0 | 7 months ago |
53 changed files with 8092 additions and 1232 deletions
@ -1,3 +1,5 @@ |
|||
node_modules/ |
|||
samples/*/ |
|||
jpc-like-websites.js |
|||
jpc-like-websites.mjs |
|||
extensions/ |
|||
|
@ -1,5 +1,21 @@ |
|||
Copyright (C) Christian Martin - All Rights Reserved |
|||
Unauthorized copying of this file, |
|||
via any medium is strictly prohibited |
|||
Proprietary and confidential |
|||
Written by Christian Martin, September 2024 |
|||
Copyright (c) 2024-2025 Christian Martin |
|||
|
|||
All rights reserved. |
|||
|
|||
This software |
|||
and associated documentation files (the "Software") |
|||
are the exclusive property of Christian Martin. |
|||
You may not use, copy, modify, merge, publish, |
|||
distribute, sublicense, or sell copies of the Software, |
|||
in whole or in part, |
|||
without the express prior written permission of the copyright holder. |
|||
|
|||
For inquiries regarding licensing, please contact: |
|||
christian.martin3(at)gmx.net. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", |
|||
WITHOUT WARRANTY OF ANY KIND, |
|||
EXPRESS OR IMPLIED, |
|||
INCLUDING BUT NOT LIMITED TO |
|||
THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|||
|
@ -0,0 +1,15 @@ |
|||
import js from "@eslint/js"; |
|||
import globals from "globals"; |
|||
import json from "@eslint/json"; |
|||
import css from "@eslint/css"; |
|||
import { defineConfig } from "eslint/config"; |
|||
|
|||
|
|||
export default defineConfig([ |
|||
{ files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"] }, |
|||
{ files: ["**/*.js"], languageOptions: { sourceType: "script" } }, |
|||
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: globals.browser } }, |
|||
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, |
|||
{ files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, |
|||
{ files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, |
|||
]); |
@ -0,0 +1,193 @@ |
|||
const fs = require('fs/promises'); |
|||
const path = require('path'); |
|||
|
|||
const copyright_disclaimer = ` |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* @copyright by its creator |
|||
*/ |
|||
|
|||
`;
|
|||
|
|||
/** |
|||
* Kept for future reference, might be that we will return to the dependency resolvement approach. |
|||
* @param {OAO} oao |
|||
* @returns {OAO} |
|||
*/ |
|||
function resolveDependencyOrder(oao) { |
|||
let roots = oao.keys.filter(k => oao.objects[k].dependencies.length === 0); |
|||
|
|||
let folderList = [...new Set(oao.keys.map(k => oao.objects[k].folder))]; |
|||
|
|||
console.log("Folders", folderList); |
|||
|
|||
/** |
|||
* |
|||
* @param {OAO} oao |
|||
* @param {Array<Array<string>>} dealt |
|||
*/ |
|||
function recursiveResolvement(oao, dealtLvls, dealt) { |
|||
const arrsEqualSize = (arr1, arr2) => arr1.length === arr2.length; |
|||
const arrHasAll = (arr1, arr2) => arr2.every(k => arr1.includes(k)) |
|||
|
|||
if (oao.keys.length === dealt.length) { |
|||
return [...dealt, ...current]; |
|||
} else { |
|||
console.log(`Received ${dealt.length} dealt of ${oao.keys.length}`); |
|||
} |
|||
|
|||
let remaining = oao.keys |
|||
.filter(k => !dealt.includes(k)); |
|||
|
|||
if (remaining.length < 2) { |
|||
return [...dealt, ...remaining]; |
|||
} |
|||
|
|||
let current = remaining |
|||
.filter(k => oao.objects[k].dependencies.every(sk => dealt.includes(sk))); |
|||
|
|||
if (current.length === 0) { |
|||
console.log("Couldn't resolve", remaining); |
|||
return remaining; |
|||
} |
|||
|
|||
dealtLvls.push(current); |
|||
|
|||
if (arrsEqualSize(remaining, current)) { |
|||
return [...dealt, ...current]; |
|||
} |
|||
|
|||
return recursiveResolvement(oao, dealtLvls, [...dealt, ...current]); |
|||
} |
|||
|
|||
let recursiveResolved = recursiveResolvement(oao, [roots], roots); |
|||
|
|||
recursiveResolved |
|||
.forEach((k, i) => { |
|||
oao.objects[k].orderNr = i; |
|||
}); |
|||
|
|||
oao.isOrdered = recursiveResolved.slice(-1).length <= 2; |
|||
if (oao.isOrdered) { |
|||
oao.keys = recursiveResolved; |
|||
} |
|||
return oao; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {string} srcFile path to src file |
|||
* @param {string} targetFile path of target file that will contain all scripts |
|||
*/ |
|||
async function appendContent(srcFile, targetFile, contentModifications = [], lineModifications = []) { |
|||
try { |
|||
let content = await fs.readFile(srcFile, 'utf8'); |
|||
console.log(`Processing '${srcFile}'`); |
|||
console.log(` READ: successfully!`, `Attepting to append file`); |
|||
|
|||
/* Apply modifications */ |
|||
content = contentModifications |
|||
.reduce((a, c) => c(a), content.split('\r\n')) |
|||
.map(line => lineModifications.reduce((a, c) => c(a), line)) |
|||
.join('\r\n'); |
|||
|
|||
await fs.appendFile(targetFile, content); |
|||
|
|||
console.log(` Append/Write: successfully!`); |
|||
} catch (error) { |
|||
console.error(`Error reading/writing files: ${error.message}`); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {string} srcFile path to src file |
|||
* @param {string} targetFile path of target file that will contain all scripts |
|||
*/ |
|||
async function bundleWithSpecifiedMode(targetFile, fileList, modulize = true) { |
|||
let contentModifications = [ |
|||
function (content) { |
|||
return [ |
|||
...copyright_disclaimer.split('\r\n'), |
|||
...content |
|||
]; |
|||
} |
|||
]; |
|||
|
|||
let lineModifications = [ |
|||
function (line) { |
|||
|
|||
let starters = ["function", "const", "let", "class"]; |
|||
if (starters.some(s => line.startsWith(s))) { |
|||
return "export " + line; |
|||
} |
|||
return line; |
|||
} |
|||
]; |
|||
|
|||
|
|||
fileList |
|||
.reduce((prevPromise, filePath) => prevPromise |
|||
.then( |
|||
() => appendContent(filePath, targetFile, contentModifications) |
|||
), Promise.resolve() |
|||
) |
|||
|
|||
if (modulize) { |
|||
let targetModule = path.basename(targetFile).split('.', 1)[0] + '.mjs'; |
|||
fileList |
|||
.reduce((prevPromise, filePath) => prevPromise |
|||
.then( |
|||
() => appendContent(filePath, targetModule, contentModifications, lineModifications) |
|||
), Promise.resolve() |
|||
) |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* @deprecated Currently this function takles the task of successfully bundling the jpc-framework. |
|||
* A custom bundler will replace this function/file. |
|||
* Some logic might be copied/used from here. |
|||
* |
|||
* Takes the given order from the jpclw-bundle.json and bundles the scripts accordingly. |
|||
* |
|||
* @param {string|path} targetFile path to the targetfile (without file extension) |
|||
* @param {boolean} modulize flag to decide to generate a mjs module as well |
|||
* @param {string|path} bundleFile path to the bundleFile if there is some |
|||
* - preparation for jpclw-bundler |
|||
*/ |
|||
function bundle(modulize = false, targetFile = "./jpc-like-websites.js", bundleFile = './jpclw-bundle.json') { |
|||
let fileOrder = require(bundleFile); |
|||
|
|||
console.log("(Re-) Creating target file: '" + targetFile + "'"); |
|||
/* EMPTY (create?) TARGET FILE */ |
|||
fs.writeFile(targetFile, "", err => { }); |
|||
|
|||
if (modulize) { |
|||
let targetModule = path.basename(targetFile).split('.', 1)[0] + '.mjs'; |
|||
fs.writeFile(targetModule, "", err => { }); |
|||
} |
|||
|
|||
let fileList = Object.keys(fileOrder.orderedGroups) |
|||
.flatMap(groupName => fileOrder.orderedGroups[groupName]) |
|||
.map(fileName => { |
|||
let f = fileOrder.objects[fileName]; |
|||
/* The Path build by name and folder */ |
|||
return `${f.folder}/${f.name}`; |
|||
}); |
|||
|
|||
|
|||
bundleWithSpecifiedMode(targetFile, fileList, modulize); |
|||
|
|||
} |
|||
|
|||
let args = process.argv.slice(2); |
|||
bundle(Boolean(args[0]), args[1], args[2]); |
|||
|
@ -0,0 +1,695 @@ |
|||
{ |
|||
"orderedGroups": { |
|||
"base": [ |
|||
"webTrinity.js", |
|||
"extStore.js" |
|||
], |
|||
"helper": [ |
|||
"ObjectAccessObject.js", |
|||
"TwoDimPoint.js", |
|||
"general.js", |
|||
"indices.js" |
|||
], |
|||
"chain_mixins": [ |
|||
"mixinModSubChain.js", |
|||
"ModificationSubChain.js" |
|||
], |
|||
"decorators": [ |
|||
"color.js", |
|||
"alignment.js", |
|||
"arrangement.js", |
|||
"siding.js", |
|||
"padding.js", |
|||
"margin.js", |
|||
"shapes.js", |
|||
"border.js", |
|||
"dimensions.js" |
|||
], |
|||
"handlers": [ |
|||
"commonEvents.js", |
|||
"contextMenu.js", |
|||
"dragAndDrop.js" |
|||
], |
|||
"modifier": [ |
|||
"Modifier.js", |
|||
"ChainableModifier.js" |
|||
], |
|||
"generators":[ |
|||
"generator.js", |
|||
"defaultGenerators.js" |
|||
], |
|||
"component": [ |
|||
"ChildbearerComponent.js", |
|||
"ModifiableComponent.js", |
|||
"StyleAndScriptStoringComponent.js", |
|||
"Component.js", |
|||
"FlexContainerComponent.js", |
|||
"Column.js", |
|||
"Row.js", |
|||
"InputComponent.js" |
|||
], |
|||
"extensions": [ |
|||
"extension.js" |
|||
], |
|||
"context": [ |
|||
"scriptAndStyleContext.js", |
|||
"framework-controls.js", |
|||
"context.js" |
|||
], |
|||
"builder": [ |
|||
"builder.js" |
|||
] |
|||
}, |
|||
"keys": [ |
|||
"extStore.js", |
|||
"webTrinity.js", |
|||
"builder.js", |
|||
"ChildbearerComponent.js", |
|||
"Column.js", |
|||
"Component.js", |
|||
"FlexContainerComponent.js", |
|||
"InputComponent.js", |
|||
"ModifiableComponent.js", |
|||
"Row.js", |
|||
"StyleAndScriptStoringComponent.js", |
|||
"context.js", |
|||
"framework-controls.js", |
|||
"scriptAndStyleContext.js", |
|||
"alignment.js", |
|||
"arrangement.js", |
|||
"border.js", |
|||
"color.js", |
|||
"dimensions.js", |
|||
"margin.js", |
|||
"padding.js", |
|||
"shapes.js", |
|||
"siding.js", |
|||
"extension.js", |
|||
"simplePagingAndNavigation.js", |
|||
"defaultGenerators.js", |
|||
"generator.js", |
|||
"commonEvents.js", |
|||
"contextMenu.js", |
|||
"dragAndDrop.js", |
|||
"general.js", |
|||
"indices.js", |
|||
"ObjectAccessObject.js", |
|||
"TwoDimPoint.js", |
|||
"ChainableModifier.js", |
|||
"mixinModSubChain.js", |
|||
"ModificationSubChain.js", |
|||
"Modifier.js" |
|||
], |
|||
"objects": { |
|||
"extStore.js": { |
|||
"folder": "src/base", |
|||
"name": "extStore.js", |
|||
"exports": [ |
|||
"ESAggregation", |
|||
"ExtStorePosition", |
|||
"OverwriteBehaviour", |
|||
"clearFunctionDeclarationText", |
|||
"getScriptTagInjectionText", |
|||
"FunctionStoreBuffer", |
|||
"ExtStorage", |
|||
"ExtStoreType", |
|||
"SStoreDefinition", |
|||
"resolveOverwrite", |
|||
"identifyAndResolveOverwrite", |
|||
"generateAndFillScriptTag", |
|||
"getStylingInjectionText", |
|||
"generateAndFillStyleTag", |
|||
"executeOnExtStoreTypeCollectedTriple" |
|||
], |
|||
"imports": { |
|||
"../Component": [ |
|||
"Component" |
|||
], |
|||
"../context": [ |
|||
"Page" |
|||
] |
|||
} |
|||
}, |
|||
"webTrinity.js": { |
|||
"folder": "src/base", |
|||
"name": "webTrinity.js", |
|||
"exports": [ |
|||
"WebTrinity" |
|||
] |
|||
}, |
|||
"builder.js": { |
|||
"folder": "src", |
|||
"name": "builder.js", |
|||
"exports": [ |
|||
"builder" |
|||
], |
|||
"imports": { |
|||
"./Component": [ |
|||
"Component", |
|||
"FlexContainerComponent", |
|||
"Row", |
|||
"Column", |
|||
"InputComponent" |
|||
], |
|||
"./modifier": [ |
|||
"Modifier" |
|||
], |
|||
"./context": [ |
|||
"Page" |
|||
] |
|||
} |
|||
}, |
|||
"ChildbearerComponent.js": { |
|||
"folder": "src/component", |
|||
"name": "ChildbearerComponent.js", |
|||
"exports": [ |
|||
"ChildbearerComponent" |
|||
], |
|||
"imports": { |
|||
"../decorators": [ |
|||
"Alignment", |
|||
"Arrangement" |
|||
], |
|||
"../helper": [ |
|||
"helperFun" |
|||
], |
|||
"./Component": [ |
|||
"Component" |
|||
], |
|||
"../builder": [ |
|||
"builder" |
|||
] |
|||
} |
|||
}, |
|||
"Column.js": { |
|||
"folder": "src/component", |
|||
"name": "Column.js", |
|||
"exports": [ |
|||
"Column" |
|||
], |
|||
"imports": { |
|||
"./FlexContainerComponent": [ |
|||
"FlexContainerComponent" |
|||
] |
|||
} |
|||
}, |
|||
"Component.js": { |
|||
"folder": "src/component", |
|||
"name": "Component.js", |
|||
"exports": [ |
|||
"Component" |
|||
], |
|||
"imports": { |
|||
"../base": [ |
|||
"ExtStorage" |
|||
], |
|||
"../modifier": [ |
|||
"Modifier" |
|||
], |
|||
"../context": [ |
|||
"Page", |
|||
"CommonCompelGroups" |
|||
], |
|||
"../handlers": [ |
|||
"DefaultContextMenu", |
|||
"DragAndDropImplementation" |
|||
], |
|||
"../generators": [ |
|||
"CompelGenerator" |
|||
] |
|||
} |
|||
}, |
|||
"FlexContainerComponent.js": { |
|||
"folder": "src/component", |
|||
"name": "FlexContainerComponent.js", |
|||
"exports": [ |
|||
"FlexContainerComponent" |
|||
], |
|||
"imports": { |
|||
"./Component": [ |
|||
"Component" |
|||
], |
|||
"../modifier": [ |
|||
"Modifier" |
|||
] |
|||
} |
|||
}, |
|||
"InputComponent.js": { |
|||
"folder": "src/component", |
|||
"name": "InputComponent.js", |
|||
"exports": [ |
|||
"InputComponent" |
|||
], |
|||
"imports": { |
|||
"./Component": [ |
|||
"Component" |
|||
] |
|||
} |
|||
}, |
|||
"ModifiableComponent.js": { |
|||
"folder": "src/component", |
|||
"name": "ModifiableComponent.js", |
|||
"exports": [ |
|||
"ModifiableComponent" |
|||
], |
|||
"imports": { |
|||
"./ChildbearerComponent": [ |
|||
"ChildbearerComponent" |
|||
], |
|||
"../base": [ |
|||
"SStoreDefinition" |
|||
], |
|||
"../modifier": [ |
|||
"ChainableModifier" |
|||
] |
|||
} |
|||
}, |
|||
"Row.js": { |
|||
"folder": "src/component", |
|||
"name": "Row.js", |
|||
"exports": [ |
|||
"Row" |
|||
], |
|||
"imports": { |
|||
"./FlexContainerComponent": [ |
|||
"FlexContainerComponent" |
|||
], |
|||
"../helper": [ |
|||
"onSingleOrArray" |
|||
] |
|||
} |
|||
}, |
|||
"StyleAndScriptStoringComponent.js": { |
|||
"folder": "src/component", |
|||
"name": "StyleAndScriptStoringComponent.js", |
|||
"exports": [ |
|||
"StyleAndScriptStoringComponent" |
|||
], |
|||
"imports": { |
|||
"./ModifiableComponent": [ |
|||
"ModifiableComponent" |
|||
], |
|||
"../decorators": [ |
|||
"Alignment", |
|||
"Arrangement" |
|||
], |
|||
"../base": [ |
|||
"ExtStorage", |
|||
"ExtStoreType", |
|||
"SStoreDefinition" |
|||
] |
|||
} |
|||
}, |
|||
"context.js": { |
|||
"folder": "src/context", |
|||
"name": "context.js", |
|||
"exports": [ |
|||
"PageBuilder", |
|||
"CommonCompelGroups", |
|||
"Page" |
|||
], |
|||
"imports": { |
|||
"./scriptAndStyleContext": [ |
|||
"ScriptAndStyleContext" |
|||
], |
|||
"../extensions": [ |
|||
"CompelExtension" |
|||
] |
|||
} |
|||
}, |
|||
"framework-controls.js": { |
|||
"folder": "src/context", |
|||
"name": "framework-controls.js", |
|||
"exports": [ |
|||
"frameworkControlPanel" |
|||
], |
|||
"imports": { |
|||
"../builder": [ |
|||
"builder" |
|||
], |
|||
"../modifier": [ |
|||
"Modifier" |
|||
], |
|||
"../decorators": [ |
|||
"Alignment", |
|||
"Arrangement", |
|||
"Colors", |
|||
"Border", |
|||
"MaterialFiveHundredlColors" |
|||
], |
|||
"../base": [ |
|||
"ExtStoreType" |
|||
], |
|||
"../Component": [ |
|||
"Component" |
|||
] |
|||
} |
|||
}, |
|||
"scriptAndStyleContext.js": { |
|||
"folder": "src/context", |
|||
"name": "scriptAndStyleContext.js", |
|||
"exports": [ |
|||
"ScriptAndStyleContext" |
|||
], |
|||
"imports": { |
|||
"../base": [ |
|||
"OverwriteBehaviour", |
|||
"FunctionStoreBuffer" |
|||
] |
|||
} |
|||
}, |
|||
"alignment.js": { |
|||
"folder": "src/decorators", |
|||
"name": "alignment.js", |
|||
"exports": [ |
|||
"Alignment" |
|||
] |
|||
}, |
|||
"arrangement.js": { |
|||
"folder": "src/decorators", |
|||
"name": "arrangement.js", |
|||
"exports": [ |
|||
"Arrangement" |
|||
] |
|||
}, |
|||
"border.js": { |
|||
"folder": "src/decorators", |
|||
"name": "border.js", |
|||
"exports": [ |
|||
"LineStyles", |
|||
"BorderDefinition", |
|||
"Define", |
|||
"Border", |
|||
"BorderChain", |
|||
"BorderChainedModifier" |
|||
], |
|||
"imports": { |
|||
"./siding": [ |
|||
"SizeUnits", |
|||
"Sides" |
|||
], |
|||
"./color": [ |
|||
"Color", |
|||
"Colors" |
|||
], |
|||
"./shapes": [ |
|||
"Shapes" |
|||
], |
|||
"../modifier": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
] |
|||
} |
|||
}, |
|||
"color.js": { |
|||
"folder": "src/decorators", |
|||
"name": "color.js", |
|||
"exports": [ |
|||
"Color", |
|||
"Colors", |
|||
"MaterialFiveHundredlColors" |
|||
] |
|||
}, |
|||
"dimensions.js": { |
|||
"folder": "src/decorators", |
|||
"name": "dimensions.js", |
|||
"exports": [ |
|||
"Dimensions", |
|||
"DimensionsChain", |
|||
"DimensionsChainedModifier" |
|||
], |
|||
"imports": { |
|||
"./siding": [ |
|||
"DirectionUnitDependentAttribute", |
|||
"SizeUnits" |
|||
], |
|||
"../helper": [ |
|||
"TwoDimPoint" |
|||
], |
|||
"../modifier": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
] |
|||
} |
|||
}, |
|||
"margin.js": { |
|||
"folder": "src/decorators", |
|||
"name": "margin.js", |
|||
"exports": [ |
|||
"Margin", |
|||
"MarginChain", |
|||
"MarginChainedModifier" |
|||
], |
|||
"imports": { |
|||
"./siding": [ |
|||
"Sides", |
|||
"SizeUnits" |
|||
], |
|||
"../modifier": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
] |
|||
} |
|||
}, |
|||
"padding.js": { |
|||
"folder": "src/decorators", |
|||
"name": "padding.js", |
|||
"exports": [ |
|||
"Padding", |
|||
"PaddingChain", |
|||
"PaddingChainedModifier" |
|||
], |
|||
"imports": { |
|||
"./siding": [ |
|||
"Sides", |
|||
"SizeUnits" |
|||
], |
|||
"../modifier": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
] |
|||
} |
|||
}, |
|||
"shapes.js": { |
|||
"folder": "src/decorators", |
|||
"name": "shapes.js", |
|||
"exports": [ |
|||
"Shape", |
|||
"ShapeChain", |
|||
"ShapeChainedModifier", |
|||
"Shapes" |
|||
], |
|||
"imports": { |
|||
"./siding": [ |
|||
"DirectionUnitDependentAttribute", |
|||
"SizeUnits", |
|||
"SidingRefCorners" |
|||
], |
|||
"../modifier": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
] |
|||
} |
|||
}, |
|||
"siding.js": { |
|||
"folder": "src/decorators", |
|||
"name": "siding.js", |
|||
"exports": [ |
|||
"SizeUnits", |
|||
"DirectionUnitDependentAttribute", |
|||
"SideDirections", |
|||
"SideTransitionDirection", |
|||
"Corners", |
|||
"CornerTransitionDirection", |
|||
"Sides" |
|||
] |
|||
}, |
|||
"extension.js": { |
|||
"folder": "src/extensions", |
|||
"name": "extension.js" |
|||
}, |
|||
"simplePagingAndNavigation.js": { |
|||
"folder": "src/extensions", |
|||
"name": "simplePagingAndNavigation.js" |
|||
}, |
|||
"defaultGenerators.js": { |
|||
"folder": "src/generators", |
|||
"name": "defaultGenerators.js", |
|||
"exports": [ |
|||
"singlepage" |
|||
], |
|||
"imports": { |
|||
"./generator": [ |
|||
"CompelGenerator" |
|||
] |
|||
} |
|||
}, |
|||
"generator.js": { |
|||
"folder": "src/generators", |
|||
"name": "generator.js", |
|||
"exports": [ |
|||
"CompelGenerator" |
|||
], |
|||
"imports": { |
|||
"../base": [ |
|||
"WebTrinity", |
|||
"ExtStoreType" |
|||
], |
|||
"../Component": [ |
|||
"Component" |
|||
], |
|||
"../modifier": [ |
|||
"Modifier" |
|||
] |
|||
} |
|||
}, |
|||
"commonEvents.js": { |
|||
"folder": "src/handlers", |
|||
"name": "commonEvents.js", |
|||
"exports": [ |
|||
"CommonEvents" |
|||
] |
|||
}, |
|||
"contextMenu.js": { |
|||
"folder": "src/handlers", |
|||
"name": "contextMenu.js", |
|||
"exports": [ |
|||
"DefaultContextMenu" |
|||
], |
|||
"imports": { |
|||
"../decorators": [ |
|||
"Sides" |
|||
], |
|||
"../helper": [ |
|||
"helperFun", |
|||
"getEnclosingBounds" |
|||
] |
|||
} |
|||
}, |
|||
"dragAndDrop.js": { |
|||
"folder": "src/handlers", |
|||
"name": "dragAndDrop.js", |
|||
"exports": [ |
|||
"EventDrag", |
|||
"DragAndDropImplementation", |
|||
"DADInPlace" |
|||
], |
|||
"imports": { |
|||
"../helper": [ |
|||
"TwoDimPoint" |
|||
], |
|||
"../decorators": [ |
|||
"Dimensions" |
|||
] |
|||
} |
|||
}, |
|||
"general.js": { |
|||
"folder": "src/helper", |
|||
"name": "general.js", |
|||
"exports": [ |
|||
"onSingleOrArray", |
|||
"helperFun" |
|||
] |
|||
}, |
|||
"indices.js": { |
|||
"folder": "src/helper", |
|||
"name": "indices.js", |
|||
"exports": [ |
|||
"isValueInBounds", |
|||
"areXYInArea", |
|||
"isPointInArea", |
|||
"getEnclosingBounds" |
|||
], |
|||
"imports": { |
|||
"../decorators": [ |
|||
"SideDirections" |
|||
], |
|||
"./TwoDimPoint": [ |
|||
"TwoDimPoint" |
|||
] |
|||
} |
|||
}, |
|||
"ObjectAccessObject.js": { |
|||
"folder": "src/helper", |
|||
"name": "ObjectAccessObject.js", |
|||
"exports": [ |
|||
"ObjectAccessObject" |
|||
] |
|||
}, |
|||
"TwoDimPoint.js": { |
|||
"folder": "src/helper", |
|||
"name": "TwoDimPoint.js", |
|||
"exports": [ |
|||
"TwoDimPoint" |
|||
] |
|||
}, |
|||
"ChainableModifier.js": { |
|||
"folder": "src/modifier", |
|||
"name": "ChainableModifier.js", |
|||
"exports": [ |
|||
"ChainableModifier" |
|||
], |
|||
"imports": { |
|||
"./modifier": [ |
|||
"Modifier" |
|||
], |
|||
"../Component": [ |
|||
"Component" |
|||
], |
|||
"../decorators": [ |
|||
"PaddingChainedModifier", |
|||
"MarginChainedModifier", |
|||
"ShapeChainedModifier", |
|||
"BorderChainedModifier" |
|||
] |
|||
} |
|||
}, |
|||
"mixinModSubChain.js": { |
|||
"folder": "src/modifier", |
|||
"name": "mixinModSubChain.js", |
|||
"exports": [ |
|||
"mixinModSubChainEndings", |
|||
"mixinModSubChainComponentMethods" |
|||
], |
|||
"imports": { |
|||
"./modifier": [ |
|||
"Modifier" |
|||
], |
|||
"./ChainableModifier": [ |
|||
"ChainableModifier" |
|||
] |
|||
} |
|||
}, |
|||
"ModificationSubChain.js": { |
|||
"folder": "src/modifier", |
|||
"name": "ModificationSubChain.js", |
|||
"exports": [ |
|||
"ModificationSubChain", |
|||
"ModificationSubChainReComp" |
|||
] |
|||
}, |
|||
"Modifier.js": { |
|||
"folder": "src/modifier", |
|||
"name": "Modifier.js", |
|||
"exports": [ |
|||
"Modifier" |
|||
], |
|||
"imports": { |
|||
"../decorators": [ |
|||
"Sides", |
|||
"Border", |
|||
"BorderChain", |
|||
"Color", |
|||
"Dimensions", |
|||
"DimensionsChain", |
|||
"Margin", |
|||
"MarginChain", |
|||
"Padding", |
|||
"PaddingChain", |
|||
"Shape", |
|||
"ShapeChain" |
|||
] |
|||
} |
|||
} |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,20 +1,31 @@ |
|||
{ |
|||
"name": "websites-like-jpc", |
|||
"version": "1.0.0", |
|||
"name": "jpc-like-websites", |
|||
"version": "1.9.13.34", |
|||
"description": "Framework to build websites in a Jetpack Compose like manner, as well as an extensive use of method-chaingin.", |
|||
"main": "lib/index.js", |
|||
"typings": "lib/index.d.ts", |
|||
"main": "jpc-like-websites.js", |
|||
"exports": { |
|||
"vanilla": "./jpc-like-websites.js", |
|||
"module": "./jpc-like-websites.mjs" |
|||
}, |
|||
"files": [ |
|||
"/lib" |
|||
"/src" |
|||
], |
|||
"scripts": { |
|||
"prepare": "npm run build", |
|||
"build": "tsc", |
|||
"generate": "node generate_single_file.js true", |
|||
"build": "npm run generate", |
|||
"test": "echo \"Error: no test specified\" && exit 1" |
|||
}, |
|||
"author": "", |
|||
"license": "ISC", |
|||
"author": "cm", |
|||
"license": "SEE LICENCE.md", |
|||
"dependencies": { |
|||
"jpclw-extension": "git+https://git.labos.goip.de/chris/jpclw-extension.git" |
|||
}, |
|||
"devDependencies": { |
|||
"typescript": "^5.6.2" |
|||
"@eslint/css": "^0.7.0", |
|||
"@eslint/js": "^9.26.0", |
|||
"@eslint/json": "^0.12.0", |
|||
"eslint": "^9.26.0", |
|||
"globals": "^16.1.0" |
|||
} |
|||
} |
|||
} |
@ -1,19 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* Enum providing common alignment rules |
|||
*/ |
|||
const Arrangement = Object.freeze({ |
|||
START: "start", |
|||
END: "end", |
|||
CENTER: "center", |
|||
SPACE_BETWEEN: "space-between", |
|||
SPACE_EVENLY: "space-evenly", |
|||
SPACE_AROUND: "space-around", |
|||
|
|||
}) |
@ -0,0 +1,660 @@ |
|||
/** |
|||
* ESAggregation := Extensions Storage Aggregation (method) |
|||
*/ |
|||
const ESAggregation = Object.freeze({ |
|||
INTERNALIZED: "intern", |
|||
INDIVIDUALLY: "individual", |
|||
COLLECTED: "collected", |
|||
CENTRALIZED: "centralized" |
|||
}); |
|||
|
|||
/** |
|||
* ExtStoragePos := Extensions Storage Position |
|||
* |
|||
* Determines where the extensions are positioned. |
|||
* Only relevant if ExtStorage is not 'internalized'. |
|||
* Determines where the tag (if individually) or the extensions are positioned. |
|||
*/ |
|||
const ExtStorePosition = Object.freeze({ |
|||
WITHIN: "WITHIN", |
|||
BEFORE: "BEFORE", |
|||
SEGMENT_BEGIN: "SEGMENT_BEGIN", |
|||
DOC_HEAD: "DOC_HEAD", |
|||
DOC_FOOTER: "DOC_FOOTER" |
|||
}); |
|||
|
|||
/** |
|||
* Defines how an identified dupplication should be "resolved"/dealt with. |
|||
* REPLACE: |
|||
* RENAME: |
|||
* RENAME_OLD: |
|||
* DROP_NEW: |
|||
* MOVE_ELEMENT_SPECIFIC: @ATTENTION implementation pending |
|||
*/ |
|||
const OverwriteBehaviour = Object.freeze({ |
|||
REPLACE: "REPLACE", |
|||
RENAME: "RENAME", |
|||
RENAME_OLD: "RENAME_OLD", |
|||
DROP_NEW: "DROP_NEW", |
|||
MOVE_ELEMENT_SPECIFIC: "MOVE_ELEMENT_SPECIFIC" |
|||
}); |
|||
|
|||
|
|||
/** |
|||
* Is supposed to shrink all empty strings to length 1 |
|||
* @param {Function} func |
|||
* @returns {string} |
|||
*/ |
|||
function clearFunctionDeclarationText(func) { |
|||
function shrinkEmptyStrings(text) { |
|||
for (let i = 1; i < 10; i++) { |
|||
text = text.replaceAll(" ".slice(i), ' '); |
|||
} |
|||
return text; |
|||
} |
|||
return shrinkEmptyStrings( |
|||
func.toString() |
|||
.replaceAll('\r\n', ' ') |
|||
.replaceAll('\n\r', ' ') |
|||
.replaceAll('\n', ' ') |
|||
.replaceAll('\r', ' ') |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @todo when ExtStorage-feature/logic is reevaluated |
|||
* the resolvement of functions (as basically major or main target) |
|||
* needs to be overlooked improved and simplified. |
|||
* |
|||
* @param {Function} func |
|||
* @param {string} registrationName |
|||
* @returns {string} |
|||
*/ |
|||
function getScriptTagInjectionText(func, registrationName) { |
|||
let funcHasName; |
|||
if (typeof func === 'function') { |
|||
funcHasName = ((func.name) && func.name.trim() !== ''); |
|||
} |
|||
|
|||
if (func.startsWith('function')) { |
|||
let label = ` function ${registrationName}`; |
|||
let isNameInFuncText = func.startsWith(label); |
|||
if (isNameInFuncText) { |
|||
return func; |
|||
} else { |
|||
return [label, '(', func.split('(').slice(1).join('(')].join('') |
|||
} |
|||
} else { |
|||
return ` const ${registrationName} = ${func}; `; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Stores a function until generate is called. |
|||
* Then the additional informations of the store wil be applied |
|||
* and the funcitons added to the page. |
|||
*/ |
|||
class FunctionStoreBuffer { |
|||
/** |
|||
* Stores a function until generate is called. |
|||
* Then the additional informations of the store wil be applied |
|||
* and the funcitons added to the page. |
|||
* @param {Function} func the function that will be stored |
|||
* @param {Array<any>} args additional arguments that will be given to the function |
|||
* @param {boolean} repeats weither the funciton is supposed to execute repeatedly |
|||
* @param {number} interval the time in milliseconds between executions |
|||
* @param {boolean} execAfterStart weither the function is supposed to be executed after pageload |
|||
* @param {number} delay the time in milliseconds the execution will be delayed |
|||
*/ |
|||
constructor( |
|||
func, |
|||
args = [], |
|||
repeats = false, |
|||
interval = -1, |
|||
execAfterStart = false, |
|||
delay = -1 |
|||
) { |
|||
this.func = func; |
|||
this.args = args; |
|||
this.execAfterStart = execAfterStart; |
|||
this.delay = delay; |
|||
this.repeats = repeats; |
|||
this.interval = interval; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Extracted this super class to differentiate between |
|||
* internal and external store. |
|||
*/ |
|||
class ExtStorage { |
|||
constructor( |
|||
aggregation = ESAggregation.INTERNALIZED, |
|||
position = ExtStorePosition.WITHIN, |
|||
behaviour = OverwriteBehaviour.DROP_NEW |
|||
) { |
|||
/** |
|||
* @type {ESAggregation} |
|||
*/ |
|||
this._aggregation = aggregation; |
|||
/** |
|||
* @type {ExtStorePosition} |
|||
*/ |
|||
this._position = position; |
|||
/** |
|||
* @type {OverwriteBehaviour} |
|||
*/ |
|||
this._overwriteBehaviour = behaviour; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {ESAggregation} position |
|||
*/ |
|||
setExtStoreAggregation(aggregation) { |
|||
this._aggregation = aggregation; |
|||
return this; |
|||
} |
|||
/** |
|||
* |
|||
* @param {ExtStoreType} position |
|||
*/ |
|||
setExtStorePosition(position) { |
|||
this._position = position; |
|||
return this; |
|||
} |
|||
/** |
|||
* |
|||
* @param {OverwriteBehaviour} behave |
|||
* @returns {ExtStorage} |
|||
*/ |
|||
setOverwriteBehaviour(behave) { |
|||
this._overwriteBehaviour = behave; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {ExtStorage} extStore |
|||
* @returns {boolean} |
|||
*/ |
|||
equals(extStore = null) { |
|||
if (!extStore) return false; |
|||
|
|||
return extStore._type === this._type |
|||
&& extStore._overwriteBehaviour === this._overwriteBehaviour; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {boolean} |
|||
*/ |
|||
isMissing() { |
|||
return this._type === null || this._overwriteBehaviour === null; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {boolean} |
|||
*/ |
|||
isNotInternalOrIndividual() { |
|||
return !( |
|||
this._aggregation === ESAggregation.INTERNALIZED |
|||
|| this._aggregation === ESAggregation.INDIVIDUALLY |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {ExtStorage} otherExtStore |
|||
* @returns {ExtStorage} |
|||
*/ |
|||
fillBy(otherExtStore) { |
|||
if (this._type === null) { |
|||
this._type = otherExtStore._type; |
|||
} |
|||
|
|||
if (this._overwriteBehaviour === null) { |
|||
this._overwriteBehaviour = otherExtStore._overwriteBehaviour; |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* @todo check if still implemented correctly |
|||
* Takes the singleValue and an ExtStore object to copy all values from. |
|||
* Then the singleValue will be compared to the three enums for the type of value. |
|||
* After the type is identified the corresponding (copied) value will be updated. |
|||
* @param {ExtStoreType|ExtStorePosition|OverwriteBehaviour} singleValue |
|||
* @param {ExtStorage} extStoreToClone |
|||
* @returns {ExtStorage} |
|||
*/ |
|||
setSingleValueToClone(singleValue, extStoreToClone) { |
|||
this._type = extStoreToClone._type; |
|||
this._position = extStoreToClone._position; |
|||
this._overwriteBehaviour = extStoreToClone._overwriteBehaviour; |
|||
|
|||
let target = [ |
|||
...Object.values(ExtStoreType).map(text => Object({ "value": text, "ref": "type" })), |
|||
...Object.values(ExtStorePosition).map(text => Object({ "value": text, "ref": "pos" })), |
|||
...Object.values(OverwriteBehaviour).map(text => Object({ "value": text, "ref": "over" })) |
|||
] |
|||
.find(compareObj => compareObj["value"] === singleValue); |
|||
|
|||
if (target) { |
|||
switch (target["ref"]) { |
|||
case "type": |
|||
this._type = singleValue; |
|||
break; |
|||
case "pos": |
|||
this._position = singleValue; |
|||
break; |
|||
case "over": |
|||
this._overwriteBehaviour = singleValue; |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {ExtStorage} this extStore (updated if rules were used, that don't work for functions) |
|||
*/ |
|||
setupForFunctions() { |
|||
if (this._type === ExtStoreType.INTERNALIZED_WITHIN) { |
|||
console.log("Updated Functions extstore from INTERNALIZED_WITHIN to INDIVIDUALLY_BEFORE") |
|||
this._type = ExtStoreType.INDIVIDUALLY_BEFORE; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {ExtStorage} |
|||
*/ |
|||
setupForGeneralStyling() { |
|||
if (this === ExtStoreType.INTERNALIZED_WITHIN) { |
|||
this._position = ExtStorePosition.WITHIN; |
|||
this._aggregation = ESAggregation.INTERNALIZED; |
|||
return this; |
|||
} |
|||
|
|||
this._position = ExtStorePosition.DOC_HEAD; |
|||
|
|||
switch (this) { |
|||
case ExtStoreType.INDIVIDUALLY_WITHIN: |
|||
case ExtStoreType.INDIVIDUALLY_BEFORE: |
|||
case ExtStoreType.INDIVIDUALLY_SEGMENT_BEGIN: |
|||
case ExtStoreType.INDIVIDUALLY_DOC_FOOTER: |
|||
case ExtStoreType.INDIVIDUALLY_DOC_HEAD: |
|||
this._aggregation = ESAggregation.INDIVIDUALLY; |
|||
break; |
|||
|
|||
case ExtStoreType.COLLECTED_BEFORE: |
|||
case ExtStoreType.COLLECTED_SEGMENT_BEGIN: |
|||
case ExtStoreType.COLLECTED_DOC_FOOTER: |
|||
case ExtStoreType.COLLECTED_DOC_HEAD: |
|||
this._aggregation = ESAggregation.COLLECTED; |
|||
|
|||
this._aggregation = ESAggregation.COLLECTED; |
|||
break; |
|||
case ExtStoreType.CENTRALIZED_DOC_HEAD: |
|||
case ExtStoreType.CENTRALIED_SEGMENT_BEGIN: |
|||
case ExtStoreType.CENTRALIZED_DOC_FOOTER: |
|||
default: |
|||
this._aggregation = ESAggregation.CENTRALIZED; |
|||
break |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Currently it works the same as the "updateForFunctions()" since the same rules won't work. |
|||
* @returns {ExtStorage} this extStore (updated if rules were used, that won't work for StyleClasses) |
|||
*/ |
|||
setupForStyleClass() { |
|||
/* |
|||
const positionedAfter = [ |
|||
COLLECTED_DOC_FOOTER, |
|||
INDIVIDUALLY_DOC_FOOTER, |
|||
CENTRALIZED_DOC_FOOTER |
|||
]; |
|||
|
|||
if (positionedAfter.includes(this._type)) { |
|||
this._type = ExtStoreType.INTERNALIZED_WITHIN; |
|||
} |
|||
*/ |
|||
|
|||
return this.setupForGeneralStyling(); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {InsertPosition} |
|||
*/ |
|||
getRelativePositioning() { |
|||
switch (this._position) { |
|||
case ExtStorePosition.BEFORE: |
|||
return "beforebegin" |
|||
case ExtStorePosition.SEGMENT_BEGIN: |
|||
return "afterbegin"; |
|||
case ExtStorePosition.DOC_HEAD: |
|||
case ExtStorePosition.DOC_FOOTER: |
|||
return "beforeend" |
|||
case ExtStorePosition.WITHIN: |
|||
default: |
|||
return "afterbegin"; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Expects a reference element for the positions before and segment_begin. |
|||
* Otherwise will return head, footer or element accordingly. |
|||
* @param {HTMLLIElement|Component} element |
|||
* @returns {HTMLElement} |
|||
*/ |
|||
getRefElement(element = null) { |
|||
let ensuredElement = element; |
|||
if (!element) { |
|||
console.log("ExtStorePosition defines a relative position, but no reference Element is given - using head!") |
|||
return document.querySelector('head'); |
|||
} |
|||
|
|||
if (element instanceof Component) { |
|||
ensuredElement = element.generate().compext; |
|||
} |
|||
|
|||
switch (this._position) { |
|||
case ExtStorePosition.BEFORE: |
|||
case ExtStorePosition.SEGMENT_BEGIN: |
|||
return ensuredElement.closest('[data-compel-isHCompel="true"]'); |
|||
case ExtStorePosition.DOC_HEAD: |
|||
return document.querySelector('head'); |
|||
case ExtStorePosition.DOC_FOOTER: |
|||
return document.querySelector('footer'); |
|||
case ExtStorePosition.WITHIN: |
|||
default: |
|||
return ensuredElement; |
|||
} |
|||
} |
|||
|
|||
insertElementAccordingly(element) { |
|||
this.getRefElement(element) |
|||
.insertAdjacentElement( |
|||
this.getRelativePositioning(), |
|||
this.getRefElement(element) |
|||
) |
|||
} |
|||
|
|||
/** |
|||
* Returns a function that will setup the distribution of a given styling. |
|||
* @returns {function(SStoreDefinition,HTMLElement,number): boolean} |
|||
*/ |
|||
getStylingDistribution() { |
|||
switch (this._aggregation) { |
|||
case ESAggregation.INDIVIDUALLY: |
|||
return function (ssd, orgElement, counter) { |
|||
let container = generateAndFillStyleTag([ssd]); |
|||
container.setAttribute("data-compel-individually-nr", counter++); |
|||
Page.addElementToPage(container, this); |
|||
return false; |
|||
} |
|||
case ESAggregation.COLLECTED: |
|||
return function (ssd, orgElement) { |
|||
return true; |
|||
} |
|||
case ESAggregation.CENTRALIZED: |
|||
return function (ssd, orgElement) { |
|||
Page.registerStyling(ssd._identifier, ssd._definition); |
|||
return false; |
|||
} |
|||
|
|||
case ESAggregation.INTERNALIZED: |
|||
default: |
|||
return function (ssd, orgElement) { |
|||
helperFun.fillAttrsInContainerByCb( |
|||
ssd._definition, |
|||
orgElement, |
|||
(key, val, el) => { el.style[key] = val; } |
|||
); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {function(SStoreDefinition, Map<ExtStorage, Array<SStoreDefinition>, number): boolean} |
|||
*/ |
|||
getFunctionDistribution() { |
|||
switch (this._aggregation) { |
|||
case ESAggregation.INTERNALIZED: |
|||
case ESAggregation.INDIVIDUALLY: |
|||
return function (ssd, counter) { |
|||
let container = document.createElement("script"); |
|||
container.setAttribute("data-compel-individually-nr", counter++); |
|||
container.innerText += getScriptTagInjectionText( |
|||
clearFunctionDeclarationText(ssd._definition), |
|||
ssd._identifier |
|||
); |
|||
Page.addElementToPage(container, refESType); |
|||
return false; |
|||
} |
|||
case ESAggregation.COLLECTED: |
|||
return function () { |
|||
return true; |
|||
} |
|||
|
|||
case ESAggregation.CENTRALIZED: |
|||
default: |
|||
return function (ssd) { |
|||
Page.registerPageFunction(ssd._identifier, ssd._definition); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* ExtStorage := Extensions storage (type) |
|||
* Extensions in this context are stylings and scripts (currently only javascript). |
|||
* internalized: the extensions are part of the element code/attributes - works obviously only with styling |
|||
* individually: an individual tag is created/used |
|||
* collected: the extension can/will be collected with others in a higher position of the element hierarchy |
|||
* (but not document - root) |
|||
* centralized: the extensions are send to the Page to be joined in a centralized tag/position of the document |
|||
* (either head or footer tag) |
|||
*/ |
|||
const ExtStoreType = Object.freeze({ |
|||
INTERNALIZED_WITHIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.WITHIN), |
|||
INDIVIDUALLY_WITHIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.WITHIN), |
|||
INDIVIDUALLY_BEFORE: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.BEFORE), |
|||
INDIVIDUALLY_SEGMENT_BEGIN: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.SEGMENT_BEGIN), |
|||
INDIVIDUALLY_DOC_HEAD: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.DOC_HEAD), |
|||
INDIVIDUALLY_DOC_FOOTER: new ExtStorage(ESAggregation.INDIVIDUALLY, ExtStorePosition.DOC_FOOTER), |
|||
COLLECTED_BEFORE: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.BEFORE), |
|||
COLLECTED_SEGMENT_BEGIN: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.SEGMENT_BEGIN), |
|||
COLLECTED_DOC_HEAD: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.DOC_HEAD), |
|||
COLLECTED_DOC_FOOTER: new ExtStorage(ESAggregation.COLLECTED, ExtStorePosition.DOC_FOOTER), |
|||
CENTRALIZED_DOC_HEAD: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.DOC_HEAD), |
|||
CENTRALIZED_SEGMENT_BEGIN: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.SEGMENT_BEGIN), |
|||
CENTRALIZED_DOC_FOOTER: new ExtStorage(ESAggregation.CENTRALIZED, ExtStorePosition.DOC_FOOTER) |
|||
}); |
|||
|
|||
/** |
|||
* Style or Script Store Definition |
|||
* @property {string} _identifier; |
|||
* @property {any} _definition; |
|||
* @property {any} _additionaly; |
|||
* @property {ExtStorage} _extStore; |
|||
*/ |
|||
class SStoreDefinition { |
|||
constructor(identifier, definition, extStore = null, additions = null) { |
|||
/** |
|||
* Usually the name or the selector |
|||
* @type {string} _identifier; |
|||
*/ |
|||
this._identifier = identifier; |
|||
/** |
|||
* the values |
|||
* @type {any} _definition; |
|||
*/ |
|||
this._definition = definition; |
|||
/** |
|||
* additional values, if needed. E.g. funciton args |
|||
* @type {any} _additionaly; |
|||
*/ |
|||
this._additions = additions; |
|||
/** |
|||
* The corresponding extStore |
|||
* @type {ExtStorage} _extStore; |
|||
*/ |
|||
this._extStore = extStore; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Resolves an overwrite case for a map/object. |
|||
* @param {string} key |
|||
* @param {Map|Object} container |
|||
* @param {OverwriteBehaviour} overwriteBehaviour |
|||
* @returns {string} the key to be used |
|||
*/ |
|||
function resolveOverwrite(key, container, overwriteBehaviour) { |
|||
let dealAsMap = container instanceof Map; |
|||
let occurances = [...( |
|||
dealAsMap |
|||
? container.keys() |
|||
: Object.keys(container) |
|||
) |
|||
.filter(e => e.includes(key) |
|||
)].length; |
|||
|
|||
switch (overwriteBehaviour) { |
|||
case OverwriteBehaviour.REPLACE: { |
|||
break; |
|||
|
|||
} |
|||
case OverwriteBehaviour.RENAME_OLD: { |
|||
let nameForOld = `${key}${occurances}`; |
|||
if (dealAsMap) { |
|||
container.set(nameForOld, container.get(key)); |
|||
container.delete(key); |
|||
} else { |
|||
container[nameForOld] = container[key]; |
|||
delete container[key]; |
|||
} |
|||
break; |
|||
|
|||
} |
|||
case OverwriteBehaviour.RENAME: |
|||
default: |
|||
key = `${key}${occurances}`; |
|||
break; |
|||
} |
|||
|
|||
return key; |
|||
} |
|||
|
|||
/** |
|||
* Will resolve the compareKey according to the overwriteBehaviour |
|||
* and add the newValue to the targetContainer with it. |
|||
* @param {Object} targetContainer |
|||
* @param {string} compareKey |
|||
* @param {Object} newValue |
|||
* @param {OverwriteBehaviour} overwriteBehaviour |
|||
* @returns {string} the "resolved" compareKey |
|||
*/ |
|||
function identifyAndResolveOverwrite(targetContainer, compareKey, newValue, overwriteBehaviour) { |
|||
let keys = Object.keys(targetContainer); |
|||
if (keys.includes(compareKey)) { |
|||
if (overwriteBehaviour === OverwriteBehaviour.DROP_NEW) { |
|||
console.log("Not Adding, because overwrite is set to DROP_NEW"); |
|||
return compareKey; |
|||
} |
|||
|
|||
compareKey = resolveOverwrite(compareKey, targetContainer, overwriteBehaviour); |
|||
} |
|||
|
|||
targetContainer[compareKey] = newValue; |
|||
return compareKey; |
|||
} |
|||
|
|||
/** |
|||
* Creates a new Script Tag |
|||
* and then fills the given css rules into it. |
|||
* @param {Array<SStoreDefinition>} ssdArray |
|||
* @returns {HTMLScriptElement} |
|||
*/ |
|||
function generateAndFillScriptTag(ssdArray) { |
|||
let tag = document.createElement("script"); |
|||
tag.setAttribute("data-compel-gen", "true"); |
|||
|
|||
for (let i = 0; i < ssdArray.length; i++) { |
|||
const ssd = ssdArray[i]; |
|||
tag.innerText += getScriptTagInjectionText( |
|||
clearFunctionDeclarationText(ssd._definition), |
|||
ssd._identifier |
|||
); |
|||
} |
|||
|
|||
return tag; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {string} selector |
|||
* @param {Map<string,string>} stylingMap |
|||
* @returns {string} |
|||
*/ |
|||
function getStylingInjectionText(selector, stylingMap) { |
|||
function keyValueToString(key) { |
|||
return `${key}: ${stylingMap[key]}; `; |
|||
} |
|||
|
|||
return `${selector |
|||
} { ${Object.keys(stylingMap) |
|||
.map(keyValueToString) |
|||
.join(" ") |
|||
} }; `;
|
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Array<SStoreDefinition>} ssdArray |
|||
* @returns {HTMLStyleElement} |
|||
*/ |
|||
function generateAndFillStyleTag(ssdArray) { |
|||
let tag = document.createElement("style"); |
|||
tag.setAttribute("data-compel-gen", "true"); |
|||
|
|||
for (let i = 0; i < ssdArray.length; i++) { |
|||
const ssd = ssdArray[i]; |
|||
tag.innerText += getStylingInjectionText(ssd._identifier, ssd._definition); |
|||
} |
|||
|
|||
return tag; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Executes the given function upon the delegating ExtStoreTypes |
|||
* @param {Function} func |
|||
* @returns {Map<ExtStoreType, *} |
|||
*/ |
|||
function executeOnExtStoreTypeCollectedTriple(func) { |
|||
return new Map([ |
|||
{ [ExtStoreType.COLLECTED_SEGMENT_BEGIN]: func(ExtStoreType.COLLECTED_SEGMENT_BEGIN) }, |
|||
{ [ExtStoreType.COLLECTED_DOC_HEAD]: func(ExtStoreType.COLLECTED_DOC_HEAD) }, |
|||
{ [ExtStoreType.COLLECTED_DOC_FOOTER]: func(ExtStoreType.COLLECTED_DOC_FOOTER) } |
|||
]); |
|||
} |
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Wenity := Web Trinity |
|||
*/ |
|||
class WebTrinity { |
|||
/** |
|||
* @deprecated renamed - use copext instead |
|||
* @type {HTMLElement|Component|string} = compext, for a migration period |
|||
*/ |
|||
html; |
|||
|
|||
/** |
|||
* @deprecated renamed - use scripts instead |
|||
* @type {HTMLStyleElement|Map<ExtStorageType, Array<SStoreDefinition>} = scripts, for a migration period |
|||
*/ |
|||
js; |
|||
|
|||
/** |
|||
* @deprecated renamed - use stylings instead |
|||
* @type {HTMLScriptElement|Array<SStoreDefinition>} = stylings, for a migration period |
|||
*/ |
|||
css; |
|||
|
|||
/** |
|||
* |
|||
* @param {HTMLElement|Component|string} compext := Component Text |
|||
* @param {HTMLStyleElement|Map<ExtStorageType, Array<SStoreDefinition>} scripts |
|||
* @param {HTMLScriptElement|Array<SStoreDefinition>} stylings |
|||
*/ |
|||
constructor(compext = null, scripts = null, stylings = null) { |
|||
this.compext = compext; |
|||
this.html = compext; |
|||
this.scripts = scripts; |
|||
this.js = scripts; |
|||
this.stylings = stylings; |
|||
this.css = stylings; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {boolean} |
|||
*/ |
|||
isSSEmpty() { |
|||
return (this.scripts === null || this.scripts.length === 0) |
|||
&& (this.stylings === null || this.stylings.size === 0) |
|||
/* LEGACY CHECK */ |
|||
&& (this.js === null || this.js.length === 0) |
|||
&& (this.css === null || this.css.size === 0); |
|||
} |
|||
} |
@ -1,106 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* Represents container Components. |
|||
* Some predefined modifications are applied on the child components. |
|||
*/ |
|||
class FlexContainerComponent extends Component { |
|||
constructor(attr = {}) { |
|||
super(document.createElement("div"), attr) |
|||
.addStyleClass("flex-container-component") |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {FlexContainerComponent} this component object |
|||
*/ |
|||
childContext(innerComponent) { |
|||
if (innerComponent instanceof Array) { |
|||
innerComponent |
|||
.map(cl => { |
|||
if (cl instanceof Component) { |
|||
return cl |
|||
} else { |
|||
return cl.ensureModifier().toComponent() |
|||
} |
|||
}) |
|||
.forEach(icomp => { |
|||
icomp._modifier = new Modifier() |
|||
.setStyleRule("flex", "none") |
|||
.join(icomp._modifier) |
|||
|
|||
}) |
|||
} |
|||
return super.childContext(innerComponent); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* A FlexContainerComponent, which organizes the children in a column like manner. |
|||
*/ |
|||
class Column extends FlexContainerComponent { |
|||
constructor(attr = {}) { |
|||
super(attr) |
|||
.addStyleClass("column-component") |
|||
.modifier( |
|||
new Modifier() |
|||
.setStyleRule("flex-direction", "column") |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* A FlexContainerComponent, which organizes the children in a row like manner. |
|||
*/ |
|||
class Row extends FlexContainerComponent { |
|||
constructor(attr = {}) { |
|||
super(attr) |
|||
.addStyleClass("row-component") |
|||
.modifier( |
|||
new Modifier() |
|||
.fillMaxWidth() |
|||
.setStyleRule("flex-direction", "row") |
|||
) |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {*} innerComponent |
|||
* @returns {Row} |
|||
*/ |
|||
childContext(innerComponent) { |
|||
if (innerComponent instanceof Array) { |
|||
innerComponent |
|||
.map(cl => (cl instanceof Component ? cl : cl.ensureModifier().toComponent())) |
|||
.forEach((icomp, i) => { |
|||
/* sets the width for all elements, |
|||
to avoid overlapping or line break because of lacking width, |
|||
a percent is subtracted for every child element */ |
|||
/* To enable "override" a new Modifier is generated and joined |
|||
with the modifier of the component */ |
|||
icomp._modifier = new Modifier() |
|||
.setStyleRule("float", (i === 0 ? "left" : "right")) |
|||
.join(icomp._modifier) |
|||
}) |
|||
} |
|||
return super.childContext(innerComponent) |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {Row} |
|||
*/ |
|||
distibuteSpacingEvenly() { |
|||
this._element.children.forEach(child => { |
|||
child.style["width"] = (100 / innerComponent.length); |
|||
}) |
|||
return this; |
|||
} |
|||
} |
|||
|
@ -1,15 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* Enum to access common events |
|||
*/ |
|||
const CommonEvents = Object.freeze({ |
|||
ONCLICK: "onclick", |
|||
ONCHANGE: "onchange" |
|||
}) |
|||
|
@ -1,246 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
|
|||
/** |
|||
* A chainable HTMLElement builder. |
|||
*/ |
|||
class Component { |
|||
_element; |
|||
_modifier |
|||
_alignment; |
|||
_arrangement; |
|||
_toRegister; |
|||
|
|||
constructor(element, attr = {}) { |
|||
this._modifier = new Modifier().margin(new Sides().all(0)); |
|||
var akeys = Object.keys(attr); |
|||
for (let i = 0; i < akeys.length; i++) { |
|||
element.setAttribute(akeys[i], attr[akeys[i]]); |
|||
} |
|||
this._element = element; |
|||
this._toRegister = []; |
|||
} |
|||
|
|||
/** |
|||
* Sets the alignment (modifications) for this element or more specific for its children. |
|||
* @param {Alignment} alignment |
|||
* @returns {Component} this component object |
|||
*/ |
|||
alignment(alignment) { |
|||
this._alignment = alignment; |
|||
|
|||
this._modifier._modifications["display"] = "flex"; |
|||
this._modifier._modifications["align-content"] = alignment; |
|||
this._modifier._modifications["align-items"] = alignment; |
|||
this._modifier._modifications["text-align"] = alignment; |
|||
//this._modifier._modifications["justify-content"] = alignment;
|
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the arrangement (modifications) for this element or more specific for its children. |
|||
* @param {Arrangement} arrangement |
|||
* @returns {Component} this component object |
|||
*/ |
|||
arrangement(arrangement) { |
|||
this._arrangement = arrangement; |
|||
switch (arrangement) { |
|||
case Arrangement.START: |
|||
this._modifier._modifications["justify-content"] = "start"; |
|||
break; |
|||
case Arrangement.END: |
|||
this._modifier._modifications["justify-content"] = "end"; |
|||
break; |
|||
case Arrangement.CENTER: |
|||
this._modifier._modifications["justify-content"] = "center"; |
|||
break; |
|||
case Arrangement.SPACE_AROUND: |
|||
this._modifier._modifications["justify-content"] = "space-around"; |
|||
break; |
|||
case Arrangement.SPACE_BETWEEN: |
|||
this._modifier._modifications["justify-content"] = "space-between"; |
|||
break; |
|||
case Arrangement.SPACE_EVENLY: |
|||
this._modifier._modifications["justify-content"] = "space-evenly"; |
|||
break; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
* @returns {Component} this component object |
|||
*/ |
|||
modifier(modifier) { |
|||
this._modifier = this._modifier.join(modifier.ensureModifier()) |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the innerText of the element |
|||
* @param {string} text |
|||
* @returns {Component} this component object |
|||
*/ |
|||
text(text) { |
|||
this._element.innerText = text; |
|||
return this; |
|||
} |
|||
|
|||
title(text) { |
|||
this._element.title = text; |
|||
return this; |
|||
} |
|||
/** |
|||
* |
|||
* @param {string} styleClass |
|||
* @param {Modifier} modifier |
|||
* @returns {Component} this component object |
|||
*/ |
|||
addStyleClass(styleClass, modifier = null) { |
|||
if (modifier) { |
|||
Page.registerStyling(styleClass, modifier._modifications); |
|||
} |
|||
this._element.classList.add(styleClass); |
|||
return this; |
|||
} |
|||
|
|||
registerStyleClass(styleClass, styleRuleMap) { |
|||
Page.registerStyling('.' + styleClass, styleRuleMap); |
|||
return this.addStyleClass(styleClass); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key |
|||
* @param {string} value |
|||
* @returns {Component} this component object |
|||
*/ |
|||
setAttribute(key, value) { |
|||
this._element.setAttribute(key, value); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Ends chain. |
|||
* Applies all modifications on the element. |
|||
* @returns {HTMLElemment} the html element |
|||
*/ |
|||
generate() { |
|||
/* apply styling to element */ |
|||
var mkeys = Object.keys(this._modifier._modifications); |
|||
for (let i = 0; i < mkeys.length; i++) { |
|||
this._element.style[mkeys[i]] = this._modifier._modifications[mkeys[i]]; |
|||
} |
|||
/* subscribe/register to lists */ |
|||
for (let i = 0; i < this._toRegister.length; i++) { |
|||
this._toRegister[i].push(this._element); |
|||
} |
|||
return this._element; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Opens a context to create element children. |
|||
* Either as one component or a list/array of components. |
|||
* @param {Component|Array<Component>} component |
|||
* @returns {Component} this component object |
|||
*/ |
|||
childContext(component) { |
|||
if (arguments.length > 1) { |
|||
for (let i = 0; i < arguments.length; i++) { |
|||
this.childContext(arguments[i]); |
|||
} |
|||
} else if (component instanceof Array) { |
|||
for (let i = 0; i < component.length; i++) { |
|||
this.childContext(component[i]); |
|||
} |
|||
} else { |
|||
this._element.append( |
|||
(component instanceof Component |
|||
? component |
|||
: component.toComponent() |
|||
) |
|||
.generate() |
|||
); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* @deprecated |
|||
* @param {Array<Component>} innerComponent |
|||
* @returns {Component} this component object |
|||
*/ |
|||
componentChildren(innerComponent) { |
|||
for (let i = 0; i < innerComponent.length; i++) { |
|||
this.childContext(innerComponent[i]); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {CommonEvent} commonEvent |
|||
* @param {string} functionName |
|||
* @returns {Component} this component object |
|||
*/ |
|||
setEvent(commonEvent, functionName) { |
|||
return this.setAttribute(commonEvent, `${functionName}(this)`); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
chainModifier() { |
|||
return new ChainableModifier(this); |
|||
} |
|||
|
|||
/** |
|||
* Collects the given List in the _toRegister attribute. |
|||
* When generate() is called, |
|||
* the created Element will be registered (added) in every list |
|||
* within the list. |
|||
* @param {Array} listName |
|||
*/ |
|||
subscribeOnGenerate(listName) { |
|||
this._toRegister.push(listName); |
|||
return this; |
|||
} |
|||
|
|||
/* |
|||
clickable(eventName) { |
|||
let cssClass = "button-like" |
|||
this.addStyleClass(cssClass) |
|||
this._modifier._modifications["box-shadow"] = "4px 6px #999"; |
|||
|
|||
let bckk = "background-color"; |
|||
if (this._modifier._modifications.hasOwnProperty(bckk)) { |
|||
let bckc = this._modifier._modifications[bckk]; |
|||
delete this._modifier._modifications[bckk]; |
|||
Page.registerStyling(`.${cssClass}`, { |
|||
[bckk]: bckc |
|||
}) |
|||
} |
|||
|
|||
Page.registerStyling(`.${cssClass}:hover`, { |
|||
"background-color": Colors.greenyellow.cssRGBString(), |
|||
}) |
|||
Page.registerStyling(`.${cssClass}:active`, { |
|||
"background-color": Colors.greenyellow.cssRGBString(), |
|||
"box-shadow": "1px 2px #666", |
|||
"transform": "translateY(4px)" |
|||
}) |
|||
|
|||
return this.setEvent(CommonEvents.ONCLICK, eventName); |
|||
} |
|||
*/ |
|||
} |
@ -0,0 +1,234 @@ |
|||
/** |
|||
* Represents the most basic and simple form of a Component. |
|||
* It is mainly a collection of wrapper methods |
|||
* around the HTMLElement methods to make them chainable. |
|||
* It serves as base for further functionallity extensions. |
|||
* @abstract |
|||
*/ |
|||
class ElementWrapper { |
|||
/** |
|||
* The basic HTMLElement the Component is wrapped around. |
|||
* It will be modified in several ways and in the end returned. |
|||
* @type {HTMLElement} |
|||
*/ |
|||
_element; |
|||
/** |
|||
* The auto-generated name of the component. |
|||
* @type {string} |
|||
*/ |
|||
_compName; |
|||
/** |
|||
* The auto-generated name of the component. |
|||
* @type {string} |
|||
*/ |
|||
_givenName; |
|||
/** |
|||
* @type {Component} |
|||
*/ |
|||
_parentComponent; |
|||
|
|||
/** |
|||
* Initializes the component |
|||
* @param {HTMLElement} element the base element |
|||
* @param {map<string,string>} attr Specific already known attributes |
|||
*/ |
|||
constructor(element, attr = {}) { |
|||
helperFun.fillAttrsInContainerByCb( |
|||
attr, |
|||
element, |
|||
function (k, v, con) { |
|||
con.setAttribute(k, v); |
|||
} |
|||
); |
|||
|
|||
this._compName = Page.autoRegisterComponent(); |
|||
element.setAttribute('data-autocompel', this._compName); |
|||
this._element = element; |
|||
this.addStyleClass(this._compName); |
|||
} |
|||
|
|||
setComponentName(name) { |
|||
this._givenName = name; |
|||
this.setAttribute('data-compel', name); |
|||
Page.registerComponent(name); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* (Wrapper) Sets the innerText of the element |
|||
* @todo add Alignment of text functionality |
|||
* @param {string} text |
|||
* @returns {Component} this component object |
|||
*/ |
|||
text(text) { |
|||
this._element.innerText = text; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Wrapper to set the HTMLElement.title attribute |
|||
* @param {string} text |
|||
* @returns {Component} |
|||
*/ |
|||
title(text) { |
|||
this._element.title = text; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Wrapper for HTMLElement.classList.add() |
|||
* @param {string} styleClass |
|||
* @returns {Component} this component object |
|||
*/ |
|||
addStyleClass(styleClass) { |
|||
this._element.classList.add(styleClass); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Wrapper for the HTMLElement.setAttribute() method. |
|||
* @param {string} key |
|||
* @param {string} value |
|||
* @returns {Component} this component object |
|||
*/ |
|||
setAttribute(key, value) { |
|||
this._element.setAttribute(key, value); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Wrapper for the HTMLElement.addEventListener() |
|||
* @param {keyof WindowEventMap|CommonEvents} theEvent |
|||
* @param {Function} theListener |
|||
* @param {boolean|AddEventListenerOptions} options |
|||
* @returns {Component} this component object |
|||
*/ |
|||
addEventListener(theEvent, theListener, options = null) { |
|||
this._element.addEventListener(theEvent, theListener, options) |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @extends ElementWrapper |
|||
* @abstract |
|||
*/ |
|||
class ChildbearerComponent extends ElementWrapper { |
|||
/** |
|||
* @type {Array<Component>} children |
|||
*/ |
|||
_children; |
|||
/** |
|||
* @type {Alignment} alignment |
|||
*/ |
|||
_alignment; |
|||
|
|||
/** |
|||
* @type {Arrangement} arrangement |
|||
*/ |
|||
_arrangement; |
|||
|
|||
constructor(element, attr = {}) { |
|||
super(element, attr); |
|||
this._children = []; |
|||
} |
|||
|
|||
/** |
|||
* @todo: Unify logic extract modifications into responsible construct |
|||
* @todo: Make it work as expected, fix docu |
|||
* @todo: Differentiate between directions (horizontal, vertiacl) |
|||
* |
|||
* Sets the alignment (modifications) for this element or more specific for its children. |
|||
* @param {Alignment} alignment |
|||
* @returns {Component} this component object |
|||
*/ |
|||
alignment(alignment) { |
|||
/* |
|||
this._modifier._modifications["display"] = "flex"; |
|||
this._modifier._modifications["align-content"] = alignment; |
|||
this._modifier._modifications["align-items"] = alignment; |
|||
this._modifier._modifications["text-align"] = alignment; |
|||
*/ |
|||
this._alignment = alignment; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* @todo: Unify logic extract modifications into responsible construct |
|||
* @todo: Differentiate between directions (horizontal, vertical) |
|||
* @todo: Make it work as expected, fix docu |
|||
* |
|||
* Sets the arrangement (modifications) for this element or more specific for its children. |
|||
* @param {Arrangement} arrangement |
|||
* @returns {Component} this component object |
|||
*/ |
|||
arrangement(arrangement) { |
|||
/* |
|||
this._modifier._modifications["justify-content"] = arrangement; |
|||
*/ |
|||
this._arrangement = arrangement; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Opens a context to create children elements. |
|||
* Either as one component or a list/array of components. |
|||
* @param {Component|Array<Component>} component |
|||
* @returns {Component} this component object |
|||
*/ |
|||
childContext(component) { |
|||
if (!component) return this; |
|||
|
|||
if (arguments.length > 1) { |
|||
for (let i = 0; i < arguments.length; i++) { |
|||
this.childContext(arguments[i]); |
|||
} |
|||
} else if (component instanceof Array) { |
|||
for (let i = 0; i < component.length; i++) { |
|||
this.childContext(component[i]); |
|||
} |
|||
} else { |
|||
if (!(component instanceof Component)) { |
|||
this.childContext(component.toComponent()) |
|||
} else { |
|||
this._children.push(component.end()); |
|||
} |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Ends chain for the current component. |
|||
* Returns the builder object. |
|||
* The Component that is selected there will be set as child to this one. |
|||
* |
|||
* This funciton is a convenience function. |
|||
* Mainly to offer the possibility to reduce the depth of method chains. |
|||
* Especially in the case of components with only one child. |
|||
* |
|||
* @returns {builder} the given |
|||
*/ |
|||
chainChild() { |
|||
return builder._nextComponent(this); |
|||
} |
|||
|
|||
/** |
|||
* Ends a chainChild - chain. |
|||
* If components are setup as chainChild |
|||
* they would be wrongfully taken through childContext(). |
|||
* Therefore thoose chains are recursively resolved through this method. |
|||
* @returns {Component} |
|||
*/ |
|||
end() { |
|||
let parent = this._parentComponent; |
|||
if (parent) { |
|||
this._parentComponent = null; |
|||
return parent |
|||
.childContext(this) |
|||
.end(); |
|||
} |
|||
return this; |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
/** |
|||
* A FlexContainerComponent, which organizes the children in a column like manner. |
|||
* @extends FlexContainerComponent |
|||
* @inheritdoc |
|||
*/ |
|||
class Column extends FlexContainerComponent { |
|||
/** |
|||
* |
|||
* @param {Attr} attr |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(attr = {}, modifier = null) { |
|||
super(attr, modifier); |
|||
this.addStyleClass("column-component"); |
|||
this._flexDirection = "column"; |
|||
this.setFlexDirection(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,329 @@ |
|||
/** |
|||
* Represents the most basic and simple form of a Component. |
|||
* It is mainly a collection of wrapper methods |
|||
* around the HTMLElement methods to make them chainable. |
|||
* It serves as base for further functionallity extensions. |
|||
* @extends StyleAndScriptStoringComponent |
|||
* @inheritdoc |
|||
* |
|||
*/ |
|||
class Component extends StyleAndScriptStoringComponent { |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_isCompel; |
|||
/** |
|||
* @type {Array<any>} |
|||
*/ |
|||
_toRegister; |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_isContextMenu; |
|||
|
|||
|
|||
/** |
|||
* Initializes the component |
|||
* @param {HTMLElement} element the base element |
|||
* @param {Map<string,string>} attr Specific already known attributes |
|||
*/ |
|||
constructor(element, attr = {}) { |
|||
super(element, attr); |
|||
this._isCompel = false; |
|||
this._isContextMenu = false; |
|||
|
|||
this._modifier = new Modifier() |
|||
.margin(0); |
|||
this._modifier._modifications['display'] = "flex"; |
|||
this._modifier._modifications["box-sizing"] = "border-box"; |
|||
|
|||
this._toRegister = []; |
|||
} |
|||
|
|||
/** |
|||
* Adds a class to classList via HTMLElement.classList.add() method. |
|||
* Further collects rules in a property until generate is called. |
|||
* |
|||
* @CAUGHTION implementation is not safe to use, ignoring extStore is recommended; |
|||
* |
|||
* @todo difference between stylings and classes, extStore logic in combination with the Page.register... logic |
|||
* |
|||
* @override |
|||
* |
|||
* @param {string} styleClass (without the '.' in the front) |
|||
* @param {string|Modifier|map<string,string>} styling |
|||
* @param {ExtStorage|ExtStoreType|ExtStorePosition|OverwriteBehaviour|EXPosConfer|ESOverwriteConfer} extStore |
|||
* if a unique definition is desired, all constants or configurator objects are allowed - they will be processed accordingly |
|||
* @returns {Component} this component object |
|||
*/ |
|||
addStyleClass(styleClass, styling = null, extStore = null) { |
|||
if (!extStore) { |
|||
extStore = this._styleClassesExtStore; |
|||
} else if (extStore.isMissing()) { |
|||
extStore = extStore.fillBy(this._styleClassesExtStore); |
|||
} |
|||
|
|||
if (styling) { |
|||
if (styling instanceof Modifier) { |
|||
styling = styling._modifications; |
|||
} |
|||
|
|||
Page.registerStyling('.' + styleClass, styling); |
|||
} |
|||
|
|||
this._element.classList.add(styleClass); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {boolean} vertical Defines if the Component should overflow vertically (default: true) |
|||
* @param {boolean} horizontal Defines if the Component should overflow horizontally (default: false) |
|||
* @returns {Component} |
|||
*/ |
|||
overflow(vertical = true, horizontal = false ) { |
|||
if (vertical || horizontal) { |
|||
this._modifier.join( |
|||
new Modifier() |
|||
.removeStyleRule("flex") |
|||
.setStyleRule("overflow", "hidden auto") |
|||
); |
|||
this.subscribeOnGenerate(CommonCompelGroups.OVERFLOWING); |
|||
} |
|||
|
|||
if (vertical) { |
|||
this._modifier._modifications["overflow-y"] = "hidden auto"; |
|||
} |
|||
if (horizontal) { |
|||
this._modifier._modifications["overflow-x"] = "hidden auto"; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {boolean} untilFound |
|||
* @returns {Component} |
|||
*/ |
|||
hidden(untilFound = false) { |
|||
let styleClass = "compel-mech-hidden"; |
|||
let hid = "hidden"; |
|||
Page.registerStyling("." + styleClass, { [hid]: hid }); |
|||
this.addStyleClass(styleClass); |
|||
|
|||
this._modifier.removeStyleRule("display"); |
|||
this.setAttribute( |
|||
hid, |
|||
(untilFound ? "until-found" : hid) |
|||
); |
|||
|
|||
this.subscribeOnGenerate(CommonCompelGroups.HIDDEN_ON_START); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Subscribes element under higher_compel group |
|||
* sets corr. variable true |
|||
* setAttribute("data-compel-isHCompel", "true") |
|||
* |
|||
* @returns {Component} |
|||
*/ |
|||
isHigherComponent() { |
|||
this.subscribeOnGenerate(CommonCompelGroups.HIGHER_COMPEL); |
|||
this._isCompel = true; |
|||
this.addStyleClass("compel-higher"); |
|||
return this.setAttribute("data-compel-isHCompel", "true") |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {string} |
|||
*/ |
|||
getHigherCompelSelector() { |
|||
return this._element |
|||
.closest('[data-compel-isHCompel="true"].compel-higher') |
|||
.getAttribute("data-autocompel"); |
|||
} |
|||
|
|||
/** |
|||
* Collects the given List in the _toRegister attribute. |
|||
* When generate() is called, |
|||
* the created Element will be registered (added) in every list |
|||
* within the list. |
|||
* @param {*|string|Array<*>} listName |
|||
*/ |
|||
subscribeOnGenerate(listName) { |
|||
this._toRegister.push(listName); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {Component} |
|||
*/ |
|||
registerAsContextMenu() { |
|||
this.subscribeOnGenerate(CommonCompelGroups.IS_CONTEXT_MENU); |
|||
this._isContextMenu = true; |
|||
|
|||
return this.addStyleClass('contextmenu') |
|||
.hidden(); |
|||
} |
|||
|
|||
/** |
|||
* @todo Positioning of the contextmenu element |
|||
* @todo extract into an extra function(allity) provider |
|||
* |
|||
* @param {Component} component |
|||
* @param {Sides|Function<HTMLElement|Event> => Sides} getRefPos |
|||
* @param {ExtStorage} [extStore=null] |
|||
* @returns {Component} |
|||
*/ |
|||
contextMenu(component, getRefPos = null, extStore = null) { |
|||
if (!component._isContextMenu) { |
|||
component.registerAsContextMenu(); |
|||
} |
|||
if (!getRefPos) { |
|||
getRefPos = function (cmEvent) { |
|||
return new Sides() |
|||
.left(cmEvent.pageX) |
|||
.top(cmEvent.pageY); |
|||
} |
|||
} |
|||
|
|||
this.subscribeOnGenerate(CommonCompelGroups.HAS_CONTEXT_MENU); |
|||
|
|||
let identifier = component._compName; |
|||
|
|||
this.addEventListener( |
|||
"contextmenu", |
|||
DefaultContextMenu.openContextMenuAction(identifier, getRefPos) |
|||
); |
|||
|
|||
return this.childContext(component); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {DragAndDropImplementation} dadImpl |
|||
* @returns {Component} |
|||
*/ |
|||
draggable(dadImpl = new DragAndDropImplementation()) { |
|||
this.subscribeOnGenerate(CommonCompelGroups.DRAGGABLE); |
|||
this.subscribeOnGenerate(CommonCompelGroups.HAS_DRAG_EVENT); |
|||
|
|||
let addedClass = "comp-el-mech-draggable"; |
|||
let selector = this._element.getAttribute("data-autocompel"); |
|||
|
|||
selector = `.${addedClass}[data-autocompel="${selector}"]`; |
|||
|
|||
return this.addStyleClass(addedClass) |
|||
.setAttribute("draggable", "true") |
|||
.setAttribute("dropEffect", "none") |
|||
.addEventListener( |
|||
CommonEvents.DRAG_START, |
|||
dadImpl.dragStartAction("text/plain", selector) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {EventDrag} dragEvent |
|||
* @param {Function} action |
|||
*/ |
|||
onDrag(dragEvent, action = (e) => { e.preventDefault(); }) { |
|||
this.subscribeOnGenerate(CommonCompelGroups.HAS_DRAG_EVENT); |
|||
let selector = `comp-el-mech-drag${dragEvent}`; |
|||
|
|||
return this.addEventListener( |
|||
'drag' + dragEvent, |
|||
action |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {DragAndDropImplementation} dadImpl |
|||
* @returns {Component} |
|||
*/ |
|||
dropTarget(dadImpl = new DragAndDropImplementation()) { |
|||
this.subscribeOnGenerate(CommonCompelGroups.DROP_TARGET); |
|||
let specialClass = "comp-el-mech-droptarget"; |
|||
|
|||
this.addStyleClass(specialClass) |
|||
.onDrag(EventDrag.OVER); |
|||
|
|||
let selector = `.${specialClass}[data-autocompel="${this._compName}"]` |
|||
|
|||
this._element.addEventListener( |
|||
"drop", |
|||
dadImpl.dropEventAction("text/plain", selector) |
|||
); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* An echo of Scope-Functions from kotlin for convenience |
|||
* |
|||
* Executes a given function injects this component into the function. |
|||
* @param {Function} func |
|||
* @returns {Component} |
|||
*/ |
|||
apply(func) { |
|||
func(this); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* An echo of Scope-Functions from kotlin for convenience |
|||
* |
|||
* Executes a given function injects the htmlelement of this component into the function. |
|||
* @param {Function} func |
|||
* @returns {Component} |
|||
*/ |
|||
applyToEl(func) { |
|||
func(this._element) |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Ends chain. |
|||
* Applies all modifications on the element. |
|||
* Processes all stored additions. |
|||
* Returns the constructed HTMLElement of this Component. |
|||
* |
|||
* @param {CompelGenerator} generator |
|||
* @param {Modifier | undefined} [modifier=null] |
|||
* @param {ExtStorage | undefined} [styleStore=null] |
|||
* @param {ExtStorage | undefined} [functionStore=null] |
|||
* @param {ExtStorage} |
|||
* @returns {WebTrinity} the constructed HTMLElement of this Component. |
|||
*/ |
|||
generate(generator = singlepage, styleStore = null, functionStore = null) { |
|||
/** |
|||
* In the case that this component is a chainChild created one. |
|||
* The generation chain needs to be setup in the proper order |
|||
* so that the resulting element tree equals the expected/desired result. |
|||
* |
|||
* Therefore if this is a chainChild, |
|||
* it will be added to the parent via the regular childContext |
|||
* and the generation of the parent will be returned. |
|||
* |
|||
* The parent will generate this component on its generate(). |
|||
*/ |
|||
if (this._parentComponent) { |
|||
let parent = this._parentComponent; |
|||
this._parentComponent = null; |
|||
return parent.childContext(this) |
|||
.generate(generator, styleStore, functionStore); |
|||
} |
|||
|
|||
return generator.generate(this, styleStore, functionStore); |
|||
} |
|||
} |
@ -0,0 +1,173 @@ |
|||
/** |
|||
* Represents container Components. |
|||
* Some predefined modifications are applied on the child components. |
|||
* @extends Component |
|||
* @inheritdoc |
|||
*/ |
|||
class FlexContainerComponent extends Component { |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_distributeEvenglyAfterGenerate; |
|||
/** |
|||
* @type {number} the amount that should be left out of the total space before the children |
|||
* space is set. |
|||
*/ |
|||
#distributionRecess; |
|||
/** |
|||
* @type {number} the additional gap that should be left for children before their space is set |
|||
*/ |
|||
#distributionGapPerChild; |
|||
/** |
|||
* @type {string} |
|||
*/ |
|||
_flexDirection; |
|||
/** |
|||
* |
|||
* @param {Attr} attr |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(attr = {}, modifier = null, baseElement = "div") { |
|||
super(document.createElement(baseElement), attr); |
|||
this._flexDirection = ""; |
|||
this._distributeEvenglyAfterGenerate = false; |
|||
this.addStyleClass("flex-container-component"); |
|||
if (modifier) { |
|||
this.modifier(modifier); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {boolean} [vertical=true] |
|||
* @returns {FlexContainerComponent} |
|||
*/ |
|||
setFlexDirection(vertical = true) { |
|||
return this.modifier( |
|||
new Modifier() |
|||
.setStyleRule( |
|||
"flex-direction", |
|||
(vertical ? "column" : "row") |
|||
) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {FlexContainerComponent} this component object |
|||
*/ |
|||
childContext(innerComponent) { |
|||
if (innerComponent instanceof Array) { |
|||
innerComponent |
|||
.map(cl => { |
|||
if (cl instanceof Component) { |
|||
return cl; |
|||
} else { |
|||
return cl.ensureModifier().toComponent(); |
|||
} |
|||
}) |
|||
.forEach(icomp => { |
|||
icomp._modifier = new Modifier() |
|||
.removeStyleRule("flex") |
|||
.join(icomp._modifier); |
|||
}); |
|||
} |
|||
return super.childContext(innerComponent); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} [recessFraction=0.0] recessFraction a fraction/percentage of the recess |
|||
* that should be left out before distributing the remaining space. |
|||
* @param {number} [gapPerChild=1] gapPerChild the gap per child, therefore inbetween children |
|||
* that shouldn't be distributed to the children |
|||
*/ |
|||
_distributingSpacing(recessFraction = 0.0, gapPerChild = 1) { |
|||
if (this._children && this._children.length > 1) { |
|||
|
|||
let distributableSpace = 100 - 100 * recessFraction - (this._children.length - 1) * gapPerChild; |
|||
|
|||
let childDistributionFraction = Math.floor( |
|||
(distributableSpace / this._children.length) * 100 |
|||
) / 100; |
|||
|
|||
let direction = (this._flexDirection === 'column' ? 'height' : "width"); |
|||
|
|||
for (const icomp of this._children) { |
|||
/* sets the width for all elements, |
|||
to avoid overlapping or line break because of lacking width, |
|||
a percent is subtracted for every child element */ |
|||
/* To enable "override" a new Modifier is generated and joined |
|||
with the modifier of the component */ |
|||
icomp._modifier._modifications[direction] = childDistributionFraction + "%"; |
|||
icomp._modifier._modifications["max-" + direction] = childDistributionFraction + "%"; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Distributes the spacing of the childrens evengly, |
|||
* according to the flexdirection of this component. |
|||
* By default this will be executed imediately when called. |
|||
* |
|||
* @param {boolean} [rightNow=true] if set to false it will set properties accordingly |
|||
* so that the distribution will be executed on generate |
|||
* @param {number} [recessFraction=0.0] recessFraction a fraction/percentage of the recess |
|||
* that should be left out before distributing the remaining space. |
|||
* @param {number} [gapPerChild=1] gapPerChild the gap per child, therefore inbetween children |
|||
* that shouldn't be distributed to the children |
|||
* @returns {FlexContainerComponent} |
|||
*/ |
|||
distibuteSpacingEvenly(rightNow = true, recessFraction = 0.0, gapPerChild = 1) { |
|||
if (rightNow) { |
|||
this._distributingSpacing(recessFraction, gapPerChild); |
|||
} else { |
|||
this.#distributionRecess = recessFraction; |
|||
this.#distributionGapPerChild = gapPerChild; |
|||
this._distributeEvenglyAfterGenerate = true; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds, sets, updates or overwrites the Modification of/for the children of this component. |
|||
* Just calls .modifier(modifier) on each child. |
|||
* |
|||
* @param {Modifier} modifier |
|||
* @param {boolean|ExtStorage|ExtStoreType|OverwriteBehaviour} [extStore=null] |
|||
* @returns {FlexContainerComponent} this component object |
|||
*/ |
|||
modifyChildren(modifier, underTheName = "", extStore = false) { |
|||
if (underTheName === "") { |
|||
underTheName = `.${this._compName}-style-children`; |
|||
} |
|||
if (!extStore) { |
|||
for (const child of this._children) { |
|||
child.modifier(modifier) |
|||
} |
|||
} else { |
|||
this.addStyleClass(underTheName); |
|||
this._styles.push( |
|||
new SStoreDefinition( |
|||
underTheName, |
|||
modifier.ensureModifier() |
|||
) |
|||
); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* @override |
|||
* @inheritdoc |
|||
* @extends Component.generate() |
|||
*/ |
|||
generate(generator = singlepage, styleStore = null, functionStore = null) { |
|||
if (this._distributeEvenglyAfterGenerate) { |
|||
this._distributingSpacing(this.#distributionRecess, this.#distributionGapPerChild); |
|||
} |
|||
|
|||
return super.generate(generator, styleStore, functionStore); |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
/** |
|||
* Represents a Component (of an HTMLElement) that is capable of receiving input. |
|||
* @extends Component |
|||
* @inheritdoc |
|||
*/ |
|||
class InputComponent extends Component { |
|||
/** |
|||
* |
|||
* @param {string} element |
|||
* @param {Attr} attr |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(element, attr = {}, modifier = null) { |
|||
super(element, attr); |
|||
this.addStyleClass("el-input-comp"); |
|||
if (modifier) { |
|||
this.modifier(modifier); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This overrides the text() method in such a way, |
|||
* that it sets the value attribute for input elements instead. |
|||
* |
|||
* @override |
|||
* @inheritdoc |
|||
* @param {string} text |
|||
* @returns {InputComponent} |
|||
*/ |
|||
text(text){ |
|||
if (this._element instanceof HTMLInputElement) { |
|||
this._element.value = text; |
|||
} else { |
|||
super.text(text); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* The parameter makes it optional to trigger the state by a variable |
|||
* @param {boolean} readonly |
|||
* @returns {Component} |
|||
*/ |
|||
readonly(readonly = true) { |
|||
if (readonly) { |
|||
this._element.setAttribute("readonly", readonly); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the value of the InputComponent |
|||
* @param {string} Value |
|||
* @returns {InputComponent|Component} |
|||
*/ |
|||
value(value) { |
|||
this._element.value = value; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} name |
|||
* @returns {InputComponent|Component} |
|||
*/ |
|||
name(name) { |
|||
return this.setAttribute("name", name); |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
/** |
|||
* A FlexContainerComponent, which organizes the children in a row like manner. |
|||
* |
|||
* @extends FlexContainerComponent |
|||
* @inheritdoc |
|||
*/ |
|||
class Row extends FlexContainerComponent { |
|||
/** |
|||
* |
|||
* @param {Attr} attr |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(attr = {}, modifier = null) { |
|||
super(attr, modifier); |
|||
this.addStyleClass("row-component") |
|||
this._flexDirection = "row"; |
|||
//this.modifier(new Modifier().fillMaxWidth());
|
|||
this.setFlexDirection(false); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {Row} |
|||
*/ |
|||
childContext(innerComponent) { |
|||
function setFloat(comp, side = "right") { |
|||
comp._modifier = new Modifier() |
|||
.setStyleRule("float", side) |
|||
.join(comp._modifier); |
|||
} |
|||
|
|||
super.childContext(innerComponent); |
|||
|
|||
for (const child of this._children) { |
|||
onSingleOrArray( |
|||
child, |
|||
(e, i) => setFloat(e, (i === 0 ? "left" : "right")) |
|||
); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
} |
|||
|
@ -0,0 +1,194 @@ |
|||
/** |
|||
* @inheritdoc |
|||
* @abstract |
|||
* @extends ModifiableComponent |
|||
*/ |
|||
class StyleAndScriptStoringComponent extends ModifiableComponent { |
|||
/** |
|||
* @type {ExtStorage} |
|||
*/ |
|||
_styleClassesExtStore |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_isClassESDefault; |
|||
/** |
|||
* @type {ExtStorage} |
|||
*/ |
|||
_stylesExtStore; |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_isStyleESDefault; |
|||
/** |
|||
* @type {Array<SStoreDefinition>} |
|||
*/ |
|||
_styles; |
|||
/** |
|||
* @type {ExtStorage} |
|||
*/ |
|||
_functionsExtStore; |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
_isFunESDefault; |
|||
/** |
|||
* @type {Array<SStoreDefinition>} |
|||
*/ |
|||
_functions; |
|||
|
|||
/** |
|||
* |
|||
* @param {HTMLElement} element |
|||
* @param {Map<string,string>} attr |
|||
*/ |
|||
constructor(element, attr = {}) { |
|||
super(element, attr); |
|||
|
|||
this._isClassESDefault = true; |
|||
this._isStyleESDefault = true; |
|||
this._styles = []; |
|||
this._isFunESDefault = true; |
|||
this._functions = []; |
|||
|
|||
this._styleClassesExtStore = ExtStoreType.CENTRALIZED_DOC_HEAD |
|||
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE); |
|||
this._stylesExtStore = ExtStoreType.INTERNALIZED_WITHIN |
|||
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE); |
|||
this._functionsExtStore = ExtStoreType.CENTRALIZED_DOC_HEAD |
|||
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @todo: Unify logic extract modifications into responsible construct |
|||
* @todo: Make it work as expected, fix docu |
|||
* @todo: Differentiate between directions (horizontal, vertiacl) |
|||
* @override |
|||
* @inheritdoc |
|||
* Sets the alignment (modifications) for this element. |
|||
* @param {Alignment} alignment |
|||
* @returns {Component} this component object |
|||
*/ |
|||
alignment(alignment) { |
|||
/* |
|||
this._modifier._modifications["display"] = "flex"; |
|||
this._modifier._modifications["align-content"] = alignment; |
|||
this._modifier._modifications["align-items"] = alignment; |
|||
this._modifier._modifications["text-align"] = alignment; |
|||
*/ |
|||
this._alignment = alignment; |
|||
this._modifier._modifications["align-content"] = this._alignment; |
|||
this._modifier._modifications["align-items"] = this._alignment; |
|||
this._modifier._modifications["text-align"] = this._alignment; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* @todo: Unify logic extract modifications into responsible construct |
|||
* @todo: Differentiate between directions (horizontal, vertical) |
|||
* @todo: Make it work as expected, fix docu |
|||
* @override |
|||
* @inheritdoc |
|||
* Sets the arrangement (modifications) for this element or more specific for its children. |
|||
* Therefore it defines the space distribution to the children. |
|||
* @param {Arrangement} arrangement |
|||
* @returns {Component} this component object |
|||
*/ |
|||
arrangement(arrangement) { |
|||
/* |
|||
this._modifier._modifications["justify-content"] = arrangement; |
|||
*/ |
|||
this._arrangement = arrangement; |
|||
this._modifier._modifications["justify-content"] = this._arrangement; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Defines/Sets the general "storage-behaviour" for styling of this component. |
|||
* Further for potential css definitions. |
|||
* If a styling/modifier/class is passed via the corresponding methods/way, |
|||
* an alternative behaviour can be passed as a parameter - which will only be applied on/for that single one. |
|||
* |
|||
* @param {ExtStorage|ExtStoreType|OverwriteBehaviour} extStore |
|||
* @returns {Component} |
|||
*/ |
|||
setStylingsStorage(extStore) { |
|||
if (extStore) { |
|||
if (extStore instanceof ExtStorage) { |
|||
this._stylesExtStore = extStore; |
|||
} else if (extStore instanceof ExtStoreType) { |
|||
this._stylesExtStore.setExtStoreType(extStore); |
|||
} else { |
|||
this._stylesExtStore.OverwriteBehaviour(extStore); |
|||
} |
|||
this._isStyleESDefault = false; |
|||
} else { |
|||
console.log("(Style)ExtStore was empty, did nothing"); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Defines/Sets the general "storage-behaviour" for functions of this component. |
|||
* If a function is passed via "registerFunction", |
|||
* an alternative behaviour can be passed as a parameter - which will only be applied on/for that single one. |
|||
* |
|||
* @param {ExtStorage|ExtStoreType|OverwriteBehaviour} extStore |
|||
* @returns {Component} |
|||
*/ |
|||
setFunctionsStorage(extStore) { |
|||
if (extStore) { |
|||
if (extStore instanceof ExtStorage) { |
|||
this._functionsExtStore = extStore; |
|||
} else if (extStore instanceof ExtStoreType) { |
|||
this._functionsExtStore.setExtStoreType(extStore); |
|||
} else { |
|||
this._functionsExtStore.OverwriteBehaviour(extStore); |
|||
} |
|||
this._isFunESDefault = false; |
|||
} else { |
|||
console.log("(Function)ExtStore was empty, did nothing"); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Function} func |
|||
* @param {string} underTheName |
|||
* @param {ExtStorage|ExtStoreType|ExtStorePosition|OverwriteBehaviour|EXPosConfer|ESOverwriteConfer} extStore |
|||
* if a unique definition is desired, all constants or configurator objects are allowed - they will be processed accordingly |
|||
* @returns |
|||
*/ |
|||
registerFunction(func, underTheName = "", extStore = null) { |
|||
|
|||
let registrationName = [ |
|||
underTheName.trim(), |
|||
func.name.trim(), |
|||
`func${this._compName}${Object.keys(this._functions).length}` |
|||
] |
|||
.find(e => e !== ''); |
|||
|
|||
/* |
|||
if (!extStore) { |
|||
extStore = this._functionsExtStore; |
|||
} else if (extStore instanceof ExtStoreConfer) { |
|||
extStore = extStore.ensureStore(); |
|||
} else { |
|||
extStore = new ExtStorage().setSingleValueToClone(extStore, this._functionsExtStore); |
|||
} |
|||
*/ |
|||
|
|||
let entry = new SStoreDefinition(); |
|||
entry._identifier = registrationName; |
|||
entry._definition = func; |
|||
entry._extStore = extStore; |
|||
|
|||
this._functions.push(entry); |
|||
|
|||
//identifyAndResolveOverwrite(this._functions, registrationName, entry, extStore._overwriteBehaviour);
|
|||
|
|||
return this; |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
/** |
|||
* @inheritdoc |
|||
* @extends ChildbearerComponent |
|||
* @abstract |
|||
*/ |
|||
class ModifiableComponent extends ChildbearerComponent { |
|||
/** |
|||
* @type {Modifier} modifier |
|||
*/ |
|||
_modifier; |
|||
|
|||
constructor(element, attr = {}) { |
|||
super(element, attr); |
|||
this._modifier = new Modifier(); |
|||
} |
|||
|
|||
/** |
|||
* Sets, updates or overwrites the Modifier-Object for this component |
|||
* @param {Modifier} modifier |
|||
* @param {boolean|ExtStorage|ExtStoreType|OverwriteBehaviour} [extStore=null] |
|||
* @returns {Component} this component object |
|||
*/ |
|||
modifier(modifier, underTheName = "", extStore = false) { |
|||
if (underTheName === "") { |
|||
underTheName = `.${this._compName}-style-${this._styles.length}`; |
|||
} |
|||
|
|||
if (!extStore) { |
|||
this._modifier = this._modifier |
|||
.join(modifier.ensureModifier()); |
|||
} else { |
|||
this.addStyleClass(underTheName); |
|||
this._styles.push( |
|||
new SStoreDefinition( |
|||
underTheName, |
|||
modifier.ensureModifier() |
|||
) |
|||
); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Returns a Modifier to chain modifications |
|||
* instead of setting them within an sepperate context. |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
chainModifier() { |
|||
return new ChainableModifier(this); |
|||
} |
|||
|
|||
} |
@ -1,177 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* The class provides overreaching options for building the website. |
|||
*/ |
|||
class PageBuilder { |
|||
#cssClasses; |
|||
#functions; |
|||
#delayedFunctions; |
|||
#repeatingFunctions; |
|||
#functionNames; |
|||
#cssElementIdentifiers; |
|||
|
|||
constructor() { |
|||
this.#cssClasses = document.createElement("style"); |
|||
this.#functions = document.createElement("script"); |
|||
this.#functionNames = []; |
|||
this.#cssElementIdentifiers = []; |
|||
} |
|||
|
|||
/** |
|||
* Registers a function to be added later in a script tag in the head of the document. |
|||
* @ATTENTION Be careful with intended empty strings (e.g. in variable values), |
|||
* empty strings within the function code will be shrunk. |
|||
* @param {string} name |
|||
* @param {function} fun |
|||
*/ |
|||
registerFunction(name, fun) { |
|||
/** |
|||
* Is supposed to shrink all empty strings to length 1 |
|||
* @param {string} text |
|||
* @returns {string} |
|||
*/ |
|||
function shrinkEmptyStrings(text){ |
|||
for (let i = 1; i < 10; i++) { |
|||
text = text.replaceAll(" ".slice(i), ' '); |
|||
} |
|||
return text; |
|||
} |
|||
if (!this.#functionNames.includes(name)) { |
|||
let clearedFuncText = shrinkEmptyStrings( |
|||
fun.toString() |
|||
.replaceAll('\n', ' ') |
|||
.replaceAll('\r\n', ' ') |
|||
.replaceAll('\n\r', ' ') |
|||
); |
|||
let isFuncWritten = clearedFuncText.startsWith('function'); |
|||
let funcHasName = fun.name && fun.name.trim() !== ''; |
|||
if(isFuncWritten){ |
|||
let isNameInFuncText = clearedFuncText.startsWith(`function ${name}`); |
|||
this.#functions.innerText += (funcHasName && isNameInFuncText |
|||
? clearedFuncText |
|||
: clearedFuncText.replace('function ', 'function '+name) |
|||
)+'; '; |
|||
}else{ |
|||
this.#functions.innerText += `const ${name} = ${clearedFuncText}; ` |
|||
} |
|||
this.#functionNames.push(name); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
|
|||
registerNamedFunction(namedFunction) { |
|||
return this.registerFunction(namedFunction.name, namedFunction) |
|||
} |
|||
|
|||
/** |
|||
* @experimental Attention is adviced, registration mechanism doesn't work yet |
|||
* @param {string} name The name the interval will be tied to |
|||
* @param {Function} fun the function that is supposed to be executed repeatedly |
|||
* @param {number} interval the time in ms between executions |
|||
*/ |
|||
registerRepeatingFunction(name, fun, interval) { |
|||
if (!Object.keys(this.#repeatingFunctions).includes(name)) { |
|||
this.#repeatingFunctions[name] = { |
|||
name: name, |
|||
fun: fun, |
|||
interval: interval |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds the styling rules to the element identifiers into the style tag. |
|||
* An elementDefinition can only be used once, repeated use will be ignored. |
|||
* @param {string} elementDefinition The element identifier |
|||
* @param {Map<string, string>} styleRuleMap The Styling rules/values |
|||
*/ |
|||
registerStyling(elementDefinition, styleRuleMap) { |
|||
if (!this.#cssElementIdentifiers.includes(elementDefinition)) { |
|||
this.#cssClasses.innerText += `${elementDefinition |
|||
} {${Object.keys(styleRuleMap) |
|||
.map(e => e + ": " + styleRuleMap[e] + "; ") |
|||
.join(" ") |
|||
}} ` |
|||
this.#cssElementIdentifiers.push(elementDefinition) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds into the (head) document. |
|||
* - script tag |
|||
* - function tag |
|||
* - sets and registers repeatedly executed functions |
|||
* - sets (timeout and) functions that are supposed to be executed after load |
|||
*/ |
|||
generate() { |
|||
let head = document.querySelector("head"); |
|||
head.appendChild(this.#functions) |
|||
head.appendChild(this.#cssClasses) |
|||
|
|||
/* set repeating functions */ |
|||
if (this.#repeatingFunctions) { |
|||
let repeatedFun = Object.values(this.#repeatingFunctions) |
|||
.reduce((a, c, i, arr) => Object.assign(a, { |
|||
[c.name]: setInterval(c.fun, c.interval) |
|||
}), {}); |
|||
} |
|||
|
|||
/* set timeouts for funcitons executed after load */ |
|||
if (this.#delayedFunctions) { |
|||
for (let i = 0; i < this.#delayedFunctions.length; i++) { |
|||
let func = this.#delayedFunctions[i]; |
|||
if (func.repeat) { |
|||
setTimeout(setInterval(func.func, func.interval), func.dl, func.args); |
|||
} else { |
|||
setTimeout(func.func, func.dl, func.args); |
|||
} |
|||
} |
|||
} |
|||
console.log(this.#functionNames); |
|||
} |
|||
|
|||
/** |
|||
* Registeres a function to be executed after page-load |
|||
* @param {Function} func the function that will be executed |
|||
* @param {number} delay the time in ms the execution is delayed after load |
|||
* @param {string} name if provided the function will be registered as well |
|||
* @param {Array<any>} args arguments for the function |
|||
* @param {boolean} repeat defines if the function is supposed to be repeated as well |
|||
* @param {number} interval if the function is supposed to repeat, this defines the interval of repetition |
|||
*/ |
|||
executeAfterLoad(func, delay = 1000, name = '', args = [], repeat = false, interval = 5000) { |
|||
if (name !== '') { |
|||
this.registerFunction(name, func); |
|||
} |
|||
if (!this.#delayedFunctions) { |
|||
this.#delayedFunctions = []; |
|||
} |
|||
this.#delayedFunctions.push({ dl: delay, func: func, args: args, repeat: repeat, interval: interval }); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} relaunchSeconds timeinterval for page to reload (changes) |
|||
*/ |
|||
inDev(relaunchSeconds = 20) { |
|||
let head = document.querySelector("head"); |
|||
let meta = document.createElement("meta"); |
|||
meta.setAttribute("http-equiv", "refresh"); |
|||
meta.setAttribute("content", `${relaunchSeconds}`); |
|||
|
|||
head.insertAdjacentElement("beforeend", meta); |
|||
this.#functions.innerText = ` |
|||
let ts = new Date(); |
|||
console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s"); |
|||
`;
|
|||
} |
|||
} |
|||
|
|||
const Page = new PageBuilder(); |
@ -0,0 +1,196 @@ |
|||
/** |
|||
* @todo Potentially class implementation could be removed entirely. |
|||
* Since it (at the time being) is only working as a singleton anyway. |
|||
* Further maybe rename to 'AppBuilder' instead of 'PageBuilder', |
|||
* page might later become part of a navigation setup. |
|||
* |
|||
* |
|||
* The class provides overreaching options for building the website. |
|||
* @extends ScriptAndStyleContext |
|||
*/ |
|||
class PageBuilder extends ScriptAndStyleContext { |
|||
/** |
|||
* @type {Array<string>} |
|||
*/ |
|||
#autoRegisteredComponents; |
|||
/** |
|||
* @type {Array<string>} |
|||
*/ |
|||
#registeredComponents; |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
#showFrameworkConsole; |
|||
/** |
|||
* @type {Array<CompelExtension>} |
|||
*/ |
|||
_extensions; |
|||
/** |
|||
* @type {Map<string,*>} |
|||
*/ |
|||
_groups; |
|||
|
|||
constructor() { |
|||
super(); |
|||
this.#showFrameworkConsole = false; |
|||
|
|||
this.#autoRegisteredComponents = []; |
|||
this.#registeredComponents = []; |
|||
this._extensions = []; |
|||
|
|||
this._groups = new Map(); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {*|Array<*>} groups |
|||
* @param {Component} component |
|||
*/ |
|||
subscribeComponentToGroup(groups, component) { |
|||
if (groups instanceof Array && !(groups instanceof String)) { |
|||
for (let i = 0; i < groups.length; i++) { |
|||
this.subscribeComponentToGroup(groups[i], component); |
|||
} |
|||
} else { |
|||
if (!this._groups.has(groups)) { |
|||
this._groups.set(groups, []); |
|||
} |
|||
this._groups.get(groups).push(component); |
|||
} |
|||
} |
|||
|
|||
autoRegisterComponent() { |
|||
let compName = 'comp-el-' + this.#autoRegisteredComponents.length; |
|||
this.#autoRegisteredComponents.push(compName); |
|||
return compName; |
|||
} |
|||
|
|||
registerComponent(compName) { |
|||
this.#registeredComponents.push(compName); |
|||
return compName; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Inserts the given element according to the extStore into the page/document. |
|||
* The refElement is a reference element for the case |
|||
* that extStore._position defines "before" or "segment_begin", |
|||
* which will then look for the refElement as the corresponding insert reference. |
|||
* |
|||
* @param {HTMLElement|Component} element |
|||
* @param {ExtStorage} extStore |
|||
* @param {HTMLElement|Component} refElement |
|||
*/ |
|||
addElementToPage(element, extStore = ExtStoreType.CENTRALIZED_DOC_HEAD) { |
|||
let { insertCallEl, relativePositioning } = {}; |
|||
|
|||
relativePositioning = extStore.getRelativePositioning(); |
|||
insertCallEl = extStore.getRefElement(element); |
|||
|
|||
insertCallEl.insertAdjacentElement( |
|||
relativePositioning, |
|||
element |
|||
); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* Determines that the jpc-like-websites libs shouldn't be part of the resulting page. |
|||
* Therefore the generate() methods will package/generate finalized js, css and html elements |
|||
* into the final html page in the end. |
|||
* Especially tricky are reusable elements and functions that use such. |
|||
* |
|||
* @todo This method/feature will have to have a logic implemented for state altering components |
|||
* that then can be "packaged" into a single page. |
|||
* @todo This method/feature will work only, if an automatic reuse logic for elements/components is implemented within the jpc lib. |
|||
* @ATTENTION DO NOT USE |
|||
*/ |
|||
enableFrameworkConsole() { |
|||
this.#showFrameworkConsole = true; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Little helper function. |
|||
* If a single page application is in development. |
|||
* This method sets an autoreload interval for the page. |
|||
* Default is 20 (sec). |
|||
* @param {number} relaunchSeconds timeinterval for page to reload (changes) |
|||
*/ |
|||
inDev(relaunchSeconds = 20) { |
|||
let head = document.querySelector("head"); |
|||
let meta = document.createElement("meta"); |
|||
meta.setAttribute("http-equiv", "refresh"); |
|||
meta.setAttribute("content", `${relaunchSeconds}`); |
|||
|
|||
let devScript = document.createElement('script'); |
|||
devScript.setAttribute("data-label", "devScript"); |
|||
devScript.innerText = ` |
|||
let ts = new Date(); |
|||
console.log("Page is in Dev-Mode (through 'inDev()' call of PageBuilder."); |
|||
console.log("Refreshed at: ", ts.getHours()+':'+ts.getMinutes()+':'+ts.getSeconds(), "Intervall ${relaunchSeconds}s"); |
|||
`;
|
|||
head.appendChild(devScript); |
|||
head.insertAdjacentElement("beforeend", meta); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {CompelExtension} extension |
|||
*/ |
|||
addExtension(extension) { |
|||
if (extension instanceof CompelExtension) { |
|||
this._extensions.push(extension); |
|||
extension.install(); |
|||
} |
|||
} |
|||
|
|||
setPageTitle(title) { |
|||
this._apptitle = title; |
|||
} |
|||
|
|||
|
|||
|
|||
generate() { |
|||
super.generate(); |
|||
|
|||
let docBody = document.querySelector('body'); |
|||
|
|||
if (this._apptitle) { |
|||
document.querySelector("title") |
|||
.innerText = this._apptitle; |
|||
} |
|||
|
|||
if (this.#showFrameworkConsole) { |
|||
let pageContextControlPanel = frameworkControlPanel(this._extensions); |
|||
pageContextControlPanel = pageContextControlPanel.generate(); |
|||
|
|||
docBody.insertAdjacentElement( |
|||
"afterbegin", |
|||
pageContextControlPanel.html |
|||
); |
|||
} |
|||
|
|||
compelgroups = this._groups; |
|||
} |
|||
|
|||
} |
|||
|
|||
const CommonCompelGroups = Object.freeze({ |
|||
AUTO_REGISTRATED: "auto_registrated", |
|||
REUSABLE_COMPEL: "reusable", |
|||
HIGHER_COMPEL: "higher_compel", |
|||
|
|||
OVERFLOWING: "overflowing", |
|||
HIDDEN_ON_START: "hidden_on_start", |
|||
|
|||
IS_CONTEXT_MENU: "is_contextmenu", |
|||
HAS_CONTEXT_MENU: "has_contextmenu", |
|||
|
|||
DRAGGABLE: "draggable", |
|||
HAS_DRAG_EVENT: "has_drag", |
|||
DROP_TARGET: "droptarget", |
|||
|
|||
}); |
|||
|
|||
const Page = new PageBuilder(); |
@ -0,0 +1,61 @@ |
|||
/** |
|||
* |
|||
* @param {Array<CompelExtension>} extensions |
|||
* @returns {Component} |
|||
*/ |
|||
function frameworkControlPanel( |
|||
extensions = [] |
|||
) { |
|||
return builder.row() |
|||
.alignment(Alignment.CENTER) |
|||
.arrangement(Arrangement.CENTER) |
|||
.isHigherComponent() |
|||
.setStylingsStorage(ExtStoreType.INTERNALIZED_WITHIN) |
|||
.modifier( |
|||
new Modifier() |
|||
.fillMaxWidth() |
|||
.background(MaterialFiveHundredlColors.ORANGE) |
|||
.padding(4) |
|||
.border( |
|||
new Border(3) |
|||
.color(Colors.goldenrod_3) |
|||
) |
|||
.dimensions().height(200) |
|||
) |
|||
.childContext([ |
|||
builder.column() |
|||
.modifier(new Modifier().fillMaxHeight()) |
|||
.childContext([ |
|||
builder.label().text("Installed Extensions:") |
|||
, |
|||
builder.column() |
|||
.overflow() |
|||
.modifier( |
|||
new Modifier() |
|||
.padding(4) |
|||
.border(1) |
|||
) |
|||
.childContext( |
|||
extensions.map( |
|||
/** |
|||
* |
|||
* @param {CompelExtension} ext |
|||
* @returns {Component} |
|||
*/ |
|||
ext => builder.span().text(ext.diplayTitle) |
|||
) |
|||
) |
|||
]) |
|||
, |
|||
builder.div() |
|||
.alignment(Alignment.CENTER) |
|||
.arrangement(Arrangement.CENTER) |
|||
.childContext([ |
|||
builder.label() |
|||
.text("to generate and download page displayed below click on 'generate'") |
|||
, |
|||
builder.button() |
|||
.text("generate") |
|||
]) |
|||
]); |
|||
} |
@ -0,0 +1,197 @@ |
|||
/** |
|||
* @abstract |
|||
* Class adds function and style storing properties to the context (PageBuilder). |
|||
*/ |
|||
class ScriptAndStyleContext { |
|||
/** |
|||
* @property {Map<string, Map<string, string>>} #css |
|||
* @type {Map<string, Map<string, string>>} #css |
|||
*/ |
|||
#css; |
|||
/** |
|||
* @property {Map<string, FunctionStoreBuffer>} #functions |
|||
* @type {Map<string, FunctionStoreBuffer>} #functions |
|||
*/ |
|||
#functions; |
|||
|
|||
constructor() { |
|||
this.#functions = new Map(); |
|||
this.#css = new Map(); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} nameAddition text that will be added to the end of the generated name |
|||
* @returns a name for a function (e.g. for storing) |
|||
*/ |
|||
getFunctionName(nameAddition = "") { |
|||
return `func${this.#functions.size}${nameAddition}`; |
|||
} |
|||
|
|||
/** |
|||
* Registers a function to be added later in a script tag in the head of the document. |
|||
* @ATTENTION Be careful with intended empty strings (e.g. in variable values), |
|||
* empty strings within the function code will be shrunk. |
|||
* |
|||
* @param {Function} fun The function that will be registered |
|||
* @param {string} underTheName (alternative) name for the registration of the function, |
|||
* if none the name of the function will be used, if that is missing a name will be generated. |
|||
* @param {OverwriteBehaviour} overwriteBehaviour defines what to do, |
|||
* if the registration name already exists (default: OverwriteBehaviour.RENAME - adds a nr to the name) |
|||
* @returns {string} the name under witch the function is registered (and therefore can be called from) |
|||
*/ |
|||
registerPageFunction(fun, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME) { |
|||
|
|||
/* Find name-root */ |
|||
let registrationName = [ |
|||
underTheName.trim(), |
|||
fun.name.trim(), |
|||
this.getFunctionName() |
|||
].find(e => e !== ''); |
|||
|
|||
/* deal with name already present */ |
|||
if (this.#functions.has(registrationName)) { |
|||
registrationName = resolveOverwrite(registrationName, this.#functions, overwriteBehaviour); |
|||
} |
|||
|
|||
/* clear function text */ |
|||
let clearedFuncText = clearFunctionDeclarationText(fun); |
|||
|
|||
this.#functions.set(registrationName, new FunctionStoreBuffer(clearedFuncText)); |
|||
|
|||
return registrationName; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @experimental Attention is adviced, registration mechanism doesn't work yet |
|||
* @param {Function} fun The function that is supposed to be executed repeatedly |
|||
* @param {number} interval the time in ms between executions |
|||
* @param {string} underTheName the name the interval will be tied to |
|||
* @param {OverwriteBehaviour} overwriteBehaviour defines what to do, |
|||
* if the registration name already exists (default: OverwriteBehaviour.RENAME - adds a nr to the name) |
|||
*/ |
|||
registerRepeatingFunction(fun, interval, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME, args = []) { |
|||
let registrationName = this.registerPageFunction(fun, underTheName, overwriteBehaviour); |
|||
let fsb = this.#functions.get(registrationName); |
|||
fsb.repeats = true; |
|||
fsb.interval = interval; |
|||
fsb.args = args; |
|||
return registrationName; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Registeres a function to be executed after page-load |
|||
* @param {Function} func the function that will be executed |
|||
* @param {number} delay the time in ms the execution is delayed after load |
|||
* @param {string} name if provided the function will be registered as well |
|||
* @param {Array<any>} args arguments for the function |
|||
* @param {boolean} repeat defines if the function is supposed to be repeated as well |
|||
* @param {number} interval if the function is supposed to repeat, this defines the interval of repetition |
|||
*/ |
|||
executeAfterLoad(fun, delay = 1000, underTheName = '', overwriteBehaviour = OverwriteBehaviour.RENAME, interval = -1, args = []) { |
|||
let registrationName = this.registerPageFunction(fun, underTheName, overwriteBehaviour); |
|||
let fsb = this.#functions.get(registrationName); |
|||
|
|||
fsb.execAfterStart = true; |
|||
fsb.delay = delay; |
|||
fsb.args = args; |
|||
|
|||
if (interval > 0) { |
|||
fsb.repeats = true; |
|||
fsb.interval = interval; |
|||
} |
|||
|
|||
return registrationName; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} nameAddition text that will be added to the end of the generated name |
|||
* @returns a name for a styling (e.g. for storing) |
|||
*/ |
|||
getStyleName(nameAddition = "") { |
|||
return `styling${this.#css.size}${nameAddition}`; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Adds the styling rules to the element identifiers into the style tag. |
|||
* An element definition can only be used once, repeated use will be ignored. |
|||
* |
|||
* @todo implement extStore logic |
|||
* |
|||
* @param {string} elementIdentifier The element identifier |
|||
* @param {map<string, string>|Modifier} styleRuleMap The Styling rules/values |
|||
*/ |
|||
registerStyling(elementIdentifier, styleRuleMap) { |
|||
if (styleRuleMap instanceof Modifier) { |
|||
styleRuleMap = styleRuleMap._modifications; |
|||
} |
|||
|
|||
if (!this.#css.has(elementIdentifier)) { |
|||
this.#css.set(elementIdentifier, styleRuleMap); |
|||
} |
|||
return elementIdentifier; |
|||
} |
|||
|
|||
/** |
|||
* Adds into the (head) document. |
|||
* - script tag |
|||
* - function tag(s) |
|||
* - sets and registers repeatedly executed functions |
|||
* - sets (timeout and) functions that are supposed to be executed after load |
|||
* @class ScriptAndStyleContext |
|||
*/ |
|||
generate() { |
|||
let head = document.querySelector('head'); |
|||
|
|||
/* generate style tag and fill it with stored stylings */ |
|||
let styleTag = document.createElement('style'); |
|||
|
|||
for (const tuple of this.#css.entries()) { |
|||
styleTag.innerText += `${tuple[0]} {${Object.entries(tuple[1]) |
|||
.map(style => style[0] + ": " + style[1] + "; ") |
|||
.join(" ") |
|||
}} `;
|
|||
} |
|||
|
|||
head.appendChild(styleTag); |
|||
|
|||
/* |
|||
generate script tag(s) and fill it with stored functions |
|||
for now there will be 3 script tags, so interval, execAfterStart and normal functions are sepperated. |
|||
*/ |
|||
|
|||
let containersTag = document.createElement('script'); |
|||
containersTag.setAttribute("data-compel-mech-script", "main"); |
|||
containersTag.innerText = 'const delayed = {}; '; |
|||
containersTag.innerText += 'const repeated = {}; '; |
|||
head.appendChild(containersTag); |
|||
|
|||
if (this.#functions.size > 0) { |
|||
let funcTag = document.createElement('script'); |
|||
for (const tuple of this.#functions.entries()) { |
|||
let regName = tuple[0]; |
|||
let fsb = tuple[1]; |
|||
funcTag.innerText += getScriptTagInjectionText(fsb.func, regName); |
|||
|
|||
if (fsb.repeats && !fsb.execAfterStart) { |
|||
repeated[regName] = setInterval(regName, fsb.interval, fsb.args); |
|||
} |
|||
|
|||
if (!fsb.repeats && fsb.execAfterStart) { |
|||
delayed[regName] = setTimeout(regName, fsb.interval, fsb.args); |
|||
} |
|||
|
|||
if (fsb.repeats && fsb.execAfterStart) { |
|||
repeated[regName] = setInterval(regName, fsb.interval, fsb.args); |
|||
delayed[regName] = setTimeout(repeated[regName], fsb.delay, fsb.args); |
|||
} |
|||
} |
|||
head.appendChild(funcTag); |
|||
} |
|||
} |
|||
} |
@ -1,10 +1,3 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* Enum providing common alignment rules |
|||
*/ |
@ -0,0 +1,11 @@ |
|||
/** |
|||
* Enum providing common alignment rules |
|||
*/ |
|||
const Arrangement = Object.freeze({ |
|||
START: "start", |
|||
END: "end", |
|||
CENTER: "center", |
|||
SPACE_BETWEEN: "space-between", |
|||
SPACE_EVENLY: "space-evenly", |
|||
SPACE_AROUND: "space-around", |
|||
}); |
@ -0,0 +1,53 @@ |
|||
/** |
|||
* @inheritdoc |
|||
* @extends Sides |
|||
*/ |
|||
class Margin extends Sides { |
|||
/** |
|||
* |
|||
* @param {number|string} defaultValue |
|||
* @param {SizeUnits} defaultUnit |
|||
*/ |
|||
constructor(defaultValue = 0, defaultUnit = SizeUnits.PIXEL) { |
|||
super("margin", defaultValue, defaultUnit); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @ATTENTION since it just increases complexity to constantly use padding and margin |
|||
* it is recommended to use padding and to stick to that as often as possible. |
|||
* Padding values take affect inside/within the element. |
|||
* @inheritdoc |
|||
* @extends Margin |
|||
* @mixes |
|||
*/ |
|||
class MarginChain extends mixinModSubChainEndings(Margin) { |
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(modifier) { |
|||
super(modifier); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @ATTENTION since it just increases complexity to constantly use padding and margin |
|||
* it is recommended to use padding and to stick to that as often as possible. |
|||
* Padding values take affect inside/within the element. |
|||
* @inheritdoc |
|||
* @extends MarginChain |
|||
* @mixin ModificationDefinition |
|||
*/ |
|||
class MarginChainedModifier extends mixinModSubChainComponentMethods(Margin) { |
|||
/** |
|||
* |
|||
* @param {ChainableModifier} modifier |
|||
*/ |
|||
constructor(modifier) { |
|||
super(modifier); |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
/** |
|||
* @inheritdoc |
|||
* @extends Sides |
|||
*/ |
|||
class Padding extends Sides { |
|||
/** |
|||
* |
|||
* @param {number|string} defaultValue |
|||
* @param {SizeUnits} defaultUnit |
|||
*/ |
|||
constructor(defaultValue = 0, defaultUnit = SizeUnits.PIXEL) { |
|||
super("padding", defaultValue, defaultUnit); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @extends Padding |
|||
*/ |
|||
class PaddingChain extends mixinModSubChainEndings(Padding){ |
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(modifier) { |
|||
super(modifier); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @extends PaddingChain |
|||
*/ |
|||
class PaddingChainedModifier extends mixinModSubChainComponentMethods(Padding){ |
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
*/ |
|||
constructor(modifier) { |
|||
super(modifier); |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
const singlepage = new CompelGenerator(); |
@ -0,0 +1,313 @@ |
|||
/** |
|||
* This class defines the Component generator. |
|||
* It externalizes all decision making about script or style storage from the component. |
|||
* The component stores the generator (if set, if not =default or passed down from parent component). |
|||
* The CompelGenerator class enables the setup/definition of storage, generation and distribution. |
|||
* |
|||
* Further if other frameworks are targeted: |
|||
* if the components should be generated in a manner |
|||
* that they fullfill the setup/look of other framework-components. |
|||
* Only the generator has to be modified, implemented, extended ... |
|||
* not the component, modifier or any other of the classes. |
|||
* |
|||
* Therefore the usages of CompelGenerator-feature-logic resets all style and script storages to local. |
|||
* Only towards the end (when "generate()" is called) any of that will be resolved. |
|||
*/ |
|||
class CompelGenerator { |
|||
/** |
|||
* @param {ExtStorage} styleStore default ExtStoreType.INTERNALIZED_WITHIN |
|||
* @param {ExtStorage} functionStore default ExtStoreType.CENTRALIZED_DOC_HEAD |
|||
*/ |
|||
constructor( |
|||
styleStore = ExtStoreType.INTERNALIZED_WITHIN |
|||
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE), |
|||
functionStore = ExtStoreType.CENTRALIZED_DOC_HEAD |
|||
.setOverwriteBehaviour(OverwriteBehaviour.REPLACE) |
|||
) { |
|||
this._styleStore = styleStore; |
|||
this._functionStore = functionStore; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Deals with the direct component stylings |
|||
* @param {Component} component |
|||
* @param {Array<WebTrinity>} childrenWenity |
|||
* @param {ExtStorage} extStore |
|||
* @returns {Array<SStoreDefinition>} |
|||
*/ |
|||
processStyles(component, extStore = null) { |
|||
extStore = (extStore |
|||
? extStore |
|||
: component._stylesExtStore |
|||
) |
|||
.setupForGeneralStyling(); |
|||
|
|||
let forCollection = []; |
|||
|
|||
let counter = 0; |
|||
for (const ssd of component._styles) { |
|||
/* Make sure that the type is unified for later processing */ |
|||
if (ssd._definition instanceof Modifier) { |
|||
ssd._definition = ssd._definition._modifications; |
|||
} |
|||
|
|||
/* Check/Ensure proper ExtStorageType for following comparison */ |
|||
/** |
|||
* @type {ExtStorage} |
|||
*/ |
|||
let curExtStore = extStore; |
|||
|
|||
if (ssd.hasOwnProperty("_extStore") && ssd._extStore) { |
|||
curExtStore = ssd._extStore.setupForGeneralStyling(); |
|||
} |
|||
|
|||
if (curExtStore.getStylingDistribution()(ssd, component._element, counter)) { |
|||
forCollection.push(ssd); |
|||
} |
|||
} |
|||
|
|||
return forCollection; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* First deals with the scripts/functions. |
|||
* |
|||
* @param {Component} component |
|||
* @param {Array<WebTrinity>} childrenWenity |
|||
* @param {ExtStorage} extStore |
|||
* @returns {Array<SStoreDefinition>} |
|||
*/ |
|||
processFunctions(component, extStore = null) { |
|||
extStore = (extStore |
|||
? extStore |
|||
: component._functionsExtStore |
|||
) |
|||
.setupForFunctions(); |
|||
|
|||
const forCollection = new Map(); |
|||
const collectForBefore = []; |
|||
|
|||
let counter = 0; |
|||
for (const ssd of component._functions) { |
|||
/* Make sure that the type is unified for later processing */ |
|||
|
|||
let curExtStore = extStore; |
|||
if (Object.hasOwn(ssd, "_extStore") && ssd._extStore) { |
|||
curExtStore = ssd._extStore.setupForFunctions(); |
|||
} |
|||
|
|||
if (curExtStore.getFunctionDistribution()(ssd, counter)) { |
|||
if (curExtStore._position.BEFORE) { |
|||
collectForBefore.push(ssd); |
|||
} else { |
|||
if (!forCollection.has(curExtStore)) { |
|||
forCollection.set(curExtStore, []); |
|||
} |
|||
forCollection.get(curExtStore).push(ssd); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return forCollection; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* checks if the source has the extStoreType |
|||
* fills the target extStoreType-array with the corr. elements of source. |
|||
* @param {Map<ExtStoreType, Array<SStoreDefinition>>} source |
|||
* @param {Map<ExtStoreType, Array<SStoreDefinition>>} target |
|||
* @param {ExtStoreType} extStoreType |
|||
* @returns |
|||
*/ |
|||
transferCollectedFunctions(source, target, extStoreType) { |
|||
if (source) { |
|||
if (source.has(extStoreType)) { |
|||
if (funcCollections.has(extStoreType)) { |
|||
target.get(extStoreType) |
|||
.push(source.get(extStoreType)) |
|||
} else { |
|||
target.set( |
|||
extStoreType, |
|||
source.get(extStoreType) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return target; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* |
|||
* @param {Map<ExtStoreType, *>} extFuncMap |
|||
* @param {ExtStorageType} extStoreType |
|||
*/ |
|||
dealCollectedFuncs(extFuncMap, extStoreType) { |
|||
if (extFuncMap.has(extStoreType)) { |
|||
let collectionScriptTag = generateAndFillScriptTag(extFuncMap.get(extStoreType)); |
|||
if (extStoreType === ExtStoreType.COLLECTED_SEGMENT_BEGIN) { |
|||
this._element.insertAdjacentElement( |
|||
"afterbegin", |
|||
generateAndFillScriptTag(segment) |
|||
); |
|||
} else { |
|||
Page.addElementToPage( |
|||
collectionScriptTag, |
|||
extStoreType |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Generates and appends a child Component. |
|||
* @param {Component} parent component the child component to add it. |
|||
* @param {Component|WebTrinity|string} child |
|||
* @returns {WebTrinity} |
|||
*/ |
|||
appendChildComponent(parent, child) { |
|||
let childWT = new WebTrinity(); |
|||
if (child instanceof Component) { |
|||
childWT = child.generate(this); |
|||
} |
|||
|
|||
if (child instanceof WebTrinity) { |
|||
childWT = child; |
|||
} |
|||
|
|||
if (child instanceof HTMLElement) { |
|||
console.log("No wenity set - htmlEl was given"); |
|||
childWT.compext = child; |
|||
} |
|||
|
|||
parent._element.append(childWT.compext); |
|||
|
|||
return childWT; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Iterates over the children of the component |
|||
* and calls generate on each child. |
|||
* The resulting compext (the text of the component) is added/appended accordingly. |
|||
* If the generation returns with delegatable scripts or styles |
|||
* the WebTrinity object is collected in an array, |
|||
* which will be returned. |
|||
* |
|||
* @param {Component} component |
|||
* @returns {Array<WebTrinity>} |
|||
*/ |
|||
resolveChildren(component, styleStore, functionStore) { |
|||
/** |
|||
* @type {Array<WebTrinity>} |
|||
*/ |
|||
let wenities = []; |
|||
|
|||
for (let child of component._children) { |
|||
child = child.generate(this, styleStore, functionStore); |
|||
|
|||
let wenity = this.appendChildComponent(component, child); |
|||
|
|||
if (!wenity.isSSEmpty()) { |
|||
wenities.push(wenity); |
|||
} |
|||
} |
|||
|
|||
return wenities; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {Component} component |
|||
* @param {ExtStorage} [styleStore=null] |
|||
* @param {ExtStorage} [functionStore=null] |
|||
* @returns {WebTrinity} |
|||
*/ |
|||
generate(component, styleStore = null, functionStore = null) { |
|||
|
|||
if (!styleStore) { |
|||
styleStore = component._stylesExtStore; |
|||
} |
|||
if (!functionStore) { |
|||
functionStore = component._functionsExtStore; |
|||
} |
|||
|
|||
/** |
|||
* DEAL WITH COMPONENT MODIFICATION FIRST |
|||
* |
|||
* @todo pay attention to the "overwrite" behaviour |
|||
* the local modifier styles are the "closest" |
|||
* it might be appropriate to use this._styles.unshift(...) instead. |
|||
*/ |
|||
component._styles.push(new SStoreDefinition( |
|||
(styleStore._aggregation !== ESAggregation.INTERNALIZED |
|||
? "." |
|||
: "") |
|||
+ component._compName, |
|||
component._modifier, |
|||
component._stylesExtStore |
|||
)); |
|||
|
|||
/** |
|||
* DEAL WITH CHILDREN |
|||
* |
|||
* Depending on the setup of the generator/-tion |
|||
* The children might return scripts or styles |
|||
* that are supposed to be collected |
|||
* and dealt with as a collection. |
|||
*/ |
|||
let childrenWenities = this.resolveChildren(component, styleStore, functionStore); |
|||
|
|||
/** |
|||
* DEAL WITH STYLES AND SCRIPTS |
|||
*/ |
|||
|
|||
/** |
|||
* @type {Array<SStoreDefinition>} |
|||
*/ |
|||
let styleCollection = this.processStyles(component, styleStore); |
|||
/** |
|||
* @type {Map<ExtStoreType, Array<SStoreDefinition>>} |
|||
*/ |
|||
let funcCollections = this.processFunctions(component, functionStore); |
|||
|
|||
/** |
|||
* DEAL WITH CHILDREN WENITY SCRIPTS |
|||
*/ |
|||
for (const child of childrenWenities) { |
|||
if (child.scripts) { |
|||
executeOnExtStoreTypeCollectedTriple( |
|||
(extstoretype) => this.transferCollectedFunctions(child.scripts, funcCollections, extstoretype) |
|||
); |
|||
} |
|||
} |
|||
|
|||
|
|||
let wenity = new WebTrinity(); |
|||
/** |
|||
* DEAL WITH CHILDREN WENITY STYLE |
|||
*/ |
|||
if (component._isCompel) { |
|||
executeOnExtStoreTypeCollectedTriple((est) => this.dealCollectedFuncs(funcCollections, est)); |
|||
} else { |
|||
wenity.scripts = funcCollections; |
|||
wenity.stylings = styleCollection; |
|||
} |
|||
|
|||
wenity.compext = component._element |
|||
|
|||
for (const group of component._toRegister) { |
|||
Page.subscribeComponentToGroup(group, component._compName); |
|||
} |
|||
|
|||
return wenity; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,14 @@ |
|||
/** |
|||
* Enum to access common events |
|||
*/ |
|||
const CommonEvents = Object.freeze({ |
|||
ONCLICK: "onclick", |
|||
ONCHANGE: "onchange", |
|||
SCROLL: "scroll", |
|||
DRAG_START: "dragstart", |
|||
DRAG_END: "dragend", |
|||
DRAG_ENTER: "dragenter", |
|||
DRAG_LEAVE: "dragleave", |
|||
DRAG_OVER: "dragover", |
|||
DROP: "drop", |
|||
}); |
@ -0,0 +1,63 @@ |
|||
/** |
|||
* Default implementation of a contextmenu-behaviour |
|||
*/ |
|||
const DefaultContextMenu = { |
|||
openContextMenuAction: function (identifier, refPos) { |
|||
if (!refPos) { |
|||
refPos = function (event) { |
|||
return new Sides() |
|||
.left(event.pageX) |
|||
.top(event.pageY); |
|||
} |
|||
} |
|||
|
|||
return function (event) { |
|||
event.preventDefault(); |
|||
|
|||
if (refPos instanceof Function) { |
|||
refPos = refPos(event); |
|||
} |
|||
|
|||
let menu = document.querySelector(`[data-autocompel="${identifier}"`); |
|||
|
|||
menu.style.left = `${refPos.getByIndex(SideDirections.LEFT)}px`; |
|||
menu.style.top = `${refPos.getByIndex(SideDirections.TOP)}px`; |
|||
|
|||
helperFun.toggleElementVisibility(menu, true); |
|||
helperFun.toggleElementVisibility(menu); |
|||
|
|||
document.addEventListener( |
|||
"click", |
|||
DefaultContextMenu.hideOnClickOutsideBoundsAction(identifier) |
|||
); |
|||
document.addEventListener( |
|||
"keyup", |
|||
DefaultContextMenu.hideOnEscapeAction(identifier) |
|||
); |
|||
} |
|||
|
|||
} |
|||
, |
|||
hideOnEscapeAction: function (identifier) { |
|||
return function (event) { |
|||
if (event.key === "Escape") { |
|||
let menu = document.querySelector(`[data-autocompel="${identifier}"`); |
|||
helperFun.toggleElementVisibility(menu, true); |
|||
document.removeEventListener("keyup"); |
|||
} |
|||
} |
|||
} |
|||
, |
|||
hideOnClickOutsideBoundsAction: function (identifier) { |
|||
return function (event) { |
|||
let menu = document.querySelector(`[data-autocompel="${identifier}"`); |
|||
|
|||
let area = getEnclosingBounds(menu); |
|||
|
|||
if (!areXYInArea(area, event.clientX, event.clientY)) { |
|||
helperFun.toggleElementVisibility(menu, true); |
|||
document.removeEventListener("click") |
|||
} |
|||
} |
|||
} |
|||
}; |
@ -0,0 +1,123 @@ |
|||
const EventDrag = Object.freeze({ |
|||
OVER: "over", |
|||
START: "start", |
|||
END: "end", |
|||
ENTER: "enter", |
|||
LEAVE: "leave" |
|||
}); |
|||
|
|||
/** |
|||
* Defines default functions to setup a drag/drop behaviour |
|||
*/ |
|||
class DragAndDropImplementation { |
|||
_isMngmtElNeeded; |
|||
|
|||
ensureManagementElementExists() { |
|||
|
|||
} |
|||
|
|||
getManagementElement() { |
|||
|
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} datakey |
|||
* @param {string} selector |
|||
* @returns {Function} |
|||
*/ |
|||
dragStartAction(datakey, selector) { |
|||
return function (event) { |
|||
console.log("DragEvent", event, "on", selector); |
|||
event.dataTransfer |
|||
.setData( |
|||
datakey, |
|||
JSON.stringify({ |
|||
["selector"]: selector, |
|||
["relDims"]: new TwoDimPoint( |
|||
event.clientX - event.target.getBoundingClientRect().left, |
|||
event.clientY - event.target.getBoundingClientRect().top |
|||
) |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
|
|||
dragEventAction(selector) { |
|||
return function (event) { |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {string} datakey |
|||
* @param {string} selector |
|||
* @returns {Function} |
|||
*/ |
|||
dropEventAction(datakey, selector) { |
|||
return function (event) { |
|||
event.preventDefault(); |
|||
|
|||
let draggedData = JSON.parse(event.dataTransfer.getData(datakey)); |
|||
let draggedElement = document.querySelector(draggedData.selector); |
|||
|
|||
let target = event.target |
|||
.closest(selector); |
|||
|
|||
console.log("Default drop event"); |
|||
|
|||
//if (![...target.childNodes].includes(draggedElement)) {
|
|||
target.appendChild(draggedElement); |
|||
//}
|
|||
} |
|||
} |
|||
}; |
|||
|
|||
|
|||
const DADInPlace = new DragAndDropImplementation(); |
|||
DADInPlace.dropEventAction = function (datakey, selector) { |
|||
/** |
|||
* @param {Event} event |
|||
*/ |
|||
return function (event) { |
|||
event.preventDefault(); |
|||
|
|||
let draggedData = JSON.parse(event.dataTransfer.getData(datakey)); |
|||
let draggedElement = document.querySelector(draggedData.selector); |
|||
|
|||
let target = event.target |
|||
.closest(selector); |
|||
|
|||
let dimsDraggedEl = new Dimensions() |
|||
.width(draggedElement.offsetWidth) |
|||
.height(draggedElement.offsetHeight); |
|||
|
|||
let pagePoint = new TwoDimPoint( |
|||
event.clientX, |
|||
event.clientY |
|||
); |
|||
|
|||
let parentPagePoint = new TwoDimPoint( |
|||
target.getBoundingClientRect().left, |
|||
target.getBoundingClientRect().top |
|||
); |
|||
|
|||
draggedElement.style["width"] = dimsDraggedEl._fFirst + "px"; |
|||
draggedElement.style["height"] = dimsDraggedEl._fSecond + "px"; |
|||
|
|||
// x page to relative is (parent-page), then -rel
|
|||
|
|||
let dropPosition = pagePoint |
|||
.minusTDP(parentPagePoint) |
|||
.minusTDP(draggedData.relDims); |
|||
|
|||
draggedElement.style.position = "relative"; |
|||
draggedElement.style.left = dropPosition.x + "px"; |
|||
draggedElement.style.top = dropPosition.y + "px"; |
|||
//if (![...target.childNodes].includes(draggedElement)) {
|
|||
target.appendChild(draggedElement); |
|||
//}
|
|||
} |
|||
} |
@ -0,0 +1,55 @@ |
|||
/** |
|||
* |
|||
*/ |
|||
class ObjectAccessObject { |
|||
/** |
|||
* |
|||
* @param {Object} object |
|||
*/ |
|||
constructor(object = null) { |
|||
/** |
|||
* @type {Array<string>} |
|||
*/ |
|||
this.keys = (object ? Object.keys(object) : []); |
|||
/** |
|||
* @type {Object} |
|||
*/ |
|||
this.objects = (object ? object : {}); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Array<string>} keyArr |
|||
* @param {Object} refObject |
|||
* @returns |
|||
*/ |
|||
fillByArrayReference(keyArr, refObject) { |
|||
this.keys = keyArr; |
|||
refObject = keyArr.reduce((a, c) => Object.assign(a, { [c]: refObject[c] }), {}); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key |
|||
* @param {*} value |
|||
* @returns {ObjectAccessObject} |
|||
*/ |
|||
add(key, value) { |
|||
this.objects[key, value]; |
|||
this.keys.push(key); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key |
|||
* @returns {*} |
|||
*/ |
|||
remove(key) { |
|||
let tmp = this.objects[key]; |
|||
delete this.objects[key]; |
|||
return tmp; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,101 @@ |
|||
/** |
|||
* |
|||
* @param {Object | Array<Object>} singleOrArray |
|||
* @param {Function} fun |
|||
* @returns {Object | Array<Object>} |
|||
*/ |
|||
function onSingleOrArray(singleOrArray, fun) { |
|||
if (singleOrArray instanceof Array) { |
|||
return singleOrArray.map(fun); |
|||
} |
|||
return fun(singleOrArray); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
const helperFun = { |
|||
/** |
|||
* Iterates over the keys of attrs, |
|||
* extracts the corresponding value |
|||
* and applies the callback (cb) on it in the order (key, value, targetContainer) |
|||
* @extends StyleAndScriptStoringComponent |
|||
* @param {map<string,any>} attrs |
|||
* @param {Object} intoContainer |
|||
* @param {Function<string, any, Object>} cb |
|||
* @returns {Object} the filled container |
|||
*/ |
|||
fillAttrsInContainerByCb: function (attrs, intoContainer, cb) { |
|||
let keys = Object.keys(attrs); |
|||
for (let i = 0; i < keys.length; i++) { |
|||
cb(keys[i], attrs[keys[i]], intoContainer); |
|||
} |
|||
return intoContainer; |
|||
}, |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {HTMLElement} element |
|||
* @returns {string} |
|||
*/ |
|||
getHigherCompSelector: function (element) { |
|||
let dac = "data-autocompel"; |
|||
let hcompel = element.closest('[data-compel-ishcompel="true"]'); |
|||
return `[${dac}="${hcompel.getAttribute(dac)}"]`; |
|||
}, |
|||
|
|||
/** |
|||
* |
|||
* @param {string} selector |
|||
* @returns {boolean} [ensureHidden=false] for true element is now hidden and false it is not hidden. |
|||
*/ |
|||
toggleSelectorElementVisibility: function (selector, ensureHidden = false) { |
|||
/** |
|||
* @type {HTMLElement} |
|||
*/ |
|||
let el = document.querySelector(selector); |
|||
let name = el.getAttribute("data-autocompel"); |
|||
|
|||
console.log("De-/hiding", name, selector); |
|||
|
|||
return helperFun.toggleElementVisibility(el, ensureHidden); |
|||
}, |
|||
|
|||
/** |
|||
* (De-) hides the given element. |
|||
* On ensureHidden=true it will be hidden regardless of the current state. |
|||
* |
|||
* @param {HTMLElement} element |
|||
* @param {boolean} ensureHidden |
|||
* @returns {boolean} true if element is now hidden |
|||
*/ |
|||
toggleElementVisibility: function (element, ensureHidden = false) { |
|||
element.classList.toggle("compel-mech-hidden"); |
|||
|
|||
let isNowHidden = false; |
|||
|
|||
if (element.hasAttribute("hidden")) { |
|||
element.removeAttribute("hidden"); |
|||
element.style["display"] = "flex"; |
|||
isNowHidden = false; |
|||
} else { |
|||
element.setAttribute("hidden", "hidden"); |
|||
element.style.removeProperty("display"); |
|||
isNowHidden = true; |
|||
} |
|||
|
|||
if (ensureHidden && !isNowHidden) { |
|||
return helperFun.toggleElementVisibility(element); |
|||
} else { |
|||
return isNowHidden; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Contains helperFun functions from the installed/loaded extensions |
|||
* (jpclwe-<extension-name>) |
|||
*/ |
|||
extensions: {} |
|||
} |
@ -0,0 +1,84 @@ |
|||
/** |
|||
* |
|||
* @param {number} start |
|||
* @param {number} end |
|||
* @param {number} value |
|||
* @param {number} tolerance |
|||
* @param {boolean} usePercentage |
|||
* @returns {boolean} |
|||
*/ |
|||
function isValueInBounds(start, end, value, tolerance = 0, usePercentage = false) { |
|||
if (tolerance !== 0) { |
|||
if (usePercentage) { |
|||
start = start * (1 - tolerance / 100); |
|||
end = end * (1 + tolerance / 100); |
|||
} else { |
|||
start = start - tolerance; |
|||
end = end + tolerance; |
|||
} |
|||
} |
|||
|
|||
return value >= start && value <= end; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @param {number} x |
|||
* @param {number} y |
|||
* @param {Map<SideDirections,number>} area |
|||
* @param {number} tolerance |
|||
* @param {boolean} usePercentage if tolerance is given and this is set to true, |
|||
* the tolerance will be calculated by percentage, |
|||
* otherwise it will be subtracted/added |
|||
* @returns {boolean} |
|||
*/ |
|||
function areXYInArea(x, y, area, tolerance = 0, usePercentage = false) { |
|||
return isValueInBounds( |
|||
area.get(SideDirections.LEFT), |
|||
area.get(SideDirections.RIGHT), |
|||
x, |
|||
tolerance, |
|||
usePercentage |
|||
) && isValueInBounds( |
|||
area.get(SideDirections.TOP), |
|||
area.get(SideDirections.BOTTOM), |
|||
y, |
|||
tolerance, |
|||
usePercentage |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {TwoDimPoint} point |
|||
* @param {Map<SideDirections,number>} area |
|||
* @param {number} tolerance |
|||
* @param {boolean} usePercentage if tolerance is given and this is set to true, |
|||
* the tolerance will be calculated by percentage, |
|||
* otherwise it will be subtracted/added |
|||
*/ |
|||
function isPointInArea(point, area, tolerance = 0, usePercentage = false) { |
|||
return areXYInArea( |
|||
point.x, |
|||
point.y, |
|||
area, |
|||
tolerance, |
|||
usePercentage |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {HTMLElement} element |
|||
* @returns {Object<SideDirections, number} |
|||
*/ |
|||
function getEnclosingBounds(element) { |
|||
let area = element.getBoundingClientRect(); |
|||
let parentArea = element.parentElement.getBoundingClientRect(); |
|||
return { |
|||
[SideDirections.LEFT]: Math.min(area.left, parentArea.left), |
|||
[SideDirections.RIGHT]: Math.max(area.right, parentArea.right), |
|||
[SideDirections.TOP]: Math.min(area.top, parentArea.top), |
|||
[SideDirections.BOTTOM]: Math.max(area.bottom, parentArea.bottom) |
|||
} |
|||
} |
@ -0,0 +1,348 @@ |
|||
/** |
|||
* Class containing two numbers. |
|||
* Usually they represent coordinates, |
|||
* but they also might serve as length and width |
|||
* as this class provides several convenience methods. |
|||
*/ |
|||
class TwoDimPoint { |
|||
/** |
|||
* |
|||
* @param {number} x |
|||
* @param {number} y |
|||
*/ |
|||
constructor(x = -1, y = -1) { |
|||
this.x = x; |
|||
this.y = y; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {string} |
|||
*/ |
|||
toString() { |
|||
return `P(${this.x},${this.y})`; |
|||
} |
|||
|
|||
/** |
|||
* Loads the width and height of an element into the x and y of this TDP. |
|||
* @param {HTMLElement} element |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
loadFromElementDimensions(element) { |
|||
let bcr = element.getBoundingClientRect(); |
|||
this.x = bcr.width; |
|||
this.y = bcr.height; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Loads the coordinates of the top-left corner of an element, |
|||
* into the x and y of this TDP. |
|||
* @param {HTMLElement} element |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
loadFromElementZero(element) { |
|||
let bcr = element.getBoundingClientRect(); |
|||
this.x = bcr.left; |
|||
this.y = bcr.top; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Loads the coordinates of the bottom-right corner of an element, |
|||
* into the x and y of this TDP. |
|||
* @param {HTMLElement} element |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
loadFromElementMax(element) { |
|||
let bcr = element.getBoundingClientRect(); |
|||
this.x = bcr.left; |
|||
this.y = bcr.top; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* If onConditionMet is true (default) and the value of x is positive |
|||
* the x of this TDP will be multiplied with -1, |
|||
* otherwise nothing will be done. |
|||
* @param {boolean} [onConditionMet = true] onConditionMet |
|||
* @returns {TwoDimPoint} this TDP Object |
|||
*/ |
|||
setXAsNegative(onConditionMet = true) { |
|||
if (onConditionMet && this.x > 0) { |
|||
this.x = this.x * -1; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* If onConditionMet is true (default) and the value of y is positive |
|||
* the y of this TDP will be multiplied with -1, |
|||
* otherwise nothing will be done. |
|||
* @param {boolean} [onConditionMet = true] onConditionMet |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setYAsNegative(onConditionMet = true) { |
|||
if (onConditionMet && this.y > 0) { |
|||
this.y = this.y * -1; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @param {boolean} onConditionMetX |
|||
* @param {boolean} onConditionMetY |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setAsNegative(onConditionMetX, onConditionMetY) { |
|||
this.setXAsNegative(onConditionMetX); |
|||
this.setYAsNegative(onConditionMetY); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Subtracts the given Delta from the x-value |
|||
* @param {number} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setXMinus(delta) { |
|||
this.x = this.x - delta; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds the given Delta from the x-value |
|||
* @param {number} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setXPlus(delta) { |
|||
this.x = this.x + delta; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Subtracts the given Delta from the y-value |
|||
* @param {number} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setYMinus(delta) { |
|||
this.y = this.y - delta; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds the given Delta from the y-value |
|||
* @param {number} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setYPlus(delta) { |
|||
this.y = this.y + delta; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Returns the absolute delta between this x and given ref |
|||
* @param {number} refnumber |
|||
* @returns {number} |
|||
*/ |
|||
getXDeltaTo(refnumber) { |
|||
return Math.abs(this.x - refnumber); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Returns the absolute delta between this y and given ref |
|||
* @param {number} refnumber |
|||
* @returns {number} |
|||
*/ |
|||
getYDeltaTo(refnumber) { |
|||
return Math.abs(this.y - refnumber); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} times |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setXMultiplied(times) { |
|||
this.x = this.x * times; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} times |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setYMultiplied(times) { |
|||
this.y = this.y * times; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} times |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setXDivided(times) { |
|||
if (times === 0) { |
|||
throw new Error("Dividing by 0 is not defined"); |
|||
} |
|||
this.x = this.x / times; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} times |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setYDivided(times) { |
|||
if (times === 0) { |
|||
throw new Error("Dividing by 0 is not defined"); |
|||
} |
|||
this.y = this.y / times; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Subtracts the given Delta {TwoDimPoint} TDP from this TDP |
|||
* @param {TwoDimPoint} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setThisByMinusTDP(delta) { |
|||
this.x = this.x - delta.x; |
|||
this.y = this.y - delta.y; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds the given Delta {TwoDimPoint} TDP to this TDP |
|||
* @param {TwoDimPoint} delta |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setThisByPlusTDP(delta) { |
|||
this.x = this.x + delta.x; |
|||
this.y = this.y + delta.y; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {nr} tdp |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setByMultipliedByNumber(nr) { |
|||
this.x = this.x * nr; |
|||
this.y = this.y * nr; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {TwoDimPoint} tdp |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setByMultipliedByTDP(tdp) { |
|||
this.x = this.x * tdp.x; |
|||
this.y = this.y * tdp.y; |
|||
return this; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @param {nr} tdp |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setByDividedByNumber(nr) { |
|||
if (nr === 0) { |
|||
throw new Error("Dividing by 0 is not defined"); |
|||
} |
|||
this.x = this.x / nr; |
|||
this.y = this.y / nr; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {TwoDimPoint} tdp |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
setByDividedByTDP(tdp) { |
|||
if (tdp.x === 0 | tdp.y === 0) { |
|||
throw new Error("Dividing by 0 is not defined"); |
|||
} |
|||
this.x = this.x / tdp.x; |
|||
this.y = this.y / tdp.y; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Creates and returns a new TDP |
|||
* consisting of the absolute deltas of the x/y coordinates of this and the reference TDP. |
|||
* @param {TwoDimPoint} refTDP |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
getNewDeltaTDP(refTDP) { |
|||
return new TwoDimPoint( |
|||
this.getXDeltaTo(refTDP.x), |
|||
this.getYDeltaTo(refTDP.y) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Returns the absolute Distance between two points. |
|||
* @param {TwoDimPoint} refTDP |
|||
* @returns {number} |
|||
*/ |
|||
getDistanceBetween(refTDP) { |
|||
return Math.hypot( |
|||
this.getXDeltaTo(refTDP.x), |
|||
this.getYDeltaTo(refTDP.y) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Returns a new TDP where x and y are calculated, |
|||
* based on the given distance and angle (degree). |
|||
* @param {number} distance |
|||
* @param {number} degrees |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
getNewTDPByDistance( |
|||
distance, |
|||
degrees |
|||
) { |
|||
/** angle in radians */ |
|||
let angle = ((parseFloat(degrees) * Math.PI) / 180); |
|||
return new TwoDimPoint( |
|||
this.x + distance * Math.cos(angle), |
|||
this.y + distance * Math.sin(angle), |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Multiplies both (x,y) with -1 to inverse them. |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
inverse() { |
|||
this.x = this.x * -1 |
|||
this.y = this.y * -1 |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Flips/changes x and y with each other. |
|||
* @returns {TwoDimPoint} |
|||
*/ |
|||
flip() { |
|||
let oldX = this.x; |
|||
this.x = this.y; |
|||
this.y = oldX; |
|||
return this; |
|||
} |
|||
|
|||
} |
@ -1,276 +0,0 @@ |
|||
/** |
|||
* This file is part of the jps-like-websites lib |
|||
* URL: https://git.labos.goip.de/chris/jpc-like-websites
|
|||
* COPYRIGHT and LICENCE are owned by its creator Christian Martin |
|||
* Copy, altering or distribution without the allowance of the owner are prohibited |
|||
*/ |
|||
|
|||
/** |
|||
* A chained class that sets most of the stylings of an element |
|||
* Attributes: |
|||
* _modifications: {Object} |
|||
*/ |
|||
class Modifier { |
|||
/** |
|||
* @property {Map<string,string>} _modifications |
|||
*/ |
|||
_modifications; |
|||
_shape; |
|||
|
|||
constructor() { |
|||
this._modifications = new Object(); |
|||
} |
|||
|
|||
/** |
|||
* Sets the modifications for widht and height to 100%. |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxSize(widthFraction = 1, heightFraction = 1) { |
|||
return this.fillMaxWidth(widthFraction) |
|||
.fillMaxHeight(heightFraction); |
|||
} |
|||
|
|||
/** |
|||
* Sets the modification for width to the given fraction of 1 (default 1 := 100%). |
|||
* @param {number} fraction |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxWidth(fraction = 1) { |
|||
this._modifications["width"] = (100 * fraction) + "%"; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the modification for height to the given fraction of 1 (default 1 := 100%). |
|||
* @param {number} fraction |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxHeight(fraction = 1) { |
|||
this._modifications["height"] = (100 * fraction) + "%"; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets modifications according to the dimensions object. |
|||
* @param {Dimensions} dimensions |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
dimensions(dimensions) { |
|||
dimensions.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[kvpair.key] = kvpair.value; |
|||
}) |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the padding on all sides according to the given padding object. |
|||
* Currently the padding will always be set |
|||
* to the most recent padding/padding. |
|||
* @param {Sides} siding |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
padding(siding) { |
|||
let keyToAdd = ""; |
|||
if (siding instanceof PaddingChain) { |
|||
|
|||
} else if (siding instanceof Sides) { |
|||
keyToAdd = "padding-" |
|||
} |
|||
siding.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[keyToAdd + kvpair.key] = kvpair.value; |
|||
}) |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the margin on all sides according to the given siding object. |
|||
* Currently the margin will always be set |
|||
* to the most recent margin/siding. |
|||
* @param {Sides} siding |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
margin(siding) { |
|||
let keyToAdd = ""; |
|||
if (siding instanceof Sides) { |
|||
keyToAdd = "margin-" |
|||
} |
|||
|
|||
siding.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[keyToAdd + kvpair.key] = kvpair.value; |
|||
}); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the background-color as a rgb color. |
|||
* @param {Color} color |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
background(color) { |
|||
this._modifications["background-color"] = color.cssRGBString(); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the color as a rgb color. |
|||
* @param {Color} color |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
color(color) { |
|||
this._modifications["color"] = color.cssRGBString(); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds the modifications of the given Modifier to current Modifier. |
|||
* This is especailly used in the cases of extending existing/pre defined |
|||
* Components. |
|||
* CAUTION matching existing modifications will be overwritten. |
|||
* @param modifier The "new" Modifier |
|||
* @returns {Modifier} The "old/current" Modifier, |
|||
* extended with the modifications of the given Modifier. |
|||
*/ |
|||
join(modifier, modifications = {}) { |
|||
var keys = Object.keys(modifier.ensureModifier()._modifications); |
|||
for (let i = 0; i < keys.length; i++) { |
|||
/* if (!this._modifications.hasOwnProperty(keys[i])) */ |
|||
this._modifications[keys[i]] = modifier.ensureModifier()._modifications[keys[i]]; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key a css style rule |
|||
* @param {string} value the corresponding value to the css style rule |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
setStyleRule(key, value) { |
|||
this._modifications[key] = value; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets a border line (with given linestyle) to all sides. |
|||
* If lineStyle is an array, the containing LineStyles, |
|||
* are applied in the order: [top, right, bottom, left]. |
|||
* If the border has a shape defined, |
|||
* this shape will override earlier shape definitions. |
|||
* Otherwise existing shape definitions will be applied. |
|||
* @param {Border} border the style of the border line |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
border(border) { |
|||
if (border._shape){ |
|||
this.clip(border._shape); |
|||
}else if(this._shape){ |
|||
border._shape = this._shape; |
|||
} |
|||
border.toModifications() |
|||
.forEach(e => this._modifications[e.key] = e.value); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Shape} shape |
|||
* @returns {Modifier} |
|||
*/ |
|||
clip(shape) { |
|||
this._shape = shape; |
|||
this._modifications["border-radius"] = shape.getOrderedValues().join(' '); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} size of width and height in pixels |
|||
* @returns {DimensionsChain} |
|||
*/ |
|||
linkDimensions(size = -1) { |
|||
if (size === -1) { |
|||
return new DimensionsChain(this); |
|||
} else { |
|||
return new DimensionsChain(this).all(size).ensureModifier() |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} amount the padding for all four sides |
|||
* @returns {PaddingChain} |
|||
*/ |
|||
linkPadding(amount = -1) { |
|||
if (amount === -1) { |
|||
return new PaddingChain(this); |
|||
} else { |
|||
return new PaddingChain(this).all(amount); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} cornerRadius will create a rounded rectangle with the given cornerRadius |
|||
* @returns {ShapeChain} |
|||
*/ |
|||
linkClip(cornerRadius = -1) { |
|||
if (cornerRadius === -1) { |
|||
return new ShapeChain(this); |
|||
} else { |
|||
return new ShapeChain(this).all(cornerRadius); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {number} borderWidth sets the width of all four border sides |
|||
* @returns {BorderChain} |
|||
*/ |
|||
linkBorder(borderWidth = -1) { |
|||
if (borderWidth === -1) { |
|||
return new BorderChain(this); |
|||
} else { |
|||
return new BorderChain(this).width(borderWidth); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {Modifier} |
|||
*/ |
|||
ensureModifier() { |
|||
return this; |
|||
} |
|||
|
|||
} |
|||
|
|||
class ChainableModifier extends Modifier { |
|||
_component; |
|||
|
|||
constructor(component) { |
|||
super(); |
|||
this._component = component; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {Component} |
|||
*/ |
|||
toComponent() { |
|||
return this._component.modifier(this); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {Component} the parent Component |
|||
*/ |
|||
childContext(innerComponent) { |
|||
return this._component |
|||
.modifier(this) |
|||
.childContext(innerComponent); |
|||
} |
|||
} |
@ -0,0 +1,187 @@ |
|||
/** |
|||
* @extends Modifier |
|||
* @inheritdoc |
|||
*/ |
|||
class ChainableModifier extends Modifier { |
|||
/** |
|||
* @type {Component} |
|||
*/ |
|||
_component; |
|||
|
|||
/** |
|||
* |
|||
* @param {Component} component |
|||
*/ |
|||
constructor(component) { |
|||
super(); |
|||
this._component = component; |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
fillMaxSize(widthFraction = 1, heightFraction = 1) { |
|||
return super.fillMaxSize(widthFraction, heightFraction); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
fillMaxWidth(fraction = 1) { |
|||
return super.fillMaxWidth(fraction); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
fillMaxHeight(fraction = 1) { |
|||
return super.fillMaxHeight(fraction); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
background(color) { |
|||
return super.background(color); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
color(color) { |
|||
return super.color(color); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
setStyleRule(key, value) { |
|||
return super.setStyleRule(key, value); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
addStyleRuleMap(rulemap) { |
|||
return super.addStyleRuleMap(rulemap); |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @returns {ChainableModifier} |
|||
*/ |
|||
removeStyleRule(key) { |
|||
return super.removeStyleRule(key); |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* |
|||
* In Case it is called from a ChainableModifier chain, |
|||
* the Return type would be DimensionsChainedModifier instead of DimensionsChain. |
|||
* @override |
|||
* @param {Dimensions | number | undefined} [modify=null] dimensions |
|||
* @returns {ChainableModifier | DimensionsChainedModifier} |
|||
*/ |
|||
dimensions(modify = null) { |
|||
if (modify instanceof Dimensions || Number.isInteger(modify)) { |
|||
return super.dimensions(modify); |
|||
} |
|||
return new DimensionsChainedModifier(this); |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @param {Padding | number | undefined} [modify=null] modify as in modifiers |
|||
* @returns {ChainableModifier | PaddingChainedModifier} |
|||
*/ |
|||
padding(modify = null) { |
|||
if (modify instanceof Padding || Number.isInteger(modify)) { |
|||
return super.padding(modify); |
|||
} |
|||
return new PaddingChainedModifier(this); |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* @override |
|||
* @param {Margin | number | undefined} [modify=null] modify as in modifiers |
|||
* @returns {ChainableModifier | MarginChainedModifier} |
|||
*/ |
|||
margin(modify = null) { |
|||
if (modify instanceof Margin || Number.isInteger(modify)) { |
|||
return super.margin(modify); |
|||
} |
|||
return new MarginChainedModifier(this); |
|||
} |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
* |
|||
* @override |
|||
* @param {Shape | number | undefined} [modify=null] modify |
|||
* @returns {ChainableModifier | ShapeChainedModifier} |
|||
*/ |
|||
clip(modify = null) { |
|||
if (modify instanceof Shape || Number.isInteger(modify)) { |
|||
return super.clip(modify); |
|||
} |
|||
return new ShapeChainedModifier(this); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @inheritdoc |
|||
* |
|||
* @override |
|||
* @param {Border | number | undefined} [modify=null] modify |
|||
* @returns {ChainableModifier | BorderChainedModifier} |
|||
*/ |
|||
border(modify = null) { |
|||
if (modify instanceof Border || Number.isInteger(modify)) { |
|||
return super.border(modify); |
|||
} |
|||
return new BorderChainedModifier(this); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* @returns {Component} |
|||
*/ |
|||
toComponent() { |
|||
return this._component.modifier(this); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {Component} the parent Component |
|||
*/ |
|||
childContext(innerComponent) { |
|||
return this._component |
|||
.modifier(this) |
|||
.childContext(innerComponent); |
|||
} |
|||
|
|||
/** |
|||
* Calls chainChild() from Component (ChildbearerComponent) |
|||
* @see {ChildbearerComponent.chainChild} |
|||
* @returns {builder} |
|||
*/ |
|||
chainChild() { |
|||
return this._component |
|||
.modifier(this) |
|||
.chainChild(); |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
/** |
|||
* @abstract |
|||
*/ |
|||
class ModificationSubChain { |
|||
/** |
|||
* @type {Modifier} |
|||
*/ |
|||
_modifier; |
|||
/** |
|||
* @type {string} |
|||
*/ |
|||
_modMethod; |
|||
|
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
* @param {string} modMethod |
|||
*/ |
|||
constructor(modifier, modMethod) { |
|||
this._modifier = modifier; |
|||
this._modMethod = modMethod; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Modifier SubChain to the Modifier |
|||
* @returns {Modifier|ChainableModifier} |
|||
*/ |
|||
toModifier() { |
|||
return this._modifier[this._modMethod](this); |
|||
} |
|||
|
|||
/** |
|||
* Returns chain to the Modifier |
|||
* @returns {Modifier|ChainableModifier} |
|||
*/ |
|||
ensureModifier() { |
|||
return this.toModifier(); |
|||
} |
|||
} |
|||
/** |
|||
* @inheritdoc |
|||
* @extends ModificationSubChain |
|||
* @abstract |
|||
*/ |
|||
class ModificationSubChainReComp extends ModificationSubChain { |
|||
/** |
|||
* |
|||
* @param {ChainableModifier} modifier |
|||
* @param {string} modMethod |
|||
*/ |
|||
constructor(modifier) { |
|||
super(); |
|||
this._modifier = modifier; |
|||
} |
|||
|
|||
/** |
|||
* Returns chain to the component that is under modification |
|||
* @returns {Component} the Component that was (supposed to be) modified by this obj. |
|||
*/ |
|||
toComponent() { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent(); |
|||
} |
|||
|
|||
/** |
|||
* Returns chain to the component that is under modification |
|||
* and adds the given innerComponent as children to the component. |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {Component} |
|||
*/ |
|||
childContext(innerComponent) { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent() |
|||
.childContext(innerComponent); |
|||
} |
|||
|
|||
/** |
|||
* Closes modifier chain and returns builder to define the next child of the component |
|||
* @returns {Component} |
|||
*/ |
|||
chainChild() { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent() |
|||
.chainChild(); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,466 @@ |
|||
/** |
|||
* A chained class that sets most of the stylings of an element |
|||
* Attributes: |
|||
* _modifications: {Object} |
|||
*/ |
|||
class Modifier { |
|||
/** |
|||
* @type {Map<string,string>} _modifications |
|||
*/ |
|||
_modifications; |
|||
/** |
|||
* @type {Array<string>} |
|||
*/ |
|||
_removeMods; |
|||
/** |
|||
* @type {Shape} |
|||
*/ |
|||
_shape; |
|||
/** |
|||
* @type {Sides} paddingValues |
|||
*/ |
|||
_paddingValues; |
|||
|
|||
constructor() { |
|||
this._modifications = new Object(); |
|||
this._removeMods = []; |
|||
} |
|||
|
|||
/** |
|||
* Sets the modifications for widht and height to 100%. |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxSize(widthFraction = 1, heightFraction = 1) { |
|||
return this.fillMaxWidth(widthFraction) |
|||
.fillMaxHeight(heightFraction); |
|||
} |
|||
|
|||
/** |
|||
* Sets the modification for width to the given fraction of 1 (default 1 := 100%). |
|||
* @param {number} fraction |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxWidth(fraction = 1) { |
|||
this._modifications["width"] = (100 * fraction) + "%"; |
|||
this._modifications["max-width"] = (100 * fraction) + "%"; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the modification for height to the given fraction of 1 (default 1 := 100%). |
|||
* @param {number} fraction |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
fillMaxHeight(fraction = 1) { |
|||
this._modifications["height"] = (100 * fraction) + "%"; |
|||
this._modifications["max-height"] = (100 * fraction) + "%"; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} keyWord weither 'height' or 'width' that will be adjusted and looked for |
|||
* @param {Sides} parentalPadding |
|||
*/ |
|||
_updateDimensionsBy(parentalPadding) { |
|||
function updateDirection(keyWord, modifications, parentalAdjustment) { |
|||
let refKeys = Object.keys(modifications) |
|||
.filter(k => k.includes(keyWord)); |
|||
|
|||
if (refKeys.length > 0) { |
|||
for (let i = 0; i < refKeys.length; i++) { |
|||
let key = refKeys[i]; |
|||
let value = modifications[key]; |
|||
|
|||
if (key.includes("calc")) { |
|||
console.log( |
|||
`Modifier._updateByParent... ${keyWord |
|||
} - unexpected value '${value |
|||
}' for '${key |
|||
}', skipping...` |
|||
); |
|||
} else { |
|||
let newValue = `calc(${value} - ${parentalAdjustment});`; |
|||
modifications[key] = newValue.trim(); |
|||
} |
|||
} |
|||
} |
|||
return modifications; |
|||
} |
|||
|
|||
if (parentalPadding) { |
|||
let pval = parentalPadding.getValues(); |
|||
if (pval["horizontal"] > 0) { |
|||
this._modifications = updateDirection("width", this._modifications, pval["horizontal"] + parentalPadding._unit); |
|||
} |
|||
|
|||
if (pval["vertical"] > 0) { |
|||
this._modifications = updateDirection("height", this._modifications, pval["vertical"] + parentalPadding._unit); |
|||
} |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Takes dimensions as param which can be either of three: |
|||
* |
|||
* param == null (not given): |
|||
* Returns new DimensionsChain (to chain Dimensions-Modifications) |
|||
* |
|||
* param == number (>0): |
|||
* Applies Dimensions.all(dimension); |
|||
* Returns this modifier |
|||
* |
|||
* param == Dimensions: |
|||
* Sets modifications according to the dimensions object; |
|||
* Returns this modifier |
|||
* @param {Dimensions | number | undefined} [modify=null] modify as in modifiers |
|||
* @returns { Modifier | DimensionsChain } this modifier object or a DimensionsChain |
|||
*/ |
|||
dimensions(modify = null) { |
|||
if (modify instanceof Dimensions) { |
|||
modify.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[kvpair.key] = kvpair.value; |
|||
}); |
|||
return this; |
|||
} else { |
|||
let modSub = new DimensionsChain(this); |
|||
if (Number.isInteger(modify) && modify > 0) { |
|||
return modSub.all(modify).ensureModifier(); |
|||
} |
|||
// case dimension is number but < 0 or dimensions == null or anything else
|
|||
return modSub; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets the padding on all sides according to the given padding object. |
|||
* Currently the padding will always be set |
|||
* to the most recent padding/padding. |
|||
* @param {Padding | number | undefined} modify as in modifiers |
|||
* @returns {Modifier | PaddingChain} this modifier object |
|||
*/ |
|||
padding(modify = null) { |
|||
if (modify instanceof Sides && !(modify instanceof Padding)) { |
|||
modify._modMethod = "padding"; |
|||
modify = Object.assign(new Padding(), modify); |
|||
} |
|||
|
|||
if (modify instanceof Padding) { |
|||
modify.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[kvpair.key] = kvpair.value; |
|||
}); |
|||
} else { |
|||
let modSub = new PaddingChain(this); |
|||
if (Number.isInteger(modify) && modify > -1) { |
|||
return modSub.all(modify).ensureModifier(); |
|||
} |
|||
return modSub |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the margin on all sides according to the given siding object. |
|||
* Currently the margin will always be set |
|||
* to the most recent margin/siding. |
|||
* @ATTENTION since it just increases complexity to constantly use padding and margin |
|||
* it is recommended to use padding and to stick to that as often as possible. |
|||
* Padding values take affect inside/within the element. |
|||
* @param {Margin | number | undefined} modify |
|||
* @returns {Modifier | MarginChain} this modifier object |
|||
*/ |
|||
margin(modify = null) { |
|||
if (modify instanceof Sides && !(modify instanceof Margin)) { |
|||
modify._modMethod = "margin"; |
|||
modify = Object.assign(new Margin(), modify); |
|||
} |
|||
|
|||
if (modify instanceof Margin) { |
|||
modify.toModifications() |
|||
.forEach(kvpair => { |
|||
this._modifications[kvpair.key] = kvpair.value; |
|||
}); |
|||
} else { |
|||
let modSub = new MarginChain(this); |
|||
if (Number.isInteger(modify) && modify > -1) { |
|||
return modSub.all(modify).ensureModifier(); |
|||
} |
|||
return modSub |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the background-color as a rgb color. |
|||
* If no color is given/specified the styling will be set to "inherit" |
|||
* and use the color setting from (one of) the parent. |
|||
* @param {Color} color |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
background(color) { |
|||
if (color) { |
|||
if (color._hex) { |
|||
this._modifications["background-color"] = color._hex; |
|||
} else { |
|||
this._modifications["background-color"] = color.cssRGBString(); |
|||
} |
|||
} else { |
|||
this._modifications["background-color"] = "inherit"; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the color as a rgb color. |
|||
* If no color is given/specified the styling will be set to "inherit" |
|||
* and use the color setting from (one of) the parent. |
|||
* @param {Color} color |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
color(color) { |
|||
this._modifications["color"] = ( |
|||
color |
|||
? color.cssRGBString() |
|||
: "inherit" |
|||
); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Adds the modifications of the given Modifier to current Modifier. |
|||
* This is especailly used in the cases of extending existing/pre defined |
|||
* Components. |
|||
* |
|||
* CAUTION matching existing modifications will be overwritten. |
|||
* |
|||
* @todo finish second parameter "modifications" - logic |
|||
* |
|||
* @param modifier The "new" Modifier |
|||
* @returns {Modifier} The "old/current" Modifier, |
|||
* extended with the modifications of the given Modifier. |
|||
*/ |
|||
join(modifier, modifications = {}) { |
|||
let keys = Object.keys(modifier.ensureModifier()._modifications); |
|||
for (let i = 0; i < keys.length; i++) { |
|||
/* if (!this._modifications.hasOwnProperty(keys[i])) */ |
|||
this._modifications[keys[i]] = modifier.ensureModifier()._modifications[keys[i]]; |
|||
} |
|||
let removeMods = modifier.ensureModifier()._removeMods; |
|||
if (removeMods.length > 0) { |
|||
for (let i = 0; i < removeMods.length; i++) { |
|||
delete this._modifications[removeMods[i]]; |
|||
} |
|||
} |
|||
if (modifier._paddingValues) { |
|||
this._paddingValues = modifier._paddingValues; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key a css style rule |
|||
* @param {string} value the corresponding value to the css style rule |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
setStyleRule(key, value) { |
|||
this._modifications[key] = value; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sets the style rules to deactivate text-selection in the component. |
|||
* @returns {Modifier} |
|||
*/ |
|||
noneSelect() { |
|||
return this |
|||
.setStyleRule("user-select", "none") |
|||
.setStyleRule("-ms-user-select", "none") |
|||
.setStyleRule("-webkit-user-select", "none") |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {StylePropertyMap} rulemap |
|||
* @returns {Modifier} |
|||
*/ |
|||
addStyleRuleMap(rulemap) { |
|||
for (const ruleKey of Object.keys(rulemap)) { |
|||
this._modifications[ruleKey] = rulemap[ruleKey]; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param {string} key |
|||
* @returns {Modifier} this modifier object |
|||
*/ |
|||
removeStyleRule(key) { |
|||
this._removeMods.push(key); |
|||
if (Object.keys(this._modifications).includes(key)) { |
|||
delete this._modifications[key]; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Takes border as param which can be either of three: |
|||
* |
|||
* param == null (not given): |
|||
* Returns new BorderChain (to chain Border-Modifications) |
|||
* |
|||
* param == number (>0): |
|||
* Applies Border.width(border); |
|||
* Rerturns this modifier |
|||
* |
|||
* param == Border: |
|||
* |
|||
* Sets a border line (with given linestyle) to all sides. |
|||
* If lineStyle is an array, the containing LineStyles, |
|||
* are applied in the order: [top, right, bottom, left]. |
|||
* If the border has a shape defined, |
|||
* this shape will override earlier shape definitions. |
|||
* Otherwise existing shape definitions will be applied; |
|||
* |
|||
* Rerturns this modifier |
|||
* |
|||
* @param {Border | number | undefined} [modify=null] modify as in modifiers |
|||
* @returns {Modifier | BorderChain} this modifier or BorderChain |
|||
*/ |
|||
border(modify = null) { |
|||
if (modify instanceof Border) { |
|||
if (modify._shape) { |
|||
this.clip(modify._shape); |
|||
} else if (this._shape) { |
|||
modify._shape = this._shape; |
|||
} |
|||
|
|||
modify.toModifications() |
|||
.forEach(e => this._modifications[e.key] = e.value); |
|||
|
|||
return this; |
|||
} else { |
|||
let modSub = new BorderChain(this); |
|||
if (Number.isInteger(modify) && modify > 0) { |
|||
return modSub.width(modify).ensureModifier(); |
|||
} |
|||
return modSub; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Takes shape as param which can be either of three: |
|||
* |
|||
* param == null (not given): |
|||
* Returns new ShapeChain (to chain Shape-Modifications) |
|||
* |
|||
* param == number (>0): |
|||
* Applies Shape.all(dimension); |
|||
* Returns this modifier |
|||
* |
|||
* param == Shape: |
|||
* Sets modifications according to the shape object; |
|||
* Returns this modifier |
|||
* |
|||
* |
|||
* @param {Shape | number | undefined} [modify=null] modify as in modifiers |
|||
* @returns { Modifier | ShapeChain } |
|||
*/ |
|||
clip(modify = null) { |
|||
if (modify instanceof Shape) { |
|||
this._shape = modify; |
|||
this._modifications["border-radius"] = modify.getOrderedValues().join(' '); |
|||
return this; |
|||
} else { |
|||
let modSub = new ShapeChain(this); |
|||
if (Number.isInteger(modify) && modify > 0) { |
|||
return modSub.all(modify).ensureModifier(); |
|||
} |
|||
return modSub; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @deprecated use Modifier.dimensions() instead |
|||
* @param {number} size of width and height in pixels |
|||
* @returns {DimensionsChain} |
|||
*/ |
|||
linkDimensions(size = -1) { |
|||
if (size === -1) { |
|||
return new DimensionsChainedModifier(this); |
|||
} else { |
|||
return new DimensionsChain(this).all(size).ensureModifier() |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @deprecated use Modifier.padding() instead |
|||
* @param {number} amount the padding for all four sides |
|||
* @returns {PaddingChain} |
|||
*/ |
|||
linkPadding(amount = -1) { |
|||
if (amount === -1) { |
|||
return new PaddingChainedModifier(this); |
|||
} else { |
|||
return new PaddingChain(this).all(amount); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @deprecated use Modifier.margin() instead |
|||
* @ATTENTION since it just increases complexity to constantly use padding and margin |
|||
* it is recommended to use padding and to stick to that as often as possible. |
|||
* Padding values take affect inside/within the element. |
|||
* @param {number} amount the padding for all four sides |
|||
* @returns {PaddingChain} |
|||
*/ |
|||
linkMargin(amount = -1) { |
|||
if (amount === -1) { |
|||
return new MarginChainedModifier(this); |
|||
} else { |
|||
return new MarginChain(this).all(amount); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @deprecated use Modifier.clip() instead |
|||
* @param {number} cornerRadius will create a rounded rectangle with the given cornerRadius |
|||
* @returns {ShapeChain} |
|||
*/ |
|||
linkClip(cornerRadius = -1) { |
|||
if (cornerRadius === -1) { |
|||
return new ShapeChainedModifier(this); |
|||
} else { |
|||
return new ShapeChain(this).all(cornerRadius); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @deprecated use Modifier.border() instead |
|||
* @param {number} borderWidth sets the width of all four border sides |
|||
* @returns {BorderChain} |
|||
*/ |
|||
linkBorder(borderWidth = -1) { |
|||
if (borderWidth === -1) { |
|||
return new BorderChainedModifier(this); |
|||
} else { |
|||
return new BorderChain(this).width(borderWidth); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @returns {Modifier} |
|||
*/ |
|||
ensureModifier() { |
|||
return this; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
@ -0,0 +1,87 @@ |
|||
/** |
|||
* Function is a mixin to add endings to a Modifier SubChain. |
|||
* Explicitly used for "link..." methods called from a Modifier, |
|||
* that is unaware of its component. |
|||
* |
|||
* @param {typeof DirectionUnitDependentAttribute} classToExtend |
|||
* @returns {typeof ModificationSubChain} |
|||
*/ |
|||
function mixinModSubChainEndings(classToExtend) { |
|||
return class extends classToExtend { |
|||
/** |
|||
* @type {Modifier} |
|||
*/ |
|||
_modifier; |
|||
|
|||
/** |
|||
* |
|||
* @param {Modifier} modifier |
|||
* @param {string} modMethod |
|||
*/ |
|||
constructor(modifier) { |
|||
super(); |
|||
this._modifier = modifier; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Modifier SubChain to the Modifier |
|||
* @returns {Modifier|ChainableModifier} |
|||
*/ |
|||
toModifier() { |
|||
let tmp = this._modifier[this._modMethod]; |
|||
return this._modifier[this._modMethod](this); |
|||
} |
|||
|
|||
/** |
|||
* Returns chain to the Modifier |
|||
* @returns {Modifier|ChainableModifier} |
|||
*/ |
|||
ensureModifier() { |
|||
return this.toModifier() |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* |
|||
* Function is a mixin to add "return" methods to a Modifier SubChain. |
|||
* Explicitly used for "link..." methods called from a ChainableModifier. |
|||
* |
|||
* @param {typeof DirectionUnitDependentAttribute} classToExtend |
|||
* @returns {typeof ModificationSubChainReComp} |
|||
*/ |
|||
function mixinModSubChainComponentMethods(classToExtend) { |
|||
return class extends mixinModSubChainEndings(classToExtend) { |
|||
/** |
|||
* Returns chain to the component that is under modification |
|||
* @returns {Component} the Component that was (supposed to be) modified by this obj. |
|||
*/ |
|||
toComponent() { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent(); |
|||
} |
|||
|
|||
/** |
|||
* Returns chain to the component that is under modification |
|||
* and adds the given innerComponent as children to the component. |
|||
* @param {Component|Array<Component>} innerComponent |
|||
* @returns {Component} |
|||
*/ |
|||
childContext(innerComponent) { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent() |
|||
.childContext(innerComponent); |
|||
} |
|||
|
|||
/** |
|||
* Closes modifier chain and returns builder to define the next child of the component |
|||
* @returns {Component} |
|||
*/ |
|||
chainChild() { |
|||
return this._modifier[this._modMethod](this) |
|||
.toComponent() |
|||
.chainChild(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
# Upcoming Features/Changes |
|||
|
|||
- [ ] extend generator logic/possibilities (consider extensions logic) |
|||
- [ ] [browserify](https://browserify.org/)? |
|||
- [ ] potential frameworks |
|||
- [ ] [alpinejs](https://alpinejs.dev/) |
|||
- [ ] [lit](https://lit.dev/) |
|||
- [ ] [htmx](https://htmx.org/) |
|||
- [ ] [www](https://www.solidjs.com/) |
|||
- [ ] [nuxt](https://nuxt.com/) |
|||
- [ ] [vuejs](https://vuejs.org/) |
|||
- [ ] externalize generator and corresponding functions |
|||
- [ ] make the generator modular |
|||
- [ ] include several generator-options into frameworkConsole |
|||
- [ ] (re-) enable "packageWithoutFramework" |
|||
- [x] check/refactor/-implement the extStorage logic |
|||
- [ ] check naming and setup of PageBuilder-class |
|||
- [ ] move builder.page to PageBuilderdd |
|||
- [ ] Re-Implementation Alignment and Arrangement |
|||
- [ ] Introducing differentiation between element and text |
|||
- [ ] Externalize Alignment and Arrangement from component |
|||
- [ ] Check for inheritence of Arrange-, allignment for children |
|||
- [ ] unify data-compel attribute logic |
|||
- [ ] naming(s) of attributes and values |
|||
- [ ] functionallity |
|||
- [ ] the autonaming and setting compel-name |
|||
- [ ] usage of the name in functions (selector)s |
|||
- [ ] Check Sides, Padding, Margin etc. logic for consistency |
|||
- [ ] Check builder.asComponent and similar functions for logical consistency |
|||
- [ ] reevaluate compelgroups logic |
|||
- [ ] Common compel groups in context.js |
|||
- [ ] combine changelog and upcoming |
Loading…
Reference in new issue