From 3d91068e08a3ce6338bc95d34ba4fb5d0aeda9cc Mon Sep 17 00:00:00 2001 From: versustunez Date: Thu, 19 Aug 2021 13:43:41 +0200 Subject: [PATCH] init --- .gitignore | 693 +++++++++++++++++++ build/.gitignore | 2 + build/Readme.md | 91 +++ build/buildConfig.example.json | 30 + build/gulpfile.js | 20 + build/index.js | 10 + build/package.json | 29 + build/task/smartcss.js | 58 ++ build/task/smarticon.js | 53 ++ build/task/smartjs.js | 116 ++++ build/tools/buildConfig.js | 36 + build/tools/file-includer.js | 24 + build/tools/helperUnit.js | 57 ++ build/tools/iconSprite.js | 71 ++ buildConfig.json | 54 ++ public/favicon.ico | Bin 0 -> 15086 bytes public/favicon/android-chrome-192x192.png | Bin 0 -> 1108 bytes public/favicon/android-chrome-512x512.png | Bin 0 -> 2507 bytes public/favicon/apple-touch-icon.png | Bin 0 -> 1115 bytes public/favicon/favicon-16x16.png | Bin 0 -> 311 bytes public/favicon/favicon-32x32.png | Bin 0 -> 407 bytes public/favicon/mstile-150x150.png | Bin 0 -> 890 bytes public/favicon/safari-pinned-tab.svg | 1 + public/index.html | 36 + src/app.path | 10 + src/app/Core/Config.js | 44 ++ src/app/Core/CustomElements.js | 257 +++++++ src/app/Core/EventHandler.js | 52 ++ src/app/Core/Math.js | 13 + src/app/Core/ModalController.js | 96 +++ src/app/Core/Network.js | 25 + src/app/Core/Notification.js | 87 +++ src/app/Core/PreLoader.js | 18 + src/app/Core/PrettyConsole.js | 112 +++ src/app/Core/Registry.js | 20 + src/app/Core/Template.js | 33 + src/app/External/SelectJs.js | 210 ++++++ src/app/External/VCollapse.js | 72 ++ src/app/External/VRipple.js | 77 +++ src/app/External/VTepL/Core.js | 70 ++ src/app/External/VTepL/Interpreter.js | 205 ++++++ src/app/External/VTepL/Parser.js | 208 ++++++ src/app/External/VTepL/Template.js | 26 + src/app/External/VUtils.js | 147 ++++ src/app/External/external.path | 8 + src/app/app.js | 28 + src/app/startup.js | 173 +++++ src/icons/.gitkeep | 0 src/icons/close.svg | 3 + src/icons/error.svg | 1 + src/icons/help-circle.svg | 6 + src/icons/info.svg | 4 + src/icons/success.svg | 4 + src/icons/warning.svg | 5 + src/loader.path | 5 + src/theme/_base.scss | 44 ++ src/theme/_loading.scss | 60 ++ src/theme/_scrollbar.scss | 42 ++ src/theme/_variable.scss | 31 + src/theme/components/_btn.scss | 150 ++++ src/theme/components/_collapse.scss | 85 +++ src/theme/components/_color.scss | 29 + src/theme/components/_input.scss | 111 +++ src/theme/components/_modal.scss | 79 +++ src/theme/components/_notification-look.scss | 141 ++++ src/theme/components/_range.scss | 94 +++ src/theme/components/_select.scss | 87 +++ src/theme/gui/_graph.scss | 63 ++ src/theme/gui/_header.scss | 44 ++ src/theme/gui/_page.scss | 11 + src/theme/main.scss | 19 + 71 files changed, 4490 insertions(+) create mode 100644 .gitignore create mode 100644 build/.gitignore create mode 100644 build/Readme.md create mode 100644 build/buildConfig.example.json create mode 100644 build/gulpfile.js create mode 100644 build/index.js create mode 100644 build/package.json create mode 100644 build/task/smartcss.js create mode 100644 build/task/smarticon.js create mode 100644 build/task/smartjs.js create mode 100644 build/tools/buildConfig.js create mode 100644 build/tools/file-includer.js create mode 100644 build/tools/helperUnit.js create mode 100644 build/tools/iconSprite.js create mode 100644 buildConfig.json create mode 100755 public/favicon.ico create mode 100755 public/favicon/android-chrome-192x192.png create mode 100755 public/favicon/android-chrome-512x512.png create mode 100755 public/favicon/apple-touch-icon.png create mode 100755 public/favicon/favicon-16x16.png create mode 100755 public/favicon/favicon-32x32.png create mode 100755 public/favicon/mstile-150x150.png create mode 100755 public/favicon/safari-pinned-tab.svg create mode 100644 public/index.html create mode 100644 src/app.path create mode 100644 src/app/Core/Config.js create mode 100644 src/app/Core/CustomElements.js create mode 100644 src/app/Core/EventHandler.js create mode 100644 src/app/Core/Math.js create mode 100644 src/app/Core/ModalController.js create mode 100644 src/app/Core/Network.js create mode 100644 src/app/Core/Notification.js create mode 100644 src/app/Core/PreLoader.js create mode 100644 src/app/Core/PrettyConsole.js create mode 100644 src/app/Core/Registry.js create mode 100644 src/app/Core/Template.js create mode 100644 src/app/External/SelectJs.js create mode 100644 src/app/External/VCollapse.js create mode 100644 src/app/External/VRipple.js create mode 100644 src/app/External/VTepL/Core.js create mode 100644 src/app/External/VTepL/Interpreter.js create mode 100644 src/app/External/VTepL/Parser.js create mode 100644 src/app/External/VTepL/Template.js create mode 100644 src/app/External/VUtils.js create mode 100644 src/app/External/external.path create mode 100644 src/app/app.js create mode 100644 src/app/startup.js create mode 100644 src/icons/.gitkeep create mode 100644 src/icons/close.svg create mode 100644 src/icons/error.svg create mode 100644 src/icons/help-circle.svg create mode 100644 src/icons/info.svg create mode 100644 src/icons/success.svg create mode 100644 src/icons/warning.svg create mode 100644 src/loader.path create mode 100644 src/theme/_base.scss create mode 100644 src/theme/_loading.scss create mode 100644 src/theme/_scrollbar.scss create mode 100644 src/theme/_variable.scss create mode 100644 src/theme/components/_btn.scss create mode 100644 src/theme/components/_collapse.scss create mode 100644 src/theme/components/_color.scss create mode 100644 src/theme/components/_input.scss create mode 100644 src/theme/components/_modal.scss create mode 100644 src/theme/components/_notification-look.scss create mode 100644 src/theme/components/_range.scss create mode 100644 src/theme/components/_select.scss create mode 100644 src/theme/gui/_graph.scss create mode 100644 src/theme/gui/_header.scss create mode 100644 src/theme/gui/_page.scss create mode 100644 src/theme/main.scss diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7233fdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,693 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/node,intellij+all,intellij,visualstudio,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=node,intellij+all,intellij,visualstudio,visualstudiocode + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Nuget personal access tokens and Credentials +nuget.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools + +# Local History for Visual Studio Code + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/node,intellij+all,intellij,visualstudio,visualstudiocode diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/build/Readme.md b/build/Readme.md new file mode 100644 index 0000000..117cd06 --- /dev/null +++ b/build/Readme.md @@ -0,0 +1,91 @@ +# Usage + +*NOTES* +THIS NEED TO BE INSTALLED IN A SUBFOLDER! + +Copy buildConfig.example.json to parent directory and rename it to buildConfig.json + +Compiler Flags in the JS Tree are from "Google Closure Compiler" + +# Install +``` +git clone https://git.vstz.dev/versustunez/gulp-dynamic.git build +cd build +npm install +cp buildConfig.example.json ../buildConfig.json +``` + +# Config +### Variables +| Key | VALUE | +|-------|---------------------| +| $dir | ROOT DIR | +| $src | SRC FROM Config | +| $out | OUT FROM CONFIG | +| $name | NAME KEY FROM BLOCK | +| $ | BLOCK INPUT VALUE | +|-------|---------------------| + +### ROOT +| Key | isRequired | TYPE | +|------|------------|--------| +| src | YES | STRING | +| out | YES | STRING | +| js | NO | ARRAY | +| scss | NO | ARRAY | + +### JS-Array +| Key | isRequired | TYPE | DEFAULT | +|------------|------------|--------|-----------------------| +| name | YES | STRING | - | +| minify | NO | BOOL | false | +| onlyMinify | NO | BOOL | false | +| input | YES | STRING | - | +| output | YES | STRING | - | +| files | YES | ARRAY | - | +| compiler | NO | OBJECT | #JS-Compiler Settings | + +### JS-Compiler Settings +| Key | isRequired | TYPE | DEFAULT | +|-------------------|------------|--------|-----------------------| +| compilation_level | NO | STRING | SIMPLE | +| warning_level | NO | STRING | VERBOSE | +| language_in | NO | STRING | ECMASCRIPT6_STRICT | +| language_out | NO | STRING | ECMASCRIPT6_STRICT | +| js_output_file | NO | STRING | $name.min.js | + +*NOTES* SEE: [Google Compiler Flags](https://github.com/google/closure-compiler/wiki/Flags-and-Options) + +## Example Config +```json +{ + "src": "$dir/src", + "out": "$dir/public/out", + "js": [ + { + "name": "main", + "minify": true, + "onlyMinify": true, + "input": "$src/js/", + "output": "$out/js/", + "files": [ + "app.js" + ], + "compiler": { + "compilation_level": "ADVANCED", + "warning_level": "VERBOSE", + "language_in": "ECMASCRIPT6_STRICT", + "language_out": "ECMASCRIPT6_STRICT", + "js_output_file": "$name.min.js" + } + } + ], + "scss": [ + { + "name": "main", + "input": "$src/theme/**/*.scss", + "output": "$out/theme/$name" + } + ] +} +``` diff --git a/build/buildConfig.example.json b/build/buildConfig.example.json new file mode 100644 index 0000000..b0aed61 --- /dev/null +++ b/build/buildConfig.example.json @@ -0,0 +1,30 @@ + { + "src": "$dir/src", + "out": "$dir/public/out", + "js": [ + { + "name": "main", + "minify": true, + "onlyMinify": true, + "input": "$src/js/", + "output": "$out/js/", + "files": [ + "app.js" + ], + "compiler": { + "compilation_level": "ADVANCED", + "warning_level": "VERBOSE", + "language_in": "ECMASCRIPT6_STRICT", + "language_out": "ECMASCRIPT6_STRICT", + "js_output_file": "$name.min.js" + } + } + ], + "scss": [ + { + "name": "main", + "input": "$src/theme/**/*.scss", + "output": "$out/theme/$name" + } + ] +} diff --git a/build/gulpfile.js b/build/gulpfile.js new file mode 100644 index 0000000..50e9357 --- /dev/null +++ b/build/gulpfile.js @@ -0,0 +1,20 @@ +const gulp = require('gulp'), + config = require('./tools/buildConfig'), + smartJS = require('./task/smartjs'), + smartIcon = require('./task/smarticon'), + smartCss = require('./task/smartcss'); + +config.prepare(); +smartJS.prepare(config); +smartCss.prepare(config); +smartIcon.prepare(config); + +gulp.task('watchMe', () => { + smartJS.startWatch(); + smartCss.startWatch(); + smartIcon.startWatch(); +}); + +gulp.task('default', gulp.parallel([...smartJS.build(), ...smartCss.build(), ...smartIcon.build()])); + +gulp.task('watch', gulp.parallel(['watchMe', ...smartJS.build(), ...smartCss.build(), ...smartIcon.build()])); diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..b6cf764 --- /dev/null +++ b/build/index.js @@ -0,0 +1,10 @@ +const gulp = require('gulp-cli'), + shouldWatch = process.argv.length > 2 && process.argv[2] === 'watch'; + +gulp() +require('./gulpfile') +if (shouldWatch) { + gulp.series('watchMe')(); +} else { + gulp.series('default')(); +} diff --git a/build/package.json b/build/package.json new file mode 100644 index 0000000..2b26bcc --- /dev/null +++ b/build/package.json @@ -0,0 +1,29 @@ +{ + "name": "v-build", + "version": "1.0.0", + "description": "A Small Gulp Build System for Dynamic JS and SCSs", + "main": "index.js", + "scripts": { + "build-nm": "./node_modules/gulp-cli/bin/gulp.js", + "watch-nm": "./node_modules/gulp-cli/bin/gulp.js watch", + "build": "gulp", + "watch": "gulp watch" + }, + "keywords": [ + "GULP", + "JS", + "SCSS" + ], + "author": "Maurice Grönwoldt", + "license": "GPL-3.0-or-later", + "dependencies": { + "google-closure-compiler": "^20210202.0.0", + "gulp": "^4.0.2", + "gulp-clean-css": "^4.3.0", + "gulp-cli": "^2.3.0", + "gulp-concat": "^2.6.1", + "gulp-sass": "^4.1.0", + "node-fs-extra": "^0.8.2", + "svg-sprite": "^1.5.0" + } +} diff --git a/build/task/smartcss.js b/build/task/smartcss.js new file mode 100644 index 0000000..ebeddc9 --- /dev/null +++ b/build/task/smartcss.js @@ -0,0 +1,58 @@ +const gulp = require('gulp'), + HelperUnit = require('./../tools/helperUnit'), + sass = require('gulp-sass'), + clean = require('gulp-clean-css'), + path = require('path'), + workers = []; + +class SCSSWorker { + constructor(config, helper) { + this.config = config; + this.helper = helper.clone(); + this.input = this.helper.replaceVariables(this.config.input); + this.watchInput = '.' + this.helper.replaceAbsolutePath(this.input); + this.helper.addConfigItem("$name", this.config.name); + this.helper.addConfigItem("$watch", this.watchInput); + this.helper.addConfigItem("$", this.input); + this.out = this.helper.replaceVariables(this.config.output); + this.watch = this.helper.replaceVariables(this.config.watch || '$watch'); + gulp.task(this.taskName, this.work.bind(this)); + } + + startWatch() { + console.log(`[WATCH][${this.config.name}] >> ${this.watch}`); + gulp.watch(this.watch, gulp.series(this.taskName)) + } + + get taskName() { + return `scss-${this.config.name || 'unknown'}`; + } + + work() { + return gulp.src(this.input) + .pipe(sass().on('error', sass.logError)) + .pipe(clean()) + .pipe(gulp.dest(this.out)); + } +} + +module.exports = { + build: function () { + let array = []; + for (let worker of workers) { + array.push(worker.taskName); + } + return array; + }, + prepare: function (config) { + const scss = config.scss || []; + for (let scssItem of scss) { + workers.push(new SCSSWorker(scssItem, config.helper)); + } + }, + startWatch: function () { + for (let worker of workers) { + worker.startWatch(); + } + } +} diff --git a/build/task/smarticon.js b/build/task/smarticon.js new file mode 100644 index 0000000..6a4aaba --- /dev/null +++ b/build/task/smarticon.js @@ -0,0 +1,53 @@ +const gulp = require('gulp'), + builder = require('./../tools/iconSprite'), + workers = []; + +class IconWorker { + constructor(icon, helper) { + this.config = icon; + this.helper = helper.clone(); + this.input = this.helper.replaceVariables(this.config.input); + this.watchInput = '.' + this.helper.replaceAbsolutePath(this.input); + this.helper.addConfigItem("$name", this.config.name); + this.helper.addConfigItem("$watch", this.watchInput); + this.helper.addConfigItem("$", this.input); + this.out = this.helper.replaceVariables(this.config.output); + this.watch = this.helper.replaceVariables(this.config.watch || '$watch'); + gulp.task(this.taskName, this.work.bind(this)); + } + + startWatch() { + console.log(`[WATCH][${this.config.name}] >> ${this.watch}`); + gulp.watch(this.watch, gulp.series(this.taskName)) + } + + get taskName() { + return `icon-${this.config.name}`; + } + + work() { + builder.buildIconSprites(this); + return gulp.src(this.input) + } +} + +module.exports = { + build: function () { + let array = []; + for (let worker of workers) { + array.push(worker.taskName); + } + return array; + }, + prepare: function (config) { + const icons = config.icons || []; + for (let icon of icons) { + workers.push(new IconWorker(icon, config.helper)); + } + }, + startWatch: function () { + for (let worker of workers) { + worker.startWatch(); + } + } +} diff --git a/build/task/smartjs.js b/build/task/smartjs.js new file mode 100644 index 0000000..2cca809 --- /dev/null +++ b/build/task/smartjs.js @@ -0,0 +1,116 @@ +const closureCompiler = require('google-closure-compiler').gulp(), + concat = require('gulp-concat'), + gulp = require('gulp'), + path = require('path'), + fileInclude = require('./../tools/file-includer'), + workers = []; + +class JSWorker { + constructor(config, helper) { + this.config = config; + this.helper = helper.clone(); + if (!this.config.name) { + throw Error("Found Empty name for JS Job..."); + } + this.validateConfig(); + this.input = this.helper.replaceVariables(this.config.input); + this.watchInput = '.' + this.helper.replaceAbsolutePath(this.input); + this.helper.addConfigItem("$name", this.config.name); + this.helper.addConfigItem("$watch", this.watchInput) + this.helper.addConfigItem("$", this.input); + this.getCompilerConfig(); + this.out = this.helper.replaceVariables(this.config.output); + this.watch = this.helper.replaceVariables(this.config.watch || '$watch/**/*.js'); + if (this.config.includeFile) { + this.includeFile = `${this.input}/${this.config.includeFile}`; + } + this.prepareFiles() + this.path = path.parse(this.out); + this.path = this.path.dir + "/" + this.path.base + "/"; + gulp.task(this.taskName, this.work.bind(this)); + } + + validateConfig() { + let config = this.config; + config.onlyMinify = config.onlyMinify || false; + config.minify = config.minify || false; + if (!this.config.input || !this.config.output || !(this.config.files || this.config.includeFile)) { + throw Error("Invalid Config for: " + this.config.name + ")"); + } + } + + getCompilerConfig() { + this.compiler = { + compilation_level: 'SIMPLE', + warning_level: 'VERBOSE', + language_in: 'ECMASCRIPT6_STRICT', + language_out: 'ECMASCRIPT6_STRICT', + js_output_file: this.config.name + ".min.js" + } + if (this.config.compiler) { + this.compiler = Object.assign(this.compiler, this.config.compiler); + } + this.compiler.js_output_file = this.helper.replaceVariables(this.compiler.js_output_file); + } + + startWatch() { + console.log(`[WATCH][${this.config.name}] >> ${this.watch}`); + const watchArray = [this.watch]; + if (this.includeFile) { + watchArray.push(this.includeFile); + } + gulp.watch(watchArray, gulp.parallel([this.taskName])); + } + + get taskName() { + return `js-${this.config.name}`; + } + + prepareFiles() { + if (this.config.includeFile) { + this.files = fileInclude.findFiles(this.input, this.includeFile, this.helper); + } else { + this.files = []; + for (const file of this.config.files) { + this.files.push(this.input + "/" + this.helper.replaceVariables(file)); + } + } + } + + work() { + if (this.config.includeFile) { + this.prepareFiles(); + } + let d = gulp.src(this.files).pipe(concat(this.config.name + '.js')) + if (!this.config.onlyMinify) { + d.pipe(gulp.dest(this.path)); + } + if (this.config.minify) { + d.pipe(closureCompiler(this.compiler)).pipe(gulp.dest(this.path)); + } + return d; + } +} + +module.exports = { + build: function () { + let array = []; + for (let worker of workers) { + array.push(worker.taskName); + } + return array; + }, + prepare: function (config) { + // we load the json and parse it here + const js = config.js || []; + for (let jsConfig of js) { + const worker = new JSWorker(jsConfig, config.helper); + workers.push(worker); + } + }, + startWatch: function () { + for (let worker of workers) { + worker.startWatch(); + } + } +} diff --git a/build/tools/buildConfig.js b/build/tools/buildConfig.js new file mode 100644 index 0000000..a38f158 --- /dev/null +++ b/build/tools/buildConfig.js @@ -0,0 +1,36 @@ +const fs = require('fs'), + HelperUnit = require('./helperUnit'), + helper = new HelperUnit(), + config = {}; + +module.exports = { + config: config, + helper: helper, + prepare: function () { + if (!fs.existsSync(__dirname + '/../../buildConfig.json')) { + console.error("Cannot find Config JSON"); + process.exit(40); + } + const currentPath = process.cwd(); + const baseDir = currentPath + '/../'; + const data = JSON.parse(fs.readFileSync(currentPath + '/../buildConfig.json').toString()); + const src = data['src'].replace(/(\$dir)/gm, baseDir); + const out = data['out'].replace(/(\$dir)/gm, baseDir); + helper.setConfig({ + $dir: baseDir, + $out: out, + $src: src, + $rawDir: __dirname, + $forWatch: data['src'].replace(/(\$dir)/gm, '../') + }) + config.dir = baseDir; + config.src = src; + config.out = out; + config.js = data.js || []; + config.scss = data.scss || []; + config.icons = data.icons || []; + this.js = config.js; + this.scss = config.scss; + this.icons = config.icons; + } +} diff --git a/build/tools/file-includer.js b/build/tools/file-includer.js new file mode 100644 index 0000000..7083a6d --- /dev/null +++ b/build/tools/file-includer.js @@ -0,0 +1,24 @@ +const fs = require('fs'); + +function findFiles(dir, file, helper) { + file = helper.replaceVariables(file); + const d = fs.readFileSync(file).toString("UTF8"); + const split = d.split("\n"); + let files = []; + for (let file of split) { + if (file.startsWith("--") || file.trim() === "" || file.startsWith("#")) { + continue; + } + if (file.startsWith("@import")) { + let iFile = file.split("@import")[1].trim(); + files.push(...this.findFiles(dir, `${dir}/${iFile}.path`, helper)); + } else { + files.push(`${dir}/${helper.replaceVariables(file)}.js`); + } + } + return files; +} + +module.exports = { + findFiles +}; diff --git a/build/tools/helperUnit.js b/build/tools/helperUnit.js new file mode 100644 index 0000000..e424211 --- /dev/null +++ b/build/tools/helperUnit.js @@ -0,0 +1,57 @@ +let inConfig = { + $dir: __dirname, + $out: __dirname, + $src: __dirname, + $: __dirname +} + +class HelperUnit { + constructor(config = null) { + this.config = config || inConfig; + this.regEx = {}; + this.buildRegex(); + } + + static setGlobalConfig(config) { + inConfig = config; + } + + addConfigItem(name, value, regexOnly = false) { + if (!regexOnly) { + this.config[name] = value; + } + let replace = name.replace("$", "\\$"); + this.regEx[name] = new RegExp(`(${replace})`, "gm"); + } + + setConfig(config) { + this.config = config; + this.buildRegex(); + } + + buildRegex() { + const keys = Object.keys(this.config); + this.regEx = {}; + for (const key of keys) { + this.addConfigItem(key, null, true); + } + } + + replaceVariables(string) { + const keys = Object.keys(this.config); + for (const key of keys) { + string = string.replace(this.regEx[key], this.config[key]); + } + return string; + } + + replaceAbsolutePath(string) { + string = string.replace(this.config["$dir"], this.config["$forWatch"]) + return string; + } + + clone() { + return new HelperUnit(this.config); + } +} +module.exports = HelperUnit; diff --git a/build/tools/iconSprite.js b/build/tools/iconSprite.js new file mode 100644 index 0000000..52acdf2 --- /dev/null +++ b/build/tools/iconSprite.js @@ -0,0 +1,71 @@ +const path = require('path'), + SVGSpriter = require('svg-sprite'), + fs = require('fs'), + fsExtra = require('node-fs-extra'); + +let toMergeData = []; + +function buildIconSprites(config) { + config.spriter = { + shape: { + id: { + generator: function (name) { + return "vt-" + name.replace('.svg', ''); + } + } + }, + svg: { + xmlDeclaration: true, + doctypeDeclaration: true + }, + mode: { + symbol: true, + defs: true + } + } + + builder.init(config); + builder.generateFromIcons(); +} + +let builder = { + init: function (config) { + this.config = config; + }, + generateFromIcons: function () { + const spriter = new SVGSpriter(this.config.spriter); + this.readFiles(this.config.input, spriter); + spriter.compile(function (error, result) { + if (error !== null) { + throw new Error(error); + } + builder.writeData(result.symbol.sprite.contents.toString()); + }); + }, + + writeData: function (data) { + let out = builder.config.out + builder.config.config.name + ".svg"; + let dirname = path.dirname(out); + if (!fs.existsSync(dirname)) { + fsExtra.mkdirpSync(dirname); + } + fs.writeFileSync(out, data); + }, + + readFiles: function (dir, spriter) { + let files = fs.readdirSync(dir); + for (let file of files) { + const split = file.split('.'); + if (split[split.length - 1] === 'svg') { + let filePath = path.resolve(dir, file); + spriter.add( + filePath, + file, + fs.readFileSync(filePath, {encoding: 'utf-8'}) + ); + } + } + } +}; + +module.exports.buildIconSprites = buildIconSprites; diff --git a/buildConfig.json b/buildConfig.json new file mode 100644 index 0000000..3b0dd42 --- /dev/null +++ b/buildConfig.json @@ -0,0 +1,54 @@ +{ + "src": "$dir/src", + "out": "$dir/public/out", + "js": [ + { + "name": "app", + "minify": true, + "onlyMinify": true, + "input": "$src/app/", + "output": "$out/app/", + "watch": "../src/app/**/*.js", + "includeFile": "../app.path", + "compiler": { + "compilation_level": "SIMPLE", + "warning_level": "DEFAULT", + "language_in": "ECMASCRIPT_2018", + "language_out": "ECMASCRIPT_2018", + "js_output_file": "$name.min.js" + } + }, + { + "name": "loader", + "minify": true, + "onlyMinify": true, + "input": "$src/app/", + "output": "$out/app/", + "watch": "../src/app/**/*.js", + "includeFile": "../loader.path", + "compiler": { + "compilation_level": "SIMPLE", + "warning_level": "DEFAULT", + "language_in": "ECMASCRIPT_2018", + "language_out": "ECMASCRIPT_2018", + "js_output_file": "$name.min.js" + } + } + ], + "scss": [ + { + "name": "main", + "input": "$src/theme/**/*.scss", + "output": "$out/theme/$name", + "watch": "../src/theme/**/*.scss" + } + ], + "icons": [ + { + "name": "icon-sprite", + "input": "$src/icons/", + "output": "$out/", + "watch": "../src/icons/**/*.svg" + } + ] +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..07f434f80dee5c8747cdfa46a0e1dece8f293503 GIT binary patch literal 15086 zcmeI3ziL!b5XO%r5D>%#i5_btX!(o19PBf5Jk;xiBk1OkCTAP@)y0)apv5C{YUf&UbN z8@3l*BJ914pPRl=3U)SPo;Y1c^74AQepYSOl2pEQFR8U_yq7UHW-QSSGj17wE;y-& zFRv$^x~07frN$=uP?NqnVWX#K>VG$;7xtVc%};F4n0Z%?eO;YPb699Afg|;(J1wyT zIi))|YkvIf--emFrXuRl|04A+Ge5&AzlU-`Vs}XEo( z2m}IwKp+qZ1OkCTAP@)y0)ar_Zz3=pQA$@w&eVH1ZJwpNu7@Vq2d8pvP!CK$*{SPe zlgHaD^~rKdgX;E3*1wkO;ZjO~T?@Tq;)hGlQR3cYqyU$O@G9Cxm@Ajq`YXo;>bgPY-O6YOn{k{BDS8>n~q^ zU)V+Lnd3w+Y!G*$^T<&6&};?QXm>DRj}mD=zZYla$j=&8EpU}h5+8F(Cx)4j*mEk)q!{nx?3oy^`*ZH|I_IE3AP@)y0)c;o z!0Goh=lI;M<_5>rI()ca?HpFscJ+2x8TW_#EA!{5Gz#Va$vVbeUESkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?smm1^9%x`eYUYnYe)0p1-C*InI(GzhH(%zKWkR+OF>Y%@lc@fq^;3)5S5Q z;?~<4XZwW=C62Yv;-dF}n>`PLP3=ni;LFIcks?!1jB|KB)kncklm-eSf!|MYSf z>si|smnd)B!fEODa{Kk~OCB7U7wrO+JN>DXYmVTi$*g&tlAXGRc5|NVcg7~1=brOo z37czppQ2*4Ytfv#pH8zlE@ZNq$Z$t0ELq*vBBJwo!B$V@Mn5aDnOc!?%PtCwUbZ-Q zxh~)64AW2ki1`-WQ;x($g$WwhJ58>V%ko04N%py6BGcM5 zd4sG^_?M_2PZM0U@t!a5LhF^UKThS1`1I(Zqj^Bit3bEd6CN2H8QZQ3*RF8?#n+n8 z;%_RJpyu0iQ-0-CKaM%g2PQ~NK6ZU~Q9S!ImDj9!tm|jIiVdAPMJiPJK;8e-Ne^5u zF0%Z6(P6<_%j_R=+()xajE~RTec(V&_P%?C&!tzNEuXl#Ve6(T_jq;)TiJT;JD(P> zAIqYn_Vjh%hUa!qj-R?+%+A>D`Sa`CkI(qkcOJ@%)pxkP?rm$Ce!k`T-yt%+yO;Pi z98E~I*Yf-6WuedB&)u3kx8drN&^NUo-+cVDeDbTHRM8Eyw(PpEnI3mx|IV3rg;)Ay zv6z(}zppU&*VJuu_T2K=E*Pq?WtK^?<_EDR1(pA4J*ul~m@|25XhPn) zB~K-|zcL+WFUS#IVVrqOGjM%^YRUh<8#%XR`OG?I`DoqMSy5ZJb6%M>Wo^)!l2e@} z8#r_ByvtZvxX_eIai#Ifm!U@tUB0S?>WlYIve_OU^!DqDPTyFcRWfFa7|pKrK1;f^ zD8+r5$PKU7ZcoYZWpCwTJUm5MncOFD^3iMYn)`WJ&U_jH7(a*>Y3P! zhTKOktzK?;|Dan(vx36DHJwbwo)2d4yeDfj`GE50nQ#2(us+)8`QThp?!@?Wx=e5T z;u5|aZ{py&!^)H>v;T+mi<8xr@(ZOcF*kLgM0QTG2 zo^t{KurLJ!P>3+VTsu330UBtDw*-J^x#HWtB0>g5JK0zPwSDpnLXdW}cfr{QMGDiu zy~m_vMWKSlI5{{2VRD~ptvjj);{X6YZFkPnC1GM^ikUQFh)(xi7TwYdYI1-@f$Shl z=?&fmuNKUZZF~|!SC)eR`Mh!dub~I0Uzc1%?{yd(Gp=<)|2F6!<3QZb9DA}*pq|UB z+T5Mqp8s~9jzspiWO^m1K2B*U74?GWR#s2{(hj=C>mTDQ>~iS5*p}j9*5wyti-MB3 z>$F6!rQD}M`&@!R$#i%8`;IJqYA=2x`j_M!b;y_kE8IIyIqyhc@QmC=0}g8_3Zo85 zSlFIw+KgBq;>*q#%qMx}qTf_*%(U^iv%E;O-LY$&r7@pG1yUC<@nJOuW`Fd2za7Sh zuP`v#=`!jkQpYP9z$RYkHW9^d2woGIo}=X=5g!r_d}hV31ssqx3-mqY-<{M5yv=4W z6r`2ETZ{OBpq?hDyo86Dg548(Qn7lUfwFBOrh8)^|FWdq!1YbqkHf8o3+; z!qV1*Q+#<~0i1b)GWzzUkN)gjCaAFu%^Z#=Fwmzp<%_>&g3d`>vSz#|jL~PIME5Jp zGg-7i^O0$j)Z1X5BJ3mbGnzRystx>>`e-l4$OsaLB>1hY2;wQA0*i61Z$sdlRsMT- zmf{d1iGQ}&y=6t~VeHP;wyCB!%AemOziQu|kX?yuHYh!!jU)Egq6C|!w{80Q3B6R(R_rySXFBpuVsIdm0~ z#rEp?#<^{g{qaJBVYV)&5#Cj7m9FL|eQIkni>TlUohu(3MC5otiXsbbN0G9wH|LD( zLD%_e&E(WajPUdxmI-7u50&SCpyLO&%nxy+1hPSrczfS8N2@(QwIkxb{9g3f@^CZ7 zM|9(nfsVkW1Tin!YCjH@G{}@WWg~eJdVPoL%QShyLSnKtC~s3c>OAf)Xr`5e-P>OJ zo#!4p{j|7V7mi`TyS^(8tMn0}Y2K1+OBwclv-{@BkFCuYmox0^+hn1RL7U~-eX%g# zQb!Nbb5L5n3TH9nCbo(N#FdLkzK~H~&A1uTespie9i-{9rRudqeRk=}eSu#MkmXUK zP|rVGl-zZ@>G*X(FN^kCkx`;_y~_$?LarF9T<-Y;&FIN1?2?l_@#WGq)~(katW5159N1{@A|x!dnT(wW{gf0 zJa6hAeUd@qyi|D$$$WKUbr^Y(n^Ey@N2&~@DXopyz8OC;ac^!5c4MlY4MJzR9qoPb zvDqss>fY>b5F3aIAaaBIpZUf`l`rqK*Ma=2Kg{Q}H`lR}lO{QNVoQi zZd11^!57z0JW)Q9;MNK0_?ks~su<;sU1_=a=Tc>79dNo`IU$J_pH9@T)%}9Xl~vLN zdX)_$N%Czb!-siuEw*wNWkZ=pAFvhCC9=F?nPZY@aicTkzz7l% zUam(9v)x>`0=s`cXgREkY0T57KS;N*1txFtEv9R-1FKZPdciVcD_T&L6O3oL z>kb_zDm8PlZ?I%lT%BqCT|RaZa*8zjKj9^4jtTD753r8ZR&Im{?md7vRE?Ip9aH_B z&+P&|RHv+H-1!k}wOai91qlpZDs(qW_viW!4Z@7q`Dx&QG?TOZeP$A2#9eHP0&Zln zpRLgwu}XBUA_3qeEH>_)4HPpp67|#`(Iva!)|%E0==#~UXYQ=YukWMT{scvqKar(( zf7aEKNv#>sCdc5`)>y4rCClhCDT_ap6ATx7xio&Ha~84YyNIKj1|=hsK8Q#$W4QGK zUBb`5B*$XQGD(4VG2~l9Vw)d#01Xie6Gf;DcynUb^gcT|^61XsOx~8U(Gwv5AoJtT z^+t4H=peKC*N{|ikfuhJ?aE16D_uwfu5$DKlb?m)a(V*nC|0ihiiT0z;(+A|L;otYR z(!TCQ;Z&^yLa>Vw@R)eRm7P~nc`q33Y<&4jK z=3f3Z|7u#P0?U3qxb_zK5o{k@2l48|rF<#OqwxoTsyVluf2RNXG7k=>)_CAR3EPG_ zR)F~URRd>PfB_W!8%FKaS2$e=!idwTqO>{(LCb8YG}s+~3oN<)@ZLN;q5G0f3YGvG zx3N(Jb!5{VfWj-7->u(-=?;p~4jYmmUtunYvEv>{lWn9%7NmEvbI?*M2Tk4Gq+)=v zTANzB8uRCWd4GXt6i~TC4s4-28Sj?NjDCaP2}k|oGvfanF{LDseN(pUA!TvlEd{tG zxwywT`Nm@+qQV1$LN8-t5+g2Sf+Ax403e}+v%U`=>3aNl7ooeyZq5mSsED8R6Gy3_ z&KIB|D9kysUaLPurQQY8JvP$O*3%~6CfZH5rTyz9uqY!Hvu<^!@UPK!IEQn!Rz%8w E0974XPyhe` literal 0 HcmV?d00001 diff --git a/public/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png new file mode 100755 index 0000000000000000000000000000000000000000..aea74d11822cddf24c92330fa5a06b6eaa754f0c GIT binary patch literal 1115 zcmV-h1f=_kP)Px#Do{*RMgRZ*0002;^70D{3&Fv`K0ZEeZEfJ-;Gm$O zA|fIyD=UYGhgMcrva+(mRHE|$00V4EL_t(&-tCydYZE~f#)nXvK!S_n3xJyr=-yrlM|r=_-(#7l~oLQh3S#8Xd&dTEdz^kjPx5h0k1e~vRdvzs@Y zH-EsMQLoq4MP{e2 z2vs#ejj4-~sRFGi0| z1l+IJmyEsQyopM;qsKAXV_uRUA*%D+rwZBf?t6%M;j?~NW7%~+6Qa@+#^;FAifVy> zxVNW}O=b5W;?9D;j9!_=-IqP$r9o*f`dV4N6nffM%+7}8(WV;Rc;us@ZQHA3?{_SZ z*1#;pe(9TY>q1zsRJJ@5PJS0(PX@~6UEHaaN@aM>*{r9Uodxwy)JGPus$Wh&4&Ad_ zPY%L`Tv7^Q-!u950-toGMd78QeZu=%FOpOiwe%XfJ&_6ulQ}&K(byE2W2_PBHRV41cF6lU-25Wxo zi1SC=mGVgG?!W@hgaSz=GxKeu@#BtL+HF11Tad5wI*ylXPU-PzFL0srAM6U5Ev>y& zc1f=qVQE`1%SuaDC{3l=Nup$}4y6s^- z#2kS8}0prh{I?0coKan9whYK2hI+Rs{~u9w$r~890>)J21whsf=z$v>MX;gmqJ`x z?)kpblP8$;@-NG_Tjl=J893eN4Mpm(aNc+lU*2sPJoWy0UD4Q&?wN`~nf@g~5ClOG z1VIo4K@bE%5ClOG|C~Q8p#n!~yZXrh0038dR9JLUVRs;Ka&Km7Y-J#Hd2nSQX>fF7 z004NL;1lBNlUWF4Vg<9No?-(^aFzu51v7A&dPE85DD?+4I|Ks-ojhF}LpZJ{ zCn&HQ7;&_8v9WO(Y-rh~vRZYggn@zd?3qTI9ij#XyE=CnYD+MSY8vk})ZV6HV8F3q z^(HPY(FrNc44UWIn~sP7-3v4=yecH3Bq*_5p`a)~Ei)%op`@}PRUxyWB$Jc1ICAEQ%n|m} d4IT@;^cY@=3zmFxGMx(Yo~Nsy%Q~loCIIz3R|Nn7 literal 0 HcmV?d00001 diff --git a/public/favicon/favicon-32x32.png b/public/favicon/favicon-32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..eef4bd3091ca966e0349514f2c348bed7c55674c GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIBP0X`wFKAD9;CVo&~5tRy5!BZ0C7tG+xzOB!CrFwyQ;_-z@q zLp07OCrBJ}Xl!ikboA`>^z>xpXk^lskO(tWi(V-qt#4XZ&dTND|KN_)-herw0ULrg zC~;~mxOQOT1|E-M4g-S_b_U6wX_HwONvj)dFflOL(ZG}H6|?5Rj2u3pgu8s()UI%8 zykZgx$Y$U+h%z#8WD)ZZVBpqC5Ma)JcxUc34l|d=g#iUj%+-v`7cBS@z?Z}@zfF1x zXF|kvpbNvRLLy3n63Z0|it^Jkb5a#bDhpB-G7CzQ85k<&JpRPPQ5dG7amxSn8PBId z49v>hddb|v%EI20MVN&ZTpCOcr!XsT4pBILXBmi|@!Oa=yvHz|`gG;uunK z>+OxR`Lhiqju#raoKv}47I$M)T;}Ed3p}r8UkQCQYoE`~aFc)E)1~)2D0qi{eDmdR zj1z~heUAOH2`WHi(Se)s%%>ku=REr+x%2MBr+N9C4eR>yj~eAHcMW{izy5sd#x2%+ z)N502{4lNS^V!?VHuKioH$jo_t=hT7PVTvRLv89V;TO}r|IVMAUHtAz;9u?Iir4Em z3QH(d*K#d4ntgNcD)Y8|FG_@8$b9?Uo_BDU$ce&aX+^WMy{qEGj!nI{nak_D>h-l{ z6EtRIN+_HSxVidkyL5ri%f0!}cJ93Jt-`YK&rD`Jdxx^RiZy35?|pjrUVQbrzMWQf z30oL9eLI|-ZvFhl_v60Lf2}V5`|ah-GrhZ)@m;#7ai{#3bk=a{ z%3Ie&&Hi4NUw&6+;~DqoRd;%?$K5R}<_OIVW_-W)*VfNBuCA1s8FIDl=jrq5GcSGq z^>&lYxrNVX#Avn8KOcL1|E=FQW9?=*ugP+r-`-dL{qORHGAkpl{*v-Yp8KNog1_kU ziDi6uE0@Oftvq)zj^8&;oPX6lz9#}}qN269!%g1u{kV7DP;gCD^{U*>Y*IU3mlSz~ zt&J7Uw3$3bRsNw^9hT6mwr0%-CR}Ly1`mh=h=j{ zx85mjz0a!HUUK!;?Ah{i?*bp1_T8S~e4nv?_i~P%(R1rVO8Wh@${bHDlz(x@`Rdvm z1}nuDrq20SyN-*0=GKV5*KY&+QuF^Vl#h8*oKjiyJ>gI1=V#6rpPYE)-Ql#OX7Yzu zOFu7D%iKHn9)IHDC=AokIOTu(jOWuJ24-b$y<~1-Wnu5hBFw@HE)6D!Q<#-EhbWxB naplC3Ge=~Ou%B-5Sm33{@Jd{;RFK7 \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..fff8de1 --- /dev/null +++ b/public/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + Template + + + + + Loading + + 0% + + +
+ +
+ + + + + +
+ + + diff --git a/src/app.path b/src/app.path new file mode 100644 index 0000000..3d8cc71 --- /dev/null +++ b/src/app.path @@ -0,0 +1,10 @@ +@import External/external +Core/Template +Core/CustomElements +Core/Config +Core/Notification +Core/ModalController +Core/EventHandler +Core/Registry + +app diff --git a/src/app/Core/Config.js b/src/app/Core/Config.js new file mode 100644 index 0000000..99751f7 --- /dev/null +++ b/src/app/Core/Config.js @@ -0,0 +1,44 @@ +(() => { + class Config { + constructor() { + this._config = {}; + this.updateCB = {}; + } + + get data() { + return this._config; + } + + get(key, def = null) { + if (this._config[key] !== undefined) { + return this._config[key]; + } + this.set(key, def); + return def; + } + + getFloat(key, def = 0) { + return parseFloat(this.get(key, def)); + } + + getInt(key, def) { + return parseInt(this.get(key, def)); + } + + set(key, value) { + this._config[key] = value; + if (this.updateCB[key]) { + this.updateCB[key](value); + } + } + + onUpdate(s, cb) { + this.updateCB[s] = cb; + } + } + + moduleLoader.isModuleLoaded(['Registry'], () => { + window.config = new Config(); + registry.set('config', window.config); + }); +})(); diff --git a/src/app/Core/CustomElements.js b/src/app/Core/CustomElements.js new file mode 100644 index 0000000..1b1ce16 --- /dev/null +++ b/src/app/Core/CustomElements.js @@ -0,0 +1,257 @@ +(() => { + + class NavItem extends HTMLElement { + constructor() { + super() + if (!this.hasAttribute('tabindex')) { + this.setAttribute('tabindex', '0'); + } + } + + } + + class VInput extends HTMLElement { + constructor() { + super(); + let self = this; + self.id = self.id || VUtils.tempId(); + let val = self.innerHTML; + self.innerHTML = ''; + let input = self.input = self.createNew('input', {id: self.id}) + let label = self.createNew('label', {content: self.dataset.label}); + self.createNew('span', {classes: 'error', content: self.dataset.error}); + label.setAttribute('for', self.id); + input.type = self.getAttribute('type') || 'text'; + input.value = val.trim(); + input.required = self.hasAttribute('required'); + input.name = self.getAttribute('name'); + input.addMultiListener('change input', self.cb.bind(self)); + } + + get value() { + return this.input.value; + } + + connectedCallback() { + this.cb({currentTarget: this.input}, true); + } + + cb(e, noInvalid) { + let el = e.currentTarget + let errorMessage = $('.error-message', el.find('form')); + if (errorMessage) { + errorMessage.classList.add('hide') + } + let cl = this.classList; + if (el.value === "") { + cl.remove('focus') + } else { + cl.add('focus') + } + if (el.checkValidity()) { + cl.add('valid'); + cl.remove('invalid'); + } else { + if (!noInvalid) { + cl.remove('valid'); + cl.add('invalid'); + } + } + } + } + + class VSwitch extends HTMLElement { + constructor() { + super(); + const id = this.dataset.id || VUtils.tempId(); + $('input', this).id = id; + $('label', this).setAttribute('for', id); + } + } + + class VSlider extends HTMLElement { + constructor() { + super(); + this.input = $('input', this); + this.input.id = this.dataset.id || VUtils.tempId(); + this.input.addEventListener('input', this.changeValue.bind(this)); + this.cur = $('.current', this); + this.cur.addEventListener('input', this.onChange.bind(this)); + this.cur.addEventListener('keypress', this.onKey.bind(this)); + this.cur.addEventListener('focusout', this.onChange.bind(this)); + this.checkStepSize(); + this.minLabel = $('.min', this); + this.maxLabel = $('.max', this); + } + + checkStepSize() { + const stepSize = this.input.getAttribute('step'); + const max = parseFloat(this.input.getAttribute('max')); + const min = parseFloat(this.input.getAttribute('min')); + if (stepSize === "" && max - min <= 1) { + this.input.setAttribute('step', "0.05"); + } + } + + set value(value) { + this.input.value = value; + this.changeValue(); + this.dispatchEvent(new Event('input')); + } + + get value() { + return this.input.value; + } + + set max(value) { + this.input.max = value; + this.maxLabel.innerHTML = value; + } + + get max() { + return this.input.max; + } + + set min(value) { + this.input.min = value; + this.minLabel.innerHTML = value; + } + + get min() { + return this.input.min; + } + + onKey(evt) { + const code = evt.keyCode; + if (code === 44 || code === 46) return; + if (code < 48 || code > 57 || code === 13) evt.preventDefault(); + } + + changeValue() { + this.cur.innerText = this.input.value; + } + + onChange(e) { + if (e.type === 'focusout') { + this.cur.innerText = this.input.value; + return; + } + try { + const text = this.cur.innerText.trim(); + if (text === '') { + this.cur.innerText = 0; + } + let val = parseFloat(text.replace(",", ".")); + let min = parseFloat(this.input.getAttribute('min')); + let max = parseFloat(this.input.getAttribute('max')); + if (val < min) val = min; + if (val > max) val = max; + if (isNaN(val)) { + val = this.input.value; + } + let step = this.input.step || 1; + if (Math.floor(step) === step) { + val = Math.round(val); + } + this.input.value = val; + } catch (err) { + this.cur.innerText = this.input.value; + PrettyConsole.error(VSlider, err); + } + } + } + + class VColor extends HTMLElement { + constructor() { + super(); + this.input = $('input', this); + this.blob = $('.colorBlob', this); + this.addEventListener('input', this.onChange.bind(this)); + } + + onChange() { + requestAnimationFrame(() => { + this.blob.style.backgroundColor = this.input.value; + }); + } + + set value(value) { + this.input.value = value; + this.onChange(); + this.dispatchEvent(new Event('input')); + } + + get value() { + return this.input.value; + } + } + + class VCollapseHead extends HTMLElement { + constructor() { + super(); + if (!$('.btn-ripple', this)) { + this.createNew('div', { + classes: ['ripple'] + }); + } + } + } + + class GUIItem extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + requestAnimationFrame(() => { + this.update(); + }) + } + + update() { + let bindItem = $('[data-bind]', this); + if (bindItem && bindItem.dataset.bind && bindItem.dataset.bind.trim() !== '') { + let bind = bindItem.dataset.bind; + if (bindItem.nodeName === 'INPUT' && bindItem.type === 'checkbox') { + bindItem.checked = config.get(bind, bindItem.checked); + } else if (bindItem.nodeName === 'V-SELECT') { + this.initVSelect(bindItem, bind); + } else { + bindItem.value = config.get(bind, bindItem.value); + } + if (!bindItem._hasEvent) { + bindItem.addEventListener('input', () => { + if (bindItem.nodeName === 'INPUT' && bindItem.type === 'checkbox') { + config.set(bind, bindItem.checked); + } else if (bindItem.nodeName === 'V-SELECT') { + config.set(bind, bindItem.rawValue); + } else { + config.set(bind, bindItem.value); + } + bindItem._hasEvent = true; + }); + } + } + } + + initVSelect(bindItem, bind) { + let value = config.get(bind, bindItem.rawValue).toString().split(", "); + bindItem.options.forEach(item => { + if (value.indexOf(item.value) === -1) { + item.removeAttribute('selected'); + } else { + item.setAttribute('selected', value.indexOf(item.value) !== -1); + } + }) + } + } + + + customElements.define("v-input", VInput); + customElements.define("v-switch", VSwitch); + customElements.define("v-slider", VSlider); + customElements.define("v-color", VColor); + customElements.define("nav-item", NavItem); + customElements.define("v-gui-item", GUIItem); + customElements.define("v-collapse-head", VCollapseHead); +})(); diff --git a/src/app/Core/EventHandler.js b/src/app/Core/EventHandler.js new file mode 100644 index 0000000..7a66652 --- /dev/null +++ b/src/app/Core/EventHandler.js @@ -0,0 +1,52 @@ +(() => { + class EventHandler { + constructor() { + this.events = {}; + document.body.addDelegatedEventListener('click', '[data-event]', this.handleClickEvent.bind(this)) + } + + handleClickEvent(ev, el) { + this.send(el.dataset.event, el); + } + + addEvent(events, cb) { + let names = events.split(","); + for (let name of names) { + this.events[name.trim()] = cb; + } + } + + addEvents(obj) { + let keys = Object.keys(obj); + for (let key of keys) { + this.addEvent(key, obj[key]); + } + } + + handleEvent(event) { + let data = event.data; + if (!data.cmd) { + return false; + } + return this.send(data.cmd, data.data, data.status); + } + + // will send the request to internal events + send(name, data, status = 'success') { + if (this.events[name]) { + try { + this.events[name](data, status); + } catch (e) { + PrettyConsole.error(EventHandler, e.message); + } + return true; + } + return false; + } + } + + moduleLoader.registerInit(i => { + window.eventHandler = new EventHandler(); + moduleLoader.finishModule('EventHandler'); + }); +})(); diff --git a/src/app/Core/Math.js b/src/app/Core/Math.js new file mode 100644 index 0000000..bc5dee3 --- /dev/null +++ b/src/app/Core/Math.js @@ -0,0 +1,13 @@ +class VMath { + static lerp(a, b, f) { + return (a + (b - a) * f); + } + + static clamp(value, min, max) { + return value < min ? min : value > max ? max : value; + } + + static easeIn(a) { + return a * a * a; + } +} diff --git a/src/app/Core/ModalController.js b/src/app/Core/ModalController.js new file mode 100644 index 0000000..a8bdc3f --- /dev/null +++ b/src/app/Core/ModalController.js @@ -0,0 +1,96 @@ +class ModalController { + constructor() { + const self = this; + self.modalContainer = $('v-modal-container'); + self.observer = new MutationObserver(function () { + if (self.modalContainer.children.length > 0) { + self.modalContainer.classList.add('open'); + } else { + self.modalContainer.classList.remove('open'); + } + }); + const config = {attributes: false, childList: true, characterData: false}; + self.observer.observe(self.modalContainer, config); + } + + static create(header, content, closeButton = true) { + return registry.get('modal').createModal(header, content, closeButton); + } + + createModal(header, content, closeButton) { + return template.render('gui/modal', { + header: header, + content: content, + closeButton: closeButton ? '' : 'close' + }).then(node => { + this.modalContainer.appendChild(node); + if (closeButton) { + node.addDelegatedEventListener('click', '.close-modal', () => { + console.log("remove"); + node.remove(); + }) + } + return node; + }); + } +} + +class ConfirmDialog { + constructor() { + const self = this; + this.dissmiss = new Promise((resolve, reject) => { + self.resolve = resolve; + self.reject = reject; + }); + this.id = VUtils.tempId(); + } + + static async create(header, text, options) { + const dialog = new ConfirmDialog(); + const data = dialog.getContent(text, options); + const content = await template.renderOn('gui/dialog-confirm', data); + const modal = await ModalController.create(header, content, false); + modal.addDelegatedEventListener('click', '.modal-confirm-button', (ev, el) => { + dialog.close(el.dataset.value); + }); + dialog.modal = modal; + return dialog.onDismiss(); + } + + static async createBasic(header, text) { + return this.create(header, text, [ + { + type: 'error', + value: "0", + content: 'Nein', + }, { + type: 'primary', + value: "1", + content: 'Ja', + } + ]); + } + + getContent(text, options) { + return { + text: text, + options: options + } + } + + close(value) { + this.modal.remove(); + this.resolve(value); + } + + onDismiss() { + return this.dissmiss; + } +} + +(() => { + moduleLoader.isModuleLoaded(['Registry'], () => { + const modal = new ModalController(); + registry.set('modal', modal); + }) +})(); diff --git a/src/app/Core/Network.js b/src/app/Core/Network.js new file mode 100644 index 0000000..e53b1c6 --- /dev/null +++ b/src/app/Core/Network.js @@ -0,0 +1,25 @@ +class Network { + static getStaticUrl(path) { + if (!path.startsWith('/')) { + path = '/' + path; + } + return path; + } + + static async requestUrl(path) { + let url = this.getStaticUrl(path); + let raw = await fetch(url) + if (!raw.ok) { + return Promise.reject(`Failed to load: ${url}`); + } + return await raw.text(); + } + + static loadAll(array, richUrl) { + const promises = [] + for (const name of array) { + promises.push(this.requestUrl(richUrl + name)); + } + return Promise.allSettled(promises); + } +} diff --git a/src/app/Core/Notification.js b/src/app/Core/Notification.js new file mode 100644 index 0000000..1ba4b6d --- /dev/null +++ b/src/app/Core/Notification.js @@ -0,0 +1,87 @@ +class NotificationHandler { + constructor() { + this.outer = $('v-notification'); + this.toast = $('v-toast'); + this.notifications = []; + } + + static get instance() { + if (NotificationHandler._instance === undefined) { + NotificationHandler._instance = new NotificationHandler(); + } + return NotificationHandler._instance; + } + + static createNotification(message, type, time, toast = false) { + time = parseInt(time || "3000"); + let handler = NotificationHandler.instance, + notification = new NotificationItem(message, type, time, toast); + handler.notifications.push(notification); + notification.show(); + return notification; + } + + static createToast(message, type = 'info') { + let handler = NotificationHandler.instance, + notification = new NotificationItem(message, type, 2000, true); + handler.notifications.push(notification); + notification.show(); + return notification; + } +} + +class NotificationItem { + constructor(message, type, time, toast = false) { + this.outer = NotificationHandler.instance.outer; + this.toastCon = NotificationHandler.instance.toast; + this.message = message; + this.type = type; + this.time = time || 1000; + this.isRemoved = false; + this.toast = toast; + } + + async show() { + let self = this, + endless = self.time === -1; + self.updateContent(self.message); + if (!endless) { + setTimeout(this.remove.bind(this), self.time) + } + } + + async remove() { + if (this.isRemoved) { + return; + } + this.isRemoved = true; + const cont = this.toast ? this.toastCon : this.outer; + cont.removeChild($('#' + this.id)); + let not = NotificationHandler.instance.notifications, + index = not.indexOf(this); + not.splice(index, 1); + } + + updateContent(message) { + this.id = VUtils.tempId(); + let self = this, + isEndless = self.time === -1, + data = { + message: message, + type: self.type, + id: self.id + }; + if (isEndless && !self.toast) data.type += ' endless'; + const tpl = self.toast ? 'gui/toast' : 'gui/notification'; + const cont = self.toast ? self.toastCon : self.outer; + template.renderOn(tpl, data).then(html => { + cont.prepend(document.createRange().createContextualFragment(html)); + if (!self.toast) $(`#${self.id} .fade-bar`).style.animationDuration = isEndless ? '1000ms' : `${self.time + 1}ms`; + }); + } + + updateMessageOnly(message) { + let item = $(`#${this.id} .message`); + if (item) item.innerHTML = message; + } +} diff --git a/src/app/Core/PreLoader.js b/src/app/Core/PreLoader.js new file mode 100644 index 0000000..50edc75 --- /dev/null +++ b/src/app/Core/PreLoader.js @@ -0,0 +1,18 @@ +class PreLoader { + constructor() { + this.preloadData = {}; + moduleLoader.registerModule(PreLoader); + Network.requestUrl('/out/preload-list.json').then(data => { + this.preloadData = JSON.parse(data); + PrettyConsole.debug(PreLoader, this.preloadData) + moduleLoader.finishModule(PreLoader) + }).catch(err => { + moduleLoader.moduleErrored(PreLoader, err); + PrettyConsole.error(PreLoader, err); + }) + } + + getData(name) { + return this.preloadData[name] || []; + } +} diff --git a/src/app/Core/PrettyConsole.js b/src/app/Core/PrettyConsole.js new file mode 100644 index 0000000..52f8656 --- /dev/null +++ b/src/app/Core/PrettyConsole.js @@ -0,0 +1,112 @@ +class PrettyConsole { + static setColour(module, color) { + module = this.getModuleNameFromArgs([module]); + if (!this.colours) { + this.colours = {}; + } + this.colours[module] = color; + } + + static getColour(module) { + if (!this.colours) { + this.colours = {}; + } + if (!this.colours.hasOwnProperty(module)) { + this.colours[module] = this.rgbToHex(...this.hexToRgb(this.generateHexFromString(module))); + } + return this.colours[module]; + } + + static log(...args) { + const module = this.getModuleNameFromArgs(args); + if (args.length === 1 && !Array.isArray(args[0])) { + this.output(module, "#5c6bc0", args[0], 'log'); + } else { + if (args.length === 1) args = args[0]; + this.outputGroup(module, "#5c6bc0", args, 'log'); + } + } + + static error(...args) { + const module = this.getModuleNameFromArgs(args); + if (args.length === 1 && !Array.isArray(args[0])) { + this.output(module, "#c62828", args[0], 'error'); + } else { + if (args.length === 1) args = args[0]; + this.outputGroup(module, "#c62828", args, 'error'); + } + } + + static warn(...args) { + const module = this.getModuleNameFromArgs(args); + if (args.length === 1 && !Array.isArray(args[0])) { + this.output(module, "#f9a825", args[0], 'warn'); + } else { + if (args.length === 1) args = args[0]; + this.outputGroup(module, "#f9a825", args, 'warn'); + } + } + + static debug(...args) { + const module = this.getModuleNameFromArgs(args); + if (args.length > 1) { + this.outputGroup(module, '#e91e63', args, 'debug'); + } else { + this.output(module, '#e91e63', args[0], 'debug'); + } + } + + static output(module, style, message, type) { + const color = this.getColour(module); + if (typeof message === "object") { + console[type](`%c[${module}]`, `color:${color};font-weight: bold;`, message); + return; + } + console[type](`%c[${module}] %c ${message}`, `color:${color};font-weight: bold;`, `color: ${style}`); + } + + static outputGroup(module, style, messages, type) { + const color = this.getColour(module); + console.group(`%c===[${module}]===`, `color:${color};font-weight: bold;`); + for (let message of messages) { + if (type === 'debug' && typeof message === 'string') { + message = `%c${message}`; + console.debug(message, `color: ${style}`); + } else { + console[type](message); + } + } + console.groupEnd(); + } + + static generateHexFromString(string = "") { + let hash = 0, len = string.length; + for (let i = 0; i < len; i++) { + string.charCodeAt(i) + hash = ((hash << 5) - hash) + string.charCodeAt(i); + hash |= 0; // to 32bit integer + } + return Math.abs(hash).toString(16).slice(0, 6); + } + + static hexToRgb(hex) { + let bigint = parseInt(hex, 16); + let r = (bigint >> 16) & 255; + let g = (bigint >> 8) & 255; + let b = bigint & 255; + return [r, g, b]; + } + + static rgbToHex(r, g, b) { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + } + + static getModuleNameFromArgs(args) { + if (typeof args[0] === 'function') { + const x = args.shift(); + return x.name; + } + return 'App'; + } +} +PrettyConsole.setColour("APP", '#90ff20'); diff --git a/src/app/Core/Registry.js b/src/app/Core/Registry.js new file mode 100644 index 0000000..9643abb --- /dev/null +++ b/src/app/Core/Registry.js @@ -0,0 +1,20 @@ +(() => { + class Registry { + constructor() { + this.items = {}; + } + + get(name) { + return this.items[name]; + } + + set(name, item) { + this.items[name] = item; + } + } + + moduleLoader.registerInit(i => { + window.registry = new Registry(); + moduleLoader.finishModule('Registry'); + }) +})(); diff --git a/src/app/Core/Template.js b/src/app/Core/Template.js new file mode 100644 index 0000000..fbee4a2 --- /dev/null +++ b/src/app/Core/Template.js @@ -0,0 +1,33 @@ +const TemplateModule = "Template"; +(() => { + class Template { + constructor() { + moduleLoader.registerModule(TemplateModule); + this.onInit(); + } + + onInit() { + PrettyConsole.log(TemplateModule, "Initialize...") + window.template = new VTepLCore({ + path: "/out/tpl/", + cache: false // we want never caching :) i mean it's nice but horrible to update // maybe adding later checksum + }); + PrettyConsole.debug(TemplateModule, "Use Cache? " + template.cache) + // init requests + this.loadArray(preLoader.getData('tpl'), true).catch(console.error); + } + + async loadArray(files, moduleLoading = false) { + window.template.loadArray(files).then(() => { + if (moduleLoading) moduleLoader.finishModule(TemplateModule); + }).catch(err => { + PrettyConsole.error(TemplateModule, err); + if (moduleLoading) moduleLoader.moduleErrored(TemplateModule, err); + }); + } + } + + moduleLoader.registerInit(() => { + new Template(); + }) +})(); diff --git a/src/app/External/SelectJs.js b/src/app/External/SelectJs.js new file mode 100644 index 0000000..8c4faf8 --- /dev/null +++ b/src/app/External/SelectJs.js @@ -0,0 +1,210 @@ +(function () { + window._openVSelect = null; + + requestAnimationFrame(e => { + document.body.addEventListener('click', ev => { + if (window._openVSelect && ev.target.closest('v-select') !== window._openVSelect) { + window._openVSelect.toggle(false); + } + }) + }) + + class VSelectElement extends HTMLElement { + constructor() { + super(); + let self = this; + self.rawValue = ""; + self.setAttribute('tabindex', '0'); + requestAnimationFrame(() => { + self.update(); + }) + } + + get value() { + return this._value; + } + + set value(value) { + this._value = value; + this.dispatchEvent(new Event('input')); + } + + + get required() { + return this.hasAttribute('required'); + } + + set required(flag) { + this.toggleAttribute('required', Boolean(flag)); + } + + get name() { + return this.getAttribute('name'); + } + + set name(val) { + this.toggleAttribute('name', val); + } + + get form() { + return this.closest('form'); + } + + get options() { + return $$('v-options v-option', this); + } + + get selected() { + return $$('v-options v-option[selected]', this); + } + + connectedCallback() { + /* const selected = this.selected; + if (selected) return;*/ + const options = Array.prototype.slice.call(this.options); + const values = this.dataset.value ? this.dataset.value.split(",") : [""]; + if (values.length === 1 && values[0].trim() === '') { + return; + } + options + .filter(item => values.indexOf(item.getAttribute('value')) !== -1) + .forEach(item => item.setAttribute('selected', 'true')); + } + + update() { + let selected = [], + lbl = $('v-label', this), + fd = new FormData(); + this.selected.forEach(e => { + selected.push(e.innerText); + fd.append(this.name, e.value); + }) + if (lbl.attributeChangedCallback) { + lbl.attributeChangedCallback('value', '', selected.join(", ")); + } + this.isValid = !(this.required && selected.length === 0); + this.rawValue = selected.join(", "); + this.value = fd; + } + + checkValidity() { + return this.isValid; + } + + toggle(open) { + if (window._openVSelect && window._openVSelect.toggleSelect && open) { + window._openVSelect.toggleSelect(false); + } + const options = $('v-options', this); + if (!open || this.isOpen) { + options.style.maxHeight = '0'; + window._openVSelect = false; + this.isOpen = false; + this.update(); + } else { + options.focus(); + let height = 0, + children = options.children; + for (let i = 0; i < children.length; i++) { + height += children[i].offsetHeight; + } + options.style.maxHeight = height + 'px'; + window._openVSelect = this; + this.isOpen = true; + } + let l = $('v-label', this).classList; + if (this.isOpen) { + l.add('open'); + } else { + l.remove('open'); + } + } + } + + class VSelectOptionElement extends HTMLElement { + constructor() { + super(); + this._value = this.getAttribute('value'); + this.addEventListener('click', e => { + let parent = this.parentNode.parentNode, + select = !this.selected; + if (!parent.hasAttribute('multiple')) { + parent.toggle(false); + for (let item of parent.selected) { + if (item !== this) { + item.removeAttribute('selected'); + } + } + } + if (!this.disabled) { + this.attributeChangedCallback('selected', false, select, true); + this.parentNode.parentNode.update(); + } + }); + this.createNew('div', {classes: ['ripple']}); + } + + static get observedAttributes() { + return ['selected', 'disabled', 'value']; + } + + get value() { + return this._value; + } + + set value(value) { + this._value = value; + } + + attributeChangedCallback(name, oldValue, newValue, force) { + if (name === 'selected' && this.hasAttribute('disabled')) { + this.removeAttribute(name); + return; + } + if (name === 'disabled' && newValue === true && this.hasAttribute('selected')) { + this.attributeChangedCallback('selected', false, false); + } + + if (force) { + if (newValue) { + this.setAttribute(name, newValue); + } else { + this.removeAttribute(name); + } + } + this[name] = newValue; + } + } + + class VLabel extends HTMLElement { + constructor() { + super(); + this.span = this.createNew('span'); + this.createNew('div', {classes: ['ripple']}); + this.empty = this.getAttribute('empty') || ""; + this.span.innerHTML = this.getAttribute("value") || this.empty; + this.addEventListener('click', this.openPopUp.bind(this)); + } + + static get observedAttributes() { + return ['empty', 'value']; + } + + openPopUp() { + this.parentNode.toggle(true); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'value') { + this.span.innerHTML = newValue || this.empty; + } + this[name] = newValue; + } + } + + customElements.define("v-label", VLabel); + customElements.define("v-option", VSelectOptionElement); + customElements.define("v-select", VSelectElement); +})(); + + diff --git a/src/app/External/VCollapse.js b/src/app/External/VCollapse.js new file mode 100644 index 0000000..1102c6f --- /dev/null +++ b/src/app/External/VCollapse.js @@ -0,0 +1,72 @@ +(() => { + const defaultConfig = "{\"outer\":\"v-collapse\",\"listenTo\":\"body\",\"itemSelector\":\"v-collapse-item\",\"headSelector\":\"v-collapse-head\",\"contentSelector\":\"v-collapse-content\"}"; + + window.VCollapse = class VCollapse { + constructor(options = {}) { + this.options = Object.assign(options, JSON.parse(defaultConfig)); + this.initListener(); + } + + initListener() { + const self = this, + opt = self.options; + $(opt.listenTo).addDelegatedEventListener('click', opt.headSelector, (e, el) => { + self.toggleItem(el, false); + }); + this.resize(); + window.addEventListener('resize', this.resize.bind(this)); + } + + toggleItem(el, forceClose, noClose) { + if (!el._maxHeight) { + this.resize(); + } + if (!noClose && el.find(this.options.outer).hasAttribute('accordion')) { + this.closeAll(el); + } + let parent = el.find(this.options.itemSelector), + cont = $(this.options.contentSelector, parent), + cl = parent.classList; + if (cl.contains('open') || forceClose) { + cont.style.maxHeight = '0px'; + cl.remove('open'); + } else { + cont.style.maxHeight = parent._maxHeight + 'px'; + cl.add('open'); + } + } + + closeAll(el) { + const self = this, + opt = self.options, + items = $$(opt.headSelector, el.find(opt.outer)); + VUtils.forEach(items, e => { + if (e !== el) { + self.toggleItem(e, true, true); + } + }) + } + + calcHeight(el) { + let children = $(this.options.contentSelector, el).children, + height = 0; + VUtils.forEach(children, e => { + height += e.offsetHeight; + }) + el._maxHeight = height; + } + + resize() { + const self = this, + opt = self.options, + items = $$(opt.itemSelector, $(opt.listenTo)); + VUtils.forEach(items, e => { + self.calcHeight(e); + if (e.classList.contains('open')) { + $(opt.contentSelector, e).style.maxHeight = parent._maxHeight + 'px'; + } + }) + } + } + new VCollapse(); +})(); diff --git a/src/app/External/VRipple.js b/src/app/External/VRipple.js new file mode 100644 index 0000000..b01b71a --- /dev/null +++ b/src/app/External/VRipple.js @@ -0,0 +1,77 @@ +class VRipple { + constructor(options = {}) { + if (!VUtils.isUsed) { + throw "VRipply is only with Public VUtils usable!" + } + let self = this; + self.options = JSON.parse('{"classes":["ripple__effect"],"target":"body","selector":".ripple"}'); + VUtils.mergeOptions(self.options, options); + if (self.options.selector.indexOf("#") > -1) { + throw "ID's are not allowed as selector!"; + } + this.instanceCheck(); + this.ripples = []; + requestAnimationFrame(this.initHandler.bind(this)); + } + + instanceCheck() { + let opts = this.options; + const rawKey = [opts.target, opts.selector, opts.classes.join(".")].join(" "); + VRipple.instances = VRipple.instances || {}; + VRipple.instances[rawKey] = this; + } + + initHandler() { + let self = this; + let selector = self.options.selector; + let target = $(self.options.target); + target.addDelegatedEventListener('mousedown touchstart', selector, (e, el) => { + let pos = e.touches ? e.touches[0] : e; + let parent = el.parentNode; + let circle = el.createNew('span', self.options); + let bounds = parent.getBoundingClientRect(); + let x = pos.clientX - bounds.left; + let y = pos.clientY - bounds.top; + circle.style.top = y + 'px'; + circle.style.left = x + 'px'; + circle._mouseDown = true; + circle._animationEnded = false; + self.ripples.push(circle); + }); + document.body.addDelegatedEventListener('animationend', '.' + VUtils.get(self.options.classes, ''), self.rippleEnd.bind(self)) + if (!document.body._vRippleInit) { + document.body.addMultiListener('mouseup touchend mouseleave rippleClose', e => { + let keys = Object.keys(VRipple.instances); + for (let key of keys) { + for (let ripple of VRipple.instances[key].ripples) { + requestAnimationFrame(x => { + self.rippleEnd.bind(VRipple.instances[key])(e, ripple); + }); + } + } + }) + document.body._vRippleInit = true; + } + } + + rippleEnd(ev, el) { + const parent = el.parentNode; + if (parent) { + if (ev.type === 'animationend') { + el._animationEnded = true; + } else { + el._mouseDown = false; + } + if (!el._mouseDown && el._animationEnded) { + if (el.classList.contains('to-remove')) { + el.parentNode.removeChild(el); + this.ripples.splice(this.ripples.indexOf(el), 1) + } else { + el.classList.add('to-remove'); + } + } + } + } +} + +const rippler = new VRipple(); diff --git a/src/app/External/VTepL/Core.js b/src/app/External/VTepL/Core.js new file mode 100644 index 0000000..586325b --- /dev/null +++ b/src/app/External/VTepL/Core.js @@ -0,0 +1,70 @@ +class VTepLCore { + constructor({path, suffix, template, cache} = {}) { + this.templates = {}; + this.dir = path || '/tpl/'; + this.suffix = suffix || 'tpl'; + this.path = template || `${this.dir}%s.${this.suffix}`; + this.cache = cache === undefined ? true : cache; + } + + // Add download queue! + async loadTemplate(name) { + if (this.templates[name]) { + return null; + } + this.templates[name] = true; // small hack to prevent double loading :) + let path = this.path.replace('%s', name); + let data = await Network.requestUrl(path) + this.addTpl(name, data); + loader.addLoadedItems(1); + return null; + } + + async loadArray(names) { + loader.addToLoadItem(names.length) + const promises = [] + for (const name of names) { + promises.push(this.loadTemplate(name)); + } + await Promise.all(promises) + } + + addTpl(name, content) { + let temp = this.templates[name] = new VTepLTemplate(name, content, this); + temp.parseContent(this.cache); + } + + async renderOn(name, data) { + if (this.templates[name]) { + return await this.templates[name].render(data); + } + PrettyConsole.warn("VTepLRender", `Template: "${name}" dont exist`); + return ''; + } + + renderTo(element, name, data = {}) { + this.renderOn(name, data).then(html => { + element.innerHTML = html; + }) + } + + // gets a html node element + render(name, data = {}) { + return this.renderOn(name, data).then(html => { + const template = document.createElement('template'); + template.innerHTML = html.trim(); + return template.content.firstChild; + }) + } + + async renderToAsync(element, name, data = {}) { + return this.renderOn(name, data).then(html => { + element.innerHTML = html; + }) + } + + has(toRender) { + return this.templates[toRender] + } + +} diff --git a/src/app/External/VTepL/Interpreter.js b/src/app/External/VTepL/Interpreter.js new file mode 100644 index 0000000..0da2830 --- /dev/null +++ b/src/app/External/VTepL/Interpreter.js @@ -0,0 +1,205 @@ +class VTepLInterpreter { + constructor(parser, core) { + this.parser = parser; + this.data = []; + this.content = ''; + this.core = core; + } + + async render(data) { + let self = this; + self.data = data; + let newData = await self.interpreter(self.parser.parsed); + self.data = []; + return newData[0]; + } + + // This stuff is slow as fuck xD Optimize parser to create faster interpreter + async interpreter(parsed, index = 0) { + let self = this; + let types = VParserTypes; + let tplCont = ''; + for (let i = index; i < parsed.length; i++) { + let item = parsed[i], + content = item.content; + switch (item.type) { + case types.content: + tplCont += content; + break; + case types.variable: + tplCont += self.getVariable(content) + break; + case types.assign: + let data = content.split("="), + key = data.shift(); + self.setVariable(data.join("=").trim().replace(/"/g, ""), key.trim()); + break; + case types.forEach: + let d = await this.handleForEach(item, parsed, i); + i = d[0]; + tplCont += d[1]; + break; + case types.for: + let fd = await this.handleFor(item, parsed, i); + i = fd[0]; + tplCont += fd[1]; + break; + case types.if: + let id = await this.handleIf(item, parsed, i); + i = id[0]; + tplCont += id[1]; + break; + case types.include: + tplCont += await this.handleInclude(item); + break; + case types.ifEnd: + tplCont += content; + return [tplCont, i]; + case types.forEnd: + tplCont += content; + return [tplCont, i]; + default: + console.warn("Invalid Type found"); + break; + } + } + return [tplCont, parsed.length]; + } + + getVariable(variable, isEqualCheck = false) { + variable = variable.toString(); + if (!isEqualCheck) { + let v = variable.split("=="); + if (v.length > 1) { + return this.getEqual(v[0].trim(), v[1].trim()); + } + v = variable.split("?"); + if (v.length > 1) { + return this.getIsDefined(v[0].trim(), v[1].trim()); + } + v = variable.split("||"); + if (v.length > 1) { + return this.getDefault(v[0].trim(), v[1].trim()); + } + } + if (this.data[variable]) { + return this.data[variable]; + } + let split = variable.split("."), + prevVar = this.data; + for (let i = 0; i < split.length; i++) { + prevVar = prevVar[split[i]] || prevVar; + } + if (typeof prevVar === 'string') { + return prevVar; + } + if (typeof prevVar === 'number') { + return prevVar.toString(); + } + return ''; + } + + getEqual(variable1, variable2) { + let split = variable2.split("?"); + let var1 = this.getVariable(variable1, true); + let var2 = this.getVariable(split[0].trim(), true); + if (split.length > 1) { + let newSplit = split[1].split(":"); + let right = newSplit[1] || ''; + return var1 === var2 ? newSplit[0].trim() : right.trim(); + } + return var1 === var2 ? 'true' : 'false'; + } + + setVariable(value, variable) { + let c = this.getVariable(value); + if (c !== '') { + value = c; + } + this.data[variable] = value; + } + + async handleForEach(item, parsed, i) { + let content = item.content.split(" as "); + let root = this.getVariable(content[0].trim()); + let addTo = 0, + isInvalid = false; + if (root === '') { + isInvalid = true; + root = {invalid: "true"}; + } + if (typeof root === 'string') { + root = JSON.parse(root); + } + if (Array.isArray(root) && root.length === 0) { + root.push(""); + isInvalid = true; + } + let d = Object.keys(root), + raw = '', + varContent = content[1].trim().split(","); + for (let x of d) { + if (varContent.length === 2) { + this.setVariable(x, varContent[1]); + } + this.setVariable(root[x], varContent[0]); + let data = await this.interpreter(parsed, i + 1); + addTo = data[1]; + raw += data[0]; + } + if (isInvalid) { + raw = ''; + } + return [addTo, raw]; + } + + async handleInclude(item) { + let split = item.content.split(";"), + name = split.shift(), + data = {}; + await this.core.loadTemplate(name); + for (let item of split) { + let d = item.split("="), + index = d.shift(), + dat = d.join("="); + if (!dat.startsWith("\"")) { + dat = this.getVariable(dat); + } else { + dat = dat.replace(/"/g, ""); + } + + data[index] = dat; + } + return await this.core.renderOn(name, data); + } + + async handleFor(item, parsed, ind) { + let content = item.content.split(" as "), + addTo = 0, + count = content[0].trim().split(".."), + max = parseInt(count[1]), + min = parseInt(count[0]), + newContent = ''; + for (let i = min; i < max; i++) { + this.setVariable(i.toString(), content[1]); + let data = await this.interpreter(parsed, ind + 1); + addTo = data[1]; + newContent += data[0]; + } + return [addTo, newContent]; + } + + async handleIf(item, parsed, i) { + let data = await this.interpreter(parsed, i + 1); + return [data[1], data[0]]; + } + + getIsDefined(variable, value) { + return this.getVariable(variable, true) !== '' ? value : ''; + } + + getDefault(variable, value) { + let vars = this.getVariable(variable, true); + return vars !== '' ? vars : value; + } +} diff --git a/src/app/External/VTepL/Parser.js b/src/app/External/VTepL/Parser.js new file mode 100644 index 0000000..d2e977a --- /dev/null +++ b/src/app/External/VTepL/Parser.js @@ -0,0 +1,208 @@ +const VParserTypes = { + content: 0, + variable: 1, + for: 2, + forEach: 3, + forContent: 4, + forEnd: 5, + if: 6, + ifContent: 7, + ifEnd: 8, + assign: 9, + include: 10, + none: -1, +}; + + +class VTpeLParser { + constructor(name, content) { + let self = this; + self.name = name; + self.legex = content.trim(); + self.index = 0; + self.content = ''; + self.parsed = []; + self.contexts = [0]; + self.line = 1; + self.charPos = 1; + } + + tokenize() { + let self = this; + for (self.index = 0; self.index < self.legex.length; self.index++) { + let i = self.index, + char = self.legex.charAt(i); + self.charPos++; + if (char === "\n") { + self.line++; + self.charPos = 0; + } + if (self.nextContains('/* ', i, true)) { + self.extract(' */', VParserTypes.none) + } else if (self.nextContains('// ', i, true)) { + self.extract("\n", VParserTypes.none); + } else if (self.nextContains('', VParserTypes.none); + } else if (self.nextContains('{for(', i, true)) { + self.extract(')}', VParserTypes.for); + self.contexts.push(VParserTypes.for); + } else if (self.nextContains('{include(', i, true)) { + self.extract(')}', VParserTypes.include); + self.contexts.push(VParserTypes.include); + } else if (self.nextContains('{foreach(', i, true)) { + self.extract(')}', VParserTypes.forEach); + self.contexts.push(VParserTypes.forEach); + } else if (self.nextContains('{/for}', i, true)) { + self.addType(VParserTypes.forEnd); + self.contexts.pop(); + } else if (self.nextContains('{if(', i, true)) { + self.extract(')}', VParserTypes.if); + self.contexts.push(VParserTypes.if); + } else if (self.nextContains('{/if}', i, true)) { + self.addType(VParserTypes.ifEnd); + self.contexts.pop(); + } else if (self.nextContains('$${', i, true)) { + self.extract('}', VParserTypes.assign); + } else if (self.nextContains('${', i, true)) { + self.extract('}', VParserTypes.variable); + } else { + self.content += char; + } + } + self.addType(VParserTypes.content); + return self.parsed; + } + + addType(type) { + let self = this; + let content = self.content.replace(/^\n+|\n+$/g, ''), + instructions = self.findInstructions(type); + self.content = ''; + if (type !== VParserTypes.none) { + if (type === VParserTypes.content && content === '') { + return null; + } + return self.parsed.push({ + content: content, + type: type, + context: self.contexts[self.contexts.length - 1], + instructions: instructions + }); + } + return null; + } + + nextContains(find, index, add = false) { + let count = this.nextContainsRaw(this.legex, find, index); + if (add && count > 0) { + this.index += count; + } + return count > 0 || count === -1; + } + + nextContainsRaw(raw, find, index) { + if (typeof find === "string") { + find = find.split(""); + } + let count = find.length; + if (count < 1) { + return -1; + } + for (let i = 0; i < count; i++) { + let nc = raw.charAt(index + i); + if ((find[i] === '\n' && nc === undefined)) { + return count; + } + if (find[i] !== nc) { + return 0; + } + } + return count; + } + + extract(findUntil = '}', type = 1) { + let self = this; + self.addType(0); + findUntil = findUntil.split("") + let content = '', + index = self.index, + legex = self.legex, + firstFind = findUntil.shift(); + for (let i = self.index; i < legex.length; i++) { + let char = legex.charAt(i); + if (char === firstFind && self.nextContains(findUntil, i + 1)) { + // PrettyConsole.debug("Parser", `[${index} > ${i}] >> ${content}`); + // console.debug(`[Parser][${index} > ${i}] >> ${content}`); + self.index = i + findUntil.length; + self.content = content.trim(); + self.addType(type); + return; + } + content += char; + } + if (firstFind === "\n") { + self.index = legex.length; + self.content = content.trim(); + self.addType(type); + return + } + throw `Template "${self.name}" Parsing Failed because variable at Position: ${index} <${self.line}:${self.charPos}> not closed!`; + } + + // @todo implement split operator Splitter + getOperators(string) { + let statements = []; + let innerCon = ''; + for (let i = 0; i < string.length; i++) { + let c = string.charAt(i); + if (innerCon === '' && c === ' ') { + continue; + } + if (c === '(') { + if (innerCon !== '') { + statements.push(this.parseOperator(innerCon)); + } + innerCon = ''; + for (let x = 1; x < string.length; x++) { + let char = string.charAt(i + x); + if (char === ')') { + i = i + x; + break; + } + innerCon += char; + } + statements.push(this.parseOperator(innerCon)); + innerCon = ''; + } else { + innerCon += c; + } + } + if (innerCon !== '') { + statements.push(this.parseOperator(innerCon)); + } + return statements; + } + + parseOperator(operatorString) { + return this.operator(operatorString); + } + + findInstructions(type) { + if (type === VParserTypes.if) { + return this.getOperators(this.content); + } + // @todo add support for assign, for, foreach and variables... can optimize interpreter times because we dont need to find it then + return []; + } + + // right needs to be a string or a operator and should not be null! + // left cant be null! this is the case if it's ! operator + operator(op, left, right) { + return { + type: op, + lvalue: left, + r: right + } + } + +} diff --git a/src/app/External/VTepL/Template.js b/src/app/External/VTepL/Template.js new file mode 100644 index 0000000..ef9772b --- /dev/null +++ b/src/app/External/VTepL/Template.js @@ -0,0 +1,26 @@ +class VTepLTemplate { + constructor(name, content, core) { + this.name = name; + this.tpl = content; + this.parser = new VTpeLParser(name, content); + this.core = core; + } + + async render(data = {}) { + return await new VTepLInterpreter(this.parser, this.core).render(data); + } + + parseContent(cache) { + if (cache) { + let storage = localStorage.getItem("vtepl-" + this.name); + if (storage) { + this.parser.parsed = JSON.parse(storage); + return; + } + } + this.parser.tokenize(); + if (cache) { + localStorage.setItem("vtepl-" + this.name, JSON.stringify(this.parser.parsed)); + } + } +} diff --git a/src/app/External/VUtils.js b/src/app/External/VUtils.js new file mode 100644 index 0000000..f353688 --- /dev/null +++ b/src/app/External/VUtils.js @@ -0,0 +1,147 @@ +class VUtils { + static makePublic() { + if (VUtils.isUsed) { + return; + } + moduleLoader.registerModule("VUtils") + this.initHandlers(); + VUtils.isUsed = true; + PrettyConsole.log("VUtils", "VUtils is now available in the Global Space! no VUtils. anymore needed"); + moduleLoader.finishModule("VUtils") + } + + static initHandlers() { + window.$ = this.$; + window.$$ = this.$$; + window.tryCatch = this.tryCatch; + VUtils.nodePrototypes(); + } + + static $(selector, from) { + from = from || document; + return from.querySelector(selector); + } + + static $$(selector, from) { + from = from || document; + return from.querySelectorAll(selector); + } + + static tryCatch(data, callback, error) { + data = VUtils.wrap(data, []); + error = error || console.error; + callback = callback || console.log; + try { + callback(...data); + } catch (e) { + error(e); + } + } + + static forEach(items, cb, error) { + for (let i = 0; i < items.length; i++) { + VUtils.tryCatch([items[i], i], cb, error); + } + } + + static get(valueOne, value) { + return this.wrap(valueOne, value); + } + + static mergeKeys(root, target) { + root = root || {}; + let keys = Object.keys(root); + for (let key of keys) { + target[key] = root[key]; + } + return target; + } + + static mergeOptions(target, root) { + root = root || {}; + let keys = Object.keys(root); + for (let key of keys) { + target[key] = VUtils.get(root[key], target[key]); + } + return target; + } + + static wrap(valueOne, valueTwo) { + let x = typeof valueTwo; + if (!(valueOne instanceof Array) && valueTwo instanceof Array) { + return [valueOne]; + } + if (x === 'string' && valueOne instanceof Array) { + return valueOne.join("."); + } + return valueOne === undefined ? valueTwo : valueOne; + } + + static tempId() { + return 'temp_' + Math.random().toString(36).substr(2, 16); + } + + static hexToRgb(hex) { + hex = hex.replace("#", ""); + let bigint = parseInt(hex, 16), + r = (bigint >> 16) & 255, + g = (bigint >> 8) & 255, + b = bigint & 255; + + return [r / 255, g / 255, b / 255]; + } + + static nodePrototypes() { + Node.prototype.find = function (selector) { + return this.closest(selector); + }; + Node.prototype.createNew = function (tag, options = {}) { + let el = document.createElement(tag); + if (options.classes) { + el.classList.add(...VUtils.get(options.classes, [])); + } + el.id = VUtils.get(options.id, ''); + el.innerHTML = VUtils.get(options.content, ""); + VUtils.mergeKeys(options.dataset, el.dataset); + if (VUtils.get(options.append, true) === true) { + this.appendChild(el); + } + + return el; + }; + Node.prototype.addDelegatedEventListener = function (type, aim, callback, err) { + if (!callback || !type || !aim) + return; + this.addMultiListener(type, (event) => { + let target = event.target; + if (event.detail instanceof HTMLElement) { + target = event.detail; + } + if (target instanceof HTMLElement) { + if (target.matches(aim)) { + VUtils.tryCatch([event, target], callback, err); + } else { + const parent = target.find(aim); + if (parent) { + VUtils.tryCatch([event, parent], callback, err); + } + } + } + }); + }; + Node.prototype.addMultiListener = function (listener, cb, options = {}) { + let splits = listener.split(" "); + for (let split of splits) { + this.addEventListener(split, cb, options); + } + }; + String.prototype.firstUpper = function () { + return this.charAt(0).toUpperCase() + this.slice(1); + }; + String.prototype.firstLower = function () { + return this.charAt(0).toLowerCase() + this.slice(1); + }; + } +} + +VUtils.makePublic(); diff --git a/src/app/External/external.path b/src/app/External/external.path new file mode 100644 index 0000000..e38e74f --- /dev/null +++ b/src/app/External/external.path @@ -0,0 +1,8 @@ +External/VTepL/Core +External/VTepL/Interpreter +External/VTepL/Parser +External/VTepL/Template +External/VUtils +External/VRipple +External/VCollapse +External/SelectJs diff --git a/src/app/app.js b/src/app/app.js new file mode 100644 index 0000000..c77ae4a --- /dev/null +++ b/src/app/app.js @@ -0,0 +1,28 @@ +class App { + async ignite() { + window.onerror = (err) => { + PrettyConsole.error(err); + } + moduleLoader.ignite(); + moduleLoader.finishModule('Startup'); + } + + finish() { + setTimeout(() => { + $('v-loading-screen').classList.add('hide'); + }, 300) + $('body').addDelegatedEventListener('click', '.menu-toggle', () => { + $('v-app').classList.toggle('open'); + }) + PrettyConsole.debug(App, moduleLoader.modules); + } +} + +const app = new App(); +moduleLoader.setCb(app.finish.bind(app)); +(() => { + app.ignite().catch(onerror => { + PrettyConsole.error(App, onerror); + alert("App Startup failed... open console to see details"); + }); +})(); diff --git a/src/app/startup.js b/src/app/startup.js new file mode 100644 index 0000000..8e99cc5 --- /dev/null +++ b/src/app/startup.js @@ -0,0 +1,173 @@ +const EVENT_MODULE_LOADED = "module-loaded"; + +class Startup { + constructor(modules) { + this.modules = modules; + this.cb = null; + this.isErrored = false; + this.isAllLoadedFired = false; + document.addEventListener(EVENT_MODULE_LOADED, this.allModulesLoaded.bind(this)); + } + + setCb(cb) { + this.cb = cb; + } + + setErrCb(cb) { + this.errCb = cb; + } + + finishModule(name) { + name = Startup.getName(name); + this.modules[name] = !0; + PrettyConsole.debug(name, "Module Loading Done"); + document.dispatchEvent(new CustomEvent(EVENT_MODULE_LOADED, {detail: name})); + } + + moduleErrored(name, error) { + name = Startup.getName(name); + if (this.errCb) { + this.errCb(name, error); + } + this.isErrored = true; + document.dispatchEvent(new CustomEvent(`module-errored`, { + data: {name, error} + })); + } + + isModuleLoaded(names, cb) { + let self = this; + if (typeof names === 'string') { + names = [names]; + } + let pendingModules = names.filter(function (module) { + return !self.modules[Startup.getName(module)] + }); + if (pendingModules.length > 0) { + function listener() { + document.removeEventListener(EVENT_MODULE_LOADED, listener); + self.isModuleLoaded.bind(self)(pendingModules, cb); + } + + document.addEventListener(EVENT_MODULE_LOADED, listener); + } else { + cb(); + } + } + + registerModule(name) { + this.modules[Startup.getName(name)] = false; + } + + allModulesLoaded() { + if (this.isErrored) { + return false; + } + if (this.isAllLoadedFired) { + return true; + } + for (let module in this.modules) { + if (this.modules.hasOwnProperty(module) && !this.modules[module]) { + return false; + } + } + this.isAllLoadedFired = true; + if (this.cb) { + this.cb(); + } + document.dispatchEvent(new CustomEvent('startupFin')); + return true; + } + + ignite() { + document.dispatchEvent(new CustomEvent('ignite')); + } + + registerInit(onInit) { + document.addEventListener('ignite', onInit); + } + + loadOtherJsFiles(files) { + for (let file of files) { + this.registerModule(file[1]); + let script = document.createElement('script'); + script.onload = () => { + moduleLoader.finishModule(file[1]); + } + script.onerror = (err) => { + moduleLoader.moduleErrored(file[1], err); + } + script.src = file[0]; + document.body.appendChild(script); + } + } + + static getName(name) { + if (typeof name === 'function') { + return name.name; + } + return name; + } +} + +class Loader { + constructor() { + document.addEventListener(EVENT_MODULE_LOADED, this.loadProgress.bind(this)); + document.addEventListener('startupFin', this.finish.bind(this)); + this.loadingText = document.querySelector('v-loading-text'); + this.loadingPerc = document.querySelector('v-loading-stats'); + this.loadingScreen = document.querySelector('v-loading-screen'); + this.loaded = 0; + this.toLoad = 0; + this.isErrored = false; + } + + addToLoadItem(i) { + this.toLoad += i; + } + + addLoadedItems(i) { + this.loaded += i; + this.loadProgress(); + } + + loadProgress() { + if (this.isErrored) return; + let loaded = 0, + keys = Object.keys(moduleLoader.modules); + for (let key of keys) { + if (moduleLoader.modules[key]) { + loaded++; + } + } + loaded += this.loaded; + let toLoad = keys.length + this.toLoad; + const perc = ((loaded / toLoad) * 100).toFixed(2) + "%"; + this.loadingScreen.style.setProperty('--loading-scale', perc); + this.loadingPerc.innerText = `${loaded} / ${toLoad} (${perc})`; + } + + onError(module, error) { + this.isErrored = true; + this.loadingText.innerText = "Errored"; + this.loadingPerc.innerHTML = `Module ${module} failed to load
${error}`; + this.loadingScreen.style.setProperty('--loading-color', '#fa0000'); + PrettyConsole.error("Startup", `Module ${module} failed to load`) + } + + finish() { + document.removeEventListener('module-loaded-progress', this.loadProgress.bind(this)); + } +} + +const moduleLoader = new Startup({ + 'Startup': false, +}); +const loader = new Loader(); +moduleLoader.setErrCb(loader.onError.bind(loader)); +const preLoader = new PreLoader(); +moduleLoader.isModuleLoaded('PreLoader', () => { + moduleLoader.loadOtherJsFiles([ + ['/out/app/app.min.js', 'app.js'], + ]); +}); diff --git a/src/icons/.gitkeep b/src/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/icons/close.svg b/src/icons/close.svg new file mode 100644 index 0000000..dd53990 --- /dev/null +++ b/src/icons/close.svg @@ -0,0 +1,3 @@ + + diff --git a/src/icons/error.svg b/src/icons/error.svg new file mode 100644 index 0000000..587a471 --- /dev/null +++ b/src/icons/error.svg @@ -0,0 +1 @@ + diff --git a/src/icons/help-circle.svg b/src/icons/help-circle.svg new file mode 100644 index 0000000..958dbbf --- /dev/null +++ b/src/icons/help-circle.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/src/icons/info.svg b/src/icons/info.svg new file mode 100644 index 0000000..df32735 --- /dev/null +++ b/src/icons/info.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/icons/success.svg b/src/icons/success.svg new file mode 100644 index 0000000..de19ded --- /dev/null +++ b/src/icons/success.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/icons/warning.svg b/src/icons/warning.svg new file mode 100644 index 0000000..fd7d1fe --- /dev/null +++ b/src/icons/warning.svg @@ -0,0 +1,5 @@ + diff --git a/src/loader.path b/src/loader.path new file mode 100644 index 0000000..11e230f --- /dev/null +++ b/src/loader.path @@ -0,0 +1,5 @@ +Core/PrettyConsole +Core/Network +Core/Math +Core/PreLoader +startup diff --git a/src/theme/_base.scss b/src/theme/_base.scss new file mode 100644 index 0000000..840b25a --- /dev/null +++ b/src/theme/_base.scss @@ -0,0 +1,44 @@ +* { + box-sizing: border-box; +} + +*:focus { + outline: none; +} + +html, body { + margin: 0; + padding: 0; + background-color: $bg; + color: #fff; + font-size: 16px; + font-family: 'Inter', sans-serif;; +} + +.hideAll { + display: none !important; +} + +.icon { + width: 1em; + height: 1em; + vertical-align: middle; + font-size: 1em; + shape-rendering: geometricPrecision; + transition: transform .5s cubic-bezier(.22, .61, .36, 1); + stroke-width: 5px; + text-align: center; + margin-right: .5rem; + + &.block { + display: block; + } +} + +v-content { + display: block; +} + +v-collapse-content .inner { + padding: 1rem; +} diff --git a/src/theme/_loading.scss b/src/theme/_loading.scss new file mode 100644 index 0000000..4683f80 --- /dev/null +++ b/src/theme/_loading.scss @@ -0,0 +1,60 @@ +@import "variable"; + +v-loading-screen { + background-color: #000; + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + z-index: 10000; + + &.hide { + pointer-events: none; + opacity: 0; + transition-delay: 1s; + transition: opacity 1s; + z-index: -1; + } + + v-loading-text { + font-family: monospace; + font-size: 4vw; + text-transform: uppercase; + } + + v-loading-stats { + font-family: monospace; + color: var(--loading-color, $primary); + display: block; + font-size: .75rem; + margin-top: .2rem; + height: 1.5rem; + letter-spacing: .01rem; + text-align: center; + transition: color .5s ease-in; + } + + v-loader { + position: relative; + display: block; + width: 30vw; + height: 1vw; + background-color: $loader-delay-color; + + &::after { + content: ''; + width: var(--loading-scale, 0); + height: 100%; + transition: all .5s ease-in; + background-color: var(--loading-color, $primary); + position: absolute; + top: 0; + left: 0; + } + } +} diff --git a/src/theme/_scrollbar.scss b/src/theme/_scrollbar.scss new file mode 100644 index 0000000..716abd9 --- /dev/null +++ b/src/theme/_scrollbar.scss @@ -0,0 +1,42 @@ + +::-webkit-scrollbar { + width: 3px; + height: 3px; +} + +::-webkit-scrollbar-button { + width: 15px; + height: 15px; +} + +::-webkit-scrollbar-thumb { + background: #e1e1e1; + border: 0 none #ffffff; + border-radius: 100px; +} + +::-webkit-scrollbar-thumb:hover { + background: #ffffff; +} + +::-webkit-scrollbar-thumb:active { + background: $primary; +} + +::-webkit-scrollbar-track { + background: #666666; + border: 0 none #ffffff; + border-radius: 46px; +} + +::-webkit-scrollbar-track:hover { + background: #666666; +} + +::-webkit-scrollbar-track:active { + background: #666666; +} + +::-webkit-scrollbar-corner { + background: transparent; +} \ No newline at end of file diff --git a/src/theme/_variable.scss b/src/theme/_variable.scss new file mode 100644 index 0000000..52c0958 --- /dev/null +++ b/src/theme/_variable.scss @@ -0,0 +1,31 @@ +$bg: #303030; +$darker: #212121; +$transparentDark: rgba(33, 33, 33, 0.39); +$nav: #1b1b1b; + +$primary: #3949ab; +$second: #ff0089; +$active: #4dbb5d; + +$loader-delay-color: rgb(1, 21, 29); + +$hoverDark: rgba(0,0,0,.6); + +$successBorder: #39ab48; +$errorBorder: #a70101; +$warningBorder: #ff7700; + +$box-shadow-1: 0 .3rem .34rem rgba(0, 0, 0, 0.16), 0 .3rem .34rem rgba(0, 0, 0, 0.23); +$box-shadow-2: 0 .3rem .4rem rgba(0, 0, 0, 0.16), 0 .3rem .4rem rgba(0, 0, 0, 0.23); + +$textColor: #fff; +$textColorLessBrightness: #dcdcdc; +$textColorMoreLessBrightness: #aaa; + +$surfaceOnColor: #e9e9e9; +$errorInYourFace: #f32f32; +$errorInYourFaceBorder: #cf1b1b; +$errorColor: #c51162; +$errorOnColor: #8e0038; +$validColor: #39ab48; +$validOnColor: #39ab48; diff --git a/src/theme/components/_btn.scss b/src/theme/components/_btn.scss new file mode 100644 index 0000000..f6e5561 --- /dev/null +++ b/src/theme/components/_btn.scss @@ -0,0 +1,150 @@ +.btn { + font-size: inherit; + border: none; + background: $primary radial-gradient(circle at 0px 0px, #3949ab 0%, rgba(0, 0, 0, .2) 100%) no-repeat; + color: #fff; + padding: 0.3em 1.1em; + margin: .2em 0; + cursor: pointer; + position: relative; + user-select: none; + border-radius: .1rem; + box-shadow: $box-shadow-1; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + transition: background-color .5s, box-shadow .5s; + + &:focus { + box-shadow: $box-shadow-2; + } + + &--empty { + background: transparent; + } + + &--outline { + background: transparent; + border: 1px solid $primary; + } + + &--space { + margin-top: .5rem; + margin-bottom: .5rem; + } + + &[disabled] { + background: $hoverDark !important; + color: #303030 !important; + pointer-events: none; + } + + &--valid { + background: $validColor radial-gradient(circle at 0px 0px, $validColor 0%, rgba(0, 0, 0, .2) 100%) no-repeat; + } + + &--accent { + background: $second radial-gradient(circle at 0px 0px, $second 0%, rgba(0, 0, 0, .2) 100%) no-repeat; + } + + &--warn { + background: $warningBorder radial-gradient(circle at 0px 0px, $warningBorder 0%, rgba(0, 0, 0, .2) 100%) no-repeat; + } + &--error { + background: $errorBorder radial-gradient(circle at 0px 0px, $errorBorder 0%, rgba(0, 0, 0, .2) 100%) no-repeat; + } + + &--icon { + .icon { + font-size: 1em; + margin-right: 0; + } + } +} + +.btn-fab { + border-radius: 2em; + width: 2em; + height: 2em; + padding: .2em; +} + +.ripple { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + background: transparent; + pointer-events: stroke; + + &__effect { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 1; + width: 200%; + height: 0; + padding-bottom: 200%; + border-radius: 50%; + background: rgba(99, 99, 99, 0.2); + animation: a-ripple .5s ease-in; + pointer-events: none; + + &.to-remove { + animation: remove-ripple .5s; + } + } +} + +.btn--outline .ripple__effect { + background: rgba(57, 73, 171, .5); + z-index: -1; +} + +.btn__content { + z-index: 1; + font-weight: 400; + pointer-events: none; +} + +@keyframes remove-ripple { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes a-ripple { + 0% { + opacity: 0; + padding-bottom: 0; + width: 0; + } + 25% { + opacity: 1; + } + 100% { + width: 200%; + padding-bottom: 200%; + } +} + +.btn-line { + text-align: right; + + button { + margin-right: 0.2em; + margin-left: 0.2em; + display: inline-block; + } + + button:last-child { + margin-right: 0; + } +} diff --git a/src/theme/components/_collapse.scss b/src/theme/components/_collapse.scss new file mode 100644 index 0000000..e83b099 --- /dev/null +++ b/src/theme/components/_collapse.scss @@ -0,0 +1,85 @@ +v-collapse { + display: flex; + flex-direction: column; + + v-collapse-item.open { + margin-top: .5rem; + margin-bottom: 1rem; + } +} + +v-collapse-item, v-collapse-content, v-collapse-head { + display: block; +} + +v-collapse-item { + margin-top: 0; + transition: box-shadow .5s, margin .3s; + box-shadow: #3949ab; + + &.open { + box-shadow: $box-shadow-1; + + v-collapse-content { + overflow: unset; + animation: collapse-overflow .5s linear; + } + } +} + +@keyframes collapse-overflow { + 0%, 99% { + overflow: hidden; + } + 100% { + overflow: unset; + } +} + +v-collapse-item:not(:last-child) { + v-collapse-head { + border-bottom: 0.05rem solid rgba(59, 59, 59, .3); + } +} + +v-collapse-head { + padding: 1rem .5rem; + cursor: pointer; + user-select: none; + background-color: rgba(0, 0, 0, .2); + color: #ffffff; + font-weight: bold; + position: relative; + + &:after { + content: ''; + border: solid #fff; + border-width: 0 .1rem .1rem 0; + width: .5rem; + height: .5rem; + position: absolute; + right: 1em; + margin-top: .1rem; + transform: rotate(45deg); + transition: .3s; + } +} + +v-collapse-item.open { + v-collapse-head:after { + transform: rotate(225deg); + margin-top: .4rem; + } +} + +v-collapse-content { + max-height: 0; + overflow: hidden; + transition: max-height .5s cubic-bezier(0.65, -0.02, 0.56, 1.04); + background-color: rgba(0, 0, 0, .05); +} + +.collapse-inner { + display: block; + padding: 1rem; +} diff --git a/src/theme/components/_color.scss b/src/theme/components/_color.scss new file mode 100644 index 0000000..1fd0537 --- /dev/null +++ b/src/theme/components/_color.scss @@ -0,0 +1,29 @@ +input[type="color"] { + opacity: 0; +} + +.colorBlob { + display: block; + width: 100%; + height: 100%; + +} + +.color-picker { + display: block; + position: relative; + margin: .2rem 0; + height: 1.5rem; + width: 4rem; + border-radius: 5rem; + overflow: hidden; + box-shadow: $box-shadow-1; + + input { + position: absolute !important; + top: 0; + width: 100%; + height: 100%; + cursor: pointer; + } +} diff --git a/src/theme/components/_input.scss b/src/theme/components/_input.scss new file mode 100644 index 0000000..07eb131 --- /dev/null +++ b/src/theme/components/_input.scss @@ -0,0 +1,111 @@ +v-input input { + font-size: 1em; + background-color: rgba(0, 0, 0, .4); + border: none; + border-bottom: 0.15em solid $primary; + color: $textColor; + padding: 0.5em; +} + +v-input.focus input, +v-input input:focus { + border-color: $second; +} + +v-input.valid input { + border-color: $validOnColor; +} + +v-input.invalid input { + border-color: $errorInYourFaceBorder; +} + +v-input { + display: flex; + flex-direction: column; + margin-bottom: 0.2em; + position: relative; + font-size: 1.3rem; + padding-top: 1em; + + &.no-space { + padding-top: .1em; + } +} + +v-input label { + font-size: .7em; + position: absolute; + top: 1.8em; + left: .5em; + pointer-events: none; + vertical-align: middle; + transform: translateY(50%); + color: #dcdcdc; + transition: all .2s ease-out; +} + +v-input.focus label, +v-input input:focus ~ label { + top: 0.3em; + left: 0; + transform: translateY(0); + font-size: .6em; +} + +v-input .error { + display: none; +} + +v-input.invalid .error { + margin-top: 0.2em; + display: block; + font-size: .7em; + color: $errorInYourFace; +} + +$height: 1.5rem; +$innerHeight: 1rem; +$width: 3rem; +$innerWidth: 1rem; + +v-switch { + display: flex; + flex-direction: row; + align-items: center; + margin-top: .5rem; + + input { + position: absolute; + appearance: none; + opacity: 0; + } + + label { + display: block; + border-radius: 9999px; + width: $width; + height: $height; + background-color: $hoverDark; + position: relative; + cursor: pointer; + margin-right: 0.5rem; + + &:after { + content: ''; + background-color: #dcdcdc; + position: absolute; + top: 0.2rem; + left: 0.25rem; + height: $innerHeight; + width: $innerWidth; + border-radius: 9999px; + transition: .5s; + } + } + + input:checked + label:after { + transform: translateX($width - $height); + background-color: $primary; + } +} diff --git a/src/theme/components/_modal.scss b/src/theme/components/_modal.scss new file mode 100644 index 0000000..e94db64 --- /dev/null +++ b/src/theme/components/_modal.scss @@ -0,0 +1,79 @@ +v-modal-container { + display: none; + + &.open { + display: flex; + } + + justify-content: center; + align-items: center; + width: 100vw; + height: 100vh; + position: absolute; + top: 0; + left: 0; + + &:after { + content: ''; + position: absolute; + z-index: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, .7); + } + + v-modal { + display: block; + max-width: 768px; + min-width: 300px; + z-index: 1; + background-color: #333333; + color: #fff; + box-shadow: $box-shadow-1; + + v-modal-head, v-modal-content { + display: block; + padding: .2rem; + } + + v-modal-head { + background-color: #212121; + box-shadow: $box-shadow-1; + display: flex; + align-items: center; + + .headline { + padding: .5rem 1rem; + font-weight: bold; + } + } + + v-modal-content { + padding: 1rem; + + .button-group { + display: flex; + justify-content: flex-end; + margin-top: 1rem; + + .btn { + margin-right: 1rem; + } + } + } + + .close-modal { + margin-left: auto; + fill: #fff; + cursor: pointer; + + * { + pointer-events: none; + } + + &.close { + display: none; + } + } + } +} diff --git a/src/theme/components/_notification-look.scss b/src/theme/components/_notification-look.scss new file mode 100644 index 0000000..d509ff7 --- /dev/null +++ b/src/theme/components/_notification-look.scss @@ -0,0 +1,141 @@ +@import "../variable"; + +v-notification, v-toast { + top: 10vh; + padding-bottom: .3rem; + height: 90vh; + display: flex; + flex-direction: column-reverse; + pointer-events: none; + position: absolute; +} + +v-notification { + width: 90%; + max-width: 400px; + right: .3rem; +} + +v-toast { + right: 0; + left: 0; + margin: 0 auto; + align-items: center; +} + +v-notification-toast { + margin-bottom: 10px; + box-shadow: $box-shadow-1; + overflow: hidden; + background-color: $darker; + color: #fff; + border-radius: 9999rem; + animation: toastOut ease-in-out 2000ms; + display: flex; + + .message { + padding: 1rem 1.5rem; + } + + .icon-wrap { + background-color: $primary; + font-size: 1.3em; + padding: .5em .5em .5em 1rem; + display: flex; + justify-content: center; + align-items: center; + + .icon { + margin-right: 0; + } + + &.success { + background-color: $successBorder; + } + + &.error { + background-color: $errorBorder; + } + + &.warning { + background-color: $warningBorder; + } + } +} + +v-notification-item { + margin-bottom: 10px; + position: relative; + width: 100%; + box-shadow: $box-shadow-1; + overflow: hidden; + background-color: $darker; + + .message { + padding: 1rem; + } + + .fade-bar { + animation: fadeOut ease-in-out 3000ms; + height: .25rem; + width: 100%; + position: absolute; + bottom: 0; + transform-origin: left; + background-color: $primary; + + &.endless { + animation: endlessFade ease-in-out 500ms infinite; + } + + &.success { + background-color: $successBorder; + } + + &.error { + background-color: $errorBorder; + } + + &.warning { + background-color: $warningBorder; + } + } +} + + +@keyframes fadeOut { + from { + transform: scaleX(1); + } + to { + transform: scaleX(0); + } +} + +@keyframes toastOut { + 0%, 90% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(200%); + opacity: 0; + } +} + +@keyframes endlessFade { + 0% { + transform: scaleX(1); + transform-origin: right; + } + 49% { + transform-origin: right; + } + 50% { + transform: scaleX(0); + transform-origin: left; + } + 100% { + transform: scaleX(1); + } +} diff --git a/src/theme/components/_range.scss b/src/theme/components/_range.scss new file mode 100644 index 0000000..5c04259 --- /dev/null +++ b/src/theme/components/_range.scss @@ -0,0 +1,94 @@ +.range { + -webkit-appearance: none; + width: 100%; + margin: .5rem 0; + background-color: rgba(0, 0, 0, 0.12); + + &.center { + margin-left: auto; + margin-right: auto; + } + + &.right { + margin-left: 10%; + } + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + width: 100%; + height: .3rem; + cursor: pointer; + background-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.12); + border-radius: 9999px; + border: none; + } + + &::-webkit-slider-thumb { + border: 0 solid rgba(0, 0, 30, 0); + height: 1rem; + width: 1rem; + border-radius: 9999px; + background: $primary; + cursor: pointer; + -webkit-appearance: none; + margin-top: -.33rem; + } + + &::-moz-range-track { + width: 100%; + height: .3rem; + cursor: pointer; + background-color: rgba(0, 0, 0, 0.12); + color: rgba(0, 0, 0, 0.12); + border-radius: 9999px; + border: none; + } + + &::-moz-range-thumb { + border: 0 solid rgba(0, 0, 30, 0); + height: 1rem; + width: 1rem; + border-radius: 9999px; + background: $primary; + cursor: pointer; + margin-top: -.33rem; + } +} + +.input-range { + display: block; + position: relative; + margin-bottom: 2rem; + + .max, .min, .current { + font-size: .8em; + color: #dcdcdc; + position: absolute; + bottom: -1rem; + } + + .current { + display: block; + width: 100%; + text-align: center; + + &:focus { + outline: none; + font-weight: bold; + color: $second; + } + } + + .min { + left: 0; + } + + .max { + right: 0; + } +} + diff --git a/src/theme/components/_select.scss b/src/theme/components/_select.scss new file mode 100644 index 0000000..7851463 --- /dev/null +++ b/src/theme/components/_select.scss @@ -0,0 +1,87 @@ +$borderRadius: 4px; + +v-select, v-options, v-option, v-label { + display: inline-block; + box-sizing: border-box; +} + +v-select { + display: block; + position: relative; + margin-bottom: .5rem; + + &:focus { + outline: none; + + v-label { + border-color: rgba(0, 0, 0, .8); + } + } +} + +v-label { + padding: .5rem 1rem; + background-color: rgba(0, 0, 0, .1); + border: 0.1rem solid rgba(0, 0, 0, .6); + color: $textColor; + cursor: pointer; + display: block; + position: relative; + margin-top: .5rem; + + &:after { + content: ''; + border: solid #fff; + border-width: 0 .1rem .1rem 0; + height: .5rem; + width: .5rem; + position: absolute; + right: 1.1em; + margin-top: .1rem; + transition: .3s; + transform: rotate(45deg); + } + + &.open:after { + transform: rotate(-135deg); + margin-top: .4rem; + } +} + +v-options { + position: absolute; + display: flex; + flex-direction: column; + overflow: hidden; + max-height: 0; + background-color: $bg; + z-index: 1000; + cursor: pointer; + color: $textColor; + box-shadow: $box-shadow-1; + width: 100%; + transition: max-height .3s; +} + + +v-option { + padding: .5rem 1rem .5rem .5rem; + position: relative; + + &:not(:last-child) { + border-bottom: solid 1px #333; + } +} + +v-option:not([disabled]):hover { + background-color: rgba(0, 0, 0, 0.3); +} + +v-option[selected] { + background-color: $primary; + color: $textColor; +} + +v-option[disabled] { + color: $textColorMoreLessBrightness; +} diff --git a/src/theme/gui/_graph.scss b/src/theme/gui/_graph.scss new file mode 100644 index 0000000..d674557 --- /dev/null +++ b/src/theme/gui/_graph.scss @@ -0,0 +1,63 @@ +.graphContent { + position: relative; + height: 250px; + background-color: #171717; + margin: 20px auto; + box-shadow: $box-shadow-1; + + canvas { + position: absolute; + top: 0; + left: 0; + } +} + +.indicator { + display: inline; + font-size: .5rem; + border: 1px solid #dcdcdc; + color: #dcdcdc; + padding: 2px; + vertical-align: middle; + + &.running { + border-color: $successBorder; + color: $successBorder; + } + + &.warning { + border-color: $warningBorder; + color: $warningBorder; + } + + &.finished { + border-color: $primary; + color: $primary; + } +} + +.table { + margin-bottom: 20px; + width: 50%; + @media only screen and (max-width: 860px) { + width: 100%; + + .table-group .right { + display: flex; + flex-grow: 1; + justify-content: flex-end; + } + } + + .table-group { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + margin-top: 5px; + border-bottom: 0.001em solid #dcdcdc; + + .left { + min-width: 50% + } + } +} diff --git a/src/theme/gui/_header.scss b/src/theme/gui/_header.scss new file mode 100644 index 0000000..846f0bd --- /dev/null +++ b/src/theme/gui/_header.scss @@ -0,0 +1,44 @@ +#main-header { + background-color: #1b1b1b; + color: #ffffff; + box-shadow: $box-shadow-1; + display: flex; + + .logo { + display: block; + font-family: monospace; + font-size: 32px; + font-weight: bolder; + padding: .5rem 1rem; + } + + nav { + margin-left: auto; + display: flex; + + nav-item { + display: flex; + align-items: center; + padding: 0.5rem 1rem; + cursor: pointer; + position: relative; + + &:hover { + background-color: rgba(0, 0, 0, .5) !important; + } + + &.active { + background-color: rgba(0, 0, 0, .3); + &:after { + position: absolute; + content: ''; + width: 100%; + height: 4px; + top: 0; + left: 0; + background-color: $primary; + } + } + } + } +} diff --git a/src/theme/gui/_page.scss b/src/theme/gui/_page.scss new file mode 100644 index 0000000..500b79e --- /dev/null +++ b/src/theme/gui/_page.scss @@ -0,0 +1,11 @@ +.error-page { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + + +.wrapper { + padding: 1rem; +} diff --git a/src/theme/main.scss b/src/theme/main.scss new file mode 100644 index 0000000..3f56580 --- /dev/null +++ b/src/theme/main.scss @@ -0,0 +1,19 @@ +@import "variable"; +@import "base"; +@import "scrollbar"; +@import "loading"; + +// Components +@import "components/btn"; +@import "components/input"; +@import "components/select"; +@import "components/range"; +@import "components/color"; +@import "components/collapse"; +@import "components/modal"; +@import "components/notification-look"; + +// GUI +@import "gui/header"; +@import "gui/graph"; +@import "gui/page";