├── .editorconfig
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── angular.json
├── commitlint.config.js
├── eslint.config.js
├── lint-staged.config.js
├── package.json
├── projects
├── dev-app
│ ├── eslint.config.js
│ ├── src
│ │ ├── app
│ │ │ ├── app.component.html
│ │ │ ├── app.component.scss
│ │ │ ├── app.component.ts
│ │ │ ├── app.config.ts
│ │ │ ├── app.routes.ts
│ │ │ ├── controls
│ │ │ │ ├── controls.component.html
│ │ │ │ ├── controls.component.scss
│ │ │ │ └── controls.component.ts
│ │ │ ├── examples
│ │ │ │ ├── examples.component.html
│ │ │ │ ├── examples.component.scss
│ │ │ │ └── examples.component.ts
│ │ │ ├── home
│ │ │ │ ├── home.component.html
│ │ │ │ ├── home.component.scss
│ │ │ │ └── home.component.ts
│ │ │ ├── layout
│ │ │ │ ├── layout.component.html
│ │ │ │ ├── layout.component.scss
│ │ │ │ └── layout.component.ts
│ │ │ ├── playground
│ │ │ │ ├── playground.component.html
│ │ │ │ ├── playground.component.scss
│ │ │ │ └── playground.component.ts
│ │ │ └── settings.service.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── styles.scss
│ │ ├── theme-m2.scss
│ │ └── theme.scss
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
├── docs
│ ├── eslint.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ ├── icons
│ │ │ ├── github.svg
│ │ │ ├── invert_colors.svg
│ │ │ └── menu.svg
│ │ └── images
│ │ │ ├── align_center.png
│ │ │ ├── align_left.png
│ │ │ ├── align_right.png
│ │ │ ├── icon3d1.webp
│ │ │ ├── icon3d2.webp
│ │ │ └── icon3d3.webp
│ ├── src
│ │ ├── app
│ │ │ ├── app.component.html
│ │ │ ├── app.component.scss
│ │ │ ├── app.component.ts
│ │ │ ├── app.config.ts
│ │ │ ├── app.routes.ts
│ │ │ ├── layout
│ │ │ │ ├── layout.component.html
│ │ │ │ ├── layout.component.scss
│ │ │ │ └── layout.component.ts
│ │ │ ├── pages
│ │ │ │ ├── basic-controls
│ │ │ │ │ ├── basic-controls.component.html
│ │ │ │ │ ├── basic-controls.component.scss
│ │ │ │ │ └── basic-controls.component.ts
│ │ │ │ ├── conditions
│ │ │ │ │ ├── conditions.component.html
│ │ │ │ │ ├── conditions.component.scss
│ │ │ │ │ └── conditions.component.ts
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── getting-started.component.html
│ │ │ │ │ ├── getting-started.component.scss
│ │ │ │ │ └── getting-started.component.ts
│ │ │ │ ├── group-controls
│ │ │ │ │ ├── group-controls.component.html
│ │ │ │ │ ├── group-controls.component.scss
│ │ │ │ │ └── group-controls.component.ts
│ │ │ │ ├── home
│ │ │ │ │ ├── home.component.html
│ │ │ │ │ ├── home.component.scss
│ │ │ │ │ └── home.component.ts
│ │ │ │ ├── media-controls
│ │ │ │ │ ├── media-controls.component.html
│ │ │ │ │ ├── media-controls.component.scss
│ │ │ │ │ └── media-controls.component.ts
│ │ │ │ └── playground
│ │ │ │ │ ├── playground.component.html
│ │ │ │ │ ├── playground.component.scss
│ │ │ │ │ └── playground.component.ts
│ │ │ └── shared
│ │ │ │ ├── example-viewer
│ │ │ │ ├── example-viewer.component.html
│ │ │ │ ├── example-viewer.component.scss
│ │ │ │ └── example-viewer.component.ts
│ │ │ │ ├── gradient-generator
│ │ │ │ ├── gradient-generator.component.html
│ │ │ │ ├── gradient-generator.component.scss
│ │ │ │ ├── gradient-generator.component.ts
│ │ │ │ └── gradient-presets.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── page-header
│ │ │ │ ├── page-header.component.html
│ │ │ │ ├── page-header.component.scss
│ │ │ │ └── page-header.component.ts
│ │ │ │ ├── style-manager
│ │ │ │ └── style-manager.ts
│ │ │ │ └── theme-picker
│ │ │ │ ├── theme-picker.html
│ │ │ │ ├── theme-picker.scss
│ │ │ │ ├── theme-picker.ts
│ │ │ │ └── theme-storage.ts
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── styles.scss
│ │ └── styles
│ │ │ ├── _app-theme.scss
│ │ │ └── _reboot.scss
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
└── gui
│ ├── _index.scss
│ ├── button-toggle
│ ├── button-toggle.html
│ ├── button-toggle.scss
│ ├── button-toggle.ts
│ ├── icon.scss
│ └── icon.ts
│ ├── codearea
│ ├── codearea-config.ts
│ ├── codearea-dialog.html
│ ├── codearea-dialog.scss
│ ├── codearea-dialog.ts
│ ├── codearea.html
│ ├── codearea.scss
│ └── codearea.ts
│ ├── combobox
│ ├── combobox.html
│ ├── combobox.scss
│ └── combobox.ts
│ ├── eslint.config.js
│ ├── field-group
│ ├── field-group.html
│ ├── field-group.scss
│ └── field-group.ts
│ ├── field-label
│ ├── field-label.html
│ ├── field-label.scss
│ └── field-label.ts
│ ├── file-uploader
│ ├── file-uploader-config.ts
│ ├── file-uploader.html
│ ├── file-uploader.scss
│ └── file-uploader.ts
│ ├── fill
│ ├── fill.html
│ ├── fill.scss
│ └── fill.ts
│ ├── gui-form.html
│ ├── gui-form.scss
│ ├── gui-form.ts
│ ├── gui-icons.ts
│ ├── gui-module.ts
│ ├── gui-utils.ts
│ ├── icon-button-wrapper
│ ├── icon-button-wrapper.html
│ ├── icon-button-wrapper.scss
│ └── icon-button-wrapper.ts
│ ├── image-select
│ ├── image-select.html
│ ├── image-select.scss
│ └── image-select.ts
│ ├── inline-group
│ ├── inline-group.html
│ ├── inline-group.scss
│ └── inline-group.ts
│ ├── input-number
│ ├── input-number.html
│ ├── input-number.scss
│ └── input-number.ts
│ ├── input-text
│ ├── input-text.html
│ ├── input-text.scss
│ └── input-text.ts
│ ├── interface.ts
│ ├── ng-package.json
│ ├── package.json
│ ├── public-api.ts
│ ├── select
│ ├── select.html
│ ├── select.scss
│ └── select.ts
│ ├── slider
│ ├── slider.html
│ ├── slider.scss
│ └── slider.ts
│ ├── switch
│ ├── switch.html
│ ├── switch.scss
│ └── switch.ts
│ ├── textarea
│ ├── textarea.html
│ ├── textarea.scss
│ └── textarea.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | /.nx/cache
34 | .sass-cache/
35 | /connect.lock
36 | /coverage
37 | /libpeerconnection.log
38 | testem.log
39 | /typings
40 |
41 | # System files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no-install commitlint --edit "$1"
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx --no-install lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # add files you wish to ignore here
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "htmlWhitespaceSensitivity": "ignore",
5 | "insertPragma": false,
6 | "jsxBracketSameLine": false,
7 | "jsxSingleQuote": false,
8 | "printWidth": 100,
9 | "proseWrap": "preserve",
10 | "quoteProps": "consistent",
11 | "requirePragma": false,
12 | "semi": true,
13 | "singleQuote": true,
14 | "tabWidth": 2,
15 | "trailingComma": "es5",
16 | "useTabs": false,
17 | "overrides": [
18 | {
19 | "files": "*.html",
20 | "options": {
21 | "parser": "angular"
22 | }
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreFiles": [
3 | "dist/**"
4 | ],
5 | "extends": [
6 | "stylelint-config-standard",
7 | "stylelint-config-recommended-scss"
8 | ],
9 | "rules": {
10 | "alpha-value-notation": null,
11 | "annotation-no-unknown": null,
12 | "at-rule-empty-line-before": null,
13 | "block-no-empty": null,
14 | "color-function-notation": "legacy",
15 | "function-no-unknown": null,
16 | "import-notation": null,
17 | "no-descending-specificity": null,
18 | "no-empty-source": null,
19 | "media-query-no-invalid": null,
20 | "number-max-precision": null,
21 | "selector-pseudo-element-no-unknown": [
22 | true,
23 | {
24 | "ignorePseudoElements": [
25 | "ng-deep"
26 | ]
27 | }
28 | ],
29 | "selector-class-pattern": null,
30 | "selector-type-no-unknown": null,
31 | "value-keyword-case": null,
32 | "scss/at-extend-no-missing-placeholder": null,
33 | "scss/at-if-no-null": null,
34 | "scss/comment-no-empty": null,
35 | "scss/operator-no-unspaced": null,
36 | "scss/operator-no-newline-after": null
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": [
4 | "angular.ng-template",
5 | "cyrilletuzi.angular-schematics",
6 | "esbenp.prettier-vscode",
7 | "stylelint.vscode-stylelint",
8 | "syler.sass-indented",
9 | "editorconfig.editorconfig",
10 | "mrmlnc.vscode-scss"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.rulers": [100],
3 | "html.format.wrapLineLength": 100,
4 | "html.format.wrapAttributes": "preserve-aligned",
5 | "[javascript]": {
6 | "editor.defaultFormatter": "esbenp.prettier-vscode"
7 | },
8 | "[typescript]": {
9 | "editor.defaultFormatter": "esbenp.prettier-vscode"
10 | },
11 | "[jsonc]": {
12 | "editor.defaultFormatter": "esbenp.prettier-vscode"
13 | },
14 | "[html]": {
15 | "editor.defaultFormatter": "esbenp.prettier-vscode"
16 | },
17 | "editor.codeActionsOnSave": {
18 | "source.fixAll.stylelint": "explicit"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Acrodata
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GUI
2 |
3 | [](https://www.npmjs.com/package/@acrodata/gui)
4 | [](https://github.com/acrodata/gui/blob/main/LICENSE)
5 |
6 | 
7 |
8 | JSON powered GUI for configurable panels.
9 |
10 | #### Quick links
11 |
12 | [Documentation](https://acrodata.github.io/gui/) |
13 | [Playground](https://acrodata.github.io/gui/playground)
14 |
15 | ## Compatibility
16 |
17 | | Angular | @acrodata/gui | Theming |
18 | | -------- | ------------- | ------- |
19 | | >=18.0.0 | 2.x | M2, M3 |
20 | | >=17.0.0 | 1.x | M2 |
21 | | >=16.0.0 | 0.x | M2 |
22 |
23 | ## Installation
24 |
25 | ```bash
26 | npm install @angular/material @ng-matero/extensions @acrodata/gui --save
27 | ```
28 |
29 | ## Setup
30 |
31 | Define a theme with Angular Material's theming system. [More about theming](https://material.angular.io/guide/theming).
32 |
33 | ```scss
34 | @use '@angular/material' as mat;
35 | @use '@acrodata/gui' as gui;
36 |
37 | @include mat.core();
38 |
39 | $theme: mat.define-theme(...);
40 |
41 | @include gui.all-control-themes($theme);
42 | ```
43 |
44 | 🚨 If you use the Angular Material as default library and have included all component themes, there's no need to include the GUI themes anymore.
45 |
46 | ```diff
47 | + @include mat.all-component-themes($theme);
48 | + @include mtx.all-component-themes($theme);
49 | - @include gui.all-control-themes($theme);
50 | ```
51 |
52 | ## Usage
53 |
54 | ```ts
55 | import { Component } from '@angular/core';
56 | import { GuiFields, GuiForm } from '@acrodata/gui';
57 |
58 | @Component({
59 | selector: 'your-app',
60 | template: ` `,
61 | standalone: true,
62 | imports: [GuiForm],
63 | })
64 | export class YourAppComponent {
65 | config: GuiFields = {
66 | title: {
67 | type: 'text',
68 | name: 'Title',
69 | default: 'I am title',
70 | },
71 | };
72 | model = {};
73 | form = new FormGroup({});
74 | }
75 | ```
76 |
77 | ## License
78 |
79 | MIT
80 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'type-enum': [
5 | 2,
6 | 'always',
7 | [
8 | 'build',
9 | 'ci',
10 | 'chore',
11 | 'docs',
12 | 'feat',
13 | 'fix',
14 | 'perf',
15 | 'refactor',
16 | 'revert',
17 | 'style',
18 | 'test',
19 | ],
20 | ],
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const eslint = require('@eslint/js');
3 | const tseslint = require('typescript-eslint');
4 | const angular = require('angular-eslint');
5 |
6 | module.exports = tseslint.config(
7 | {
8 | files: ['**/*.ts'],
9 | extends: [
10 | eslint.configs.recommended,
11 | ...tseslint.configs.recommended,
12 | ...tseslint.configs.stylistic,
13 | ...angular.configs.tsRecommended,
14 | ],
15 | processor: angular.processInlineTemplates,
16 | rules: {
17 | 'max-len': [
18 | 'warn',
19 | {
20 | code: 100,
21 | ignoreComments: true,
22 | ignoreStrings: true,
23 | ignoreTemplateLiterals: true,
24 | },
25 | ],
26 | 'object-shorthand': ['warn', 'always', { avoidQuotes: true }],
27 | 'quote-props': ['warn', 'consistent-as-needed'],
28 | 'quotes': ['warn', 'single', { allowTemplateLiterals: true }],
29 | 'semi': ['warn', 'always'],
30 | '@typescript-eslint/no-empty-function': 'off',
31 | '@typescript-eslint/no-explicit-any': 'off',
32 | '@typescript-eslint/no-inferrable-types': 'off',
33 | '@typescript-eslint/no-unused-vars': 'off',
34 | '@angular-eslint/component-class-suffix': 'off',
35 | '@angular-eslint/directive-class-suffix': 'off',
36 | '@angular-eslint/no-empty-lifecycle-method': 'off',
37 | '@angular-eslint/no-output-native': 'off',
38 | },
39 | },
40 | {
41 | files: ['**/*.html'],
42 | extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility],
43 | rules: {
44 | '@angular-eslint/template/prefer-self-closing-tags': 'warn',
45 | },
46 | }
47 | );
48 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.ts': filenames => [
3 | `eslint --fix ${filenames.join(' ')}`,
4 | `prettier --write ${filenames.join(' ')}`,
5 | ],
6 | '*.scss': filenames => `stylelint --fix ${filenames.join(' ')}`,
7 | '*.{html,css,js,json,md,yml}': filenames => `git add ${filenames.join(' ')}`,
8 | };
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gui-srcs",
3 | "version": "0.0.0-NOT-USED",
4 | "engines": {
5 | "yarn": ">=1.22.0 <2",
6 | "npm": "Please use Yarn instead of NPM to install dependencies"
7 | },
8 | "scripts": {
9 | "ng": "ng",
10 | "start": "ng serve dev-app",
11 | "build": "ng build dev-app --base-href=/gui/",
12 | "watch": "ng build dev-app --watch --configuration development",
13 | "test": "ng test dev-app",
14 | "serve:docs": "ng serve docs",
15 | "build:docs": "ng build docs --base-href=/gui/",
16 | "lint": "ng lint --fix && npm run lint:scss",
17 | "lint:ts": "eslint \"projects/**/*.ts\" --fix",
18 | "lint:scss": "stylelint \"projects/**/*.scss\" --fix",
19 | "build:lib": "ng build gui && cp README.md dist/gui",
20 | "publish": "npm run build:lib && cd dist/gui && npm publish",
21 | "deploy": "npm run build:docs && ng deploy",
22 | "prepare": "husky install"
23 | },
24 | "private": true,
25 | "dependencies": {
26 | "@acrodata/code-editor": "^0.5.1",
27 | "@acrodata/rnd-dialog": "^0.3.2",
28 | "@angular/animations": "^18.2.0",
29 | "@angular/cdk": "^18.2.0",
30 | "@angular/common": "^18.2.0",
31 | "@angular/compiler": "^18.2.0",
32 | "@angular/core": "^18.2.0",
33 | "@angular/forms": "^18.2.0",
34 | "@angular/material": "^18.2.0",
35 | "@angular/platform-browser": "^18.2.0",
36 | "@angular/platform-browser-dynamic": "^18.2.0",
37 | "@angular/router": "^18.2.0",
38 | "@codemirror/language-data": "^6.5.1",
39 | "@mdi/font": "^7.4.47",
40 | "@ng-matero/extensions": "^18.5.0",
41 | "lodash-es": "^4.17.21",
42 | "ngx-highlightjs": "^12.0.0",
43 | "rxjs": "~7.8.0",
44 | "tslib": "^2.3.0",
45 | "zone.js": "~0.14.10"
46 | },
47 | "devDependencies": {
48 | "@angular-devkit/build-angular": "^18.2.0",
49 | "@angular/cli": "^18.2.0",
50 | "@angular/compiler-cli": "^18.2.0",
51 | "@commitlint/cli": "^19.5.0",
52 | "@commitlint/config-conventional": "^19.5.0",
53 | "@types/jasmine": "~5.1.0",
54 | "@types/lodash-es": "^4.17.10",
55 | "@types/node": "^20.14.0",
56 | "angular-cli-ghpages": "^2.0.0",
57 | "angular-eslint": "^18.3.1",
58 | "eslint": "^9.12.0",
59 | "husky": "^9.1.6",
60 | "jasmine-core": "~5.1.0",
61 | "karma": "~6.4.0",
62 | "karma-chrome-launcher": "~3.2.0",
63 | "karma-coverage": "~2.2.0",
64 | "karma-jasmine": "~5.1.0",
65 | "karma-jasmine-html-reporter": "~2.1.0",
66 | "lint-staged": "^15.2.0",
67 | "ng-packagr": "^18.2.0",
68 | "prettier": "^3.3.0",
69 | "stylelint": "^16.7.0",
70 | "stylelint-config-recommended-scss": "^14.1.0",
71 | "stylelint-config-standard": "^36.0.0",
72 | "typescript": "~5.5.2",
73 | "typescript-eslint": "^8.9.0"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/projects/dev-app/eslint.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const tseslint = require('typescript-eslint');
3 | const rootConfig = require('../../eslint.config.js');
4 |
5 | module.exports = tseslint.config(
6 | ...rootConfig,
7 | {
8 | files: ['**/*.ts'],
9 | rules: {
10 | '@angular-eslint/directive-selector': [
11 | 'error',
12 | {
13 | type: 'attribute',
14 | prefix: 'app',
15 | style: 'camelCase',
16 | },
17 | ],
18 | '@angular-eslint/component-selector': [
19 | 'error',
20 | {
21 | type: 'element',
22 | prefix: 'app',
23 | style: 'kebab-case',
24 | },
25 | ],
26 | },
27 | },
28 | {
29 | files: ['**/*.html'],
30 | rules: {},
31 | }
32 | );
33 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/dev-app/src/app/app.component.scss
--------------------------------------------------------------------------------
/projects/dev-app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | standalone: true,
7 | imports: [RouterOutlet],
8 | templateUrl: './app.component.html',
9 | styleUrl: './app.component.scss',
10 | })
11 | export class AppComponent {
12 | title = 'dev-app';
13 | }
14 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient } from '@angular/common/http';
2 | import { ApplicationConfig } from '@angular/core';
3 | import { provideAnimations } from '@angular/platform-browser/animations';
4 | import { provideRouter } from '@angular/router';
5 |
6 | import { routes } from './app.routes';
7 |
8 | export const appConfig: ApplicationConfig = {
9 | providers: [provideRouter(routes), provideAnimations(), provideHttpClient()],
10 | };
11 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { LayoutComponent } from './layout/layout.component';
3 | import { HomeComponent } from './home/home.component';
4 | import { ExamplesComponent } from './examples/examples.component';
5 | import { ControlsComponent } from './controls/controls.component';
6 | import { PlaygroundComponent } from './playground/playground.component';
7 |
8 | export const routes: Routes = [
9 | {
10 | path: '',
11 | component: LayoutComponent,
12 | children: [
13 | { path: '', pathMatch: 'full', redirectTo: 'home' },
14 | { path: 'home', component: HomeComponent },
15 | { path: 'examples', component: ExamplesComponent },
16 | { path: 'controls', component: ControlsComponent },
17 | { path: 'playground', component: PlaygroundComponent },
18 | ],
19 | },
20 | ];
21 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/controls/controls.component.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
56 |
57 |
58 |
59 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/controls/controls.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/dev-app/src/app/controls/controls.component.scss
--------------------------------------------------------------------------------
/projects/dev-app/src/app/controls/controls.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiModule } from '@acrodata/gui';
2 | import { Component } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'app-controls',
6 | standalone: true,
7 | imports: [GuiModule],
8 | templateUrl: './controls.component.html',
9 | styleUrl: './controls.component.scss',
10 | })
11 | export class ControlsComponent {}
12 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/examples/examples.component.html:
--------------------------------------------------------------------------------
1 |
4 |
7 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/examples/examples.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: grid;
3 | grid-template-columns: 320px 1fr;
4 | gap: 16px;
5 | height: 100%;
6 | }
7 |
8 | section {
9 | position: relative;
10 | border: 1px solid var(--mat-divider-color);
11 | overflow: auto;
12 | }
13 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
Welcome to the development demos for GUI!
2 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/home/home.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/dev-app/src/app/home/home.component.scss
--------------------------------------------------------------------------------
/projects/dev-app/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-home',
5 | standalone: true,
6 | templateUrl: './home.component.html',
7 | styleUrl: './home.component.scss',
8 | })
9 | export class HomeComponent {}
10 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/layout/layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 | {{isDark ? 'Light' : 'Dark'}} theme
13 | {{isRtl ? 'LTR' : 'RTL'}}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/layout/layout.component.scss:
--------------------------------------------------------------------------------
1 | mat-sidenav-container {
2 | height: 100%;
3 | }
4 |
5 | mat-toolbar {
6 | justify-content: space-between;
7 | }
8 |
9 | .demo-content {
10 | height: calc(100% - 64px);
11 | padding: 16px;
12 | }
13 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/layout/layout.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiCodeareaConfig } from '@acrodata/gui';
2 | import { Directionality } from '@angular/cdk/bidi';
3 | import { DOCUMENT } from '@angular/common';
4 | import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
5 | import { MatButtonModule } from '@angular/material/button';
6 | import { MatSidenavModule } from '@angular/material/sidenav';
7 | import { MatToolbarModule } from '@angular/material/toolbar';
8 | import { RouterModule } from '@angular/router';
9 | import { LanguageDescription } from '@codemirror/language';
10 | import { SettingsService } from '../settings.service';
11 | import { json, jsonParseLinter } from '@codemirror/lang-json';
12 | import { lintGutter, linter } from '@codemirror/lint';
13 |
14 | export const CODEAREA_LANGUAGES = [
15 | LanguageDescription.of({
16 | name: 'JSON',
17 | alias: ['json5'],
18 | extensions: ['json', 'map'],
19 | load: () => import('@codemirror/lang-json').then(m => m.json()),
20 | }),
21 | LanguageDescription.of({
22 | name: 'JavaScript',
23 | alias: ['ecmascript', 'js', 'node'],
24 | extensions: ['js', 'mjs', 'cjs'],
25 | load: () => import('@codemirror/lang-javascript').then(m => m.javascript()),
26 | }),
27 | ];
28 |
29 | @Component({
30 | selector: 'app-layout',
31 | standalone: true,
32 | imports: [RouterModule, MatSidenavModule, MatToolbarModule, MatButtonModule],
33 | templateUrl: './layout.component.html',
34 | styleUrl: './layout.component.scss',
35 | encapsulation: ViewEncapsulation.None,
36 | })
37 | export class LayoutComponent implements OnInit {
38 | htmlElement!: HTMLHtmlElement;
39 | darkThemeClass = 'dark-theme';
40 | isDark = false;
41 | isRtl = false;
42 |
43 | constructor(
44 | @Inject(DOCUMENT) private document: Document,
45 | private dir: Directionality,
46 | private settings: SettingsService,
47 | private codeareaCfg: GuiCodeareaConfig
48 | ) {}
49 |
50 | ngOnInit(): void {
51 | this.htmlElement = this.document.querySelector('html')!;
52 | this.isRtl = this.dir.value === 'rtl';
53 | this.codeareaCfg.languages = CODEAREA_LANGUAGES;
54 | this.codeareaCfg.extensions = data => {
55 | return data.language == 'json' ? [lintGutter(), linter(jsonParseLinter())] : [];
56 | };
57 | }
58 |
59 | toggleThemeClass() {
60 | this.isDark = !this.isDark;
61 |
62 | if (this.isDark) {
63 | this.htmlElement.classList.add(this.darkThemeClass);
64 | this.codeareaCfg.theme = 'dark';
65 | } else {
66 | this.htmlElement.classList.remove(this.darkThemeClass);
67 | this.codeareaCfg.theme = 'light';
68 | }
69 |
70 | this.codeareaCfg.changes.next();
71 | this.settings.themeChange.next(this.isDark ? 'dark' : 'light');
72 | }
73 |
74 | toggleRtl() {
75 | this.isRtl = !this.isRtl;
76 |
77 | if (this.isRtl) {
78 | this.htmlElement.dir = 'rtl';
79 | } else {
80 | this.htmlElement.dir = 'ltr';
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/playground/playground.component.html:
--------------------------------------------------------------------------------
1 |
8 |
11 |
14 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/playground/playground.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: grid;
3 | grid-template-columns: 1fr 320px 1fr;
4 | gap: 16px;
5 | height: 100%;
6 | }
7 |
8 | section {
9 | position: relative;
10 | border: 1px solid var(--mat-divider-color);
11 | overflow: auto;
12 | }
13 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/playground/playground.component.ts:
--------------------------------------------------------------------------------
1 | import { CodeEditor, Theme } from '@acrodata/code-editor';
2 | import { GuiFields, GuiForm } from '@acrodata/gui';
3 | import { CommonModule } from '@angular/common';
4 | import { Component, DestroyRef, OnInit } from '@angular/core';
5 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6 | import { FormGroup, FormsModule } from '@angular/forms';
7 | import { json, jsonParseLinter } from '@codemirror/lang-json';
8 | import { lintGutter, linter } from '@codemirror/lint';
9 | import { SettingsService } from '../settings.service';
10 |
11 | @Component({
12 | selector: 'app-playground',
13 | standalone: true,
14 | imports: [CommonModule, FormsModule, CodeEditor, GuiForm],
15 | templateUrl: './playground.component.html',
16 | styleUrl: './playground.component.scss',
17 | })
18 | export class PlaygroundComponent implements OnInit {
19 | form = new FormGroup({});
20 | config: GuiFields = {
21 | title: {
22 | type: 'text',
23 | name: '标题',
24 | default: '我是标题',
25 | },
26 | align: {
27 | type: 'buttonToggle',
28 | name: '对齐方式',
29 | options: [
30 | { value: 'left', label: '左对齐' },
31 | { value: 'center', label: '居中对齐' },
32 | { value: 'right', label: '右对齐' },
33 | ],
34 | default: 'center',
35 | },
36 | size: {
37 | type: 'group',
38 | name: '尺寸',
39 | children: {
40 | width: {
41 | type: 'number',
42 | name: '宽度',
43 | default: 100,
44 | },
45 | height: {
46 | type: 'number',
47 | name: '高度',
48 | default: 100,
49 | },
50 | },
51 | },
52 | };
53 | model: any = {};
54 |
55 | configStr = '';
56 |
57 | theme: Theme = 'light';
58 |
59 | extensions = [json(), linter(jsonParseLinter()), lintGutter()];
60 |
61 | constructor(
62 | private destroyRef: DestroyRef,
63 | private settings: SettingsService
64 | ) {}
65 |
66 | ngOnInit(): void {
67 | this.configStr = JSON.stringify(this.config, null, 2);
68 |
69 | this.form.valueChanges.subscribe(v => {
70 | console.log(v);
71 | });
72 |
73 | this.settings.themeChange.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(theme => {
74 | this.theme = theme;
75 | });
76 | }
77 |
78 | onConfigChange() {
79 | this.config = JSON.parse(this.configStr);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/projects/dev-app/src/app/settings.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Subject } from 'rxjs';
3 |
4 | @Injectable({
5 | providedIn: 'root',
6 | })
7 | export class SettingsService {
8 | themeChange = new Subject<'light' | 'dark'>();
9 |
10 | constructor() {}
11 | }
12 |
--------------------------------------------------------------------------------
/projects/dev-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/dev-app/src/assets/.gitkeep
--------------------------------------------------------------------------------
/projects/dev-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/dev-app/src/favicon.ico
--------------------------------------------------------------------------------
/projects/dev-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | DevApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/projects/dev-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
6 |
--------------------------------------------------------------------------------
/projects/dev-app/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | // @import '@angular/material/prebuilt-themes/indigo-pink.css';
4 | // @import '@ng-matero/extensions/prebuilt-themes/indigo-pink.css';
5 |
6 | @use './theme';
7 |
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html,
15 | body {
16 | height: 100%;
17 | margin: 0;
18 | }
19 |
20 | textarea {
21 | display: block;
22 | width: 100%;
23 | height: 100%;
24 | font-size: 12px;
25 | border: none;
26 | outline: none;
27 | resize: vertical;
28 | background: transparent;
29 | color: inherit;
30 | }
31 |
32 | .gui-form {
33 | background-color: white;
34 |
35 | .dark-theme & {
36 | background-color: transparent;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/projects/dev-app/src/theme-m2.scss:
--------------------------------------------------------------------------------
1 | @use '@angular/material' as mat;
2 | @use '@ng-matero/extensions' as mtx;
3 | @use '../../gui' as gui;
4 |
5 | @include mat.core();
6 |
7 | $my-primary: mat.m2-define-palette(mat.$m2-indigo-palette, 500);
8 | $my-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
9 | $my-theme: mat.m2-define-light-theme(
10 | (
11 | color: (
12 | primary: $my-primary,
13 | accent: $my-accent,
14 | ),
15 | typography: mat.m2-define-typography-config(),
16 | density: 0,
17 | )
18 | );
19 |
20 | @include mat.all-component-themes($my-theme);
21 | @include mtx.all-component-themes($my-theme);
22 | // @include gui.all-control-themes($my-theme);
23 |
24 | .dark-theme {
25 | $dark-primary: mat.m2-define-palette(mat.$m2-blue-grey-palette);
26 | $dark-accent: mat.m2-define-palette(mat.$m2-amber-palette, A200, A100, A400);
27 | $dark-theme: mat.m2-define-dark-theme(
28 | (
29 | color: (
30 | primary: $dark-primary,
31 | accent: $dark-accent,
32 | ),
33 | typography: mat.m2-define-typography-config(),
34 | density: 0,
35 | )
36 | );
37 |
38 | @include mat.all-component-colors($dark-theme);
39 | @include mtx.all-component-colors($dark-theme);
40 | // @include gui.all-control-colors($dark-theme);
41 | }
42 |
--------------------------------------------------------------------------------
/projects/dev-app/src/theme.scss:
--------------------------------------------------------------------------------
1 | @use '@angular/material' as mat;
2 | @use '@ng-matero/extensions' as mtx;
3 | @use '../../gui' as gui;
4 |
5 | @include mat.core();
6 |
7 | $light-config: (
8 | color: (
9 | theme-type: 'light',
10 | primary: mat.$azure-palette,
11 | tertiary: mat.$blue-palette,
12 | ),
13 | );
14 |
15 | // Define the default (light) theme.
16 | $light-theme: mat.private-deep-merge-all(
17 | mat.define-theme($light-config),
18 | mtx.define-theme($light-config)
19 | );
20 |
21 | html {
22 | @include mat.all-component-themes($light-theme);
23 | @include mtx.all-component-themes($light-theme);
24 | // @include gui.all-control-themes($light-theme);
25 | }
26 |
27 | $dark-config: (
28 | color: (
29 | theme-type: 'dark',
30 | primary: mat.$cyan-palette,
31 | tertiary: mat.$orange-palette,
32 | ),
33 | );
34 |
35 | // Define an alternate dark theme.
36 | $dark-theme: mat.private-deep-merge-all(
37 | mat.define-theme($dark-config),
38 | mtx.define-theme($dark-config)
39 | );
40 |
41 | .dark-theme {
42 | @include mat.all-component-colors($dark-theme);
43 | @include mtx.all-component-colors($dark-theme);
44 | // @include gui.all-control-colors($dark-theme);
45 | }
46 |
--------------------------------------------------------------------------------
/projects/dev-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/dev-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/docs/eslint.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const tseslint = require("typescript-eslint");
3 | const rootConfig = require("../../eslint.config.js");
4 |
5 | module.exports = tseslint.config(
6 | ...rootConfig,
7 | {
8 | files: ["**/*.ts"],
9 | rules: {
10 | "@angular-eslint/directive-selector": [
11 | "error",
12 | {
13 | type: "attribute",
14 | prefix: "app",
15 | style: "camelCase",
16 | },
17 | ],
18 | "@angular-eslint/component-selector": [
19 | "error",
20 | {
21 | type: "element",
22 | prefix: "app",
23 | style: "kebab-case",
24 | },
25 | ],
26 | },
27 | },
28 | {
29 | files: ["**/*.html"],
30 | rules: {},
31 | }
32 | );
33 |
--------------------------------------------------------------------------------
/projects/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/favicon.ico
--------------------------------------------------------------------------------
/projects/docs/public/icons/github.svg:
--------------------------------------------------------------------------------
1 | github
2 |
--------------------------------------------------------------------------------
/projects/docs/public/icons/invert_colors.svg:
--------------------------------------------------------------------------------
1 | invert-colors
2 |
--------------------------------------------------------------------------------
/projects/docs/public/icons/menu.svg:
--------------------------------------------------------------------------------
1 | menu
2 |
--------------------------------------------------------------------------------
/projects/docs/public/images/align_center.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/align_center.png
--------------------------------------------------------------------------------
/projects/docs/public/images/align_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/align_left.png
--------------------------------------------------------------------------------
/projects/docs/public/images/align_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/align_right.png
--------------------------------------------------------------------------------
/projects/docs/public/images/icon3d1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/icon3d1.webp
--------------------------------------------------------------------------------
/projects/docs/public/images/icon3d2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/icon3d2.webp
--------------------------------------------------------------------------------
/projects/docs/public/images/icon3d3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/public/images/icon3d3.webp
--------------------------------------------------------------------------------
/projects/docs/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/projects/docs/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/app.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core';
2 | import { MatIconRegistry } from '@angular/material/icon';
3 | import { DomSanitizer } from '@angular/platform-browser';
4 | import { RouterOutlet } from '@angular/router';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | standalone: true,
9 | imports: [RouterOutlet],
10 | templateUrl: './app.component.html',
11 | styleUrl: './app.component.scss',
12 | encapsulation: ViewEncapsulation.None,
13 | })
14 | export class AppComponent {
15 | title = 'docs';
16 |
17 | icons = ['menu', 'github', 'invert_colors'];
18 |
19 | constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
20 | for (const i of this.icons) {
21 | iconRegistry.addSvgIcon(i, sanitizer.bypassSecurityTrustResourceUrl(`icons/${i}.svg`));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/projects/docs/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { GuiCodeareaConfig } from '@acrodata/gui';
2 | import { provideHttpClient } from '@angular/common/http';
3 | import { ApplicationConfig } from '@angular/core';
4 | import { provideAnimations } from '@angular/platform-browser/animations';
5 | import { provideRouter, withInMemoryScrolling } from '@angular/router';
6 | import { LanguageDescription } from '@codemirror/language';
7 | import { provideHighlightOptions } from 'ngx-highlightjs';
8 | import { routes } from './app.routes';
9 |
10 | export const CODEAREA_LANGUAGES = [
11 | LanguageDescription.of({
12 | name: 'JSON',
13 | alias: ['json5'],
14 | extensions: ['json', 'map'],
15 | load: () => import('@codemirror/lang-json').then(m => m.json()),
16 | }),
17 | LanguageDescription.of({
18 | name: 'JavaScript',
19 | alias: ['ecmascript', 'js', 'node'],
20 | extensions: ['js', 'mjs', 'cjs'],
21 | load: () => import('@codemirror/lang-javascript').then(m => m.javascript()),
22 | }),
23 | LanguageDescription.of({
24 | name: 'CSS',
25 | extensions: ['css'],
26 | load: () => import('@codemirror/lang-css').then(m => m.css()),
27 | }),
28 | LanguageDescription.of({
29 | name: 'HTML',
30 | alias: ['xhtml'],
31 | extensions: ['html', 'htm', 'handlebars', 'hbs'],
32 | load: () => import('@codemirror/lang-html').then(m => m.html()),
33 | }),
34 | ];
35 |
36 | export const appConfig: ApplicationConfig = {
37 | providers: [
38 | provideRouter(
39 | routes,
40 | withInMemoryScrolling({
41 | scrollPositionRestoration: 'enabled',
42 | anchorScrolling: 'enabled',
43 | })
44 | ),
45 | provideAnimations(),
46 | provideHttpClient(),
47 | provideHighlightOptions({
48 | coreLibraryLoader: () => import('highlight.js/lib/core'),
49 | languages: {
50 | typescript: () => import('highlight.js/lib/languages/typescript'),
51 | scss: () => import('highlight.js/lib/languages/scss'),
52 | xml: () => import('highlight.js/lib/languages/xml'),
53 | json: () => import('highlight.js/lib/languages/json'),
54 | diff: () => import('highlight.js/lib/languages/diff'),
55 | bash: () => import('highlight.js/lib/languages/bash'),
56 | },
57 | }),
58 | {
59 | provide: GuiCodeareaConfig,
60 | useValue: {
61 | ...new GuiCodeareaConfig(),
62 | languages: CODEAREA_LANGUAGES,
63 | },
64 | },
65 | ],
66 | };
67 |
--------------------------------------------------------------------------------
/projects/docs/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { LayoutComponent } from './layout/layout.component';
3 | import { HomeComponent } from './pages/home/home.component';
4 |
5 | export const routes: Routes = [
6 | {
7 | path: '',
8 | component: LayoutComponent,
9 | children: [
10 | { path: '', redirectTo: 'home', pathMatch: 'full' },
11 | { path: 'home', component: HomeComponent },
12 | {
13 | path: 'getting-started',
14 | loadComponent: () =>
15 | import('./pages/getting-started/getting-started.component').then(
16 | m => m.GettingStartedComponent
17 | ),
18 | },
19 | {
20 | path: 'basic-controls',
21 | loadComponent: () =>
22 | import('./pages/basic-controls/basic-controls.component').then(
23 | m => m.BasicControlsComponent
24 | ),
25 | },
26 | {
27 | path: 'media-controls',
28 | loadComponent: () =>
29 | import('./pages/media-controls/media-controls.component').then(
30 | m => m.MediaControlsComponent
31 | ),
32 | },
33 | {
34 | path: 'group-controls',
35 | loadComponent: () =>
36 | import('./pages/group-controls/group-controls.component').then(
37 | m => m.GroupControlsComponent
38 | ),
39 | },
40 | {
41 | path: 'conditions',
42 | loadComponent: () =>
43 | import('./pages/conditions/conditions.component').then(m => m.ConditionsComponent),
44 | },
45 | {
46 | path: 'playground',
47 | loadComponent: () =>
48 | import('./pages/playground/playground.component').then(m => m.PlaygroundComponent),
49 | },
50 | ],
51 | },
52 | { path: '**', redirectTo: 'home' },
53 | ];
54 |
--------------------------------------------------------------------------------
/projects/docs/src/app/layout/layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
40 |
41 |
42 |
43 |
44 | © 2023 Made with ❤️ by nzbin
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/projects/docs/src/app/layout/layout.component.scss:
--------------------------------------------------------------------------------
1 | .container-wrap {
2 | display: block;
3 | max-width: 1280px;
4 | padding: 0 64px;
5 | margin: 0 auto;
6 | }
7 |
8 | .container {
9 | display: flex;
10 | gap: 32px;
11 | }
12 |
13 | main {
14 | flex: 1;
15 | width: 0;
16 | padding-top: 16px;
17 | }
18 |
19 | aside {
20 | position: sticky;
21 | top: 0;
22 | display: block;
23 | width: 240px;
24 | height: 100vh;
25 | padding: 16px 0 32px;
26 | overflow: auto;
27 | background-color: var(--bg-color);
28 | }
29 |
30 | header {
31 | position: relative;
32 | z-index: 100;
33 | display: flex;
34 | align-items: center;
35 | padding: 12px 0;
36 | border-bottom: 1px solid var(--border-color);
37 | background-color: var(--bg-translucent-color);
38 | backdrop-filter: blur(6px);
39 | }
40 |
41 | footer {
42 | padding: 32px 0;
43 | margin-top: 64px;
44 | font-size: 0.75rem;
45 | text-align: center;
46 | }
47 |
48 | .menu,
49 | .submenu {
50 | padding: 0;
51 | margin: 0;
52 | list-style-type: none;
53 |
54 | .menu-item {
55 | position: relative;
56 | display: block;
57 | padding: 8px 16px;
58 | border-radius: 8px;
59 | text-decoration: none;
60 |
61 | &:hover {
62 | background-color: var(--bg-secondary-color);
63 | }
64 | }
65 | }
66 |
67 | .menu > li + li {
68 | margin-top: 24px;
69 | }
70 |
71 | .menu-item {
72 | font-weight: 500;
73 | color: var(--fg-color);
74 |
75 | &.active {
76 | &::before {
77 | content: '';
78 | position: absolute;
79 | top: 12px;
80 | left: 0;
81 | display: inline-block;
82 | width: 4px;
83 | height: 16px;
84 | background-color: currentColor;
85 | border-radius: 2px;
86 | }
87 | }
88 |
89 | .submenu & {
90 | color: var(--fg-secondary-color);
91 | }
92 | }
93 |
94 | .brand {
95 | display: inline-block;
96 | padding: 0 16px;
97 | color: var(--heading-color);
98 | font-size: 20px;
99 | font-weight: 600;
100 | line-height: 40px;
101 | }
102 |
103 | .spacer {
104 | flex-grow: 1;
105 | }
106 |
107 | @media (width <=960px) {
108 | .container-wrap {
109 | padding: 0 32px;
110 | }
111 |
112 | aside {
113 | position: fixed;
114 | top: 0;
115 | bottom: 0;
116 | left: 0;
117 | z-index: 200;
118 | transform: translateX(-100%);
119 | transition: transform 0.3s ease-in-out;
120 |
121 | &.show {
122 | transform: none;
123 | }
124 | }
125 |
126 | .backdrop {
127 | position: fixed;
128 | top: 0;
129 | left: 0;
130 | z-index: 190;
131 | width: 100vw;
132 | height: 100vh;
133 | visibility: hidden;
134 | transition-duration: 400ms;
135 | transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
136 | transition-property: background-color, visibility;
137 |
138 | &.show {
139 | background-color: rgba(0, 0, 0, 0.5);
140 | visibility: visible;
141 | }
142 | }
143 |
144 | header {
145 | position: sticky;
146 | top: 0;
147 | }
148 |
149 | .brand {
150 | padding: 0 8px;
151 | }
152 | }
153 |
154 | @media (width <=600px) {
155 | .container-wrap {
156 | padding: 0 16px;
157 | }
158 | }
159 |
160 | @media (width > 960px) {
161 | .menu-toggle {
162 | display: none;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/projects/docs/src/app/layout/layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core';
2 | import { MatIconModule } from '@angular/material/icon';
3 | import { NavigationEnd, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
4 | import { filter } from 'rxjs';
5 | import { ThemePickerComponent } from '../shared';
6 |
7 | @Component({
8 | selector: 'app-layout',
9 | standalone: true,
10 | imports: [RouterOutlet, RouterLink, RouterLinkActive, MatIconModule, ThemePickerComponent],
11 | templateUrl: './layout.component.html',
12 | styleUrl: './layout.component.scss',
13 | host: {
14 | class: 'container-wrap',
15 | },
16 | encapsulation: ViewEncapsulation.None,
17 | })
18 | export class LayoutComponent {
19 | menu = [
20 | {
21 | name: 'Getting started',
22 | route: 'getting-started',
23 | children: [],
24 | },
25 | {
26 | name: 'Basic controls',
27 | route: 'basic-controls',
28 | children: [
29 | { name: 'Text', route: 'text' },
30 | { name: 'Number', route: 'number' },
31 | { name: 'Switch', route: 'switch' },
32 | { name: 'Slider', route: 'slider' },
33 | { name: 'Fill', route: 'fill' },
34 | { name: 'Select', route: 'select' },
35 | { name: 'Combobox', route: 'combobox' },
36 | { name: 'Button Toggle', route: 'button_toggle' },
37 | { name: 'Image Select', route: 'image_select' },
38 | { name: 'Textarea', route: 'textarea' },
39 | { name: 'Codearea', route: 'codearea' },
40 | { name: 'Hidden', route: 'hidden' },
41 | ],
42 | },
43 | {
44 | name: 'Group controls',
45 | route: 'group-controls',
46 | children: [
47 | { name: 'Group', route: 'group' },
48 | { name: 'Inline Group', route: 'inline_group' },
49 | { name: 'Tabs', route: 'tabs' },
50 | { name: 'Menu', route: 'menu' },
51 | ],
52 | },
53 | {
54 | name: 'Media controls',
55 | route: 'media-controls',
56 | children: [
57 | { name: 'Upload Settings', route: 'upload_settings' },
58 | { name: 'Image', route: 'image' },
59 | { name: 'Video', route: 'video' },
60 | { name: 'Audio', route: 'audio' },
61 | { name: 'File', route: 'file' },
62 | ],
63 | },
64 | {
65 | name: 'Conditions',
66 | route: 'conditions',
67 | children: [],
68 | },
69 | {
70 | name: 'Playground',
71 | route: 'playground',
72 | children: [],
73 | },
74 | ];
75 |
76 | menuOpened = false;
77 |
78 | constructor(private router: Router) {
79 | this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(e => {
80 | this.menuOpened = false;
81 | });
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/basic-controls/basic-controls.component.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 | Range slider
32 |
33 |
34 |
35 |
36 |
37 |
38 | Fill
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 | Multiple choice
51 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
63 | Multiple choice
64 |
65 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
80 | Multiple choice
81 |
82 |
83 |
84 |
91 | Text
92 |
93 |
94 |
101 | Font icon
102 |
103 |
104 |
111 | Image icon
112 |
113 |
114 |
115 |
116 |
117 |
118 | If you have lots of options, you can make a grid for options with the
119 | col
120 | attr.
121 |
122 |
123 |
124 |
125 |
128 |
129 |
130 |
131 |
134 |
135 |
136 |
137 |
140 |
141 |
142 |
143 | If you want to configure the language, you must define the
144 | languages
145 | at first.
146 |
147 |
148 |
159 |
160 |
161 |
162 |
163 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/basic-controls/basic-controls.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/basic-controls/basic-controls.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/basic-controls/basic-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields } from '@acrodata/gui';
2 | import { Component } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { HighlightModule } from 'ngx-highlightjs';
5 | import { ExampleViewerComponent, PageHeaderComponent } from '../../shared';
6 |
7 | @Component({
8 | selector: 'app-basic-controls',
9 | standalone: true,
10 | imports: [ExampleViewerComponent, PageHeaderComponent, FormsModule, HighlightModule],
11 | templateUrl: './basic-controls.component.html',
12 | styleUrl: './basic-controls.component.scss',
13 | })
14 | export class BasicControlsComponent {
15 | textConfig: GuiFields = {
16 | content: {
17 | type: 'text',
18 | name: 'Content',
19 | default: 'Hello world',
20 | description: 'I am description',
21 | prefix: '👋',
22 | suffix: '💖',
23 | },
24 | };
25 |
26 | numberConfig: GuiFields = {
27 | opacity: {
28 | type: 'number',
29 | name: 'Opacity',
30 | default: 0.33,
31 | min: 0,
32 | max: 1,
33 | step: 0.01,
34 | },
35 | };
36 |
37 | switchConfig: GuiFields = {
38 | visible: {
39 | type: 'switch',
40 | name: 'Visible',
41 | default: false,
42 | },
43 | };
44 |
45 | sliderConfig: GuiFields = {
46 | temperature: {
47 | type: 'slider',
48 | name: 'Temperature',
49 | mode: 'normal',
50 | default: 30,
51 | min: 0,
52 | max: 100,
53 | step: 5,
54 | suffix: '°C',
55 | },
56 | };
57 | isRangeSlider = false;
58 | toggleRangeSlider() {
59 | const { temperature } = this.sliderConfig;
60 | temperature.mode = this.isRangeSlider ? 'range' : 'normal';
61 | temperature.default = this.isRangeSlider ? [20, 60] : 30;
62 | this.sliderConfig = { ...this.sliderConfig };
63 | }
64 |
65 | fillConfig: GuiFields = {
66 | color: {
67 | type: 'fill',
68 | name: 'Color',
69 | default: '#ff0055',
70 | },
71 | };
72 |
73 | selectConfig: GuiFields = {
74 | font: {
75 | type: 'select',
76 | name: 'Font',
77 | default: 'arial',
78 | multiple: false,
79 | useFont: true,
80 | options: [
81 | { value: 'arial', label: 'Arial' },
82 | { value: 'fantasy', label: 'Fantasy' },
83 | { value: 'monospace', label: 'Monospace' },
84 | ],
85 | },
86 | };
87 | isMultiSelect = false;
88 | toggleMultiSelect() {
89 | const { font } = this.selectConfig;
90 | font.multiple = this.isMultiSelect;
91 | font.default = this.isMultiSelect ? [] : 'arial';
92 | this.selectConfig = { ...this.selectConfig };
93 | }
94 |
95 | comboboxConfig: GuiFields = {
96 | font: {
97 | type: 'combobox',
98 | name: 'Font',
99 | default: 'arial',
100 | multiple: false,
101 | useFont: true,
102 | options: [
103 | { value: 'arial', label: 'Arial' },
104 | { value: 'fantasy', label: 'Fantasy' },
105 | { value: 'monospace', label: 'Monospace' },
106 | ],
107 | },
108 | };
109 | isMultiCombobox = false;
110 | toggleMultiCombobox() {
111 | const { font } = this.comboboxConfig;
112 | font.multiple = this.isMultiCombobox;
113 | font.default = this.isMultiCombobox ? [] : 'arial';
114 | this.comboboxConfig = { ...this.comboboxConfig };
115 | }
116 |
117 | buttonToggleConfig: GuiFields = {
118 | textAlign: {
119 | type: 'buttonToggle',
120 | name: 'Align',
121 | default: 'right',
122 | multiple: false,
123 | options: [
124 | { value: 'left', label: 'Left' },
125 | { value: 'center', label: 'Center' },
126 | { value: 'right', label: 'Right' },
127 | ],
128 | },
129 | };
130 | isMultiButtonToggle = false;
131 | toggleMultiButtonToggle() {
132 | const { textAlign } = this.buttonToggleConfig;
133 | textAlign.multiple = this.isMultiButtonToggle;
134 | textAlign.default = this.isMultiButtonToggle ? [] : 'right';
135 | this.buttonToggleConfig = { ...this.buttonToggleConfig };
136 | }
137 | useIcon = 0;
138 | toggleIconButtonToggle() {
139 | const { textAlign } = this.buttonToggleConfig;
140 | textAlign.useIcon = this.useIcon > 0 ? true : false;
141 | textAlign.options = textAlign.options?.map(opt => {
142 | return {
143 | ...opt,
144 | src:
145 | this.useIcon == 1
146 | ? 'mdi mdi-format-align-' + opt.value
147 | : this.useIcon == 2
148 | ? './images/align_' + opt.value + '.png'
149 | : undefined,
150 | };
151 | });
152 | this.buttonToggleConfig = { ...this.buttonToggleConfig };
153 | }
154 |
155 | buttonToggleConfig2: GuiFields = {
156 | direction: {
157 | type: 'buttonToggle',
158 | name: 'Direction',
159 | default: 'c',
160 | options: [
161 | { value: 'nw', label: 'NW', col: 33.33 },
162 | { value: 'n', label: 'N', col: 33.33 },
163 | { value: 'ne', label: 'NE', col: 33.33 },
164 | { value: 'w', label: 'W', col: 33.33 },
165 | { value: 'c', label: 'C', col: 33.33 },
166 | { value: 'e', label: 'E', col: 33.33 },
167 | { value: 'sw', label: 'SW', col: 33.33 },
168 | { value: 's', label: 'S', col: 33.33 },
169 | { value: 'se', label: 'SE', col: 33.33 },
170 | ],
171 | },
172 | };
173 |
174 | imageSelectConfig: GuiFields = {
175 | background: {
176 | type: 'imageSelect',
177 | name: 'Background',
178 | default: 'img1',
179 | options: [
180 | {
181 | label: 'img1',
182 | value: 'img1',
183 | src: './images/icon3d1.webp',
184 | },
185 | {
186 | label: 'img2',
187 | value: 'img2',
188 | src: './images/icon3d2.webp',
189 | },
190 | {
191 | label: 'img3',
192 | value: 'img3',
193 | src: './images/icon3d3.webp',
194 | },
195 | ],
196 | },
197 | };
198 |
199 | textareaConfig: GuiFields = {
200 | foo: {
201 | type: 'textarea',
202 | name: 'Foo',
203 | default: 'I am a textarea',
204 | rows: 3,
205 | },
206 | };
207 |
208 | codeareaConfig: GuiFields = {
209 | snippet: {
210 | type: 'codearea',
211 | name: 'Snippet',
212 | default: 'console.log("Hello, World!")',
213 | language: 'js',
214 | },
215 | };
216 |
217 | hiddenConfig: GuiFields = {
218 | id: {
219 | type: 'hidden',
220 | name: 'ID',
221 | default: 1,
222 | },
223 | };
224 | }
225 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/conditions/conditions.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 | Add showIf
to control the visibility of the field.
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Parameter
18 | Description
19 |
20 |
21 |
22 |
23 | path
24 |
25 | At first, it will find the condition form control from the parent form group of current
26 | field.
27 |
28 | If the control cannot be found, it will continue to find from the root form group.
29 |
30 |
31 |
32 | operator
33 |
34 |
35 |
36 | $eq
-
37 | equal
38 |
39 |
40 | $ne
-
41 | not equal
42 |
43 |
44 | $gt
-
45 | greater than
46 |
47 |
48 | $lt
-
49 | less than
50 |
51 |
52 | $gte
-
53 | greater than or equal
54 |
55 |
56 | $lte
-
57 | less than or equal
58 |
59 |
60 | $in
-
61 | in the set
62 |
63 |
64 | $nin
-
65 | not in the set
66 |
67 |
68 |
69 |
70 |
71 | value
72 | The value of condition form control.
73 |
74 |
75 | logicalType
76 |
77 |
78 |
79 | $and
-
80 | All the conditions should be true
81 |
82 |
83 | $or
-
84 | As long as one condition is true
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
95 |
96 |
97 |
98 |
99 | Demo2
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/conditions/conditions.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/conditions/conditions.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/conditions/conditions.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields } from '@acrodata/gui';
2 | import { ChangeDetectionStrategy, Component } from '@angular/core';
3 | import { HighlightModule } from 'ngx-highlightjs';
4 | import { ExampleViewerComponent, PageHeaderComponent } from '../../shared';
5 |
6 | @Component({
7 | selector: 'app-conditions',
8 | standalone: true,
9 | imports: [PageHeaderComponent, ExampleViewerComponent, HighlightModule],
10 | templateUrl: './conditions.component.html',
11 | styleUrl: './conditions.component.scss',
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | })
14 | export class ConditionsComponent {
15 | ruleJson = `{
16 | ...
17 | "showIf": {
18 | "conditions": [
19 | ["path", "operator", "value"],
20 | ...
21 | ],
22 | "logicalType": "$or"
23 | },
24 | }`;
25 |
26 | demo1: GuiFields = {
27 | a: {
28 | type: 'group',
29 | name: 'groupA',
30 | children: {
31 | switchA: {
32 | type: 'switch',
33 | name: 'switchA',
34 | default: true,
35 | },
36 | textA: {
37 | type: 'text',
38 | name: 'textA',
39 | default: 'A',
40 | showIf: {
41 | conditions: [
42 | ['a.switchA', '$ne', false],
43 | ['b.switchB', '$eq', true],
44 | ],
45 | logicalType: '$or',
46 | },
47 | },
48 | },
49 | expanded: true,
50 | },
51 | b: {
52 | type: 'group',
53 | name: 'groupB',
54 | children: {
55 | switchB: {
56 | type: 'switch',
57 | name: 'switchB',
58 | default: false,
59 | },
60 | textB: {
61 | type: 'text',
62 | name: 'textB',
63 | default: 'B',
64 | },
65 | },
66 | expanded: true,
67 | },
68 | };
69 |
70 | demo2: GuiFields = {
71 | array: {
72 | name: 'Array',
73 | type: 'tabs',
74 | default: [
75 | { switch: true, text: 'A' },
76 | { switch: true, text: 'B' },
77 | ],
78 | template: {
79 | name: 'No.<%= i + 1%>',
80 | children: {
81 | switch: {
82 | type: 'switch',
83 | name: 'switch',
84 | default: true,
85 | },
86 | text: {
87 | type: 'text',
88 | name: 'text',
89 | showIf: {
90 | conditions: [['switch', '$eq', true]],
91 | },
92 | },
93 | },
94 | },
95 | expanded: true,
96 | mode: 'list',
97 | },
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/getting-started/getting-started.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
10 |
11 |
14 |
15 | Define a theme with Angular Material's theming system.
16 | More about theming.
17 |
18 |
19 |
28 |
29 | 🚨 If you use the Angular Material as default library and have included all component themes,
30 | there's no need to include the GUI themes anymore.
31 |
32 |
36 |
37 |
40 |
41 |
62 |
63 |
66 |
67 |
70 |
71 |
72 |
73 |
74 | Name
75 | Type
76 | Required
77 | Description
78 |
79 |
80 |
81 |
82 | config
83 | GuiFields
84 | yes
85 | The field configurations for building the form.
86 |
87 |
88 | model
89 | any
90 | no
91 | The model to be represented by the form.
92 |
93 |
94 | form
95 | FormGroup
96 | no
97 | The form instance which allow to track model value and validation status.
98 |
99 |
100 |
101 |
102 |
105 |
106 |
107 |
108 |
109 | Name
110 | Description
111 |
112 |
113 |
114 |
115 | modelChange
116 | Fired on model value change.
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/getting-started/getting-started.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/getting-started/getting-started.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/getting-started/getting-started.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { PageHeaderComponent } from '../../shared';
3 | import { HighlightModule } from 'ngx-highlightjs';
4 |
5 | @Component({
6 | selector: 'app-getting-started',
7 | standalone: true,
8 | imports: [HighlightModule, PageHeaderComponent],
9 | templateUrl: './getting-started.component.html',
10 | styleUrl: './getting-started.component.scss',
11 | })
12 | export class GettingStartedComponent {}
13 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/group-controls/group-controls.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 | Tabs
18 |
19 |
20 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/group-controls/group-controls.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/group-controls/group-controls.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/group-controls/group-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields } from '@acrodata/gui';
2 | import { Component } from '@angular/core';
3 | import { ExampleViewerComponent, PageHeaderComponent } from '../../shared';
4 |
5 | @Component({
6 | selector: 'app-group-controls',
7 | standalone: true,
8 | imports: [ExampleViewerComponent, PageHeaderComponent],
9 | templateUrl: './group-controls.component.html',
10 | styleUrl: './group-controls.component.scss',
11 | })
12 | export class GroupControlsComponent {
13 | groupConfig: GuiFields = {
14 | size: {
15 | type: 'group',
16 | name: 'Size',
17 | children: {
18 | width: {
19 | name: 'Width',
20 | type: 'number',
21 | default: 1920,
22 | suffix: 'px',
23 | },
24 | height: {
25 | name: 'Height',
26 | type: 'number',
27 | default: 1080,
28 | suffix: 'px',
29 | },
30 | },
31 | expanded: true,
32 | },
33 | };
34 |
35 | inlineConfig: GuiFields = {
36 | offset: {
37 | type: 'inline',
38 | name: 'Offset',
39 | children: {
40 | x: {
41 | name: 'X',
42 | type: 'number',
43 | default: 0,
44 | col: 50,
45 | },
46 | y: {
47 | name: 'Y',
48 | type: 'number',
49 | default: 0,
50 | col: 50,
51 | },
52 | },
53 | },
54 | };
55 |
56 | tabs1Config: GuiFields = {
57 | series: {
58 | type: 'tabs',
59 | name: 'Series',
60 | description: 'Dynamic add/delete',
61 | default: [
62 | { id: 1, name: 'bar' },
63 | { id: 2, name: 'foo' },
64 | ],
65 | template: {
66 | name: 'No.<%= i + 1 %>',
67 | children: {
68 | id: {
69 | type: 'number',
70 | name: 'ID',
71 | },
72 | name: {
73 | type: 'text',
74 | name: 'Name',
75 | },
76 | },
77 | },
78 | expanded: true,
79 | },
80 | };
81 |
82 | tabs2Config: GuiFields = {
83 | misc: {
84 | type: 'tabs',
85 | name: 'Misc',
86 | mode: 'list',
87 | children: [
88 | {
89 | type: 'tab',
90 | name: 'Full Name',
91 | children: {
92 | firstName: {
93 | type: 'text',
94 | name: 'First Name',
95 | default: 'James',
96 | },
97 | lastName: {
98 | type: 'text',
99 | name: 'Last Name',
100 | default: 'Bob',
101 | },
102 | },
103 | },
104 | {
105 | type: 'tab',
106 | name: 'Contact',
107 | children: {
108 | phone: {
109 | type: 'text',
110 | name: 'Phone',
111 | default: '5550100',
112 | },
113 | },
114 | },
115 | ],
116 | expanded: true,
117 | },
118 | };
119 |
120 | tabs3Config: GuiFields = {
121 | transitionProperty: {
122 | type: 'tabs',
123 | name: 'Transition property',
124 | default: ['color', 'width'],
125 | template: {
126 | type: 'text',
127 | name: 'Prop <%= i + 1 %>',
128 | },
129 | expanded: true,
130 | },
131 | };
132 |
133 | tabs4Config: GuiFields = {
134 | coordinate: {
135 | name: 'Coordinate',
136 | type: 'tabs',
137 | mode: 'list',
138 | children: [
139 | {
140 | type: 'slider',
141 | name: 'Latitude',
142 | min: -90,
143 | max: 90,
144 | step: 0.01,
145 | default: 39.92,
146 | },
147 | {
148 | type: 'slider',
149 | name: 'Longitude',
150 | min: -180,
151 | max: 180,
152 | step: 0.01,
153 | default: 116.41,
154 | },
155 | ],
156 | expanded: true,
157 | },
158 | };
159 |
160 | menuConfig: GuiFields = {
161 | options: {
162 | type: 'menu',
163 | name: 'Menu',
164 | children: {
165 | menuA: {
166 | type: 'menuItem',
167 | name: 'Menu A',
168 | children: {
169 | textarea: {
170 | type: 'textarea',
171 | name: 'Comments',
172 | default: 'Hello world',
173 | },
174 | color: {
175 | type: 'fill',
176 | name: 'Color',
177 | default: '#50b4ff99',
178 | },
179 | },
180 | },
181 | menuB: {
182 | type: 'menu',
183 | name: 'Menu B',
184 | children: {
185 | menuB1: {
186 | type: 'menuItem',
187 | name: 'Menu B1',
188 | children: {
189 | label: {
190 | type: 'text',
191 | name: 'Label',
192 | default: 'Hello',
193 | },
194 | },
195 | },
196 | menuB2: {
197 | type: 'menuItem',
198 | name: 'Menu B2',
199 | children: {
200 | display: {
201 | type: 'switch',
202 | name: 'Display',
203 | default: true,
204 | },
205 | },
206 | },
207 | },
208 | },
209 | },
210 | },
211 | };
212 | }
213 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | Built on top of Angular
9 | Reactive Forms
10 | JSON powered config
11 | Easy to understand config and model
12 | Uses Angular Material as basic UI library
13 | A11y support
14 | RTL support
15 |
16 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/home/home.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/home/home.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { GradientGeneratorComponent } from '../../shared';
3 |
4 | @Component({
5 | selector: 'app-home',
6 | standalone: true,
7 | imports: [GradientGeneratorComponent],
8 | templateUrl: './home.component.html',
9 | styleUrl: './home.component.scss',
10 | })
11 | export class HomeComponent {}
12 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/media-controls/media-controls.component.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
11 | If you want to use the upload feature, you should replace the
12 | GuiFileUploaderConfig
13 | with your own uploading logic. The returns of upload function must be a stream of file URL (or
14 | null).
15 |
16 |
17 |
47 |
48 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
67 | File
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/media-controls/media-controls.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/docs/src/app/pages/media-controls/media-controls.component.scss
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/media-controls/media-controls.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields } from '@acrodata/gui';
2 | import { Component } from '@angular/core';
3 | import { HighlightModule } from 'ngx-highlightjs';
4 | import { ExampleViewerComponent, PageHeaderComponent } from '../../shared';
5 |
6 | @Component({
7 | selector: 'app-media-controls',
8 | standalone: true,
9 | imports: [ExampleViewerComponent, PageHeaderComponent, HighlightModule],
10 | templateUrl: './media-controls.component.html',
11 | styleUrl: './media-controls.component.scss',
12 | })
13 | export class MediaControlsComponent {
14 | imageConfig: GuiFields = {
15 | image: {
16 | type: 'image',
17 | name: 'Upload Image',
18 | default:
19 | 'https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg',
20 | },
21 | };
22 |
23 | videoConfig: GuiFields = {
24 | video: {
25 | type: 'video',
26 | name: 'Upload Video',
27 | default: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
28 | },
29 | };
30 |
31 | audioConfig: GuiFields = {
32 | audio: {
33 | type: 'audio',
34 | name: 'Upload Audio',
35 | default: 'https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3',
36 | },
37 | };
38 |
39 | fileConfig: GuiFields = {
40 | file: {
41 | type: 'file',
42 | name: 'Upload File',
43 | default: '',
44 | },
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/playground/playground.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/playground/playground.component.scss:
--------------------------------------------------------------------------------
1 | :host ::ng-deep {
2 | .mtx-split>.mtx-split-gutter {
3 | background-color: var(--border-color);
4 | }
5 |
6 | >mtx-split {
7 | position: relative;
8 | height: calc(100vh - 96px);
9 | border: 1px solid var(--border-color);
10 | border-radius: 8px;
11 | }
12 | }
13 |
14 | .preview {
15 | mtx-split-pane {
16 | padding: 8px;
17 | }
18 |
19 | textarea {
20 | display: block;
21 | width: 100%;
22 | height: 100%;
23 | border: none;
24 | outline: none;
25 | resize: none;
26 | font-size: 12px;
27 | color: var(--fg-color);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/projects/docs/src/app/pages/playground/playground.component.ts:
--------------------------------------------------------------------------------
1 | import { CodeEditor } from '@acrodata/code-editor';
2 | import { GuiFields, GuiForm } from '@acrodata/gui';
3 | import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
4 | import { CommonModule } from '@angular/common';
5 | import {
6 | ChangeDetectionStrategy,
7 | ChangeDetectorRef,
8 | Component,
9 | DestroyRef,
10 | OnInit,
11 | inject,
12 | } from '@angular/core';
13 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14 | import { FormsModule } from '@angular/forms';
15 | import { json, jsonParseLinter } from '@codemirror/lang-json';
16 | import { lintGutter, linter } from '@codemirror/lint';
17 | import { MtxSplitModule } from '@ng-matero/extensions/split';
18 |
19 | @Component({
20 | selector: 'app-playground',
21 | standalone: true,
22 | imports: [CommonModule, FormsModule, MtxSplitModule, CodeEditor, GuiForm],
23 | templateUrl: './playground.component.html',
24 | styleUrl: './playground.component.scss',
25 | changeDetection: ChangeDetectionStrategy.OnPush,
26 | })
27 | export class PlaygroundComponent implements OnInit {
28 | config: GuiFields = {
29 | title: {
30 | type: 'text',
31 | name: 'Title',
32 | default: 'Hello, World!',
33 | },
34 | align: {
35 | type: 'buttonToggle',
36 | name: 'Align',
37 | options: [
38 | { value: 'left', label: 'Left' },
39 | { value: 'center', label: 'Center' },
40 | { value: 'right', label: 'Right' },
41 | ],
42 | default: 'center',
43 | },
44 | size: {
45 | type: 'group',
46 | name: 'Size',
47 | children: {
48 | width: {
49 | type: 'number',
50 | name: 'Width',
51 | default: 100,
52 | },
53 | height: {
54 | type: 'number',
55 | name: 'Height',
56 | default: 100,
57 | },
58 | },
59 | },
60 | };
61 | model: any = {};
62 |
63 | configStr = '';
64 |
65 | isMobile = false;
66 |
67 | private readonly destroy = inject(DestroyRef);
68 |
69 | extensions = [json(), linter(jsonParseLinter()), lintGutter()];
70 |
71 | constructor(
72 | private breakpointObserver: BreakpointObserver,
73 | private cdr: ChangeDetectorRef
74 | ) {}
75 |
76 | ngOnInit(): void {
77 | this.breakpointObserver
78 | .observe([Breakpoints.XSmall])
79 | .pipe(takeUntilDestroyed(this.destroy))
80 | .subscribe(result => {
81 | this.isMobile = result.matches;
82 | this.cdr.detectChanges();
83 | });
84 |
85 | this.configStr = JSON.stringify(this.config, null, 2);
86 | }
87 |
88 | onConfigChange() {
89 | this.config = JSON.parse(this.configStr);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/example-viewer/example-viewer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/example-viewer/example-viewer.component.scss:
--------------------------------------------------------------------------------
1 | .demo-wrapper {
2 | position: relative;
3 | display: flex;
4 | margin: 16px 0;
5 | background-color: var(--bg-secondary-color);
6 | border-radius: 8px;
7 | }
8 |
9 | .demo-code {
10 | flex: 1;
11 | max-height: 600px;
12 | overflow: auto;
13 |
14 | pre {
15 | margin: 0;
16 | }
17 | }
18 |
19 | .demo-result {
20 | width: 320px;
21 | padding: 32px;
22 | margin: 0 auto;
23 | }
24 |
25 | .demo-console {
26 | padding: 4px;
27 | margin-top: 16px;
28 | border: 1px solid var(--border-color);
29 | border-radius: 4px;
30 |
31 | textarea {
32 | display: block;
33 | width: 100%;
34 | padding: 0 4px;
35 | background-color: var(--console-bg-color);
36 | color: var(--console-color);
37 | border-width: 0;
38 | font-size: 12px;
39 | line-height: 20px;
40 | appearance: none;
41 | outline: none;
42 | }
43 | }
44 |
45 | @media (width < 600px){
46 | .demo-wrapper {
47 | flex-direction: column;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/example-viewer/example-viewer.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields, GuiModule } from '@acrodata/gui';
2 | import { TextFieldModule } from '@angular/cdk/text-field';
3 | import { CommonModule } from '@angular/common';
4 | import { Component, Input } from '@angular/core';
5 | import { HighlightModule } from 'ngx-highlightjs';
6 |
7 | @Component({
8 | selector: 'app-example-viewer',
9 | standalone: true,
10 | imports: [CommonModule, GuiModule, TextFieldModule, HighlightModule],
11 | templateUrl: './example-viewer.component.html',
12 | styleUrl: './example-viewer.component.scss',
13 | })
14 | export class ExampleViewerComponent {
15 | @Input() config: GuiFields = {};
16 |
17 | model = {};
18 | }
19 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/gradient-generator/gradient-generator.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Acrodata GUI
4 |
JSON powered GUI for configurable panels
5 |
6 |
7 |
8 |
Gradient Generator
9 |
10 |
11 |
12 |
13 | @for (m of presets; track m; let i = $index) {
14 |
15 |
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/gradient-generator/gradient-generator.component.scss:
--------------------------------------------------------------------------------
1 | .jumbotron {
2 | display: flex;
3 | min-height: 30vw;
4 | padding: 64px;
5 | margin-bottom: 24px;
6 | border-radius: 8px;
7 | }
8 |
9 | .text {
10 | flex: 1;
11 | }
12 |
13 | h1,
14 | p {
15 | color: rgba(255, 255, 255, 1);
16 | text-shadow: 4px 4px 6px rgba(0, 0, 0, 0.85);
17 | }
18 |
19 | .gui-wrapper {
20 | width: 260px;
21 | }
22 |
23 | .gui-heading {
24 | position: relative;
25 | z-index: 1;
26 | padding: 0 12px;
27 | border-radius: 4px 4px 0 0;
28 | background-color: var(--bg-color);
29 | border-bottom: 1px solid var(--border-color);
30 | text-align: center;
31 | line-height: 32px;
32 | font-size: 12px;
33 | }
34 |
35 | .thumbnail {
36 | width: 64px;
37 | height: 36px;
38 | background-color: transparent;
39 | border: 1px solid var(--border-color);
40 | border-radius: 4px;
41 | cursor: pointer;
42 | }
43 |
44 | @media (width <= 600px) {
45 | .jumbotron {
46 | flex-direction: column;
47 | align-items: center;
48 | padding: 16px;
49 | }
50 |
51 | .text {
52 | margin-bottom: 32px;
53 | text-align: center;
54 | }
55 | }
56 |
57 | button + button {
58 | margin-left: 8px;
59 | }
60 |
61 | :host ::ng-deep {
62 | .gui-form {
63 | border-top-left-radius: 0;
64 | border-top-right-radius: 0;
65 |
66 | .mat-expansion-panel {
67 | position: relative;
68 | padding-left: 4px;
69 |
70 | &::before {
71 | content: '';
72 | position: absolute;
73 | top: 10px;
74 | bottom: 10px;
75 | left: 2px;
76 | z-index: 2;
77 | display: block;
78 | width: 4px;
79 | border: 2px solid var(--mat-expansion-container-text-color);
80 | border-right: none;
81 | opacity: 0.4;
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/gradient-generator/gradient-presets.ts:
--------------------------------------------------------------------------------
1 | export interface IBackground {
2 | gradients: IGradient[];
3 | blendMode: string[];
4 | repeat: string;
5 | }
6 |
7 | export interface IGradient {
8 | type: 'linear' | 'radial' | 'conic';
9 | repeating?: boolean;
10 | reverse?: boolean;
11 | angle?: number;
12 | radialBase?: string;
13 | conicBase?: string;
14 | stops?: IColorStop[];
15 | position?: { x?: string; y?: string };
16 | size?: { w?: string; h?: string };
17 | }
18 |
19 | export interface IColorStop {
20 | color: string;
21 | offset: string;
22 | }
23 |
24 | export const gradientPresets: IBackground[] = [
25 | {
26 | gradients: [
27 | {
28 | type: 'linear',
29 | repeating: true,
30 | reverse: false,
31 | angle: 45,
32 | stops: [
33 | { color: 'rgba(75, 75, 75, 0.5)', offset: '0%' },
34 | { color: 'rgba(220, 235, 255, 0.75)', offset: '50%' },
35 | ],
36 | position: {},
37 | size: { w: '100px', h: '100px' },
38 | },
39 | {
40 | type: 'linear',
41 | repeating: true,
42 | reverse: false,
43 | angle: 135,
44 | stops: [
45 | { color: 'rgba(5, 30, 50, 0.75)', offset: '0%' },
46 | { color: 'rgba(115, 150, 255, 0.5)', offset: '50%' },
47 | ],
48 | position: {},
49 | size: {},
50 | },
51 | ],
52 | blendMode: ['overlay'],
53 | repeat: 'repeat',
54 | },
55 | {
56 | gradients: [
57 | {
58 | type: 'conic',
59 | repeating: true,
60 | conicBase: '',
61 | stops: [
62 | { color: '#023047', offset: '0 25%' },
63 | { color: '#00000000', offset: '0 50%' },
64 | ],
65 | position: { x: '0', y: '0' },
66 | size: { w: '50px', h: '86.5px' },
67 | },
68 | {
69 | type: 'conic',
70 | repeating: true,
71 | conicBase: 'from -30deg',
72 | stops: [
73 | { color: '#fb8500', offset: '0 16.67%' },
74 | { color: '#023047', offset: '0 50%' },
75 | ],
76 | position: { x: '0', y: '0' },
77 | size: { w: '25px', h: '43.25px' },
78 | },
79 | ],
80 | blendMode: ['normal'],
81 | repeat: 'repeat',
82 | },
83 | {
84 | gradients: [
85 | {
86 | type: 'conic',
87 | conicBase: 'from -60deg at 50% 33.33%',
88 | stops: [
89 | { color: '#d9d9d9', offset: '0 120deg' },
90 | { color: '#00000000', offset: '0' },
91 | ],
92 | position: {},
93 | size: { w: '142.024px', h: '82px' },
94 | },
95 | {
96 | type: 'conic',
97 | conicBase: 'from 120deg at 50% 66.67%',
98 | stops: [
99 | { color: '#d9d9d9', offset: '0 120deg' },
100 | { color: '#00000000', offset: '0' },
101 | ],
102 | position: {},
103 | size: {},
104 | },
105 | {
106 | type: 'conic',
107 | conicBase: 'from 60deg at 66.67%',
108 | stops: [
109 | { color: '#d9d9d9', offset: '60deg' },
110 | { color: '#ffffff', offset: '0 120deg' },
111 | { color: '#00000000', offset: '0' },
112 | ],
113 | position: {},
114 | size: {},
115 | },
116 | {
117 | type: 'conic',
118 | conicBase: 'from 180deg at 33.33%',
119 | stops: [
120 | { color: '#b2b2b2', offset: '60deg' },
121 | { color: '#d9d9d9', offset: '0 120deg' },
122 | { color: '#00000000', offset: '0' },
123 | ],
124 | position: {},
125 | size: {},
126 | },
127 | {
128 | type: 'linear',
129 | angle: 90,
130 | stops: [
131 | { color: '#b2b2b2', offset: '16.7%' },
132 | { color: '#ffffff', offset: '0 50%' },
133 | { color: '#b2b2b2', offset: '0 83.33%' },
134 | { color: '#ffffff', offset: '0' },
135 | ],
136 | position: {},
137 | size: {},
138 | },
139 | ],
140 | blendMode: ['normal'],
141 | repeat: 'repeat',
142 | },
143 | {
144 | gradients: [
145 | {
146 | type: 'linear',
147 | repeating: true,
148 | angle: 45,
149 | stops: [
150 | { color: 'rgba(0,0,0,.2)', offset: '0' },
151 | { color: 'transparent', offset: '5px 50px' },
152 | ],
153 | position: {},
154 | size: {},
155 | },
156 | {
157 | type: 'linear',
158 | repeating: true,
159 | angle: -45,
160 | stops: [
161 | { color: 'rgba(0,0,0,.2)', offset: '0' },
162 | { color: 'transparent', offset: '5px 50px' },
163 | ],
164 | position: {},
165 | size: {},
166 | },
167 | {
168 | type: 'linear',
169 | repeating: true,
170 | angle: 45,
171 | stops: [
172 | { color: '#000', offset: '0 10px' },
173 | { color: '#333', offset: '0 20px' },
174 | { color: '#d79033', offset: '0 30px' },
175 | { color: '#d7d7d3', offset: '0 40px' },
176 | { color: '#e9e9ea', offset: '0 50px' },
177 | ],
178 | position: {},
179 | size: {},
180 | },
181 | {
182 | type: 'linear',
183 | repeating: true,
184 | angle: -45,
185 | stops: [
186 | { color: '#000', offset: '0 10px' },
187 | { color: '#333', offset: '0 20px' },
188 | { color: '#d79033', offset: '0 30px' },
189 | { color: '#d7d7d3', offset: '0 40px' },
190 | { color: '#e9e9ea', offset: '0 50px' },
191 | ],
192 | position: {},
193 | size: {},
194 | },
195 | ],
196 | blendMode: ['multiply', 'multiply', 'lighten'],
197 | repeat: 'repeat',
198 | },
199 | ];
200 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './example-viewer/example-viewer.component';
2 | export * from './page-header/page-header.component';
3 | export * from './gradient-generator/gradient-generator.component';
4 | export * from './theme-picker/theme-picker';
5 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/page-header/page-header.component.html:
--------------------------------------------------------------------------------
1 |
9 | @if (config) {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/page-header/page-header.component.scss:
--------------------------------------------------------------------------------
1 | .page-header {
2 | position: relative;
3 | display: flex;
4 | align-items: center;
5 | justify-content: space-between;
6 | padding: 64px;
7 | background-color: var(--bg-secondary-color);
8 | border-radius: 8px;
9 | }
10 |
11 | .page-header-text {
12 | h1 {
13 | margin: 0;
14 | }
15 |
16 | p {
17 | margin-top: 16px;
18 | margin-bottom: 0;
19 | }
20 | }
21 |
22 | @media (width < 600px){
23 | .page-header {
24 | flex-direction: column;
25 | padding: 64px 16px;
26 | text-align: center;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/page-header/page-header.component.ts:
--------------------------------------------------------------------------------
1 | import { GuiFields } from '@acrodata/gui';
2 | import { Component, Input, ViewEncapsulation } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'app-page-header',
6 | standalone: true,
7 | templateUrl: './page-header.component.html',
8 | styleUrl: './page-header.component.scss',
9 | encapsulation: ViewEncapsulation.None,
10 | host: {
11 | class: 'page-header',
12 | },
13 | })
14 | export class PageHeaderComponent {
15 | @Input() pageTitle = '';
16 | @Input() pageContent = '';
17 | @Input() config: GuiFields | null = null;
18 | }
19 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/style-manager/style-manager.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | /**
4 | * Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
5 | * removed or changed later.
6 | */
7 | @Injectable({ providedIn: 'root' })
8 | export class StyleManager {
9 | /**
10 | * Set the stylesheet with the specified key.
11 | */
12 | setStyle(key: string, href: string) {
13 | getLinkElementForKey(key).setAttribute('href', href);
14 | }
15 |
16 | /**
17 | * Remove the stylesheet with the specified key.
18 | */
19 | removeStyle(key: string) {
20 | const existingLinkElement = getExistingLinkElementByKey(key);
21 | if (existingLinkElement) {
22 | document.head.removeChild(existingLinkElement);
23 | }
24 | }
25 | }
26 |
27 | function getLinkElementForKey(key: string) {
28 | return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
29 | }
30 |
31 | function getExistingLinkElementByKey(key: string) {
32 | return document.head.querySelector(`link[rel="stylesheet"].${getClassNameForKey(key)}`);
33 | }
34 |
35 | function createLinkElementWithKey(key: string) {
36 | const linkEl = document.createElement('link');
37 | linkEl.setAttribute('rel', 'stylesheet');
38 | linkEl.classList.add(getClassNameForKey(key));
39 | document.head.appendChild(linkEl);
40 | return linkEl;
41 | }
42 |
43 | function getClassNameForKey(key: string) {
44 | return `style-manager-${key}`;
45 | }
46 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/theme-picker/theme-picker.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
14 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/theme-picker/theme-picker.scss:
--------------------------------------------------------------------------------
1 | .theme-picker-toggle {
2 | min-width: 64px;
3 |
4 | mat-icon {
5 | vertical-align: middle;
6 | }
7 |
8 | &::after {
9 | display: inline-block;
10 | margin-left: .4em;
11 | vertical-align: .1em;
12 | content: '';
13 | border-top: .3em solid;
14 | border-right: .3em solid transparent;
15 | border-bottom: 0;
16 | border-left: .3em solid transparent;
17 | }
18 | }
19 |
20 | .theme-picker {
21 | position: relative;
22 | }
23 |
24 | .theme-picker-menu {
25 | position: absolute;
26 | right: 0;
27 | z-index: 100;
28 | display: none;
29 | padding: 4px;
30 | margin: 0;
31 | list-style: none;
32 | border: 1px solid var(--border-color);
33 | background-color: var(--bg-color);
34 | border-radius: 8px;
35 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
36 |
37 | &.show {
38 | display: block;
39 | }
40 |
41 | li+li {
42 | margin-top: 2px;
43 | }
44 | }
45 |
46 | .theme-picker-item {
47 | display: flex;
48 | gap: 4px;
49 | width: 100%;
50 | padding: 4px 16px 4px 8px;
51 | border-radius: 4px;
52 | white-space: nowrap;
53 | font-size: 14px;
54 | background-color: transparent;
55 | color: inherit;
56 | border: none;
57 | cursor: pointer;
58 |
59 | &:hover {
60 | background-color: var(--bg-secondary-color);
61 | }
62 |
63 | &::before {
64 | content: '';
65 | display: inline-block;
66 | width: 20px;
67 | text-align: center;
68 | }
69 |
70 | &.active::before {
71 | content: '✔️';
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/theme-picker/theme-picker.ts:
--------------------------------------------------------------------------------
1 | import { GuiCodeareaConfig } from '@acrodata/gui';
2 | import { LiveAnnouncer } from '@angular/cdk/a11y';
3 | import {
4 | ChangeDetectionStrategy,
5 | ChangeDetectorRef,
6 | Component,
7 | OnDestroy,
8 | OnInit,
9 | ViewEncapsulation,
10 | } from '@angular/core';
11 | import { MatIconModule } from '@angular/material/icon';
12 | import { ActivatedRoute, ParamMap } from '@angular/router';
13 | import { fromEvent, Subscription } from 'rxjs';
14 | import { filter, map } from 'rxjs/operators';
15 | import { StyleManager } from '../style-manager/style-manager';
16 | import { DocsSiteTheme, ThemeStorage } from './theme-storage';
17 |
18 | @Component({
19 | selector: 'app-theme-picker',
20 | templateUrl: 'theme-picker.html',
21 | styleUrl: 'theme-picker.scss',
22 | host: {
23 | class: 'theme-picker',
24 | },
25 | changeDetection: ChangeDetectionStrategy.OnPush,
26 | encapsulation: ViewEncapsulation.None,
27 | standalone: true,
28 | imports: [MatIconModule],
29 | })
30 | export class ThemePickerComponent implements OnInit, OnDestroy {
31 | private queryParamSubscription = Subscription.EMPTY;
32 | currentTheme: DocsSiteTheme | undefined;
33 |
34 | // The below colors need to align with the themes defined in theme-picker.scss
35 | themes: DocsSiteTheme[] = [
36 | {
37 | color: '#ffd9e1',
38 | displayName: '🌞 M3 (Rose & Red)',
39 | name: 'rose-red',
40 | },
41 | {
42 | color: '#d7e3ff',
43 | displayName: '🌞 M3 (Azure & Blue)',
44 | name: 'azure-blue',
45 | isDefault: true,
46 | },
47 | {
48 | color: '#810081',
49 | displayName: '🌚 M3 (Magenta & Violet)',
50 | name: 'magenta-violet',
51 | isDark: true,
52 | },
53 | {
54 | color: '#004f4f',
55 | displayName: '🌚 M3 (Cyan & Orange)',
56 | name: 'cyan-orange',
57 | isDark: true,
58 | },
59 | {
60 | color: '#673AB7',
61 | displayName: '🌞 M2 (Deep Purple & Amber)',
62 | name: 'deeppurple-amber',
63 | },
64 | {
65 | color: '#3F51B5',
66 | displayName: '🌞 M2 (Indigo & Pink)',
67 | name: 'indigo-pink',
68 | },
69 | {
70 | color: '#E91E63',
71 | displayName: '🌚 M2 (Pink & Blue-grey)',
72 | name: 'pink-bluegrey',
73 | isDark: true,
74 | },
75 | {
76 | color: '#9C27B0',
77 | displayName: '🌚 M2 (Purple & Green)',
78 | name: 'purple-green',
79 | isDark: true,
80 | },
81 | ];
82 |
83 | showMenu = false;
84 |
85 | constructor(
86 | private styleManager: StyleManager,
87 | private themeStorage: ThemeStorage,
88 | private activatedRoute: ActivatedRoute,
89 | private liveAnnouncer: LiveAnnouncer,
90 | private cdr: ChangeDetectorRef,
91 | private codeareaCfg: GuiCodeareaConfig
92 | ) {
93 | const themeName = this.themeStorage.getStoredThemeName();
94 | if (themeName) {
95 | this.selectTheme(themeName);
96 | } else {
97 | this.themes.find(theme => {
98 | if (theme.isDefault === true) {
99 | this.selectTheme(theme.name);
100 | }
101 | });
102 | }
103 | }
104 |
105 | ngOnInit() {
106 | this.queryParamSubscription = this.activatedRoute.queryParamMap
107 | .pipe(map((params: ParamMap) => params.get('theme')))
108 | .subscribe((themeName: string | null) => {
109 | if (themeName) {
110 | this.selectTheme(themeName);
111 | }
112 | });
113 |
114 | fromEvent(document, 'click')
115 | .pipe(filter(event => event.target != document.querySelector('.theme-picker-menu')))
116 | .subscribe(v => {
117 | this.showMenu = false;
118 | this.cdr.detectChanges();
119 | });
120 | }
121 |
122 | ngOnDestroy() {
123 | this.queryParamSubscription.unsubscribe();
124 | }
125 |
126 | selectTheme(themeName: string) {
127 | const theme =
128 | this.themes.find(currentTheme => currentTheme.name === themeName) ||
129 | this.themes.find(currentTheme => currentTheme.isDefault)!;
130 |
131 | this.currentTheme = theme;
132 |
133 | this.styleManager.setStyle('theme1', `themes/material/${theme.name}.css`);
134 | this.styleManager.setStyle('theme2', `themes/extensions/${theme.name}.css`);
135 |
136 | if (this.currentTheme) {
137 | this.liveAnnouncer.announce(`${theme.displayName} theme selected.`, 'polite', 3000);
138 | this.themeStorage.storeTheme(this.currentTheme);
139 | }
140 |
141 | this.codeareaCfg.theme = theme.isDark ? 'dark' : 'light';
142 | this.codeareaCfg.changes.next();
143 |
144 | this.showMenu = false;
145 | }
146 |
147 | toggleMenu(e: MouseEvent) {
148 | e.stopPropagation();
149 | this.showMenu = !this.showMenu;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/projects/docs/src/app/shared/theme-picker/theme-storage.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, EventEmitter } from '@angular/core';
2 |
3 | export interface DocsSiteTheme {
4 | name: string;
5 | displayName?: string;
6 | color: string;
7 | isDefault?: boolean;
8 | isDark?: boolean;
9 | }
10 |
11 | @Injectable({ providedIn: 'root' })
12 | export class ThemeStorage {
13 | static storageKey = 'docs-theme';
14 |
15 | onThemeUpdate: EventEmitter = new EventEmitter();
16 |
17 | storeTheme(theme: DocsSiteTheme) {
18 | try {
19 | window.localStorage[ThemeStorage.storageKey] = theme.name;
20 | } catch {
21 | //
22 | }
23 |
24 | this.onThemeUpdate.emit(theme);
25 | }
26 |
27 | getStoredThemeName(): string | null {
28 | try {
29 | return window.localStorage[ThemeStorage.storageKey] || null;
30 | } catch {
31 | return null;
32 | }
33 | }
34 |
35 | clearStorage() {
36 | try {
37 | window.localStorage.removeItem(ThemeStorage.storageKey);
38 | } catch {
39 | //
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/projects/docs/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Acrodata GUI
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/projects/docs/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
6 |
--------------------------------------------------------------------------------
/projects/docs/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | @use './styles/reboot';
4 | // @use './styles/app-theme';
5 |
--------------------------------------------------------------------------------
/projects/docs/src/styles/_app-theme.scss:
--------------------------------------------------------------------------------
1 | @use '@angular/material' as mat;
2 | @use '@ng-matero/extensions' as mtx;
3 |
4 | @include mat.core();
5 |
6 | $primary: mat.m2-define-palette(mat.$m2-indigo-palette, 500);
7 | $accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
8 | $theme: mat.m2-define-light-theme((
9 | color: (
10 | primary: $primary,
11 | accent: $accent,
12 | ),
13 | typography: mat.m2-define-typography-config(),
14 | density: 0,
15 | ));
16 |
17 | @include mat.all-component-themes($theme);
18 | @include mtx.all-component-themes($theme);
19 |
--------------------------------------------------------------------------------
/projects/docs/src/styles/_reboot.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --bg-color: white;
3 | --bg-translucent-color: rgba(255, 255, 255, 0.9);
4 | --bg-secondary-color: rgba(240, 240, 240, 1);
5 | --fg-color: rgba(85, 85, 90, 1);
6 | --fg-secondary-color: rgba(85, 85, 90, .7);
7 | --border-color: rgba(0, 0, 0, .15);
8 | --heading-color: rgba(50, 50, 55, 1);
9 | --console-color: rgba(0, 0, 0, .4);
10 | --console-bg-color: rgba(0, 0, 0, .04);
11 | --a-color: rgba(65, 105, 190, 1);
12 | --hl-keyword: rgb(85, 140, 180);
13 | --hl-string: rgb(115, 105, 175);
14 | --hl-number: rgb(50, 160, 85);
15 | --hl-comment: rgba(100, 100, 100);
16 | --hl-title: rgb(165, 120, 20);
17 | --hl-inserted-text: rgb(17, 99, 41);
18 | --hl-inserted-bg: rgb(218, 251, 225);
19 | --hl-deleted-text: rgb(130, 7, 30);
20 | --hl-deleted-bg: rgb(255, 235, 233);
21 | }
22 |
23 | *,
24 | *::before,
25 | *::after {
26 | box-sizing: border-box;
27 | }
28 |
29 | body {
30 | padding: 0;
31 | margin: 0;
32 | color: var(--fg-color);
33 | background-color: var(--bg-color);
34 | line-height: 1.5;
35 | font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
36 | }
37 |
38 | h1,
39 | h2,
40 | h3,
41 | h4,
42 | h5,
43 | h6 {
44 | color: var(--heading-color);
45 |
46 | a {
47 | color: var(--heading-color);
48 | }
49 | }
50 |
51 | a {
52 | color: var(--a-color);
53 | text-decoration: none;
54 |
55 | &:hover {
56 | text-decoration: underline;
57 | }
58 | }
59 |
60 | code,
61 | kbd,
62 | pre,
63 | samp {
64 | font-family: "Roboto Mono", monospace;
65 | }
66 |
67 | pre {
68 | padding: 32px;
69 | border-radius: 8px;
70 | background-color: var(--bg-secondary-color);
71 | line-height: 1.2;
72 | overflow: auto;
73 |
74 | code {
75 | display: inline;
76 | padding: 0;
77 | }
78 | }
79 |
80 | @media (width < 600px) {
81 | pre {
82 | padding: 16px;
83 | }
84 | }
85 |
86 | code {
87 | display: inline-block;
88 | padding: 0 4px;
89 | font-size: 0.75rem;
90 | word-wrap: break-word;
91 | background-color: var(--bg-secondary-color);
92 | border-radius: 4px;
93 | }
94 |
95 | table {
96 | width: 100%;
97 | border-spacing: 0;
98 |
99 | tr {
100 | th {
101 | border-top: 1px solid var(--border-color);
102 | }
103 |
104 | th,
105 | td {
106 | padding: 8px;
107 | border-right: 1px solid var(--border-color);
108 | border-bottom: 1px solid var(--border-color);
109 |
110 | &:first-child {
111 | border-left: 1px solid var(--border-color);
112 | }
113 | }
114 |
115 | &:first-child {
116 | th {
117 | &:first-child {
118 | border-top-left-radius: 8px;
119 | }
120 |
121 | &:last-child {
122 | border-top-right-radius: 8px;
123 | }
124 | }
125 | }
126 |
127 | &:last-child {
128 | td {
129 | &:first-child {
130 | border-bottom-left-radius: 8px;
131 | }
132 |
133 | &:last-child {
134 | border-bottom-right-radius: 8px;
135 | }
136 | }
137 | }
138 | }
139 | }
140 |
141 | .gui-form {
142 | background-color: var(--mat-expansion-container-background-color);
143 | color: var(--mat-expansion-container-text-color);
144 | box-shadow: 0 2px 4px rgba(0, 0, 0, .2);
145 | border-radius: 4px;
146 |
147 | .mat-expansion-panel {
148 | border-radius: 4px;
149 | }
150 | }
151 |
152 | .hljs-keyword {
153 | color: var(--hl-keyword);
154 | }
155 |
156 | .hljs-number {
157 | color: var(--hl-number);
158 | }
159 |
160 | .hljs-string {
161 | color: var(--hl-string);
162 | }
163 |
164 | .hljs-comment,
165 | .hljs-quote {
166 | color: var(--hl-comment);
167 | }
168 |
169 | .hljs-title {
170 | color: var(--hl-title);
171 | }
172 |
173 | .hljs-addition {
174 | color: var(--hl-inserted-text);
175 | background-color: var(--hl-inserted-bg);
176 | }
177 |
178 | .hljs-deletion {
179 | color: var(--hl-deleted-text);
180 | background-color: var(--hl-deleted-bg);
181 | }
182 |
183 | .btn {
184 | height: 40px;
185 | padding: 8px;
186 | background-color: transparent;
187 | border: none;
188 | border-radius: 20px;
189 | cursor: pointer;
190 | color: var(--fg-color);
191 |
192 | &:hover,
193 | &:focus {
194 | background-color: var(--bg-secondary-color);
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/projects/docs/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/docs/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/gui/_index.scss:
--------------------------------------------------------------------------------
1 | @use '@angular/material' as mat;
2 | @use '@ng-matero/extensions' as mtx;
3 |
4 | @mixin all-control-themes($theme) {
5 | @include mat.option-theme($theme);
6 | @include mat.optgroup-theme($theme);
7 | @include mat.pseudo-checkbox-theme($theme);
8 | @include mat.form-field-theme($theme);
9 | @include mat.input-theme($theme);
10 | @include mat.button-toggle-theme($theme);
11 | @include mat.slide-toggle-theme($theme);
12 | @include mat.select-theme($theme);
13 | @include mat.slider-theme($theme);
14 | @include mat.icon-theme($theme);
15 | @include mat.icon-button-theme($theme);
16 | @include mat.button-theme($theme);
17 | @include mat.tabs-theme($theme);
18 | @include mat.expansion-theme($theme);
19 | @include mat.tooltip-theme($theme);
20 | @include mtx.colorpicker-theme($theme);
21 | @include mtx.select-theme($theme);
22 | }
23 |
24 | @mixin all-control-colors($theme) {
25 | @include mat.option-color($theme);
26 | @include mat.optgroup-color($theme);
27 | @include mat.pseudo-checkbox-color($theme);
28 | @include mat.form-field-color($theme);
29 | @include mat.input-color($theme);
30 | @include mat.button-toggle-color($theme);
31 | @include mat.slide-toggle-color($theme);
32 | @include mat.select-color($theme);
33 | @include mat.slider-color($theme);
34 | @include mat.icon-color($theme);
35 | @include mat.icon-button-color($theme);
36 | @include mat.button-color($theme);
37 | @include mat.tabs-color($theme);
38 | @include mat.expansion-color($theme);
39 | @include mat.tooltip-color($theme);
40 | @include mtx.colorpicker-color($theme);
41 | @include mtx.select-color($theme);
42 | }
43 |
--------------------------------------------------------------------------------
/projects/gui/button-toggle/button-toggle.html:
--------------------------------------------------------------------------------
1 |
9 | @for (opt of config.options; track opt) {
10 |
18 | @if (config.useIcon) {
19 |
20 | } @else {
21 | {{ opt.label }}
22 | }
23 |
24 | }
25 |
26 | @if (config.parentType === 'inline') {
27 |
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/projects/gui/button-toggle/button-toggle.scss:
--------------------------------------------------------------------------------
1 | .gui-button-toggle {
2 | --mat-standard-button-toggle-shape: 0.25rem;
3 | --mat-standard-button-toggle-height: 1.5rem;
4 | --mat-standard-button-toggle-label-text-size: 0.75rem;
5 | --mat-standard-button-toggle-label-text-weight: 400;
6 | --mat-standard-button-toggle-label-text-line-height: var(--mat-standard-button-toggle-height);
7 |
8 | .mat-button-toggle-group {
9 | flex-wrap: wrap;
10 | border-width: 0;
11 | }
12 |
13 | .mat-button-toggle-group-appearance-standard {
14 | .mat-button-toggle-appearance-standard+.mat-button-toggle-appearance-standard {
15 | border-width: 0;
16 | }
17 | }
18 |
19 | .mat-button-toggle-appearance-standard {
20 | background-color: var(--mdc-filled-text-field-container-color);
21 | }
22 | }
23 |
24 | .gui-icon-toggle {
25 | &.mat-button-toggle {
26 | .mat-button-toggle-label-content {
27 | padding: 0;
28 | line-height: normal;
29 | }
30 | }
31 |
32 | &.mat-button-toggle-checked img {
33 | opacity: .64;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/projects/gui/button-toggle/button-toggle.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle';
11 | import { MatHint } from '@angular/material/form-field';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiFlexDirective } from '../gui-utils';
14 | import { GuiBasicValue, GuiControl } from '../interface';
15 | import { GuiIcon } from './icon';
16 |
17 | @Component({
18 | selector: 'gui-button-toggle',
19 | templateUrl: './button-toggle.html',
20 | styleUrl: './button-toggle.scss',
21 | host: {
22 | class: 'gui-field gui-button-toggle',
23 | },
24 | encapsulation: ViewEncapsulation.None,
25 | changeDetection: ChangeDetectionStrategy.OnPush,
26 | providers: [
27 | {
28 | provide: NG_VALUE_ACCESSOR,
29 | useExisting: forwardRef(() => GuiButtonToggle),
30 | multi: true,
31 | },
32 | ],
33 | standalone: true,
34 | imports: [
35 | FormsModule,
36 | MatButtonToggleGroup,
37 | MatButtonToggle,
38 | MatHint,
39 | GuiFlexDirective,
40 | GuiIcon,
41 | GuiFieldLabel,
42 | ],
43 | })
44 | export class GuiButtonToggle implements ControlValueAccessor {
45 | @Input() config: Partial = {};
46 | @Input() disabled = false;
47 |
48 | value: GuiBasicValue | GuiBasicValue[] = '';
49 |
50 | private onChange: (value: GuiBasicValue | GuiBasicValue[]) => void = () => {};
51 | private onTouched: () => void = () => {};
52 |
53 | constructor(private cdr: ChangeDetectorRef) {}
54 |
55 | writeValue(value: GuiBasicValue | GuiBasicValue[]) {
56 | this.value = value;
57 | this.cdr.markForCheck();
58 | }
59 |
60 | registerOnChange(fn: (value: GuiBasicValue | GuiBasicValue[]) => void) {
61 | this.onChange = fn;
62 | }
63 |
64 | registerOnTouched(fn: () => void) {
65 | this.onTouched = fn;
66 | }
67 |
68 | setDisabledState(isDisabled: boolean) {
69 | this.disabled = isDisabled;
70 | this.cdr.markForCheck();
71 | }
72 |
73 | onValueChange() {
74 | this.onChange(this.value);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/projects/gui/button-toggle/icon.scss:
--------------------------------------------------------------------------------
1 | .gui-icon {
2 | display: inline-flex;
3 |
4 | img,
5 | i {
6 | display: block;
7 | width: var(--mat-standard-button-toggle-height);
8 | height: var(--mat-standard-button-toggle-height);
9 | line-height: var(--mat-standard-button-toggle-height);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/projects/gui/button-toggle/icon.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'gui-icon',
5 | template: `
6 | @if (isUrl()) {
7 |
8 | } @else {
9 |
10 | }
11 | `,
12 | styleUrl: './icon.scss',
13 | host: {
14 | class: 'gui-icon',
15 | },
16 | encapsulation: ViewEncapsulation.None,
17 | changeDetection: ChangeDetectionStrategy.OnPush,
18 | standalone: true,
19 | })
20 | export class GuiIcon {
21 | @Input() src = '';
22 |
23 | isUrl() {
24 | return /^(https?:\/\/|\.?\/)\w+/.test(this.src);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea-config.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from '@acrodata/code-editor';
2 | import { Injectable } from '@angular/core';
3 | import { LanguageDescription } from '@codemirror/language';
4 | import { Extension } from '@codemirror/state';
5 | import { Subject } from 'rxjs';
6 | import { GuiCodeareaDialogData } from './codearea-dialog';
7 |
8 | @Injectable({
9 | providedIn: 'root',
10 | })
11 | export class GuiCodeareaConfig {
12 | readonly changes: Subject = new Subject();
13 |
14 | theme: Theme = 'light';
15 |
16 | languages: LanguageDescription[] = [];
17 |
18 | extensions: Extension[] | ((data: GuiCodeareaDialogData) => Extension[]) = [];
19 | }
20 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea-dialog.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea-dialog.scss:
--------------------------------------------------------------------------------
1 | .gui-codearea-dialog-panel {
2 | --rnd-dialog-container-color: var(--mdc-dialog-container-color);
3 | }
4 |
5 | .gui-codearea-dialog {
6 | --mdc-outlined-button-label-text-size: 0.75rem;
7 | --mdc-outlined-button-container-height: 1.5rem;
8 | --mat-outlined-button-horizontal-padding: 0.75rem;
9 | --mat-outlined-button-touch-target-display: none;
10 | --mdc-filled-button-label-text-size: 0.75rem;
11 | --mdc-filled-button-container-height: 1.5rem;
12 | --mat-filled-button-horizontal-padding: 0.75rem;
13 | --mat-filled-button-touch-target-display: none;
14 |
15 | display: flex;
16 | flex-direction: column;
17 | height: 100%;
18 | overflow: hidden;
19 | border-radius: inherit;
20 | font-size: 0.75rem;
21 | }
22 |
23 | .gui-codearea-dialog-header {
24 | display: flex;
25 | gap: 0.5rem;
26 | align-items: center;
27 | padding: 0.5rem;
28 | cursor: grab;
29 | border-bottom: 1px solid rgba(0, 0, 0, 0.24);
30 | box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.24);
31 |
32 | &.dragging {
33 | cursor: grabbing;
34 | }
35 | }
36 |
37 | .gui-codearea-dialog-title {
38 | overflow: hidden;
39 | text-overflow: ellipsis;
40 | white-space: nowrap;
41 | }
42 |
43 | .gui-codearea-dialog-spacer {
44 | flex: 1;
45 | }
46 |
47 | .gui-codearea-dialog-content {
48 | position: relative;
49 | flex: 1;
50 | overflow: auto;
51 |
52 | .code-editor {
53 | height: 100%;
54 | overflow: auto;
55 | border: 1px solid transparent;
56 | }
57 |
58 | &:hover {
59 | .gui-codearea-btns {
60 | display: block;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea-dialog.ts:
--------------------------------------------------------------------------------
1 | import { CodeEditor } from '@acrodata/code-editor';
2 | import { RndDialogDragHandle } from '@acrodata/rnd-dialog';
3 | import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
4 | import {
5 | ChangeDetectionStrategy,
6 | ChangeDetectorRef,
7 | Component,
8 | DestroyRef,
9 | Inject,
10 | ViewEncapsulation,
11 | } from '@angular/core';
12 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
13 | import { FormsModule } from '@angular/forms';
14 | import { MatButton, MatIconButton } from '@angular/material/button';
15 | import { MatIcon } from '@angular/material/icon';
16 | import { GuiIconsRegistry } from '../gui-icons';
17 | import { GuiIconButtonWrapper } from '../icon-button-wrapper/icon-button-wrapper';
18 | import { GuiCodeareaConfig } from './codearea-config';
19 |
20 | export interface GuiCodeareaDialogData {
21 | value: string;
22 | disabled?: boolean;
23 | readonly?: boolean;
24 | language?: string;
25 | title?: string;
26 | }
27 |
28 | @Component({
29 | selector: 'gui-codearea-dialog',
30 | standalone: true,
31 | imports: [
32 | FormsModule,
33 | MatButton,
34 | MatIconButton,
35 | MatIcon,
36 | RndDialogDragHandle,
37 | CodeEditor,
38 | GuiIconButtonWrapper,
39 | ],
40 | templateUrl: './codearea-dialog.html',
41 | styleUrl: './codearea-dialog.scss',
42 | host: {
43 | class: 'gui-codearea-dialog',
44 | },
45 | encapsulation: ViewEncapsulation.None,
46 | changeDetection: ChangeDetectionStrategy.OnPush,
47 | })
48 | export class GuiCodeareaDialog {
49 | get languages() {
50 | return this.codeareaCfg.languages;
51 | }
52 |
53 | get theme() {
54 | return this.codeareaCfg.theme;
55 | }
56 |
57 | get extensions() {
58 | return typeof this.codeareaCfg.extensions === 'function'
59 | ? this.codeareaCfg.extensions(this.data)
60 | : this.codeareaCfg.extensions;
61 | }
62 |
63 | langDesc = this.codeareaCfg.languages.find(
64 | lang => this.data.language && lang.alias.includes(this.data.language.toLowerCase())
65 | );
66 |
67 | title = `${this.data.title || ''} (${this.langDesc?.name || 'Plain Text'})`;
68 |
69 | lineWrapping = false;
70 |
71 | constructor(
72 | private dialogRef: DialogRef,
73 | @Inject(DIALOG_DATA) public data: GuiCodeareaDialogData,
74 | private cdr: ChangeDetectorRef,
75 | private destroyRef: DestroyRef,
76 | private codeareaCfg: GuiCodeareaConfig,
77 | iconsRegistry: GuiIconsRegistry
78 | ) {
79 | iconsRegistry.add('wrap');
80 |
81 | this.codeareaCfg.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
82 | this.cdr.markForCheck();
83 | });
84 | }
85 |
86 | toggleLineWrapping() {
87 | this.lineWrapping = !this.lineWrapping;
88 | }
89 |
90 | save() {
91 | this.dialogRef.close(this.data.value);
92 | }
93 |
94 | close() {
95 | this.dialogRef.close();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | @if (config.parentType === 'inline') {
22 |
23 |
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea.scss:
--------------------------------------------------------------------------------
1 | .gui-codearea {
2 | position: relative;
3 | overflow: auto;
4 | border: 1px solid var(--mdc-outlined-text-field-outline-color);
5 | border-radius: var(--mdc-outlined-text-field-container-shape);
6 |
7 | .code-editor {
8 | width: 100%;
9 | min-height: 1.5rem;
10 | overflow: auto;
11 | resize: vertical;
12 | }
13 |
14 | &:hover {
15 | .gui-codearea-btns {
16 | display: block;
17 | }
18 | }
19 | }
20 |
21 | .gui-codearea-btns {
22 | position: absolute;
23 | top: 0;
24 | right: 0;
25 | display: none;
26 | margin: 2px;
27 |
28 | [dir='rtl'] & {
29 | left: 0;
30 | right: auto;
31 | }
32 |
33 | .gui-icon-button-wrapper {
34 | .mat-mdc-icon-button,
35 | .mat-icon {
36 | width: 1.25rem;
37 | height: 1.25rem;
38 | }
39 | }
40 |
41 | .mat-mdc-icon-button {
42 | backdrop-filter: blur(8px);
43 | border-radius: 0.25rem;
44 |
45 | .mat-mdc-button-persistent-ripple {
46 | border-radius: inherit;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/projects/gui/codearea/codearea.ts:
--------------------------------------------------------------------------------
1 | import { CodeEditor, Setup } from '@acrodata/code-editor';
2 | import { RndDialog } from '@acrodata/rnd-dialog';
3 | import { coerceCssPixelValue } from '@angular/cdk/coercion';
4 | import {
5 | ChangeDetectionStrategy,
6 | ChangeDetectorRef,
7 | Component,
8 | DestroyRef,
9 | forwardRef,
10 | Input,
11 | ViewEncapsulation,
12 | } from '@angular/core';
13 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
14 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
15 | import { MatIconButton } from '@angular/material/button';
16 | import { MatHint } from '@angular/material/form-field';
17 | import { MatIcon } from '@angular/material/icon';
18 | import { GuiFieldLabel } from '../field-label/field-label';
19 | import { GuiIconsRegistry } from '../gui-icons';
20 | import { GuiIconButtonWrapper } from '../icon-button-wrapper/icon-button-wrapper';
21 | import { GuiControl } from '../interface';
22 | import { GuiCodeareaConfig } from './codearea-config';
23 | import { GuiCodeareaDialog, GuiCodeareaDialogData } from './codearea-dialog';
24 |
25 | @Component({
26 | selector: 'gui-codearea',
27 | templateUrl: './codearea.html',
28 | styleUrl: './codearea.scss',
29 | host: {
30 | class: 'gui-field gui-codearea',
31 | },
32 | encapsulation: ViewEncapsulation.None,
33 | changeDetection: ChangeDetectionStrategy.OnPush,
34 | providers: [
35 | {
36 | provide: NG_VALUE_ACCESSOR,
37 | useExisting: forwardRef(() => GuiCodearea),
38 | multi: true,
39 | },
40 | ],
41 | standalone: true,
42 | imports: [
43 | FormsModule,
44 | MatIcon,
45 | MatIconButton,
46 | MatHint,
47 | CodeEditor,
48 | GuiFieldLabel,
49 | GuiIconButtonWrapper,
50 | ],
51 | })
52 | export class GuiCodearea implements ControlValueAccessor {
53 | @Input() config: Partial = {};
54 | @Input() disabled = false;
55 |
56 | @Input() setup: Setup = 'minimal';
57 |
58 | @Input()
59 | get height() {
60 | return coerceCssPixelValue(this.config.height || this._height);
61 | }
62 | set height(value: string | number) {
63 | this._height = value;
64 | }
65 | private _height: string | number = 120;
66 |
67 | @Input()
68 | get language() {
69 | return this.config.language || this._language;
70 | }
71 | set language(value: string) {
72 | this._language = value;
73 | }
74 | private _language = '';
75 |
76 | get languages() {
77 | return this.codeareaCfg.languages;
78 | }
79 |
80 | get theme() {
81 | return this.codeareaCfg.theme;
82 | }
83 |
84 | get dialogData(): GuiCodeareaDialogData {
85 | return {
86 | value: this.value,
87 | disabled: this.disabled,
88 | language: this.language,
89 | };
90 | }
91 |
92 | get extensions() {
93 | return typeof this.codeareaCfg.extensions === 'function'
94 | ? this.codeareaCfg.extensions(this.dialogData)
95 | : this.codeareaCfg.extensions;
96 | }
97 |
98 | value = '';
99 | private oldValue = '';
100 |
101 | private onChange: (value: string) => void = () => {};
102 | private onTouched: () => void = () => {};
103 |
104 | constructor(
105 | private rndDialog: RndDialog,
106 | private cdr: ChangeDetectorRef,
107 | private destroyRef: DestroyRef,
108 | private codeareaCfg: GuiCodeareaConfig,
109 | iconsRegistry: GuiIconsRegistry
110 | ) {
111 | iconsRegistry.add('expand');
112 |
113 | this.codeareaCfg.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
114 | this.cdr.markForCheck();
115 | });
116 | }
117 |
118 | writeValue(value: any) {
119 | if (typeof value === 'string' || value == null) {
120 | this.value = value || '';
121 | } else {
122 | this.value = value.toString();
123 | }
124 | this.oldValue = this.value;
125 | this.cdr.markForCheck();
126 | }
127 |
128 | registerOnChange(fn: (value: string) => void) {
129 | this.onChange = fn;
130 | }
131 |
132 | registerOnTouched(fn: () => void) {
133 | this.onTouched = fn;
134 | }
135 |
136 | setDisabledState(isDisabled: boolean) {
137 | this.disabled = isDisabled;
138 | this.cdr.markForCheck();
139 | }
140 |
141 | onValueChange() {
142 | if (this.value !== this.oldValue) {
143 | this.onChange(this.value);
144 | this.oldValue = this.value;
145 | }
146 | }
147 |
148 | onExpand() {
149 | const dialogRef = this.rndDialog.open(
150 | GuiCodeareaDialog,
151 | {
152 | panelClass: 'gui-codearea-dialog-panel',
153 | hasBackdrop: false,
154 | width: '600px',
155 | data: this.dialogData,
156 | }
157 | );
158 |
159 | dialogRef.closed.subscribe(newValue => {
160 | if (newValue) {
161 | this.value = newValue;
162 | this.cdr.detectChanges();
163 | this.onValueChange();
164 | }
165 | });
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/projects/gui/combobox/combobox.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
19 |
20 |
21 | {{ item.label }}
22 |
23 |
24 |
25 | @if (config.suffix) {
26 | {{ config.suffix }}
27 | }
28 | @if (config.parentType === 'inline') {
29 |
30 |
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/projects/gui/combobox/combobox.scss:
--------------------------------------------------------------------------------
1 | .gui-combobox {
2 | .ng-select {
3 | padding-left: 0.5rem;
4 | padding-right: 0.5rem;
5 | margin-left: -0.5rem;
6 | margin-right: -0.5rem;
7 |
8 | &.ng-select-multiple .ng-value {
9 | flex-direction: row-reverse;
10 | }
11 |
12 | .ng-value-icon {
13 | font-family: math;
14 | font-size: 10px;
15 | }
16 |
17 | .ng-clear-wrapper .ng-clear {
18 | font-family: math;
19 | font-size: 12px;
20 | }
21 |
22 | .ng-arrow {
23 | vertical-align: -1px;
24 | }
25 | }
26 |
27 | &.ng-dropdown-panel {
28 | padding: 0.5rem 0;
29 |
30 | .ng-dropdown-panel-items {
31 | .ng-option {
32 | padding: 0 0.5rem;
33 | line-height: var(--mat-option-label-text-line-height);
34 |
35 | .ng-tag-label {
36 | line-height: 1;
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/projects/gui/combobox/combobox.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewInit,
3 | ChangeDetectionStrategy,
4 | ChangeDetectorRef,
5 | Component,
6 | forwardRef,
7 | Input,
8 | ViewChild,
9 | ViewEncapsulation,
10 | } from '@angular/core';
11 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
12 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
13 | import { MtxSelect, MtxSelectOptionTemplate } from '@ng-matero/extensions/select';
14 | import { GuiFieldLabel } from '../field-label/field-label';
15 | import { GuiBasicValue, GuiControl } from '../interface';
16 |
17 | @Component({
18 | selector: 'gui-combobox',
19 | templateUrl: './combobox.html',
20 | styleUrl: './combobox.scss',
21 | host: {
22 | class: 'gui-field gui-combobox',
23 | },
24 | encapsulation: ViewEncapsulation.None,
25 | changeDetection: ChangeDetectionStrategy.OnPush,
26 | providers: [
27 | {
28 | provide: NG_VALUE_ACCESSOR,
29 | useExisting: forwardRef(() => GuiCombobox),
30 | multi: true,
31 | },
32 | ],
33 | standalone: true,
34 | imports: [
35 | FormsModule,
36 | MatFormField,
37 | MatPrefix,
38 | MatSuffix,
39 | MatHint,
40 | MtxSelect,
41 | MtxSelectOptionTemplate,
42 | GuiFieldLabel,
43 | ],
44 | })
45 | export class GuiCombobox implements ControlValueAccessor, AfterViewInit {
46 | @ViewChild(MtxSelect) mtxSelect!: MtxSelect;
47 |
48 | @Input() config: Partial = {};
49 | @Input() disabled = false;
50 | @Input() appendTo = 'body';
51 |
52 | value: GuiBasicValue | GuiBasicValue[] = '';
53 |
54 | private onChange: (value: GuiBasicValue | GuiBasicValue[]) => void = () => {};
55 | private onTouched: () => void = () => {};
56 |
57 | constructor(private cdr: ChangeDetectorRef) {}
58 |
59 | ngAfterViewInit(): void {
60 | // Add additional class for ng-select's dropdown panel
61 | const { ngSelect } = this.mtxSelect;
62 | ngSelect.classes = (ngSelect.classes || '') + ' gui-combobox';
63 | }
64 |
65 | writeValue(value: GuiBasicValue | GuiBasicValue[]) {
66 | this.value = value;
67 | this.cdr.markForCheck();
68 | }
69 |
70 | registerOnChange(fn: (value: GuiBasicValue | GuiBasicValue[]) => void) {
71 | this.onChange = fn;
72 | }
73 |
74 | registerOnTouched(fn: () => void) {
75 | this.onTouched = fn;
76 | }
77 |
78 | setDisabledState(isDisabled: boolean) {
79 | this.disabled = isDisabled;
80 | this.cdr.markForCheck();
81 | }
82 |
83 | onValueChange() {
84 | this.onChange(this.value);
85 | }
86 |
87 | addTagFn(label: string) {
88 | return { label, value: label };
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/projects/gui/eslint.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const tseslint = require('typescript-eslint');
3 | const rootConfig = require('../../eslint.config.js');
4 |
5 | module.exports = tseslint.config(
6 | ...rootConfig,
7 | {
8 | files: ['**/*.ts'],
9 | rules: {
10 | '@angular-eslint/directive-selector': [
11 | 'error',
12 | {
13 | type: 'attribute',
14 | prefix: '',
15 | style: 'camelCase',
16 | },
17 | ],
18 | '@angular-eslint/component-selector': [
19 | 'error',
20 | {
21 | type: 'element',
22 | prefix: 'gui',
23 | style: 'kebab-case',
24 | },
25 | ],
26 | },
27 | },
28 | {
29 | files: ['**/*.html'],
30 | rules: {},
31 | }
32 | );
33 |
--------------------------------------------------------------------------------
/projects/gui/field-group/field-group.html:
--------------------------------------------------------------------------------
1 | @if (config.parentType !== 'inline' && config.name) {
2 |
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/projects/gui/field-group/field-group.scss:
--------------------------------------------------------------------------------
1 | .gui-field-group {
2 | display: flex;
3 | padding: 0.25rem 0.5rem;
4 | }
5 |
6 | .gui-field {
7 | --mat-form-field-container-vertical-padding: 0.25rem;
8 | --mat-form-field-container-text-line-height: 1rem;
9 | --mat-form-field-container-text-size: 0.75rem;
10 |
11 | display: inline-flex;
12 | flex: 1;
13 | flex-wrap: wrap;
14 |
15 | >.mat-mdc-form-field {
16 | flex: 1;
17 | width: 0;
18 | max-width: 100%;
19 | }
20 |
21 | .mat-mdc-text-field-wrapper,
22 | .mat-mdc-form-field-hint-wrapper {
23 | position: relative;
24 | padding: 0 0.5rem;
25 | border-radius: var(--mdc-filled-text-field-container-shape);
26 |
27 | [dir='rtl'] & {
28 | padding: 0 0.5rem;
29 | }
30 | }
31 |
32 | .mat-mdc-form-field-infix {
33 | min-height: auto;
34 | }
35 |
36 | .mat-mdc-form-field-hint-spacer,
37 | .mat-mdc-form-field-bottom-align::before,
38 | .mdc-line-ripple {
39 | display: none;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/projects/gui/field-group/field-group.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | DoCheck,
6 | Input,
7 | KeyValueDiffer,
8 | KeyValueDiffers,
9 | OnInit,
10 | ViewEncapsulation,
11 | } from '@angular/core';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiControl } from '../interface';
14 |
15 | @Component({
16 | selector: 'gui-field-group',
17 | templateUrl: './field-group.html',
18 | styleUrl: './field-group.scss',
19 | host: {
20 | class: 'gui-field-group',
21 | },
22 | encapsulation: ViewEncapsulation.None,
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | standalone: true,
25 | imports: [GuiFieldLabel],
26 | })
27 | export class GuiFieldGroup implements OnInit, DoCheck {
28 | @Input() config: Partial = {};
29 |
30 | private configDiffer?: KeyValueDiffer;
31 |
32 | constructor(
33 | private differs: KeyValueDiffers,
34 | private cdr: ChangeDetectorRef
35 | ) {}
36 |
37 | ngOnInit(): void {
38 | this.configDiffer = this.differs.find(this.config).create();
39 | }
40 |
41 | ngDoCheck(): void {
42 | const changes = this.configDiffer?.diff(this.config);
43 | changes?.forEachChangedItem(record => {
44 | this.cdr.markForCheck();
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/projects/gui/field-label/field-label.html:
--------------------------------------------------------------------------------
1 | @if (!config.description) {
2 | {{ title }}
3 | } @else {
4 |
10 | {{ title }}
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/projects/gui/field-label/field-label.scss:
--------------------------------------------------------------------------------
1 | .gui-field-label {
2 | display: inline-block;
3 | width: 4rem;
4 | height: 1.5rem;
5 | padding: 0 0.25rem;
6 | line-height: 1.5rem;
7 | overflow: hidden;
8 | white-space: nowrap;
9 | text-overflow: ellipsis;
10 | }
11 |
12 | .gui-field-label-with-description {
13 | text-decoration: underline dotted;
14 | cursor: help;
15 | }
16 |
17 | .gui-field-label-tooltip {
18 | white-space: pre-wrap;
19 | }
20 |
--------------------------------------------------------------------------------
/projects/gui/field-label/field-label.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | Input,
5 | OnChanges,
6 | ViewEncapsulation,
7 | } from '@angular/core';
8 | import { MatTooltip } from '@angular/material/tooltip';
9 | import { ejsTmpl } from '../gui-utils';
10 | import { GuiControl } from '../interface';
11 |
12 | @Component({
13 | selector: 'gui-field-label',
14 | templateUrl: './field-label.html',
15 | styleUrl: './field-label.scss',
16 | host: {
17 | '[class.gui-field-label]': '!styless',
18 | '[title]': 'title',
19 | },
20 | encapsulation: ViewEncapsulation.None,
21 | changeDetection: ChangeDetectionStrategy.OnPush,
22 | standalone: true,
23 | imports: [MatTooltip],
24 | })
25 | export class GuiFieldLabel implements OnChanges {
26 | @Input() config: Partial = {};
27 |
28 | @Input() index?: number;
29 |
30 | title = '';
31 |
32 | styless = false;
33 |
34 | ngOnChanges(): void {
35 | const { index, name, parentType, type } = this.config;
36 | this.title = index != null && !isNaN(index) ? ejsTmpl(name || '', { i: index }) : name;
37 | this.styless =
38 | (parentType === 'inline' && type !== 'inline') || type === 'group' || type === 'tabs';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/projects/gui/file-uploader/file-uploader-config.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient, HttpResponse } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { map } from 'rxjs';
4 | import { GuiControl } from '../interface';
5 |
6 | export interface FileUploadResponseBody {
7 | bytes: number;
8 | mime: string;
9 | url: string;
10 | }
11 |
12 | @Injectable({
13 | providedIn: 'root',
14 | })
15 | export class GuiFileUploaderConfig {
16 | constructor(protected http: HttpClient) {}
17 |
18 | /**
19 | * The file upload URL
20 | */
21 | url = '';
22 |
23 | /**
24 | * The File upload API
25 | *
26 | * @param formData The FormData with file binary
27 | * @param config The custom upload config that passed from component input
28 | * @returns The uploaded file url stream
29 | */
30 | upload(formData: FormData, config: Partial) {
31 | return this.http
32 | .post(this.url, formData, {
33 | reportProgress: true,
34 | observe: 'events',
35 | })
36 | .pipe(
37 | map(res => {
38 | if (res instanceof HttpResponse && res.body) {
39 | return res.body.url;
40 | }
41 | return null;
42 | })
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/projects/gui/file-uploader/file-uploader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
16 | @if (url) {
17 |
18 |
19 |
20 |
21 |
22 | }
23 |
24 |
25 |
26 | @if (url) {
27 | @switch (type) {
28 | @case ('image') {
29 |
30 | }
31 | @case ('video') {
32 |
33 | }
34 | @case ('audio') {
35 |
36 | }
37 | @default {
38 |
39 | }
40 | }
41 | } @else {
42 |
43 |
44 |
45 | }
46 |
47 |
55 |
56 |
57 | @if (config.parentType === 'inline') {
58 |
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/projects/gui/file-uploader/file-uploader.scss:
--------------------------------------------------------------------------------
1 | .gui-file-uploader {
2 | display: block;
3 | overflow: auto;
4 |
5 | .mat-mdc-form-field {
6 | width: 100%;
7 | }
8 |
9 | .gui-file-content {
10 | position: relative;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | height: 7.5rem;
15 | margin: 0.25rem 0 0;
16 | padding: 0.25rem;
17 | background-color: var(--mdc-filled-text-field-container-color);
18 | border: 1px solid transparent;
19 | border-radius: 0.25rem;
20 |
21 | &:hover,
22 | &:focus-within {
23 | border-color: currentColor;
24 | }
25 |
26 | img,
27 | video {
28 | width: 100%;
29 | height: 100%;
30 | object-fit: contain;
31 | }
32 |
33 | input[type='file'] {
34 | position: absolute;
35 | top: 0;
36 | left: 0;
37 | width: 100%;
38 | height: 100%;
39 | opacity: 0;
40 | cursor: pointer;
41 | }
42 |
43 | .mat-icon svg {
44 | width: 1.5rem;
45 | height: 1.5rem;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/projects/gui/file-uploader/file-uploader.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | ElementRef,
6 | EventEmitter,
7 | Input,
8 | OnChanges,
9 | Output,
10 | SimpleChanges,
11 | ViewChild,
12 | ViewEncapsulation,
13 | forwardRef,
14 | } from '@angular/core';
15 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
16 | import { MatIconButton } from '@angular/material/button';
17 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
18 | import { MatIcon } from '@angular/material/icon';
19 | import { MatInput } from '@angular/material/input';
20 | import { finalize } from 'rxjs/operators';
21 |
22 | import { GuiFieldLabel } from '../field-label/field-label';
23 | import { GuiIconsRegistry } from '../gui-icons';
24 | import { GuiIconButtonWrapper } from '../icon-button-wrapper/icon-button-wrapper';
25 | import { GuiControl } from '../interface';
26 | import { GuiFileUploaderConfig } from './file-uploader-config';
27 |
28 | export type FileUploadType = 'image' | 'video' | 'audio' | '*';
29 |
30 | export interface FileUploadContent {
31 | data: File;
32 | progress: number;
33 | inProgress: boolean;
34 | }
35 |
36 | @Component({
37 | selector: 'gui-file-uploader',
38 | templateUrl: './file-uploader.html',
39 | styleUrl: './file-uploader.scss',
40 | host: {
41 | class: 'gui-field gui-file-uploader',
42 | },
43 | encapsulation: ViewEncapsulation.None,
44 | changeDetection: ChangeDetectionStrategy.OnPush,
45 | providers: [
46 | {
47 | provide: NG_VALUE_ACCESSOR,
48 | useExisting: forwardRef(() => GuiFileUploader),
49 | multi: true,
50 | },
51 | ],
52 | standalone: true,
53 | imports: [
54 | FormsModule,
55 | MatFormField,
56 | MatIcon,
57 | MatPrefix,
58 | MatInput,
59 | MatIconButton,
60 | MatSuffix,
61 | MatHint,
62 | GuiFieldLabel,
63 | GuiIconButtonWrapper,
64 | ],
65 | })
66 | export class GuiFileUploader implements ControlValueAccessor, OnChanges {
67 | @ViewChild('fileInput') fileInput!: ElementRef;
68 |
69 | @Input() config: Partial = {};
70 | @Input() disabled = false;
71 | @Input() type: FileUploadType = '*';
72 | @Input() name = '';
73 | @Input() accept = '';
74 |
75 | @Output() fileChange = new EventEmitter();
76 |
77 | // file url that returned from the server
78 | url = '';
79 |
80 | // file to upload
81 | fileUpload!: FileUploadContent;
82 |
83 | private onChange: (value: string) => void = () => {};
84 | private onTouched: () => void = () => {};
85 |
86 | constructor(
87 | private fileUploaderCfg: GuiFileUploaderConfig,
88 | private cdr: ChangeDetectorRef,
89 | iconsRegistry: GuiIconsRegistry
90 | ) {
91 | iconsRegistry.add('link', 'clear', 'file', 'upload');
92 | }
93 |
94 | ngOnChanges(changes: SimpleChanges): void {
95 | if (changes['config'] || changes['accept'] || changes['type']) {
96 | this.accept = this.config.accept || this.accept || this.type + '/*';
97 | }
98 | }
99 |
100 | writeValue(value: string) {
101 | this.url = value;
102 | this.cdr.markForCheck();
103 | }
104 |
105 | registerOnChange(fn: (value: string) => void) {
106 | this.onChange = fn;
107 | }
108 |
109 | registerOnTouched(fn: () => void) {
110 | this.onTouched = fn;
111 | }
112 |
113 | setDisabledState(isDisabled: boolean) {
114 | this.disabled = isDisabled;
115 | this.cdr.markForCheck();
116 | }
117 |
118 | upload(fileUpload: FileUploadContent) {
119 | const formData = new FormData();
120 | formData.append('file', fileUpload.data);
121 |
122 | fileUpload.inProgress = true;
123 |
124 | this.fileUploaderCfg
125 | .upload(formData, this.config)
126 | .pipe(
127 | finalize(() => {
128 | fileUpload.inProgress = false;
129 | })
130 | )
131 | .subscribe(result => {
132 | if (result) {
133 | this.url = result;
134 | this.cdr.detectChanges();
135 |
136 | this.onChange(this.url);
137 | this.onTouched();
138 |
139 | this.fileChange.emit(this.url);
140 | }
141 | });
142 | }
143 |
144 | onUrlChange(e: Event) {
145 | this.url = (e.target as HTMLInputElement).value;
146 |
147 | this.onChange(this.url);
148 |
149 | this.fileChange.emit(this.url);
150 | }
151 |
152 | onFileChange(e: Event) {
153 | this.fileUpload = {
154 | data: (e.target as HTMLInputElement).files![0],
155 | inProgress: false,
156 | progress: 0,
157 | };
158 |
159 | this.upload(this.fileUpload);
160 |
161 | // reset input value
162 | (e.target as HTMLInputElement).value = '';
163 | }
164 |
165 | onBlur() {
166 | this.onTouched();
167 | }
168 |
169 | onClear() {
170 | this.url = '';
171 |
172 | this.onChange(this.url);
173 | this.onTouched();
174 |
175 | this.fileChange.emit(this.url);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/projects/gui/fill/fill.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | @if (config.parentType === 'inline') {
22 |
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/projects/gui/fill/fill.scss:
--------------------------------------------------------------------------------
1 | .gui-color-block,
2 | .gui-color-block-empty {
3 | position: relative;
4 | display: inline-block;
5 | width: 1.5rem;
6 | height: 1.5rem;
7 | }
8 |
9 | .gui-color-block-empty {
10 | position: absolute;
11 | background-color: #fff;
12 | background-image:
13 | linear-gradient(45deg, #ccc 25%, transparent 25%),
14 | linear-gradient(-45deg, #ccc 25%, transparent 25%),
15 | linear-gradient(45deg, transparent 75%, #ccc 75%),
16 | linear-gradient(-45deg, transparent 75%, #ccc 75%);
17 | background-position: 0 0, 0 0.25rem, 0.25rem -0.25rem, -0.25rem 0;
18 | background-size: 0.5rem 0.5rem;
19 | }
20 |
21 | // use the pseudo-elements `::after` to simulate the border for color-block
22 | .gui-color-block::after {
23 | display: inline-block;
24 | width: 100%;
25 | height: 100%;
26 | box-sizing: border-box;
27 | border: 1px solid currentColor;
28 | border-top-left-radius: var(--mdc-filled-text-field-container-shape);
29 | border-bottom-left-radius: var(--mdc-filled-text-field-container-shape);
30 | opacity: .12;
31 | content: '';
32 |
33 | [dir='rtl'] & {
34 | border-radius: 0;
35 | border-top-right-radius: var(--mdc-filled-text-field-container-shape);
36 | border-bottom-right-radius: var(--mdc-filled-text-field-container-shape);
37 | }
38 | }
39 |
40 | // fix the vertical alignment for icon
41 | .gui-fill {
42 | .mat-mdc-icon-button {
43 | display: inline-flex;
44 | justify-content: center;
45 | align-items: center;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/projects/gui/fill/fill.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
11 | import { MatInput } from '@angular/material/input';
12 | import {
13 | MtxColorpicker,
14 | MtxColorpickerInput,
15 | MtxColorpickerToggle,
16 | } from '@ng-matero/extensions/colorpicker';
17 | import { GuiFieldLabel } from '../field-label/field-label';
18 | import { GuiIconButtonWrapper } from '../icon-button-wrapper/icon-button-wrapper';
19 | import { GuiControl } from '../interface';
20 |
21 | @Component({
22 | selector: 'gui-fill',
23 | templateUrl: './fill.html',
24 | styleUrl: './fill.scss',
25 | host: {
26 | class: 'gui-field gui-fill',
27 | },
28 | encapsulation: ViewEncapsulation.None,
29 | changeDetection: ChangeDetectionStrategy.OnPush,
30 | providers: [
31 | {
32 | provide: NG_VALUE_ACCESSOR,
33 | useExisting: forwardRef(() => GuiFill),
34 | multi: true,
35 | },
36 | ],
37 | standalone: true,
38 | imports: [
39 | FormsModule,
40 | MatFormField,
41 | MatPrefix,
42 | MatInput,
43 | MatSuffix,
44 | MatHint,
45 | MtxColorpickerInput,
46 | MtxColorpicker,
47 | MtxColorpickerToggle,
48 | GuiFieldLabel,
49 | GuiIconButtonWrapper,
50 | ],
51 | })
52 | export class GuiFill implements ControlValueAccessor {
53 | @Input() config: Partial = {};
54 | @Input() disabled = false;
55 |
56 | value = '';
57 |
58 | private onChange: (value: string) => void = () => {};
59 | private onTouched: () => void = () => {};
60 |
61 | constructor(private cdr: ChangeDetectorRef) {}
62 |
63 | writeValue(value: string) {
64 | if (typeof value === 'string') {
65 | this.value = value;
66 | this.cdr.markForCheck();
67 | }
68 | }
69 |
70 | registerOnChange(fn: (value: string) => void) {
71 | this.onChange = fn;
72 | }
73 |
74 | registerOnTouched(fn: () => void) {
75 | this.onTouched = fn;
76 | }
77 |
78 | setDisabledState(isDisabled: boolean) {
79 | this.disabled = isDisabled;
80 | this.cdr.markForCheck();
81 | }
82 |
83 | onValueChange() {
84 | this.onChange(this.value);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/projects/gui/gui-form.scss:
--------------------------------------------------------------------------------
1 | .gui-form {
2 | --mat-expansion-header-text-size: 0.75rem;
3 | --mat-expansion-header-collapsed-state-height: 2rem;
4 | --mat-expansion-header-expanded-state-height: 2rem;
5 | --mat-expansion-container-text-size: 0.75rem;
6 | --mat-expansion-container-shape: 0;
7 |
8 | position: relative;
9 | display: block;
10 | font-size: 0.75rem;
11 |
12 | .mat-expansion-panel:not([class*="mat-elevation-z"]) {
13 | box-shadow: none;
14 | }
15 |
16 | .mat-expansion-panel-body {
17 | padding: 0;
18 | }
19 |
20 | .mat-expansion-panel-header {
21 | padding: 0 0.75rem;
22 |
23 | .mat-content {
24 | align-items: center;
25 | padding-right: 0.5rem;
26 |
27 | [dir='rtl'] & {
28 | padding-right: 0;
29 | padding-left: 0.5rem;
30 | }
31 |
32 | gui-field-label {
33 | flex: 1;
34 | overflow: hidden;
35 | white-space: nowrap;
36 | text-overflow: ellipsis;
37 | }
38 | }
39 | }
40 |
41 | .mat-expansion-indicator {
42 | svg {
43 | width: 1.5rem;
44 | height: 1.5rem;
45 | margin: 0 -0.5rem;
46 | }
47 |
48 | &::after {
49 | vertical-align: 0.125rem;
50 | }
51 | }
52 |
53 | .mat-mdc-tab-header {
54 | --mat-tab-header-label-text-size: 0.75rem;
55 | --mdc-secondary-navigation-tab-container-height: 1.5rem;
56 | }
57 |
58 | .mat-mdc-tab-header-pagination {
59 | min-width: var(--mdc-secondary-navigation-tab-container-height);
60 | }
61 |
62 | .mat-mdc-tab {
63 | min-width: auto;
64 | padding: 0 0.75rem;
65 | }
66 | }
67 |
68 | .gui-list-item-heading {
69 | display: flex;
70 | align-items: center;
71 | position: relative;
72 | padding: 0 0.75rem;
73 | line-height: 1.5rem;
74 | border-bottom: var(--mat-tab-header-divider-height) solid transparent;
75 |
76 | &::before {
77 | position: absolute;
78 | left: 0;
79 | width: 0.125rem;
80 | height: 1rem;
81 | background-color: var(--mat-expansion-header-text-color);
82 | content: '';
83 |
84 | [dir='rtl'] & {
85 | left: auto;
86 | right: 0;
87 | }
88 | }
89 | }
90 |
91 | .gui-list-item-title {
92 | display: flex;
93 | flex: 1;
94 | padding: 0;
95 | background-color: transparent;
96 | border: none;
97 | color: inherit;
98 | font-size: inherit;
99 | font-family: inherit;
100 | letter-spacing: inherit;
101 | line-height: inherit;
102 | cursor: pointer;
103 |
104 | .gui-list-item-active & {
105 | font-weight: bold;
106 | }
107 | }
108 |
109 | .gui-menu {
110 | .mat-mdc-tab-header {
111 | --mdc-tab-indicator-active-indicator-shape: 0.25rem;
112 |
113 | padding: 0.25rem;
114 | background-color: var(--mdc-filled-text-field-container-color);
115 |
116 | .mdc-tab__ripple::before {
117 | border-radius: var(--mdc-tab-indicator-active-indicator-shape);
118 | }
119 | }
120 |
121 | .mat-mdc-tab-labels {
122 | gap: 0.25rem;
123 | }
124 |
125 | .mdc-tab-indicator .mdc-tab-indicator__content {
126 | height: 100%;
127 | background-color: var(--mdc-tab-indicator-active-indicator-color);
128 | }
129 |
130 | .mdc-tab-indicator--active .mdc-tab-indicator__content {
131 | opacity: 0.24;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/projects/gui/gui-icons.ts:
--------------------------------------------------------------------------------
1 | import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
2 | import { MatIconRegistry } from '@angular/material/icon';
3 | import { DomSanitizer } from '@angular/platform-browser';
4 |
5 | export const svgIcons = {
6 | horizontal: `
7 |
8 |
9 |
10 | `,
11 | vertical: `
12 |
13 |
14 |
15 | `,
16 | add: `
17 |
18 |
19 |
20 | `,
21 | delete: `
22 |
23 |
24 |
25 | `,
26 | copy: `
27 |
28 |
29 |
30 | `,
31 | link: `
32 |
33 |
34 |
35 | `,
36 | clear: `
37 |
38 |
39 |
40 | `,
41 | file: `
42 |
43 |
44 |
45 | `,
46 | upload: `
47 |
48 |
49 |
50 | `,
51 | expand: `
52 |
53 |
54 | `,
55 | wrap: `
56 |
57 |
58 | `,
59 | };
60 |
61 | export type GuiIconType = keyof typeof svgIcons;
62 |
63 | export type GuiIconsConfig = Record;
64 |
65 | /** Injection token that can be used to provide the default icons. */
66 | export const GUI_ICONS_CONFIG = new InjectionToken('gui-icons-config');
67 |
68 | @Injectable({ providedIn: 'root' })
69 | export class GuiIconsRegistry {
70 | constructor(
71 | private _iconRegistry: MatIconRegistry,
72 | private _sanitizer: DomSanitizer,
73 | @Optional() @Inject(GUI_ICONS_CONFIG) private _defaultIcons?: GuiIconsConfig
74 | ) {}
75 |
76 | add(...iconNames: GuiIconType[]) {
77 | const icons = Object.assign(svgIcons, this._defaultIcons);
78 | iconNames.forEach(k => {
79 | this._iconRegistry.addSvgIconLiteral(k, this._sanitizer.bypassSecurityTrustHtml(icons[k]));
80 | });
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/projects/gui/gui-module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { ReactiveFormsModule, FormsModule } from '@angular/forms';
4 |
5 | import { MatFormFieldModule } from '@angular/material/form-field';
6 | import { MatInputModule } from '@angular/material/input';
7 | import { MatIconModule } from '@angular/material/icon';
8 | import { MatSelectModule } from '@angular/material/select';
9 | import { MatSliderModule } from '@angular/material/slider';
10 | import { MatSlideToggleModule } from '@angular/material/slide-toggle';
11 | import { MatButtonToggleModule } from '@angular/material/button-toggle';
12 | import { MatButtonModule } from '@angular/material/button';
13 | import { MatTabsModule } from '@angular/material/tabs';
14 | import { MatExpansionModule } from '@angular/material/expansion';
15 | import { MatTooltipModule } from '@angular/material/tooltip';
16 | import { MtxColorpickerModule } from '@ng-matero/extensions/colorpicker';
17 | import { MtxSelectModule } from '@ng-matero/extensions/select';
18 |
19 | import { GuiForm } from './gui-form';
20 | import { GuiFieldGroup } from './field-group/field-group';
21 | import { GuiFieldLabel } from './field-label/field-label';
22 | import { GuiInlineGroup } from './inline-group/inline-group';
23 | import { GuiInputText } from './input-text/input-text';
24 | import { GuiInputNumber } from './input-number/input-number';
25 | import { GuiSelect } from './select/select';
26 | import { GuiSwitch } from './switch/switch';
27 | import { GuiSlider } from './slider/slider';
28 | import { GuiIcon } from './button-toggle/icon';
29 | import { GuiButtonToggle } from './button-toggle/button-toggle';
30 | import { GuiFill } from './fill/fill';
31 | import { GuiImageSelect } from './image-select/image-select';
32 | import { GuiCombobox } from './combobox/combobox';
33 | import { GuiTextarea } from './textarea/textarea';
34 | import { GuiCodearea } from './codearea/codearea';
35 | import { GuiCodeareaDialog } from './codearea/codearea-dialog';
36 | import { GuiFileUploader } from './file-uploader/file-uploader';
37 | import { GuiIconButtonWrapper } from './icon-button-wrapper/icon-button-wrapper';
38 | import { GuiEjsPipe, GuiFlexDirective } from './gui-utils';
39 |
40 | @NgModule({
41 | imports: [
42 | CommonModule,
43 | ReactiveFormsModule,
44 | FormsModule,
45 | MatFormFieldModule,
46 | MatInputModule,
47 | MatSelectModule,
48 | MatSliderModule,
49 | MatSlideToggleModule,
50 | MatButtonToggleModule,
51 | MatIconModule,
52 | MatButtonModule,
53 | MatTabsModule,
54 | MatExpansionModule,
55 | MatTooltipModule,
56 | MtxColorpickerModule,
57 | MtxSelectModule,
58 | GuiForm,
59 | GuiInputText,
60 | GuiInputNumber,
61 | GuiSelect,
62 | GuiSwitch,
63 | GuiSlider,
64 | GuiIcon,
65 | GuiButtonToggle,
66 | GuiFill,
67 | GuiFieldGroup,
68 | GuiFieldLabel,
69 | GuiInlineGroup,
70 | GuiImageSelect,
71 | GuiCombobox,
72 | GuiTextarea,
73 | GuiCodearea,
74 | GuiCodeareaDialog,
75 | GuiFileUploader,
76 | GuiIconButtonWrapper,
77 | GuiEjsPipe,
78 | GuiFlexDirective,
79 | ],
80 | exports: [
81 | GuiForm,
82 | GuiInputText,
83 | GuiInputNumber,
84 | GuiSelect,
85 | GuiSwitch,
86 | GuiSlider,
87 | GuiIcon,
88 | GuiButtonToggle,
89 | GuiFill,
90 | GuiFieldGroup,
91 | GuiFieldLabel,
92 | GuiInlineGroup,
93 | GuiImageSelect,
94 | GuiCombobox,
95 | GuiTextarea,
96 | GuiCodearea,
97 | GuiCodeareaDialog,
98 | GuiFileUploader,
99 | GuiIconButtonWrapper,
100 | GuiEjsPipe,
101 | GuiFlexDirective,
102 | ],
103 | })
104 | export class GuiModule {}
105 |
--------------------------------------------------------------------------------
/projects/gui/gui-utils.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Input, OnInit, Pipe, PipeTransform } from '@angular/core';
2 | import { GuiDefaultValue, GuiOperator } from './interface';
3 |
4 | /**
5 | * Lightweight EJS template engine
6 | *
7 | * @param str template string
8 | * @param data data passed to the template
9 | * @returns
10 | *
11 | * ### Example
12 | *
13 | * ```ts
14 | * const people = ['geddy', 'neil', 'alex'];
15 | * const res = ejsTmpl('<%= people.join(", ") %>', {people: people});
16 | * console.log(res);
17 | * // => 'geddy, neil, alex'
18 | * ```
19 | *
20 | */
21 | export function ejsTmpl(str: string, data: any) {
22 | const fn = new Function(
23 | 'obj',
24 | 'var p=[],print=function(){p.push.apply(p,arguments);};' +
25 | // Introduce the data as local variables using with(){}
26 | 'with(obj){p.push("' +
27 | // Convert the template into pure JavaScript
28 | str
29 | .replace(/[\r\t\n]/g, ' ')
30 | .split('<%')
31 | .join('\t')
32 | .replace(/((^|%>)[^\t]*)'/g, '$1\r')
33 | .replace(/\t=(.*?)%>/g, '",$1,"')
34 | .split('\t')
35 | .join('");')
36 | .split('%>')
37 | .join('p.push("')
38 | .split('\r')
39 | .join('"') +
40 | '");}return p.join("");'
41 | );
42 |
43 | // Provide some basic currying to the user
44 | return data ? fn(data) : fn;
45 | }
46 |
47 | @Pipe({
48 | name: 'ejs',
49 | standalone: true,
50 | })
51 | export class GuiEjsPipe implements PipeTransform {
52 | transform(value: string, data = {}): string {
53 | return ejsTmpl(value, data);
54 | }
55 | }
56 |
57 | @Directive({
58 | selector: '[flex]',
59 | standalone: true,
60 | })
61 | export class GuiFlexDirective implements OnInit {
62 | @Input() flex: number | undefined = 100;
63 |
64 | constructor(private el: ElementRef) {}
65 |
66 | ngOnInit(): void {
67 | this.el.nativeElement.style.flex = `1 1 ${this.flex}%`;
68 | this.el.nativeElement.style.maxWidth = `${this.flex}%`;
69 | }
70 | }
71 |
72 | export function compareValues(a: GuiDefaultValue, b: GuiDefaultValue, operator: GuiOperator) {
73 | switch (operator) {
74 | case '$eq':
75 | return a === b;
76 | case '$ne':
77 | return a !== b;
78 | case '$gt':
79 | return (a ?? 0) > (b ?? 0);
80 | case '$lt':
81 | return (a ?? 0) < (b ?? 0);
82 | case '$gte':
83 | return (a ?? 0) >= (b ?? 0);
84 | case '$lte':
85 | return (a ?? 0) <= (b ?? 0);
86 | case '$in':
87 | return Array.isArray(b) && b.includes(a);
88 | case '$nin':
89 | return Array.isArray(b) && !b.includes(a);
90 | default:
91 | return false;
92 | }
93 | }
94 |
95 | export function getValueByPath(obj: Record, path: string) {
96 | return path.split('.').reduce((acc: Record | undefined, key) => {
97 | return acc?.['children']?.[key] ? acc['children'][key] : acc?.[key];
98 | }, obj);
99 | }
100 |
--------------------------------------------------------------------------------
/projects/gui/icon-button-wrapper/icon-button-wrapper.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/projects/gui/icon-button-wrapper/icon-button-wrapper.scss:
--------------------------------------------------------------------------------
1 | .gui-icon-button-wrapper {
2 | display: block;
3 | line-height: 1;
4 |
5 | .mat-mdc-icon-button {
6 | --mdc-icon-button-state-layer-size: 1.5rem;
7 | --mdc-icon-button-icon-size: 0.875rem;
8 | --mat-icon-button-touch-target-display: none;
9 |
10 | padding: 0;
11 | }
12 |
13 | .mat-icon {
14 | display: inline-flex;
15 | justify-content: center;
16 | align-items: center;
17 | width: 1.5rem;
18 | height: 1.5rem;
19 |
20 | svg {
21 | width: 0.875rem;
22 | height: 0.875rem;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/projects/gui/icon-button-wrapper/icon-button-wrapper.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'gui-icon-button-wrapper',
5 | standalone: true,
6 | templateUrl: './icon-button-wrapper.html',
7 | styleUrl: './icon-button-wrapper.scss',
8 | host: {
9 | class: 'gui-icon-button-wrapper',
10 | },
11 | encapsulation: ViewEncapsulation.None,
12 | changeDetection: ChangeDetectionStrategy.OnPush,
13 | })
14 | export class GuiIconButtonWrapper {}
15 |
--------------------------------------------------------------------------------
/projects/gui/image-select/image-select.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
14 |
15 |
16 | {{ opt.label }}
17 |
18 |
19 |
20 | {{ opt.label }}
21 |
22 |
23 | @if (config.suffix) {
24 | {{ config.suffix }}
25 | }
26 | @if (config.parentType === 'inline') {
27 |
28 |
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/projects/gui/image-select/image-select.scss:
--------------------------------------------------------------------------------
1 | %img {
2 | height: 100%;
3 | max-width: 7.5rem;
4 | object-fit: contain;
5 | border-radius: 0.25rem;
6 | background-color: var(--mat-option-hover-state-layer-color);
7 | }
8 |
9 | .gui-image-select {
10 | --mat-form-field-container-vertical-padding: 0;
11 |
12 | .ng-select {
13 | padding: 0 0.5rem;
14 | margin: 0 -0.5rem;
15 |
16 | .ng-value-container {
17 | height: 4rem;
18 | }
19 |
20 | .ng-value {
21 | display: inline-flex;
22 | align-items: center;
23 | gap: 0.5rem;
24 | height: 100%;
25 | padding: 0.5rem 0;
26 |
27 | img {
28 | @extend %img;
29 | }
30 | }
31 |
32 | .ng-clear-wrapper .ng-clear {
33 | font-family: math;
34 | font-size: 12px;
35 | }
36 |
37 | .ng-arrow {
38 | vertical-align: -1px;
39 | }
40 | }
41 |
42 | &.ng-dropdown-panel {
43 | .ng-dropdown-panel-items {
44 | .ng-option {
45 | display: flex;
46 | align-items: center;
47 | gap: 0.5rem;
48 | height: 4rem;
49 | padding: 0.5rem;
50 |
51 | img {
52 | @extend %img;
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/projects/gui/image-select/image-select.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewInit,
3 | ChangeDetectionStrategy,
4 | ChangeDetectorRef,
5 | Component,
6 | forwardRef,
7 | Input,
8 | ViewChild,
9 | ViewEncapsulation,
10 | } from '@angular/core';
11 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
12 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
13 | import {
14 | MtxSelect,
15 | MtxSelectLabelTemplate,
16 | MtxSelectOptionTemplate,
17 | } from '@ng-matero/extensions/select';
18 | import { GuiFieldLabel } from '../field-label/field-label';
19 | import { GuiControl } from '../interface';
20 |
21 | @Component({
22 | selector: 'gui-image-select',
23 | templateUrl: './image-select.html',
24 | styleUrl: './image-select.scss',
25 | host: {
26 | class: 'gui-field gui-image-select',
27 | },
28 | encapsulation: ViewEncapsulation.None,
29 | changeDetection: ChangeDetectionStrategy.OnPush,
30 | providers: [
31 | {
32 | provide: NG_VALUE_ACCESSOR,
33 | useExisting: forwardRef(() => GuiImageSelect),
34 | multi: true,
35 | },
36 | ],
37 | standalone: true,
38 | imports: [
39 | FormsModule,
40 | MatFormField,
41 | MatPrefix,
42 | MatSuffix,
43 | MatHint,
44 | MtxSelect,
45 | MtxSelectLabelTemplate,
46 | MtxSelectOptionTemplate,
47 | GuiFieldLabel,
48 | ],
49 | })
50 | export class GuiImageSelect implements ControlValueAccessor, AfterViewInit {
51 | @ViewChild(MtxSelect) mtxSelect!: MtxSelect;
52 |
53 | @Input() config: Partial = {};
54 | @Input() disabled = false;
55 | @Input() appendTo = 'body';
56 |
57 | value: unknown;
58 |
59 | private onChange: (value: unknown) => void = () => {};
60 | private onTouched: () => void = () => {};
61 |
62 | constructor(private cdr: ChangeDetectorRef) {}
63 |
64 | ngAfterViewInit(): void {
65 | // Add additional class for ng-select's dropdown panel
66 | const { ngSelect } = this.mtxSelect;
67 | ngSelect.classes = (ngSelect.classes || '') + ' gui-image-select';
68 | }
69 |
70 | writeValue(value: unknown) {
71 | this.value = value;
72 | this.cdr.markForCheck();
73 | }
74 |
75 | registerOnChange(fn: (value: unknown) => void) {
76 | this.onChange = fn;
77 | }
78 |
79 | registerOnTouched(fn: () => void) {
80 | this.onTouched = fn;
81 | }
82 |
83 | setDisabledState(isDisabled: boolean) {
84 | this.disabled = isDisabled;
85 | this.cdr.markForCheck();
86 | }
87 |
88 | onValueChange() {
89 | this.onChange(this.value);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/projects/gui/inline-group/inline-group.html:
--------------------------------------------------------------------------------
1 | @if (config.name) {
2 |
3 | }
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/projects/gui/inline-group/inline-group.scss:
--------------------------------------------------------------------------------
1 | .gui-inline-group {
2 | display: flex;
3 | padding: 0.25rem 0.5rem;
4 |
5 | mat-hint {
6 | width: 100%;
7 | line-height: var(--mat-form-field-subscript-text-line-height);
8 | -webkit-font-smoothing: antialiased;
9 | }
10 | }
11 |
12 | .gui-inline-group-content {
13 | display: inline-flex;
14 | flex: 1;
15 | flex-flow: row wrap;
16 | margin: 0 -0.25rem;
17 |
18 | >.gui-field-group,
19 | >.gui-inline-group,
20 | >.mat-expansion-panel,
21 | >.gui-menu {
22 | width: 100%;
23 | padding: 0 0.25rem;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/projects/gui/inline-group/inline-group.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
2 | import { GuiFieldLabel } from '../field-label/field-label';
3 | import { GuiControl } from '../interface';
4 |
5 | @Component({
6 | selector: 'gui-inline-group',
7 | templateUrl: './inline-group.html',
8 | styleUrl: './inline-group.scss',
9 | host: {
10 | class: 'gui-inline-group',
11 | },
12 | encapsulation: ViewEncapsulation.None,
13 | changeDetection: ChangeDetectionStrategy.OnPush,
14 | standalone: true,
15 | imports: [GuiFieldLabel],
16 | })
17 | export class GuiInlineGroup {
18 | @Input() config: Partial = {};
19 | }
20 |
--------------------------------------------------------------------------------
/projects/gui/input-number/input-number.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
16 | @if (config.suffix) {
17 | {{ config.suffix }}
18 | }
19 | @if (config.parentType === 'inline') {
20 |
21 |
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/projects/gui/input-number/input-number.scss:
--------------------------------------------------------------------------------
1 | .gui-input-number {
2 | }
3 |
--------------------------------------------------------------------------------
/projects/gui/input-number/input-number.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
11 | import { MatInput } from '@angular/material/input';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiControl } from '../interface';
14 |
15 | @Component({
16 | selector: 'gui-input-number',
17 | templateUrl: './input-number.html',
18 | styleUrl: './input-number.scss',
19 | host: {
20 | class: 'gui-field gui-input-number',
21 | },
22 | encapsulation: ViewEncapsulation.None,
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | providers: [
25 | {
26 | provide: NG_VALUE_ACCESSOR,
27 | useExisting: forwardRef(() => GuiInputNumber),
28 | multi: true,
29 | },
30 | ],
31 | standalone: true,
32 | imports: [FormsModule, MatFormField, MatPrefix, MatInput, MatSuffix, MatHint, GuiFieldLabel],
33 | })
34 | export class GuiInputNumber implements ControlValueAccessor {
35 | @Input() config: Partial = {};
36 | @Input() disabled = false;
37 |
38 | value!: number;
39 |
40 | private onChange: (value: number) => void = () => {};
41 | private onTouched: () => void = () => {};
42 |
43 | constructor(private cdr: ChangeDetectorRef) {}
44 |
45 | writeValue(value: number) {
46 | this.value = value;
47 | this.cdr.markForCheck();
48 | }
49 |
50 | registerOnChange(fn: (value: number) => void) {
51 | this.onChange = fn;
52 | }
53 |
54 | registerOnTouched(fn: () => void) {
55 | this.onTouched = fn;
56 | }
57 |
58 | setDisabledState(isDisabled: boolean) {
59 | this.disabled = isDisabled;
60 | this.cdr.markForCheck();
61 | }
62 |
63 | onValueChange() {
64 | this.onChange(this.value);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/projects/gui/input-text/input-text.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
13 | @if (config.suffix) {
14 | {{ config.suffix }}
15 | }
16 | @if (config.parentType === 'inline') {
17 |
18 |
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/projects/gui/input-text/input-text.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acrodata/gui/cb79cb68ecc0af7663e677743690feffc7effd8a/projects/gui/input-text/input-text.scss
--------------------------------------------------------------------------------
/projects/gui/input-text/input-text.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
11 | import { MatInput } from '@angular/material/input';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiControl } from '../interface';
14 |
15 | @Component({
16 | selector: 'gui-input-text',
17 | templateUrl: './input-text.html',
18 | styleUrl: './input-text.scss',
19 | host: {
20 | class: 'gui-field gui-input-text',
21 | },
22 | encapsulation: ViewEncapsulation.None,
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | providers: [
25 | {
26 | provide: NG_VALUE_ACCESSOR,
27 | useExisting: forwardRef(() => GuiInputText),
28 | multi: true,
29 | },
30 | ],
31 | standalone: true,
32 | imports: [FormsModule, MatFormField, MatPrefix, MatInput, MatSuffix, MatHint, GuiFieldLabel],
33 | })
34 | export class GuiInputText implements ControlValueAccessor {
35 | @Input() config: Partial = {};
36 | @Input() disabled = false;
37 |
38 | value = '';
39 |
40 | private onChange: (value: string) => void = () => {};
41 | private onTouched: () => void = () => {};
42 |
43 | constructor(private cdr: ChangeDetectorRef) {}
44 |
45 | writeValue(value: string) {
46 | this.value = value;
47 | this.cdr.markForCheck();
48 | }
49 |
50 | registerOnChange(fn: (value: string) => void) {
51 | this.onChange = fn;
52 | }
53 |
54 | registerOnTouched(fn: () => void) {
55 | this.onTouched = fn;
56 | }
57 |
58 | setDisabledState(isDisabled: boolean) {
59 | this.disabled = isDisabled;
60 | this.cdr.markForCheck();
61 | }
62 |
63 | onValueChange() {
64 | this.onChange(this.value);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/projects/gui/interface.ts:
--------------------------------------------------------------------------------
1 | export type GuiFieldType =
2 | | 'group'
3 | | 'inline'
4 | | 'tabs'
5 | | 'tab'
6 | | 'menu'
7 | | 'menuItem'
8 | | 'text'
9 | | 'number'
10 | | 'switch'
11 | | 'slider'
12 | | 'fill'
13 | | 'select'
14 | | 'buttonToggle'
15 | | 'imageSelect'
16 | | 'combobox'
17 | | 'textarea'
18 | | 'codearea'
19 | | 'hidden'
20 | | 'image'
21 | | 'video'
22 | | 'audio'
23 | | 'file';
24 |
25 | export type GuiFillMode = 'flat' | 'gradient' | 'image';
26 |
27 | export type GuiSliderMode = 'normal' | 'range';
28 |
29 | export type GuiTabsMode = 'normal' | 'list';
30 |
31 | export type GuiBasicValue = boolean | number | string;
32 |
33 | export type GuiDefaultValue = boolean | number | string | any[] | Record | null;
34 |
35 | export type GuiOperator = '$eq' | '$ne' | '$gt' | '$lt' | '$gte' | '$lte' | '$in' | '$nin';
36 |
37 | export type GuiCondition = [string, GuiOperator, GuiDefaultValue];
38 |
39 | export interface GuiFieldShowIf {
40 | conditions: GuiCondition[];
41 | logicalType?: '$and' | '$or';
42 | }
43 |
44 | export interface GuiField {
45 | type: GuiFieldType;
46 | name: string;
47 | default?: GuiDefaultValue;
48 | description?: string;
49 | placeholder?: string;
50 | disabled?: boolean;
51 | col?: number;
52 | showIf?: GuiFieldShowIf;
53 | show?: boolean;
54 | // group & menu & tabs & inline
55 | children?: GuiFields | GuiField[];
56 | // group & tabs
57 | expanded?: boolean;
58 | // number & slider
59 | min?: number;
60 | max?: number;
61 | step?: number;
62 | // text & number & slider & select
63 | prefix?: string;
64 | suffix?: string;
65 | // select & buttonToggle & imageSelect & combobox
66 | options?: GuiFieldOption[];
67 | // select & combobox
68 | useFont?: boolean;
69 | // select & buttonToggle & combobox
70 | multiple?: boolean;
71 | // buttonToggle
72 | useIcon?: boolean;
73 | // tabs
74 | template?: Partial;
75 | addable?: boolean;
76 | // fill & slider & tabs
77 | mode?: GuiFillMode | GuiSliderMode | GuiTabsMode;
78 | // textarea
79 | rows?: number;
80 | // image & video & audio & file
81 | accept?: string;
82 | // codearea
83 | height?: string | number;
84 | language?: string;
85 | }
86 |
87 | export type GuiFields = Record;
88 |
89 | export interface GuiFieldOption {
90 | label: string;
91 | value: string | number | boolean;
92 | disabled?: boolean;
93 | src?: string;
94 | col?: number;
95 | }
96 |
97 | export interface GuiControl extends GuiField {
98 | _type: 'control' | 'group' | 'array';
99 | key: string;
100 | parentType: GuiFieldType;
101 | model?: any;
102 | default?: any;
103 | index?: number;
104 | children?: GuiControl[];
105 | template?: Partial;
106 | selectedIndex?: number;
107 | }
108 |
109 | export type GuiControls = Record;
110 |
--------------------------------------------------------------------------------
/projects/gui/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/gui",
4 | "assets": [
5 | "**/*.scss"
6 | ],
7 | "lib": {
8 | "entryFile": "public-api.ts"
9 | },
10 | "allowedNonPeerDependencies": [
11 | "@acrodata/code-editor",
12 | "@acrodata/rnd-dialog"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/gui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@acrodata/gui",
3 | "version": "2.5.1",
4 | "description": "JSON powered GUI for configurable panels.",
5 | "publishConfig": {
6 | "access": "public"
7 | },
8 | "author": "nzbin",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/acrodata/gui.git"
13 | },
14 | "homepage": "https://acrodata.github.io/gui/",
15 | "keywords": [
16 | "gui",
17 | "angular",
18 | "typescript",
19 | "creative-coding"
20 | ],
21 | "exports": {
22 | ".": {
23 | "sass": "./_index.scss"
24 | }
25 | },
26 | "peerDependencies": {
27 | "@angular/cdk": ">=18.0.0",
28 | "@angular/common": ">=18.0.0",
29 | "@angular/core": ">=18.0.0",
30 | "@angular/forms": ">=18.0.0",
31 | "@angular/material": ">=18.0.0",
32 | "@ng-matero/extensions": ">=18.0.0"
33 | },
34 | "dependencies": {
35 | "@acrodata/code-editor": "^0.5.0",
36 | "@acrodata/rnd-dialog": "^0.3.0",
37 | "tslib": "^2.3.0"
38 | },
39 | "sideEffects": false
40 | }
41 |
--------------------------------------------------------------------------------
/projects/gui/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of gui
3 | */
4 |
5 | export * from './gui-module';
6 | export * from './gui-form';
7 | export * from './button-toggle/button-toggle';
8 | export * from './button-toggle/icon';
9 | export * from './codearea/codearea';
10 | export * from './codearea/codearea-dialog';
11 | export * from './codearea/codearea-config';
12 | export * from './combobox/combobox';
13 | export * from './field-group/field-group';
14 | export * from './field-label/field-label';
15 | export * from './file-uploader/file-uploader';
16 | export * from './file-uploader/file-uploader-config';
17 | export * from './fill/fill';
18 | export * from './icon-button-wrapper/icon-button-wrapper';
19 | export * from './image-select/image-select';
20 | export * from './inline-group/inline-group';
21 | export * from './input-number/input-number';
22 | export * from './input-text/input-text';
23 | export * from './select/select';
24 | export * from './slider/slider';
25 | export * from './switch/switch';
26 | export * from './textarea/textarea';
27 | export * from './gui-utils';
28 | export * from './gui-icons';
29 | export * from './interface';
30 |
--------------------------------------------------------------------------------
/projects/gui/select/select.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
15 | @for (opt of config.options; track opt) {
16 |
17 | {{ opt.label }}
18 |
19 | }
20 |
21 | @if (config.suffix) {
22 | {{ config.suffix }}
23 | }
24 | @if (config.parentType === 'inline') {
25 |
26 |
27 |
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/projects/gui/select/select.scss:
--------------------------------------------------------------------------------
1 | .gui-select {
2 | --mat-select-trigger-text-line-height: 1rem;
3 | --mat-select-trigger-text-size: 0.75rem;
4 | --mat-option-label-text-size: 0.75rem;
5 |
6 | .mat-mdc-option {
7 | min-height: var(--mat-option-label-text-line-height);
8 | padding: 0 0.5rem;
9 |
10 | .mat-pseudo-checkbox-full {
11 | margin-right: 0.5rem;
12 |
13 | [dir='rtl'] & {
14 | margin-right: 0;
15 | margin-left: 0.5rem;
16 | }
17 | }
18 | }
19 |
20 | .mat-mdc-select-arrow-wrapper {
21 | height: 1rem;
22 | }
23 |
24 | .mat-pseudo-checkbox {
25 | width: 1rem;
26 | height: 1rem;
27 | border-width: 1px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/projects/gui/select/select.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatOption } from '@angular/material/core';
11 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
12 | import { MatSelect } from '@angular/material/select';
13 | import { GuiFieldLabel } from '../field-label/field-label';
14 | import { GuiBasicValue, GuiControl } from '../interface';
15 |
16 | @Component({
17 | selector: 'gui-select',
18 | templateUrl: './select.html',
19 | styleUrl: './select.scss',
20 | host: {
21 | class: 'gui-field gui-select',
22 | },
23 | encapsulation: ViewEncapsulation.None,
24 | changeDetection: ChangeDetectionStrategy.OnPush,
25 | providers: [
26 | {
27 | provide: NG_VALUE_ACCESSOR,
28 | useExisting: forwardRef(() => GuiSelect),
29 | multi: true,
30 | },
31 | ],
32 | standalone: true,
33 | imports: [
34 | FormsModule,
35 | MatFormField,
36 | MatPrefix,
37 | MatSelect,
38 | MatOption,
39 | MatSuffix,
40 | MatHint,
41 | GuiFieldLabel,
42 | ],
43 | })
44 | export class GuiSelect implements ControlValueAccessor {
45 | @Input() config: Partial = {};
46 | @Input() disabled = false;
47 |
48 | value: GuiBasicValue | GuiBasicValue[] = '';
49 |
50 | private onChange: (value: GuiBasicValue | GuiBasicValue[]) => void = () => {};
51 | private onTouched: () => void = () => {};
52 |
53 | constructor(private cdr: ChangeDetectorRef) {}
54 |
55 | writeValue(value: GuiBasicValue | GuiBasicValue[]) {
56 | this.value = value;
57 | this.cdr.markForCheck();
58 | }
59 |
60 | registerOnChange(fn: (value: GuiBasicValue | GuiBasicValue[]) => void) {
61 | this.onChange = fn;
62 | }
63 |
64 | registerOnTouched(fn: () => void) {
65 | this.onTouched = fn;
66 | }
67 |
68 | setDisabledState(isDisabled: boolean) {
69 | this.disabled = isDisabled;
70 | this.cdr.markForCheck();
71 | }
72 |
73 | onValueChange() {
74 | this.onChange(this.value);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/projects/gui/slider/slider.html:
--------------------------------------------------------------------------------
1 | @if (config.mode !== 'range') {
2 |
3 |
4 |
5 |
6 |
7 | @if (config.prefix) {
8 | {{ config.prefix }}
9 | }
10 |
21 | @if (config.suffix) {
22 | {{ config.suffix }}
23 | }
24 |
25 | } @else {
26 |
27 |
28 |
29 |
30 |
31 |
32 | @if (config.prefix) {
33 | {{ config.prefix }}
34 | }
35 |
46 | @if (config.suffix) {
47 | {{ config.suffix }}
48 | }
49 |
50 |
51 | @if (config.prefix) {
52 | {{ config.prefix }}
53 | }
54 |
65 | @if (config.suffix) {
66 | {{ config.suffix }}
67 | }
68 |
69 | }
70 |
71 | @if (config.parentType === 'inline') {
72 |
73 |
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/projects/gui/slider/slider.scss:
--------------------------------------------------------------------------------
1 | .gui-slider {
2 | .mat-mdc-slider {
3 | --mdc-slider-handle-width: 0.75rem;
4 | --mdc-slider-handle-height: 0.75rem;
5 | --mdc-slider-inactive-track-height: 0.125rem;
6 | --mdc-slider-active-track-height: 0.125rem;
7 |
8 | flex: 3;
9 | min-width: auto;
10 | height: calc(var(--mdc-slider-handle-width) * 2);
11 | margin: 0 calc(var(--mdc-slider-handle-width) / 2);
12 |
13 | +.mat-mdc-form-field {
14 | flex: 2;
15 | }
16 |
17 | &.mdc-slider--range {
18 | +.mat-mdc-form-field,
19 | +.mat-mdc-form-field+.mat-mdc-form-field {
20 | flex: 1.5;
21 | }
22 | }
23 |
24 | .mdc-slider__input {
25 | top: 0;
26 | height: calc(var(--mdc-slider-handle-width) * 2);
27 | }
28 |
29 | .mdc-slider__thumb {
30 | left: calc(var(--mdc-slider-handle-width) * -1);
31 | width: calc(var(--mdc-slider-handle-width) * 2);
32 | height: calc(var(--mdc-slider-handle-width) * 2);
33 | border-radius: 50%;
34 | overflow: hidden;
35 | }
36 | }
37 |
38 | .mat-mdc-form-field {
39 | margin-left: 0.375rem;
40 |
41 | [dir='rtl'] & {
42 | margin-left: 0;
43 | margin-right: 0.375rem;
44 | }
45 |
46 | input::-webkit-outer-spin-button,
47 | input::-webkit-inner-spin-button {
48 | appearance: none;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/projects/gui/slider/slider.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
11 | import { MatInput } from '@angular/material/input';
12 | import { MatSlider, MatSliderRangeThumb, MatSliderThumb } from '@angular/material/slider';
13 | import { GuiFieldLabel } from '../field-label/field-label';
14 | import { GuiControl } from '../interface';
15 |
16 | @Component({
17 | selector: 'gui-slider',
18 | templateUrl: './slider.html',
19 | styleUrl: './slider.scss',
20 | host: {
21 | class: 'gui-field gui-slider',
22 | },
23 | encapsulation: ViewEncapsulation.None,
24 | changeDetection: ChangeDetectionStrategy.OnPush,
25 | providers: [
26 | {
27 | provide: NG_VALUE_ACCESSOR,
28 | useExisting: forwardRef(() => GuiSlider),
29 | multi: true,
30 | },
31 | ],
32 | standalone: true,
33 | imports: [
34 | FormsModule,
35 | MatSlider,
36 | MatSliderThumb,
37 | MatFormField,
38 | MatPrefix,
39 | MatInput,
40 | MatSuffix,
41 | MatSliderRangeThumb,
42 | MatHint,
43 | GuiFieldLabel,
44 | ],
45 | })
46 | export class GuiSlider implements ControlValueAccessor {
47 | @Input() config: Partial = {};
48 | @Input() disabled = false;
49 |
50 | value!: number | number[];
51 |
52 | // The input binding value for range slider
53 | minValue!: number;
54 | maxValue!: number;
55 |
56 | private onChange: (value: number | number[]) => void = () => {};
57 | private onTouched: () => void = () => {};
58 |
59 | constructor(private cdr: ChangeDetectorRef) {}
60 |
61 | writeValue(value: number) {
62 | this.value = value;
63 | this.setInputValue();
64 | this.cdr.markForCheck();
65 | }
66 |
67 | registerOnChange(fn: (value: number | number[]) => void) {
68 | this.onChange = fn;
69 | }
70 |
71 | registerOnTouched(fn: () => void) {
72 | this.onTouched = fn;
73 | }
74 |
75 | setDisabledState(isDisabled: boolean) {
76 | this.disabled = isDisabled;
77 | this.cdr.markForCheck();
78 | }
79 |
80 | onValueChange() {
81 | this.onChange(this.value);
82 | }
83 |
84 | onRangeSliderChange() {
85 | this.setInputValue();
86 | this.onValueChange();
87 | }
88 |
89 | onRangeInputChange() {
90 | this.value = [this.minValue, this.maxValue];
91 | this.onValueChange();
92 | }
93 |
94 | setInputValue() {
95 | if (Array.isArray(this.value)) {
96 | this.minValue = this.value[0];
97 | this.maxValue = this.value[1];
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/projects/gui/switch/switch.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.parentType === 'inline') {
3 |
4 |
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/projects/gui/switch/switch.scss:
--------------------------------------------------------------------------------
1 | .gui-switch {
2 | .mat-mdc-slide-toggle {
3 | --mat-switch-unselected-handle-size: 0.5rem;
4 | --mat-switch-selected-handle-size: 0.75rem;
5 | --mat-switch-pressed-handle-size: 0.75rem;
6 | --mat-switch-selected-handle-horizontal-margin: 0;
7 | --mat-switch-selected-pressed-handle-horizontal-margin: 0;
8 | --mat-switch-unselected-handle-horizontal-margin: 0 0.25rem;
9 | --mat-switch-unselected-pressed-handle-horizontal-margin: 0 0.125rem;
10 | --mat-switch-track-outline-width: 1px;
11 | --mdc-switch-track-width: 1.75rem;
12 | --mdc-switch-track-height: 1rem;
13 | --mdc-switch-track-shape: 0.5rem;
14 | --mdc-switch-handle-width: 0.875rem;
15 | --mdc-switch-state-layer-size: 1.5rem;
16 |
17 | line-height: var(--mdc-switch-state-layer-size);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/projects/gui/switch/switch.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatHint } from '@angular/material/form-field';
11 | import { MatSlideToggle } from '@angular/material/slide-toggle';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiControl } from '../interface';
14 |
15 | @Component({
16 | selector: 'gui-switch',
17 | templateUrl: './switch.html',
18 | styleUrl: './switch.scss',
19 | host: {
20 | class: 'gui-field gui-switch',
21 | },
22 | encapsulation: ViewEncapsulation.None,
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | providers: [
25 | {
26 | provide: NG_VALUE_ACCESSOR,
27 | useExisting: forwardRef(() => GuiSwitch),
28 | multi: true,
29 | },
30 | ],
31 | standalone: true,
32 | imports: [FormsModule, MatSlideToggle, MatHint, GuiFieldLabel],
33 | })
34 | export class GuiSwitch implements ControlValueAccessor {
35 | @Input() config: Partial = {};
36 | @Input() disabled = false;
37 |
38 | value = false;
39 |
40 | private onChange: (value: boolean) => void = () => {};
41 | private onTouched: () => void = () => {};
42 |
43 | constructor(private cdr: ChangeDetectorRef) {}
44 |
45 | writeValue(value: boolean) {
46 | this.value = value;
47 | this.cdr.markForCheck();
48 | }
49 |
50 | registerOnChange(fn: (value: boolean) => void) {
51 | this.onChange = fn;
52 | }
53 |
54 | registerOnTouched(fn: () => void) {
55 | this.onTouched = fn;
56 | }
57 |
58 | setDisabledState(isDisabled: boolean) {
59 | this.disabled = isDisabled;
60 | this.cdr.markForCheck();
61 | }
62 |
63 | onValueChange() {
64 | this.onChange(this.value);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/projects/gui/textarea/textarea.html:
--------------------------------------------------------------------------------
1 |
2 | @if (config.prefix) {
3 | {{ config.prefix }}
4 | }
5 |
13 | @if (config.suffix) {
14 | {{ config.suffix }}
15 | }
16 | @if (config.parentType === 'inline') {
17 |
18 |
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/projects/gui/textarea/textarea.scss:
--------------------------------------------------------------------------------
1 | .gui-textarea {
2 | }
3 |
--------------------------------------------------------------------------------
/projects/gui/textarea/textarea.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | forwardRef,
6 | Input,
7 | ViewEncapsulation,
8 | } from '@angular/core';
9 | import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
10 | import { MatFormField, MatHint, MatPrefix, MatSuffix } from '@angular/material/form-field';
11 | import { MatInput } from '@angular/material/input';
12 | import { GuiFieldLabel } from '../field-label/field-label';
13 | import { GuiControl } from '../interface';
14 |
15 | @Component({
16 | selector: 'gui-textarea',
17 | templateUrl: './textarea.html',
18 | styleUrl: './textarea.scss',
19 | host: {
20 | class: 'gui-field gui-textarea',
21 | },
22 | encapsulation: ViewEncapsulation.None,
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | providers: [
25 | {
26 | provide: NG_VALUE_ACCESSOR,
27 | useExisting: forwardRef(() => GuiTextarea),
28 | multi: true,
29 | },
30 | ],
31 | standalone: true,
32 | imports: [FormsModule, MatFormField, MatPrefix, MatInput, MatSuffix, MatHint, GuiFieldLabel],
33 | })
34 | export class GuiTextarea {
35 | @Input() config: Partial = {};
36 | @Input() disabled = false;
37 |
38 | value = '';
39 |
40 | private onChange: (value: string) => void = () => {};
41 | private onTouched: () => void = () => {};
42 |
43 | constructor(private cdr: ChangeDetectorRef) {}
44 |
45 | writeValue(value: string) {
46 | this.value = value;
47 | this.cdr.markForCheck();
48 | }
49 |
50 | registerOnChange(fn: (value: string) => void) {
51 | this.onChange = fn;
52 | }
53 |
54 | registerOnTouched(fn: () => void) {
55 | this.onTouched = fn;
56 | }
57 |
58 | setDisabledState(isDisabled: boolean) {
59 | this.disabled = isDisabled;
60 | this.cdr.markForCheck();
61 | }
62 |
63 | onValueChange() {
64 | this.onChange(this.value);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/projects/gui/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "**/*.spec.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/gui/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/projects/gui/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "**/*.spec.ts",
12 | "**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ],
26 | "paths": {
27 | "@acrodata/gui": [
28 | "projects/gui/public-api"
29 | ],
30 | }
31 | },
32 | "angularCompilerOptions": {
33 | "enableI18nLegacyMessageIdFormat": false,
34 | "strictInjectionParameters": true,
35 | "strictInputAccessModifiers": true,
36 | "strictTemplates": true
37 | }
38 | }
39 |
--------------------------------------------------------------------------------