From da353cec84cc613e0540c60d1bc224e39d04469d Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Fri, 1 Mar 2024 00:22:28 +0700 Subject: [PATCH] add changes diffing modal & silence some logs --- client/package-lock.json | 853 +++++++++++++++++- client/package.json | 2 + .../src/app/core/services/recipe.service.ts | 15 + .../merge/changes/changes.component.css | 0 .../merge/changes/changes.component.html | 0 .../merge/changes/changes.component.ts | 116 +++ .../app/features/merge/merge.component.html | 96 +- .../src/app/features/merge/merge.component.ts | 253 +++++- .../recipe-list/recipe-list.component.html | 3 +- .../recipe-list/recipe-list.component.ts | 242 ++++- .../recipe-topping.component.ts | 29 +- .../features/recipes/recipes.component.html | 5 +- .../app/features/recipes/recipes.component.ts | 32 +- server/data/commit.go | 6 +- server/data/data.go | 132 ++- server/data/redis.go | 44 +- server/data/sqlite.go | 6 +- server/routers/auth.go | 5 +- server/routers/recipe.go | 38 +- server/routers/topping.go | 2 +- server/services/logger/logger.go | 9 +- server/services/recipe/recipe.go | 2 +- 22 files changed, 1770 insertions(+), 120 deletions(-) create mode 100644 client/src/app/features/merge/changes/changes.component.css create mode 100644 client/src/app/features/merge/changes/changes.component.html create mode 100644 client/src/app/features/merge/changes/changes.component.ts diff --git a/client/package-lock.json b/client/package-lock.json index 4edb9ff..c3f9b76 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^16.2.0", + "@angular/cdk": "^16.2.12", "@angular/common": "^16.2.0", "@angular/compiler": "^16.2.0", "@angular/core": "^16.2.0", "@angular/forms": "^16.2.0", + "@angular/material": "^16.2.12", "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", @@ -350,6 +352,34 @@ "@angular/core": "16.2.5" } }, + "node_modules/@angular/cdk": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.12.tgz", + "integrity": "sha512-wT8/265zm2WKY0BDaRoYbrAT4kadrmejTRLjuimQIEUKnw4vBsJMWCwQkpFo3s6zr6eznGqYVAFb8KKPVLKGBg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@angular/cli": { "version": "16.2.2", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.2.tgz", @@ -517,6 +547,70 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.12.tgz", + "integrity": "sha512-k1DGRfP1mMmhg/nLJjZBOPzX3SyAjgbRBY2KauKOV8OFCXJGoMn/oLgMBh+qB1WugzIna/31dBV8ruHD3Uvp2w==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/auto-init": "15.0.0-canary.bc9ae6c9c.0", + "@material/banner": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/card": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/chips": "15.0.0-canary.bc9ae6c9c.0", + "@material/circular-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/data-table": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dialog": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/drawer": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/fab": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/form-field": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/image-list": "15.0.0-canary.bc9ae6c9c.0", + "@material/layout-grid": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/radio": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/segmented-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/select": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/slider": "15.0.0-canary.bc9ae6c9c.0", + "@material/snackbar": "15.0.0-canary.bc9ae6c9c.0", + "@material/switch": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-bar": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", + "@material/textfield": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tooltip": "15.0.0-canary.bc9ae6c9c.0", + "@material/top-app-bar": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0 || ^17.0.0", + "@angular/cdk": "16.2.12", + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "@angular/forms": "^16.0.0 || ^17.0.0", + "@angular/platform-browser": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "16.2.5", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.5.tgz", @@ -2940,6 +3034,758 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@material/animation": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-leRf+BcZTfC/iSigLXnYgcHAGvFVQveoJT5+2PIRdyPI/bIG7hhciRgacHRsCKC0sGya81dDblLgdkjSUemYLw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/auto-init": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-uxzDq7q3c0Bu1pAsMugc1Ik9ftQYQqZY+5e2ybNplT8gTImJhNt4M2mMiMHbMANk2l3UgICmUyRSomgPBWCPIA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/banner": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-SHeVoidCUFVhXANN6MNWxK9SZoTSgpIP8GZB7kAl52BywLxtV+FirTtLXkg/8RUkxZRyRWl7HvQ0ZFZa7QQAyA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/base": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Fc3vGuOf+duGo22HTRP6dHdc+MUe0VqQfWOuKrn/wXKD62m0QQR2TqJd3rRhCumH557T5QUyheW943M3E+IGfg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-3AQgwrPZCTWHDJvwgKq7Cj+BurQ4wTjDdGL+FEnIGUAjJDskwi1yzx5tW2Wf/NxIi7IoPFyOY3UB41jwMiOrnw==", + "dependencies": { + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/card": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-nPlhiWvbLmooTnBmV5gmzB0eLWSgLKsSRBYAbIBmO76Okgz1y+fQNLag+lpm/TDaHVsn5fmQJH8e0zIg0rYsQA==", + "dependencies": { + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/checkbox": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-4tpNnO1L0IppoMF3oeQn8F17t2n0WHB0D7mdJK9rhrujen/fLbekkIC82APB3fdGtLGg3qeNqDqPsJm1YnmrwA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/chips": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-fqHKvE5bSWK0bXVkf57MWxZtytGqYBZvvHIOs4JI9HPHEhaJy4CpSw562BEtbm3yFxxALoQknvPW2KYzvADnmA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Lxe8BGAxQwCQqrLhrYrIP0Uok10h7aYS3RBXP41ph+5GmwJd5zdyE2t93qm2dyThvU6qKuXw9726Dtq/N+wvZQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/data-table": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-j/7qplT9+sUpfe4pyWhPbl01qJA+OoNAG3VMJruBBR461ZBKyTi7ssKH9yksFGZ8eCEPkOsk/+kDxsiZvRWkeQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/checkbox": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/linear-progress": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/select": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Zt3u07fXrBWLW06Tl5fgvjicxNQMkFdawLyNTzZ5TvbXfVkErILLePwwGaw8LNcvzqJP6ABLA8jiR+sKNoJQCg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-o+9a/fmwJ9+gY3Z/uhj/PMVJDq7it1NTWKJn2GwAKdB+fDkT4hb9qEdcxMPyvJJ5ups+XiKZo03+tZrD+38c1w==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dom": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-ly78R7aoCJtundSUu0UROU+5pQD5Piae0Y1MkN6bs0724azeazX1KeXFeaf06JOXnlr5/41ol+fSUPowjoqnOg==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/drawer": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-PFL4cEFnt7VTxDsuspFVNhsFDYyumjU0VWfj3PWB7XudsEfQ3lo85D3HCEtTTbRsCainGN8bgYNDNafLBqiigw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/elevation": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Ro+Pk8jFuap+T0B0shA3xI1hs2b89dNQ2EIPCNjNMp87emHKAzJfhKb7EZGIwv3+gFLlVaLyIVkb94I89KLsyg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/fab": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-dvU0KWMRglwJEQwmQtFAmJcAjzg9VFF6Aqj78bJYu/DAIGFJ1VTTTSgoXM/XCm1YyQEZ7kZRvxBO37CH54rSDg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/feature-targeting": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-wkDjVcoVEYYaJvun28IXdln/foLgPD7n9ZC9TY76GErGCwTq+HWpU6wBAAk+ePmpRFDayw4vI4wBlaWGxLtysQ==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/floating-label": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-bUWPtXzZITOD/2mkvLkEPO1ngDWmb74y0Kgbz6llHLOQBtycyJIpuoQJ1q2Ez0NM/tFLwPphhAgRqmL3YQ/Kzw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/focus-ring": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-cZHThVose3GvAlJzpJoBI1iqL6d1/Jj9hXrR+r8Mwtb1hBIUEG3hxfsRd4vGREuzROPlf0OgNf/V+YHoSwgR5w==", + "dependencies": { + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0" + } + }, + "node_modules/@material/form-field": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-+JFXy5X44Gue1CbZZAQ6YejnI203lebYwL0i6k0ylDpWHEOdD5xkF2PyHR28r9/65Ebcbwbff6q7kI1SGoT7MA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-1a0MHgyIwOs4RzxrVljsqSizGYFlM1zY2AZaLDsgT4G3kzsplTx8HZQ022GpUCjAygW+WLvg4z1qAhQHvsbqlw==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/image-list": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-WKWmiYap2iu4QdqmeUSliLlN4O2Ueqa0OuVAYHn/TCzmQ2xmnhZ1pvDLbs6TplpOmlki7vFfe+aSt5SU9gwfOQ==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/layout-grid": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-5GqmT6oTZhUGWIb+CLD0ZNyDyTiJsr/rm9oRIi3+vCujACwxFkON9tzBlZohdtFS16nuzUusthN6Jt9UrJcN6Q==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-8S30WXEuUdgDdBulzUDlPXD6qMzwCX9SxYb5mGDYLwl199cpSGdXHtGgEcCjokvnpLhdZhcT1Dsxeo1g2Evh5Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/linear-progress": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-6EJpjrz6aoH2/gXLg9iMe0yF2C42hpQyZoHpmcgTLKeci85ktDvJIjwup8tnk8ULQyFiGiIrhXw2v2RSsiFjvQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/progress-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/list": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-TQ1ppqiCMQj/P7bGD4edbIIv4goczZUoiUAaPq/feb1dflvrFMzYqJ7tQRRCyBL8nRhJoI2x99tk8Q2RXvlGUQ==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-IlAh61xzrzxXs38QZlt74UYt8J431zGznSzDtB1Fqs6YFNd11QPKoiRXn1J2Qu/lUxbFV7i8NBKMCKtia0n6/Q==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-dMtSPN+olTWE+08M5qe4ea1IZOhVryYqzK0Gyb2u1G75rSArUxCOB5rr6OC/ST3Mq3RS6zGuYo7srZt4534K9Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/notched-outline": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-WuurMg44xexkvLTBTnsO0A+qnzFjpcPdvgWBGstBepYozsvSF9zJGdb1x7Zv1MmqbpYh/Ohnuxtb/Y3jOh6irg==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/progress-indicator": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-uOnsvqw5F2fkeTnTl4MrYzjI7KCLmmLyZaM0cgLNuLsWVlddQE+SGMl28tENx7DUK3HebWq0FxCP8f25LuDD+w==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/radio": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-ehzOK+U1IxQN+OQjgD2lsnf1t7t7RAwQzeO6Czkiuid29ookYbQynWuLWk7NW8H8ohl7lnmfqTP1xSNkkL/F0g==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/ripple": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-JfLW+g3GMVDv4cruQ19+HUxpKVdWCldFlIPw1UYezz2h3WTNDy05S3uP2zUdXzZ01C3dkBFviv4nqZ0GCT16MA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/rtl": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-SkKLNLFp5QtG7/JEFg9R92qq4MzTcZ5As6sWbH7rRg6ahTHoJEuqE+pOb9Vrtbj84k5gtX+vCYPvCILtSlr2uw==", + "dependencies": { + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/segmented-button": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-YDwkCWP9l5mIZJ7pZJZ2hMDxfBlIGVJ+deNzr8O+Z7/xC5LGXbl4R5aPtUVHygvXAXxpf5096ZD+dSXzYzvWlw==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/touch-target": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/select": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-unfOWVf7T0sixVG+3k3RTuATfzqvCF6QAzA6J9rlCh/Tq4HuIBNDdV4z19IVu4zwmgWYxY0iSvqWUvdJJYwakQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/list": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu": "15.0.0-canary.bc9ae6c9c.0", + "@material/menu-surface": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/shape": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Dsvr771ZKC46ODzoixLdGwlLEQLfxfLrtnRojXABoZf5G3o9KtJU+J+5Ld5aa960OAsCzzANuaub4iR88b1guA==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/slider": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-3AEu+7PwW4DSNLndue47dh2u7ga4hDJRYmuu7wnJCIWJBnLCkp6C92kNc4Rj5iQY2ftJio5aj1gqryluh5tlYg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/snackbar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-TwwQSYxfGK6mc03/rdDamycND6o+1p61WNd7ElZv1F1CLxB4ihRjbCoH7Qo+oVDaP8CTpjeclka+24RLhQq0mA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/icon-button": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/switch": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-OjUjtT0kRz1ASAsOS+dNzwMwvsjmqy5edK57692qmrP6bL4GblFfBDoiNJ6t0AN4OaKcmL5Hy/xNrTdOZW7Qqw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-s/L9otAwn/pZwVQZBRQJmPqYeNbjoEbzbjMpDQf/VBG/6dJ+aP03ilIBEkqo8NVnCoChqcdtVCoDNRtbU+yp6w==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/focus-ring": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-Xmtq0wJGfu5k+zQeFeNsr4bUKv7L+feCmUp/gsapJ655LQKMXOUQZtSv9ZqWOfrCMy55hoF1CzGFV+oN3tyWWQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-indicator": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab-scroller": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-indicator": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-despCJYi1GrDDq7F2hvLQkObHnSLZPPDxnOzU16zJ6FNYvIdszgfzn2HgAZ6pl5hLOexQ8cla6cAqjTDuaJBhQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-scroller": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-QWHG/EWxirj4V9u2IHz+OSY9XCWrnNrPnNgEufxAJVUKV/A8ma1DYeFSQqxhX709R8wKGdycJksg0Flkl7Gq7w==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/tab": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-R3qRex9kCaZIAK8DuxPnVC42R0OaW7AB7fsFknDKeTeVQvRcbnV8E+iWSdqTiGdsi6QQHifX8idUrXw+O45zPw==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/density": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/floating-label": "15.0.0-canary.bc9ae6c9c.0", + "@material/line-ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/notched-outline": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/theme": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-CpUwXGE0dbhxQ45Hu9r9wbJtO/MAlv5ER4tBHA9tp/K+SU+lDgurBE2touFMg5INmdfVNtdumxb0nPPLaNQcUg==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tokens": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-nbEuGj05txWz6ZMUanpM47SaAD7soyjKILR+XwDell9Zg3bGhsnexCNXPEz2fD+YgomS+jM5XmIcaJJHg/H93Q==", + "dependencies": { + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0" + } + }, + "node_modules/@material/tooltip": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-UzuXp0b9NuWuYLYpPguxrjbJnCmT/Cco8CkjI/6JajxaeA3o2XEBbQfRMTq8PTafuBjCHTc0b0mQY7rtxUp1Gg==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/button": "15.0.0-canary.bc9ae6c9c.0", + "@material/dom": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/tokens": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/top-app-bar": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-vJWjsvqtdSD5+yQ/9vgoBtBSCvPJ5uF/DVssv8Hdhgs1PYaAcODUi77kdi0+sy/TaWyOsTkQixqmwnFS16zesA==", + "dependencies": { + "@material/animation": "15.0.0-canary.bc9ae6c9c.0", + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/elevation": "15.0.0-canary.bc9ae6c9c.0", + "@material/ripple": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/shape": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "@material/typography": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/touch-target": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-AqYh9fjt+tv4ZE0C6MeYHblS2H+XwLbDl2mtyrK0DOEnCVQk5/l5ImKDfhrUdFWHvS4a5nBM4AA+sa7KaroLoA==", + "dependencies": { + "@material/base": "15.0.0-canary.bc9ae6c9c.0", + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/rtl": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography": { + "version": "15.0.0-canary.bc9ae6c9c.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.bc9ae6c9c.0.tgz", + "integrity": "sha512-CKsG1zyv34AKPNyZC8olER2OdPII64iR2SzQjpqh1UUvmIFiMPk23LvQ1OnC5aCB14pOXzmVgvJt31r9eNdZ6Q==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.bc9ae6c9c.0", + "@material/theme": "15.0.0-canary.bc9ae6c9c.0", + "tslib": "^2.1.0" + } + }, "node_modules/@ng-select/ng-select": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-11.2.0.tgz", @@ -5693,7 +6539,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12" }, @@ -10500,6 +11346,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + }, "node_modules/sass": { "version": "1.64.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", diff --git a/client/package.json b/client/package.json index b48d96c..2c3074f 100644 --- a/client/package.json +++ b/client/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "^16.2.0", + "@angular/cdk": "^16.2.12", "@angular/common": "^16.2.0", "@angular/compiler": "^16.2.0", "@angular/core": "^16.2.0", "@angular/forms": "^16.2.0", + "@angular/material": "^16.2.12", "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", diff --git a/client/src/app/core/services/recipe.service.ts b/client/src/app/core/services/recipe.service.ts index 9ef3939..ccd0272 100644 --- a/client/src/app/core/services/recipe.service.ts +++ b/client/src/app/core/services/recipe.service.ts @@ -309,4 +309,19 @@ export class RecipeService { { withCredentials: true, responseType: 'json' } ); } + + + async sortRecipe( + country: string, + filename: string, + sortKey: string, + ): Promise> { + return this._httpClient.post( + environment.api + '/recipes/sort/' + country + '/' + filename , + JSON.stringify({ + "sortKey": sortKey + }), + { withCredentials: true, responseType: 'json' } + ); + } } diff --git a/client/src/app/features/merge/changes/changes.component.css b/client/src/app/features/merge/changes/changes.component.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/features/merge/changes/changes.component.html b/client/src/app/features/merge/changes/changes.component.html new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/features/merge/changes/changes.component.ts b/client/src/app/features/merge/changes/changes.component.ts new file mode 100644 index 0000000..123c500 --- /dev/null +++ b/client/src/app/features/merge/changes/changes.component.ts @@ -0,0 +1,116 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatCardModule } from '@angular/material/card'; +import { compare } from 'src/app/shared/helpers/compare'; +import { RecipeListComponent } from '../../recipes/recipe-details/recipe-list/recipe-list.component'; + +@Component({ + selector: 'app-changes', + standalone: true, + templateUrl: './changes.component.html', + imports: [CommonModule, MatCardModule, RecipeListComponent], +}) +export class ChangesComponent implements OnInit { + @Input() changePackage: + | { + // if is unchanged, do not show + minimizeUnchanged: boolean; + // do highlight when matched this data + highlightChangeWhenMatched: {}; + // base product code + targetProductCode: string; + // full recipelist + targetRecipe: any; + // path + path: string; + // changes + changes: any; + } + | undefined = undefined; + + diffList: any[] = []; + matchedIdx: number[] = []; + + ngOnInit(): void { + // this.createDiffList(this.changePackage?.targetRecipe, this.changePackage?.targetRecipe); + + // iterate + this.traverseByPath( + this.changePackage?.path!, + this.changePackage?.targetRecipe + ); + } + + createDiffList = (base: any, change: any) => { + // console.log("base", base, "change", change); + let compRes = compare(base, change); + // if (!this.diffList.includes(compRes)) { + + // } + + // for(let c of compRes){ + // console.log("c", c, "diffList", this.diffList.indexOf(c)); + // if(this.diffList.indexOf(c) == -1){ + // this.diffList.push(c); + // } + + // } + + if(this.diffList.length == 0){ + this.diffList = compRes; + } + // else if(this.diffList.length > 0){ + + // let concatable = true; + // // check if exist + // for(let i = 0; i < this.diffList.length; i++){ + // for(let j = 0; j < compRes.length; j++){ + // if(this.diffList[i] == compRes[j]){ + // concatable = false; + // } + // } + // } + + // if(concatable){ + // this.diffList = this.diffList.concat(compRes); + // } + // } + + console.log("diff list", this.diffList); + }; + + traverseByPath = (path: string, obj: any) => { + let pathList = path.split('.'); + let data = obj; + for (let pathKey of pathList) { + if (pathKey != '') { + if (!isNaN(parseInt(pathKey))) { + let asIntKey = parseInt(pathKey); + + data = data[asIntKey]; + if (!this.matchedIdx.includes(asIntKey)) { + this.matchedIdx.push(parseInt(pathKey)); + } + } else { + data = data[pathKey]; + } + } + } + + return data; + }; + + popLast = (keyString: string) => { + let keyList = keyString.split('.'); + keyList.pop(); + return keyList.join('.'); + }; + + getChanges = () => + this.traverseByPath( + this.popLast(this.changePackage!.path), + this.changePackage!.changes + ); + + removeDot = (s: string) => s.replace(".", ""); +} diff --git a/client/src/app/features/merge/merge.component.html b/client/src/app/features/merge/merge.component.html index f0e06bb..5a3aba4 100644 --- a/client/src/app/features/merge/merge.component.html +++ b/client/src/app/features/merge/merge.component.html @@ -1,40 +1,68 @@ -

merge works!

- -
- -
-
+
+ +
+ + + + +
+ +
+ +
+
+ {{ selectedCommit }} +
+ +
+
+
+ +
+ - - - {{getCommitAttr(patchKey, 'Msg')}} - - -
-

date: {{getCommitAttr(patchKey, 'Created_at') }}

-

editor: {{getCommitAttr(patchKey, 'Editor') }}

-

ref: {{patchKey}}

- - - - -
-
-
- +
+
+
+ {{ pd }} +
+ +
+ +
+
+
- - -
-

master

- -
- - -
-

machine

-
diff --git a/client/src/app/features/merge/merge.component.ts b/client/src/app/features/merge/merge.component.ts index 4dd6558..71a89a2 100644 --- a/client/src/app/features/merge/merge.component.ts +++ b/client/src/app/features/merge/merge.component.ts @@ -1,4 +1,16 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { + AfterViewInit, + ApplicationRef, + Component, + ComponentFactoryResolver, + Injector, + Input, + OnDestroy, + OnInit, + TemplateRef, + ViewChild, + ViewContainerRef, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { RecipeService } from 'src/app/core/services/recipe.service'; import { MatCardModule } from '@angular/material/card'; @@ -6,14 +18,38 @@ import { MatButtonModule } from '@angular/material/button'; import { copy } from 'src/app/shared/helpers/copy'; import { compare } from 'src/app/shared/helpers/compare'; import { AsyncStorage } from 'src/app/shared/helpers/asyncStorage'; +import { ChangesComponent } from './changes/changes.component'; +import { + CdkPortal, + DomPortalOutlet, + Portal, + PortalModule, + TemplatePortal, +} from '@angular/cdk/portal'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { FormsModule } from '@angular/forms'; +import { RecipeListComponent } from '../recipes/recipe-details/recipe-list/recipe-list.component'; @Component({ selector: 'app-merge', standalone: true, - imports: [CommonModule, MatCardModule, MatButtonModule], templateUrl: './merge.component.html', + imports: [ + CommonModule, + MatCardModule, + MatButtonModule, + ChangesComponent, + RecipeListComponent, + PortalModule, + MatFormFieldModule, + MatSelectModule, + FormsModule + ], }) -export class MergeComponent implements OnInit { +export class MergeComponent implements OnInit, AfterViewInit, OnDestroy { + onRecipeListFormChange($event: any) {} + @Input() commit: Array | undefined = undefined; patchMap: any = {}; @@ -23,11 +59,47 @@ export class MergeComponent implements OnInit { //map productCode:commits mapProductCodeWithCommits: any = {}; // master recipes for product code given by commits - targetRecipe: any = {} + targetRecipe: any = {}; // change map changeMap: any = {}; - constructor(private _recipeService: RecipeService) {} + // -------------------- current selection + + currentTargetOfMaster: any = undefined; + highlightChanges: any = {}; + selectedProductCode = ''; + + changePackage: + | { + minimizeUnchanged: boolean; + highlightChangeWhenMatched: {}; + targetProductCode: string; + targetRecipe: any; + path: string; + changes: any; + } + | undefined = undefined; + + selectedCommit: string = ''; + + // --------------------- Portal + // deprecated~! + // @ViewChild(CdkPortal) + // private portal: CdkPortal | undefined; + // private host: DomPortalOutlet | undefined; + + selectedPortal!: Portal; + templatePortal!: TemplatePortal; + // template + @ViewChild('templateRecipeChangeContent') + templateRecipeChangeContent!: TemplateRef; + + constructor( + private _recipeService: RecipeService, + private _containerRef: ViewContainerRef // private cfr: ComponentFactoryResolver, + ) // private appRef: ApplicationRef, + // private injector: Injector + {} async ngOnInit(): Promise { ( @@ -51,7 +123,7 @@ export class MergeComponent implements OnInit { if (productCode) { // check if exist in map if (!this.mapProductCodeWithCommits[productCode]) { - this.mapProductCodeWithCommits[productCode] = [ key ]; + this.mapProductCodeWithCommits[productCode] = [key]; } else { this.mapProductCodeWithCommits[productCode].push(key); } @@ -69,7 +141,29 @@ export class MergeComponent implements OnInit { }); // fetch related product codes + } + ngAfterViewInit(): void { + //Called after ngAfterContentInit when the component's view has been initialized. Applies to components only. + //Add 'implements AfterViewInit' to the class. + // this.host = new DomPortalOutlet( + // document.querySelector('#view_change')!, + // this.cfr, + // this.appRef, + // this.injec2tor + // ); + + // this.host.attach(this.portal); + // console.log("Portal attached!"); + + this.templatePortal = new TemplatePortal( + this.templateRecipeChangeContent, + this._containerRef + ); + } + + ngOnDestroy(): void { + // this.host?.detach(); } // funcitons @@ -77,20 +171,73 @@ export class MergeComponent implements OnInit { // get patch map keys getPatchMapKeys = () => Object.keys(this.patchMap); + // check if load patch keys + isPatchMapKeysLoaded = () => this.getPatchMapKeys().length > 0; + testLoadCheck = () => console.log('test load check', this.isPatchMapKeysLoaded()); + + // get product code targets + getProductCodesOfCommits = () => Object.keys(this.mapProductCodeWithCommits); + + // mapProductCodeWithCommits have data in it yet? + hasProductCodeOfCommits = () => + Object.keys(this.mapProductCodeWithCommits).length > 0; + // get commit message by commit id getCommitAttr = (id: string, attr: string) => this.fullPatches[id][attr]; // copy to clipboard copyRef = async (ref: string) => await copy(ref); + // do show warning conflict if contains same path key in change map + isThisCommitConflict = (patchId: string) => { + // loop key of change map + // find other patch if .changes array contains same path key + // if yes, return true + if ( + this.changeMap[patchId] == undefined || + this.changeMap[patchId] == null || + !Object.keys(this.changeMap[patchId]).includes('changes') + ) { + return false; + } + + let commit = this.changeMap[patchId]['changes'] as Array; + for (let key in this.changeMap) { + if (key !== patchId) { + let testCondition = this.changeMap[key].changes.some( + (change: any) => + commit.find((commit: any) => commit.path === change.path) != + undefined + ); + return testCondition; + } + } + + return false; + }; + + selectCommit = (commit: any) => { + console.log('select commit', commit.target.value); + this.selectedCommit = commit.target.value; + } + + buildContext = () => { + return { + changeContext: undefined, + skipZeroes: true, + }; + }; // ----------------------------- Master Functions --------------------------- + getMasterRecipeOfProductCode = async (productCode: string) => { - (await this._recipeService.getRawRecipeOfProductCode( - await this._recipeService.getCurrentCountry(), - this._recipeService.getCurrentFile(), - productCode - )).subscribe({ + ( + await this._recipeService.getRawRecipeOfProductCode( + await this._recipeService.getCurrentCountry(), + this._recipeService.getCurrentFile(), + productCode + ) + ).subscribe({ next: (data: any) => { this.targetRecipe[productCode] = data; // console.log("get master recipe", this.targetRecipe); @@ -106,12 +253,88 @@ export class MergeComponent implements OnInit { ); // save only what changes this.changeMap[patchId] = { - changes: cmp + changes: cmp, }; }); - console.log("change map", this.changeMap); - } + console.log('change map', this.changeMap); + }, }); - }; + }; + + generateMasterRecipeList(productCode: string): any { + // this do fetch recipelist + + if ( + this.targetRecipe[productCode] != undefined || + this.targetRecipe[productCode] != null + ) { + this.getMasterRecipeOfProductCode(productCode); + } + return this.targetRecipe[productCode].recipes; + } + + getRecipeFromSingleLayerPath = ( + productCode: string, + path: string, + ref_id: string, + popLast?: boolean + ) => { + console.log( + 'mapProductCodeWithCommits map = ', + this.mapProductCodeWithCommits + ); + // split path + let pathList = path.split('.'); + + if (popLast) { + // pop last pathlist + pathList.pop(); + } + + // get product code data + let productCodeData = this.targetRecipe[productCode]; + let data = undefined; + for (let pathKey of pathList) { + if (data == undefined) { + if (!isNaN(parseInt(pathKey))) { + data = productCodeData[parseInt(pathKey)]; + } else { + data = productCodeData[pathKey]; + } + } else { + if (!isNaN(parseInt(pathKey))) { + data = data[parseInt(pathKey)]; + } else { + data = data[pathKey]; + } + } + } + + // console.log('data from path', path,"=", data); + + // setter + this.selectedProductCode = productCode; + this.currentTargetOfMaster = productCodeData.recipes; + // configure for changes display + // this.highlightChanges = data; + // this.minimizeUnchanged = true; + + // packing into one map; do update if selected + let changeConfigure = { + minimizeUnchanged: true, + highlightChangeWhenMatched: data, + targetProductCode: productCode, + targetRecipe: productCodeData, + path: path, + changes: this.fullPatches[ref_id].contents, + }; + + this.changePackage = changeConfigure; + }; + + // revert search by using commit ref id to find productCode + getProductCodeByCommitRef = (commitRef: string) => { + return this.patchMap[commitRef].productCode; + }; } diff --git a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html index cc937a3..d1ae230 100644 --- a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html +++ b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html @@ -1,4 +1,4 @@ -
+
@@ -25,6 +25,7 @@ (click)="addToSelection(i)" (mousedown)="initHoldEvent()" (mouseup)="openRecipeListEditor(i)" + *ngIf="invokeZeroMaterialChecker(i)" > diff --git a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.ts b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.ts index 36da4ce..383723d 100644 --- a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.ts +++ b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.ts @@ -1,5 +1,5 @@ import { CommonModule, NgFor, NgIf } from '@angular/common'; -import { Component, EventEmitter, Input, OnInit, Output, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR } from '@angular/core'; import { FormArray, FormBuilder, @@ -42,10 +42,15 @@ import Lang from 'src/app/shared/helpers/lang'; standalone: true, imports: [CommonModule,NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent] }) -export class RecipeListComponent implements OnInit { +export class RecipeListComponent implements OnInit, OnChanges { @Input({ required: true }) productCode!: string; @Input() noFetch: boolean = false; @Input() recipeList: any | undefined = undefined; + @Input() displayOnly: boolean = false; + @Input() diffChangeContext: { + changeContext: any; + skipZeroes: boolean; + } | undefined = undefined; @Output() recipeListFormChange = new EventEmitter(); materialList: MaterialCode[] = []; @@ -267,6 +272,7 @@ export class RecipeListComponent implements OnInit { this.isMatLoaded = true; }); } else if(this.recipeList != undefined){ + console.log("test recipelist", this.recipeList); this.recipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => { // StringParam @@ -297,7 +303,7 @@ export class RecipeListComponent implements OnInit { // --------------- mapping missing data --------------- // map name - console.log("use recipeList input; ", recipeDetailMat); + // console.log("use recipeList input; ", recipeDetailMat); this.recipeListData.push( this._formBuilder.group({ @@ -460,6 +466,216 @@ export class RecipeListComponent implements OnInit { }); } + async ngOnChanges(changes: SimpleChanges): Promise { + + if(changes['recipeList']){ + let previousRecipeList = changes['recipeList'].previousValue; + let newUpdatedRecipeList = changes['recipeList'].currentValue; + let firstTime = changes['recipeList'].isFirstChange(); + + console.log("test on changes", changes); + if(newUpdatedRecipeList != undefined && !firstTime){ + console.log("test updated input recipelist", newUpdatedRecipeList); + + // reset + this.stringParamData.clear(); + this.recipeListData.clear(); + + newUpdatedRecipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => { + + // StringParam + if ( + recipeDetailMat.StringParam != '' || + recipeDetailMat.StringParam != null + ) { + let currStringParam = new StringParam(recipeDetailMat.StringParam); + let stringParamList = currStringParam.extract().as_list(); + + let stringParamListTransform: any[] = []; + for (let param of stringParamList) { + // boolean transform + if (param.pvalue == 'true') { + param.pvalue = true; + } else if (param.pvalue == 'false') { + param.pvalue = false; + } + } + + this.stringParamData.push( + this._formBuilder.array(stringParamListTransform) + ) + + this.stringParams[index] = stringParamList; + } + + // --------------- mapping missing data --------------- + + // map name + // console.log("use recipeList input; ", recipeDetailMat); + + this.recipeListData.push( + this._formBuilder.group({ + StringParam: [ + { + value: recipeDetailMat.StringParam, + disabled: !this.isEditable(), + }, + ], + isUse: [ + { value: recipeDetailMat.isUse, disabled: !this.isEditable() }, + ], + materialPathId: [ + { + value: recipeDetailMat.materialPathId, + disabled: !this.isEditable(), + }, + ], + name: [{ value: recipeDetailMat.name, disabled: true }], + MixOrder: [ + { + value: recipeDetailMat.MixOrder, + disabled: !this.isEditable(), + }, + ], + stirTime: [ + { + value: recipeDetailMat.stirTime, + disabled: !this.isEditable(), + }, + ], + powderGram: [ + { + value: recipeDetailMat.powderGram, + disabled: !this.isEditable(), + }, + ], + powderTime: [ + { + value: recipeDetailMat.powderTime, + disabled: !this.isEditable(), + }, + ], + syrupGram: [ + { + value: recipeDetailMat.syrupGram, + disabled: !this.isEditable(), + }, + ], + syrupTime: [ + { + value: recipeDetailMat.syrupTime, + disabled: !this.isEditable(), + }, + ], + waterCold: [ + { + value: recipeDetailMat.waterCold, + disabled: !this.isEditable(), + }, + ], + waterYield: [ + { + value: recipeDetailMat.waterYield, + disabled: !this.isEditable(), + }, + ], + feedPattern: [ + { + value: recipeDetailMat.feedPattern, + disabled: !this.isEditable(), + } + ], + feedParameter: [ + { + value: recipeDetailMat.feedParameter, + disabled: !this.isEditable(), + } + ] + }) + ); + }); + + // resub listener + + this.recipeListForm.valueChanges.subscribe((value) => { + // console.log(value.recipeListData); + // console.log("original recipe detail",this._recipeListOriginalArray); + if ( + !isEqual( + sortBy(value, 'materialID'), + sortBy(this._recipeListOriginalArray, 'materialID') + ) + ) { + let emitted_res: any[] = []; + + // force type change. temporary solution + forEach(value.recipeListData!, (recipeDetailMat: any) => { + recipeDetailMat.materialPathId = parseInt( + recipeDetailMat.materialPathId! + ); + + // revert stirTime + recipeDetailMat.stirTime = recipeDetailMat.stirTime!; + + emitted_res.push(recipeDetailMat); + }); + + this.recipeListFormChange.emit([this.toppingList, emitted_res] as unknown[]); + } else { + this.recipeListFormChange.emit([]); + } + }); + + this.stringParamForm.valueChanges.subscribe((value) => { + // value.stringParamData: Array + // where this.stringParams: {[key: number] : {pkey: string, pvalue: any}} + + // transform value to map + let mapValue: { [key: number]: { pkey: string; pvalue: any }[] } = {}; + forEach(value.stringParamData, (param: any, index: number) => { + mapValue[index] = param; + }); + + let checkLen = + Object.keys(mapValue).length == + Object.keys(this.stringParams as any).length; + let baseLen = + Object.keys(this.stringParams as any).length >= + Object.keys(mapValue).length + ? Object.keys(this.stringParams as any).length + : Object.keys(mapValue).length; + + if (checkLen) { + for (let i = 0; i < baseLen; i++) { + if (!isEqual(this.stringParams[i], mapValue[i])) { + // console.log('check', (this.stringParams as any)[i], mapValue[i]); + + // transform back to string + let initString = ','; + for (let key of Object.keys(mapValue[i])) { + initString += `${mapValue[i][parseInt(key)].pkey}=${ + mapValue[i][parseInt(key)].pvalue + },`; + } + + if (initString.length > 1) { + (this.recipeListData.at(i) as any).controls.StringParam.setValue( + initString + ); + // console.log('set', initString); + } + + // do last + this.stringParams[i] = mapValue[i]; + } + } + } + }); + + } + } + } + // add new row addRow() { @@ -521,6 +737,11 @@ export class RecipeListComponent implements OnInit { } isEditable() { + + if(this.displayOnly){ + return !this.displayOnly; + } + return this._userService .getCurrentUser()! .permissions.includes(UserPermissions.EDITOR); @@ -737,7 +958,7 @@ export class RecipeListComponent implements OnInit { await Promise.resolve(); if (this.timeoutHandler) { - if (this.timeout >= 20) { + if (this.timeout >= 20 && !this.displayOnly) { // alert("Opening Recipe List Editor in detail") if (confirm("Are you sure you want to open Recipe List Editor in detail?")) { this.showDetailRecipeList = true; @@ -810,4 +1031,17 @@ export class RecipeListComponent implements OnInit { } + // ----------------------------- Change/Diff ------------------------------- + + invokeZeroMaterialChecker = (index: number) => { + if(this.diffChangeContext?.skipZeroes == true){ + // check if current index's material is 0 + let isZeroMaterial = this.recipeListData.at(index).get('materialPathId')?.value == "0"; + if(isZeroMaterial){ + return false; + } + } + return true; + } + } diff --git a/client/src/app/features/recipes/recipe-details/recipe-topping/recipe-topping.component.ts b/client/src/app/features/recipes/recipe-details/recipe-topping/recipe-topping.component.ts index 504592b..32545ff 100644 --- a/client/src/app/features/recipes/recipe-details/recipe-topping/recipe-topping.component.ts +++ b/client/src/app/features/recipes/recipe-details/recipe-topping/recipe-topping.component.ts @@ -82,21 +82,26 @@ export class RecipeToppingComponent implements OnInit { this._toppingSetOriginalArray = data; console.log('ToppingSet', data, this.index, data.length >= this.index!, data[0]); - this.listGroupId.push(data[this.index!].ListGroupID); - // check length of toppingList if in range with given index - if (data.length >= this.index!) { - this.toppingList.push( - this._formBuilder.group({ - isUse: data[this.index!].isUse, - groupID: data[this.index!].groupID, - defaultIDSelect: data[this.index!].defaultIDSelect, - ListGroupID: data[this.index!].ListGroupID, - }) - ); + if(data[this.index!] != undefined && data[this.index!] != null){ + + this.listGroupId.push(data[this.index!].ListGroupID); + + // check length of toppingList if in range with given index + if (data.length >= this.index!) { + this.toppingList.push( + this._formBuilder.group({ + isUse: data[this.index!].isUse, + groupID: data[this.index!].groupID, + defaultIDSelect: data[this.index!].defaultIDSelect, + ListGroupID: data[this.index!].ListGroupID, + }) + ); + } + + console.log('SubscribeToppingSet', this.toppingList, "list group id=", this.listGroupId); } - console.log('SubscribeToppingSet', this.toppingList, "list group id=", this.listGroupId); }); // get all topping diff --git a/client/src/app/features/recipes/recipes.component.html b/client/src/app/features/recipes/recipes.component.html index 2bfca8b..d9f2c7f 100644 --- a/client/src/app/features/recipes/recipes.component.html +++ b/client/src/app/features/recipes/recipes.component.html @@ -305,7 +305,8 @@
{{ header }} - +
diff --git a/client/src/app/features/recipes/recipes.component.ts b/client/src/app/features/recipes/recipes.component.ts index ea91847..1ffc989 100644 --- a/client/src/app/features/recipes/recipes.component.ts +++ b/client/src/app/features/recipes/recipes.component.ts @@ -166,7 +166,6 @@ export class RecipesComponent implements OnInit, OnDestroy, AfterViewInit { // ); // // get default file that should be opened - } ); @@ -626,4 +625,35 @@ export class RecipesComponent implements OnInit, OnDestroy, AfterViewInit { console.log('copyToTsvErr', err); }); } + + // ------------------------------------ sorting ------------------------------------ + + async sortByHeader(header: string) { + // productCode + // name + // otherName + // description + // otherDescription + // LastUpdate + + // activate sorting + console.log('sortByHeader', header); + // + + // send to server [/recipe/sort] + ( + await this._recipeService.sortRecipe( + await this._recipeService.getCurrentCountry(), + this._recipeService.getCurrentFile(), + header + ) + ).subscribe({ + + next: (data: any) => { + if(data.status == 'OK'){ + window.location.reload(); + } + } + }); + } } diff --git a/server/data/commit.go b/server/data/commit.go index 7708317..60247a8 100644 --- a/server/data/commit.go +++ b/server/data/commit.go @@ -77,7 +77,7 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err } commitDB, err := sqlx.Connect("sqlite3", "./data/database.db") - // fmt.Println("GetCommitLogOfFilename", err) + // //fmt.Println("GetCommitLogOfFilename", err) if err != nil { return nil, err @@ -89,7 +89,7 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err err = commitDB.Select(&commits, "SELECT * FROM commit_log WHERE change_file LIKE ?", "%"+filename+"%") - // fmt.Println("commits", err) + // //fmt.Println("commits", err) if err != nil { return nil, err @@ -112,7 +112,7 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err return nil, err } - // fmt.Println("commitsByCountryID", len(commitsByCountryID) == 0) + // //fmt.Println("commitsByCountryID", len(commitsByCountryID) == 0) if len(commitsByCountryID) == 0 { return nil, fmt.Errorf("no commit found for %s", filename) diff --git a/server/data/data.go b/server/data/data.go index c5e7a03..244cf10 100644 --- a/server/data/data.go +++ b/server/data/data.go @@ -8,6 +8,7 @@ import ( "recipe-manager/helpers" "recipe-manager/models" "recipe-manager/services/logger" + "slices" "strconv" "strings" "time" @@ -358,22 +359,22 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) // try convert if len(countryID) != 3 { for k, v := range d.CurrentCountryID { - fmt.Println("GetRecipe01ByProductCode.Iterate", k, v, v == countryID) + // //fmt.Println("GetRecipe01ByProductCode.Iterate", k, v, v == countryID) if v == countryID { countryID = k break } } } - fmt.Println("GetRecipe01ByProductCode", filename, countryID, productCode) + // //fmt.Println("GetRecipe01ByProductCode", filename, countryID, productCode) if !strings.Contains(filename, "tmp") { if filename == "" || filename == d.CurrentFile[countryID] { // , d.CurrentFile, countryID, "result by country id", len(d.currentRecipe[countryID].Recipe01) - // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::filename", filename) - // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::countryID", countryID) - // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile) - // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID) + // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::filename", filename) + // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::countryID", countryID) + // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile) + // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID) for _, v := range d.CurrentRecipe[countryID].Recipe01 { if v.ProductCode == productCode { @@ -386,17 +387,17 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) } } } - // fmt.Println("No result in current recipe", countryID) + // //fmt.Println("No result in current recipe", countryID) } else if recipe, ok := d.recipeMap[filename]; ok { - // fmt.Println("GetRecipe01ByProductCode.ReadMap", filename, d.CurrentFile, recipe.Recipe[countryID], "countryID=", countryID) + // //fmt.Println("GetRecipe01ByProductCode.ReadMap", filename, d.CurrentFile, recipe.Recipe[countryID], "countryID=", countryID) for _, v := range recipe.Recipe[countryID].Recipe01 { if v.ProductCode == productCode { - d.taoLogger.Log.Debug("GetRecipe01ByProductCode.getSuccess", zap.Any("fromFile", filename), zap.Any("whereSource", d.recipeMap)) + // d.taoLogger.Log.Debug("GetRecipe01ByProductCode.getSuccess", zap.Any("fromFile", filename), zap.Any("whereSource", d.recipeMap)) return v, nil } else if len(v.SubMenu) > 0 { for _, subMenu := range v.SubMenu { if subMenu.ProductCode == productCode { - d.taoLogger.Log.Debug("GetRecipe01ByProductCode.getSuccess", zap.Any("fromFile", filename), zap.Any("whereSource", d.recipeMap)) + // d.taoLogger.Log.Debug("GetRecipe01ByProductCode.getSuccess", zap.Any("fromFile", filename), zap.Any("whereSource", d.recipeMap)) return subMenu, nil } } @@ -915,3 +916,114 @@ func (d *Data) GetCountryIDByName(countryName string) (string, error) { } return "", fmt.Errorf("country name: %s not found", countryName) } + +// ------ sorting ------ +func (d *Data) SortRecipe(countryID, filename string, sort_by string) (error, []string) { + // Get recipe + recipe := d.GetRecipe(countryID, filename) + + // error code + errorCode := 0 + emptiedComparators := make([]string, 0) + + // Sort + switch sort_by { + case "Product Code": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + + if a.ProductCode == "" || b.ProductCode == "" { + errorCode = 1 + emptiedComparators = append(emptiedComparators, a.ProductCode+" !compare! "+b.ProductCode) + } + + return strings.Compare(a.ProductCode, b.ProductCode) + }) + case "Name": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + + if a.Name == "" || b.Name == "" { + errorCode = 2 + emptiedComparators = append(emptiedComparators, a.Name+" !compare! "+b.Name) + } + + return strings.Compare(a.Name, b.Name) + }) + case "Other Name": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + + if a.OtherName == "" || b.OtherName == "" { + errorCode = 3 + emptiedComparators = append(emptiedComparators, a.OtherName+" !compare! "+b.OtherName) + } + + return strings.Compare(a.OtherName, b.OtherName) + }) + case "Description": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + + if a.Description == "" || b.Description == "" { + errorCode = 4 + emptiedComparators = append(emptiedComparators, a.Description+" !compare! "+b.Description) + } + + return strings.Compare(a.Description, b.Description) + }) + case "Other Description": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + + if a.OtherDescription == "" || b.OtherDescription == "" { + errorCode = 5 + emptiedComparators = append(emptiedComparators, a.OtherDescription+" !compare! "+b.OtherDescription) + } + + return strings.Compare(a.OtherDescription, b.OtherDescription) + }) + case "Last Updated": + slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int { + // parse date + layout := "02-Jan-2006 15:04:05" + + if a.LastChange == "" || b.LastChange == "" { + errorCode = 6 + emptiedComparators = append(emptiedComparators, a.ProductCode+":"+a.LastChange+" !compare! "+b.ProductCode+":"+b.LastChange) + } + + timeA, err := time.Parse(layout, a.LastChange) + if err != nil { + // fmt.Println("Parse error! not in layout format: ", a.LastChange) + + errorCode = 7 + emptiedComparators = append(emptiedComparators, a.ProductCode+":"+a.LastChange) + } + + timeB, err := time.Parse(layout, b.LastChange) + if err != nil { + // fmt.Println("Parse error! not in layout format: ", b.LastChange) + + errorCode = 8 + emptiedComparators = append(emptiedComparators, b.ProductCode+":"+b.LastChange) + } + + if a.LastChange == "" && b.LastChange != "" { + errorCode = 0 + return 1 + } else if a.LastChange != "" && b.LastChange == "" { + errorCode = 0 + return -1 + } else if a.LastChange == "" && b.LastChange == "" { + errorCode = 0 + return 0 + } + + return timeA.Compare(timeB) + }) + } + + if errorCode != 0 { + errStatus := fmt.Errorf("ERR[%v]", errorCode) + fmt.Println(errStatus) + return errStatus, emptiedComparators + } + + return nil, emptiedComparators +} diff --git a/server/data/redis.go b/server/data/redis.go index e283717..2455aa5 100644 --- a/server/data/redis.go +++ b/server/data/redis.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "fmt" "time" "github.com/redis/go-redis/v9" @@ -27,7 +26,7 @@ func NewRedisClient(address, password string) *RedisCli { client := redis.NewClient(&options) if err := client.Ping(context.Background()); err.Err() != nil { - fmt.Println("trying localhost ...", err) + // //fmt.Println("trying localhost ...", err) client = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: password, @@ -35,13 +34,14 @@ func NewRedisClient(address, password string) *RedisCli { }) if err_local := client.Ping(context.Background()); err_local.Err() != nil { - fmt.Println("> result ====> ", err_local) + // //fmt.Println("> result ====> ", err_local) + // do as warning } else { - fmt.Println("\n> Localhost Redis OK!\n") + // //fmt.Println("\n> Localhost Redis OK!\n") } } else { - fmt.Println("\n> Redis OK! \n") + // //fmt.Println("\n> Redis OK! \n") } return &RedisCli{ @@ -54,51 +54,51 @@ func (r *RedisCli) HealthCheck() error { } func (r *RedisCli) GetKeyTo(source string, dest interface{}) error { - fmt.Println("Redis> GET ", source) + // //fmt.Println("Redis> GET ", source) // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> GET error ", err) + // //fmt.Println("HS> GET error ", err) return err } saved, err := r.Client.Get(context.Background(), source).Result() // chcek EOF - // fmt.Println("GET last char ", saved[len(saved)-1:]) + // //fmt.Println("GET last char ", saved[len(saved)-1:]) if saved == "" || err != nil { - fmt.Println("GET error (empty on error)|", err, "|while saved=", saved) + // //fmt.Println("GET error (empty on error)|", err, "|while saved=", saved) return err } - // fmt.Println("GET ", saved) + // //fmt.Println("GET ", saved) // if err != nil { - // fmt.Println("GET error ", err) + // //fmt.Println("GET error ", err) // return err // } err = json.NewDecoder(bytes.NewBufferString(saved)).Decode(dest) if err != nil { - fmt.Println("GET error ", err) + // //fmt.Println("GET error ", err) } return err } func (r *RedisCli) SetToKey(key string, value interface{}) error { - fmt.Println("Redis> SET ", key) + // //fmt.Println("Redis> SET ", key) // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> SET error ", err) + // //fmt.Println("HS> SET error ", err) return err } saved, err := json.Marshal(value) if err != nil { - fmt.Println("SET error ", err) + //fmt.Println("SET error ", err) return err } @@ -106,7 +106,7 @@ func (r *RedisCli) SetToKey(key string, value interface{}) error { err = r.Client.Set(context.Background(), key, saved, redis.KeepTTL).Err() if err != nil { - fmt.Println("error on SET ", err) + //fmt.Println("error on SET ", err) } return err @@ -116,7 +116,7 @@ func (r *RedisCli) ExpireKey(key string) error { // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> EXPIRE error ", err) + //fmt.Println("HS> EXPIRE error ", err) return err } @@ -127,19 +127,19 @@ func (r *RedisCli) SetKeyTimeout(key string, value interface{}, timeout int) err // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> EXPIRE error ", err) + //fmt.Println("HS> EXPIRE error ", err) return err } err := r.Client.Expire(context.Background(), key, time.Duration(timeout)*time.Second).Err() - fmt.Println("error on EXPIRE ", err) + //fmt.Println("error on EXPIRE ", err) return err } func (r *RedisCli) KeyList() ([]string, error) { // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> KEYS error ", err) + //fmt.Println("HS> KEYS error ", err) return nil, err } @@ -153,7 +153,7 @@ func (r *RedisCli) KeyList() ([]string, error) { func (r *RedisCli) GetList(key string) ([]string, error) { // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> List.GET error ", err) + //fmt.Println("HS> List.GET error ", err) return nil, err } @@ -163,7 +163,7 @@ func (r *RedisCli) GetList(key string) ([]string, error) { func (r *RedisCli) Add(key string, value interface{}) error { // if cannot pass healthcheck, return err if err := r.HealthCheck(); err != nil { - fmt.Println("HS> List.ADD error ", err) + //fmt.Println("HS> List.ADD error ", err) return err } diff --git a/server/data/sqlite.go b/server/data/sqlite.go index 2007dee..f327443 100644 --- a/server/data/sqlite.go +++ b/server/data/sqlite.go @@ -11,11 +11,9 @@ import ( func NewSqliteDatabase() *sqlx.DB { // ensure that database exists - info, err := os.Stat("./data/database.db") + _, err := os.Stat("./data/database.db") if os.IsNotExist(err) { - fmt.Println("No database found. Check path: ", err) - } else { - fmt.Println("Database existed. ", info) + fmt.Errorf("No database found. Check path: ", err) } db := sqlx.MustConnect("sqlite3", "./data/database.db") diff --git a/server/routers/auth.go b/server/routers/auth.go index b09a128..95d651e 100644 --- a/server/routers/auth.go +++ b/server/routers/auth.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "encoding/base64" "encoding/json" - "fmt" "net/http" "net/url" "recipe-manager/config" @@ -63,7 +62,7 @@ func (ar *AuthRouter) Route(r chi.Router) { var kind string state := r.URL.Query().Get("state") - fmt.Println("url query", r.URL.Query()) + //fmt.Println("url query", r.URL.Query()) if state == "" { http.Error(w, "State not found", http.StatusBadRequest) @@ -76,7 +75,7 @@ func (ar *AuthRouter) Route(r chi.Router) { return } - fmt.Println("val", val) + //fmt.Println("val", val) redirectTo = val["redirect_to"] kind = val["kind"] diff --git a/server/routers/recipe.go b/server/routers/recipe.go index d7733e4..74c6c6f 100644 --- a/server/routers/recipe.go +++ b/server/routers/recipe.go @@ -198,6 +198,8 @@ func (rr *RecipeRouter) Route(r chi.Router) { return } }) + + r.Post("/sort/{country}/{filename}", rr.sortRecipe) }) } @@ -594,7 +596,7 @@ func (rr *RecipeRouter) getSavedRecipes(w http.ResponseWriter, r *http.Request) } commits, err := data.GetCommitLogOfFilename(countryID, file_version) - // fmt.Println("commits", commits) + // //fmt.Println("commits", commits) rr.taoLogger.Log.Debug("RecipeRouter.getSavedRecipes", zap.Any("commits", commits)) if err != nil || len(commits) == 0 { @@ -836,3 +838,37 @@ func lockThenTimeout(mutex *sync.Mutex, timeout time.Duration) bool { return false } } + +// ------------------ Sorting ------------------ +func (rr *RecipeRouter) sortRecipe(w http.ResponseWriter, r *http.Request) { + country := chi.URLParam(r, "country") + filename := chi.URLParam(r, "filename") + + // get sort type from body + var sortConfig map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&sortConfig) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + sortKey := sortConfig["sortKey"].(string) + + // sort recipe + err, emptiedComparators := rr.data.SortRecipe(country, filename, sortKey) + + if err != nil { + + rr.taoLogger.Log.Error("RecipeRouter.sortRecipe", zap.Error(err), zap.Any("emptiedComparators", emptiedComparators)) + + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "key": sortKey, + }) + +} diff --git a/server/routers/topping.go b/server/routers/topping.go index 8dbafb8..86bad2e 100644 --- a/server/routers/topping.go +++ b/server/routers/topping.go @@ -27,7 +27,7 @@ func NewToppingRouter(data *data.Data, taoLogger *logger.TaoLogger) *ToppingRout func (tr *ToppingRouter) Route(r chi.Router) { // check incoming request - // fmt.Println("topping router", r.Routes()) + // //fmt.Println("topping router", r.Routes()) r.Route("/toppover", func(r chi.Router) { diff --git a/server/services/logger/logger.go b/server/services/logger/logger.go index c77e5e4..e850ca1 100644 --- a/server/services/logger/logger.go +++ b/server/services/logger/logger.go @@ -32,11 +32,10 @@ func (tl *TaoLogger) initConfig() *zap.Logger { EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, }), zapcore.AddSync(&lumberjack.Logger{ - Filename: "services/logger/serverlog.log", - MaxSize: 500, // megabytes - MaxBackups: 3, - MaxAge: 28, //days - LocalTime: true, + Filename: "services/logger/serverlog.log", + MaxSize: 10, // megabytes + MaxAge: 28, //days + LocalTime: true, }), enableDebugMode), zapcore.NewCore(zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ TimeKey: "timestamp", diff --git a/server/services/recipe/recipe.go b/server/services/recipe/recipe.go index 77d3201..dd2acc2 100644 --- a/server/services/recipe/recipe.go +++ b/server/services/recipe/recipe.go @@ -118,7 +118,7 @@ func (rs *recipeService) GetRecipeDetailMat(request *contracts.RecipeDetailReque break } else if mat.MaterialName != "" { mat_name = mat.MaterialName - // fmt.Println("SetMat", mat_name) + // //fmt.Println("SetMat", mat_name) break } }