├── src ├── assets │ ├── .gitkeep │ └── favicon.svg ├── app │ ├── preview │ │ ├── form │ │ │ ├── form.component.css │ │ │ ├── form.component.html │ │ │ └── form.component.ts │ │ ├── list │ │ │ ├── list.component.css │ │ │ └── list.component.html │ │ ├── menu │ │ │ ├── menu.component.css │ │ │ └── menu.component.html │ │ ├── tabs │ │ │ ├── tabs.component.css │ │ │ ├── tabs.component.html │ │ │ └── tabs.component.ts │ │ ├── cardview │ │ │ ├── cardview.component.css │ │ │ └── cardview.component.html │ │ ├── sliders │ │ │ ├── sliders.component.css │ │ │ ├── sliders.component.html │ │ │ └── sliders.component.ts │ │ ├── treelist │ │ │ ├── treelist.component.css │ │ │ └── treelist.component.html │ │ ├── treeview │ │ │ ├── treeview.component.css │ │ │ ├── treeview.component.html │ │ │ └── treeview.component.ts │ │ ├── datatgrid │ │ │ ├── datatgrid.component.css │ │ │ └── datatgrid.component.html │ │ ├── pivotgrid │ │ │ ├── pivotgrid.component.css │ │ │ └── pivotgrid.component.html │ │ ├── progressbar │ │ │ ├── progressbar.component.css │ │ │ ├── progressbar.component.html │ │ │ └── progressbar.component.ts │ │ ├── scheduler │ │ │ ├── scheduler.component.css │ │ │ └── scheduler.component.html │ │ ├── filterbuilder │ │ │ ├── filterbuilder.component.css │ │ │ ├── filterbuilder.component.html │ │ │ └── filterbuilder.component.ts │ │ ├── fieldset │ │ │ ├── fieldset.component.css │ │ │ ├── fieldset.component.ts │ │ │ └── fieldset.component.html │ │ ├── scrollview │ │ │ ├── scrollview.component.css │ │ │ ├── scrollview.component.ts │ │ │ └── scrollview.component.html │ │ ├── toolbar │ │ │ ├── toolbar.component.html │ │ │ ├── toolbar.component.css │ │ │ └── toolbar.component.ts │ │ ├── accordion │ │ │ ├── accordion.component.html │ │ │ ├── accordion.component.css │ │ │ └── accordion.component.ts │ │ ├── chat │ │ │ ├── chat.component.css │ │ │ ├── chat.component.html │ │ │ └── chat.component.ts │ │ ├── gallery │ │ │ ├── gallery.component.css │ │ │ ├── gallery.component.html │ │ │ └── gallery.component.ts │ │ ├── preview-title │ │ │ ├── preview-title.component.html │ │ │ ├── preview-title.component.css │ │ │ └── preview-title.component.ts │ │ ├── drawer │ │ │ ├── drawer.component.css │ │ │ ├── drawer.component.html │ │ │ └── drawer.component.ts │ │ ├── index.component.html │ │ ├── button-detailed │ │ │ ├── button-detailed.component.css │ │ │ ├── button-detailed.component.html │ │ │ └── button-detailed.component.ts │ │ ├── stepper │ │ │ ├── stepper.component.css │ │ │ ├── stepper.component.ts │ │ │ └── stepper.component.html │ │ ├── index.component.css │ │ ├── splitter │ │ │ ├── splitter.component.css │ │ │ └── splitter.component.ts │ │ ├── buttons │ │ │ ├── buttons.component.css │ │ │ └── buttons.component.ts │ │ ├── button-group │ │ │ └── button-group.component.css │ │ ├── editors │ │ │ ├── editors.component.ts │ │ │ └── editors.component.css │ │ ├── overlays │ │ │ ├── overlays.component.css │ │ │ └── overlays.component.ts │ │ ├── wizard │ │ │ ├── wizard.component.css │ │ │ └── wizard.component.html │ │ ├── preview │ │ │ └── preview.component.css │ │ └── index.component.ts │ ├── layouts │ │ ├── loading │ │ │ ├── loading.component.css │ │ │ ├── loading.component.html │ │ │ └── loading.component.ts │ │ ├── preview-layout │ │ │ ├── preview-layout.component.css │ │ │ ├── preview-layout.component.html │ │ │ └── preview-layout.component.ts │ │ ├── button │ │ │ ├── button.component.html │ │ │ ├── button.component.ts │ │ │ └── button.component.css │ │ ├── app-layout │ │ │ ├── app-layout.component.html │ │ │ ├── app-layout.component.css │ │ │ └── app-layout.component.ts │ │ ├── footer │ │ │ ├── footer.component.ts │ │ │ └── footer.component.css │ │ ├── header │ │ │ ├── export-popup │ │ │ │ └── help-tooltip │ │ │ │ │ ├── help-tooltip.component.ts │ │ │ │ │ └── help-tooltip.component.html │ │ │ ├── header-button │ │ │ │ ├── header-button.component.ts │ │ │ │ ├── header-button.component.css │ │ │ │ └── header-button.component.html │ │ │ ├── popup │ │ │ │ ├── popup.component.html │ │ │ │ └── popup.component.ts │ │ │ ├── help-button │ │ │ │ ├── help-button.component.css │ │ │ │ ├── help-button.component.ts │ │ │ │ └── help-button.component.html │ │ │ ├── import-popup │ │ │ │ ├── import-popup.component.css │ │ │ │ ├── import-popup.component.ts │ │ │ │ └── import-popup.component.html │ │ │ ├── header.component.css │ │ │ ├── header.component.ts │ │ │ └── header.component.html │ │ └── bootstrap-uploader │ │ │ ├── bootstrap-uploader.component.html │ │ │ ├── bootstrap-uploader.component.css │ │ │ └── bootstrap-uploader.component.ts │ ├── app.component.html │ ├── icons │ │ ├── icon-loading │ │ │ ├── icon-loading.component.css │ │ │ ├── icon-loading.component.ts │ │ │ └── icon-loading.component.html │ │ ├── icon-tm-info │ │ │ ├── icon-tm-info.component.css │ │ │ ├── icon-tm-info.component.ts │ │ │ └── icon-tm-info.component.html │ │ ├── icon-export │ │ │ ├── icon-export.component.css │ │ │ ├── icon-export.component.ts │ │ │ └── icon-export.component.html │ │ ├── icon-themes │ │ │ ├── icon-themes.component.css │ │ │ ├── icon-themes.component.ts │ │ │ └── icon-themes.component.html │ │ ├── icon-bootstrap │ │ │ ├── icon-bootstrap.component.css │ │ │ ├── icon-bootstrap.component.ts │ │ │ └── icon-bootstrap.component.html │ │ ├── icon-fluent │ │ │ ├── icon-fluent.component.css │ │ │ ├── icon-fluent.component.ts │ │ │ └── icon-fluent.component.html │ │ ├── icon-generic │ │ │ ├── icon-generic.component.css │ │ │ ├── icon-generic.component.ts │ │ │ └── icon-generic.component.html │ │ ├── icon-material │ │ │ ├── icon-material.component.css │ │ │ ├── icon-material.component.ts │ │ │ └── icon-material.component.html │ │ ├── icon-theme-circle │ │ │ ├── icon-theme-circle.component.html │ │ │ ├── icon-theme-circle.component.ts │ │ │ └── icon-theme-circle.component.css │ │ ├── icon-metadata │ │ │ ├── icon-metadata.component.css │ │ │ ├── icon-metadata.component.ts │ │ │ └── icon-metadata.component.html │ │ ├── icon-logo │ │ │ ├── icon-logo.component.ts │ │ │ └── icon-logo.component.css │ │ └── button-icon │ │ │ ├── button-icon.component.ts │ │ │ └── button-icon.component.css │ ├── types │ │ ├── notify.ts │ │ ├── exported-item.ts │ │ ├── route-id.ts │ │ ├── theme.ts │ │ ├── meta-item.ts │ │ ├── builder-result.ts │ │ ├── metadata.ts │ │ ├── left-menu-item.ts │ │ └── builder-config.ts │ ├── shared │ │ └── badge │ │ │ ├── badge.component.html │ │ │ ├── badge.component.ts │ │ │ └── badge.component.css │ ├── iframe │ │ ├── iframe.component.html │ │ ├── iframe.component.css │ │ └── iframe.component.ts │ ├── app.component.css │ ├── advanced │ │ ├── advanced.component.html │ │ ├── advanced.component.ts │ │ └── advanced.component.css │ ├── import │ │ ├── import-bootstrap │ │ │ ├── import-bootstrap.component.css │ │ │ ├── import-bootstrap.component.ts │ │ │ └── import-bootstrap.component.html │ │ └── import-meta │ │ │ ├── import-meta.component.ts │ │ │ ├── import-meta.component.css │ │ │ └── import-meta.component.html │ ├── promise-helper.ts │ ├── app.component.ts │ ├── notify-error │ │ ├── notify-error.component.html │ │ ├── notify-error.component.css │ │ └── notify-error.component.ts │ ├── left-menu │ │ ├── back-navigator │ │ │ ├── back-navigator.component.html │ │ │ ├── back-navigator.component.ts │ │ │ └── back-navigator.component.css │ │ ├── search-opener │ │ │ ├── search-opener.component.css │ │ │ ├── search-opener.component.ts │ │ │ └── search-opener.component.html │ │ ├── editor │ │ │ ├── editor.component.css │ │ │ ├── editor.component.html │ │ │ └── editor.component.ts │ │ ├── base-parameters │ │ │ ├── base-parameters.component.css │ │ │ ├── base-parameters.component.html │ │ │ └── base-parameters.component.ts │ │ └── main │ │ │ └── left-menu.component.html │ ├── analytics-events.service.ts │ ├── loading.service.ts │ ├── index │ │ ├── index.component.ts │ │ └── index.component.css │ ├── notification.service.ts │ ├── names.service.ts │ ├── theme-builder.service.ts │ ├── master │ │ ├── master.component.html │ │ ├── master.component.ts │ │ └── master.component.css │ ├── app-routing.module.ts │ └── import.service.ts ├── _config.yml ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── images │ ├── person.png │ ├── gallery1.jpg │ ├── gallery2.jpg │ ├── gallery3.jpg │ ├── gallery4.jpg │ ├── gallery5.jpg │ ├── employees │ │ ├── Ed Holmes.jpg │ │ ├── Harv Mudd.jpg │ │ ├── Lucy Ball.jpg │ │ ├── Barb Banks.jpg │ │ ├── Bart Arnaz.jpg │ │ ├── Billy Zimmer.jpg │ │ ├── Brad Farkus.jpg │ │ ├── Brad Jameson.jpg │ │ ├── Brett Wade.jpg │ │ ├── Clark Morgan.jpg │ │ ├── Dallas Lou.jpg │ │ ├── Davey Jones.jpg │ │ ├── Gabe Jones.jpg │ │ ├── Greta Sims.jpg │ │ ├── Jenny Hobbs.jpg │ │ ├── Jim Packard.jpg │ │ ├── Kevin Carter.jpg │ │ ├── Leah Simpson.jpg │ │ ├── Mary Stern.jpg │ │ ├── Nat Maguiree.jpg │ │ ├── Sammy Hill.jpg │ │ ├── Sandy Bright.jpg │ │ ├── Stu Pizaro.jpg │ │ ├── Taylor Riley.jpg │ │ ├── Todd Hoffman.jpg │ │ ├── Wally Hobbs.jpg │ │ ├── Amelia Harper.jpg │ │ ├── Antony Remmen.jpg │ │ ├── Arnie Schwartz.jpg │ │ ├── Arthur Miller.jpg │ │ ├── Cindy Stanwick.jpg │ │ ├── Hannah Brookly.jpg │ │ ├── Jackie Garmin.jpg │ │ ├── James Anderson.jpg │ │ ├── Karen Goodson.jpg │ │ ├── Ken Samuelson.jpg │ │ ├── Maggie Boxter.jpg │ │ ├── Marcus Orbison.jpg │ │ ├── Morgan Kennedy.jpg │ │ ├── Olivia Peyton.jpg │ │ ├── Robert Reagan.jpg │ │ ├── Robin Cosworth.jpg │ │ ├── Samantha Piper.jpg │ │ ├── Sandra Johnson.jpg │ │ ├── Terry Bradley.jpg │ │ ├── Victor Norris.jpg │ │ ├── Violet Bailey.jpg │ │ ├── Kelly Rodriguez.jpg │ │ └── Lincoln Bartlett.jpg │ ├── arrow-left.svg │ └── loading.svg ├── content │ └── fonts │ │ ├── OpenSansCondensedBold.eot │ │ ├── OpenSansCondensedBold.ttf │ │ └── OpenSansCondensedBold.woff ├── tsconfig.app.json ├── tsconfig.spec.json ├── tslint.json ├── browserslist ├── main.ts ├── styles.css └── index.html ├── SECURITY.md ├── webpack.config.js ├── .vscode └── settings.json ├── copyfile.js ├── ngcc.config.js ├── .editorconfig ├── .github ├── workflows │ ├── ci.yml │ ├── deploy.yml │ └── codeql-analysis.yml └── stale.yml ├── .gitignore ├── tsconfig.json ├── README.md └── package.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/form/form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/list/list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/menu/menu.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/tabs/tabs.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/cardview/cardview.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/sliders/sliders.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/treelist/treelist.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/treeview/treeview.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layouts/loading/loading.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/datatgrid/datatgrid.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/pivotgrid/pivotgrid.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/progressbar/progressbar.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/scheduler/scheduler.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/filterbuilder/filterbuilder.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/layouts/preview-layout/preview-layout.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_config.yml: -------------------------------------------------------------------------------- 1 | include: ["_functions.scss", "_variables.scss"] 2 | -------------------------------------------------------------------------------- /src/app/preview/menu/menu.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/form/form.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/list/list.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/icons/icon-loading/icon-loading.component.css: -------------------------------------------------------------------------------- 1 | svg { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/icons/icon-tm-info/icon-tm-info.component.css: -------------------------------------------------------------------------------- 1 | svg { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/preview/cardview/cardview.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/layouts/preview-layout/preview-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/tabs/tabs.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/fieldset/fieldset.component.css: -------------------------------------------------------------------------------- 1 | :host .dx-fieldset { 2 | margin-left: 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/preview/scrollview/scrollview.component.css: -------------------------------------------------------------------------------- 1 | #content { 2 | padding-right: 10px; 3 | } 4 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/images/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/person.png -------------------------------------------------------------------------------- /src/app/preview/toolbar/toolbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/types/notify.ts: -------------------------------------------------------------------------------- 1 | export class Notification { 2 | message: string; 3 | type: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/images/gallery1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/gallery1.jpg -------------------------------------------------------------------------------- /src/images/gallery2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/gallery2.jpg -------------------------------------------------------------------------------- /src/images/gallery3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/gallery3.jpg -------------------------------------------------------------------------------- /src/images/gallery4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/gallery4.jpg -------------------------------------------------------------------------------- /src/images/gallery5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/gallery5.jpg -------------------------------------------------------------------------------- /src/app/preview/pivotgrid/pivotgrid.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/scheduler/scheduler.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/shared/badge/badge.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/iframe/iframe.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/types/exported-item.ts: -------------------------------------------------------------------------------- 1 | export class ExportedItem { 2 | key: string; 3 | value: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/preview/accordion/accordion.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/datatgrid/datatgrid.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/treeview/treeview.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/images/employees/Ed Holmes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Ed Holmes.jpg -------------------------------------------------------------------------------- /src/images/employees/Harv Mudd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Harv Mudd.jpg -------------------------------------------------------------------------------- /src/images/employees/Lucy Ball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Lucy Ball.jpg -------------------------------------------------------------------------------- /src/app/icons/icon-export/icon-export.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill:#f05b41; 3 | } 4 | 5 | .st1 { 6 | fill:#c5c5c5; 7 | } -------------------------------------------------------------------------------- /src/app/icons/icon-themes/icon-themes.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } -------------------------------------------------------------------------------- /src/app/preview/progressbar/progressbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/preview/toolbar/toolbar.component.css: -------------------------------------------------------------------------------- 1 | ::ng-deep .dx-theme-generic-typography .toolbar { 2 | margin: 20px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/images/employees/Barb Banks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Barb Banks.jpg -------------------------------------------------------------------------------- /src/images/employees/Bart Arnaz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Bart Arnaz.jpg -------------------------------------------------------------------------------- /src/images/employees/Billy Zimmer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Billy Zimmer.jpg -------------------------------------------------------------------------------- /src/images/employees/Brad Farkus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Brad Farkus.jpg -------------------------------------------------------------------------------- /src/images/employees/Brad Jameson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Brad Jameson.jpg -------------------------------------------------------------------------------- /src/images/employees/Brett Wade.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Brett Wade.jpg -------------------------------------------------------------------------------- /src/images/employees/Clark Morgan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Clark Morgan.jpg -------------------------------------------------------------------------------- /src/images/employees/Dallas Lou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Dallas Lou.jpg -------------------------------------------------------------------------------- /src/images/employees/Davey Jones.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Davey Jones.jpg -------------------------------------------------------------------------------- /src/images/employees/Gabe Jones.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Gabe Jones.jpg -------------------------------------------------------------------------------- /src/images/employees/Greta Sims.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Greta Sims.jpg -------------------------------------------------------------------------------- /src/images/employees/Jenny Hobbs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Jenny Hobbs.jpg -------------------------------------------------------------------------------- /src/images/employees/Jim Packard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Jim Packard.jpg -------------------------------------------------------------------------------- /src/images/employees/Kevin Carter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Kevin Carter.jpg -------------------------------------------------------------------------------- /src/images/employees/Leah Simpson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Leah Simpson.jpg -------------------------------------------------------------------------------- /src/images/employees/Mary Stern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Mary Stern.jpg -------------------------------------------------------------------------------- /src/images/employees/Nat Maguiree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Nat Maguiree.jpg -------------------------------------------------------------------------------- /src/images/employees/Sammy Hill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Sammy Hill.jpg -------------------------------------------------------------------------------- /src/images/employees/Sandy Bright.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Sandy Bright.jpg -------------------------------------------------------------------------------- /src/images/employees/Stu Pizaro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Stu Pizaro.jpg -------------------------------------------------------------------------------- /src/images/employees/Taylor Riley.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Taylor Riley.jpg -------------------------------------------------------------------------------- /src/images/employees/Todd Hoffman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Todd Hoffman.jpg -------------------------------------------------------------------------------- /src/images/employees/Wally Hobbs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Wally Hobbs.jpg -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please refer to [DevExpress Security Policy](https://github.com/DevExpress/Shared/security/policy) 4 | -------------------------------------------------------------------------------- /src/app/icons/icon-bootstrap/icon-bootstrap.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } -------------------------------------------------------------------------------- /src/app/icons/icon-fluent/icon-fluent.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/icons/icon-generic/icon-generic.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/icons/icon-material/icon-material.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } -------------------------------------------------------------------------------- /src/app/icons/icon-theme-circle/icon-theme-circle.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/images/employees/Amelia Harper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Amelia Harper.jpg -------------------------------------------------------------------------------- /src/images/employees/Antony Remmen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Antony Remmen.jpg -------------------------------------------------------------------------------- /src/images/employees/Arnie Schwartz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Arnie Schwartz.jpg -------------------------------------------------------------------------------- /src/images/employees/Arthur Miller.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Arthur Miller.jpg -------------------------------------------------------------------------------- /src/images/employees/Cindy Stanwick.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Cindy Stanwick.jpg -------------------------------------------------------------------------------- /src/images/employees/Hannah Brookly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Hannah Brookly.jpg -------------------------------------------------------------------------------- /src/images/employees/Jackie Garmin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Jackie Garmin.jpg -------------------------------------------------------------------------------- /src/images/employees/James Anderson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/James Anderson.jpg -------------------------------------------------------------------------------- /src/images/employees/Karen Goodson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Karen Goodson.jpg -------------------------------------------------------------------------------- /src/images/employees/Ken Samuelson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Ken Samuelson.jpg -------------------------------------------------------------------------------- /src/images/employees/Maggie Boxter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Maggie Boxter.jpg -------------------------------------------------------------------------------- /src/images/employees/Marcus Orbison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Marcus Orbison.jpg -------------------------------------------------------------------------------- /src/images/employees/Morgan Kennedy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Morgan Kennedy.jpg -------------------------------------------------------------------------------- /src/images/employees/Olivia Peyton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Olivia Peyton.jpg -------------------------------------------------------------------------------- /src/images/employees/Robert Reagan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Robert Reagan.jpg -------------------------------------------------------------------------------- /src/images/employees/Robin Cosworth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Robin Cosworth.jpg -------------------------------------------------------------------------------- /src/images/employees/Samantha Piper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Samantha Piper.jpg -------------------------------------------------------------------------------- /src/images/employees/Sandra Johnson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Sandra Johnson.jpg -------------------------------------------------------------------------------- /src/images/employees/Terry Bradley.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Terry Bradley.jpg -------------------------------------------------------------------------------- /src/images/employees/Victor Norris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Victor Norris.jpg -------------------------------------------------------------------------------- /src/images/employees/Violet Bailey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Violet Bailey.jpg -------------------------------------------------------------------------------- /src/app/icons/icon-metadata/icon-metadata.component.css: -------------------------------------------------------------------------------- 1 | .st0 { 2 | fill: #2190F7; 3 | } 4 | 5 | .st1 { 6 | fill: #ffffff; 7 | } 8 | -------------------------------------------------------------------------------- /src/images/employees/Kelly Rodriguez.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Kelly Rodriguez.jpg -------------------------------------------------------------------------------- /src/images/employees/Lincoln Bartlett.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/images/employees/Lincoln Bartlett.jpg -------------------------------------------------------------------------------- /src/content/fonts/OpenSansCondensedBold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/content/fonts/OpenSansCondensedBold.eot -------------------------------------------------------------------------------- /src/content/fonts/OpenSansCondensedBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/content/fonts/OpenSansCondensedBold.ttf -------------------------------------------------------------------------------- /src/content/fonts/OpenSansCondensedBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/ThemeBuilder/HEAD/src/content/fonts/OpenSansCondensedBold.woff -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | height: 100%; 4 | /* min-width: 1000px; */ 5 | /* overflow: hidden; */ 6 | } 7 | -------------------------------------------------------------------------------- /src/app/layouts/loading/loading.component.html: -------------------------------------------------------------------------------- 1 |
2 | loading... 3 |
4 | -------------------------------------------------------------------------------- /src/app/preview/treelist/treelist.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/types/route-id.ts: -------------------------------------------------------------------------------- 1 | export enum RouteId { 2 | MAIN_INDEX = 1, 3 | NESTED_INDEX = 2, 4 | IMPORT_OR_MASTER = 3, 5 | GROUPED_WIDGET = 4 6 | } 7 | -------------------------------------------------------------------------------- /src/app/preview/chat/chat.component.css: -------------------------------------------------------------------------------- 1 | .chat-container { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | 6 | .full-chat { 7 | margin-right: 40px; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/preview/gallery/gallery.component.css: -------------------------------------------------------------------------------- 1 | .image { 2 | height: 100%; 3 | background-size: cover; 4 | background-position: center; 5 | background-repeat: no-repeat; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/advanced/advanced.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | fallback: { 4 | path: require.resolve('path-browserify'), 5 | fs: false, 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | { 5 | "language": "typescript", 6 | "autoFix": true 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /copyfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { copyFileSync } = require('fs'); // eslint-disable-line 4 | copyFileSync('dist/devextreme-themebuilder-app/index.html', 'dist/devextreme-themebuilder-app/404.html'); 5 | -------------------------------------------------------------------------------- /src/app/preview/sliders/sliders.component.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/iframe/iframe.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | border: 0; 5 | } 6 | 7 | :host { 8 | display: block; 9 | height: 100%; 10 | overflow: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/preview/filterbuilder/filterbuilder.component.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/app/layouts/button/button.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /ngcc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | packages: { 3 | 'devextreme-angular': { 4 | ignorableDeepImportMatchers: [ 5 | /devextreme\// 6 | ] 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/preview/preview-title/preview-title.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 |

5 | New 6 |
7 | -------------------------------------------------------------------------------- /src/app/types/theme.ts: -------------------------------------------------------------------------------- 1 | export class Theme { 2 | name: string; 3 | colorScheme: string; 4 | } 5 | 6 | export class ThemeConfig extends Theme { 7 | themeId: number; 8 | text: string; 9 | group: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/import/import-bootstrap/import-bootstrap.component.css: -------------------------------------------------------------------------------- 1 | .file-uploader { 2 | margin: 20px auto 0; 3 | } 4 | 5 | .text { 6 | margin: 45px auto; 7 | } 8 | 9 | .apply { 10 | width: 270px; 11 | } 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/preview/drawer/drawer.component.css: -------------------------------------------------------------------------------- 1 | .drawer-panel { 2 | width: 200px; 3 | height: 200px; 4 | padding: 5px 10px; 5 | } 6 | 7 | .drawer-panel p { 8 | margin: 0; 9 | } 10 | 11 | .drawer-content { 12 | padding: 15px; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/promise-helper.ts: -------------------------------------------------------------------------------- 1 | export const mutePromise = (promise: Promise): void => { 2 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 3 | if(promise && promise.catch) { 4 | promise.catch(null); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app/types/meta-item.ts: -------------------------------------------------------------------------------- 1 | export class MetaItem { 2 | Name: string; 3 | Key: string; 4 | Value: string; 5 | Type?: string; 6 | TypeValues?: string; 7 | TypeValuesArray?: string[]; 8 | Items?: MetaItem[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "src/test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } -------------------------------------------------------------------------------- /src/app/types/builder-result.ts: -------------------------------------------------------------------------------- 1 | export class BuilderResult { 2 | css: string; 3 | compiledMetadata: { [key: string]: string }; 4 | swatchSelector: string; 5 | unusedWidgets: string[]; 6 | widgets: string[]; 7 | version: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/images/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/preview/preview-title/preview-title.component.css: -------------------------------------------------------------------------------- 1 | .preview-title-wrapper{ 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 10px; 5 | } 6 | 7 | .preview-title { 8 | line-height: 100%; 9 | font-size: 24px; 10 | font-weight: 500; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'], 7 | standalone: false 8 | }) 9 | 10 | export class AppComponent { } 11 | -------------------------------------------------------------------------------- /src/app/notify-error/notify-error.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{notifyMessage}}

4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/shared/badge/badge.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-badge', 5 | templateUrl: './badge.component.html', 6 | styleUrls: ['./badge.component.css'], 7 | standalone: false 8 | }) 9 | export class BadgeComponent{} 10 | -------------------------------------------------------------------------------- /src/app/types/metadata.ts: -------------------------------------------------------------------------------- 1 | import { ThemeConfig } from './theme'; 2 | import { MetaItem } from './meta-item'; 3 | 4 | export class Metadata { 5 | baseParameters: string[]; 6 | generic: MetaItem[]; 7 | material: MetaItem[]; 8 | themes: ThemeConfig[]; 9 | version: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/advanced/advanced.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-advanced', 5 | templateUrl: './advanced.component.html', 6 | styleUrls: ['./advanced.component.css'], 7 | standalone: false 8 | }) 9 | export class AdvancedComponent {} 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/app/types/left-menu-item.ts: -------------------------------------------------------------------------------- 1 | import { MetaItem } from './meta-item'; 2 | 3 | export class LeftMenuItem { 4 | name: string; 5 | regs?: RegExp[]; 6 | equivalents?: string; 7 | groups?: LeftMenuItem[]; 8 | items?: MetaItem[]; 9 | route?: string; 10 | isNew?: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/icons/icon-logo/icon-logo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-logo', 5 | templateUrl: './icon-logo.component.html', 6 | styleUrls: ['./icon-logo.component.css'], 7 | standalone: false 8 | }) 9 | export class IconLogoComponent { } 10 | -------------------------------------------------------------------------------- /src/app/preview/index.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/preview/gallery/gallery.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/left-menu/back-navigator/back-navigator.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{text}} 5 | -------------------------------------------------------------------------------- /src/app/left-menu/search-opener/search-opener.component.css: -------------------------------------------------------------------------------- 1 | .search { 2 | height: 100%; 3 | width: 100%; 4 | display: flex; 5 | justify-content: center; 6 | } 7 | 8 | .search:hover .icon { 9 | fill: #158BFF; 10 | } 11 | 12 | .icon { 13 | align-self: center; 14 | fill: #505050; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/preview/button-detailed/button-detailed.component.css: -------------------------------------------------------------------------------- 1 | button { 2 | background-color: #158BFF;; 3 | width: 20px; 4 | height: 20px; 5 | position: absolute; 6 | right: -2px; 7 | top: -2px; 8 | cursor: pointer; 9 | padding: 2px; 10 | border: none; 11 | border-radius: 0 4px 0 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/icons/icon-export/icon-export.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-export', 5 | templateUrl: './icon-export.component.html', 6 | styleUrls: ['./icon-export.component.css'], 7 | standalone: false 8 | }) 9 | export class IconExportComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-fluent/icon-fluent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-fluent', 5 | templateUrl: './icon-fluent.component.html', 6 | styleUrls: ['./icon-fluent.component.css'], 7 | standalone: false 8 | }) 9 | export class IconFluentComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-themes/icon-themes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-themes', 5 | templateUrl: './icon-themes.component.html', 6 | styleUrls: ['./icon-themes.component.css'], 7 | standalone: false 8 | }) 9 | export class IconThemesComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-generic/icon-generic.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-generic', 5 | templateUrl: './icon-generic.component.html', 6 | styleUrls: ['./icon-generic.component.css'], 7 | standalone: false 8 | }) 9 | export class IconGenericComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-loading/icon-loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-loading', 5 | templateUrl: './icon-loading.component.html', 6 | styleUrls: ['./icon-loading.component.css'], 7 | standalone: false 8 | }) 9 | export class IconLoadingComponent {} 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-material/icon-material.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-material', 5 | templateUrl: './icon-material.component.html', 6 | styleUrls: ['./icon-material.component.css'], 7 | standalone: false 8 | }) 9 | export class IconMaterialComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-metadata/icon-metadata.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-metadata', 5 | templateUrl: './icon-metadata.component.html', 6 | styleUrls: ['./icon-metadata.component.css'], 7 | standalone: false 8 | }) 9 | export class IconMetadataComponent { } 10 | -------------------------------------------------------------------------------- /src/app/icons/icon-bootstrap/icon-bootstrap.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-bootstrap', 5 | templateUrl: './icon-bootstrap.component.html', 6 | styleUrls: ['./icon-bootstrap.component.css'], 7 | standalone: false 8 | }) 9 | export class IconBootstrapComponent { } 10 | -------------------------------------------------------------------------------- /src/app/layouts/app-layout/app-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/layouts/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.css'], 7 | standalone: false 8 | }) 9 | export class FooterComponent { 10 | year = new Date().getFullYear(); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/layouts/preview-layout/preview-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-preview-layout', 5 | templateUrl: './preview-layout.component.html', 6 | styleUrls: ['./preview-layout.component.css'], 7 | standalone: false 8 | }) 9 | export class PreviewLayoutComponent {} 10 | -------------------------------------------------------------------------------- /src/app/advanced/advanced.component.css: -------------------------------------------------------------------------------- 1 | .advanced { 2 | position: absolute; 3 | top: 70px; 4 | bottom: 0; 5 | right: 0; 6 | left: 0; 7 | display: flex; 8 | } 9 | 10 | .advanced .preview { 11 | width: 100%; 12 | flex-basis: 759px; 13 | flex-shrink: 0; 14 | flex-grow: 1; 15 | position: relative; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "node" 7 | ] 8 | }, 9 | "files": [ 10 | "test.ts", 11 | "polyfills.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /src/app/icons/button-icon/button-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-button-icon', 5 | templateUrl: './button-icon.component.html', 6 | styleUrls: ['./button-icon.component.css'], 7 | standalone: false 8 | }) 9 | export class ButtonIconComponent { 10 | @Input('name') name: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/icons/icon-tm-info/icon-tm-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-tm-info', 5 | templateUrl: './icon-tm-info.component.html', 6 | styleUrls: ['./icon-tm-info.component.css'], 7 | standalone: false 8 | }) 9 | export class IconTmInfoComponent { 10 | @Input('name') name: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/preview/preview-title/preview-title.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-preview-title', 5 | templateUrl: './preview-title.component.html', 6 | styleUrls: ['./preview-title.component.css'], 7 | standalone: false 8 | }) 9 | export class PreviewTitleComponent{ 10 | @Input() isNew = false; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/left-menu/search-opener/search-opener.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-search-opener', 5 | templateUrl: './search-opener.component.html', 6 | styleUrls: ['./search-opener.component.css'], 7 | standalone: false 8 | }) 9 | export class SearchOpenerComponent { 10 | @Input() opened: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/left-menu/back-navigator/back-navigator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-back-navigator', 5 | templateUrl: './back-navigator.component.html', 6 | styleUrls: ['./back-navigator.component.css'], 7 | standalone: false 8 | }) 9 | export class BackNavigatorComponent { 10 | @Input() text: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/left-menu/editor/editor.component.css: -------------------------------------------------------------------------------- 1 | .property-header { 2 | padding: 20px 20px 3px 0; 3 | } 4 | 5 | .property-widget { 6 | padding-right: 20px; 7 | } 8 | 9 | .property-delimiter { 10 | margin-top: 20px; 11 | height: 1px; 12 | background-color: #e3e7ea; 13 | } 14 | 15 | :host ::ng-deep .dx-dropdowneditor-icon { 16 | color: #9c9c9c; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/app/icons/icon-generic/icon-generic.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/layouts/header/export-popup/help-tooltip/help-tooltip.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-help-tooltip', 5 | templateUrl: './help-tooltip.component.html', 6 | styles: [`span { 7 | display: flex; 8 | cursor: pointer; 9 | }`], 10 | standalone: false 11 | }) 12 | export class HelpTooltipComponent {} 13 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /src/app/preview/accordion/accordion.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .dx-accordion-item-body { 2 | padding: 20px; 3 | } 4 | 5 | :host ::ng-deep .dx-accordion-item-body p { 6 | margin: 0; 7 | } 8 | 9 | :host ::ng-deep .dx-accordion-item-title .dx-icon { 10 | font-size: 18px; 11 | } 12 | 13 | :host ::ng-deep .dx-accordion-item-title .dx-accordion-item-title-caption { 14 | font-size: 18px; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/layouts/bootstrap-uploader/bootstrap-uploader.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/layouts/header/header-button/header-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header-button', 5 | templateUrl: './header-button.component.html', 6 | styleUrls: ['./header-button.component.css'], 7 | standalone: false 8 | }) 9 | export class HeaderButtonComponent { 10 | @Input() text: string; 11 | @Input() icon: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if(environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch((err) => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/app/preview/fieldset/fieldset.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-fieldset', 6 | templateUrl: './fieldset.component.html', 7 | styleUrls: ['./fieldset.component.css'], 8 | standalone: false 9 | }) 10 | export class FieldSetComponent { 11 | widgetGroup = 'fieldset'; 12 | isExpanded = new Subject(); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/shared/badge/badge.component.css: -------------------------------------------------------------------------------- 1 | 2 | .badge { 3 | width: 28px; 4 | height: 16px; 5 | line-height: 16px; 6 | font-weight: 500; 7 | font-size: 14px; 8 | border-radius: 50px; 9 | text-align: center; 10 | background-color: #DD1144; 11 | color: white; 12 | border: none; 13 | padding: 2px 6px; 14 | font-family: Roboto, Segoe UI, Tahoma, Geneva, Verdana, sans-serif; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/app/preview/scrollview/scrollview.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-scrollview', 6 | templateUrl: './scrollview.component.html', 7 | styleUrls: ['./scrollview.component.css'], 8 | standalone: false 9 | }) 10 | export class ScrollviewComponent { 11 | widgetGroup = 'scrollview'; 12 | isExpanded = new Subject(); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/preview/sliders/sliders.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-sliders', 6 | templateUrl: './sliders.component.html', 7 | styleUrls: ['./sliders.component.css'], 8 | standalone: false 9 | }) 10 | export class SlidersComponent { 11 | widgetGroup = 'sliders'; 12 | isExpanded = new BehaviorSubject(false); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/preview/stepper/stepper.component.css: -------------------------------------------------------------------------------- 1 | .stepper-wrapper{ 2 | display: flex; 3 | height: 100%; 4 | justify-content: center; 5 | align-items: center; 6 | } 7 | 8 | .stepper-wrapper-extended{ 9 | height: 320px; 10 | } 11 | 12 | #stepperOptionsBlock { 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | .vertical-separator{ 18 | width: 1px; 19 | height: 32px; 20 | background-color: #DADADA; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/left-menu/back-navigator/back-navigator.component.css: -------------------------------------------------------------------------------- 1 | :host:hover { 2 | color: #2190F6; 3 | } 4 | 5 | .back { 6 | padding-right: 8px; 7 | } 8 | 9 | :host:hover .back path { 10 | fill: #2190F6; 11 | } 12 | 13 | .back path { 14 | position: absolute; 15 | top: 50%; 16 | margin-top: -9px; 17 | left: -2px; 18 | fill: #fff; 19 | } 20 | 21 | :host { 22 | display: flex; 23 | align-items: center; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/preview/progressbar/progressbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-progressbars', 6 | templateUrl: './progressbar.component.html', 7 | styleUrls: ['./progressbar.component.css'], 8 | standalone: false 9 | }) 10 | export class ProgressbarComponent { 11 | widgetGroup = 'progressbars'; 12 | isExpanded = new BehaviorSubject(false); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/icons/icon-logo/icon-logo.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | height: min-content; 3 | display: flex; 4 | gap: 12px; 5 | align-items: center; 6 | } 7 | 8 | :host.blue-colored svg path#top-square { 9 | fill: rgb(33, 144, 247); 10 | } 11 | 12 | :host.blue-colored svg path#bottom-square { 13 | fill: #667788; 14 | } 15 | 16 | :host.blue-colored svg path#first-letter, 17 | :host.blue-colored svg path#second-letter { 18 | fill: white; 19 | } 20 | 21 | :host.white-colored svg#devextreme-logo path { 22 | fill: white; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/analytics-events.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { environment } from '../environments/environment'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | 8 | export class AnalyticsEventsService { 9 | trackEvent(...items): void { 10 | if(!environment.production) return; 11 | 12 | const _paq = (window as any)._paq || []; 13 | 14 | if(_paq.push) { 15 | const eventArgs = ['trackEvent', ...items]; 16 | _paq.push(eventArgs); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/import/import-bootstrap/import-bootstrap.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: './import-bootstrap.component.html', 5 | styleUrls: ['./import-bootstrap.component.css'], 6 | standalone: false 7 | }) 8 | export class ImportBootstrapComponent { 9 | bootstrapVariantsData = [{ 10 | text: 'Bootstrap 5', 11 | value: 0, 12 | version: 5, 13 | acceptFormat: '.scss', 14 | uploadButtonText: 'Upload SCSS Variables' 15 | }]; 16 | 17 | selectedIndex = 0; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/layouts/header/header-button/header-button.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | align-self: center; 3 | } 4 | 5 | .header-button { 6 | height: 60px; 7 | display: flex; 8 | cursor: pointer; 9 | } 10 | 11 | .header-button > div { 12 | align-self: center; 13 | user-select: none; 14 | } 15 | 16 | .header-button .icon { 17 | height: 22px; 18 | padding-right: 10px; 19 | } 20 | 21 | .header-button:hover .icon path { 22 | fill: #158BFF; 23 | } 24 | 25 | .header-button:hover .button-text { 26 | color: #158BFF; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/preview/index.component.css: -------------------------------------------------------------------------------- 1 | #preview-container { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | } 8 | 9 | 10 | ::ng-deep .options-block { 11 | display: flex; 12 | gap: 24px; 13 | margin-bottom: 20px; 14 | } 15 | 16 | ::ng-deep .options-block .option { 17 | display: flex; 18 | gap: 24px; 19 | align-items: center; 20 | } 21 | 22 | ::ng-deep .options-block .option span { 23 | font-size: 13px; 24 | font-style: normal; 25 | font-weight: 400; 26 | line-height: normal; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/loading.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | @Injectable() 5 | 6 | export class LoadingService { 7 | busy = new BehaviorSubject(false); 8 | stack = []; 9 | 10 | toggle(show: boolean): void { 11 | if(show) this.stack.push(true); 12 | else this.stack.pop(); 13 | this.busy.next(!!this.stack.length); 14 | } 15 | 16 | show(): void { 17 | this.toggle(true); 18 | } 19 | 20 | hide(): void { 21 | this.toggle(false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | @Component({ 5 | templateUrl: './index.component.html', 6 | styleUrls: ['./index.component.css'], 7 | standalone: false 8 | }) 9 | 10 | export class IndexComponent { 11 | url: string; 12 | 13 | constructor(private route: ActivatedRoute) { 14 | this.route.url.subscribe((u) => { 15 | const urlSegment = u.shift(); 16 | this.url = urlSegment ? urlSegment.path : ''; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/preview/fieldset/fieldset.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Personal Data
3 |
4 |
First Name
5 |
6 | 7 |
8 |
9 |
10 |
Last Name
11 |
12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/layouts/header/popup/popup.component.html: -------------------------------------------------------------------------------- 1 | 12 |
{{title}}
13 |
14 |
15 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /src/app/preview/splitter/splitter.component.css: -------------------------------------------------------------------------------- 1 | 2 | ::ng-deep #splitter { 3 | border: 1px solid var(--dx-color-border); 4 | overflow: hidden; 5 | } 6 | 7 | ::ng-deep .pane-content { 8 | padding: 12px; 9 | } 10 | 11 | ::ng-deep .pane-title { 12 | font-weight: 600; 13 | margin-bottom: 2px; 14 | } 15 | 16 | ::ng-deep .pane-state { 17 | font-size: 12px; 18 | font-weight: 400; 19 | color: rgba(51, 51, 51, 0.7); 20 | margin-bottom: 4px; 21 | } 22 | 23 | ::ng-deep .pane-option { 24 | color: rgba(51, 51, 51, 1); 25 | font-size: 14px; 26 | font-weight: 600; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/preview/drawer/drawer.component.html: -------------------------------------------------------------------------------- 1 | 2 | 11 |
12 |
13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/app/icons/icon-theme-circle/icon-theme-circle.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-theme-circle', 5 | templateUrl: './icon-theme-circle.component.html', 6 | styleUrls: ['./icon-theme-circle.component.css'], 7 | standalone: false 8 | }) 9 | export class IconThemeCircleComponent implements OnInit { 10 | @Input() theme: string; 11 | @Input() small: boolean; 12 | 13 | smallClass: string; 14 | 15 | ngOnInit(): void { 16 | this.smallClass = this.small ? 'small' : ''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/types/builder-config.ts: -------------------------------------------------------------------------------- 1 | import { ExportedItem } from './exported-item'; 2 | 3 | export class BuilderConfig { 4 | baseTheme?: string; 5 | makeSwatch?: boolean; 6 | widgets?: Array; 7 | outputColorScheme?: string; 8 | outColorScheme?: string; 9 | items?: Array; 10 | noClean?: boolean; 11 | data?: string; 12 | inputFile?: string; 13 | outputFile?: string; 14 | assetsBasePath?: string; 15 | themeName?: string; 16 | colorScheme?: string; 17 | removeExternalResources?: boolean; 18 | bootstrapVersion?: number; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/layouts/loading/loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { LoadingService } from 'src/app/loading.service'; 3 | 4 | @Component({ 5 | selector: 'app-loading', 6 | templateUrl: './loading.component.html', 7 | styleUrls: ['./loading.component.css'], 8 | standalone: false 9 | }) 10 | 11 | export class LoadingComponent implements OnInit { 12 | loading = false; 13 | 14 | constructor(private loadingService: LoadingService) { } 15 | 16 | ngOnInit(): void { 17 | this.loadingService.busy.subscribe((busy) => this.loading = busy); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/icons/icon-export/icon-export.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/preview/tabs/tabs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-tabs', 6 | templateUrl: './tabs.component.html', 7 | styleUrls: ['./tabs.component.css'], 8 | standalone: false 9 | }) 10 | export class TabsComponent { 11 | widgetGroup = 'tabs'; 12 | isExpanded = new Subject(); 13 | 14 | dataSource: any[] = [ 15 | { text: 'user', icon: 'user' }, 16 | { text: 'comment', icon: 'comment' }, 17 | { text: 'find', icon: 'find', badge: 7 }, 18 | { text: 'disabled', disabled: true } 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/preview/gallery/gallery.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-gallery', 6 | templateUrl: './gallery.component.html', 7 | styleUrls: ['./gallery.component.css'], 8 | standalone: false 9 | }) 10 | export class GalleryComponent { 11 | widgetGroup = 'gallery'; 12 | isExpanded = new Subject(); 13 | 14 | galleryItems: string[] = [ 15 | 'images/gallery1.jpg', 16 | 'images/gallery2.jpg', 17 | 'images/gallery3.jpg', 18 | 'images/gallery4.jpg', 19 | 'images/gallery5.jpg' 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/import/import-meta/import-meta.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { mutePromise } from 'src/app/promise-helper'; 3 | import { ImportService } from '../../import.service'; 4 | 5 | @Component({ 6 | selector: 'app-import-meta', 7 | templateUrl: './import-meta.component.html', 8 | styleUrls: ['./import-meta.component.css'], 9 | standalone: false 10 | }) 11 | export class ImportMetaComponent { 12 | constructor(private importService: ImportService) {} 13 | 14 | importValue = ''; 15 | 16 | applyClick(t): void { 17 | mutePromise(this.importService.importMetadata(t.value, 'advanced')); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/icons/button-icon/button-icon.component.css: -------------------------------------------------------------------------------- 1 | svg { 2 | display: block; 3 | } 4 | 5 | svg path { 6 | fill: #707070; 7 | } 8 | 9 | svg rect { 10 | stroke: #707070; 11 | fill: transparent; 12 | } 13 | /* 14 | :host-context(.button:hover) svg path, 15 | :host-context(.button.orange) svg path { 16 | fill: #fff; 17 | } */ 18 | 19 | /* :host-context(.button:hover) svg rect { 20 | stroke: #fff; 21 | } */ 22 | 23 | /* :host-context(.button:active) svg path, 24 | :host-context(.button:focus) svg path { 25 | fill: #fff; 26 | } 27 | 28 | :host-context(.button:active) svg rect, 29 | :host-context(.button:focus) svg rect { 30 | stroke: #fff; 31 | } */ 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 20 9 | 10 | steps: 11 | - name: Get sources 12 | uses: actions/checkout@v4 13 | 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: '20' 18 | 19 | - name: Restore npm cache 20 | uses: actions/cache@v4 21 | with: 22 | path: ./node_modules 23 | key: ${{ runner.os }}-node-modules 24 | 25 | - name: Run npm ci 26 | run: npm ci --no-audit --no-fund 27 | 28 | - name: Build 29 | run: npm run test 30 | 31 | -------------------------------------------------------------------------------- /src/app/import/import-meta/import-meta.component.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | 7 | .text { 8 | margin: 15px auto 10px; 9 | } 10 | 11 | .container .text.upload-desc { 12 | margin: 48px 0; 13 | align-self: center; 14 | } 15 | 16 | .text.first { 17 | margin-bottom: 0; 18 | } 19 | 20 | dx-text-area { 21 | display: block; 22 | margin: 0 auto 20px; 23 | } 24 | 25 | .content .text { 26 | font-size: 16px; 27 | margin-bottom: 32px; 28 | } 29 | 30 | ::ng-deep .import-meta .upload-meta .button { 31 | box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.16); 32 | } 33 | -------------------------------------------------------------------------------- /.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | .angular 41 | -------------------------------------------------------------------------------- /src/app/layouts/button/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | const DEFAULT_BUTTON_WIDTH = 200; 4 | const DEFAULT_BUTTON_HEIGHT = 44; 5 | 6 | @Component({ 7 | selector: 'app-button', 8 | templateUrl: './button.component.html', 9 | styleUrls: ['./button.component.css'], 10 | standalone: false 11 | }) 12 | export class ButtonComponent { 13 | @Input() type: string; 14 | @Input() disabled = false; 15 | @Input() width = DEFAULT_BUTTON_WIDTH; 16 | @Input() height = DEFAULT_BUTTON_HEIGHT; 17 | @Output() clicked = new EventEmitter(); 18 | 19 | onClick(): void { 20 | this.clicked.emit(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/left-menu/search-opener/search-opener.component.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/app/preview/buttons/buttons.component.css: -------------------------------------------------------------------------------- 1 | span { 2 | font-size: 14px; 3 | font-weight: 500; 4 | line-height: 24px; 5 | opacity: 0.86; 6 | } 7 | 8 | .btn-container { 9 | width: 100%; 10 | display: flex; 11 | flex-direction: column; 12 | gap: 16px; 13 | } 14 | 15 | .btn-row { 16 | display: flex; 17 | gap: 40px; 18 | } 19 | 20 | .btn-cell:first-child { 21 | width: 10%; 22 | justify-content: flex-start; 23 | } 24 | 25 | .btn-cell { 26 | width: 30%; 27 | display: flex; 28 | justify-content: center; 29 | } 30 | 31 | .btn-list { 32 | display: flex; 33 | gap: 16px; 34 | } 35 | 36 | .btn-container-click-disabled { 37 | pointer-events: none; 38 | } 39 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /src/app/icons/icon-material/icon-material.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/layouts/header/popup/popup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-popup', 5 | templateUrl: './popup.component.html', 6 | styleUrls: ['./popup.component.css'], 7 | standalone: false 8 | }) 9 | 10 | export class PopupComponent { 11 | @Input() title: string; 12 | @Input() height: number|string; 13 | @Output() popupShown = new EventEmitter(); 14 | 15 | visible = false; 16 | 17 | shown(): void { 18 | this.popupShown.emit(); 19 | } 20 | 21 | show(): void { 22 | this.visible = true; 23 | } 24 | 25 | hide(): void { 26 | this.visible = false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/icons/icon-tm-info/icon-tm-info.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "resolveJsonModule": true, 13 | "target": "ES2022", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2017", 19 | "dom" 20 | ], 21 | "paths": { 22 | "jszip": [ 23 | "node_modules/jszip/dist/jszip.min.js" 24 | ] 25 | }, 26 | "useDefineForClassFields": false 27 | } 28 | } -------------------------------------------------------------------------------- /src/app/preview/button-group/button-group.component.css: -------------------------------------------------------------------------------- 1 | span { 2 | font-size: 14px; 3 | font-weight: 500; 4 | line-height: 24px; 5 | opacity: 0.86; 6 | } 7 | 8 | .btn-container { 9 | width: 100%; 10 | display: flex; 11 | flex-direction: column; 12 | gap: 16px; 13 | } 14 | 15 | .btn-row { 16 | display: flex; 17 | gap: 40px; 18 | } 19 | 20 | .btn-cell:first-child { 21 | width: 10%; 22 | justify-content: flex-start; 23 | } 24 | 25 | .btn-cell { 26 | width: 30%; 27 | display: flex; 28 | justify-content: center; 29 | } 30 | 31 | .btn-list { 32 | display: flex; 33 | flex-direction: column; 34 | gap: 0 16px; 35 | } 36 | 37 | .btn-container-click-disabled { 38 | pointer-events: none; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/preview/toolbar/toolbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-toolbar', 6 | templateUrl: './toolbar.component.html', 7 | styleUrls: ['./toolbar.component.css'], 8 | standalone: false 9 | }) 10 | export class ToolbarComponent { 11 | widgetGroup = 'toolbar'; 12 | isExpanded = new Subject(); 13 | 14 | items: any[] = [ 15 | { location: 'before', widget: 'dxButton', options: { type: 'back' } }, 16 | { location: 'after', widget: 'dxButton', options: { icon: 'plus' } }, 17 | { location: 'after', widget: 'dxButton', options: { icon: 'find' } }, 18 | { location: 'center', text: 'Products' } 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable, Subject } from 'rxjs'; 3 | import { Notification } from './types/notify'; 4 | 5 | @Injectable() 6 | export class NotificationsService { 7 | private $notification: Subject; 8 | 9 | constructor() { 10 | this.$notification = new BehaviorSubject(null); 11 | } 12 | 13 | error(message: string): void { 14 | this.notify(message, 'error'); 15 | } 16 | 17 | private notify(message: string, type: string): void { 18 | this.$notification.next({ message, type } as Notification); 19 | } 20 | 21 | get notification(): Observable { 22 | return this.$notification.asObservable(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/layouts/header/help-button/help-button.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | align-self: center; 3 | margin-right: 48px; 4 | margin-left: auto; 5 | } 6 | 7 | .header-button { 8 | font-size: 18px; 9 | font-weight: 500; 10 | line-height: 135%; 11 | display: flex; 12 | cursor: pointer; 13 | color: #596C7D; 14 | text-decoration: none; 15 | } 16 | 17 | .header-button > * { 18 | align-self: center; 19 | user-select: none; 20 | } 21 | 22 | .header-button svg { 23 | width: 24px; 24 | height: 24px; 25 | padding-right: 12px; 26 | } 27 | 28 | @media only screen and (max-width: 768px) { 29 | .header-button svg { 30 | padding-right: 0; 31 | } 32 | .header-button .button-text { 33 | display: none; 34 | } 35 | } -------------------------------------------------------------------------------- /src/app/icons/icon-loading/icon-loading.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/left-menu/editor/editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 9 | 10 | 15 | 16 | 22 |
23 | -------------------------------------------------------------------------------- /src/app/notify-error/notify-error.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | right: 0; 6 | margin: 0 auto; 7 | } 8 | 9 | .notify { 10 | width: 500px; 11 | background-color: #d9534f; 12 | box-shadow: 0px 4px 16px rgb(0,0,0,.15); 13 | padding: 25px; 14 | border-radius: 5px; 15 | transition: bottom .5s; 16 | visibility: hidden; 17 | } 18 | 19 | .notify.visible { 20 | visibility: visible; 21 | bottom: 50px; 22 | } 23 | 24 | .notify-message { 25 | color: white; 26 | text-align: center; 27 | } 28 | 29 | .notify-close { 30 | position: absolute; 31 | top: 10px; 32 | right: 10px; 33 | width: 14px; 34 | height: 14px; 35 | color: white; 36 | cursor: pointer; 37 | } 38 | 39 | @media only screen and (max-width: 768px) { 40 | .notify { 41 | width: auto; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/layouts/button/button.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .button { 6 | width: 191px; 7 | line-height: 1; 8 | font-weight: 500; 9 | font-size: 16px; 10 | border-radius: 40px; 11 | text-align: center; 12 | cursor: pointer; 13 | background-color: #fff; 14 | border: none; 15 | padding: 10px 20px; 16 | box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.16); 17 | margin: 0 auto; 18 | display: block; 19 | font-family: Roboto, Segoe UI, Tahoma, Geneva, Verdana, sans-serif; 20 | } 21 | 22 | .button.apply { 23 | background-color: #158BFF; 24 | color: #fff; 25 | margin-top: 45px; 26 | } 27 | 28 | button:enabled { 29 | color: #596C7D; 30 | } 31 | 32 | .button.apply:disabled { 33 | opacity: 0.5; 34 | } 35 | 36 | .advanced { 37 | margin-top: 15px; 38 | color: #596C7D; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/import/import-bootstrap/import-bootstrap.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
8 | Bootstrap Variables 9 |
10 |
11 | Upload a ".scss" file with your custom Bootstrap 5 variables to apply their values to a base DevExtreme theme. 12 |
13 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/layouts/header/help-button/help-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivationEnd, Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-help-button', 6 | templateUrl: './help-button.component.html', 7 | styleUrls: ['./help-button.component.css'], 8 | standalone: false 9 | }) 10 | export class HelpButtonComponent { 11 | readonly baseUrl: string = 'https://js.devexpress.com/Documentation/Guide/Themes_and_Styles/ThemeBuilder/'; 12 | public $docUrl: string; 13 | 14 | constructor(private router: Router) { 15 | this.router.events.subscribe((event) => { 16 | if(event instanceof ActivationEnd) { 17 | if(!(event.snapshot.data.docHash === undefined)) { 18 | this.$docUrl = `${this.baseUrl}${event.snapshot.data.docHash}`; 19 | } 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/icons/icon-metadata/icon-metadata.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/left-menu/base-parameters/base-parameters.component.css: -------------------------------------------------------------------------------- 1 | .editors { 2 | background-color: #f5f5f5; 3 | width: 100%; 4 | padding: 64px 32px 64px 88px; 5 | border-top-left-radius: 10px; 6 | border-bottom-left-radius: 10px; 7 | position: relative; 8 | box-sizing: border-box; 9 | margin-top: 23px; 10 | } 11 | 12 | .editors:after { 13 | content: "Basic Settings"; 14 | color: #aaa; 15 | position: absolute; 16 | transform: rotate(-90deg); 17 | top: 50%; 18 | left: -5px; 19 | font-size: 16px; 20 | font-weight: 400; 21 | } 22 | 23 | .work-item { 24 | width: 195px; 25 | height: 50px; 26 | margin-bottom: 18px; 27 | } 28 | 29 | :host ::ng-deep .work-item .property-header { 30 | display: block; 31 | margin-bottom: 5px; 32 | color: #9c9c9c; 33 | padding: 0; 34 | } 35 | 36 | :host ::ng-deep .work-item .property-widget { 37 | padding: 0; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/names.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Injectable() 5 | export class NamesService { 6 | 7 | constructor(private sanitizer: DomSanitizer) { } 8 | 9 | private ORDER_REGEX = /^(\d+).\s/; 10 | 11 | getRealName(orderedName): string { 12 | return orderedName.replace(this.ORDER_REGEX, ''); 13 | } 14 | 15 | getHighlightedForLeftMenuName(orderedName, searchText): SafeHtml { 16 | const text = this.getRealName(orderedName); 17 | if(!searchText) return text; 18 | 19 | const highlightedText = text.replace(new RegExp(`(${searchText})`, 'ig'), '$1'); 20 | 21 | return this.sanitizer.bypassSecurityTrustHtml(highlightedText); 22 | } 23 | 24 | sortNames(name1, name2): number { 25 | const redix = 10; 26 | return Number.parseInt(name1, redix) - Number.parseInt(name2, redix); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/preview/accordion/accordion.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-accordion', 6 | templateUrl: './accordion.component.html', 7 | styleUrls: ['./accordion.component.css'], 8 | standalone: false 9 | }) 10 | export class AccordionComponent { 11 | widgetGroup = 'accordion'; 12 | isExpanded = new Subject(); 13 | 14 | accordionItems: any[] = [ 15 | { 16 | title: 'Jack London (1876-1916)', 17 | icon: 'card', 18 | html: `

The Call of the Wild (1903)

Before Adam (1907)

19 |

Burning Daylight (1910)

The Abysmal Brute (1911)

` 20 | }, { 21 | title: 'Mark Twain (1835-1910)', 22 | icon: 'card', 23 | html: `

Screamers (1871)

Eye Openers (1871)

24 |

Colonel Sellers (1874)

Merry Tales (1892)

` 25 | } 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/preview/editors/editors.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import validationEngine from 'devextreme/ui/validation_engine'; 3 | import { BehaviorSubject, Subscription } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'app-editors', 7 | templateUrl: './editors.component.html', 8 | styleUrls: ['./editors.component.css'], 9 | standalone: false 10 | }) 11 | export class EditorsComponent implements OnInit, OnDestroy { 12 | widgetGroup = 'editors'; 13 | isExpanded = new BehaviorSubject(false); 14 | subscription: Subscription; 15 | dropDownOptions = {hideOnParentScroll: true}; 16 | labelMode = 'floating'; 17 | stylingModeValue = 'outlined'; 18 | 19 | ngOnInit(): void { 20 | this.subscription = this.isExpanded.subscribe((expanded) => { 21 | if(expanded) setTimeout(() => validationEngine.validateGroup()); 22 | }); 23 | } 24 | 25 | ngOnDestroy(): void { 26 | this.subscription.unsubscribe(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/icons/icon-fluent/icon-fluent.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 20 10 | 11 | steps: 12 | - name: Get sources 13 | uses: actions/checkout@v4 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20' 19 | 20 | - name: Restore npm cache 21 | uses: actions/cache@v4 22 | with: 23 | path: ./node_modules 24 | key: ${{ runner.os }}-node-modules 25 | 26 | - name: Run npm ci 27 | run: npm ci --no-audit --no-fund 28 | 29 | - name: Install Internal Package 30 | uses: DevExpress/github-actions/install-internal-package@main 31 | 32 | - name: Build 33 | run: npm run build 34 | 35 | - name: Deploy 36 | uses: JamesIves/github-pages-deploy-action@3.7.1 37 | with: 38 | GITHUB_TOKEN: ${{ secrets.DEPLOY_SECRET }} 39 | BRANCH: gh-pages 40 | FOLDER: dist/devextreme-themebuilder-app 41 | CLEAN: true 42 | 43 | -------------------------------------------------------------------------------- /src/app/layouts/header/import-popup/import-popup.component.css: -------------------------------------------------------------------------------- 1 | .text-area { 2 | margin: 32px 0 32px; 3 | } 4 | 5 | .radio-group { 6 | display: flex; 7 | justify-content: center; 8 | padding-bottom: 27px; 9 | } 10 | 11 | ::ng-deep .radio-group .dx-item { 12 | margin-right: 30px; 13 | } 14 | 15 | .tabs { 16 | margin-top: 48px; 17 | padding: 0 16px; 18 | } 19 | 20 | .tabs .text { 21 | text-align: center; 22 | } 23 | 24 | .tabs .text.first-tab, .tabs .text.second-tab { 25 | margin-top: 48px; 26 | margin-bottom: 20px; 27 | } 28 | 29 | .tabs .text.second-tab { 30 | margin-bottom: 42px; 31 | } 32 | 33 | app-bootstrap-uploader { 34 | margin-bottom: 32px; 35 | } 36 | 37 | .upload-meta { 38 | padding-bottom: 5px; 39 | } 40 | 41 | ::ng-deep .dx-multiview-item .upload-meta .button { 42 | box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.16); 43 | } 44 | 45 | ::ng-deep .upload-meta .button:disabled { 46 | cursor: default; 47 | } 48 | 49 | .additional-text { 50 | opacity: 0.5; 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/DevExpress/ThemeBuilder.svg?style=svg)](https://circleci.com/gh/DevExpress/ThemeBuilder) 2 | 3 | # DevExtreme ThemeBuilder UI 4 | 5 | The DevExtreme ThemeBuilder UI allows you to modify themes shipped with DevExtreme or create custom themes. This tool is available [online](https://devexpress.github.io/ThemeBuilder/). 6 | 7 | If you need to run the ThemeBuilder UI locally, clone this repository and follow the instructions below. 8 | 9 | ## Prerequisites 10 | 11 | You will need Node.js 12.16 or higher and npm 5.5.1 or higher. The app is built using angular-cli. Run `npm install @angular/cli -g` to install it. 12 | 13 | ## Install required packages and Launch 14 | 15 | Run the following commands: 16 | 17 | ``` 18 | npm install 19 | npm start 20 | ``` 21 | 22 | Go to http://localhost:4200/. 23 | 24 | NOTE: The ThemeBuilder UI creates themes for the latest minor DevExtreme version. If you want to create a theme for a specific version, use the [ThemeBuilder CLI](https://js.devexpress.com/Documentation/Guide/Common/DevExtreme_CLI/#ThemeBuilder). 25 | -------------------------------------------------------------------------------- /src/app/notify-error/notify-error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NotificationsService } from '../notification.service'; 3 | import { Notification } from '../types/notify'; 4 | 5 | @Component({ 6 | selector: 'app-notifications', 7 | templateUrl: './notify-error.component.html', 8 | styleUrls: ['./notify-error.component.css'], 9 | standalone: false 10 | }) 11 | 12 | export class NotifyErrorComponent implements OnInit { 13 | notifyMessage: string; 14 | visible = false; 15 | 16 | constructor(private service: NotificationsService) { 17 | this.service.notification.subscribe((notify) => { 18 | if(notify) { 19 | this.render(notify); 20 | } 21 | }); 22 | } 23 | 24 | ngOnInit(): void { 25 | this.notifyMessage = ''; 26 | } 27 | 28 | render(notify: Notification): void { 29 | this.notifyMessage = notify.message; 30 | this.visible = true; 31 | } 32 | 33 | closeNotify(): void { 34 | this.visible = false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/preview/buttons/buttons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { DxButtonTypes } from 'devextreme-angular/ui/button'; 4 | 5 | @Component({ 6 | selector: 'app-buttons', 7 | templateUrl: './buttons.component.html', 8 | styleUrls: ['./buttons.component.css'], 9 | standalone: false 10 | }) 11 | 12 | export class ButtonsComponent { 13 | typeValue = 'default'; 14 | 15 | initStateHover(event: DxButtonTypes.ClickEvent) { 16 | event.component.element().classList.add('dx-state-hover'); 17 | } 18 | 19 | initStateActive(event: DxButtonTypes.ClickEvent) { 20 | event.component.element().classList.add('dx-state-active'); 21 | } 22 | 23 | initStateFocused(event: DxButtonTypes.ClickEvent) { 24 | event.component.element().classList.add('dx-state-focused'); 25 | } 26 | 27 | isExpanded = new BehaviorSubject(false); 28 | 29 | buttonModes: DxButtonTypes.ButtonStyle[] = ['contained', 'outlined', 'text']; 30 | 31 | widgetGroup = 'buttons'; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/icons/icon-themes/icon-themes.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/layouts/header/export-popup/help-tooltip/help-tooltip.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/preview/button-detailed/button-detailed.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/layouts/header/header-button/header-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
{{text}}
7 |
8 | -------------------------------------------------------------------------------- /src/app/preview/filterbuilder/filterbuilder.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-filterbuilder', 6 | templateUrl: './filterbuilder.component.html', 7 | styleUrls: ['./filterbuilder.component.css'], 8 | standalone: false 9 | }) 10 | export class FilterbuilderComponent { 11 | widgetGroup = 'filterbuilder'; 12 | isExpanded = new Subject(); 13 | 14 | filterBuilderValue: any[] = [ 15 | ['Name', '=', 'Projector PlusHD'], 16 | 'or', 17 | [ 18 | ['Category', '=', 'Monitors'], 19 | ['Price', '<', '1300'] 20 | ], 21 | 'or', 22 | [ 23 | ['Category', '=', 'Televisions'] 24 | ] 25 | ]; 26 | 27 | filterBuilderFields: any[] = [{ 28 | dataField: 'ID', 29 | dataType: 'number' 30 | }, { 31 | dataField: 'Name' 32 | }, { 33 | dataField: 'Price', 34 | dataType: 'number' 35 | }, { 36 | dataField: 'Current_Inventory', 37 | dataType: 'number' 38 | }, { 39 | dataField: 'Category' 40 | }]; 41 | } 42 | -------------------------------------------------------------------------------- /src/app/layouts/bootstrap-uploader/bootstrap-uploader.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .file-uploader { 6 | margin: 0 auto 0; 7 | } 8 | 9 | :host ::ng-deep .file-uploader .dx-fileuploader-input-container { 10 | float: left; 11 | text-align: center; 12 | margin-top: 27px; 13 | opacity: 0.5; 14 | } 15 | 16 | :host ::ng-deep .file-uploader .dx-button { 17 | width: 100%; 18 | height: 44px; 19 | font-weight: bold; 20 | font-size: 16px; 21 | border-radius: 40px; 22 | background-color: #158BFF; 23 | } 24 | 25 | :host ::ng-deep .file-uploader .dx-button .dx-button-text { 26 | color: #fff; 27 | text-transform: none; 28 | font-family: 'Roboto'; 29 | font-style: normal; 30 | font-weight: 500; 31 | font-size: 14px; 32 | } 33 | 34 | /* :host ::ng-deep .file-uploader .dx-button.dx-state-hover { 35 | background-color: #707070; 36 | color: #fff; 37 | } */ 38 | 39 | /* :host ::ng-deep .file-uploader .dx-button.dx-state-active, 40 | :host ::ng-deep .file-uploader .dx-button.dx-state-focused { 41 | background-color: #9e9e9e; 42 | color: #fff; 43 | } */ 44 | -------------------------------------------------------------------------------- /src/app/layouts/header/import-popup/import-popup.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { ImportService } from '../../../import.service'; 3 | import { PopupComponent } from '../popup/popup.component'; 4 | 5 | @Component({ 6 | selector: 'app-import-popup', 7 | templateUrl: './import-popup.component.html', 8 | styleUrls: ['./import-popup.component.css'], 9 | standalone: false 10 | }) 11 | export class ImportPopupComponent { 12 | @ViewChild('popup') popup: PopupComponent; 13 | 14 | constructor( 15 | private importService: ImportService 16 | ) { } 17 | 18 | radioGroupData = [{ 19 | text: 'Bootstrap 5', 20 | value: 0, 21 | version: 5, 22 | acceptFormat: '.scss', 23 | uploadButtonText: 'Upload SCSS Variables' 24 | }]; 25 | 26 | selectedIndex = 0; 27 | importValue = ''; 28 | 29 | applyClick(t): void { 30 | this.importService.importMetadata(t.value, 'advanced').then(() => { 31 | this.popup.hide(); 32 | t.value = ''; 33 | }); 34 | } 35 | 36 | imported(): void { 37 | this.popup.hide(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/images/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/app/left-menu/base-parameters/base-parameters.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Base size
4 |
5 | 13 |
14 |
15 |
16 | 17 |
18 | 19 | 24 |
25 | -------------------------------------------------------------------------------- /src/app/preview/button-detailed/button-detailed.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { AnalyticsEventsService } from '../../../app/analytics-events.service'; 3 | 4 | @Component({ 5 | selector: 'app-button-detailed', 6 | templateUrl: './button-detailed.component.html', 7 | styleUrls: ['./button-detailed.component.css'], 8 | standalone: false 9 | }) 10 | 11 | export class ButtonDetailedComponent { 12 | @Input() widget: string; 13 | @Input() currentWidget: string; 14 | 15 | @Output() clicked = new EventEmitter(); 16 | 17 | constructor( 18 | private analyticsEventsService: AnalyticsEventsService 19 | ) { } 20 | 21 | onClick(): void { 22 | const isClosed = this.widget !== this.currentWidget; 23 | window.parent.postMessage( 24 | { widget: isClosed ? this.widget : 'base.common' }, 25 | window.parent.location.href 26 | ); 27 | 28 | this.analyticsEventsService.trackEvent( 29 | 'TB: Settings', 30 | 'Tb ' + (isClosed ? 'open' : 'close') + ' settings', 31 | this.widget 32 | ); 33 | 34 | this.clicked.emit({ isClosed }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/layouts/app-layout/app-layout.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | overflow-x: hidden; 7 | } 8 | 9 | :host ::ng-deep .content { 10 | display: flex; 11 | justify-content: center; 12 | flex-direction: column; 13 | text-align: center; 14 | color: #404040; 15 | margin: 64px 0; 16 | } 17 | 18 | :host ::ng-deep .title { 19 | font-weight: 500; 20 | font-size: 32px; 21 | margin: 0 auto; 22 | display: inline-block; 23 | line-height: 100%; 24 | } 25 | 26 | :host ::ng-deep .title-link { 27 | border-radius: 9999px; 28 | color: #899097; 29 | cursor: pointer; 30 | font-size: 16px; 31 | font-weight: 400; 32 | line-height: 125%; 33 | margin: 0 auto 48px auto; 34 | padding: 12px 24px; 35 | background: #F1F1F1; 36 | } 37 | 38 | :host ::ng-deep .title span { 39 | font-weight: bold; 40 | font-size: 24px; 41 | display: block; 42 | margin-top: -5px; 43 | } 44 | 45 | :host ::ng-deep .title-link:hover { 46 | background: #E9E9E9; 47 | } 48 | 49 | :host ::ng-deep .text { 50 | font-size: 16px; 51 | color: #222222; 52 | line-height: 125%; 53 | max-width: 824px; 54 | } 55 | -------------------------------------------------------------------------------- /src/app/import/import-meta/import-meta.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
8 | DevExtreme Metadata 9 |
10 |
11 | Upload a .json file with DevExtreme metadata 12 |
13 | 19 |
20 | or paste metadata in the area below. 21 |
22 | 30 | 31 | Upload Metadata 32 |
33 |
34 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "OpenSansCondensedBold"; 3 | src: url("./content/fonts/OpenSansCondensedBold.eot"); 4 | src: url("./content/fonts/OpenSansCondensedBold.eot?#iefix") format("embedded-opentype"), 5 | url("./content/fonts/OpenSansCondensedBold.woff") format("woff"), 6 | url("./content/fonts/OpenSansCondensedBold.ttf") format("truetype"); 7 | font-style: normal; 8 | font-weight: normal; 9 | } 10 | 11 | ul, li { 12 | margin: 0; 13 | padding: 0; 14 | list-style: none; 15 | } 16 | 17 | html, 18 | body { 19 | font-family: 'Roboto', sans-serif; 20 | font-size: 13px; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | 25 | .dx-textbox.dx-editor-outlined { 26 | border-radius: 4px; 27 | } 28 | 29 | .dx-textarea.dx-editor-outlined { 30 | border-radius: 4px; 31 | } 32 | 33 | :focus { 34 | outline: none; 35 | } 36 | 37 | .title-link { 38 | display: flex; 39 | align-items: center; 40 | } 41 | 42 | .title-link .arrow-back { 43 | margin-right: 16px; 44 | } 45 | 46 | .info-color { 47 | color: #03a9f4; 48 | } 49 | 50 | @media only screen and (max-width: 768px) { 51 | .content-width { 52 | padding-left: 24px; 53 | padding-right: 24px; 54 | } 55 | } 56 | 57 | @media (min-height: 1000px) { 58 | body { 59 | overflow-y: hidden; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/layouts/app-layout/app-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivationEnd, Router } from '@angular/router'; 3 | import { Subject } from 'rxjs'; 4 | import { routeAnimation } from './../../animations'; 5 | import { RouteId } from 'src/app/types/route-id'; 6 | 7 | @Component({ 8 | selector: 'app-app-layout', 9 | templateUrl: './app-layout.component.html', 10 | styleUrls: ['./app-layout.component.css'], 11 | animations: [routeAnimation], 12 | standalone: false 13 | }) 14 | 15 | export class AppLayoutComponent { 16 | animationValue: number; 17 | animationDone = new Subject(); 18 | themesSwitchEnabled = false; 19 | 20 | constructor(private router: Router) { 21 | this.router.events.subscribe((event) => { 22 | if(event instanceof ActivationEnd) { 23 | const snapshot = event.snapshot; 24 | if(snapshot.data.routeId) { 25 | this.animationValue = snapshot.data.routeId; 26 | this.themesSwitchEnabled = snapshot.data.routeId === RouteId.GROUPED_WIDGET; 27 | } 28 | } 29 | }); 30 | } 31 | 32 | getRouteAnimation(): number { 33 | return this.animationValue; 34 | } 35 | 36 | routeAnimationDone(): void { 37 | this.animationDone.next(true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/preview/chat/chat.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 23 | 28 |
29 |
30 | 38 | 39 | 40 | 45 | 46 | 47 |
48 |
49 | -------------------------------------------------------------------------------- /src/app/preview/overlays/overlays.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | width: 100%; 4 | } 5 | 6 | :host > .collapsed { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | height: 90px; 11 | } 12 | 13 | :host ::ng-deep .dx-button:not(.dx-closebutton) { 14 | margin: 0 15px 10px 15px; 15 | } 16 | 17 | :host ::ng-deep .dx-overlay-wrapper.dx-toast-wrapper { 18 | z-index: 1500!important; 19 | } 20 | 21 | #loadpanel-block, 22 | #toast-block, 23 | #tooltip-block { 24 | height: 1px; 25 | width: 250px; 26 | } 27 | 28 | #tooltip-block { 29 | position: relative; 30 | } 31 | 32 | #tooltip-block::ng-deep .dx-tooltip-wrapper { 33 | position: initial !important; 34 | } 35 | 36 | #loadpanel-block { 37 | width: 240px; 38 | } 39 | 40 | :host-context(.dx-theme-fluent-typography) #loadpanel-block { 41 | width: 140px; 42 | } 43 | 44 | #tooltip-block { 45 | height: auto; 46 | width: 140px; 47 | } 48 | 49 | ::ng-deep .dx-theme-material-typography #loadpanel-block { 50 | width: 100px; 51 | } 52 | 53 | #target-block { 54 | display: inline-block; 55 | text-align: center; 56 | padding: 120px 0 50px; 57 | height: 200px; 58 | width: 100%; 59 | } 60 | 61 | #target-element { 62 | display: inline-block; 63 | width: 20px; 64 | height: 10px; 65 | } 66 | 67 | #target-block #loadpanel-target { 68 | display: inline-block; 69 | width: 100%; 70 | height: 100px; 71 | margin-top: 100px; 72 | } 73 | 74 | .buttons-container { 75 | text-align: center; 76 | } 77 | -------------------------------------------------------------------------------- /src/app/theme-builder.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { BuilderResult } from './types/builder-result'; 4 | import { BuilderConfig } from './types/builder-config'; 5 | import { Theme } from './types/theme'; 6 | import { Metadata } from './types/metadata'; 7 | 8 | @Injectable() 9 | export class ThemeBuilderService { 10 | private url = 'https://js.devexpress.com/api/themebuilder'; 11 | 12 | constructor(private http: HttpClient) {} 13 | 14 | private build(theme: Theme, config: BuilderConfig): Promise { 15 | config.baseTheme = theme.name + '.' + theme.colorScheme.replace(/-/g, '.'); 16 | 17 | const postBuilder: Promise = this.http.post(`${this.url}/buildtheme`, config).toPromise(); 18 | return postBuilder; 19 | } 20 | 21 | buildTheme(theme, config: BuilderConfig): Promise { 22 | return this.build(theme, config); 23 | } 24 | 25 | buildThemeBootstrap(theme: Theme, bootstrapVariables: string, bootstrapVersion: number): Promise { 26 | const LESS_BOOTSTRAP_VERSION = 3; 27 | return this.build(theme, { 28 | data: bootstrapVariables, 29 | inputFile: bootstrapVersion === LESS_BOOTSTRAP_VERSION ? '.less' : '.scss', 30 | bootstrapVersion 31 | }); 32 | } 33 | 34 | getMetadata(): Promise { 35 | const promise = this.http.get(`${this.url}/metadata`).toPromise() as Promise; 36 | return promise; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/icons/icon-bootstrap/icon-bootstrap.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/master/master.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 9 |
10 | {{themeName | titlecase}} Theme 11 |
12 |
13 |
    14 |
  • 17 | 18 | {{theme.text.replace('Compact', '')}} 19 |
  • 20 |
21 |
22 |
23 | 24 | Export 25 | Advanced Settings 26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /src/app/preview/wizard/wizard.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | right: 0; 6 | left: 0; 7 | } 8 | 9 | .header { 10 | width: 100%; 11 | height: 80px; 12 | padding: 0 20px; 13 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.07); 14 | } 15 | 16 | :host ::ng-deep .dx-toolbar .dx-toolbar-items-container { 17 | height: 100%; 18 | } 19 | 20 | :host ::ng-deep .dx-toolbar .dx-toolbar-item { 21 | padding: 0 10px 0 0; 22 | } 23 | 24 | :host ::ng-deep .dx-toolbar-item img { 25 | border-radius: 50%; 26 | border: 1px solid #9c9c9c; 27 | display: inline-block; 28 | vertical-align: middle; 29 | margin-right: 15px; 30 | } 31 | 32 | .flex-blocks { 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | margin-top: 24px; 37 | } 38 | 39 | .flex-blocks.generic.normal { 40 | bottom: 400px; 41 | } 42 | 43 | .flex-blocks.generic.compact { 44 | bottom: 448px; 45 | } 46 | 47 | .flex-blocks.material { 48 | bottom: 362px; 49 | } 50 | 51 | .flex-block { 52 | flex: 1 1 520px; 53 | max-width: 100%; 54 | min-width: 520px; 55 | height: 330px; 56 | box-sizing: border-box; 57 | padding: 25px; 58 | } 59 | 60 | .flex-blocks.generic.normal .flex-block { 61 | height: 265px; 62 | } 63 | 64 | .flex-blocks.generic.compact .flex-block { 65 | height: 205px; 66 | } 67 | 68 | .flex-block.left { 69 | margin: 0 12px 24px 24px; 70 | } 71 | 72 | .flex-block.right { 73 | margin: 0 24px 24px 12px; 74 | } 75 | 76 | 77 | :host ::ng-deep .flex-block .dx-button { 78 | margin-left: 10px; 79 | float: right; 80 | } -------------------------------------------------------------------------------- /src/app/preview/preview/preview.component.css: -------------------------------------------------------------------------------- 1 | .component-container { 2 | padding: 40px; 3 | display: flex; 4 | flex-wrap: wrap; 5 | gap: 48px 24px; 6 | } 7 | 8 | .component-item { 9 | flex: 1 1 100%; 10 | } 11 | 12 | .component-item--half { 13 | flex: 1 1 45%; 14 | } 15 | 16 | .component-item--active { 17 | flex: 1 1 100%; 18 | } 19 | 20 | .component-item--active .component-display { 21 | max-height: max-content; 22 | } 23 | 24 | .component-display { 25 | position: relative; 26 | height: calc(100% - 88px); 27 | max-height: 320px; 28 | padding: 24px; 29 | border: 2px dashed #DADADA; 30 | border-radius: 4px; 31 | } 32 | 33 | .component-display:hover, 34 | .component-item.selected-component-item .component-display { 35 | border-color: #2190F7; 36 | } 37 | 38 | :host ::ng-deep .component-options { 39 | display: flex; 40 | gap: 24px; 41 | margin-bottom: 12px; 42 | } 43 | 44 | :host ::ng-deep .component-item:not(.component-item--active) button { 45 | display: none; 46 | } 47 | 48 | :host ::ng-deep .component-item:not(.component-item--active) .component-display:hover button { 49 | display: block; 50 | } 51 | 52 | /* =========== */ 53 | 54 | :host ::ng-deep .dx-scrollview.preview-scrollview > .dx-scrollable-wrapper > .dx-scrollable-container > .dx-scrollbar-vertical.dx-scrollbar-hoverable.dx-state-hover { 55 | background-color: rgba(191, 191, 191, 0.2); 56 | } 57 | 58 | :host ::ng-deep .dx-scrollview.preview-scrollview > .dx-scrollable-wrapper > .dx-scrollable-container > .dx-scrollbar-vertical.dx-scrollbar-hoverable .dx-scrollable-scroll .dx-scrollable-scroll-content { 59 | background-color: rgba(191, 191, 191, 0.7); 60 | } 61 | -------------------------------------------------------------------------------- /src/app/icons/icon-theme-circle/icon-theme-circle.component.css: -------------------------------------------------------------------------------- 1 | .color-circle { 2 | width: 45px; 3 | height: 45px; 4 | display: inline-block; 5 | box-sizing: border-box; 6 | border-style: solid; 7 | border-width: 10px; 8 | border-radius: 50%; 9 | vertical-align: middle; 10 | margin-right: 15px; 11 | } 12 | 13 | .color-circle.small { 14 | height: 20px; 15 | width: 20px; 16 | border-width: 4px; 17 | box-shadow: rgba(0, 0, 0, 0.35) 0px 1px 3px; 18 | margin-right: 0; 19 | } 20 | 21 | .color-circle.light { 22 | border-color: #fff; 23 | background-color: #337ab7; 24 | } 25 | 26 | .color-circle.dark { 27 | border-color: #363640; 28 | background-color: #1ca8dd; 29 | } 30 | 31 | .color-circle.blue { 32 | background-color: #03a9f4; 33 | } 34 | 35 | .color-circle.orange { 36 | background-color: #ff5722; 37 | } 38 | 39 | .color-circle.lime { 40 | background-color: #cddc39; 41 | } 42 | 43 | .color-circle.purple { 44 | background-color: #9c27b0 ; 45 | } 46 | 47 | .color-circle.teal { 48 | background-color: #009688; 49 | } 50 | 51 | .color-circle.soft { 52 | background-color: #7ab8eb; 53 | border-color: #fff; 54 | } 55 | 56 | .color-circle.carmine { 57 | background-color: #f05b41; 58 | border-color: #fff; 59 | } 60 | 61 | .color-circle.dark.moon { 62 | background-color: #3debd3; 63 | border-color: #465672; 64 | } 65 | 66 | .color-circle.dark.violet { 67 | background-color: #9c63ff; 68 | border-color: #17171f; 69 | } 70 | 71 | .color-circle.green { 72 | background-color: #3cbab2; 73 | border-color: #f5f5f5; 74 | } 75 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 5 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | # type/discussion - our discussion pages. They can be inactive for a long time. 12 | # WIP - pull requests that have "work in progress" status 13 | exemptLabels: 14 | - type/discussion 15 | - WIP 16 | - roadmap 17 | 18 | # Label to use when marking as stale 19 | staleLabel: stale 20 | 21 | # Comment to post when marking as stale. Set to `false` to disable 22 | markComment: > 23 | This issue has been automatically marked as stale because it has not had 24 | recent activity. It will be closed if no further activity occurs. Thank you 25 | for your contributions. 26 | # Comment to post when closing a stale issue. Set to `false` to disable 27 | issues: 28 | closeComment: > 29 | This issue was closed due to inactivity. 30 | If you found a bug or have a question about our product, [create a new issue](https://github.com/DevExpress/ThemeBuilder/issues) on GitHub or submit a ticket in our [Support Center](https://www.devexpress.com/Support/Center/). 31 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 32 | pulls: 33 | markComment: > 34 | This pull request has been automatically marked as stale because it has not had 35 | recent activity. It will be closed if no further activity occurs. Thank you 36 | for your contributions. -------------------------------------------------------------------------------- /src/app/preview/splitter/splitter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | interface PaneContentTemplate { 5 | name: string; 6 | data?: any; 7 | } 8 | 9 | @Component({ 10 | selector: 'app-splitter', 11 | templateUrl: './splitter.component.html', 12 | styleUrls: ['./splitter.component.css'], 13 | standalone: false 14 | }) 15 | export class SplitterComponent implements OnDestroy { 16 | isExpanded = new BehaviorSubject(false); 17 | widgetGroup = 'splitter'; 18 | dimensionOptions = new Set(['size', 'minSize', 'maxSize']); 19 | 20 | paneContentTemplates: PaneContentTemplate[] = [ 21 | { name: 'Left Pane' }, 22 | { name: 'Central Pane' }, 23 | { name: 'Right Pane' }, 24 | { name: 'Nested Left Pane' }, 25 | { name: 'Nested Central Pane' }, 26 | { name: 'Nested Right Pane' }, 27 | { name: 'Nested Top Pane' }, 28 | { name: 'Nested Bottom Pane' } 29 | ]; 30 | 31 | getPaneState(data: any): string { 32 | if(data.resizable !== false && !data.collapsible) { 33 | return 'Resizable only'; 34 | } 35 | const resizableText = data.resizable ? 'Resizable' : 'Non-resizable'; 36 | const collapsibleText = data.collapsible ? 'collapsible' : 'non-collapsible'; 37 | 38 | return `${resizableText} and ${collapsibleText}`; 39 | } 40 | 41 | filterDimensionOptions(data: any): { key: string; value: any }[] { 42 | return Object.entries(data) 43 | .reverse() 44 | .filter(([key]) => this.dimensionOptions.has(key)) 45 | .map(([key, value]) => ({ key, value })); 46 | } 47 | 48 | ngOnDestroy(): void { 49 | this.isExpanded.unsubscribe(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devextreme-themebuilder-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build --configuration=production --base-href https://devexpress.github.io/ThemeBuilder/ && node ./copyfile.js", 8 | "lint-fix": "eslint --fix \"src/**/*.ts\"", 9 | "lint": "eslint \"src/**/*.ts\"", 10 | "test": "npm run lint && npm run build" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular-devkit/schematics": "^20.3.13", 15 | "@angular/animations": "^20.3.15", 16 | "@angular/common": "^20.3.15", 17 | "@angular/compiler": "^20.3.15", 18 | "@angular/core": "^20.3.15", 19 | "@angular/forms": "^20.3.15", 20 | "@angular/platform-browser": "^20.3.15", 21 | "@angular/platform-browser-dynamic": "^20.3.15", 22 | "@angular/router": "^20.3.15", 23 | "@ngtools/webpack": "^20.3.13", 24 | "core-js": "^2.5.4", 25 | "devextreme": "25.2.3", 26 | "devextreme-angular": "25.2.3", 27 | "devextreme-themebuilder": "25.2.3", 28 | "file-saver": "^2.0.2", 29 | "jszip": "^3.2.2", 30 | "jszip-utils": "^0.1.0", 31 | "rxjs": "^7.0.0", 32 | "smoothscroll-polyfill": "^0.4.3", 33 | "zone.js": "~0.15.0" 34 | }, 35 | "devDependencies": { 36 | "@angular-builders/custom-webpack": "^20.0.0", 37 | "@angular-devkit/build-angular": "^20.3.13", 38 | "@angular-devkit/core": "^20.3.13", 39 | "@angular/cli": "^20.3.13", 40 | "@angular/compiler-cli": "^20.3.15", 41 | "@angular/language-service": "^20.3.15", 42 | "@babel/core": "^7.3.4", 43 | "@types/node": "ts5.4", 44 | "@typescript-eslint/eslint-plugin": "^5.0.0", 45 | "eslint": "^8.0.0", 46 | "path-browserify": "^1.0.1", 47 | "ts-node": "~5.0.1", 48 | "typescript": "~5.8.0" 49 | }, 50 | "overrides": { 51 | "form-data": "~2.5.4" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/left-menu/base-parameters/base-parameters.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnDestroy, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | import { MetadataRepositoryService } from '../../meta-repository.service'; 5 | import { NamesService } from '../../names.service'; 6 | import { MetaItem } from '../../types/meta-item'; 7 | 8 | @Component({ 9 | selector: 'app-base-parameters', 10 | templateUrl: './base-parameters.component.html', 11 | styleUrls: ['./base-parameters.component.css'], 12 | standalone: false 13 | }) 14 | export class BaseParametersComponent implements OnDestroy, OnInit { 15 | @Input() theme: string; 16 | @Input() themeSize: string; 17 | 18 | subscription: Subscription; 19 | editorsData: MetaItem[]; 20 | 21 | constructor( 22 | private metadataRepository: MetadataRepositoryService, 23 | private names: NamesService, 24 | private router: Router 25 | ) { } 26 | 27 | updateData(): void { 28 | this.metadataRepository.getBaseParameters().then((baseParameters) => { 29 | this.editorsData = baseParameters; 30 | }); 31 | } 32 | 33 | themeSizeChanged(e): void { 34 | const currentColorScheme = this.metadataRepository.theme.colorScheme; 35 | const size: string = e.value; 36 | const newColorScheme = size === 'compact' ? 37 | currentColorScheme + '-' + size : 38 | currentColorScheme.replace('-compact', ''); 39 | 40 | this.router.navigate(['master', this.theme, newColorScheme]); 41 | } 42 | 43 | ngOnInit(): void { 44 | this.updateData(); 45 | 46 | this.subscription = this.metadataRepository.css.subscribe(() => { 47 | this.updateData(); 48 | }); 49 | } 50 | 51 | ngOnDestroy(): void { 52 | this.subscription.unsubscribe(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/index/index.component.css: -------------------------------------------------------------------------------- 1 | .blocks { 2 | display: flex; 3 | gap: 24px; 4 | justify-content: center; 5 | } 6 | 7 | @media only screen and (max-width: 700px) { 8 | .blocks { 9 | flex-direction: column; 10 | } 11 | 12 | .blocks .block { 13 | width: 100%; 14 | flex: 1; 15 | } 16 | } 17 | 18 | @media only screen and (min-width: 700px) { 19 | .blocks .block { 20 | width: 320px; 21 | } 22 | } 23 | 24 | .blocks .block { 25 | align-items: center; 26 | display: flex; 27 | flex-direction: column; 28 | padding: 32px 0; 29 | border-radius: 12px; 30 | box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.12); 31 | color: #596C7D; 32 | cursor: pointer; 33 | } 34 | 35 | .blocks .block .block-image { 36 | width: 80px; 37 | height: 80px; 38 | } 39 | 40 | .arrow-back { 41 | margin-right: 16px; 42 | } 43 | 44 | .title-link { 45 | display: flex; 46 | align-items: center; 47 | } 48 | 49 | .blocks .block:hover { 50 | background-color: #2190F7; 51 | color: #fff; 52 | } 53 | 54 | .blocks .block .block-title { 55 | font-size: 16px; 56 | font-weight: 500; 57 | line-height: 32px; 58 | } 59 | 60 | :host ::ng-deep .blocks .block:hover .icon-container .svg-icon path { 61 | fill: #2190F7; 62 | } 63 | :host ::ng-deep .blocks .block .icon-container { 64 | background-color: #2190F7; 65 | } 66 | 67 | :host ::ng-deep .blocks .block:hover .icon-container { 68 | background-color: #fff; 69 | } 70 | 71 | :host ::ng-deep .blocks .block:hover .block-image .st0 { 72 | fill: #fff; 73 | } 74 | :host ::ng-deep .blocks .block:hover .block-image .st1 { 75 | fill: #2190F7; 76 | } 77 | 78 | :host ::ng-deep .blocks .block:hover .block-image .icon-material .st1, 79 | :host ::ng-deep .blocks .block:hover .block-image .icon-material .st2 { 80 | fill:#158BFF; 81 | } 82 | 83 | .text { 84 | margin: 24px auto 48px auto; 85 | } 86 | -------------------------------------------------------------------------------- /src/app/layouts/header/import-popup/import-popup.component.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 | Upload a .json file with DevExtreme metadata 10 |
11 | 18 |
19 | or paste metadata in the area below. 20 |
21 | 30 | 31 | Upload Metadata 32 |
33 | 34 |
35 | Upload a ".scss" file with your custom Bootstrap 5 variables to apply their values to a base DevExtreme theme. 36 |
37 | 38 | 44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/app/layouts/header/help-button/help-button.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | Theme Builder Help 9 | 10 | -------------------------------------------------------------------------------- /src/app/preview/drawer/drawer.component.ts: -------------------------------------------------------------------------------- 1 | /*eslint no-invalid-this: ['Off']*/ 2 | import { Component, ViewChild } from '@angular/core'; 3 | import { DxDrawerComponent } from 'devextreme-angular'; 4 | import { Subject } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'app-drawer', 8 | templateUrl: './drawer.component.html', 9 | styleUrls: ['./drawer.component.css'], 10 | standalone: false 11 | }) 12 | export class DrawerComponent { 13 | widgetGroup = 'drawer'; 14 | isExpanded = new Subject(); 15 | 16 | @ViewChild('drawer') drawer: DxDrawerComponent; 17 | 18 | drawerPanelContent = `

Lorem ipsum dolor sit amet, 19 | consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 20 | Penatibus et magnis dis parturient. Eget dolor morbi non arcu risus.

`; 21 | drawerContent = ` 22 |

Lorem ipsum dolor sit amet, 23 | consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 24 | Penatibus et magnis dis parturient. Eget dolor morbi non arcu risus. 25 | Tristique magna sit amet purus gravida quis blandit. 26 | Auctor urna nunc id cursus metus aliquam eleifend mi in. 27 | Tellus orci ac auctor augue mauris augue neque gravida. 28 | Nullam vehicula ipsum a arcu. 29 | Nullam ac tortor vitae purus faucibus ornare suspendisse sed nisi. 30 | Cursus in hac habitasse platea dictumst. Egestas dui id ornare arcu. 31 | Dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim.

32 |

Mauris rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar.

`; 33 | 34 | toolbarItems: any[] = [{ 35 | widget: 'dxButton', 36 | options: { 37 | icon: 'menu', 38 | stylingMode: 'text', 39 | onClick: (): void => { 40 | this.drawer.instance.toggle(); 41 | } 42 | }, 43 | location: 'before' 44 | }, { 45 | location: 'before', 46 | text: 'DRAWER' 47 | }]; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/preview/overlays/overlays.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; 2 | import { DxLoadPanelComponent, DxToastComponent, DxTooltipComponent } from 'devextreme-angular'; 3 | import { BehaviorSubject, Subscription } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'app-overlays', 7 | templateUrl: './overlays.component.html', 8 | styleUrls: ['./overlays.component.css'], 9 | standalone: false 10 | }) 11 | export class OverlaysComponent implements OnInit, OnDestroy { 12 | subscription: Subscription; 13 | 14 | @ViewChild('tooltip') tooltip: DxTooltipComponent; 15 | @ViewChild('toast') toast: DxToastComponent; 16 | @ViewChild('loadPanel') loadPanel: DxLoadPanelComponent; 17 | 18 | widgetGroup = 'overlays'; 19 | isExpanded = new BehaviorSubject(false); 20 | ofValue = '#loadpanel-target'; 21 | 22 | actionSheetData: any[] = [ 23 | { text: 'Command 1' }, 24 | { text: 'Command 2' }, 25 | { text: 'Command 3' }, 26 | { text: 'Command 4' } 27 | ]; 28 | 29 | toastInit(e: any): void { 30 | const toastContainer = this.isExpanded.getValue() ? '#target-block' : '#toast-block'; 31 | e.component.option('container', toastContainer); 32 | } 33 | 34 | hiding(e: any): void { 35 | e.cancel = true; 36 | } 37 | 38 | ngOnInit(): void { 39 | this.subscription = this.isExpanded.subscribe((value) => { 40 | const flexContainer = document.getElementsByTagName('app-overlays')[0].parentElement.parentElement; 41 | flexContainer.addEventListener('transitionend', () => { 42 | if(this.tooltip) this.tooltip.instance.repaint(); 43 | if(this.toast) this.toast.instance.repaint(); 44 | if(this.loadPanel) this.loadPanel.instance.repaint(); 45 | }, false); 46 | this.ofValue = value ? '#loadpanel-target-expanded' : '#loadpanel-target'; 47 | }); 48 | } 49 | 50 | ngOnDestroy(): void { 51 | this.subscription.unsubscribe(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/layouts/header/header.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | height: 74px; 4 | } 5 | 6 | .header { 7 | height: 70px; 8 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); 9 | display: flex; 10 | } 11 | 12 | .header.filled { 13 | background-color: #fff; 14 | box-shadow: 0 -8px 9px 4px #000; 15 | z-index: 3; 16 | position: relative; 17 | } 18 | 19 | .header > div { 20 | align-self: center; 21 | } 22 | 23 | .theme-group-header { 24 | display: flex; 25 | align-items: center; 26 | white-space: nowrap; 27 | gap: 8px; 28 | } 29 | 30 | .fluent-tm-info { 31 | cursor: pointer; 32 | height: 18px; 33 | } 34 | 35 | .theme-tm-tooltip { 36 | padding: 8px; 37 | } 38 | 39 | .logo { 40 | display: flex; 41 | gap: 6px; 42 | margin-left: 24px; 43 | cursor: pointer; 44 | } 45 | 46 | .product-name { 47 | font-size: 24px; 48 | line-height: 28px; 49 | font-weight: 700; 50 | font-stretch: 75%; 51 | font-family: 'Open Sans',sans-serif; 52 | color: #2190F6; 53 | } 54 | 55 | .header .separator { 56 | width: 1px; 57 | background-color: #e3e7ea; 58 | height: 40px; 59 | margin: 10px 20px; 60 | } 61 | 62 | .header .separator.first { 63 | margin-left: 0; 64 | } 65 | 66 | .header .separator.help { 67 | margin-left: auto; 68 | } 69 | 70 | .header .separator.help + app-help-button { 71 | margin-left: 0; 72 | } 73 | 74 | .switcher { 75 | margin-left: 40px; 76 | } 77 | 78 | .switcher-editor { 79 | border: none; 80 | min-width:200px; 81 | } 82 | 83 | .switcher-editor.dx-dropdowneditor-active { 84 | background-color: #e6e6e6; 85 | } 86 | 87 | :host ::ng-deep .switcher-editor .dx-texteditor-input { 88 | background: transparent; 89 | } 90 | 91 | .switcher-template { 92 | display: flex; 93 | } 94 | 95 | .switcher-template .switcher-icon { 96 | padding: 3px; 97 | align-self: center; 98 | align-items: center; 99 | display: flex; 100 | } 101 | 102 | .switcher-template .switcher-icon.editor { 103 | margin-left: 11px; 104 | display: flex; 105 | align-items: center; 106 | } 107 | 108 | .switcher-template .item-template-text { 109 | align-self: center; 110 | padding-left: 10px; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/app/left-menu/editor/editor.component.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-magic-numbers */ 2 | import { Component, Input } from '@angular/core'; 3 | import { SafeHtml } from '@angular/platform-browser'; 4 | import { Router } from '@angular/router'; 5 | 6 | import { MetadataRepositoryService } from '../../meta-repository.service'; 7 | import { NamesService } from '../../names.service'; 8 | import { MetaItem } from '../../types/meta-item'; 9 | import { AnalyticsEventsService } from '../../analytics-events.service'; 10 | 11 | @Component({ 12 | selector: 'app-editor', 13 | templateUrl: './editor.component.html', 14 | styleUrls: ['./editor.component.css'], 15 | standalone: false 16 | }) 17 | export class EditorComponent { 18 | @Input('item') item: MetaItem; 19 | 20 | @Input() searchText = ''; 21 | 22 | constructor(private names: NamesService, 23 | private metaRepository: MetadataRepositoryService, 24 | private analyticsEventsService: AnalyticsEventsService, 25 | private router: Router 26 | ) { } 27 | 28 | private isValueCanBePixels() { 29 | return this.item.Key.endsWith('border-radius'); 30 | } 31 | 32 | private isPositiveNumber(value) { 33 | return /^[1-9]\d*$/.test(value); 34 | } 35 | 36 | highlight(text: string): SafeHtml { 37 | return this.names.getHighlightedForLeftMenuName(text, this.searchText); 38 | } 39 | 40 | getSettingsName(): string { 41 | const routeParts = this.router.url.split('/').filter((part) => part !== ''); 42 | return routeParts[routeParts.length - 1]; 43 | } 44 | 45 | valueTextChanged(e: { value: string, component: any }, key: string): void { 46 | this.analyticsEventsService.trackEvent( 47 | 'TB: Settings', 48 | `Tb ${this.getSettingsName()} - ${key}` 49 | ); 50 | if(this.isValueCanBePixels() && this.isPositiveNumber(e.value)) { 51 | e.value = e.value + 'px'; 52 | } 53 | 54 | this.metaRepository.updateSingleVariable(e, key); 55 | } 56 | 57 | valueChanged(e: any, key: string): void { 58 | this.analyticsEventsService.trackEvent( 59 | 'TB: Settings', 60 | `Tb ${this.getSettingsName()} - ${key}` 61 | ); 62 | this.metaRepository.updateSingleVariable(e, key); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/app/preview/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-preview-index', 6 | templateUrl: './index.component.html', 7 | styleUrls: ['./index.component.css'], 8 | standalone: false 9 | }) 10 | export class PreviewIndexComponent { 11 | isStylesReady = false; 12 | viewName: string; 13 | themeName: string; 14 | themeSize: string; 15 | widgetName: string; 16 | typographyClass: string; 17 | 18 | constructor(private router: Router) { 19 | const VIEW_POSITION = 1; 20 | const THEME_POSITION = 2; 21 | const urlParts = this.router.url.split('/'); 22 | this.viewName = urlParts[VIEW_POSITION]; 23 | this.themeName = urlParts[THEME_POSITION]; 24 | this.typographyClass = 'dx-theme-' + this.themeName + '-typography'; 25 | } 26 | 27 | receiveMessage(e: any): void { 28 | if(e.data.css) { 29 | this.addHeadStyles(e.data.css); 30 | this.themeSize = e.data.themeSize; 31 | } 32 | 33 | if(e.data.widget) { 34 | this.widgetName = e.data.widget; 35 | } 36 | } 37 | 38 | addHeadStyles(css: string): void { 39 | const head = document.getElementsByTagName('head')[0]; 40 | const style = document.createElement('style'); 41 | const DYNAMIC_STYLES_ID = 'dynamic-styles'; 42 | 43 | const dynamicStylesElement = document.getElementById(DYNAMIC_STYLES_ID); 44 | 45 | if(dynamicStylesElement) { 46 | dynamicStylesElement.parentNode.removeChild(dynamicStylesElement); 47 | } 48 | 49 | style.type = 'text/css'; 50 | style.id = DYNAMIC_STYLES_ID; 51 | 52 | css = css 53 | .replace(/icons\/dxicons/gi, 'content/css/icons/dxicons') 54 | .replace(/fonts\/Roboto/gi, 'content/css/fonts/Roboto'); 55 | 56 | style.appendChild(document.createTextNode(css)); 57 | head.appendChild(style); 58 | 59 | this.isStylesReady = true; 60 | } 61 | 62 | ngAfterViewInit(): void { 63 | const messageListener = this.receiveMessage.bind(this); 64 | window.removeEventListener('message', messageListener); 65 | window.addEventListener('message', messageListener, false); 66 | window.parent.postMessage({ hideLoading: true }, window.parent.location.href); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/preview/form/form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; 2 | import { DxFormComponent } from 'devextreme-angular'; 3 | import { Subject, Subscription } from 'rxjs'; 4 | import { Properties } from 'devextreme/ui/form'; 5 | 6 | @Component({ 7 | selector: 'app-form', 8 | templateUrl: './form.component.html', 9 | styleUrls: ['./form.component.css'], 10 | standalone: false 11 | }) 12 | export class FormComponent implements OnInit, OnDestroy { 13 | widgetGroup = 'form'; 14 | isExpanded = new Subject(); 15 | subscription: Subscription; 16 | 17 | @ViewChild('form') form: DxFormComponent; 18 | 19 | collapsedOptions: Properties = { 20 | labelLocation: 'top', 21 | formData: { 22 | 'ID': 1, 23 | 'Full Name': 'John Heart' 24 | }, 25 | items: [ 26 | { 27 | itemType: 'group', 28 | caption: 'Personal Data', 29 | items: [{dataField: 'ID'}, {dataField: 'Full Name'}] 30 | } 31 | ] 32 | }; 33 | 34 | expandedOptions: Properties = { 35 | labelLocation: 'top', 36 | formData: { 37 | Address: '351 S Hill St.', 38 | City: 'Los Angeles', 39 | State: 'CA', 40 | FirstName: 'John', 41 | LastName: 'Heart' 42 | }, 43 | items: [ 44 | { 45 | itemType: 'group', 46 | caption: 'Personal Data', 47 | items: [{dataField: 'FirstName'}, {dataField: 'LastName'}] 48 | }, 49 | { 50 | itemType: 'group', 51 | colCount: 2, 52 | caption: 'Home Address', 53 | items: [ 54 | {dataField: 'Address'}, 55 | { 56 | dataField: 'City', 57 | label: { 58 | text: 'City, State' 59 | } 60 | } 61 | ] 62 | } 63 | ] 64 | }; 65 | 66 | ngOnInit(): void { 67 | this.subscription = this.isExpanded.subscribe((expanded) => { 68 | this.form.instance.option(expanded ? this.expandedOptions : this.collapsedOptions); 69 | this.form.instance.updateDimensions(); 70 | }); 71 | } 72 | 73 | ngOnDestroy(): void { 74 | this.subscription.unsubscribe(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/app/preview/treeview/treeview.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'app-treeview', 6 | templateUrl: './treeview.component.html', 7 | styleUrls: ['./treeview.component.css'], 8 | standalone: false 9 | }) 10 | export class TreeviewComponent { 11 | widgetGroup = 'treeview'; 12 | isExpanded = new Subject(); 13 | 14 | treeViewItems: any[] = [ 15 | { 16 | id: 1, 17 | text: '.NET', 18 | expanded: true, 19 | items: [ 20 | { 21 | id: 11, 22 | text: 'Individual Platforms', 23 | items: [ 24 | { id: 111, text: 'WinForms' }, 25 | { id: 112, text: 'ASP.NET' }, 26 | { id: 113, text: 'MVC' }, 27 | { id: 114, text: 'WPF' }, 28 | { id: 115, text: 'Silverlight' }, 29 | { id: 116, text: 'Windows 8 XAML' } 30 | ] 31 | }, 32 | { 33 | id: 12, 34 | text: 'Frameworks', 35 | items: [ 36 | { id: 121, text: 'eXpressApp Framework' } 37 | ] 38 | }, 39 | { 40 | id: 13, 41 | text: 'Code-Debug-Refactor', 42 | items: [ 43 | { id: 131, text: 'CodeRush for Visual Studio' } 44 | ] 45 | }, 46 | { 47 | id: 14, 48 | text: 'Mobile (HTML JS)', 49 | items: [ 50 | { id: 141, text: 'DevExtreme' } 51 | ] 52 | }, 53 | { 54 | id: 15, 55 | text: 'Cross-Platform', 56 | items: [ 57 | { id: 151, text: 'Reporting' }, 58 | { id: 152, text: 'Document Generation' } 59 | ] 60 | }, 61 | { 62 | id: 16, 63 | text: 'Enterprise Tools', 64 | items: [ 65 | { id: 161, text: 'Report Server' }, 66 | { id: 162, text: 'Analytics Dashboard' } 67 | ] 68 | } 69 | ] 70 | } 71 | ]; 72 | } 73 | -------------------------------------------------------------------------------- /src/app/preview/chat/chat.component.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-magic-numbers */ 2 | import { Component, OnDestroy } from '@angular/core'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'app-chat', 7 | templateUrl: './chat.component.html', 8 | styleUrls: ['./chat.component.css'], 9 | standalone: false 10 | }) 11 | export class ChatComponent implements OnDestroy { 12 | isExpanded = new BehaviorSubject(false); 13 | widgetGroup = 'chat'; 14 | 15 | showTyping = true; 16 | showAlert = true; 17 | date = new Date('October 11, 2024, 11:51:00'); 18 | 19 | johnDoe = { 20 | id: 'c94c0e76-fb49-4b9b-8f07-9f93ed93b4f3', 21 | name: 'John Doe' 22 | }; 23 | 24 | supportAgent = { 25 | id: 'd16d1a4c-5c67-4e20-b70e-2991c22747c3', 26 | name: 'Support Agent' 27 | }; 28 | 29 | previewMessages = [ 30 | { 31 | timestamp: new Date().setTime(this.date.getTime()), 32 | author: this.supportAgent, 33 | text: 'Hello, John! How can I assist you today?' 34 | }, 35 | { 36 | timestamp: new Date().setTime(this.date.getTime() + 2 * 60000), 37 | author: this.johnDoe, 38 | text: 'Hi, I\'m having trouble accessing my account.' 39 | } 40 | ]; 41 | 42 | messages = [ 43 | { 44 | timestamp: new Date().setTime(this.date.getTime()), 45 | author: this.supportAgent, 46 | text: 'Hello, John! How can I assist you today?' 47 | }, 48 | { 49 | timestamp: new Date().setTime(this.date.getTime() + 2 * 60000), 50 | author: this.johnDoe, 51 | text: 'Hi, I\'m having trouble accessing my account.\nIt says my password is incorrect.' 52 | }, 53 | { 54 | timestamp: new Date().setTime(this.date.getTime() + 2 * 60000), 55 | author: this.johnDoe, 56 | text: 'My UserID is john.doe1357' 57 | }, 58 | { 59 | timestamp: new Date().setTime(this.date.getTime() + 10 * 60000), 60 | author: this.supportAgent, 61 | text: '✅ Instructions to restore access have been sent to the email address registered to your account.' 62 | } 63 | ]; 64 | 65 | toogleTyping() { 66 | this.showTyping = !this.showTyping; 67 | } 68 | 69 | toogleErrors() { 70 | this.showAlert = !this.showAlert; 71 | } 72 | 73 | ngOnDestroy(): void { 74 | this.isExpanded.unsubscribe(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/app/layouts/footer/footer.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .licensing { 6 | padding-left: 16px; 7 | } 8 | 9 | .footer { 10 | background-color: #667788; 11 | bottom: 0; 12 | /* position: absolute; */ 13 | width: 100%; 14 | padding: 24px 48px; 15 | box-sizing: border-box; 16 | font-size: 12px; 17 | color: white; 18 | /* z-index: 3; */ 19 | } 20 | 21 | .footer.content-width { 22 | padding-top: 0; 23 | padding-bottom: 0; 24 | } 25 | 26 | .footer-container { 27 | display: flex; 28 | justify-content: space-between; 29 | gap: 16px; 30 | } 31 | 32 | .footer-about { 33 | display: flex; 34 | gap: 40px; 35 | } 36 | 37 | .footer-text { 38 | margin: 0 0 16px 0; 39 | font-size: 12px; 40 | line-height: 14px; 41 | } 42 | 43 | .footer-text a { 44 | color: white; 45 | } 46 | 47 | .footer-text a:hover { 48 | opacity: .8; 49 | } 50 | 51 | .footer-links { 52 | opacity: .5; 53 | padding-top: 16px; 54 | } 55 | 56 | .footer-links a { 57 | color: white; 58 | } 59 | 60 | .footer-links a:hover { 61 | text-decoration: underline; 62 | } 63 | 64 | .footer-socials { 65 | display: flex; 66 | align-items: center; 67 | } 68 | 69 | .footer-socials-title { 70 | color: white; 71 | margin-bottom: 12px; 72 | text-align: right; 73 | } 74 | 75 | .footer-socials-links { 76 | display: flex; 77 | gap: 8px; 78 | } 79 | 80 | .footer-socials-link svg { 81 | width: 32px; 82 | height: 32px; 83 | } 84 | 85 | .footer-socials-link svg:hover { 86 | opacity: 0.6; 87 | } 88 | 89 | @media only screen and (max-width: 1400px) { 90 | .footer { 91 | padding: 32px 0; 92 | height: auto; 93 | font-size: 12px; 94 | } 95 | 96 | .footer-about { 97 | flex-direction: column; 98 | align-items: center; 99 | } 100 | 101 | .footer-container { 102 | flex-direction: column; 103 | } 104 | 105 | .footer-about { 106 | flex-direction: column; 107 | align-items: center; 108 | } 109 | 110 | .footer-text, .footer-links { 111 | text-align: center; 112 | } 113 | 114 | .footer-text { 115 | margin-bottom: 16px; 116 | } 117 | 118 | .footer-socials { 119 | justify-content: center; 120 | } 121 | 122 | .footer-socials-title { 123 | display: none; 124 | } 125 | 126 | .footer-socials-links { 127 | justify-content: center; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/app/preview/scrollview/scrollview.component.html: -------------------------------------------------------------------------------- 1 | 8 |
9 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 10 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 11 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

12 | 13 |

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, 14 | totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. 15 | Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. 16 | Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. 17 | Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?

18 | 19 |

At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, 20 | similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, 21 | omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. 22 |

23 |
24 |
25 | -------------------------------------------------------------------------------- /src/app/master/master.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | import { AppLayoutComponent } from '../layouts/app-layout/app-layout.component'; 5 | import { MetadataRepositoryService } from '../meta-repository.service'; 6 | import { ThemeConfig } from '../types/theme'; 7 | 8 | @Component({ 9 | templateUrl: './master.component.html', 10 | styleUrls: ['./master.component.css'], 11 | standalone: false 12 | }) 13 | 14 | export class MasterComponent implements OnInit, OnDestroy { 15 | showIframe = false; 16 | themeName: string; 17 | colorScheme: string; 18 | isThemeCompact: boolean; 19 | themeSize: string; 20 | themesList: any[]; 21 | subscription: Subscription; 22 | 23 | constructor( 24 | private route: ActivatedRoute, 25 | private appLayoutComponent: AppLayoutComponent, 26 | private metadataService: MetadataRepositoryService 27 | ) { 28 | this.route.params.subscribe((params) => { 29 | this.themeName = params['theme'] || ''; 30 | this.colorScheme = params['color-scheme'] || ''; 31 | this.isThemeCompact = this.colorScheme.includes('compact'); 32 | this.themeSize = this.isThemeCompact ? 'compact' : 'normal'; 33 | 34 | this.changeContent(); 35 | }); 36 | } 37 | 38 | changeContent(): void { 39 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 40 | this.metadataService.getThemes().then((dataThemes: ThemeConfig[]) => { 41 | const themes = dataThemes.filter((t) => t.name === this.themeName); 42 | 43 | const themesBySize = themes.filter( 44 | (t) => this.isThemeCompact ? 45 | t.group.includes('Compact') : 46 | !t.group.includes('Compact')); 47 | if(this.themeName === 'material') { 48 | // getting sorted list by background color 49 | this.themesList = [].concat( 50 | themesBySize.filter((t) => t.text.includes('Light')), 51 | themesBySize.filter((t) => t.text.includes('Dark')) 52 | ); 53 | } else { 54 | this.themesList = themesBySize; 55 | } 56 | }); 57 | } 58 | 59 | ngOnInit(): void { 60 | this.subscription = this.appLayoutComponent.animationDone.subscribe((value) => { 61 | this.showIframe = value; 62 | }); 63 | } 64 | 65 | ngOnDestroy(): void { 66 | this.subscription.unsubscribe(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/preview/stepper/stepper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { DxStepperTypes } from 'devextreme-angular/ui/stepper'; 4 | import { DxButtonGroupTypes } from 'devextreme-angular/ui/button-group'; 5 | import { DxSelectBoxTypes } from 'devextreme-angular/ui/select-box'; 6 | 7 | @Component({ 8 | selector: 'app-stepper', 9 | templateUrl: './stepper.component.html', 10 | styleUrls: ['./stepper.component.css'], 11 | standalone: false 12 | }) 13 | export class StepperComponent implements OnDestroy { 14 | isExpanded = new BehaviorSubject(false); 15 | widgetGroup = 'stepper'; 16 | 17 | showLabel = true; 18 | showOptionalText = true; 19 | disabledSteps = false; 20 | 21 | stepperTypes = [ 22 | {text: 'Icons', showIcons: true}, 23 | {text: 'Text', showIcons: false} 24 | ]; 25 | 26 | isValidOptions = [ 27 | {id: 1, text: 'Undefined', value: undefined}, 28 | {id: 2, text: 'True', value: true}, 29 | {id: 3, text: 'False', value: false} 30 | ]; 31 | 32 | steps: DxStepperTypes.Item[] = [ 33 | { 34 | text: 'A', 35 | label: 'Cart', 36 | icon: 'cart' 37 | }, 38 | { 39 | text: 'B', 40 | label: 'Shipping Info', 41 | icon: 'clipboardtasklist' 42 | }, 43 | { 44 | text: 'C', 45 | label: 'Promo Code', 46 | icon: 'gift', 47 | optional: true 48 | }, 49 | { 50 | text: 'D', 51 | label: 'Checkout', 52 | icon: 'packagebox' 53 | }, 54 | { 55 | text: 'E', 56 | label: 'Ordered', 57 | icon: 'checkmarkcircle' 58 | } 59 | ]; 60 | 61 | selectedOption = this.isValidOptions[0]; 62 | showIcons = this.stepperTypes[0].showIcons; 63 | 64 | toggleLabel() { 65 | this.showLabel = !this.showLabel; 66 | } 67 | 68 | toggleOptionalText() { 69 | this.showOptionalText = !this.showOptionalText; 70 | } 71 | 72 | toggleStepperType(e: DxButtonGroupTypes.ItemClickEvent) { 73 | this.showIcons = e.itemData.showIcons; 74 | } 75 | 76 | toggleValidation(e: DxSelectBoxTypes.ValueChangedEvent) { 77 | this.selectedOption = e.value; 78 | this.steps.forEach((item) => item.isValid = this.selectedOption.value); 79 | } 80 | 81 | toggleDisabledSteps() { 82 | this.disabledSteps = !this.disabledSteps; 83 | this.steps.forEach((item) => item.disabled = this.disabledSteps); 84 | } 85 | 86 | ngOnDestroy(): void { 87 | this.isExpanded.unsubscribe(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/app/layouts/bootstrap-uploader/bootstrap-uploader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { alert } from 'devextreme/ui/dialog'; 3 | import { NotificationsService } from 'src/app/notification.service'; 4 | import { mutePromise } from 'src/app/promise-helper'; 5 | import { ImportService } from '../../import.service'; 6 | 7 | @Component({ 8 | selector: 'app-bootstrap-uploader', 9 | templateUrl: './bootstrap-uploader.component.html', 10 | styleUrls: ['./bootstrap-uploader.component.css'], 11 | standalone: false 12 | }) 13 | export class BootstrapUploaderComponent { 14 | @Input() accept: string; 15 | @Input() buttonText: string; 16 | @Input() type: string; 17 | @Input() version: number; 18 | @Input() height: number; 19 | @Input() labelText = 'or Drop the file here'; 20 | @Output() imported = new EventEmitter(); 21 | 22 | constructor( 23 | private importService: ImportService, 24 | private notifications: NotificationsService 25 | ) {} 26 | 27 | uploaded(e): void { 28 | const file = e.value[0]; 29 | if(file) { 30 | const fileReader = new FileReader(); 31 | fileReader.onload = (): void => { 32 | let meta: string; 33 | if(typeof fileReader.result === 'string') { 34 | meta = fileReader.result; 35 | } else { 36 | throw new Error('FileReader.readAsText set FileReader.result to a value which is not a string'); 37 | } 38 | 39 | const isWizard = location.pathname.startsWith('/import') || location.pathname.startsWith('/ThemeBuilder'); 40 | if(this.version) { 41 | this.importService.importBootstrapVariables(meta, this.version, 'advanced').catch(() => { 42 | const message = 'ThemeBuilder couldn\'t create a theme. The file may be corrupt or have an unsupported format.'; 43 | 44 | if(isWizard) { 45 | this.notifications.error(message); 46 | } else { 47 | mutePromise(alert(message, 'Import Error')); 48 | } 49 | }); 50 | } else { 51 | this.importService.importMetadata(meta, 'advanced').catch(() => { 52 | const message = 'Metadata has a wrong format.'; 53 | 54 | if(isWizard) { 55 | this.notifications.error(message); 56 | } else { 57 | mutePromise(alert(message, 'Error')); 58 | } 59 | }); 60 | } 61 | 62 | this.imported.emit(); 63 | e.component.reset(); 64 | }; 65 | fileReader.readAsText(file); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/preview/editors/editors.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: inline-block; 3 | width: 100%; 4 | } 5 | 6 | :host ::ng-deep .dx-checkbox { 7 | vertical-align: middle; 8 | margin-right: 10px; 9 | } 10 | 11 | :host ::ng-deep .dx-texteditor { 12 | display: inline-block; 13 | vertical-align: middle; 14 | margin-right: 10px; 15 | } 16 | 17 | .collapsed-block { 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | .editors { 23 | margin-bottom: 30px; 24 | flex-direction: column; 25 | } 26 | 27 | .editors .block { 28 | display: inline-flex; 29 | flex-wrap: wrap; 30 | } 31 | 32 | .editors .block .field { 33 | margin: 0 10px 20px 0; 34 | flex: 1; 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: space-between; 38 | } 39 | 40 | .editors .block .field.daterangebox { 41 | display: block; 42 | max-width: initial; 43 | margin-right: 0; 44 | } 45 | 46 | ::ng-deep .dx-theme-material-typography .editors .block .field { 47 | min-height: 60px; 48 | } 49 | 50 | :host ::ng-deep .editors .dx-texteditor { 51 | width: 100%; 52 | } 53 | 54 | .editors .block .field .label { 55 | color: #999999; 56 | margin-bottom: 5px; 57 | font-size: 14px; 58 | } 59 | 60 | .block { 61 | display: inline-block; 62 | vertical-align: top; 63 | } 64 | 65 | .block .header { 66 | color: #999999; 67 | font-weight: bold; 68 | margin-bottom: 20px; 69 | font-size: 14px; 70 | } 71 | 72 | .calendar > *, 73 | .daterangebox > * { 74 | display: flex; 75 | width: auto; 76 | margin-bottom: 10px; 77 | } 78 | 79 | .checkbox > * { 80 | display: flex;; 81 | } 82 | 83 | ::ng-deep .dx-radiogroup .dx-radiobutton { 84 | margin-bottom: 0; 85 | margin-top: 0; 86 | } 87 | 88 | .daterangebox { 89 | min-width: 270px; 90 | } 91 | 92 | .calendar { 93 | margin-right: 30px; 94 | min-width: 280px; 95 | } 96 | 97 | :host ::ng-deep .checkbox .dx-checkbox { 98 | margin-right: 0; 99 | } 100 | 101 | .switch { 102 | width: 120px; 103 | } 104 | 105 | .switch .field { 106 | display: flex; 107 | gap: 10px; 108 | } 109 | 110 | .switch .value { 111 | display: flex; 112 | } 113 | 114 | .switchers { 115 | gap: 48px; 116 | } 117 | 118 | .component-container { 119 | height: 32px; 120 | display: flex; 121 | align-items: center; 122 | } 123 | 124 | .fileuploader { 125 | width: 360px; 126 | } 127 | 128 | .component-section { 129 | display: flex; 130 | gap: 48px; 131 | } 132 | 133 | .component-section .block { 134 | flex: 1; 135 | display: flex; 136 | } 137 | 138 | .grouped-fields { 139 | display: flex; 140 | width: 100%; 141 | } 142 | 143 | .editors .grouped-fields .field.validation { 144 | flex-basis: 32%; 145 | } 146 | 147 | .editors .grouped-fields .field.daterangebox { 148 | flex-basis: 66%; 149 | } 150 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | schedule: 19 | - cron: '0 0 * * 6' 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'javascript' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v4 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v2 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | 50 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 51 | # queries: security-extended,security-and-quality 52 | 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 61 | 62 | # If the Autobuild fails above, remove it and uncomment the following three lines. 63 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 64 | 65 | # - run: | 66 | # echo "Run, Build Application using script" 67 | # ./location_of_script_within_repo/buildscript.sh 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | with: 72 | category: "/language:${{matrix.language}}" 73 | -------------------------------------------------------------------------------- /src/app/master/master.component.css: -------------------------------------------------------------------------------- 1 | .master { 2 | position: relative; 3 | display: flex; 4 | } 5 | 6 | .theme-selection { 7 | width: 126px; 8 | padding: 12px 24px; 9 | } 10 | 11 | .theme-selection > div { 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .arrow-back { 17 | margin-right: 12px; 18 | height: 18px; 19 | } 20 | 21 | :host ::ng-deep .master .loading { 22 | width: calc(100% - 750px); 23 | left: 750px; /*!*/ 24 | } 25 | 26 | .master .section { 27 | text-align: left; 28 | box-sizing: border-box; 29 | flex-shrink: 0; 30 | flex-basis: 750px; 31 | flex-grow: 0; 32 | } 33 | 34 | .master .section.left { 35 | position: relative; 36 | width: 750px; 37 | padding-top: 56px; 38 | } 39 | 40 | .master .section.right { 41 | display: block; 42 | flex-grow: 1; 43 | flex-shrink: 1; 44 | margin-top: 56px; 45 | margin-bottom: 118px; 46 | height: 820px; 47 | min-width: 1160px; 48 | background-color: #fff; 49 | box-shadow: 0 4px 12px #0000004d; 50 | z-index: 1; 51 | border-radius: 8px; 52 | } 53 | 54 | .master .section.right > * { 55 | height: 100%; 56 | border-radius: 8px; 57 | } 58 | 59 | .master .title { 60 | font-size: 32px; 61 | font-weight: 600; 62 | text-align: left; 63 | margin-left: 48px; 64 | display: inline-block; 65 | } 66 | 67 | .master .title span { 68 | margin-left: 2px; 69 | } 70 | 71 | .master .title-link { 72 | font-size: 14px; 73 | font-weight: 400; 74 | margin: 0 0 16px 48px; 75 | } 76 | .master .title-link:hover { 77 | opacity: 1; 78 | } 79 | 80 | .master .schemes-container { 81 | width: 180px; 82 | position: absolute; 83 | left: 48px; 84 | right: 300px; 85 | margin-left: 0; 86 | margin-right: 0; 87 | margin-top: 48px; 88 | } 89 | 90 | .schemes .color-name { 91 | display: inline-block; 92 | vertical-align: middle; 93 | } 94 | 95 | .schemes li { 96 | border-radius: 28px; 97 | padding: 5px 5px; 98 | text-align: left; 99 | color: #505050; 100 | cursor: pointer; 101 | margin-bottom: 2px; 102 | } 103 | 104 | .schemes li:hover { 105 | background-color: #d9dde0; 106 | } 107 | 108 | .schemes li.active { 109 | background-color: #e2e5e7; 110 | } 111 | 112 | .base-theming { 113 | width: 310px; 114 | float: right; 115 | /* margin-top: -95px; */ 116 | } 117 | 118 | 119 | 120 | @media (min-width: 1950px) { 121 | .master .section.right { 122 | margin-right: 48px; 123 | } 124 | } 125 | 126 | @media (max-width: 1000px) { 127 | .master .section.left { 128 | flex-basis: 700px; 129 | } 130 | } 131 | 132 | @media only screen and (max-width: 768px) { 133 | .master { 134 | flex-direction: column; 135 | } 136 | .master .section.right { 137 | min-width: auto; 138 | width: auto; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/app/preview/stepper/stepper.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 13 | 20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 | 34 |
35 | 36 | 44 |
45 | 50 | 55 |
56 | 61 |
62 |
63 | 71 | 80 | 81 |
82 |
83 | -------------------------------------------------------------------------------- /src/app/preview/wizard/wizard.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 32 |
33 |
Assigned: {{item.key}}
34 |
35 |
36 |
37 |
38 |
39 | 46 | 51 | 54 | 59 | 60 | 64 | 67 | 71 | 76 | 77 |
78 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AppLayoutComponent } from './layouts/app-layout/app-layout.component'; 5 | import { PreviewLayoutComponent } from './layouts/preview-layout/preview-layout.component'; 6 | 7 | import { IndexComponent } from './index/index.component'; 8 | import { MasterComponent } from './master/master.component'; 9 | 10 | import { AdvancedComponent } from './advanced/advanced.component'; 11 | import { ImportBootstrapComponent } from './import/import-bootstrap/import-bootstrap.component'; 12 | import { ImportMetaComponent } from './import/import-meta/import-meta.component'; 13 | import { PreviewIndexComponent } from './preview/index.component'; 14 | import { RouteId } from './types/route-id'; 15 | 16 | const routes: Routes = [ 17 | { 18 | path: '', 19 | component: AppLayoutComponent, 20 | children: [ 21 | { path: '', component: IndexComponent, data: { routeId: RouteId.MAIN_INDEX, docHash: '' } }, 22 | { path: 'master', component: IndexComponent, data: { routeId: RouteId.NESTED_INDEX, docHash: '#Get_Started/Create_a_New_Theme' } }, 23 | { path: 'import', component: IndexComponent, data: { routeId: RouteId.NESTED_INDEX, docHash: '#Get_Started/Import_an_Existing_Theme' } }, 24 | { path: 'import/bootstrap', component: ImportBootstrapComponent, data: { routeId: RouteId.IMPORT_OR_MASTER, docHash: '#Get_Started/Import_an_Existing_Theme' } }, 25 | { path: 'import/meta', component: ImportMetaComponent, data: { routeId: RouteId.IMPORT_OR_MASTER, docHash: '#Get_Started/Import_an_Existing_Theme' } }, 26 | { path: 'master/:theme/:color-scheme', component: MasterComponent, data: { routeId: RouteId.IMPORT_OR_MASTER, docHash: '#Get_Started/Create_a_New_Theme' } }, 27 | { path: 'advanced', redirectTo: '/advanced/generic/light/base.common/', pathMatch: 'full' }, 28 | { path: 'advanced/:theme/:color-scheme', redirectTo: 'advanced/:theme/:color-scheme/base.common/', pathMatch: 'full' }, 29 | { path: 'advanced/:theme/:color-scheme/grids', redirectTo: '/advanced/:theme/:color-scheme/grids/datagrid', pathMatch: 'full' }, 30 | { path: 'advanced/:theme/:color-scheme/datagrid', redirectTo: '/advanced/:theme/:color-scheme/grids/datagrid', pathMatch: 'full' }, 31 | { path: 'advanced/:theme/:color-scheme/treelist', redirectTo: '/advanced/:theme/:color-scheme/grids/treelist', pathMatch: 'full' }, 32 | { path: 'advanced/:theme/:color-scheme/:widget', redirectTo: '/advanced/:theme/:color-scheme/:widget/', pathMatch: 'full'}, 33 | { path: 'advanced/:theme/:color-scheme/:group/:widget', component: AdvancedComponent, data: { routeId: RouteId.GROUPED_WIDGET, docHash: '#Customize_the_Theme' } } 34 | ] 35 | }, 36 | { 37 | path: '', 38 | component: PreviewLayoutComponent, 39 | children: [ 40 | { path: 'preview/:theme', component: PreviewIndexComponent }, 41 | { path: 'wizard/:theme', component: PreviewIndexComponent } 42 | ] 43 | }, 44 | { path: '**', redirectTo: '' } 45 | ]; 46 | 47 | @NgModule({ 48 | imports: [RouterModule.forRoot(routes)], 49 | exports: [RouterModule] 50 | }) 51 | 52 | export class AppRoutingModule { } 53 | -------------------------------------------------------------------------------- /src/app/layouts/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnDestroy, OnInit, Pipe, PipeTransform } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import DataSource from 'devextreme/data/data_source'; 4 | import { confirm } from 'devextreme/ui/dialog'; 5 | import { Subscription } from 'rxjs'; 6 | import { MetadataRepositoryService } from '../../meta-repository.service'; 7 | 8 | @Pipe({ 9 | name: 'includes', 10 | standalone: false 11 | }) 12 | export class IncludesPipe implements PipeTransform { 13 | transform(value: string, str: string): boolean { 14 | return value.toLowerCase().includes(str.toLowerCase()); 15 | } 16 | } 17 | 18 | @Component({ 19 | selector: 'app-header', 20 | templateUrl: './header.component.html', 21 | styleUrls: ['./header.component.css'], 22 | providers: [IncludesPipe], 23 | standalone: false 24 | }) 25 | export class HeaderComponent implements OnInit, OnDestroy { 26 | @Input() switchEnabled: boolean; 27 | switcherData: DataSource; 28 | subscription: Subscription; 29 | currentThemeId: number; 30 | 31 | constructor(private metadataService: MetadataRepositoryService, private route: Router) {} 32 | 33 | themeChanged(e): void { 34 | if(e.component.canceled) { 35 | e.component.canceled = false; 36 | return; 37 | } 38 | 39 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 40 | this.metadataService.getThemes().then((themes) => { 41 | const newTheme = themes.filter((i) => i.themeId === e.value); 42 | 43 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 44 | confirm('Are you sure you want to change the base theme? All changes will be lost.', 'ThemeBuilder').then((confirmed) => { 45 | if(confirmed && newTheme.length) { 46 | const theme = newTheme[0].name; 47 | const colorScheme = newTheme[0].colorScheme; 48 | const urlParts = this.route.url.split('/'); 49 | const routeWidgetPosition = 4; 50 | const widget = urlParts[routeWidgetPosition]; 51 | this.route.navigate(['advanced', theme, colorScheme, widget]); 52 | } else { 53 | this.currentThemeId = e.previousValue; 54 | e.component.canceled = true; 55 | } 56 | }); 57 | }); 58 | } 59 | 60 | ngOnInit(): void { 61 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 62 | this.metadataService.getThemes().then((themes) => { 63 | this.switcherData = new DataSource({ 64 | store: themes, 65 | key: 'themeId', 66 | group: 'group' 67 | }); 68 | 69 | this.subscription = this.metadataService.css.subscribe(() => { 70 | const currentTheme = themes.filter((i) => i.name === this.metadataService.theme.name && i.colorScheme === this.metadataService.theme.colorScheme); 71 | 72 | if(currentTheme.length) { 73 | this.currentThemeId = currentTheme[0].themeId; 74 | } 75 | }); 76 | }); 77 | } 78 | 79 | ngOnDestroy(): void { 80 | if(this.subscription) this.subscription.unsubscribe(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/app/left-menu/main/left-menu.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 |
13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 |
Your search did not match any results.
30 |
31 |
32 | 33 |
34 | {{ workAreaName }} 35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | 51 |
52 |
53 | 54 | 64 | 65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /src/app/iframe/iframe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; 2 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | import { BehaviorSubject, Subscription } from 'rxjs'; 5 | import { LoadingService } from '../loading.service'; 6 | import { MetadataRepositoryService } from '../meta-repository.service'; 7 | 8 | @Component({ 9 | selector: 'app-iframe', 10 | templateUrl: './iframe.component.html', 11 | styleUrls: ['./iframe.component.css'], 12 | standalone: false 13 | }) 14 | export class IframeComponent implements OnDestroy, OnInit { 15 | @ViewChild('iframe') iframe: ElementRef; 16 | 17 | url: string; 18 | iframeUrl: SafeResourceUrl; 19 | cssSubscription: Subscription; 20 | widgetSubscription: Subscription; 21 | theme: string; 22 | widgetName = new BehaviorSubject(''); 23 | 24 | constructor( 25 | private route: ActivatedRoute, 26 | private router: Router, 27 | private sanitizer: DomSanitizer, 28 | private metadataService: MetadataRepositoryService, 29 | private loading: LoadingService) { 30 | this.route.params.subscribe((params) => { 31 | const widget = params['widget'] || params['group']; 32 | if(this.widgetName.getValue() !== widget) { 33 | this.widgetName.next(widget); 34 | } 35 | if(this.theme !== params['theme']) { 36 | this.loading.show(); 37 | this.theme = params['theme']; 38 | this.url = document.getElementsByTagName('base')[0].href + (widget ? 'preview' : 'wizard') + '/' + this.theme; 39 | this.iframeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.url); 40 | } 41 | }); 42 | } 43 | 44 | receiveMessage(e): void { 45 | if(e.data.widget) { 46 | this.router.navigate(['/advanced', this.metadataService.theme.name, this.metadataService.theme.colorScheme, e.data.widget]); 47 | } 48 | 49 | if(e.data.hideLoading) { 50 | this.onIframeLoad(); 51 | this.loading.hide(); 52 | } 53 | } 54 | 55 | onIframeLoad(): void { 56 | const frameWindow = this.iframe.nativeElement.contentWindow; 57 | 58 | // frameWindow can be null if iframe reloaded (wizard -> preview navigation) 59 | if(frameWindow === null) return; 60 | 61 | if(this.cssSubscription) this.cssSubscription.unsubscribe(); 62 | this.cssSubscription = this.metadataService.css.subscribe((css) => { 63 | const theme = this.metadataService.theme; 64 | const themeSize = theme.colorScheme.includes('compact') ? 'compact' : 'normal'; 65 | frameWindow.postMessage({ 66 | css, 67 | themeSize, 68 | widget: this.widgetName.getValue() 69 | }, this.url); 70 | }); 71 | 72 | if(this.widgetSubscription) this.widgetSubscription.unsubscribe(); 73 | this.widgetSubscription = this.widgetName.subscribe((widget) => { 74 | frameWindow.postMessage({ widget }, this.url); 75 | }); 76 | } 77 | 78 | ngOnDestroy(): void { 79 | if(this.cssSubscription) this.cssSubscription.unsubscribe(); 80 | if(this.widgetSubscription) this.widgetSubscription.unsubscribe(); 81 | } 82 | 83 | ngOnInit(): void { 84 | window.addEventListener('message', this.receiveMessage.bind(this), false); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/app/import.service.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Injectable } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import normalize from 'devextreme-themebuilder/modules/config-normalizer'; 4 | import { MetadataRepositoryService } from './meta-repository.service'; 5 | import { BuilderConfig } from './types/builder-config'; 6 | 7 | @Injectable() 8 | export class ImportService { 9 | 10 | constructor(private metaRepository: MetadataRepositoryService, private route: Router) {} 11 | private savedMetadata: BuilderConfig = {}; 12 | private normalizedMetadata: BuilderConfig = {}; 13 | changed = new EventEmitter(); 14 | 15 | importBootstrapVariables(variables: string, bootstrapVersion: number, redirectView: string): Promise { 16 | this.clearSavedMetadata(); 17 | return this.metaRepository.importBootstrap(variables, bootstrapVersion).then(() => { 18 | this.route.navigate([redirectView, this.metaRepository.theme.name, this.metaRepository.theme.colorScheme]); 19 | }); 20 | } 21 | 22 | importMetadata(meta: string, redirectView: string): Promise { 23 | this.clearSavedMetadata(); 24 | try { 25 | this.savedMetadata = JSON.parse(meta); 26 | } catch { 27 | return new Promise((_, reject): void => reject()); 28 | } 29 | 30 | this.normalizedMetadata = { ...this.savedMetadata }; 31 | normalize(this.normalizedMetadata); 32 | 33 | return this.metaRepository.import({ 34 | name: this.normalizedMetadata.themeName, 35 | colorScheme: this.normalizedMetadata.colorScheme 36 | }, this.savedMetadata.items).then(() => { 37 | this.route.navigate([redirectView, this.normalizedMetadata.themeName, this.normalizedMetadata.colorScheme]); 38 | this.changed.emit(); 39 | }); 40 | } 41 | 42 | exportMetadata(customSchemeName: string, useSwatch: boolean, widgets: string[], removeExternalResources: boolean): Promise { 43 | const SPACES_NUMBER = 4; 44 | 45 | return this.metaRepository.getVersion().then((version) => { 46 | const exportedObject = { 47 | ...this.savedMetadata, 48 | items: this.metaRepository.getModifiedItems(), 49 | baseTheme: [this.metaRepository.theme.name, this.metaRepository.theme.colorScheme.replace(/-/g, '.')].join('.'), 50 | outputColorScheme: customSchemeName, 51 | makeSwatch: useSwatch, 52 | version, 53 | widgets, 54 | removeExternalResources 55 | }; 56 | 57 | return JSON.stringify(exportedObject, null, SPACES_NUMBER); 58 | }); 59 | } 60 | 61 | exportCss(customSchemeName: string, useSwatch: boolean, widgets: string[], removeExternalResources: boolean): Promise { 62 | return this.metaRepository.export(customSchemeName, useSwatch, widgets, this.savedMetadata.assetsBasePath, removeExternalResources); 63 | } 64 | 65 | getSavedMetadata(): BuilderConfig { 66 | return this.savedMetadata; 67 | } 68 | 69 | getColorSchemeName(): string { 70 | return this.normalizedMetadata.outColorScheme || 'custom-scheme'; 71 | } 72 | 73 | getThemeName(): string { 74 | return this.metaRepository.theme.name; 75 | } 76 | 77 | getWidgets(): Array { 78 | return this.normalizedMetadata.widgets; 79 | } 80 | 81 | clearSavedMetadata(): void { 82 | this.savedMetadata = {}; 83 | this.normalizedMetadata = {}; 84 | this.changed.emit(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/app/layouts/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 | 12 | 13 |
14 |
15 | 27 |
28 |
29 | {{ data.key }} 30 | 33 | 37 |
38 |
39 | All trademarks or registered trademarks are property of their respective owners. 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | 52 |
53 |
54 |
55 |
56 | 57 |
{{data.text}}
58 |
59 |
60 |
61 |
62 |
63 | 64 |
65 | 66 | 67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 | 75 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DevExtreme ThemeBuilder 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 48 | 49 | 81 | 82 | 83 | 84 | 89 | 90 |
91 | loading... 92 |
93 |
94 | 95 | 96 | 102 | 103 | 104 | 105 | 106 | --------------------------------------------------------------------------------