├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .npmrc ├── README.md ├── _build_prod.cmd ├── _build_prod_all.cmd ├── _generate_xliff.cmd ├── _link_node_modules.cmd ├── _lint.cmd ├── _populate_xliff.cmd ├── _prettier.cmd ├── _serve.cmd ├── angular.json ├── custom-webpack.config.js ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app.component.html │ ├── app.component.ts │ ├── app.guard.ts │ ├── app.module.ts │ ├── app.routing.ts │ ├── components │ │ ├── Leaflet.GoogleMutant.js │ │ ├── constants.ts │ │ ├── dependencies-module.ts │ │ ├── types.ts │ │ ├── wy-base.component.ts │ │ ├── wy-base.module.ts │ │ ├── wy-blob-box │ │ │ ├── wy-blob-box.component.html │ │ │ ├── wy-blob-box.component.scss │ │ │ └── wy-blob-box.component.ts │ │ ├── wy-blob-manager │ │ │ ├── wy-blob-manager.component.html │ │ │ ├── wy-blob-manager.component.scss │ │ │ └── wy-blob-manager.component.ts │ │ ├── wy-blob-picker │ │ │ ├── wy-blob-picker.component.html │ │ │ ├── wy-blob-picker.component.scss │ │ │ └── wy-blob-picker.component.ts │ │ ├── wy-blob-upload │ │ │ ├── wy-blob-upload.component.html │ │ │ ├── wy-blob-upload.component.scss │ │ │ └── wy-blob-upload.component.ts │ │ ├── wy-blob-viewer │ │ │ ├── wy-blob-viewer.component.html │ │ │ ├── wy-blob-viewer.component.scss │ │ │ └── wy-blob-viewer.component.ts │ │ ├── wy-comment │ │ │ ├── wy-comment.component.html │ │ │ ├── wy-comment.component.scss │ │ │ └── wy-comment.component.ts │ │ ├── wy-custom.module.ts │ │ ├── wy-custom │ │ │ ├── wy-component-attachmentstuff.component.html │ │ │ ├── wy-component-attachmentstuff.component.scss │ │ │ ├── wy-component-attachmentstuff.component.ts │ │ │ ├── wy-component-dashboardcomponent.component.html │ │ │ ├── wy-component-dashboardcomponent.component.scss │ │ │ ├── wy-component-dashboardcomponent.component.ts │ │ │ ├── wy-component-mycustomcomponent.component.html │ │ │ ├── wy-component-mycustomcomponent.component.scss │ │ │ ├── wy-component-mycustomcomponent.component.ts │ │ │ ├── wy-component-mycustomcomponent2.component.html │ │ │ ├── wy-component-mycustomcomponent2.component.scss │ │ │ ├── wy-component-mycustomcomponent2.component.ts │ │ │ ├── wy-component-reportscomponent.component.html │ │ │ ├── wy-component-reportscomponent.component.scss │ │ │ ├── wy-component-reportscomponent.component.ts │ │ │ ├── wy-component-todoitemscomponent.component.html │ │ │ ├── wy-component-todoitemscomponent.component.scss │ │ │ └── wy-component-todoitemscomponent.component.ts │ │ ├── wy-default.module.ts │ │ ├── wy-designer │ │ │ ├── wy-designer.component.html │ │ │ ├── wy-designer.component.scss │ │ │ └── wy-designer.component.ts │ │ ├── wy-editor │ │ │ ├── wy-editor.component.html │ │ │ ├── wy-editor.component.scss │ │ │ └── wy-editor.component.ts │ │ ├── wy-entity-card │ │ │ ├── wy-entity-card-base.component.ts │ │ │ └── wy-entity-card.component.scss │ │ ├── wy-entity-dialog │ │ │ ├── wy-entity-dialog.component.html │ │ │ ├── wy-entity-dialog.component.scss │ │ │ └── wy-entity-dialog.component.ts │ │ ├── wy-entity-window │ │ │ ├── wy-entity-window.component.html │ │ │ ├── wy-entity-window.component.scss │ │ │ └── wy-entity-window.component.ts │ │ ├── wy-enum-filter │ │ │ ├── wy-enum-filter.component.html │ │ │ ├── wy-enum-filter.component.scss │ │ │ └── wy-enum-filter.component.ts │ │ ├── wy-grid │ │ │ ├── wy-component-defaultgrid_action.component.html │ │ │ ├── wy-component-defaultgrid_action.component.ts │ │ │ ├── wy-component-defaultgrid_message.component.html │ │ │ ├── wy-component-defaultgrid_message.component.ts │ │ │ ├── wy-component-defaultgrid_project.component.html │ │ │ ├── wy-component-defaultgrid_project.component.ts │ │ │ ├── wy-component-defaultgrid_socialcomment.component.html │ │ │ ├── wy-component-defaultgrid_socialcomment.component.ts │ │ │ ├── wy-component-defaultgrid_socialreaction.component.html │ │ │ ├── wy-component-defaultgrid_socialreaction.component.ts │ │ │ ├── wy-component-defaultgrid_todoitem.component.html │ │ │ ├── wy-component-defaultgrid_todoitem.component.ts │ │ │ ├── wy-component-defaultgrid_user.component.html │ │ │ ├── wy-component-defaultgrid_user.component.ts │ │ │ ├── wy-grid-base.component.ts │ │ │ ├── wy-grid-column.directive.ts │ │ │ ├── wy-grid-toolbar.component.html │ │ │ ├── wy-grid-toolbar.component.scss │ │ │ ├── wy-grid-toolbar.component.ts │ │ │ ├── wy-grid.component.scss │ │ │ └── wy-grid.directive.ts │ │ ├── wy-html │ │ │ └── wy-html.component.scss │ │ ├── wy-image-picker │ │ │ ├── wy-image-picker.component.html │ │ │ ├── wy-image-picker.component.scss │ │ │ └── wy-image-picker.component.ts │ │ ├── wy-image-viewer │ │ │ ├── wy-image-viewer.component.html │ │ │ ├── wy-image-viewer.component.scss │ │ │ └── wy-image-viewer.component.ts │ │ ├── wy-lookup-filter │ │ │ ├── wy-lookup-filter.component.html │ │ │ ├── wy-lookup-filter.component.scss │ │ │ └── wy-lookup-filter.component.ts │ │ ├── wy-map-point-editor │ │ │ ├── wy-map-point-editor.component.html │ │ │ ├── wy-map-point-editor.component.scss │ │ │ └── wy-map-point-editor.component.ts │ │ ├── wy-map-polygon-editor │ │ │ ├── wy-map-polygon-editor.component.html │ │ │ ├── wy-map-polygon-editor.component.scss │ │ │ └── wy-map-polygon-editor.component.ts │ │ ├── wy-map │ │ │ ├── wy-component-mymap.component.html │ │ │ ├── wy-component-mymap.component.ts │ │ │ └── wy-map.component.scss │ │ ├── wy-monaco.module.ts │ │ ├── wy-monaco │ │ │ ├── dart-conf.ts │ │ │ ├── wy-monaco.component.scss │ │ │ └── wy-monaco.component.ts │ │ ├── wy-one-entity │ │ │ ├── wy-component-defaultoneentity_action.component.html │ │ │ ├── wy-component-defaultoneentity_action.component.ts │ │ │ ├── wy-component-defaultoneentity_message.component.html │ │ │ ├── wy-component-defaultoneentity_message.component.ts │ │ │ ├── wy-component-defaultoneentity_project.component.html │ │ │ ├── wy-component-defaultoneentity_project.component.ts │ │ │ ├── wy-component-defaultoneentity_socialcomment.component.html │ │ │ ├── wy-component-defaultoneentity_socialcomment.component.ts │ │ │ ├── wy-component-defaultoneentity_socialreaction.component.html │ │ │ ├── wy-component-defaultoneentity_socialreaction.component.ts │ │ │ ├── wy-component-defaultoneentity_todoitem.component.html │ │ │ ├── wy-component-defaultoneentity_todoitem.component.ts │ │ │ ├── wy-component-defaultoneentity_user.component.html │ │ │ ├── wy-component-defaultoneentity_user.component.ts │ │ │ ├── wy-one-entity-base.component.ts │ │ │ └── wy-one-entity.component.scss │ │ ├── wy-pivot │ │ │ └── wy-pivot.component.ts │ │ ├── wy-property-editor │ │ │ ├── wy-property-editor.component.html │ │ │ ├── wy-property-editor.component.scss │ │ │ └── wy-property-editor.component.ts │ │ ├── wy-quick-search │ │ │ ├── wy-quick-search.component.html │ │ │ ├── wy-quick-search.component.scss │ │ │ └── wy-quick-search.component.ts │ │ └── wy-social │ │ │ ├── wy-component-defaultsocial_project.component.html │ │ │ ├── wy-component-defaultsocial_project.component.ts │ │ │ ├── wy-social-base.component.ts │ │ │ └── wy-social.component.scss │ ├── d.ts │ │ ├── Typings.d.ts │ │ ├── internal.d.ts │ │ ├── jwt-decode.d.ts │ │ ├── monaco.d.ts │ │ └── wy.d.ts │ ├── icons.ts │ ├── layouts │ │ ├── full-layout │ │ │ ├── full-layout.component.html │ │ │ ├── full-layout.component.scss │ │ │ ├── full-layout.component.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── simple-layout │ │ │ ├── index.ts │ │ │ ├── simple-layout.component.html │ │ │ ├── simple-layout.component.scss │ │ │ └── simple-layout.component.ts │ ├── models │ │ └── models.ts │ ├── pages │ │ ├── full │ │ │ ├── blobpage │ │ │ │ ├── blobpage.component.html │ │ │ │ ├── blobpage.component.scss │ │ │ │ └── blobpage.component.ts │ │ │ ├── editpage │ │ │ │ ├── editpage.component.scss │ │ │ │ └── editpage.component.ts │ │ │ ├── emptypage │ │ │ │ ├── emptypage.component.html │ │ │ │ └── emptypage.component.ts │ │ │ ├── gridpage │ │ │ │ ├── gridpage.component.scss │ │ │ │ └── gridpage.component.ts │ │ │ ├── grouppage │ │ │ │ ├── grouppage.component.scss │ │ │ │ └── grouppage.component.ts │ │ │ ├── page │ │ │ │ ├── page.component.scss │ │ │ │ └── page.component.ts │ │ │ └── utilspage │ │ │ │ ├── utilspage.component.html │ │ │ │ ├── utilspage.component.scss │ │ │ │ └── utilspage.component.ts │ │ └── simple │ │ │ ├── 404 │ │ │ ├── 404.component.html │ │ │ └── 404.component.ts │ │ │ ├── 500 │ │ │ ├── 500.component.html │ │ │ └── 500.component.ts │ │ │ ├── app-download │ │ │ ├── app-download.component.html │ │ │ └── app-download.component.ts │ │ │ └── login │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ └── login.component.ts │ └── services │ │ ├── authentication.service.ts │ │ ├── authorization.service.ts │ │ ├── base-http.service.ts │ │ ├── blob.service.ts │ │ ├── constant.service.ts │ │ ├── data.service.ts │ │ ├── event.service.ts │ │ ├── language.service.ts │ │ ├── link.service.ts │ │ ├── message.service.ts │ │ ├── monaco.service.ts │ │ ├── social.service.ts │ │ ├── storage.service.ts │ │ ├── usersettings.service.ts │ │ └── util.service.ts ├── applauncher.json ├── assets │ ├── .gitkeep │ ├── .npmignore │ ├── img │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── avatars │ │ │ └── default-avatar.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-512x512.png │ │ ├── favicon.png │ │ ├── logo-symbol.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── mstile-144x144.png │ │ └── svg │ │ │ ├── android.svg │ │ │ └── gps.svg │ ├── manifest.json │ └── uuid.js ├── config.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── i18n │ └── messages.en.xlf ├── index.html ├── kiosk.html ├── main.ts ├── polyfills.ts ├── scss │ ├── _bootstrap-variables.scss │ ├── _default-colors.scss │ ├── _kendo-customization.scss │ ├── _kendo-variables.scss │ ├── _kendo.scss │ ├── _style.scss │ ├── _wy.scss │ ├── kendo_default.scss │ └── theme_default.scss ├── test.ts ├── typings.d.ts └── web.config ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | *.suo 3 | *.user 4 | *.userosscache 5 | *.sln.docstates 6 | 7 | # Build results 8 | **/[Bb]in/[Dd]ebug/ 9 | **/[Bb]in/[Rr]elease/ 10 | [Oo]bj/ 11 | 12 | # Visual Studio 2015/2017 cache/options directory 13 | .vs/ 14 | 15 | # Visual Studio 2017 auto generated files 16 | Generated\ Files/ 17 | 18 | # MSTest test Results 19 | [Tt]est[Rr]esult*/ 20 | [Bb]uild[Ll]og.* 21 | 22 | # .NET Core 23 | project.lock.json 24 | project.fragment.lock.json 25 | artifacts/ 26 | 27 | # Visual Studio profiler 28 | *.psess 29 | *.vsp 30 | *.vspx 31 | *.sap 32 | 33 | # Visual Studio Trace Files 34 | *.e2e 35 | 36 | # Visual Studio code coverage results 37 | *.coverage 38 | *.coveragexml 39 | 40 | # NuGet Packages 41 | *.nupkg 42 | # The packages folder can be ignored because of Package Restore 43 | **/[Pp]ackages/* 44 | # except build/, which is used as an MSBuild target. 45 | !**/[Pp]ackages/build/ 46 | # Uncomment if necessary however generally it will be regenerated when needed 47 | #!**/[Pp]ackages/repositories.config 48 | # NuGet v3's project.json files produces more ignorable files 49 | *.nuget.props 50 | *.nuget.targets 51 | 52 | # Microsoft Azure Build Output 53 | csx/ 54 | *.build.csdef 55 | 56 | # Microsoft Azure Emulator 57 | ecf/ 58 | rcf/ 59 | 60 | # Visual Studio cache files 61 | # files ending in .cache can be ignored 62 | *.[Cc]ache 63 | # but keep track of directories ending in .cache 64 | !*.[Cc]ache/ 65 | 66 | # Python Tools for Visual Studio (PTVS) 67 | __pycache__/ 68 | *.pyc 69 | 70 | # Azure Stream Analytics local run output 71 | ASALocalRun/ 72 | 73 | # MSBuild Binary and Structured Log 74 | *.binlog 75 | 76 | # node 77 | node_modules/ 78 | 79 | # Custom 80 | dist/ 81 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @fortawesome:registry=https://npm.fontawesome.com/ 2 | //npm.fontawesome.com/:_authToken=4A984856-5EC5-4D98-B606-B749F231FF00 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grapes-js-example 2 | Created with CodeSandbox 3 | -------------------------------------------------------------------------------- /_build_prod.cmd: -------------------------------------------------------------------------------- 1 | ng build --prod 2 | -------------------------------------------------------------------------------- /_build_prod_all.cmd: -------------------------------------------------------------------------------- 1 | ng build --prod --localize 2 | -------------------------------------------------------------------------------- /_generate_xliff.cmd: -------------------------------------------------------------------------------- 1 | ng xi18n --output-path _temp --out-file messages.nl.xlf -------------------------------------------------------------------------------- /_link_node_modules.cmd: -------------------------------------------------------------------------------- 1 | mklink /D node_modules "C:\Estiom\git\wyStackPlatform\wyStack\wyStack.AppTemplates.NgLayer.WebApp\node_modules\" 2 | -------------------------------------------------------------------------------- /_lint.cmd: -------------------------------------------------------------------------------- 1 | ng lint 2 | -------------------------------------------------------------------------------- /_populate_xliff.cmd: -------------------------------------------------------------------------------- 1 | npx kendo-translate src\_temp\messages.nl.xlf --locale nl-NL --force -------------------------------------------------------------------------------- /_prettier.cmd: -------------------------------------------------------------------------------- 1 | prettier --write **/*.{ts,scss,html,json} --single-quote 2 | -------------------------------------------------------------------------------- /_serve.cmd: -------------------------------------------------------------------------------- 1 | ng serve --host 0.0.0.0 --disableHostCheck 2 | -------------------------------------------------------------------------------- /custom-webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.scss$/i, 6 | use: [ 7 | { 8 | loader: 'postcss-loader', 9 | options: { 10 | precision: 10, 11 | plugins: [ 12 | require('autoprefixer')(), 13 | require('postcss-calc')() 14 | ] 15 | } 16 | }, 17 | { 18 | loader: 'sass-loader', 19 | options: { 20 | implementation: require('sass'), 21 | sassOptions: { 22 | precision: 10, 23 | fiber: require('fibers') 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | }; -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('tst app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/tst'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Please wait... 19 | Succeeded 20 | Failed 21 | Save 22 | Delete 23 | Are you sure you want to delete this item? 24 | Restore this item from the recycle bin? 25 | Your request is being processed... 26 | Your request completed successfully. 27 | Your request failed. 28 | An error occurred in the background. Contact your administrator for details. 29 | Data successfully saved. 30 | There was a problem saving the data. 31 | Loading 32 | The form contains invalid data. Please correct before saving. 33 | Could not authenticate. Please contact your system administrator. 34 |
35 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Component, OnInit, NgZone, Inject, OnDestroy } from '@angular/core'; 3 | import { Router } from '@angular/router'; 4 | import { EventService } from './services/event.service'; 5 | @Component({ 6 | // tslint:disable-next-line 7 | selector: 'body', 8 | templateUrl: './app.component.html' 9 | }) 10 | export class AppComponent implements OnInit, OnDestroy { 11 | private tokenProcessHandle: any; 12 | public roleClass: string; 13 | public ready: boolean; 14 | constructor( 15 | protected zone: NgZone, 16 | @Inject('IUtilService') 17 | private _us: wy.IUtilService, 18 | @Inject('IConstantService') 19 | private _cs: wy.IConstantService, 20 | @Inject('IAuthenticationService') 21 | private _as: wy.IAuthenticationService, 22 | @Inject('IAuthorizationService') 23 | private _azs: wy.IAuthorizationService, 24 | @Inject('IUserSettingsService') 25 | private _usrSet: wy.IUserSettingsService, 26 | @Inject('ILanguageService') 27 | private _ls: wy.ILanguageService, 28 | @Inject('IMessageService') 29 | private _ms: wy.IMessageService, 30 | private _router: Router, 31 | private _es: EventService) { 32 | this.zone.onError.subscribe((e) => { 33 | this._ms.notify(this._ls.translate('BackgroundError'), 'warning'); 34 | }); 35 | } 36 | async loadRolesAndAuthorizations(jwt: wy.Jwt) { 37 | if (!jwt) { 38 | return; 39 | } 40 | // refresh authorization map and user settings 41 | await this._azs.refreshAuthorizationMaps(); 42 | await this._usrSet.refreshUserSettings(); 43 | const authorizationMapsEnabled = this._azs.isAuthorizationMapsEnabled(); 44 | if (!authorizationMapsEnabled) { 45 | // apply role(s) from jwt 46 | this.applyRoleClass(jwt.role || jwt.roles); 47 | } 48 | else { 49 | // apply role(s) from authorization map 50 | const authorizationMap = this._azs.getAuthorizationMap(); 51 | if (authorizationMap) { 52 | this.applyRoleClassArray(authorizationMap.Roles); 53 | } 54 | } 55 | } 56 | async ngOnInit() { 57 | const jwt = this._us.getValidatedJwt(); 58 | await this.loadRolesAndAuthorizations(jwt); 59 | this._es.userAuthenticated.subscribe(async jwt => { 60 | this.ready = false; 61 | await this.loadRolesAndAuthorizations(jwt); 62 | this.ready = true; 63 | }); 64 | // refresh token in background 65 | const config = this._cs.getConfig(); 66 | const accessTokenTimeoutInSeconds = parseInt(config.accessTokenTimeoutInSeconds); 67 | if (accessTokenTimeoutInSeconds > 0) { 68 | this.tokenProcessHandle = setInterval(() => { 69 | const currentUrl = this._router.url; 70 | if (currentUrl != this._cs.getLoginPageUrl()) { 71 | if (this._us.canRefreshJwt()) { 72 | this._as.refreshToken( 73 | () => { 74 | }, 75 | () => { 76 | this._us.logoutIfInvalidJwt(); 77 | } 78 | ); 79 | } else { 80 | this._us.logoutIfInvalidJwt(); 81 | } 82 | } 83 | }, (accessTokenTimeoutInSeconds / 4) * 1000); 84 | } 85 | this.ready = true; 86 | } 87 | private applyRoleClass(roles: string) { 88 | if (roles) { 89 | this.applyRoleClassArray(roles.split(' ')); 90 | } 91 | } 92 | private applyRoleClassArray(roles: string[]) { 93 | let newRoleClass = ''; 94 | if (roles) { 95 | for (let role of roles) { 96 | if (role) { 97 | newRoleClass += ' wy-role-' + role.toLowerCase().split(' ').join('_'); 98 | } 99 | } 100 | } 101 | this.roleClass = newRoleClass; 102 | } 103 | ngOnDestroy() { 104 | if (this.tokenProcessHandle) { 105 | clearInterval(this.tokenProcessHandle); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/app/app.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { Router, CanActivateChild } from '@angular/router'; 3 | 4 | @Injectable() 5 | export class AuthenticatedGuard implements CanActivateChild { 6 | constructor( 7 | @Inject('IUtilService') 8 | private _us: wy.IUtilService, 9 | 10 | @Inject('IConstantService') 11 | private _cs: wy.IConstantService, 12 | 13 | private _router: Router 14 | ) { 15 | } 16 | 17 | canActivateChild(): boolean { 18 | const currentUrl = this._router.url; 19 | 20 | if (this._us.hasValidJwt() || currentUrl == this._cs.getLoginPageUrl()) { 21 | return true; 22 | } 23 | else { 24 | this._router.navigate([this._cs.getLoginPageUrl()]); 25 | return false; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/components/constants.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { tileLayer } from 'leaflet'; 3 | import './Leaflet.GoogleMutant.js'; 4 | export const EMPTY_GUID = '00000000-0000-0000-0000-000000000000'; 5 | export const PRIMARY_KEY_NULL_VALUE = EMPTY_GUID; 6 | export const MAP_LAYERS = { 7 | worldImagery: tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { 8 | attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community', 9 | minZoom: 1, 10 | maxZoom: 18, 11 | maxNativeZoom: 16, 12 | }), 13 | osm: tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 14 | minZoom: 1, 15 | maxZoom: 18, 16 | }), 17 | googleMaps: L.gridLayer.googleMutant({ 18 | type: 'roadmap' 19 | }), 20 | googleSatellite: L.gridLayer.googleMutant({ 21 | type: 'satellite' 22 | }), 23 | googleTerrain: L.gridLayer.googleMutant({ 24 | type: 'terrain' 25 | }) 26 | } 27 | export const MAP_WEATHER_LAYERS = { 28 | clouds: tileLayer('http://{s}.tile.openweathermap.org/map/clouds/{z}/{x}/{y}.png?appid=2890209cdb7d7e64a1e73a7f7aa5248d', { 29 | maxZoom: 19, 30 | attribution: 'Map data © OpenWeatherMap', 31 | opacity: 0.5 32 | }), 33 | pressure: tileLayer('http://{s}.tile.openweathermap.org/map/pressure/{z}/{x}/{y}.png?appid=2890209cdb7d7e64a1e73a7f7aa5248d', { 34 | maxZoom: 19, 35 | attribution: 'Map data © OpenWeatherMap', 36 | opacity: 0.5 37 | }), 38 | wind: tileLayer('http://{s}.tile.openweathermap.org/map/wind/{z}/{x}/{y}.png?appid=2890209cdb7d7e64a1e73a7f7aa5248d', { 39 | maxZoom: 19, 40 | attribution: 'Map data © OpenWeatherMap', 41 | opacity: 0.5 42 | }), 43 | } 44 | export const PortalHostDivId = 'PORTAL_HOST'; 45 | export const GridColumnsConfigurationStorageKey = 'GRID_COLUMNS'; 46 | export const QuickSearchStorageKey = 'QUICK_SEARCH'; 47 | export const PageSizeStorageKey = 'PAGE_SIZE'; 48 | export const SortStorageKey = 'SORT'; 49 | export const AuthorizationMapStorageKey = 'AUTH_MAP'; 50 | export const UserSettingsStorageKey = 'USER_SETTINGS'; 51 | -------------------------------------------------------------------------------- /src/app/components/dependencies-module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule, FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { RouterModule } from '@angular/router'; 6 | import { PortalModule } from '@angular/cdk/portal'; 7 | 8 | // Kendo 9 | import { GridModule, PDFModule, ExcelModule } from '@progress/kendo-angular-grid'; 10 | import { PagerModule } from '@progress/kendo-angular-pager'; 11 | import { ButtonsModule } from '@progress/kendo-angular-buttons'; 12 | import { DialogModule, WindowModule } from '@progress/kendo-angular-dialog'; 13 | import { LayoutModule } from '@progress/kendo-angular-layout'; 14 | import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; 15 | import { InputsModule } from '@progress/kendo-angular-inputs'; 16 | import { DateInputsModule } from '@progress/kendo-angular-dateinputs'; 17 | import { UploadModule } from '@progress/kendo-angular-upload'; 18 | import { ChartsModule } from '@progress/kendo-angular-charts'; 19 | import { PDFExportModule } from '@progress/kendo-angular-pdf-export'; 20 | import { SortableModule } from '@progress/kendo-angular-sortable'; 21 | import { GaugesModule } from '@progress/kendo-angular-gauges'; 22 | import { NotificationModule } from '@progress/kendo-angular-notification'; 23 | import { SchedulerModule } from '@progress/kendo-angular-scheduler'; 24 | import { ToolBarModule } from '@progress/kendo-angular-toolbar'; 25 | import { EditorModule } from '@progress/kendo-angular-editor'; 26 | import { ChatModule } from '@progress/kendo-angular-conversational-ui'; 27 | 28 | // Leaflet 29 | import { LeafletModule } from '@asymmetrik/ngx-leaflet'; 30 | import { LeafletDrawModule } from '@asymmetrik/ngx-leaflet-draw'; 31 | 32 | // pdfjs 33 | import { PdfJsViewerModule } from 'ng2-pdfjs-viewer'; 34 | 35 | @NgModule({ 36 | imports: [ 37 | HttpClientModule, 38 | CommonModule, 39 | GridModule, 40 | PagerModule, 41 | PDFModule, 42 | ExcelModule, 43 | DialogModule, 44 | WindowModule, 45 | ButtonsModule, 46 | LayoutModule, 47 | DropDownsModule, 48 | InputsModule, 49 | DateInputsModule, 50 | UploadModule, 51 | ChartsModule, 52 | PDFExportModule, 53 | SortableModule, 54 | ToolBarModule, 55 | EditorModule, 56 | ChatModule, 57 | GaugesModule, 58 | NotificationModule, 59 | SchedulerModule, 60 | ReactiveFormsModule, 61 | FormsModule, 62 | PortalModule, 63 | RouterModule, 64 | 65 | LeafletModule, 66 | LeafletDrawModule, 67 | PdfJsViewerModule 68 | ], 69 | declarations: [ 70 | ], 71 | exports: [ 72 | HttpClientModule, 73 | CommonModule, 74 | GridModule, 75 | PagerModule, 76 | PDFModule, 77 | ExcelModule, 78 | DialogModule, 79 | WindowModule, 80 | ButtonsModule, 81 | LayoutModule, 82 | DropDownsModule, 83 | InputsModule, 84 | DateInputsModule, 85 | UploadModule, 86 | ChartsModule, 87 | PDFExportModule, 88 | SortableModule, 89 | ToolBarModule, 90 | EditorModule, 91 | ChatModule, 92 | GaugesModule, 93 | NotificationModule, 94 | SchedulerModule, 95 | ReactiveFormsModule, 96 | FormsModule, 97 | PortalModule, 98 | RouterModule, 99 | 100 | LeafletModule, 101 | LeafletDrawModule, 102 | PdfJsViewerModule 103 | ] 104 | }) 105 | export class DependenciesModule { } 106 | -------------------------------------------------------------------------------- /src/app/components/types.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup } from '@angular/forms'; 2 | 3 | /** 4 | * DynamicObjectExpressionApi, used by DisplayExpression, ComputeExpression. 5 | */ 6 | export class DynamicObjectExpressionApi { 7 | constructor(currentEntity: FormGroup, ownerEntity: FormGroup, className: string, ownerClassName: string, private _usrSet: wy.IUserSettingsService) { 8 | this.currentEntity = new DynamicObjectExpressionEntityApi(currentEntity, className); 9 | this.ownerEntity = new DynamicObjectExpressionEntityApi(ownerEntity, ownerClassName); 10 | 11 | this.math = new DynamicObjectExpressionMathApi(); 12 | } 13 | 14 | public currentEntity: DynamicObjectExpressionEntityApi; 15 | public ownerEntity: DynamicObjectExpressionEntityApi; 16 | 17 | public math: DynamicObjectExpressionMathApi; 18 | 19 | /** 20 | * Returns current datetime. 21 | */ 22 | public getCurrentDateTime(): Date { 23 | return new Date(); 24 | } 25 | 26 | /** 27 | * Converts any value to a string. 28 | * @param value Value. 29 | */ 30 | public convertToString(value: any): string { 31 | if (value) { 32 | return value.toString(); 33 | } 34 | 35 | return ""; 36 | } 37 | 38 | /** 39 | * Converts any value to a string. 40 | * @param value Value. 41 | */ 42 | public convertToInt(value: any): number { 43 | if (value) { 44 | return parseInt(value); 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | /** 51 | * Retrieve a user setting by key. 52 | * @param key Key. 53 | */ 54 | public getUserSetting(key: string): any { 55 | return this._usrSet.getUserSetting(key); 56 | } 57 | } 58 | 59 | export class DynamicObjectExpressionMathApi { 60 | public roundDouble(value: number, decimals: number) 61 | { 62 | return Number(Math.round((value + 'e' + decimals) as any) + 'e-' + decimals); 63 | } 64 | 65 | public roundFloat(value: number, decimals: number) 66 | { 67 | return this.roundDouble(value, decimals); 68 | } 69 | } 70 | 71 | export class DynamicObjectExpressionEntityApi { 72 | constructor(private _obj: FormGroup, private _className: string) { 73 | } 74 | 75 | /** 76 | * Get value by property name. 77 | * @param propertyName 78 | */ 79 | public getPropertyValue(propertyName: string) { 80 | return this._obj?.get(propertyName)?.value; 81 | } 82 | 83 | public getPropertyValueAsString(propertyName: string) { 84 | return this.getPropertyValue(propertyName); 85 | } 86 | 87 | public getPropertyValueAsFloat(propertyName: string) { 88 | return this.getPropertyValue(propertyName); 89 | } 90 | 91 | public getPropertyValueAsInt(propertyName: string) { 92 | return this.getPropertyValue(propertyName); 93 | } 94 | 95 | public getPropertyValueAsDouble(propertyName: string) { 96 | return this.getPropertyValue(propertyName); 97 | } 98 | 99 | public getPropertyValueAsBoolean(propertyName: string) { 100 | return this.getPropertyValue(propertyName); 101 | } 102 | 103 | /** 104 | * Check value by property name. 105 | * @param propertyName 106 | */ 107 | public hasPropertyValue(propertyName: string) { 108 | const value = this.getPropertyValue(propertyName); 109 | return !!value; 110 | } 111 | 112 | /** 113 | * Get class name. 114 | */ 115 | public getClassName(): string { 116 | return this._className; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/app/components/wy-base.component.ts: -------------------------------------------------------------------------------- 1 | export abstract class WyBaseComponent { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-box/wy-blob-box.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | 8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-box/wy-blob-box.component.scss: -------------------------------------------------------------------------------- 1 | .wy-blob-box-item { 2 | display: block; 3 | float: left; 4 | margin-right: 5px; 5 | margin-bottom: 5px; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-box/wy-blob-box.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject, Input, OnChanges } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wy-blob-box', 5 | templateUrl: './wy-blob-box.component.html', 6 | styleUrls: [ 7 | './wy-blob-box.component.scss' 8 | ], 9 | }) 10 | export class WyBlobBoxComponent implements OnInit, OnChanges { 11 | 12 | @Input() public boxId: string; 13 | @Input() public largeThumbnail: boolean; 14 | 15 | public blobs: wy.Blob[]; 16 | private _isInitiated = false; 17 | 18 | constructor( 19 | @Inject('IBlobService') 20 | private _bs: wy.IBlobService, 21 | 22 | @Inject('ILinkService') 23 | private _ls: wy.ILinkService 24 | ) { } 25 | 26 | ngOnInit() { 27 | this.loadBlobs(); 28 | this._isInitiated = true; 29 | } 30 | 31 | ngOnChanges() { 32 | if (this._isInitiated) { 33 | this.loadBlobs(); 34 | } 35 | } 36 | 37 | private loadBlobs() { 38 | this._bs.getFiles((blobs) => { 39 | this.blobs = blobs; 40 | }, this.boxId); 41 | } 42 | 43 | public getBlobUri(blobName: string, boxId?: string) { 44 | return this._bs.getBlobUriByName(blobName, boxId); 45 | } 46 | 47 | public getDownloadLink(blobName: string, size?: number, boxId?: string) { 48 | return this._ls.getDownloadLink(blobName, size, boxId); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-manager/wy-blob-manager.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 | 5 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | {{blob.Name}} 23 | 24 | 25 |
26 |

27 | {{blob.Name}} 28 | {{formatBytes(blob)}} 29 |

30 | 33 |
34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-manager/wy-blob-manager.component.scss: -------------------------------------------------------------------------------- 1 | .wy-blob-manager { 2 | .wy-blob-text-link { 3 | text-overflow: ellipsis; 4 | width: 100%; 5 | overflow: hidden; 6 | display: block; 7 | } 8 | 9 | .wy-blob-image-link { 10 | margin-bottom: 3px; 11 | } 12 | 13 | .card { 14 | cursor: pointer; 15 | background-color: #efefef; 16 | border: none; 17 | margin-bottom: 5px; 18 | } 19 | 20 | .card-text { 21 | margin: 3px; 22 | } 23 | 24 | .card-inverse { 25 | background-color: #aaa; 26 | border-color: #aaa; 27 | } 28 | 29 | button.close { 30 | top: 5px; 31 | position: absolute; 32 | right: 5px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-manager/wy-blob-manager.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject, Input, Output, EventEmitter, ViewChild } from '@angular/core'; 2 | import { SuccessEvent } from '@progress/kendo-angular-upload'; 3 | import { WyQuickSearchComponent } from '../wy-quick-search/wy-quick-search.component'; 4 | 5 | @Component({ 6 | selector: 'wy-blob-manager', 7 | templateUrl: './wy-blob-manager.component.html', 8 | styleUrls: ['./wy-blob-manager.component.scss'] 9 | }) 10 | export class WyBlobManagerComponent implements OnInit { 11 | public blobs: wy.Blob[]; 12 | 13 | public uploadSaveUrl: string; 14 | 15 | @ViewChild('quickSearch') 16 | quickSearch: WyQuickSearchComponent; 17 | 18 | @Input() public showItems: number; 19 | @Input() public loadOnlyImages = false; 20 | 21 | @Input() public set model(blobName: any) { 22 | this.selectedBlobName = blobName; 23 | } 24 | 25 | @Output() blobSelected: EventEmitter = new EventEmitter(); 26 | 27 | public selectedBlobName: string; 28 | 29 | constructor( 30 | @Inject('IBlobService') 31 | private _bs: wy.IBlobService, 32 | 33 | @Inject('IUtilService') 34 | private _us: wy.IUtilService, 35 | 36 | @Inject('IConstantService') 37 | private _cs: wy.IConstantService, 38 | ) { 39 | this.uploadSaveUrl = this._cs.getServiceLayerBlobUri() + '/post'; 40 | 41 | this.showItems = 20; 42 | this.selectedBlobName = null; 43 | } 44 | 45 | public showMore() { 46 | this.showItems += 20; 47 | } 48 | 49 | public selectBlob(blob: wy.Blob) { 50 | if (this.selectedBlobName == blob.Name) { 51 | this.selectedBlobName = null; 52 | } 53 | else { 54 | this.selectedBlobName = blob.Name; 55 | } 56 | 57 | this.blobSelected.emit(this.selectedBlobName); 58 | } 59 | 60 | public getBlobUri(blob: wy.Blob) { 61 | return this._bs.getBlobUri(blob); 62 | } 63 | 64 | public deleteBlob(blob: wy.Blob) { 65 | if (confirm(`Delete ${blob.Name}?`)) { 66 | return this._bs.deleteBlob(blob, () => { 67 | this.loadBlobs(); 68 | }); 69 | } 70 | } 71 | 72 | public formatBytes(blob: wy.Blob) { 73 | return this._us.formatBytes(blob.Size, 2); 74 | } 75 | 76 | public uploadSuccessEventHandler(e: SuccessEvent) { 77 | if (e.files && e.files.length > 0) { 78 | // select uploaded blob 79 | const firstFile = e.files[0]; 80 | 81 | this.selectedBlobName = firstFile.name; 82 | this.blobSelected.emit(this.selectedBlobName); 83 | } 84 | 85 | this.loadBlobs(); 86 | } 87 | 88 | private loadBlobs() { 89 | this.blobs = null; 90 | 91 | if (this.loadOnlyImages) { 92 | this._bs.getImages((blobs) => { 93 | this.blobs = blobs; 94 | }); 95 | } 96 | else { 97 | this._bs.getFiles((blobs) => { 98 | this.blobs = blobs; 99 | }); 100 | } 101 | } 102 | 103 | ngOnInit() { 104 | this.loadBlobs(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-picker/wy-blob-picker.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 | 8 |   9 | 13 |

14 | 19 | 20 | 23 | 24 | 26 | 27 | Select image 28 | 29 | Select file 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-picker/wy-blob-picker.component.scss: -------------------------------------------------------------------------------- 1 | .wy-blob-picker { 2 | a.wy-blob-link { 3 | display: block; 4 | margin-top: 5px; 5 | margin-bottom: 5px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-picker/wy-blob-picker.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, Input, OnChanges, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroupDirective, ControlContainer } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'wy-blob-picker', 6 | templateUrl: `./wy-blob-picker.component.html`, 7 | styleUrls: [ 8 | './wy-blob-picker.component.scss' 9 | ], 10 | viewProviders: [{ 11 | provide: ControlContainer, 12 | useExisting: FormGroupDirective 13 | }] 14 | }) 15 | export class WyBlobPickerComponent implements OnInit, OnChanges { 16 | public isBlobPickerActive = false; 17 | 18 | public blobDialogWidth: number; 19 | public blobDialogHeight: number; 20 | 21 | private _selectedBlobName: string; 22 | 23 | public formControl: FormControl; 24 | 25 | private _isInitiated = false; 26 | private _formControlName: string; 27 | 28 | @Input() public entityId: string; 29 | 30 | constructor(private parentForm: FormGroupDirective, 31 | @Inject('IBlobService') 32 | private bs: wy.IBlobService) { 33 | 34 | if (window.innerWidth > 992 && window.innerHeight > 720) { 35 | this.blobDialogWidth = window.innerWidth - 400; 36 | this.blobDialogHeight = window.innerHeight - 200; 37 | } 38 | else { 39 | this.blobDialogWidth = window.innerWidth - 10; 40 | this.blobDialogHeight = window.innerHeight - 10; 41 | } 42 | } 43 | 44 | @Input() 45 | set controlName(value: string) { 46 | this._formControlName = value; 47 | this.init(); 48 | } 49 | 50 | ngOnInit() { 51 | this.init(); 52 | this._isInitiated = true; 53 | } 54 | 55 | ngOnChanges() { 56 | if (this._isInitiated) { 57 | this.init(); 58 | } 59 | } 60 | 61 | private init() { 62 | if (this._formControlName) { 63 | this.formControl = this.parentForm.form.get(this._formControlName) as FormControl; 64 | } 65 | } 66 | 67 | public getBlobUri(blobName: string) { 68 | return this.bs.getBlobUriByName(blobName); 69 | } 70 | 71 | public blobSelectedHandler(blobName: string) { 72 | this._selectedBlobName = blobName; 73 | } 74 | 75 | public openBlobPicker() { 76 | this.isBlobPickerActive = true; 77 | } 78 | 79 | public cancelBlobPicker() { 80 | this.isBlobPickerActive = false; 81 | } 82 | 83 | public selectBlobPicker() { 84 | // set blob as value 85 | const control = this.formControl; 86 | control.setValue(this._selectedBlobName); 87 | control.markAsDirty(); 88 | this.isBlobPickerActive = false; 89 | } 90 | 91 | public clearBlobReference() { 92 | const control = this.formControl; 93 | control.setValue(null); 94 | control.markAsDirty(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-upload/wy-blob-upload.component.html: -------------------------------------------------------------------------------- 1 | 3 |
5 |
6 | 16 | 17 |
18 | 19 |
20 | 30 | 33 | 34 | 35 |
36 |
37 |
-------------------------------------------------------------------------------- /src/app/components/wy-blob-upload/wy-blob-upload.component.scss: -------------------------------------------------------------------------------- 1 | .wy-blob-upload { 2 | border: 2px solid #efefef; 3 | 4 | .wy-enough-files { 5 | .k-dropzone { 6 | display: none; 7 | } 8 | } 9 | 10 | .k-upload-files { 11 | border: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-viewer/wy-blob-viewer.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | 4 | 5 |
6 | 7 | 8 | 9 |
-------------------------------------------------------------------------------- /src/app/components/wy-blob-viewer/wy-blob-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .wy-blob-viewer { 2 | iframe { 3 | width: 100%; 4 | height: calc(100vh - 335px); 5 | border: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/components/wy-blob-viewer/wy-blob-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject, Input, ViewEncapsulation, ViewChild, OnChanges } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { map } from 'rxjs/operators'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'wy-blob-viewer', 8 | templateUrl: './wy-blob-viewer.component.html', 9 | styleUrls: [ 10 | './wy-blob-viewer.component.scss' 11 | ], 12 | encapsulation: ViewEncapsulation.None 13 | }) 14 | export class WyBlobViewerComponent implements OnInit, OnChanges { 15 | @ViewChild('pdfViewer') public pdfViewer: any; 16 | 17 | @Input() public boxId: string; 18 | 19 | public blob: wy.Blob; 20 | private _isInitiated = false; 21 | 22 | constructor( 23 | @Inject('IBlobService') 24 | private _bs: wy.IBlobService, 25 | 26 | @Inject('ILinkService') 27 | private _ls: wy.ILinkService, 28 | 29 | private http: HttpClient 30 | ) { } 31 | 32 | ngOnInit() { 33 | this.loadPicture(); 34 | this._isInitiated = true; 35 | } 36 | 37 | ngOnChanges() { 38 | if (this._isInitiated) { 39 | this.loadPicture(); 40 | } 41 | } 42 | 43 | private loadPicture() { 44 | this._bs.getFiles(async (blobs) => { 45 | if (blobs.length > 0) { 46 | // viewer supports only 1 blob 47 | this.blob = blobs[0]; 48 | 49 | if (this.blob.IsPdf) { 50 | const uri = this.getBlobUri(this.blob.Name, this.blob.BoxId); 51 | const binary = await this.downloadBlob(uri).toPromise(); 52 | 53 | this.pdfViewer.pdfSrc = binary; 54 | this.pdfViewer.refresh(); 55 | } 56 | } 57 | }, this.boxId); 58 | } 59 | 60 | private downloadBlob(uri: string): Observable { 61 | return this.http 62 | .get(uri, { 63 | responseType: 'blob', 64 | withCredentials: true 65 | }) 66 | .pipe(map(response => { 67 | return response; 68 | })); 69 | } 70 | 71 | public getBlobUri(blobName: string, boxId?: string) { 72 | return this._bs.getBlobUriByName(blobName, boxId, true); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/app/components/wy-comment/wy-comment.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 |
-------------------------------------------------------------------------------- /src/app/components/wy-comment/wy-comment.component.scss: -------------------------------------------------------------------------------- 1 | .wy-comment { 2 | .k-chat { 3 | height: 70vh; 4 | max-width: initial; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/app/components/wy-comment/wy-comment.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, Inject, OnDestroy } from '@angular/core'; 2 | import { Message, User, SendMessageEvent } from '@progress/kendo-angular-conversational-ui'; 3 | 4 | @Component({ 5 | selector: 'wy-comment', 6 | templateUrl: './wy-comment.component.html', 7 | styleUrls: [ 8 | './wy-comment.component.scss' 9 | ] 10 | }) 11 | export class WyCommentComponent implements OnInit, OnDestroy { 12 | 13 | @Input() socialKey: string; 14 | 15 | public feed: Message[]; 16 | 17 | public readonly user: User; 18 | 19 | protected autoRefreshProcessHandle: any; 20 | 21 | constructor( 22 | @Inject('ISocialService') 23 | protected scs: wy.ISocialService, 24 | 25 | @Inject('IUtilService') 26 | private _us: wy.IUtilService, 27 | ) { 28 | 29 | this.feed = []; 30 | 31 | const jwt = this._us.getValidatedJwt(); 32 | 33 | if (jwt) { 34 | this.user = { 35 | id: jwt.username, 36 | name: jwt.username, 37 | avatarUrl: jwt.userImage 38 | }; 39 | } 40 | } 41 | 42 | async ngOnInit() { 43 | this.refreshFeed(); 44 | this.autoRefreshProcessHandle = setInterval(() => this.refreshFeed(), (10 * 1000)); 45 | } 46 | 47 | ngOnDestroy() { 48 | if (this.autoRefreshProcessHandle) { 49 | clearInterval(this.autoRefreshProcessHandle); 50 | } 51 | } 52 | 53 | private async refreshFeed() { 54 | const initFeed = []; 55 | const comments = await this.scs.getComments(this.socialKey).toPromise(); 56 | 57 | for (let comment of comments) { 58 | initFeed.push({ 59 | author: { 60 | id: comment.UserName, 61 | name: comment.UserName, 62 | avatarUrl: comment.UserAvatar || 'assets/img/avatars/default-avatar.png' 63 | }, 64 | timestamp: comment.Created, 65 | text: comment.Text 66 | }); 67 | } 68 | 69 | this.feed = initFeed; 70 | } 71 | 72 | public async sendMessage(e: SendMessageEvent) { 73 | await this.scs.sendComment(this.socialKey, { 74 | Text: e.message.text 75 | }).toPromise(); 76 | 77 | this.feed = this.feed.concat({ 78 | author: this.user, 79 | timestamp: new Date(), 80 | text: e.message.text 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/components/wy-custom.module.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Components 2 | import { NgModule } from '@angular/core'; 3 | import { DependenciesModule } from './dependencies-module'; 4 | import { WyDefaultModule } from './wy-default.module'; 5 | import { WyMapComponent_MyMap } from '../components/wy-map/wy-component-mymap.component'; 6 | import { WyCustomComponent_MyCustomComponent } from '../components/wy-custom/wy-component-mycustomcomponent.component'; 7 | import { WyCustomComponent_DashboardComponent } from '../components/wy-custom/wy-component-dashboardcomponent.component'; 8 | import { WyCustomComponent_ReportsComponent } from '../components/wy-custom/wy-component-reportscomponent.component'; 9 | import { WyCustomComponent_TodoItemsComponent } from '../components/wy-custom/wy-component-todoitemscomponent.component'; 10 | import { WyCustomComponent_MyCustomComponent2 } from '../components/wy-custom/wy-component-mycustomcomponent2.component'; 11 | @NgModule({ 12 | imports: [ 13 | DependenciesModule, 14 | WyDefaultModule, 15 | ], 16 | declarations: [ 17 | WyMapComponent_MyMap, 18 | WyCustomComponent_MyCustomComponent, 19 | WyCustomComponent_DashboardComponent, 20 | WyCustomComponent_ReportsComponent, 21 | WyCustomComponent_TodoItemsComponent, 22 | WyCustomComponent_MyCustomComponent2, 23 | ], 24 | exports: [ 25 | WyMapComponent_MyMap, 26 | WyCustomComponent_MyCustomComponent, 27 | WyCustomComponent_DashboardComponent, 28 | WyCustomComponent_ReportsComponent, 29 | WyCustomComponent_TodoItemsComponent, 30 | WyCustomComponent_MyCustomComponent2, 31 | DependenciesModule, 32 | WyDefaultModule, 33 | ] 34 | }) 35 | export class WyCustomModule { } 36 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-attachmentstuff.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | should show attachment 😴 4 |
5 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-attachmentstuff.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-attachmentstuff { 2 | .stuff { 3 | color: green; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-attachmentstuff.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-attachmentstuff', 4 | templateUrl: './wy-component-attachmentstuff.component.html', 5 | styleUrls: [ 6 | './wy-component-attachmentstuff.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_AttachmentStuff 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-dashboardcomponent.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Dashboard 4 |

5 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-dashboardcomponent.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-dashboardcomponent { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-dashboardcomponent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-dashboardcomponent', 4 | templateUrl: './wy-component-dashboardcomponent.component.html', 5 | styleUrls: [ 6 | './wy-component-dashboardcomponent.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_DashboardComponent { 10 | } 11 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Weather info... 4 |

5 | 6 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-mycustomcomponent { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-mycustomcomponent', 4 | templateUrl: './wy-component-mycustomcomponent.component.html', 5 | styleUrls: [ 6 | './wy-component-mycustomcomponent.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_MyCustomComponent 10 | { 11 | public test() { 12 | var item = new Todo.NgLayer.TodoItem(); 13 | item.Name = 'test123'; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent2.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Test ABC123

3 | 4 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent2.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-mycustomcomponent2 { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-mycustomcomponent2.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-mycustomcomponent2', 4 | templateUrl: './wy-component-mycustomcomponent2.component.html', 5 | styleUrls: [ 6 | './wy-component-mycustomcomponent2.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_MyCustomComponent2 10 | { 11 | public filter = 'Finished eq true'; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-reportscomponent.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-reportscomponent.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-reportscomponent { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-reportscomponent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-reportscomponent', 4 | templateUrl: './wy-component-reportscomponent.component.html', 5 | styleUrls: [ 6 | './wy-component-reportscomponent.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_ReportsComponent 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-todoitemscomponent.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | My Todo list (NOT finished) 4 |

5 |
6 | {{ item.Name }} => {{ item.Owner?.Name }} 7 |
8 |
-------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-todoitemscomponent.component.scss: -------------------------------------------------------------------------------- 1 | .wy-component-todoitemscomponent { 2 | .tdo-item { 3 | background-color: orange; 4 | padding: 5px; 5 | display: block; 6 | width: 100%; 7 | margin-bottom: 3px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/components/wy-custom/wy-component-todoitemscomponent.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | @Component({ 3 | selector: 'wy-component-todoitemscomponent', 4 | templateUrl: './wy-component-todoitemscomponent.component.html', 5 | styleUrls: [ 6 | './wy-component-todoitemscomponent.component.scss' 7 | ] 8 | }) 9 | export class WyCustomComponent_TodoItemsComponent implements OnInit 10 | { 11 | public todoItems: Todo.NgLayer.TodoItem[]; 12 | public filter = 'Finished eq false'; 13 | constructor( 14 | @Inject('IDataService') 15 | private ds: wy.IDataService) { 16 | } 17 | public async ngOnInit() { 18 | this.todoItems = await this.ds.fetch('default', '/TodoItems', this.filter, 'Owner').toPromise() as Todo.NgLayer.TodoItem[]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/components/wy-designer/wy-designer.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 |
5 |
6 |
-------------------------------------------------------------------------------- /src/app/components/wy-designer/wy-designer.component.scss: -------------------------------------------------------------------------------- 1 | .wy-designer { 2 | width: 100%; 3 | height: calc(100vh - 335px); 4 | border: none; 5 | 6 | .gjs-one-bg { 7 | background-color: #414656; 8 | } 9 | 10 | .gjs-two-color { 11 | color: rgba(255, 255, 255, 0.7); 12 | } 13 | 14 | .gjs-three-bg { 15 | background-color: #0269c2; 16 | color: white; 17 | } 18 | 19 | .gjs-four-color, 20 | .gjs-four-color-h:hover { 21 | color: #0269c2; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/wy-designer/wy-designer.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Component, Input, ViewEncapsulation, OnInit, AfterViewInit, Inject, OnChanges } from '@angular/core'; 3 | import { FormGroupDirective, ControlContainer, FormControl } from '@angular/forms'; 4 | import grapesjs from 'grapesjs'; 5 | import 'grapesjs-preset-webpage'; 6 | @Component({ 7 | selector: 'wy-designer', 8 | templateUrl: `./wy-designer.component.html`, 9 | styleUrls: [ 10 | './wy-designer.component.scss', 11 | './../../../../node_modules/grapesjs/dist/css/grapes.min.css', 12 | './../../../../node_modules/grapesjs-preset-webpage/dist/grapesjs-preset-webpage.min.css' 13 | ], 14 | viewProviders: [{ 15 | provide: ControlContainer, 16 | useExisting: FormGroupDirective 17 | }], 18 | encapsulation: ViewEncapsulation.None 19 | }) 20 | export class WyDesignerComponent implements OnInit, OnChanges, AfterViewInit { 21 | private _editor: any; 22 | public formControl: FormControl; 23 | private _isInitiated = false; 24 | private _formControlName: string; 25 | @Input() public entityId: string; 26 | constructor(private parentForm: FormGroupDirective, 27 | @Inject('IUtilService') 28 | private _us: wy.IUtilService) { 29 | } 30 | @Input() 31 | set controlName(value: string) { 32 | this._formControlName = value; 33 | this.init(); 34 | } 35 | ngOnInit() { 36 | this.init(); 37 | this._isInitiated = true; 38 | } 39 | ngOnChanges() { 40 | if (this._isInitiated) { 41 | this.init(); 42 | } 43 | } 44 | private init() { 45 | if (this._formControlName) { 46 | this.formControl = this.parentForm.form.get(this._formControlName) as FormControl; 47 | } 48 | } 49 | ngAfterViewInit() { 50 | const jwt: any = this._us.getValidatedJwt(); 51 | const appPrefix = jwt.appPrefix; 52 | let cssControl = this.getCssControl(); 53 | this._editor = this.initializeEditor(this.formControl.value, cssControl.value, appPrefix); 54 | this._editor.on('storage:start', (e) => { 55 | let html = this._editor.getHtml(); 56 | let css = this._editor.getCss(); 57 | this.formControl.setValue(html); 58 | this.formControl.markAsDirty(); 59 | let cssControl = this.getCssControl(); 60 | cssControl.setValue(css); 61 | cssControl.markAsDirty(); 62 | }); 63 | } 64 | private getCssControl() { 65 | return this.parentForm.form.get(this._formControlName + '_Css'); 66 | } 67 | private initializeEditor(html: string, css: string, prefix: string): any { 68 | return grapesjs.init({ 69 | container: '#gjs', 70 | autorender: true, 71 | forceClass: false, 72 | components: html + ``, 73 | height: '100%', 74 | style: '', 75 | plugins: ['gjs-preset-webpage'], 76 | pluginsOpts: { 77 | 'gjs-preset-webpage': { 78 | navbarOpts: false, 79 | countdownOpts: false, 80 | formsOpts: false, 81 | blocksBasicOpts: { 82 | blocks: ['link-block', 'quote', 'image', 'video', 'text', 'column1', 'column2', 'column3'], 83 | flexGrid: false, 84 | stylePrefix: prefix + '-' 85 | } 86 | } 87 | }, 88 | storageManager: { 89 | type: 'onChange' 90 | }, 91 | assetManager: { 92 | embedAsBase64: true 93 | }, 94 | canvas: { 95 | styles: [ 96 | ], 97 | scripts: [ 98 | ] 99 | } 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/app/components/wy-editor/wy-editor.component.scss: -------------------------------------------------------------------------------- 1 | .wy-editor { 2 | } 3 | -------------------------------------------------------------------------------- /src/app/components/wy-editor/wy-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, Inject, Input } from '@angular/core'; 2 | import { FormControl, FormGroupDirective, ControlContainer } from '@angular/forms'; 3 | import { EditorComponent } from '@progress/kendo-angular-editor'; 4 | 5 | @Component({ 6 | selector: 'wy-editor', 7 | templateUrl: `./wy-editor.component.html`, 8 | styleUrls: [ 9 | './wy-editor.component.scss' 10 | ], 11 | viewProviders: [{ 12 | provide: ControlContainer, 13 | useExisting: FormGroupDirective 14 | }] 15 | }) 16 | export class WyEditorComponent { 17 | public isBlobPickerActive = false; 18 | public isBlobPickerImagesOnly = false; 19 | 20 | public blobDialogWidth: number; 21 | public blobDialogHeight: number; 22 | 23 | private _selectedBlobName: string; 24 | 25 | @ViewChild('editor') 26 | private _editor: EditorComponent = null; 27 | 28 | public formControl: FormControl; 29 | private _isInitiated = false; 30 | private _formControlName: string; 31 | 32 | @Input() public entityId: string; 33 | 34 | constructor(private parentForm: FormGroupDirective, 35 | @Inject('IBlobService') 36 | private bs: wy.IBlobService) { 37 | 38 | if (window.innerWidth > 992 && window.innerHeight > 720) { 39 | this.blobDialogWidth = window.innerWidth - 400; 40 | this.blobDialogHeight = window.innerHeight - 200; 41 | } 42 | else { 43 | this.blobDialogWidth = window.innerWidth - 10; 44 | this.blobDialogHeight = window.innerHeight - 10; 45 | } 46 | } 47 | 48 | @Input() 49 | set controlName(value: string) { 50 | this._formControlName = value; 51 | this.init(); 52 | } 53 | 54 | ngOnInit() { 55 | this.init(); 56 | this._isInitiated = true; 57 | } 58 | 59 | ngOnChanges() { 60 | if (this._isInitiated) { 61 | this.init(); 62 | } 63 | } 64 | 65 | private init() { 66 | if (this._formControlName) { 67 | this.formControl = this.parentForm.form.get(this._formControlName) as FormControl; 68 | } 69 | } 70 | 71 | public blobSelectedHandler(blobName: string) { 72 | this._selectedBlobName = blobName; 73 | } 74 | 75 | public openBlobPicker(imagesOnly: boolean) { 76 | this.isBlobPickerActive = true; 77 | this.isBlobPickerImagesOnly = imagesOnly; 78 | } 79 | 80 | public cancelBlobPicker() { 81 | this.isBlobPickerActive = false; 82 | } 83 | 84 | public selectBlobPicker() { 85 | // insert blob in editor 86 | const uri = this.bs.getBlobUriByName(this._selectedBlobName); 87 | 88 | if (this.isBlobPickerImagesOnly) { 89 | this._editor.exec('insertImage', { src: uri, alt: this._selectedBlobName, title: this._selectedBlobName }); 90 | } 91 | else { 92 | this._editor.exec('insertFile', { href: uri, title: this._selectedBlobName }); 93 | this._editor.exec('insertText', { text: this._selectedBlobName }); 94 | } 95 | 96 | this.isBlobPickerActive = false; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/app/components/wy-entity-card/wy-entity-card-base.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Input, Inject, OnInit, Output, EventEmitter, Directive } from '@angular/core'; 3 | import { WyBaseComponent } from '../wy-base.component'; 4 | @Directive() 5 | export abstract class WyEntityCardBaseComponent extends WyBaseComponent implements OnInit { 6 | @Input() public entity: wy.BaseEntity; 7 | @Input() public selected: boolean; 8 | @Output() onSelect: EventEmitter = new EventEmitter(); 9 | @Output() onDelete: EventEmitter = new EventEmitter(); 10 | public pictureUri: string = 'https://estiom.com/cdn/wyStack/no-picture.png'; 11 | protected abstract getPicturePropertyType(): string; 12 | protected abstract getPicturePropertyName(): string; 13 | constructor( 14 | @Inject('IBlobService') 15 | private _bs: wy.IBlobService 16 | ) { 17 | super(); 18 | } 19 | ngOnInit() { 20 | this.loadPicture(); 21 | } 22 | triggerOnDelete() { 23 | this.onDelete.emit(); 24 | } 25 | public triggerOnOpen() { 26 | this.onSelect.emit(); 27 | } 28 | private loadPicture() { 29 | const picturePropertyName = this.getPicturePropertyName(); 30 | const picturePropertyType = this.getPicturePropertyType(); 31 | const value = this.entity[picturePropertyName]; 32 | if (!value) { 33 | return; 34 | } 35 | if (picturePropertyType == 'PictureUrl') { 36 | this.pictureUri = value; 37 | } 38 | if (picturePropertyType == 'Pictures' || picturePropertyType == 'Picture') { 39 | this._bs.getFiles(async (blobs) => { 40 | if (blobs.length > 0) { 41 | this.pictureUri = this._bs.getBlobUriByName(blobs[0].Name, blobs[0].BoxId, true); 42 | } 43 | }, value); 44 | } 45 | if (picturePropertyType == 'PictureReference') { 46 | this.pictureUri = this._bs.getBlobUriByName(value); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/components/wy-entity-card/wy-entity-card.component.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | .wy-entity-card { 3 | display: table-cell; 4 | padding-right: 8px; 5 | padding-bottom: 8px; 6 | cursor: pointer; 7 | .wy-card-media-image { 8 | height: 250px; 9 | background-size: cover; 10 | background-color: #efefef; 11 | } 12 | kendo-card { 13 | width: 250px !important; 14 | } 15 | &.wy-entity-card-selected { 16 | kendo-card { 17 | background-color: #efefef; 18 | } 19 | } 20 | } 21 | @media (max-width: 568px) { 22 | .wy-entity-card { 23 | display: block; 24 | kendo-card { 25 | width: 100% !important; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/components/wy-entity-dialog/wy-entity-dialog.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 |
6 | 7 |
8 |
9 | 10 | View 11 | 12 | 13 | Create 14 | 15 | 16 | Edit 17 | 18 | - 19 | 20 | {{ title }} 21 | 22 | 23 | - {{ entity.Name }} 24 | 25 |
26 |
27 | 28 | 29 | 33 | 37 | 41 | 42 |
-------------------------------------------------------------------------------- /src/app/components/wy-entity-dialog/wy-entity-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/components/wy-entity-dialog/wy-entity-dialog.component.scss -------------------------------------------------------------------------------- /src/app/components/wy-entity-dialog/wy-entity-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wy-entity-dialog', 5 | templateUrl: './wy-entity-dialog.component.html', 6 | styleUrls: [ 7 | './wy-entity-dialog.component.scss' 8 | ] 9 | }) 10 | export class WyEntityDialogComponent { 11 | 12 | @Input() public title: string; 13 | @Input() public entity: wy.BaseEntity; 14 | @Input() public readonly: boolean; 15 | @Input() public isNew: boolean; 16 | @Input() public color: string; 17 | @Input() public icon: string = 'fas fa-dot-circle'; 18 | 19 | @Output() save: EventEmitter = new EventEmitter(); 20 | @Output() cancel: EventEmitter = new EventEmitter(); 21 | 22 | public dialogWidth: number; 23 | public dialogHeight: number; 24 | 25 | constructor() { 26 | if (window.innerWidth > 992 && window.innerHeight > 720) { 27 | this.dialogWidth = window.innerWidth - 300; 28 | this.dialogHeight = window.innerHeight - 100; 29 | } 30 | else { 31 | this.dialogWidth = window.innerWidth - 10; 32 | this.dialogHeight = window.innerHeight - 10; 33 | } 34 | } 35 | 36 | public cancelHandler() { 37 | this.cancel.emit(); 38 | } 39 | 40 | public saveHandler(saveEvent: wy.ISaveEvent) { 41 | this.save.emit(saveEvent); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/components/wy-entity-window/wy-entity-window.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | View 14 | 15 | 16 | Create 17 | 18 | 19 | Edit 20 | 21 | - 22 | 23 | {{ title }} 24 | 25 | 26 | - {{ entity.Name }} 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 | 43 | 47 | 51 |
52 |
53 |
-------------------------------------------------------------------------------- /src/app/components/wy-entity-window/wy-entity-window.component.scss: -------------------------------------------------------------------------------- 1 | .wy-entity-window { 2 | z-index: 10001 !important; // disables bringToFront feature 3 | 4 | .wy-entity-window-contents { 5 | padding: 16px; 6 | height: calc(100% - 36px); 7 | overflow: auto; 8 | } 9 | 10 | > .k-window-content { 11 | padding: 0 !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/components/wy-entity-window/wy-entity-window.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, TemplateRef, ViewChild, AfterViewInit, OnDestroy, ApplicationRef, ComponentFactoryResolver, Injector, EmbeddedViewRef, ViewEncapsulation, OnChanges, ChangeDetectorRef } from '@angular/core'; 2 | import { CdkPortal, DomPortalOutlet } from '@angular/cdk/portal'; 3 | import { PortalHostDivId } from '../constants'; 4 | 5 | @Component({ 6 | selector: 'wy-entity-window', 7 | templateUrl: './wy-entity-window.component.html', 8 | styleUrls: [ 9 | './wy-entity-window.component.scss' 10 | ], 11 | encapsulation: ViewEncapsulation.None 12 | }) 13 | export class WyEntityWindowComponent implements AfterViewInit, OnDestroy { 14 | 15 | @ViewChild(CdkPortal) 16 | portal: CdkPortal; 17 | 18 | @Input() public title: string; 19 | @Input() public entity: wy.BaseEntity; 20 | @Input() public readonly: boolean; 21 | @Input() public isNew: boolean; 22 | @Input() public color: string; 23 | @Input() public icon: string = 'fas fa-dot-circle'; 24 | 25 | @Output() save: EventEmitter = new EventEmitter(); 26 | @Output() cancel: EventEmitter = new EventEmitter(); 27 | 28 | @ViewChild('windowTitleBar', { static: true }) windowTitleBar: TemplateRef; 29 | @ViewChild('windowContent', { static: true }) windowContent: TemplateRef; 30 | 31 | public windowWidth: number; 32 | public windowHeight: number; 33 | public windowTop: number; 34 | public windowLeft: number; 35 | 36 | private embeddedViewRef: EmbeddedViewRef; 37 | 38 | constructor( 39 | private cfr: ComponentFactoryResolver, 40 | private ar: ApplicationRef, 41 | private cd: ChangeDetectorRef, 42 | private injector: Injector) { 43 | const existingWindowCount = document.getElementsByTagName('wy-entity-window').length; 44 | 45 | if (window.innerWidth > 992 && window.innerHeight > 720) { 46 | const marginLeft = (existingWindowCount * 100); 47 | const marginTop = 70 + (existingWindowCount * 60); 48 | const marginBottom = 3; 49 | 50 | this.windowLeft = (window.innerWidth / 3) + marginLeft; 51 | this.windowTop = marginTop; 52 | this.windowWidth = window.innerWidth - this.windowLeft; 53 | this.windowHeight = window.innerHeight - this.windowTop - marginBottom; 54 | } 55 | else { 56 | this.windowWidth = window.innerWidth; 57 | this.windowHeight = window.innerHeight; 58 | this.windowTop = 0; 59 | this.windowLeft = 0; 60 | } 61 | } 62 | 63 | ngAfterViewInit() { 64 | this.embeddedViewRef = new DomPortalOutlet( 65 | document.getElementById(PortalHostDivId), 66 | this.cfr, 67 | this.ar, 68 | this.injector 69 | ).attachTemplatePortal(this.portal); 70 | } 71 | 72 | ngOnDestroy() { 73 | this.embeddedViewRef.destroy(); 74 | } 75 | 76 | public cancelHandler() { 77 | this.cancel.emit(); 78 | } 79 | 80 | public saveHandler(saveEvent: wy.ISaveEvent) { 81 | this.save.emit(saveEvent); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/components/wy-enum-filter/wy-enum-filter.component.html: -------------------------------------------------------------------------------- 1 |  9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/components/wy-enum-filter/wy-enum-filter.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/components/wy-enum-filter/wy-enum-filter.component.scss -------------------------------------------------------------------------------- /src/app/components/wy-enum-filter/wy-enum-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, TemplateRef } from '@angular/core'; 2 | import { CompositeFilterDescriptor } from '@progress/kendo-data-query'; 3 | import { FilterService, BaseFilterCellComponent } from '@progress/kendo-angular-grid'; 4 | 5 | @Component({ 6 | selector: 'wy-enum-filter', 7 | templateUrl: './wy-enum-filter.component.html', 8 | styleUrls: [ 9 | './wy-enum-filter.component.scss' 10 | ] 11 | }) 12 | export class WyEnumFilterComponent extends BaseFilterCellComponent { 13 | 14 | public get selectedValue(): any { 15 | const filter = this.filterByField(this.valueField); 16 | return filter ? filter.value : null; 17 | } 18 | 19 | @Input() public filter: CompositeFilterDescriptor; 20 | @Input() public data: any[]; 21 | @Input() public valueField: string; 22 | @Input() public filterField: string; 23 | @Input() public enumValueTemplate: TemplateRef; 24 | 25 | public get defaultItem(): any { 26 | return { 27 | [this.valueField]: null 28 | }; 29 | } 30 | 31 | constructor(filterService: FilterService) { 32 | super(filterService); 33 | } 34 | 35 | public onChange(value: any): void { 36 | this.applyFilter( 37 | value === null ? 38 | this.removeFilter(this.filterField) : 39 | this.updateFilter({ 40 | field: this.filterField, 41 | operator: 'eq', 42 | value: value 43 | }) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid-column.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnChanges, OnInit } from '@angular/core'; 2 | import { ColumnComponent } from '@progress/kendo-angular-grid'; 3 | 4 | @Directive({ 5 | selector: '[wyGridColumn]' 6 | }) 7 | export class WyGridColumnDirective implements OnChanges { 8 | 9 | @Input() gcd: wy.IWyGridColumnDefinition; 10 | 11 | constructor(private column: ColumnComponent) { 12 | } 13 | 14 | ngOnChanges() { 15 | if (this.gcd.minColWidth) { 16 | this.column.minResizableWidth = +this.gcd.minColWidth; 17 | } 18 | 19 | if (this.gcd.width) { 20 | this.column.width = +this.gcd.width; 21 | } 22 | 23 | this.column.media = this.gcd.media; 24 | this.column.format = this.gcd.format; 25 | this.column.field = this.gcd.field; 26 | this.column.filter = this.gcd.filter as 'text' | 'numeric' | 'boolean' | 'date'; 27 | this.column.filterable = this.gcd.filter != ''; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid-toolbar.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 |
5 | 6 |
7 | 15 |
16 | 21 | 26 | 31 |
32 | 33 | 34 | 35 | 37 | More 38 | 39 | 40 | 41 | Export 42 | 43 | 44 | 45 | PDF 46 | 47 | 48 | 49 | Pivot 50 | 51 | 52 | 53 | Recycle bin 54 | 55 | 56 | 57 | 58 |
-------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid-toolbar.component.scss: -------------------------------------------------------------------------------- 1 | .wy-grid-toolbar { 2 | margin-bottom: 3px; 3 | 4 | > div { 5 | display: inline-block; 6 | } 7 | 8 | button { 9 | margin-right: 5px; 10 | margin-bottom: 3px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid-toolbar.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Component, Input, Output, EventEmitter, ViewEncapsulation, OnChanges, OnInit, Inject } from '@angular/core'; 3 | export enum ToolbarActionType { 4 | OpenGridConfiguration = 1, 5 | ToggleFilter = 2, 6 | ToggleGroupBy = 3, 7 | ExportExcel = 4, 8 | ExportPDF = 5, 9 | SwitchGridViewMode = 6, 10 | OpenPivot = 7, 11 | OpenRecycleBin = 8, 12 | } 13 | @Component({ 14 | encapsulation: ViewEncapsulation.None, 15 | selector: 'wy-grid-toolbar', 16 | templateUrl: './wy-grid-toolbar.component.html', 17 | styleUrls: [ 18 | './wy-grid-toolbar.component.scss' 19 | ] 20 | }) 21 | export class WyGridToolbarComponent implements OnInit, OnChanges { 22 | toolbarActionType = ToolbarActionType; 23 | @Input() public showNew: boolean; 24 | @Input() public showToolbar: boolean | 'Simple' = true; 25 | @Input() public showGridViewMode: boolean; 26 | @Input() public gridViewMode: 'Columns' | 'Cards'; 27 | @Output() onAction: EventEmitter = new EventEmitter(); 28 | public primaryToolbarItems: any[]; 29 | public secondaryToolbarItems: any[]; 30 | constructor( 31 | @Inject('IAuthorizationService') 32 | private _azs: wy.IAuthorizationService, 33 | @Inject('IUserSettingsService') 34 | private _usrSet: wy.IUserSettingsService) { 35 | } 36 | ngOnInit() { 37 | this.initToolbarItems(); 38 | } 39 | ngOnChanges() { 40 | this.initToolbarItems(); 41 | } 42 | private initToolbarItems() { 43 | this.primaryToolbarItems = []; 44 | this.secondaryToolbarItems = []; 45 | if (this.gridViewMode == 'Columns') { 46 | if (this.isFeatureEnabled('Common_GridConfiguration')) { 47 | this.primaryToolbarItems.push({ 48 | action: this.toolbarActionType.OpenGridConfiguration 49 | }); 50 | } 51 | if (this.isFeatureEnabled('Common_GridFilter')) { 52 | this.primaryToolbarItems.push({ 53 | action: this.toolbarActionType.ToggleFilter 54 | }); 55 | } 56 | if (this.isFeatureEnabled('Common_GridGroupBy')) { 57 | this.primaryToolbarItems.push({ 58 | action: this.toolbarActionType.ToggleGroupBy 59 | }); 60 | } 61 | } 62 | else { 63 | this.primaryToolbarItems = []; 64 | } 65 | if (this.gridViewMode == 'Columns') { 66 | if (this.isFeatureEnabled('Common_ExportExcel')) { 67 | this.secondaryToolbarItems.push({ 68 | action: this.toolbarActionType.ExportExcel 69 | }); 70 | } 71 | if (this.isFeatureEnabled('Common_ExportPDF')) { 72 | this.secondaryToolbarItems.push({ 73 | action: this.toolbarActionType.ExportPDF 74 | }); 75 | } 76 | } 77 | } 78 | public isFeatureEnabled(featureKey: string) { 79 | if (this._azs.isFeatureEnabled(featureKey)) { 80 | return this._usrSet.isFeatureEnabled(featureKey); 81 | } 82 | return false; 83 | } 84 | public triggerAction(action: ToolbarActionType) { 85 | this.onAction.emit(action); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid.component.scss: -------------------------------------------------------------------------------- 1 | .wy-grid { 2 | .wy-removed td { 3 | color: red !important; 4 | } 5 | 6 | td { 7 | .wy-button { 8 | margin-bottom: 3px; 9 | } 10 | 11 | .wy-coin { 12 | cursor: pointer; 13 | } 14 | 15 | .wy-cls-entity-actions { 16 | button { 17 | border-radius: 0; 18 | } 19 | } 20 | } 21 | 22 | tr { 23 | cursor: pointer; 24 | } 25 | 26 | .kendo-template { 27 | display: none; 28 | } 29 | 30 | .k-grid .k-detail-row { 31 | .wy-grid { 32 | box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12); 33 | } 34 | 35 | .wy-grid-toolbar { 36 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 37 | padding: 5px 5px 2px 5px; 38 | } 39 | 40 | .wy-cls-grid-settings, 41 | .wy-cls-grid-more { 42 | display: none; 43 | } 44 | 45 | .wy-label-indent { 46 | display: none !important; 47 | } 48 | 49 | .k-hierarchy-cell, 50 | .k-detail-cell { 51 | background-color: white; 52 | 53 | &:hover { 54 | background-color: white; 55 | } 56 | } 57 | } 58 | } 59 | 60 | .wy-entity-grid-actions-dropdown { 61 | width: 200px; 62 | } 63 | 64 | .wy-pivot { 65 | .wdr-credits { 66 | display: none; 67 | } 68 | } 69 | 70 | .wy-no-records { 71 | text-align: center; 72 | margin: 10px; 73 | } 74 | 75 | .wy-uom-value { 76 | display: inline-block; 77 | } 78 | 79 | .wy-cls-create-new-item { 80 | width: 250px; 81 | line-height: 45px; 82 | } 83 | 84 | .wy-grid-card-view { 85 | margin-top: 5px; 86 | background-color: #fff; 87 | } 88 | 89 | .k-pager { 90 | border: 0; 91 | } 92 | -------------------------------------------------------------------------------- /src/app/components/wy-grid/wy-grid.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input } from '@angular/core'; 2 | import { GridComponent } from '@progress/kendo-angular-grid'; 3 | import { State } from '@progress/kendo-data-query'; 4 | 5 | @Directive({ 6 | selector: '[wyGrid]' 7 | }) 8 | export class WyGridDirective { 9 | 10 | @Input() state: State; 11 | 12 | constructor(private grid: GridComponent) { 13 | grid.sortable = true; 14 | grid.reorderable = true; 15 | grid.selectable = true; 16 | grid.resizable = true; 17 | grid.navigable = true; 18 | grid.scrollable = 'none'; 19 | grid.sortable = true; 20 | grid.pageable = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/components/wy-html/wy-html.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/components/wy-html/wy-html.component.scss -------------------------------------------------------------------------------- /src/app/components/wy-image-picker/wy-image-picker.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 | 8 |   9 | 13 |

14 |
15 | 16 | {{formControl.value}} 17 | 18 |
19 | 21 |
23 |
24 |
25 | 28 | 29 |
30 | 31 | 34 | 35 | 37 | 38 | Select image 39 | 40 | Select file 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /src/app/components/wy-image-picker/wy-image-picker.component.scss: -------------------------------------------------------------------------------- 1 | .wy-image-picker { 2 | a.wy-blob-link { 3 | display: block; 4 | margin-top: 5px; 5 | margin-bottom: 5px; 6 | } 7 | 8 | .wy-picture-focus-point-box { 9 | position: relative; 10 | width: 300px; 11 | 12 | img { 13 | border: 1px solid #ccc; 14 | width: 300px; 15 | } 16 | 17 | .wy-picture-focus-point { 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | width: 20px; 22 | height: 20px; 23 | background-color: red; 24 | opacity: 0.8; 25 | margin-top: -10px; 26 | margin-left: -10px; 27 | border-radius: 20px !important; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/wy-image-picker/wy-image-picker.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, Input, OnChanges, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroupDirective, ControlContainer } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'wy-image-picker', 6 | templateUrl: `./wy-image-picker.component.html`, 7 | styleUrls: [ 8 | './wy-image-picker.component.scss' 9 | ], 10 | viewProviders: [{ 11 | provide: ControlContainer, 12 | useExisting: FormGroupDirective 13 | }] 14 | }) 15 | export class WyImagePickerComponent implements OnInit, OnChanges { 16 | public isBlobPickerActive = false; 17 | 18 | public blobDialogWidth: number; 19 | public blobDialogHeight: number; 20 | 21 | private _selectedBlobName: string; 22 | 23 | public formControl: FormControl; 24 | private _isInitiated = false; 25 | private _formControlName: string; 26 | 27 | @Input() public entityId: string; 28 | @Input() enablePictureFocusPoint: boolean; 29 | 30 | constructor(private parentForm: FormGroupDirective, 31 | @Inject('IBlobService') 32 | private bs: wy.IBlobService) { 33 | 34 | if (window.innerWidth > 992 && window.innerHeight > 720) { 35 | this.blobDialogWidth = window.innerWidth - 400; 36 | this.blobDialogHeight = window.innerHeight - 200; 37 | } 38 | else { 39 | this.blobDialogWidth = window.innerWidth - 10; 40 | this.blobDialogHeight = window.innerHeight - 10; 41 | } 42 | } 43 | 44 | @Input() 45 | set controlName(value: string) { 46 | this._formControlName = value; 47 | this.init(); 48 | } 49 | 50 | ngOnInit() { 51 | this.init(); 52 | this._isInitiated = true; 53 | } 54 | 55 | ngOnChanges() { 56 | if (this._isInitiated) { 57 | this.init(); 58 | } 59 | } 60 | 61 | private init() { 62 | if (this._formControlName) { 63 | this.formControl = this.parentForm.form.get(this._formControlName) as FormControl; 64 | } 65 | } 66 | 67 | public getBlobUri(blobName: string) { 68 | return this.bs.getBlobUriByName(blobName); 69 | } 70 | 71 | public blobSelectedHandler(blobName: string) { 72 | this._selectedBlobName = blobName; 73 | } 74 | 75 | public openBlobPicker() { 76 | this.isBlobPickerActive = true; 77 | } 78 | 79 | public cancelBlobPicker() { 80 | this.isBlobPickerActive = false; 81 | } 82 | 83 | public selectBlobPicker() { 84 | // set blob as value 85 | const control = this.formControl; 86 | control.setValue(this._selectedBlobName); 87 | control.markAsDirty(); 88 | this.isBlobPickerActive = false; 89 | } 90 | 91 | public clearBlobReference() { 92 | const control = this.formControl; 93 | control.setValue(null); 94 | control.markAsDirty(); 95 | } 96 | 97 | public getPosXControl() { 98 | return this.parentForm.form.get(this._formControlName + '_PosX'); 99 | } 100 | 101 | public getPosYControl() { 102 | return this.parentForm.form.get(this._formControlName + '_PosY'); 103 | } 104 | 105 | public getSizeControl() { 106 | return this.parentForm.form.get(this._formControlName + '_Size'); 107 | } 108 | 109 | // set picture focus point 110 | public setPictureFocusPoint(event: MouseEvent) { 111 | console.log(event); 112 | 113 | const img = event.target as Element; 114 | const rect = img.getBoundingClientRect(); 115 | const posXControl = this.getPosXControl(); 116 | const posYControl = this.getPosYControl(); 117 | 118 | // calculate absolute position 119 | const left = (event.clientX - rect.left); 120 | const top = (event.clientY - rect.top); 121 | 122 | // calculate position in percentage 123 | const posX = Math.round((left / img.clientWidth) * 100); 124 | const posY = Math.round((top / img.clientHeight) * 100); 125 | 126 | // set values 127 | posXControl.setValue(posX); 128 | posXControl.markAsDirty(); 129 | 130 | posYControl.setValue(posY); 131 | posYControl.markAsDirty(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/app/components/wy-image-viewer/wy-image-viewer.component.html: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | View {{ title }} {{ prettySize }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/components/wy-image-viewer/wy-image-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .wy-full-image { 2 | border: 1px solid #ccc; 3 | height: 100%; 4 | margin: auto; 5 | display: block; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/components/wy-image-viewer/wy-image-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, Inject } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wy-image-viewer', 5 | templateUrl: './wy-image-viewer.component.html', 6 | styleUrls: [ 7 | './wy-image-viewer.component.scss' 8 | ] 9 | }) 10 | export class WyImageViewerComponent implements OnInit { 11 | 12 | @Input() public title: string; 13 | @Input() public src: string; 14 | @Input() public largeThumbnail: boolean; 15 | @Input() public size: number; 16 | 17 | public isImageViewerOpen: boolean; 18 | public prettySize = ''; 19 | 20 | public dialogWidth: number; 21 | public dialogHeight: number; 22 | 23 | constructor( 24 | @Inject('IUtilService') 25 | private _us: wy.IUtilService) { 26 | if (window.innerWidth > 992 && window.innerHeight > 720) { 27 | this.dialogWidth = window.innerWidth - 150; 28 | this.dialogHeight = window.innerHeight - 50; 29 | } 30 | else { 31 | this.dialogWidth = window.innerWidth - 10; 32 | this.dialogHeight = window.innerHeight - 10; 33 | } 34 | } 35 | 36 | openImageViewer(event: Event) { 37 | this.isImageViewerOpen = true; 38 | event.stopPropagation(); 39 | } 40 | 41 | closeImageViewer() { 42 | this.isImageViewerOpen = false; 43 | } 44 | 45 | ngOnInit() { 46 | if (this.size > 0) { 47 | this.prettySize = '(' + this._us.formatBytes(this.size, 1) + ')'; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/components/wy-lookup-filter/wy-lookup-filter.component.html: -------------------------------------------------------------------------------- 1 |  11 | -------------------------------------------------------------------------------- /src/app/components/wy-lookup-filter/wy-lookup-filter.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/components/wy-lookup-filter/wy-lookup-filter.component.scss -------------------------------------------------------------------------------- /src/app/components/wy-lookup-filter/wy-lookup-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Inject, OnInit } from '@angular/core'; 2 | import { CompositeFilterDescriptor } from '@progress/kendo-data-query'; 3 | import { FilterService, BaseFilterCellComponent } from '@progress/kendo-angular-grid'; 4 | 5 | @Component({ 6 | selector: 'wy-lookup-filter', 7 | templateUrl: './wy-lookup-filter.component.html', 8 | styleUrls: [ 9 | './wy-lookup-filter.component.scss' 10 | ] 11 | }) 12 | export class WyLookupFilterComponent extends BaseFilterCellComponent implements OnInit { 13 | 14 | public get selectedValue(): any { 15 | const filter = this.filterByField(this.valueField); 16 | return filter ? filter.value : null; 17 | } 18 | 19 | @Input() public filter: CompositeFilterDescriptor; 20 | @Input() public dataStoreName: string; 21 | @Input() public dataSourceUri: string; 22 | @Input() public textField: string; 23 | @Input() public valueField: string; 24 | @Input() public filterField: string; 25 | 26 | public lookupEntityData: any[]; 27 | public filteredLookupEntityData: any[]; 28 | 29 | public get defaultItem(): any { 30 | return { 31 | [this.textField]: '-', 32 | [this.valueField]: null 33 | }; 34 | } 35 | 36 | constructor(filterService: FilterService, 37 | 38 | @Inject('IDataService') 39 | private _ds: wy.IDataService) { 40 | super(filterService); 41 | } 42 | 43 | async ngOnInit() { 44 | const data = await this._ds.fetch(this.dataStoreName, this.dataSourceUri).toPromise(); 45 | this.lookupEntityData = data; 46 | this.filteredLookupEntityData = data; 47 | } 48 | 49 | public onChange(value: any): void { 50 | this.applyFilter( 51 | value === null ? 52 | this.removeFilter(this.filterField) : 53 | this.updateFilter({ 54 | field: this.filterField, 55 | operator: 'eq', 56 | value: value 57 | }) 58 | ); 59 | } 60 | 61 | public filterChange(filter: any): void { 62 | this.filteredLookupEntityData = this.lookupEntityData.filter( 63 | (item) => { 64 | if (!item) { 65 | return false; 66 | } 67 | 68 | if (!item[this.textField]) { 69 | return false; 70 | } 71 | 72 | return item[this.textField].toLowerCase().indexOf(filter.toLowerCase()) !== -1; 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/app/components/wy-map-point-editor/wy-map-point-editor.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/app/components/wy-map-point-editor/wy-map-point-editor.component.scss: -------------------------------------------------------------------------------- 1 | .wy-map-point-editor-container { 2 | .wy-map-point-editor { 3 | height: 50vh; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/wy-map-polygon-editor/wy-map-polygon-editor.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/components/wy-map-polygon-editor/wy-map-polygon-editor.component.scss: -------------------------------------------------------------------------------- 1 | .wy-map-polygon-editor-container { 2 | .wy-map-polygon-editor { 3 | height: 50vh; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/wy-map/wy-component-mymap.component.html: -------------------------------------------------------------------------------- 1 |

Please wait...

2 |
3 | Cannot display map. No card data found. 4 |
5 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/components/wy-map/wy-map.component.scss: -------------------------------------------------------------------------------- 1 | .wy-map { 2 | height: 70vh; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/wy-monaco.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { WyMonacoComponent } from './wy-monaco/wy-monaco.component'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | declarations: [ 10 | WyMonacoComponent 11 | ], 12 | exports: [ 13 | WyMonacoComponent 14 | ] 15 | }) 16 | export class WyMonacoModule { } 17 | -------------------------------------------------------------------------------- /src/app/components/wy-monaco/wy-monaco.component.scss: -------------------------------------------------------------------------------- 1 | .wy-monaco-editor-host { 2 | border: 1px solid #ebebeb; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/wy-one-entity/wy-component-defaultoneentity_socialcomment.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation, OnInit, Inject, ChangeDetectorRef } from '@angular/core'; 2 | import { Validators, FormGroup, FormControl } from '@angular/forms'; 3 | import { IntlService } from '@progress/kendo-angular-intl'; 4 | import { Subject } from 'rxjs'; 5 | import { takeUntil } from 'rxjs/operators'; 6 | import { DynamicObjectExpressionApi } from '../types'; 7 | import { WyOneEntityBaseComponent } from './wy-one-entity-base.component'; 8 | @Component({ 9 | selector: 'wy-component-defaultoneentity_socialcomment', 10 | templateUrl: './wy-component-defaultoneentity_socialcomment.component.html', 11 | styleUrls: ['./wy-one-entity.component.scss'], 12 | encapsulation: ViewEncapsulation.None 13 | }) 14 | export class WyOneEntityComponent_DefaultOneEntity_SocialComment extends WyOneEntityBaseComponent implements OnInit { 15 | public editForm: FormGroup; 16 | public exp: DynamicObjectExpressionApi; 17 | // bindings for lookup entity/entities, enum, compute, files and pictures 18 | public properties: any = { 19 | } 20 | public events: any = { 21 | } 22 | private _editFormSubscription: Subject; 23 | constructor( 24 | cd: ChangeDetectorRef, 25 | @Inject('IAuthorizationService') 26 | azs: wy.IAuthorizationService, 27 | @Inject('IUserSettingsService') 28 | usrSet: wy.IUserSettingsService, 29 | @Inject('IDataService') 30 | ds: wy.IDataService, 31 | @Inject('IConstantService') 32 | cs: wy.IConstantService, 33 | @Inject('IUtilService') 34 | us: wy.IUtilService, 35 | @Inject('ILinkService') 36 | lns: wy.ILinkService, 37 | @Inject('IBlobService') 38 | bs: wy.IBlobService, 39 | @Inject('ILanguageService') 40 | ls: wy.ILanguageService, 41 | protected intl: IntlService, 42 | @Inject('IMessageService') 43 | ms: wy.IMessageService 44 | ) { 45 | super(cd, azs, usrSet, ds, cs, us, lns, bs, ls, intl, ms); 46 | this.createNewEditForm(); 47 | } 48 | // create the form group with validation rules 49 | public createNewEditForm() { 50 | this.editForm = new FormGroup({ 51 | '@odata.type': new FormControl(), 52 | 'Id': new FormControl(), 53 | 'SocialKey': new FormControl({value: '', disabled: JSON.parse('false')} 54 | ), 55 | 'UserName': new FormControl({value: '', disabled: JSON.parse('false')} 56 | ), 57 | 'Created': new FormControl({value: '', disabled: JSON.parse('false')} 58 | ), 59 | 'Text': new FormControl({value: '', disabled: JSON.parse('false')} 60 | ), 61 | }); 62 | if (this._editFormSubscription) { 63 | this._editFormSubscription.next(); 64 | this._editFormSubscription.complete(); 65 | } 66 | this._editFormSubscription = new Subject(); 67 | // validate only properties that are visible 68 | this.editForm.valueChanges 69 | .pipe(takeUntil(this._editFormSubscription)) 70 | .subscribe(() => { 71 | const noEmit = { emitEvent: false }; 72 | }); 73 | this.exp = new DynamicObjectExpressionApi(this.editForm, null, 'SocialComment', null, this.usrSet); // TODO: support owner entity 74 | } 75 | ngOnInit() { 76 | super.ngOnInit(); 77 | } 78 | protected afterEntityLoaded(entity: wy.BaseEntity): void { 79 | } 80 | protected getDataStoreName(): string { 81 | return 'default'; 82 | } 83 | protected getDataSourceUri(): string { 84 | return '/SocialComments'; 85 | } 86 | protected prepareEntityForSave(entity: wy.BaseEntity): wy.BaseEntity { 87 | if (entity) { 88 | delete entity['@odata.type']; 89 | } 90 | return entity; 91 | } 92 | protected getComponentIdentifier(): string { 93 | return 'wy-component-defaultoneentity_socialcomment'; 94 | } 95 | protected getExpandNavigationProperties(): string[] { 96 | const result: string[] = []; 97 | return result; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/app/components/wy-one-entity/wy-one-entity.component.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | .wy-one-entity { 3 | .wy-one-entity-readonly, .wy-one-entity-editable { 4 | padding: 8px; 5 | } 6 | div.wy-validation-message { 7 | padding: 0.2rem; 8 | margin-top: 0.2rem; 9 | } 10 | span.wy-is-required { 11 | color: red; 12 | font-weight: bold; 13 | } 14 | input.wy-input-coord { 15 | width: auto; 16 | } 17 | .k-file-name { 18 | cursor: pointer; 19 | } 20 | .form-group { 21 | input[type="text"], 22 | textarea { 23 | width: 100%; 24 | } 25 | textarea { 26 | border-top: none; 27 | border-left: none; 28 | border-right: none; 29 | border-radius: 0; 30 | } 31 | label.control-label { 32 | font-weight: bold; 33 | } 34 | } 35 | .col-fill { 36 | height: 100%; 37 | } 38 | .wy-property-value { 39 | } 40 | .wy-uom-value { 41 | display: inline-block; 42 | } 43 | .wy-property-translatable-text { 44 | background-position: top 1px right 0px; 45 | background-size: 30px; 46 | background-repeat: no-repeat; 47 | } 48 | .wy-html-edit { 49 | .k-tabstrip > .k-content { 50 | padding-top: 0; 51 | padding-bottom: 0; 52 | } 53 | } 54 | .k-tabstrip-left { 55 | > .k-content { 56 | padding-top: 0; 57 | padding-bottom: 0; 58 | } 59 | } 60 | .wy-property-description { 61 | font-weight: normal; 62 | color: #888; 63 | } 64 | } 65 | @media (max-width: 568px) { 66 | .wy-one-entity { 67 | kendo-dropdownlist { 68 | width: 100% !important; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/app/components/wy-property-editor/wy-property-editor.component.html: -------------------------------------------------------------------------------- 1 |  2 |
3 | 6 | 7 |
8 | 11 |
12 |
13 |
14 | 15 |
16 | 19 | 20 |
21 | 24 |
25 |
26 |
-------------------------------------------------------------------------------- /src/app/components/wy-property-editor/wy-property-editor.component.scss: -------------------------------------------------------------------------------- 1 | .wy-property-editor { 2 | input[type="text"], 3 | textarea { 4 | width: 100%; 5 | } 6 | 7 | textarea { 8 | border-top: none; 9 | border-left: none; 10 | border-right: none; 11 | border-radius: 0; 12 | } 13 | 14 | label.control-label { 15 | font-weight: bold; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/components/wy-property-editor/wy-property-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, forwardRef } from '@angular/core'; 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'wy-property-editor', 6 | templateUrl: './wy-property-editor.component.html', 7 | styleUrls: [ 8 | './wy-property-editor.component.scss' 9 | ], 10 | providers: [ 11 | { 12 | provide: NG_VALUE_ACCESSOR, 13 | useExisting: forwardRef(() => WyPropertyEditorComponent), 14 | multi: true, 15 | } 16 | ] 17 | }) 18 | export class WyPropertyEditorComponent implements OnInit, ControlValueAccessor { 19 | 20 | @Input() public orientation: 'horizontal' | 'vertical' = 'horizontal'; 21 | 22 | public properties: { [key: string]: string; } = {}; 23 | public isDisabled = false; 24 | 25 | private _propagateChange = (_: any) => { }; 26 | 27 | constructor( 28 | ) { } 29 | 30 | writeValue(obj: any): void { 31 | if (obj) { 32 | this.properties = JSON.parse(obj); 33 | } 34 | } 35 | 36 | registerOnChange(fn: any): void { 37 | this._propagateChange = fn; 38 | } 39 | 40 | registerOnTouched(fn: any): void { 41 | } 42 | 43 | setDisabledState(isDisabled: boolean): void { 44 | this.isDisabled = isDisabled; 45 | } 46 | 47 | onPropertyChange(key: string, value: any) { 48 | this.properties[key] = value; 49 | this._propagateChange(JSON.stringify(this.properties)); 50 | } 51 | 52 | ngOnInit() { 53 | } 54 | 55 | getPropertyTitle(key: string) { 56 | return key.replace(/([A-Z])/g, ' $1').trim(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/components/wy-quick-search/wy-quick-search.component.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /src/app/components/wy-quick-search/wy-quick-search.component.scss: -------------------------------------------------------------------------------- 1 | .wy-quick-search { 2 | margin-top: 4px; 3 | margin-bottom: 4px; 4 | 5 | .form-control { 6 | border-radius: 5px !important; 7 | margin-bottom: 4px; 8 | box-shadow: inset 0px 0px 1px 1px #efefef; 9 | border: 0; 10 | width: auto; 11 | } 12 | 13 | button.k-button { 14 | box-shadow: none; 15 | background-color: #fff; 16 | margin: 2px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/wy-quick-search/wy-quick-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, OnInit, Inject } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wy-quick-search', 5 | templateUrl: './wy-quick-search.component.html', 6 | styleUrls: [ 7 | './wy-quick-search.component.scss' 8 | ] 9 | }) 10 | export class WyQuickSearchComponent implements OnInit { 11 | 12 | @Input() storageKey: string; 13 | 14 | @Output() quickSearchChange: EventEmitter = new EventEmitter(); 15 | 16 | public search: string; 17 | 18 | private _searchDelay; 19 | 20 | constructor( 21 | @Inject('IStorageService') 22 | private _st: wy.IStorageService) { 23 | this.search = ''; 24 | } 25 | 26 | ngOnInit() { 27 | this.loadQuickSearch(); 28 | } 29 | 30 | public triggerQuickSearchChangeHandlerWithDelay() { 31 | clearTimeout(this._searchDelay); 32 | 33 | this._searchDelay = setTimeout(() => { 34 | this.triggerQuickSearchChangeHandler(); 35 | }, 500); 36 | } 37 | 38 | public triggerQuickSearchChangeHandler() { 39 | this.saveQuickSearch(); 40 | this.quickSearchChange.emit(this.search); 41 | } 42 | 43 | public clearQuickSearch() { 44 | this.search = ''; 45 | this.triggerQuickSearchChangeHandler(); 46 | } 47 | 48 | public getFilterFromQuickSearch(fields: string[]) { 49 | if (this.search == '' || this.search == null) { 50 | return null; 51 | } 52 | 53 | if (this.search.match(/^#[0-9][0-9]*/g)) { 54 | // search by Id 55 | const id = parseInt(this.search.substr(1)); 56 | 57 | return `Id eq ${id}`; 58 | } 59 | else { 60 | // search by quick search fields 61 | let result = ''; 62 | for (let i = 0; i < fields.length; i++) { 63 | if (i > 0) { 64 | result += ' or '; 65 | } 66 | result += `contains(${fields[i]},'${this.search}')`; 67 | } 68 | 69 | return '(' + result + ')'; 70 | } 71 | } 72 | 73 | private saveQuickSearch() { 74 | if (this.storageKey) { 75 | this._st.setValue(this.storageKey, this.search); 76 | } 77 | } 78 | 79 | private loadQuickSearch() { 80 | if (this.storageKey) { 81 | const quickSearch = this._st.getValue(this.storageKey); 82 | 83 | if (quickSearch) { 84 | this.search = quickSearch; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/components/wy-social/wy-component-defaultsocial_project.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /src/app/components/wy-social/wy-component-defaultsocial_project.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation, OnInit } from '@angular/core'; 2 | import { WySocialBaseComponent } from './wy-social-base.component'; 3 | @Component({ 4 | selector: 'wy-component-defaultsocial_project', 5 | templateUrl: './wy-component-defaultsocial_project.component.html', 6 | styleUrls: ['./wy-social.component.scss'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | export class WySocialComponent_DefaultSocial_Project extends WySocialBaseComponent { 10 | } 11 | -------------------------------------------------------------------------------- /src/app/components/wy-social/wy-social-base.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { WyBaseComponent } from '../wy-base.component'; 3 | import { Input, Inject, OnInit, Directive } from '@angular/core'; 4 | @Directive() 5 | export abstract class WySocialBaseComponent extends WyBaseComponent implements OnInit { 6 | @Input() socialKey: string; 7 | reaction: wy.IReaction = { 8 | ReactionType: 0, 9 | Motivation: '' 10 | }; 11 | constructor( 12 | @Inject('ISocialService') 13 | protected scs: wy.ISocialService) { 14 | super(); 15 | } 16 | async ngOnInit() { 17 | this.reaction = await this.scs.getReaction(this.socialKey).toPromise(); 18 | } 19 | async triggerReaction(event: MouseEvent, reactionType: number) { 20 | if (event.x > 0 && event.y > 0) { // this ignores keyboard enter events 21 | const reaction: wy.IReaction = { 22 | ReactionType: reactionType, 23 | Motivation: this.reaction?.Motivation 24 | }; 25 | this.reaction = await this.scs.setReaction(this.socialKey, reaction).toPromise(); 26 | } 27 | } 28 | async triggerMotivation(event: any) { 29 | const motivation = event.target.value; 30 | const reaction: wy.IReaction = { 31 | ReactionType: this.reaction?.ReactionType, 32 | Motivation: motivation 33 | }; 34 | this.reaction = await this.scs.setReaction(this.socialKey, reaction).toPromise(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/components/wy-social/wy-social.component.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | .wy-social { 3 | background-color: #efefef; 4 | padding: 15px; 5 | border-radius: 10px; 6 | .wy-social-bar { 7 | button { 8 | margin-right: 10px; 9 | } 10 | .wy-social-reaction { 11 | margin-bottom: 5px; 12 | } 13 | .wy-social-reaction-motivation { 14 | textarea { 15 | margin-top: 5px; 16 | width: 100%; 17 | padding: 5px; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/d.ts/Typings.d.ts: -------------------------------------------------------------------------------- 1 | //empty -------------------------------------------------------------------------------- /src/app/d.ts/internal.d.ts: -------------------------------------------------------------------------------- 1 | declare var uuid: any; 2 | 3 | declare namespace wy { 4 | interface IWyGridColumnDefinition { 5 | minColWidth: string; 6 | width: string; 7 | media: string; 8 | field: string; 9 | format: string; 10 | filter: string; 11 | } 12 | 13 | interface IWyGridColumnConfiguration { 14 | visible: boolean; 15 | } 16 | } 17 | 18 | declare namespace L.gridLayer { 19 | interface GoogleMutant extends GridLayer { 20 | setElementSize(e, size); 21 | addGoogleLayer(googleLayerName: string, options); 22 | removeGoogleLayer(googleLayerName: string) 23 | } 24 | 25 | interface GoogleMutantOptions extends GridLayerOptions { 26 | subdomains?: string, 27 | errorTileUrl?: string, 28 | continuousWorld?: boolean, 29 | // Google's map type. Valid values are 'roadmap', 'satellite' or 'terrain'. 'hybrid' is not really supported. 30 | type: string, 31 | } 32 | 33 | function googleMutant(options: GoogleMutantOptions): GoogleMutant; 34 | } 35 | -------------------------------------------------------------------------------- /src/app/d.ts/jwt-decode.d.ts: -------------------------------------------------------------------------------- 1 | // TODO: find d.ts 2 | declare var jwt_decode: any; -------------------------------------------------------------------------------- /src/app/d.ts/monaco.d.ts: -------------------------------------------------------------------------------- 1 | // NOTE: deleted, because of size 2 | -------------------------------------------------------------------------------- /src/app/layouts/full-layout/full-layout.component.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | $header-height: 70px; 3 | $footer-height: 80px; 4 | .wy-full-layout { 5 | .wy-header { 6 | width: 100%; 7 | padding: 15px; 8 | height: $header-height; 9 | border-bottom: 2px solid #efefef; 10 | .wy-header-drawer-toggle { 11 | .k-icon { 12 | font-size: 16pt; 13 | } 14 | } 15 | .wy-brand-logo { 16 | height: 35px; 17 | margin-right: 10px; 18 | } 19 | .wy-brand-image { 20 | height: 55px; 21 | margin-right: 10px; 22 | margin-top: -11px; 23 | } 24 | .wy-header-brand { 25 | float: left; 26 | margin-right: 8px; 27 | margin-left: 8px; 28 | margin-top: 2px; 29 | font-weight: bold; 30 | font-size: 16pt; 31 | } 32 | .wy-header-menu { 33 | float: right; 34 | .wy-avatar { 35 | width: 14px; 36 | } 37 | button { 38 | margin-right: 5px; 39 | height: 35px; 40 | box-shadow: none; 41 | } 42 | } 43 | } 44 | kendo-drawer-container { 45 | position: fixed; 46 | width: 100%; 47 | height: 100%; 48 | .wy-menu { 49 | background-color: #efefef; 50 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); 51 | .k-panelbar { 52 | height: calc(100vh - #{$header-height} - #{$footer-height}) !important; 53 | border-color: #efefef; 54 | background-color: #efefef; 55 | kendo-panelbar-item { 56 | &.k-state-expanded { 57 | background-color: #ddd; 58 | kendo-panelbar-item .k-icon { 59 | transform: scale(.6); 60 | } 61 | } 62 | span.k-link { 63 | cursor: pointer !important; 64 | } 65 | .k-icon { 66 | margin-right: 10px; 67 | } 68 | } 69 | } 70 | } 71 | kendo-drawer-content.wy-main { 72 | overflow: auto; 73 | background-color: #fff; 74 | &.wy-main-hdr { 75 | height: calc(100vh - #{$header-height}); 76 | } 77 | &.wy-main-nhdr { 78 | height: 100vh; 79 | } 80 | } 81 | } 82 | .wy-footer { 83 | position: fixed; 84 | overflow: auto; 85 | bottom: 0; 86 | width: 280px; 87 | height: #{$footer-height}; 88 | padding: 10px; 89 | border-top: 2px solid #ddd; 90 | p { 91 | display: inline; 92 | } 93 | } 94 | } 95 | .wy-app-launcher { 96 | padding: 10px; 97 | .wy-app-card { 98 | display: table-cell; 99 | padding-right: 8px; 100 | padding-bottom: 8px; 101 | color: #ffffff; 102 | .wy-card-media-image { 103 | height: 250px; 104 | background-size: cover; 105 | background-color: #efefef; 106 | } 107 | kendo-card { 108 | width: 250px !important; 109 | h4, 110 | p { 111 | color: #ffffff; 112 | } 113 | .k-button { 114 | font-weight: bold; 115 | color: #ffffff; 116 | } 117 | } 118 | } 119 | } 120 | @media (max-width: 568px) { 121 | .wy-app-launcher { 122 | .wy-app-card { 123 | display: block; 124 | kendo-card { 125 | width: 100% !important; 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/app/layouts/full-layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './full-layout.component'; 2 | -------------------------------------------------------------------------------- /src/app/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './full-layout'; 2 | export * from './simple-layout'; 3 | -------------------------------------------------------------------------------- /src/app/layouts/simple-layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './simple-layout.component'; 2 | -------------------------------------------------------------------------------- /src/app/layouts/simple-layout/simple-layout.component.html: -------------------------------------------------------------------------------- 1 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /src/app/layouts/simple-layout/simple-layout.component.scss: -------------------------------------------------------------------------------- 1 | .wy-simple-layout { 2 | position: fixed; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/layouts/simple-layout/simple-layout.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Component, OnInit, AfterViewInit, Inject, ViewEncapsulation } from '@angular/core'; 3 | @Component({ 4 | encapsulation: ViewEncapsulation.None, 5 | selector: 'wy-simple-layout', 6 | templateUrl: 'simple-layout.component.html', 7 | styleUrls: [ 8 | './simple-layout.component.scss' 9 | ], 10 | }) 11 | export class SimpleLayoutComponent implements OnInit, AfterViewInit { 12 | constructor( 13 | @Inject('IUtilService') 14 | private _us: wy.IUtilService 15 | ) { 16 | } 17 | ngOnInit() { 18 | } 19 | ngAfterViewInit() { 20 | this._us.setDocumentTitle(null); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/models/models.ts: -------------------------------------------------------------------------------- 1 | //::Bag::All 2 | module Todo.NgLayer { 3 | export abstract class BaseEntity implements wy.BaseEntity { 4 | public Id: wy.PrimaryKey; 5 | public DeleteStatus?: number; 6 | public Name?: string; 7 | } 8 | export abstract class NamedEntity 9 | extends BaseEntity 10 | { 11 | public Name: string; 12 | public Notes: string; 13 | } 14 | export class SocialComment 15 | extends BaseEntity 16 | { 17 | public SocialKey: string; 18 | public UserName: string; 19 | public Created: Date; 20 | public Text: string; 21 | } 22 | export class SocialReaction 23 | extends BaseEntity 24 | { 25 | public SocialKey: string; 26 | public UserName: string; 27 | public Reaction: number; 28 | } 29 | export class TodoItem 30 | extends NamedEntity 31 | { 32 | public Priority: number; 33 | public Pictures: string; 34 | public Pictures_Count: number; 35 | public Created: Date; 36 | public Finished: boolean; 37 | public OwnerId: number; 38 | public Owner: User; 39 | public ProjectId: number; 40 | public Project: Project; 41 | public Comments: string; 42 | } 43 | export class Project 44 | extends NamedEntity 45 | { 46 | public Status: number; 47 | public CreatedBy: string; 48 | public Picture: string; 49 | public ProjectLocation: number; 50 | public TodoItems: TodoItem[]; 51 | public Comments: string; 52 | public PdfDocument: string; 53 | public PdfDocument_Count: number; 54 | public ProjectFiles: string; 55 | public ProjectFiles_Count: number; 56 | } 57 | export class User 58 | extends NamedEntity 59 | { 60 | public EmailAddress: string; 61 | public Picture: string; 62 | public Picture_Size: number; 63 | public Picture_PosX: number; 64 | public Picture_PosY: number; 65 | public Color: string; 66 | public Description: string; 67 | public Enabled: boolean; 68 | public Attachment: string; 69 | public Attachment_Size: number; 70 | } 71 | export class Action 72 | extends TodoItem 73 | { 74 | public Contact: string; 75 | } 76 | export class Message 77 | extends TodoItem 78 | { 79 | public MessageContents: string; 80 | } 81 | export enum Priority 82 | { 83 | High = 0, 84 | Low = 2, 85 | Medium = 1, 86 | } 87 | export enum Status 88 | { 89 | Closed = 2, 90 | New = 0, 91 | Open = 1, 92 | } 93 | export enum ProjectLocation 94 | { 95 | East = 3, 96 | North = 2, 97 | South = 1, 98 | West = 0, 99 | } 100 | export enum ReactionTypes 101 | { 102 | Dislike = 2, 103 | Like = 0, 104 | Love = 1, 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/app/pages/full/blobpage/blobpage.component.html: -------------------------------------------------------------------------------- 1 | 
2 |

Manage files & images

3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/pages/full/blobpage/blobpage.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/pages/full/blobpage/blobpage.component.scss -------------------------------------------------------------------------------- /src/app/pages/full/blobpage/blobpage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wy-blobpage', 5 | templateUrl: './blobpage.component.html', 6 | styleUrls: ['./blobpage.component.scss'] 7 | }) 8 | export class BlobPageComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/full/editpage/editpage.component.scss: -------------------------------------------------------------------------------- 1 | .wy-editpage { 2 | .wy-entity-button-bar { 3 | button { 4 | margin-right: 10px; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/pages/full/emptypage/emptypage.component.html: -------------------------------------------------------------------------------- 1 | 

2 | Home 3 |

-------------------------------------------------------------------------------- /src/app/pages/full/emptypage/emptypage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'emptypage.component.html' 5 | }) 6 | export class EmptyPageComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/full/gridpage/gridpage.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/pages/full/gridpage/gridpage.component.scss -------------------------------------------------------------------------------- /src/app/pages/full/gridpage/gridpage.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::All 2 | import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, Inject, Directive } from '@angular/core'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | @Directive() 5 | export abstract class GridPageBaseComponent implements AfterViewInit, OnInit { 6 | public filter: string; 7 | @ViewChild('title') 8 | title: ElementRef; 9 | constructor(private route: ActivatedRoute, 10 | @Inject('IUtilService') 11 | private _us: wy.IUtilService 12 | ) { } 13 | ngOnInit() { 14 | const filter = this.route.snapshot.paramMap.get('filter'); 15 | if (filter) { 16 | this.filter = filter; 17 | } 18 | } 19 | ngAfterViewInit() { 20 | this._us.setDocumentTitle(this.title.nativeElement.textContent); 21 | } 22 | } 23 | @Component({ 24 | selector: 'wy-gridpage-socialcomment', 25 | template: ` 26 |
27 |

Social Comments

28 | 29 |

30 |
31 | `, 32 | styleUrls: ['./gridpage.component.scss'] 33 | }) 34 | export class GridPageComponent_SocialComment extends GridPageBaseComponent { 35 | } 36 | @Component({ 37 | selector: 'wy-gridpage-socialreaction', 38 | template: ` 39 |
40 |

Social Reactions

41 | 42 |

43 |
44 | `, 45 | styleUrls: ['./gridpage.component.scss'] 46 | }) 47 | export class GridPageComponent_SocialReaction extends GridPageBaseComponent { 48 | } 49 | @Component({ 50 | selector: 'wy-gridpage-todoitem', 51 | template: ` 52 |
53 |

Todo Items

54 | 55 |

56 |
57 | `, 58 | styleUrls: ['./gridpage.component.scss'] 59 | }) 60 | export class GridPageComponent_TodoItem extends GridPageBaseComponent { 61 | } 62 | @Component({ 63 | selector: 'wy-gridpage-project', 64 | template: ` 65 |
66 |

Projects

67 | 68 |

69 |
70 | `, 71 | styleUrls: ['./gridpage.component.scss'] 72 | }) 73 | export class GridPageComponent_Project extends GridPageBaseComponent { 74 | } 75 | @Component({ 76 | selector: 'wy-gridpage-user', 77 | template: ` 78 |
79 |

Users

80 | 81 |

82 |
83 | `, 84 | styleUrls: ['./gridpage.component.scss'] 85 | }) 86 | export class GridPageComponent_User extends GridPageBaseComponent { 87 | } 88 | @Component({ 89 | selector: 'wy-gridpage-action', 90 | template: ` 91 |
92 |

Actions

93 | 94 |

95 |
96 | `, 97 | styleUrls: ['./gridpage.component.scss'] 98 | }) 99 | export class GridPageComponent_Action extends GridPageBaseComponent { 100 | } 101 | @Component({ 102 | selector: 'wy-gridpage-message', 103 | template: ` 104 |
105 |

Messages

106 | 107 |

108 |
109 | `, 110 | styleUrls: ['./gridpage.component.scss'] 111 | }) 112 | export class GridPageComponent_Message extends GridPageBaseComponent { 113 | } 114 | -------------------------------------------------------------------------------- /src/app/pages/full/grouppage/grouppage.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/pages/full/grouppage/grouppage.component.scss -------------------------------------------------------------------------------- /src/app/pages/full/grouppage/grouppage.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::All 2 | import { Component, AfterViewInit, Inject, ViewChild, ElementRef } from '@angular/core'; 3 | @Component({ 4 | selector: 'wy-grouppage-social', 5 | template: ` 6 |
7 |

Social

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 |
22 | `, 23 | styleUrls: ['./grouppage.component.scss'] 24 | }) 25 | export class GroupPageComponent_Social implements AfterViewInit { 26 | @ViewChild('title') 27 | title: ElementRef; 28 | constructor( 29 | @Inject('IUtilService') 30 | private _us: wy.IUtilService 31 | ) { 32 | } 33 | ngAfterViewInit() { 34 | this._us.setDocumentTitle(this.title.nativeElement.textContent); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/pages/full/page/page.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/pages/full/page/page.component.scss -------------------------------------------------------------------------------- /src/app/pages/full/page/page.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Pages 2 | import { Component, AfterViewInit, Inject, ViewChild, ElementRef } from '@angular/core'; 3 | @Component({ 4 | selector: 'wy-page-demo', 5 | template: ` 6 |
7 |

8 | Demo 9 |

10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 | `, 23 | styleUrls: ['./page.component.scss'] 24 | }) 25 | export class PageComponent_Demo implements AfterViewInit { 26 | @ViewChild('title') 27 | title: ElementRef; 28 | constructor( 29 | @Inject('IUtilService') 30 | private _us: wy.IUtilService 31 | ) { 32 | } 33 | ngAfterViewInit() { 34 | this._us.setDocumentTitle(this.title.nativeElement.textContent); 35 | } 36 | showPageTitle(): boolean { 37 | return true; 38 | } 39 | } 40 | @Component({ 41 | selector: 'wy-page-dashboard', 42 | template: ` 43 |
44 |

45 | Dashboard 46 |

47 |
48 |
49 | 50 |
51 |
52 |
53 | `, 54 | styleUrls: ['./page.component.scss'] 55 | }) 56 | export class PageComponent_Dashboard implements AfterViewInit { 57 | @ViewChild('title') 58 | title: ElementRef; 59 | constructor( 60 | @Inject('IUtilService') 61 | private _us: wy.IUtilService 62 | ) { 63 | } 64 | ngAfterViewInit() { 65 | this._us.setDocumentTitle(this.title.nativeElement.textContent); 66 | } 67 | showPageTitle(): boolean { 68 | return false; 69 | } 70 | } 71 | @Component({ 72 | selector: 'wy-page-reports', 73 | template: ` 74 |
75 |

76 | Reports 77 |

78 |
79 |
80 | 81 |
82 |
83 |
84 | `, 85 | styleUrls: ['./page.component.scss'] 86 | }) 87 | export class PageComponent_Reports implements AfterViewInit { 88 | @ViewChild('title') 89 | title: ElementRef; 90 | constructor( 91 | @Inject('IUtilService') 92 | private _us: wy.IUtilService 93 | ) { 94 | } 95 | ngAfterViewInit() { 96 | this._us.setDocumentTitle(this.title.nativeElement.textContent); 97 | } 98 | showPageTitle(): boolean { 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/app/pages/full/utilspage/utilspage.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 |
7 |

Export Default

8 |

Export all entities to a ZIP file.

9 | 12 |

13 |

Export all blobs to a ZIP file.

14 | 17 |


18 |

Import Default

19 |

Import a ZIP file containing entities.

20 | 23 | 24 |

25 |

Import a ZIP file containing blobs.

26 | 29 | 30 |


31 |
32 |
33 |
34 |
35 |

-------------------------------------------------------------------------------- /src/app/pages/full/utilspage/utilspage.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/app/pages/full/utilspage/utilspage.component.scss -------------------------------------------------------------------------------- /src/app/pages/full/utilspage/utilspage.component.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { Inject, Component, OnInit } from '@angular/core'; 3 | import { FileRestrictions } from '@progress/kendo-angular-upload'; 4 | @Component({ 5 | selector: 'wy-utilspage', 6 | templateUrl: './utilspage.component.html', 7 | styleUrls: ['./utilspage.component.scss'] 8 | }) 9 | export class UtilsPageComponent implements OnInit { 10 | uploadZipRestrictions: FileRestrictions = { 11 | allowedExtensions: ['.zip'] 12 | }; 13 | constructor( 14 | @Inject('IMessageService') 15 | private _ms: wy.IMessageService, 16 | @Inject('IUtilService') 17 | private _us: wy.IUtilService, 18 | @Inject('IConstantService') 19 | private _cs: wy.IConstantService, 20 | @Inject('ILanguageService') 21 | private _ls: wy.ILanguageService, 22 | ) { 23 | this._us.setDocumentTitle('Utils'); 24 | } 25 | ngOnInit() { 26 | } 27 | showSuccess() { 28 | this._ms.modalMessage('Succeeded', 'The import from the zip file was successful.'); 29 | } 30 | uploadImportBlobsUrl(dataStoreName: string) { 31 | return this._cs.getServiceLayerODataUri(dataStoreName, `/import/blobs`); 32 | } 33 | uploadImportEntitiesUrl(dataStoreName: string) { 34 | return this._cs.getServiceLayerODataUri(dataStoreName, `/import/entities`); 35 | } 36 | exportBlobs(dataStoreName: string) { 37 | const url = this._cs.getServiceLayerODataUri(dataStoreName, `/export/blobs`); 38 | window.location.href = url; 39 | } 40 | exportEntities(dataStoreName: string) { 41 | const url = this._cs.getServiceLayerODataUri(dataStoreName, `/export/entities`); 42 | window.location.href = url; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/pages/simple/404/404.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 404 -  6 | Oops! You are lost. 7 | 8 |

The page you are looking for was not found. 9 |

10 | Go home 11 |
-------------------------------------------------------------------------------- /src/app/pages/simple/404/404.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: '404.component.html' 5 | }) 6 | export class P404Component { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/simple/500/500.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 500 -  6 | Houston, we have a problem! 7 | 8 |

The page you are looking for is temporarily unavailable.

9 | Go home 10 |
-------------------------------------------------------------------------------- /src/app/pages/simple/500/500.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: '500.component.html' 5 | }) 6 | export class P500Component { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/simple/app-download/app-download.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | App download 6 |   7 | 2.32 8 | 9 |
10 |

Download app for Android.

11 | 12 | 13 |

14 | Download 15 |
16 | | 17 | Go home 18 |
19 |
-------------------------------------------------------------------------------- /src/app/pages/simple/app-download/app-download.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'app-download.component.html' 5 | }) 6 | export class AppDownloadComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/simple/login/login.component.scss: -------------------------------------------------------------------------------- 1 | .wy-login { 2 | .input-group-prepend, 3 | .input-group-append { 4 | white-space: nowrap; 5 | vertical-align: middle; 6 | } 7 | 8 | .wy-auth-tenants { 9 | padding: 5px; 10 | 11 | button { 12 | margin-right: 5px; 13 | margin-bottom: 5px; 14 | width: 150px; 15 | height: 75px; 16 | overflow: auto; 17 | border-radius: 0; 18 | } 19 | } 20 | 21 | .card { 22 | border: 0; 23 | border-radius: 0; 24 | background-color: #efefef; 25 | 26 | .card-body { 27 | p { 28 | max-width: 40vw; 29 | } 30 | } 31 | } 32 | } 33 | 34 | .wy-login-brand-image { 35 | max-width: 300px; 36 | width: 100%; 37 | } 38 | 39 | //=xs breakpoint 40 | @media (max-width: 568px) { 41 | .wy-login { 42 | .card { 43 | .card-body { 44 | p { 45 | max-width: 80vw; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { BaseHttpService } from './base-http.service'; 3 | import { EventService } from './event.service'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import * as jwt_decode from 'jwt-decode'; 6 | 7 | @Injectable() 8 | export class AuthenticationService extends BaseHttpService implements wy.IAuthenticationService { 9 | 10 | constructor( 11 | @Inject('IConstantService') 12 | private _cs: wy.IConstantService, 13 | 14 | private http: HttpClient, 15 | 16 | private _es: EventService) { 17 | super(); 18 | } 19 | 20 | public preAuthenticate(credentials: wy.Credentials, onSucceed: (tenants: wy.IAvailableTenant[]) => void, onFail: Function) { 21 | const preAuthUri = this._cs.getServiceLayerPreAuthenticateUri(); 22 | 23 | this.http.post(preAuthUri, { userName: credentials.userName, password: credentials.password }, BaseHttpService.AsHttpJson()) 24 | .subscribe( 25 | response => { 26 | if (response == null || response.value == null || response.value.length == 0) { 27 | onFail(); 28 | } 29 | else { 30 | console.log('Got pre-authenticate result.', response); 31 | onSucceed(response.value); 32 | } 33 | }, 34 | err => { 35 | console.log(err); 36 | 37 | onFail(); 38 | } 39 | ); 40 | } 41 | 42 | public authenticate(credentials: wy.Credentials, onSucceed: (jwt: wy.Jwt) => void, onFail: Function) { 43 | const authUri = this._cs.getServiceLayerAuthenticateUri(); 44 | 45 | this.http.post(authUri + '?tenant=' + credentials.tenant, credentials, BaseHttpService.AsHttpJson()) 46 | .subscribe( 47 | response => { 48 | if (response == null || response.value == null || response.value == '') { 49 | onFail(); 50 | } 51 | else { 52 | console.log('Got token.', response); 53 | 54 | const jwt: wy.Jwt = jwt_decode(response.value); 55 | this._es.userAuthenticated.next(jwt); 56 | onSucceed(jwt); 57 | } 58 | }, 59 | err => { 60 | console.log(err); 61 | 62 | onFail(); 63 | } 64 | ); 65 | } 66 | 67 | public refreshToken(onSucceed: (jwt: wy.Jwt) => void, onFail: Function) { 68 | const refreshTokenUri = this._cs.getServiceLayerRefreshTokenUri(); 69 | 70 | this.http.post(refreshTokenUri, null, BaseHttpService.AsHttpJson()) 71 | .subscribe( 72 | response => { 73 | if (response == null || response.value == null || response.value == '') { 74 | onFail(); 75 | } 76 | else { 77 | console.log('Got refreshed token.', response); 78 | 79 | const jwt: wy.Jwt = jwt_decode(response.value); 80 | onSucceed(jwt); 81 | } 82 | }, 83 | err => { 84 | console.log(err); 85 | 86 | onFail(); 87 | } 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/services/authorization.service.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Inject, Injectable } from "@angular/core"; 4 | import { AuthorizationMapStorageKey } from '../components/constants'; 5 | import { BaseHttpService } from './base-http.service'; 6 | @Injectable() 7 | export class AuthorizationService implements wy.IAuthorizationService { 8 | private _cachedAuthMap: wy.AuthorizationMap = null; 9 | constructor( 10 | @Inject('IConstantService') 11 | private _cs: wy.IConstantService, 12 | @Inject('IStorageService') 13 | private _st: wy.IStorageService, 14 | private http: HttpClient) { 15 | } 16 | isAuthorizationMapsEnabled(): boolean { 17 | const reqAuthMapsUri = this._cs.getServiceLayerRequestAuthorizationMapsUri(); 18 | if (reqAuthMapsUri) { 19 | return true; 20 | } 21 | return false; 22 | } 23 | async refreshAuthorizationMaps(): Promise { 24 | const reqAuthMapsUri = this._cs.getServiceLayerRequestAuthorizationMapsUri(); 25 | if (reqAuthMapsUri) { 26 | try { 27 | const response = await this.http.get(reqAuthMapsUri, BaseHttpService.AsHttpJson()).toPromise(); 28 | if (response != null && response.value != null && response.value.length > 0) { 29 | const maps = response.value as wy.AuthorizationMap[]; 30 | for (let map of maps) { 31 | if (map.AppName && map.AppName.toLowerCase() == 'todo') { 32 | this.storeAuthorizationMap(map); 33 | return; 34 | } 35 | } 36 | } 37 | } 38 | catch (ex) { 39 | console.warn('⚠️ Could not refresh authorization maps.', ex); 40 | } 41 | } 42 | this.storeAuthorizationMap(null); 43 | } 44 | private storeAuthorizationMap(authMap: wy.AuthorizationMap) { 45 | console.log('Storing authorization map.', authMap); 46 | this._st.setValue(AuthorizationMapStorageKey, authMap); 47 | this._cachedAuthMap = authMap; 48 | } 49 | getAuthorizationMap(): wy.AuthorizationMap { 50 | if (this._cachedAuthMap) { 51 | return this._cachedAuthMap; 52 | } 53 | return this._st.getValue(AuthorizationMapStorageKey); 54 | } 55 | isFeatureEnabled(featureKey: string): boolean { 56 | return true; 57 | } 58 | private isMatch(requestedPermissionKey: string, authMapEntry: string): boolean { 59 | if (authMapEntry == '*') { 60 | return true; 61 | } 62 | // TODO: make simple wildcard search more advanced 63 | if (authMapEntry.indexOf('*') > -1) { 64 | if (authMapEntry.startsWith('*')) { 65 | return requestedPermissionKey.endsWith(authMapEntry.replace('*', '')); 66 | } 67 | if (authMapEntry.endsWith('*')) { 68 | return requestedPermissionKey.startsWith(authMapEntry.replace('*', '')); 69 | } 70 | } 71 | return requestedPermissionKey == authMapEntry; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/services/base-http.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpHeaders } from '@angular/common/http'; 2 | 3 | export abstract class BaseHttpService { 4 | 5 | public jsonDateParser(key, value) { 6 | const isoDateFormatPattern = /^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/; 7 | 8 | if (typeof value === 'string') { 9 | const isDate = isoDateFormatPattern.exec(value); 10 | if (isDate) { 11 | return new Date(value); 12 | } 13 | } 14 | return value; 15 | } 16 | 17 | public static AsHttpJson(): { 18 | headers?: HttpHeaders | { 19 | [header: string]: string | string[]; 20 | }; 21 | responseType: 'json'; 22 | withCredentials?: boolean; 23 | } { 24 | return { 25 | headers: new HttpHeaders({ 26 | 'Content-Type': 'application/json; charset=UTF-8' 27 | }), 28 | withCredentials: true, 29 | responseType: 'json' 30 | }; 31 | } 32 | 33 | public static AsHttpText(): { 34 | headers?: HttpHeaders | { 35 | [header: string]: string | string[]; 36 | }; 37 | responseType: 'text'; 38 | withCredentials?: boolean; 39 | } { 40 | return { 41 | headers: new HttpHeaders({ 42 | 'Content-Type': 'application/json; charset=UTF-8' 43 | }), 44 | withCredentials: true, 45 | responseType: 'text' 46 | }; 47 | } 48 | 49 | constructor() { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/services/blob.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { BaseHttpService } from './base-http.service'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { EMPTY_GUID } from '../components/constants'; 5 | 6 | @Injectable() 7 | export class BlobService extends BaseHttpService implements wy.IBlobService { 8 | constructor( 9 | @Inject('IConstantService') 10 | private _cs: wy.IConstantService, 11 | 12 | private http: HttpClient 13 | ) { 14 | super(); 15 | } 16 | 17 | public getImages(handler: (blobs: wy.Blob[]) => any, boxId?: string): void { 18 | this.getBlobs('list/images', handler, boxId); 19 | } 20 | 21 | public getFiles(handler: (blobs: wy.Blob[]) => any, boxId?: string): void { 22 | this.getBlobs('list', handler, boxId); 23 | } 24 | 25 | private getBlobs(listMethod: string, handler: (blobs: wy.Blob[]) => any, boxId?: string): void { 26 | const blobUri = this._cs.getServiceLayerBlobUri(); 27 | 28 | this.http 29 | .get(`${blobUri}/${listMethod}/${boxId ? boxId : EMPTY_GUID}`, BaseHttpService.AsHttpJson()) 30 | .subscribe(blobs => { 31 | if (blobs.value) { 32 | // NOTE: OData response detected, return inner value 33 | return handler(blobs.value); 34 | } 35 | 36 | // TODO: remove legacy URL support 37 | return handler(blobs); 38 | }); 39 | } 40 | 41 | public getBlobUri(blob: wy.Blob, boxId?: string, inline?: boolean): string { 42 | return this.getBlobUriByName(blob.Name, boxId, inline); 43 | } 44 | 45 | public getBlobUriByName(blobName: string, boxId?: string, inline?: boolean): string { 46 | const blobUri = this._cs.getServiceLayerBlobUri(); 47 | 48 | return `${blobUri}/get/${blobName}/${boxId ? boxId : EMPTY_GUID}/${inline ? 'true' : 'false'}`; 49 | } 50 | 51 | public deleteBlob(blob: wy.Blob, handler: () => any, boxId?: string): void { 52 | this.deleteBlobByName(blob.Name, handler, boxId); 53 | } 54 | 55 | public deleteBlobByName(blobName: string, handler: () => any, boxId?: string): void { 56 | const blobUri = this._cs.getServiceLayerBlobUri(); 57 | 58 | this.http 59 | .post(`${blobUri}/destroy/${blobName}/${boxId ? boxId : EMPTY_GUID}`, null, BaseHttpService.AsHttpJson()) 60 | .subscribe(result => handler()); 61 | } 62 | 63 | public deleteBlobBox(boxId: string, handler: () => any): void { 64 | const blobUri = this._cs.getServiceLayerBlobUri(); 65 | 66 | this.http 67 | .post(`${blobUri}/destroybox/${boxId}`, null, BaseHttpService.AsHttpJson()) 68 | .subscribe(result => handler()); 69 | } 70 | 71 | public copyBlobBox(sourceBoxId: string, destinationBoxId: string, handler: () => any): void { 72 | const blobUri = this._cs.getServiceLayerBlobUri(); 73 | 74 | this.http 75 | .post(`${blobUri}/copybox/${sourceBoxId}/${destinationBoxId}`, null, BaseHttpService.AsHttpJson()) 76 | .subscribe(result => handler()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/services/constant.service.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | export class ConstantService implements wy.IConstantService { 3 | private _config: wy.IConfig; 4 | private _namespace: string; 5 | private _defaultDataStoreName: string; 6 | constructor(config: wy.IConfig, 7 | namespace: string, 8 | defaultDataStoreName: string) { 9 | this._config = config; 10 | this._namespace = namespace; 11 | this._defaultDataStoreName = defaultDataStoreName; 12 | } 13 | public getLoginPageUrl(): string { 14 | return '/pages/login'; 15 | } 16 | public getConfig(): wy.IConfig { 17 | return this._config; 18 | } 19 | public getServiceLayerODataUri(dataStoreName: string = null, dataSourceUri: string = null) { 20 | if (dataStoreName == null) { 21 | dataStoreName = this._defaultDataStoreName; 22 | } 23 | if (dataSourceUri == null) { 24 | return this._config.deploymentUri + '/' + dataStoreName; 25 | } 26 | else { 27 | return this._config.deploymentUri + '/' + dataStoreName + dataSourceUri; 28 | } 29 | } 30 | public getServiceLayerBlobUri() { 31 | return this._config.deploymentUri + '/blob'; 32 | } 33 | private getAuthenticationBaseUri() { 34 | if (this._config.authenticatorUri) { 35 | return this._config.authenticatorUri; 36 | } 37 | return this._config.deploymentUri + '/' + this._defaultDataStoreName; 38 | } 39 | public getServiceLayerPreAuthenticateUri() { 40 | return this.getAuthenticationBaseUri() + '/PreAuthenticate'; 41 | } 42 | public getServiceLayerAuthenticateUri() { 43 | return this.getAuthenticationBaseUri() + '/Authenticate'; 44 | } 45 | public getServiceLayerClearTokenUri() { 46 | return this.getAuthenticationBaseUri() + '/ClearToken'; 47 | } 48 | public getServiceLayerRefreshTokenUri() { 49 | return this.getAuthenticationBaseUri() + '/RefreshToken'; 50 | } 51 | public getServiceLayerRequestAuthorizationMapsUri() { 52 | return null; // not supported 53 | } 54 | public getServiceLayerRequestUserSettingsUri() { 55 | return this.getServiceLayerRootUri() + '/' + this._defaultDataStoreName + '/RequestUserSettings'; 56 | } 57 | public getServiceLayerRootUri() { 58 | return this._config.deploymentUri; 59 | } 60 | public getHangfireDashboardUri() { 61 | return this._config.deploymentUri + '/../jobs'; 62 | } 63 | public getSwaggerUri() { 64 | return this._config.deploymentUri + '/../swagger'; 65 | return null; // not supported 66 | } 67 | public getReportServerUri() { 68 | return this._config.reportServerUri; 69 | } 70 | public getNamespace() { 71 | return this._namespace; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/services/event.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | @Injectable() 5 | export class EventService { 6 | public userAuthenticated: BehaviorSubject = new BehaviorSubject(null); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/language.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | @Injectable() 4 | export class LanguageService implements wy.ILanguageService { 5 | constructor() { 6 | } 7 | 8 | public translate(key: string): string { 9 | // TODO: find a better way 10 | const elements = document.querySelectorAll('.wy-dynamic-translations'); 11 | 12 | for (let i = 0; i < elements.length; i++) { 13 | const element = elements[i].querySelector(`.${key}`); 14 | 15 | if (element) { 16 | return element.innerHTML; 17 | } 18 | } 19 | 20 | console.log(`Translation not found.`, key); 21 | 22 | return ''; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/services/link.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class LinkService implements wy.ILinkService { 5 | constructor( 6 | @Inject('IConstantService') 7 | private _cs: wy.IConstantService, 8 | 9 | @Inject('IUtilService') 10 | private _us: wy.IUtilService, 11 | 12 | @Inject('IBlobService') 13 | private _bs: wy.IBlobService) { 14 | } 15 | 16 | public getDownloadLink(blobName: string, size?: number, boxId?: string): string { 17 | if (!blobName) { 18 | return ''; 19 | } 20 | 21 | const url = this.getBlobUrl(blobName, boxId); 22 | const prettySizeSuffix = this.getPrettySizeSuffix(size); 23 | 24 | return `${blobName}${prettySizeSuffix}`; 25 | } 26 | 27 | private getPrettySizeSuffix(size?: number) { 28 | if (size) { 29 | return ' (' + this._us.formatBytes(size, 1) + ')'; 30 | } 31 | else { 32 | return ''; 33 | } 34 | } 35 | 36 | private getBlobUrl(blobName: string, boxId?: string) { 37 | return this._bs.getBlobUriByName(blobName, boxId); 38 | } 39 | 40 | public getLocationLink(latitude: number, longitude: number): string { 41 | const url = `https://www.google.com/maps?daddr=${latitude},${longitude}`; 42 | 43 | return ` ${latitude}, ${longitude}`; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/services/message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Component, Input } from '@angular/core'; 2 | import { 3 | DialogService, 4 | DialogRef, 5 | DialogCloseResult 6 | } from '@progress/kendo-angular-dialog'; 7 | import { NotificationService } from '@progress/kendo-angular-notification'; 8 | 9 | @Component({ 10 | selector: 'message-service-embed-html', 11 | template: ` 12 |
13 | ` 14 | }) 15 | export class MessageServiceEmbedHtmlComponent { 16 | @Input() public contents: string; 17 | } 18 | 19 | @Injectable() 20 | export class MessageService implements wy.IMessageService { 21 | private _progressDialog: DialogRef; 22 | private _messageDialog: DialogRef; 23 | private _confirmationDialog: DialogRef; 24 | 25 | constructor( 26 | private _dialogService: DialogService, 27 | private _notificationService: NotificationService) { 28 | } 29 | 30 | public log(text: string) { 31 | console.log(text); 32 | } 33 | 34 | public showProgress(title: string, text: string, pictureUrl: string = null) { 35 | if (this._progressDialog) { 36 | this.hideProgress(); // close existing 37 | } 38 | 39 | this._progressDialog = this._dialogService.open({ 40 | title: title, 41 | content: MessageServiceEmbedHtmlComponent, 42 | actions: [ 43 | ] 44 | }); 45 | 46 | const c: MessageServiceEmbedHtmlComponent = this._progressDialog.content.instance; 47 | 48 | if (pictureUrl) { 49 | c.contents = `${text}

`; 50 | } 51 | else { 52 | c.contents = `${text} `; 53 | } 54 | } 55 | 56 | public hideProgress(): void { 57 | this._progressDialog.close(); 58 | } 59 | 60 | public modalMessage(title: string, text: string) { 61 | this._messageDialog = this._dialogService.open({ 62 | title: title, 63 | content: MessageServiceEmbedHtmlComponent, 64 | actions: [ 65 | { text: 'OK', primary: true } 66 | ] 67 | }); 68 | 69 | const c: MessageServiceEmbedHtmlComponent = this._messageDialog.content.instance; 70 | c.contents = text; 71 | } 72 | 73 | public modalConfirmation(title: string, text: string, handler: (confirmed: boolean) => any) { 74 | this._confirmationDialog = this._dialogService.open({ 75 | title: title, 76 | content: text, 77 | actions: [ 78 | { text: 'No' }, 79 | { text: 'Yes', primary: true } 80 | ] 81 | }); 82 | 83 | this._confirmationDialog.result.subscribe((result) => { 84 | if (result instanceof DialogCloseResult) { 85 | handler(false); 86 | } else { 87 | handler(true); 88 | } 89 | }); 90 | } 91 | 92 | public notify(text: string, style: 'none' | 'success' | 'warning' | 'error' | 'info', icon: boolean = true, hideAfter: number = 5000) { 93 | this._notificationService.show({ 94 | content: text, 95 | position: { horizontal: 'right', vertical: 'bottom' }, 96 | type: { style: style, icon: icon }, 97 | hideAfter: hideAfter 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/app/services/monaco.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | declare global { 4 | interface Window { 5 | require: any; 6 | monaco: any; 7 | } 8 | } 9 | 10 | /** 11 | * Service to load Monaco dependencies. 12 | * https://github.com/Microsoft/monaco-editor/issues/18#issuecomment-261289582 13 | */ 14 | @Injectable() 15 | export class MonacoService { 16 | private _loaded = false; 17 | private _loadPromise: Promise; 18 | 19 | constructor() { 20 | this._loadPromise = new Promise(resolve => { 21 | // Fast path - Monaco is already loaded 22 | if (typeof (window.monaco) === 'object') { 23 | resolve(); 24 | return; 25 | } 26 | 27 | const onGotAmdLoader = () => { 28 | // Load Monaco 29 | window.require.config({ paths: { 'vs': 'assets/monaco/vs' } }); 30 | 31 | window.require(['vs/editor/editor.main'], () => { 32 | this._loaded = true; 33 | resolve(); 34 | }); 35 | }; 36 | 37 | // Load AMD loader if necessary 38 | if (!window.require) { 39 | const loaderScript = document.createElement('script'); 40 | loaderScript.type = 'text/javascript'; 41 | loaderScript.src = 'assets/monaco/vs/loader.js'; 42 | loaderScript.addEventListener('load', onGotAmdLoader); 43 | document.body.appendChild(loaderScript); 44 | } else { 45 | onGotAmdLoader(); 46 | } 47 | }); 48 | } 49 | 50 | get monacoLoaded() { 51 | return this._loaded; 52 | } 53 | 54 | /** 55 | * Returns promise that will be fulfilled when Monaco is available. 56 | */ 57 | waitForMonaco(): Promise { 58 | return this._loadPromise; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/services/social.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { BaseHttpService } from './base-http.service'; 4 | import { map } from "rxjs/operators"; 5 | import { Observable, EMPTY } from 'rxjs'; 6 | 7 | @Injectable() 8 | export class SocialService extends BaseHttpService implements wy.ISocialService { 9 | 10 | constructor( 11 | @Inject('IUtilService') 12 | private _us: wy.IUtilService, 13 | 14 | @Inject('IConstantService') 15 | private _cs: wy.IConstantService, 16 | 17 | private http: HttpClient) { 18 | super(); 19 | } 20 | 21 | getReaction(socialKey: string): Observable { 22 | if (this._us.logoutIfInvalidJwt()) { return EMPTY; }; 23 | 24 | const baseUri = this._cs.getServiceLayerODataUri(); 25 | let uri = `${baseUri}/GetReaction`; 26 | 27 | return this.http 28 | .post(uri, { 29 | socialKey: socialKey 30 | }, BaseHttpService.AsHttpJson()) 31 | .pipe(map(response => { 32 | // TODO: legacy, remove when on ASP.NET Core 33 | if (response && response.value instanceof Array) { 34 | return response.value[0]; 35 | } 36 | else { 37 | return response.value; 38 | } 39 | })); 40 | } 41 | 42 | setReaction(socialKey: string, reaction: wy.IReaction): Observable { 43 | if (this._us.logoutIfInvalidJwt()) { return EMPTY; }; 44 | 45 | const baseUri = this._cs.getServiceLayerODataUri(); 46 | let uri = `${baseUri}/SetReaction`; 47 | 48 | return this.http 49 | .post(uri, { 50 | socialKey: socialKey, 51 | reactionType: reaction.ReactionType, 52 | motivation: reaction.Motivation 53 | }, BaseHttpService.AsHttpJson()) 54 | .pipe(map(response => { 55 | // TODO: legacy, remove when on ASP.NET Core 56 | if (response && response.value instanceof Array) { 57 | return response.value[0]; 58 | } 59 | else { 60 | return response.value; 61 | } 62 | })); 63 | } 64 | 65 | getComments(socialKey: string): Observable { 66 | if (this._us.logoutIfInvalidJwt()) { return EMPTY; }; 67 | 68 | const baseUri = this._cs.getServiceLayerODataUri(); 69 | let uri = `${baseUri}/GetComments`; 70 | 71 | return this.http 72 | .post(uri, { 73 | socialKey: socialKey 74 | }, BaseHttpService.AsHttpText()) 75 | .pipe(map(response => JSON.parse(response, this.jsonDateParser) as wy.IODataResponse)) 76 | .pipe(map(response => { 77 | return response.value; 78 | })); 79 | } 80 | 81 | sendComment(socialKey: string, comment: wy.IComment): Observable { 82 | if (this._us.logoutIfInvalidJwt()) { return EMPTY; }; 83 | 84 | const baseUri = this._cs.getServiceLayerODataUri(); 85 | let uri = `${baseUri}/SendComment`; 86 | 87 | return this.http 88 | .post(uri, { 89 | socialKey: socialKey, 90 | text: comment.Text 91 | }, BaseHttpService.AsHttpJson()) 92 | .pipe(map(response => { 93 | return response.value; 94 | })); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/app/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | const STORAGE_KEY_PREFIX = 'STORAGE_'; 4 | 5 | @Injectable() 6 | export class StorageService implements wy.IStorageService { 7 | constructor() { 8 | } 9 | 10 | setValue(key: string, value: any) { 11 | localStorage.setItem(STORAGE_KEY_PREFIX + key, JSON.stringify(value)); 12 | } 13 | 14 | clearValue(key: string) { 15 | localStorage.removeItem(STORAGE_KEY_PREFIX + key); 16 | } 17 | 18 | getValue(key: string): any { 19 | const value = localStorage.getItem(STORAGE_KEY_PREFIX + key); 20 | if (value) { 21 | return JSON.parse(value); 22 | } 23 | 24 | return null; 25 | } 26 | 27 | getComponentStorageKey(componentIdentifier: string, key: string) { 28 | return `${componentIdentifier}.${key}`; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/services/usersettings.service.ts: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Inject, Injectable } from "@angular/core"; 4 | import { UserSettingsStorageKey } from '../components/constants'; 5 | import { BaseHttpService } from './base-http.service'; 6 | @Injectable() 7 | export class UserSettingsService implements wy.IUserSettingsService { 8 | private _cachedUserSettings: wy.UserSetting[] = null; 9 | constructor( 10 | @Inject('IConstantService') 11 | private _cs: wy.IConstantService, 12 | @Inject('IStorageService') 13 | private _st: wy.IStorageService, 14 | private http: HttpClient) { 15 | } 16 | async refreshUserSettings(): Promise { 17 | const reqUserSettingssUri = this._cs.getServiceLayerRequestUserSettingsUri(); 18 | if (reqUserSettingssUri) { 19 | try { 20 | const response = await this.http.get(reqUserSettingssUri, BaseHttpService.AsHttpJson()).toPromise(); 21 | if (response != null && response.value != null && response.value.length > 0) { 22 | const userSettings = response.value as wy.UserSetting[]; 23 | this.storeUserSettings(userSettings); 24 | return; 25 | } 26 | } 27 | catch (ex) { 28 | console.warn('⚠️ Could not refresh user settings.', ex); 29 | } 30 | } 31 | this.storeUserSettings(null); 32 | } 33 | private storeUserSettings(userSettings: wy.UserSetting[]) { 34 | console.log('Storing user settings.', userSettings); 35 | this._st.setValue(UserSettingsStorageKey, userSettings); 36 | this._cachedUserSettings = userSettings; 37 | } 38 | getUserSettings(): wy.UserSetting[] { 39 | if (this._cachedUserSettings) { 40 | return this._cachedUserSettings; 41 | } 42 | return this._st.getValue(UserSettingsStorageKey); 43 | } 44 | isFeatureEnabled(featureKey: string): boolean { 45 | const isFeatureEnabled = this.getUserSetting('FeatureToggle_' + featureKey); 46 | if (isFeatureEnabled == "false") { 47 | return false; 48 | } 49 | return true; // features are enabled by default 50 | } 51 | public getUserSetting(key: string): string { 52 | const userSettings = this.getUserSettings(); 53 | if (userSettings) { 54 | for (let entry of userSettings) { 55 | if (entry.Key == key) { 56 | return entry.Value; 57 | } 58 | } 59 | } 60 | return null; 61 | } 62 | public getCurrency(): string { 63 | return this.getUserSetting('Currency') || 'EUR'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/applauncher.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanrestrepo7716/grapes-js-example/2f152668e0a7759236cb466b6920df4be8a06307/src/assets/.npmignore -------------------------------------------------------------------------------- /src/assets/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/avatars/default-avatar.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/favicon-16x16.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/favicon-32x32.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/favicon-512x512.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/favicon.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/logo-symbol.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/mstile-144x144.png: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/assets/img/svg/gps.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Todo App", 3 | "short_name": "Todo App", 4 | "icons": [ 5 | { 6 | "src": "/assets/img/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/assets/img/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "background_color": "#ffffff", 17 | "display": "standalone", 18 | "theme_color": "rgba(57, 194, 44, 1)" 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/uuid.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.uuid=e()}}(function(){return function e(n,r,o){function t(f,u){if(!r[f]){if(!n[f]){var a="function"==typeof require&&require;if(!u&&a)return a(f,!0);if(i)return i(f,!0);var d=new Error("Cannot find module '"+f+"'");throw d.code="MODULE_NOT_FOUND",d}var s=r[f]={exports:{}};n[f][0].call(s.exports,function(e){var r=n[f][1][e];return t(r?r:e)},s,s.exports,e,n,r,o)}return r[f].exports}for(var i="function"==typeof require&&require,f=0;f>>((3&n)<<3)&255;return i}}n.exports=r}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],4:[function(e,n,r){function o(e,n,r){var o=n&&r||0,t=n||[];e=e||{};var f=void 0!==e.clockseq?e.clockseq:a,l=void 0!==e.msecs?e.msecs:(new Date).getTime(),c=void 0!==e.nsecs?e.nsecs:s+1,v=l-d+(c-s)/1e4;if(v<0&&void 0===e.clockseq&&(f=f+1&16383),(v<0||l>d)&&void 0===e.nsecs&&(c=0),c>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");d=l,s=c,a=f,l+=122192928e5;var p=(1e4*(268435455&l)+c)%4294967296;t[o++]=p>>>24&255,t[o++]=p>>>16&255,t[o++]=p>>>8&255,t[o++]=255&p;var y=l/4294967296*1e4&268435455;t[o++]=y>>>8&255,t[o++]=255&y,t[o++]=y>>>24&15|16,t[o++]=y>>>16&255,t[o++]=f>>>8|128,t[o++]=255&f;for(var b=e.node||u,w=0;w<6;++w)t[o+w]=b[w];return n?n:i(t)}var t=e("./lib/rng"),i=e("./lib/bytesToUuid"),f=t(),u=[1|f[0],f[1],f[2],f[3],f[4],f[5]],a=16383&(f[6]<<8|f[7]),d=0,s=0;n.exports=o},{"./lib/bytesToUuid":2,"./lib/rng":3}],5:[function(e,n,r){function o(e,n,r){var o=n&&r||0;"string"==typeof e&&(n="binary"==e?new Array(16):null,e=null),e=e||{};var f=e.random||(e.rng||t)();if(f[6]=15&f[6]|64,f[8]=63&f[8]|128,n)for(var u=0;u<16;++u)n[o+u]=f[u];return n||i(f)}var t=e("./lib/rng"),i=e("./lib/bytesToUuid");n.exports=o},{"./lib/bytesToUuid":2,"./lib/rng":3}]},{},[1])(1)}); -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "deploymentUri": "http://localhost:4301/api", 3 | "defaultCultureTwoLetterISOLanguageName": "en", 4 | "accessTokenTimeoutInSeconds": "43200", 5 | "reportServerUri": "", 6 | "jwtIssuer": "https://estiom.com/Todo", 7 | "jwtCookieName": "todo_token", 8 | "authenticatorUri": "", 9 | "appLauncherConfigUri": "" 10 | } 11 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /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 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- 1 | 404: Not Found -------------------------------------------------------------------------------- /src/kiosk.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Todo App KIOSK mode 7 | 21 | 22 | 23 | 39 | 40 | 41 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | //::Bag::All 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | if (environment.production) { 7 | enableProdMode(); 8 | } 9 | const stamp = new Date().getTime(); 10 | console.log('This application is created with wyStack and generated by a software robot. ==> http://www.wystack.com'); 11 | console.log('Copyright © 2021 Estiom ==> https://estiom.com'); 12 | fetch('/config.json?' + stamp) 13 | .then(resp => resp.json()) 14 | .then((config: wy.IConfig) => { 15 | AppModule.config = config; // provide configuration 16 | platformBrowserDynamic() 17 | .bootstrapModule(AppModule, { 18 | preserveWhitespaces: false 19 | }) 20 | .catch(err => console.log(err)); 21 | }); 22 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | import '@angular/localize/init'; 65 | -------------------------------------------------------------------------------- /src/scss/_bootstrap-variables.scss: -------------------------------------------------------------------------------- 1 | // bootstrap overrides 2 | // vars: node_modules\bootstrap\scss\_variables.scss 3 | 4 | // color system 5 | $white: #fff; 6 | $gray-100: #eee; 7 | $gray-200: #ccc; 8 | $gray-300: #bbb; 9 | $gray-400: #999; 10 | $gray-500: #777; 11 | $gray-600: #555; 12 | $gray-700: #333; 13 | $gray-800: #222; 14 | $gray-900: #111; 15 | $black: #000; 16 | 17 | $blue: #0060a7; 18 | $indigo: #6610f2; 19 | $purple: #6f42c1; 20 | $pink: #e83e8c; 21 | $red: #e51c23; 22 | $orange: #e08600; 23 | $yellow: #ffc107; 24 | $green: #439a46; 25 | $teal: #20c997; 26 | $cyan: #63c2de; 27 | 28 | $colors: ( 29 | "blue": $blue, 30 | "indigo": $indigo, 31 | "purple": $purple, 32 | "pink": $pink, 33 | "red": $red, 34 | "orange": $orange, 35 | "yellow": $yellow, 36 | "green": $green, 37 | "teal": $teal, 38 | "cyan": $cyan, 39 | "white": $white, 40 | "gray": $gray-600, 41 | "gray-dark": $gray-800 42 | ); 43 | 44 | $theme-colors: ( 45 | "primary": $app-color, 46 | "secondary": $app-secondary-color, 47 | "success": $green, 48 | "info": $blue, 49 | "warning": $orange, 50 | "danger": $red, 51 | "light": $gray-100, 52 | "dark": $gray-800, 53 | "blue": $blue, 54 | "indigo": $indigo, 55 | "purple": $purple, 56 | "pink": $pink, 57 | "red": $red, 58 | "orange": $orange, 59 | "yellow": $yellow, 60 | "green": $green, 61 | "teal": $teal, 62 | "cyan": $cyan, 63 | "gray-100": $gray-100, 64 | "gray-200": $gray-200, 65 | "gray-300": $gray-300, 66 | "gray-400": $gray-400, 67 | "gray-500": $gray-500, 68 | "gray-600": $gray-600, 69 | "gray-700": $gray-700, 70 | "gray-800": $gray-800, 71 | "gray-900": $gray-900 72 | ); 73 | 74 | $enable-transitions: false; 75 | $enable-rounded: false; 76 | 77 | $body-bg: #fff; 78 | $body-color: #333; 79 | $card-border-color: $gray-200; 80 | $card-cap-bg: $gray-100; 81 | $dropdown-border-color: $gray-200; 82 | $dropdown-divider-bg: $gray-100; 83 | $btn-secondary-border: $gray-300; 84 | $progress-bg: $gray-100; 85 | $table-bg-accent: $gray-100; 86 | $table-bg-hover: $gray-100; 87 | $input-group-addon-bg: $gray-100; 88 | $input-border-color: $gray-200; 89 | $input-group-addon-border-color: $gray-200; 90 | $table-border-color: #efefef; 91 | 92 | $font-size-base: 0.875rem; 93 | $font-family-sans-serif: "Roboto", sans-serif; 94 | -------------------------------------------------------------------------------- /src/scss/_default-colors.scss: -------------------------------------------------------------------------------- 1 | $app-color: #000; 2 | $app-secondary-color: #ccc; 3 | -------------------------------------------------------------------------------- /src/scss/_kendo-customization.scss: -------------------------------------------------------------------------------- 1 | // kendo fixes/changes 2 | 3 | .k-button { 4 | text-transform: none !important; 5 | } 6 | 7 | // fix ugly search in dropdownlist 8 | .k-list-filter > .k-textbox { 9 | background-color: #fff; 10 | border-radius: 0; 11 | padding-left: 0; 12 | } 13 | 14 | // scroll tabs horizontal 15 | .k-tabstrip-top, 16 | .k-tabstrip-bottom { 17 | > .k-tabstrip-items { 18 | overflow-x: auto; 19 | overflow-y: hidden; 20 | } 21 | } 22 | 23 | // kendo dropdown buttons item padding 24 | .k-popup { 25 | kendo-button-list { 26 | .k-list .k-item, 27 | .k-list-optionlabel { 28 | padding: 5px; 29 | min-width: 120px; 30 | } 31 | } 32 | } 33 | 34 | .k-numerictextbox { 35 | width: 300px; 36 | } 37 | 38 | // fix kendo dropdown height limitation 39 | .k-dropdown .k-dropdown-wrap .k-input { 40 | height: auto !important; 41 | } 42 | 43 | // make sure notifications are always visible 44 | .k-notification-group { 45 | z-index: 20000 !important; 46 | } 47 | -------------------------------------------------------------------------------- /src/scss/_kendo-variables.scss: -------------------------------------------------------------------------------- 1 | // kendo overrides 2 | // vars: node_modules\@progress\kendo-theme-material\scss\_variables.scss 3 | 4 | $primary: $app-color; 5 | $secondary: $app-secondary-color; 6 | 7 | $grid-border: #ffffff; 8 | $grid-toolbar-padding-x: 0; 9 | 10 | $window-titlebar-padding-x: 16px; 11 | $window-titlebar-padding-y: 16px; 12 | $window-inner-padding-x: 16px; 13 | $window-inner-padding-y: 16px; 14 | 15 | $cell-padding-x: 5px; 16 | $cell-padding-y: 5px; 17 | $cell-padding: 5px; 18 | 19 | $tabstrip-content-padding-x: 16px; 20 | $tabstrip-content-padding-y: 16px; 21 | 22 | $panelbar-item-selected-bg: $app-color; 23 | $panelbar-item-selected-text: #ffffff; 24 | $panelbar-header-selected-bg: $app-color; 25 | $panelbar-header-selected-text: #ffffff; 26 | -------------------------------------------------------------------------------- /src/scss/_kendo.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * wyStack - Stylesheet - Kendo. 3 | */ 4 | 5 | // Override Kendo variables 6 | @import "kendo-variables"; 7 | 8 | // Import Kendo styles 9 | @import "/node_modules/@progress/kendo-theme-material/dist/all.scss"; 10 | 11 | // Override Kendo variables 12 | @import "kendo-customization"; 13 | -------------------------------------------------------------------------------- /src/scss/_style.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | /*! 3 | * wyStack - Stylesheet - Bootstrap + wyStack. 4 | */ 5 | // Override Boostrap variables 6 | @import "bootstrap-variables"; 7 | // Import Bootstrap source files 8 | @import "/node_modules/bootstrap/scss/bootstrap"; 9 | // wyStack styles 10 | @import "wy"; 11 | // App styles 12 | // 13 | // FontAwesome CSS variables 14 | :root { 15 | --fa-primary-color: #{$app-color}; 16 | --fa-secondary-color: #{$app-secondary-color}; 17 | } 18 | -------------------------------------------------------------------------------- /src/scss/kendo_default.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | // Default App Theme 3 | @import "default-colors"; 4 | $app-color: rgba(57, 194, 44, 1); 5 | $app-secondary-color: #454545; 6 | @import "kendo"; 7 | -------------------------------------------------------------------------------- /src/scss/theme_default.scss: -------------------------------------------------------------------------------- 1 | //::Bag::Default 2 | // Default App Theme 3 | @import "default-colors"; 4 | $app-color: rgba(57, 194, 44, 1); 5 | $app-secondary-color: #454545; 6 | @import "style"; 7 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | 7 | /* Load json (kendo internationalization) */ 8 | declare module "*.json" { 9 | const value: any; 10 | export default value; 11 | } 12 | -------------------------------------------------------------------------------- /src/web.config: -------------------------------------------------------------------------------- 1 | 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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts", 10 | "src/app/models/models.ts", 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "preserveWhitespaces": false, 24 | "fullTemplateTypeCheck": false, 25 | "strictInjectionParameters": true 26 | } 27 | } -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": false, 8 | "comment-format": [ 9 | false, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "deprecation": { 16 | "severity": "warning" 17 | }, 18 | "import-blacklist": [ 19 | true 20 | ], 21 | "import-spacing": true, 22 | "indent": [ 23 | true, 24 | "spaces" 25 | ], 26 | "interface-over-type-literal": true, 27 | "label-position": true, 28 | "max-line-length": [ 29 | false, 30 | 140 31 | ], 32 | "member-access": false, 33 | "member-ordering": [ 34 | true, 35 | "static-before-instance", 36 | "variables-before-functions" 37 | ], 38 | "no-arg": true, 39 | "no-bitwise": true, 40 | "no-console": [ 41 | false, 42 | "debug", 43 | "info", 44 | "time", 45 | "timeEnd", 46 | "trace" 47 | ], 48 | "no-construct": true, 49 | "no-debugger": true, 50 | "no-duplicate-variable": true, 51 | "no-empty": false, 52 | "no-empty-interface": true, 53 | "no-eval": true, 54 | "no-inferrable-types": [ 55 | true, 56 | "ignore-params" 57 | ], 58 | "no-shadowed-variable": true, 59 | "no-string-literal": false, 60 | "no-string-throw": true, 61 | "no-switch-case-fall-through": true, 62 | "no-trailing-whitespace": true, 63 | "no-unused-expression": true, 64 | "no-var-keyword": true, 65 | "object-literal-sort-keys": false, 66 | "one-line": [ 67 | false, 68 | "check-open-brace", 69 | "check-catch", 70 | "check-else", 71 | "check-whitespace" 72 | ], 73 | "prefer-const": true, 74 | "quotemark": [ 75 | true, 76 | "single" 77 | ], 78 | "radix": false, 79 | "semicolon": [ 80 | "always" 81 | ], 82 | "triple-equals": [ 83 | false, 84 | "allow-null-check" 85 | ], 86 | "typedef-whitespace": [ 87 | true, 88 | { 89 | "call-signature": "nospace", 90 | "index-signature": "nospace", 91 | "parameter": "nospace", 92 | "property-declaration": "nospace", 93 | "variable-declaration": "nospace" 94 | } 95 | ], 96 | "typeof-compare": true, 97 | "unified-signatures": true, 98 | "variable-name": false, 99 | "whitespace": [ 100 | false 101 | ], 102 | "directive-selector": [ 103 | false, 104 | "attribute", 105 | "app", 106 | "camelCase" 107 | ], 108 | "component-selector": [ 109 | false, 110 | "element", 111 | "app", 112 | "kebab-case" 113 | ], 114 | "no-inputs-metadata-property": true, 115 | "no-outputs-metadata-property": true, 116 | "no-host-metadata-property": true, 117 | "no-input-rename": true, 118 | "no-output-rename": true, 119 | "use-lifecycle-interface": true, 120 | "use-pipe-transform-interface": true, 121 | "component-class-suffix": false, 122 | "directive-class-suffix": true, 123 | "no-access-missing-member": true, 124 | "templates-use-public": true, 125 | "invoke-injectable": true 126 | } 127 | } 128 | --------------------------------------------------------------------------------