add changes diffing modal & silence some logs

This commit is contained in:
pakintada@gmail.com 2024-03-01 00:22:28 +07:00
parent 148488e2c4
commit da353cec84
22 changed files with 1770 additions and 120 deletions

853
client/package-lock.json generated
View file

@ -9,10 +9,12 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/animations": "^16.2.0", "@angular/animations": "^16.2.0",
"@angular/cdk": "^16.2.12",
"@angular/common": "^16.2.0", "@angular/common": "^16.2.0",
"@angular/compiler": "^16.2.0", "@angular/compiler": "^16.2.0",
"@angular/core": "^16.2.0", "@angular/core": "^16.2.0",
"@angular/forms": "^16.2.0", "@angular/forms": "^16.2.0",
"@angular/material": "^16.2.12",
"@angular/platform-browser": "^16.2.0", "@angular/platform-browser": "^16.2.0",
"@angular/platform-browser-dynamic": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0",
"@angular/router": "^16.2.0", "@angular/router": "^16.2.0",
@ -350,6 +352,34 @@
"@angular/core": "16.2.5" "@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": { "node_modules/@angular/cli": {
"version": "16.2.2", "version": "16.2.2",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.2.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.2.tgz",
@ -517,6 +547,70 @@
"rxjs": "^6.5.3 || ^7.4.0" "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": { "node_modules/@angular/platform-browser": {
"version": "16.2.5", "version": "16.2.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.5.tgz", "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==", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true "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": { "node_modules/@ng-select/ng-select": {
"version": "11.2.0", "version": "11.2.0",
"resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-11.2.0.tgz", "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-11.2.0.tgz",
@ -5693,7 +6539,7 @@
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.12" "node": ">=0.12"
}, },
@ -10500,6 +11346,11 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "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": { "node_modules/sass": {
"version": "1.64.1", "version": "1.64.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz",

View file

@ -11,10 +11,12 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^16.2.0", "@angular/animations": "^16.2.0",
"@angular/cdk": "^16.2.12",
"@angular/common": "^16.2.0", "@angular/common": "^16.2.0",
"@angular/compiler": "^16.2.0", "@angular/compiler": "^16.2.0",
"@angular/core": "^16.2.0", "@angular/core": "^16.2.0",
"@angular/forms": "^16.2.0", "@angular/forms": "^16.2.0",
"@angular/material": "^16.2.12",
"@angular/platform-browser": "^16.2.0", "@angular/platform-browser": "^16.2.0",
"@angular/platform-browser-dynamic": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0",
"@angular/router": "^16.2.0", "@angular/router": "^16.2.0",

View file

@ -309,4 +309,19 @@ export class RecipeService {
{ withCredentials: true, responseType: 'json' } { withCredentials: true, responseType: 'json' }
); );
} }
async sortRecipe(
country: string,
filename: string,
sortKey: string,
): Promise<Observable<any>> {
return this._httpClient.post<any>(
environment.api + '/recipes/sort/' + country + '/' + filename ,
JSON.stringify({
"sortKey": sortKey
}),
{ withCredentials: true, responseType: 'json' }
);
}
} }

View file

@ -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(".", "");
}

View file

@ -1,40 +1,68 @@
<p>merge works!</p> <div class="h-[80vh]">
<!-- 3 columns with master at center --> <!-- select a commit -->
<div class="grid grid-cols-3"> <div *ngIf="isPatchMapKeysLoaded()">
<!-- from patch --> <label for="patchKeys">Select a commit : </label>
<div> <select
<div class="m-2" *ngFor="let patchKey of getPatchMapKeys()"> class="select select-sm select-primary"
name="patchKeys"
(change)="selectCommit($event)"
>
<option>---</option>
<option *ngFor="let commit of getPatchMapKeys()" [value]="commit" id="commit_{{commit}}">
[{{ getCommitAttr(commit, "Created_at") }}] [{{
getCommitAttr(commit, "Editor")
}}] | {{ getCommitAttr(commit, "Msg") }}
</option>
</select>
<!-- enable from console -->
<button class="hidden" (click)="testLoadCheck()">Test load</button>
</div>
<!-- 3 columns with master at center -->
<div class="flex flex-grow gap-4 p-4">
<!-- from patch -->
<div
class="w-100 overflow-x-visible overflow-y-visible"
*ngIf="hasProductCodeOfCommits() && selectedCommit != '' && selectedCommit != '---'"
>
<details class="collapse collapse-arrow">
<summary class="collapse-title">{{ selectedCommit }}</summary>
<div class="collapse-content">
<app-recipe-list
[productCode]="
getCommitAttr(selectedCommit, 'contents').productCode
"
[noFetch]="true"
[recipeList]="getCommitAttr(selectedCommit, 'contents').recipes"
[displayOnly]="true"
[diffChangeContext]="buildContext()"
(recipeListFormChange)="onRecipeListFormChange($event)"
></app-recipe-list>
</div>
</details>
</div>
<!-- from master -->
<div *ngIf="hasProductCodeOfCommits()">
<!-- display zone -->
<mat-card class="!bg-amber-300 !rounded-xl p-2"> <div *ngFor="let pd of getProductCodesOfCommits()">
<mat-card-title> <div class="container">
{{getCommitAttr(patchKey, 'Msg')}} <details class="collapse collapse-arrow">
</mat-card-title> <summary class="collapse-title">{{ pd }}</summary>
<mat-card-content> <div>
<div> <app-recipe-list
<p>date: {{getCommitAttr(patchKey, 'Created_at') }}</p> [productCode]="pd!"
<p>editor: {{getCommitAttr(patchKey, 'Editor') }}</p> [noFetch]="false"
<p>ref: {{patchKey}}</p> [displayOnly]="true"
[diffChangeContext]="buildContext()"
<p class="hidden">full ref: {{getCommitAttr(patchKey, 'Change_file')}}</p> (recipeListFormChange)="onRecipeListFormChange($event)"
></app-recipe-list>
<button mat-raised-button class="!bg-white" (click)="copyRef(getCommitAttr(patchKey, 'Change_file'))">Copy full ref</button> </div>
</div> <!-- collapse -->
</mat-card-content> </details>
</mat-card> </div>
</div>
</div> </div>
</div> </div>
<!-- from master -->
<div>
<p>master</p>
</div>
<!-- from machine -->
<div>
<p>machine</p>
</div>
</div> </div>

View file

@ -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 { CommonModule } from '@angular/common';
import { RecipeService } from 'src/app/core/services/recipe.service'; import { RecipeService } from 'src/app/core/services/recipe.service';
import { MatCardModule } from '@angular/material/card'; 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 { copy } from 'src/app/shared/helpers/copy';
import { compare } from 'src/app/shared/helpers/compare'; import { compare } from 'src/app/shared/helpers/compare';
import { AsyncStorage } from 'src/app/shared/helpers/asyncStorage'; 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({ @Component({
selector: 'app-merge', selector: 'app-merge',
standalone: true, standalone: true,
imports: [CommonModule, MatCardModule, MatButtonModule],
templateUrl: './merge.component.html', 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<any> | undefined = undefined; @Input() commit: Array<any> | undefined = undefined;
patchMap: any = {}; patchMap: any = {};
@ -23,11 +59,47 @@ export class MergeComponent implements OnInit {
//map productCode:commits //map productCode:commits
mapProductCodeWithCommits: any = {}; mapProductCodeWithCommits: any = {};
// master recipes for product code given by commits // master recipes for product code given by commits
targetRecipe: any = {} targetRecipe: any = {};
// change map // change map
changeMap: any = {}; 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<any>;
templatePortal!: TemplatePortal<any>;
// template
@ViewChild('templateRecipeChangeContent')
templateRecipeChangeContent!: TemplateRef<any>;
constructor(
private _recipeService: RecipeService,
private _containerRef: ViewContainerRef // private cfr: ComponentFactoryResolver,
) // private appRef: ApplicationRef,
// private injector: Injector
{}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
( (
@ -51,7 +123,7 @@ export class MergeComponent implements OnInit {
if (productCode) { if (productCode) {
// check if exist in map // check if exist in map
if (!this.mapProductCodeWithCommits[productCode]) { if (!this.mapProductCodeWithCommits[productCode]) {
this.mapProductCodeWithCommits[productCode] = [ key ]; this.mapProductCodeWithCommits[productCode] = [key];
} else { } else {
this.mapProductCodeWithCommits[productCode].push(key); this.mapProductCodeWithCommits[productCode].push(key);
} }
@ -69,7 +141,29 @@ export class MergeComponent implements OnInit {
}); });
// fetch related product codes // 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 // funcitons
@ -77,20 +171,73 @@ export class MergeComponent implements OnInit {
// get patch map keys // get patch map keys
getPatchMapKeys = () => Object.keys(this.patchMap); 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 // get commit message by commit id
getCommitAttr = (id: string, attr: string) => this.fullPatches[id][attr]; getCommitAttr = (id: string, attr: string) => this.fullPatches[id][attr];
// copy to clipboard // copy to clipboard
copyRef = async (ref: string) => await copy(ref); 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<any>;
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 --------------------------- // ----------------------------- Master Functions ---------------------------
getMasterRecipeOfProductCode = async (productCode: string) => { getMasterRecipeOfProductCode = async (productCode: string) => {
(await this._recipeService.getRawRecipeOfProductCode( (
await this._recipeService.getCurrentCountry(), await this._recipeService.getRawRecipeOfProductCode(
this._recipeService.getCurrentFile(), await this._recipeService.getCurrentCountry(),
productCode this._recipeService.getCurrentFile(),
)).subscribe({ productCode
)
).subscribe({
next: (data: any) => { next: (data: any) => {
this.targetRecipe[productCode] = data; this.targetRecipe[productCode] = data;
// console.log("get master recipe", this.targetRecipe); // console.log("get master recipe", this.targetRecipe);
@ -106,12 +253,88 @@ export class MergeComponent implements OnInit {
); );
// save only what changes // save only what changes
this.changeMap[patchId] = { 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;
};
} }

View file

@ -1,4 +1,4 @@
<div class="justify-center w-full flex flex-row sticky top-0 z-10"> <div class="justify-center w-full flex flex-row sticky top-0 z-10" *ngIf="displayOnly == false">
<button class="btn w-1/2" (click)="addRow()">Add</button> <button class="btn w-1/2" (click)="addRow()">Add</button>
<button class="btn w-1/2" (click)="removeRow()">Remove</button> <button class="btn w-1/2" (click)="removeRow()">Remove</button>
</div> </div>
@ -25,6 +25,7 @@
(click)="addToSelection(i)" (click)="addToSelection(i)"
(mousedown)="initHoldEvent()" (mousedown)="initHoldEvent()"
(mouseup)="openRecipeListEditor(i)" (mouseup)="openRecipeListEditor(i)"
*ngIf="invokeZeroMaterialChecker(i)"
> >
<td class="font-medium text-gray-900 whitespace-nowrap sticky left-0"> <td class="font-medium text-gray-900 whitespace-nowrap sticky left-0">
<input type="checkbox" class="toggle" formControlName="isUse" /> <input type="checkbox" class="toggle" formControlName="isUse" />

View file

@ -1,5 +1,5 @@
import { CommonModule, NgFor, NgIf } from '@angular/common'; 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 { import {
FormArray, FormArray,
FormBuilder, FormBuilder,
@ -42,10 +42,15 @@ import Lang from 'src/app/shared/helpers/lang';
standalone: true, standalone: true,
imports: [CommonModule,NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent] imports: [CommonModule,NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent]
}) })
export class RecipeListComponent implements OnInit { export class RecipeListComponent implements OnInit, OnChanges {
@Input({ required: true }) productCode!: string; @Input({ required: true }) productCode!: string;
@Input() noFetch: boolean = false; @Input() noFetch: boolean = false;
@Input() recipeList: any | undefined = undefined; @Input() recipeList: any | undefined = undefined;
@Input() displayOnly: boolean = false;
@Input() diffChangeContext: {
changeContext: any;
skipZeroes: boolean;
} | undefined = undefined;
@Output() recipeListFormChange = new EventEmitter<unknown[]>(); @Output() recipeListFormChange = new EventEmitter<unknown[]>();
materialList: MaterialCode[] = []; materialList: MaterialCode[] = [];
@ -267,6 +272,7 @@ export class RecipeListComponent implements OnInit {
this.isMatLoaded = true; this.isMatLoaded = true;
}); });
} else if(this.recipeList != undefined){ } else if(this.recipeList != undefined){
console.log("test recipelist", this.recipeList);
this.recipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => { this.recipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => {
// StringParam // StringParam
@ -297,7 +303,7 @@ export class RecipeListComponent implements OnInit {
// --------------- mapping missing data --------------- // --------------- mapping missing data ---------------
// map name // map name
console.log("use recipeList input; ", recipeDetailMat); // console.log("use recipeList input; ", recipeDetailMat);
this.recipeListData.push( this.recipeListData.push(
this._formBuilder.group({ this._formBuilder.group({
@ -460,6 +466,216 @@ export class RecipeListComponent implements OnInit {
}); });
} }
async ngOnChanges(changes: SimpleChanges): Promise<void> {
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 // add new row
addRow() { addRow() {
@ -521,6 +737,11 @@ export class RecipeListComponent implements OnInit {
} }
isEditable() { isEditable() {
if(this.displayOnly){
return !this.displayOnly;
}
return this._userService return this._userService
.getCurrentUser()! .getCurrentUser()!
.permissions.includes(UserPermissions.EDITOR); .permissions.includes(UserPermissions.EDITOR);
@ -737,7 +958,7 @@ export class RecipeListComponent implements OnInit {
await Promise.resolve(); await Promise.resolve();
if (this.timeoutHandler) { if (this.timeoutHandler) {
if (this.timeout >= 20) { if (this.timeout >= 20 && !this.displayOnly) {
// alert("Opening Recipe List Editor in detail") // alert("Opening Recipe List Editor in detail")
if (confirm("Are you sure you want to open Recipe List Editor in detail?")) { if (confirm("Are you sure you want to open Recipe List Editor in detail?")) {
this.showDetailRecipeList = true; 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;
}
} }

View file

@ -82,21 +82,26 @@ export class RecipeToppingComponent implements OnInit {
this._toppingSetOriginalArray = data; this._toppingSetOriginalArray = data;
console.log('ToppingSet', data, this.index, data.length >= this.index!, data[0]); 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[this.index!] != undefined && data[this.index!] != null){
if (data.length >= this.index!) {
this.toppingList.push( this.listGroupId.push(data[this.index!].ListGroupID);
this._formBuilder.group({
isUse: data[this.index!].isUse, // check length of toppingList if in range with given index
groupID: data[this.index!].groupID, if (data.length >= this.index!) {
defaultIDSelect: data[this.index!].defaultIDSelect, this.toppingList.push(
ListGroupID: data[this.index!].ListGroupID, 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 // get all topping

View file

@ -305,7 +305,8 @@
<th scope="col" class="px-6 py-3" *ngFor="let header of tableHeads"> <th scope="col" class="px-6 py-3" *ngFor="let header of tableHeads">
<div class="flex items-center uppercase"> <div class="flex items-center uppercase">
{{ header }} {{ header }}
<a href="#" <!-- sorting -->
<button (click)="sortByHeader(header)"
><svg ><svg
class="w-3 h-3 ml-1.5" class="w-3 h-3 ml-1.5"
aria-hidden="true" aria-hidden="true"
@ -317,7 +318,7 @@
d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z" d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"
/> />
</svg> </svg>
</a> </button>
</div> </div>
</th> </th>
<th scope="col" class="px-6 py-3"></th> <th scope="col" class="px-6 py-3"></th>

View file

@ -166,7 +166,6 @@ export class RecipesComponent implements OnInit, OnDestroy, AfterViewInit {
// ); // );
// // get default file that should be opened // // get default file that should be opened
} }
); );
@ -626,4 +625,35 @@ export class RecipesComponent implements OnInit, OnDestroy, AfterViewInit {
console.log('copyToTsvErr', err); 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();
}
}
});
}
} }

View file

@ -77,7 +77,7 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err
} }
commitDB, err := sqlx.Connect("sqlite3", "./data/database.db") commitDB, err := sqlx.Connect("sqlite3", "./data/database.db")
// fmt.Println("GetCommitLogOfFilename", err) // //fmt.Println("GetCommitLogOfFilename", err)
if err != nil { if err != nil {
return nil, err 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+"%") err = commitDB.Select(&commits, "SELECT * FROM commit_log WHERE change_file LIKE ?", "%"+filename+"%")
// fmt.Println("commits", err) // //fmt.Println("commits", err)
if err != nil { if err != nil {
return nil, err return nil, err
@ -112,7 +112,7 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err
return nil, err return nil, err
} }
// fmt.Println("commitsByCountryID", len(commitsByCountryID) == 0) // //fmt.Println("commitsByCountryID", len(commitsByCountryID) == 0)
if len(commitsByCountryID) == 0 { if len(commitsByCountryID) == 0 {
return nil, fmt.Errorf("no commit found for %s", filename) return nil, fmt.Errorf("no commit found for %s", filename)

View file

@ -8,6 +8,7 @@ import (
"recipe-manager/helpers" "recipe-manager/helpers"
"recipe-manager/models" "recipe-manager/models"
"recipe-manager/services/logger" "recipe-manager/services/logger"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -358,22 +359,22 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string)
// try convert // try convert
if len(countryID) != 3 { if len(countryID) != 3 {
for k, v := range d.CurrentCountryID { 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 { if v == countryID {
countryID = k countryID = k
break break
} }
} }
} }
fmt.Println("GetRecipe01ByProductCode", filename, countryID, productCode) // //fmt.Println("GetRecipe01ByProductCode", filename, countryID, productCode)
if !strings.Contains(filename, "tmp") { if !strings.Contains(filename, "tmp") {
if filename == "" || filename == d.CurrentFile[countryID] { if filename == "" || filename == d.CurrentFile[countryID] {
// , d.CurrentFile, countryID, "result by country id", len(d.currentRecipe[countryID].Recipe01) // , d.CurrentFile, countryID, "result by country id", len(d.currentRecipe[countryID].Recipe01)
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::filename", filename) // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::filename", filename)
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::countryID", countryID) // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::countryID", countryID)
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile) // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile)
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID) // //fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID)
for _, v := range d.CurrentRecipe[countryID].Recipe01 { for _, v := range d.CurrentRecipe[countryID].Recipe01 {
if v.ProductCode == productCode { 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 { } 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 { for _, v := range recipe.Recipe[countryID].Recipe01 {
if v.ProductCode == productCode { 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 return v, nil
} else if len(v.SubMenu) > 0 { } else if len(v.SubMenu) > 0 {
for _, subMenu := range v.SubMenu { for _, subMenu := range v.SubMenu {
if subMenu.ProductCode == productCode { 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 return subMenu, nil
} }
} }
@ -915,3 +916,114 @@ func (d *Data) GetCountryIDByName(countryName string) (string, error) {
} }
return "", fmt.Errorf("country name: %s not found", countryName) 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
}

View file

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@ -27,7 +26,7 @@ func NewRedisClient(address, password string) *RedisCli {
client := redis.NewClient(&options) client := redis.NewClient(&options)
if err := client.Ping(context.Background()); err.Err() != nil { if err := client.Ping(context.Background()); err.Err() != nil {
fmt.Println("trying localhost ...", err) // //fmt.Println("trying localhost ...", err)
client = redis.NewClient(&redis.Options{ client = redis.NewClient(&redis.Options{
Addr: "localhost:6379", Addr: "localhost:6379",
Password: password, Password: password,
@ -35,13 +34,14 @@ func NewRedisClient(address, password string) *RedisCli {
}) })
if err_local := client.Ping(context.Background()); err_local.Err() != nil { if err_local := client.Ping(context.Background()); err_local.Err() != nil {
fmt.Println("> result ====> ", err_local) // //fmt.Println("> result ====> ", err_local)
// do as warning // do as warning
} else { } else {
fmt.Println("\n> Localhost Redis OK!\n") // //fmt.Println("\n> Localhost Redis OK!\n")
} }
} else { } else {
fmt.Println("\n> Redis OK! \n") // //fmt.Println("\n> Redis OK! \n")
} }
return &RedisCli{ return &RedisCli{
@ -54,51 +54,51 @@ func (r *RedisCli) HealthCheck() error {
} }
func (r *RedisCli) GetKeyTo(source string, dest interface{}) 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 cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> GET error ", err) // //fmt.Println("HS> GET error ", err)
return err return err
} }
saved, err := r.Client.Get(context.Background(), source).Result() saved, err := r.Client.Get(context.Background(), source).Result()
// chcek EOF // chcek EOF
// fmt.Println("GET last char ", saved[len(saved)-1:]) // //fmt.Println("GET last char ", saved[len(saved)-1:])
if saved == "" || err != nil { 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 return err
} }
// fmt.Println("GET ", saved) // //fmt.Println("GET ", saved)
// if err != nil { // if err != nil {
// fmt.Println("GET error ", err) // //fmt.Println("GET error ", err)
// return err // return err
// } // }
err = json.NewDecoder(bytes.NewBufferString(saved)).Decode(dest) err = json.NewDecoder(bytes.NewBufferString(saved)).Decode(dest)
if err != nil { if err != nil {
fmt.Println("GET error ", err) // //fmt.Println("GET error ", err)
} }
return err return err
} }
func (r *RedisCli) SetToKey(key string, value interface{}) error { 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 cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> SET error ", err) // //fmt.Println("HS> SET error ", err)
return err return err
} }
saved, err := json.Marshal(value) saved, err := json.Marshal(value)
if err != nil { if err != nil {
fmt.Println("SET error ", err) //fmt.Println("SET error ", err)
return 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() err = r.Client.Set(context.Background(), key, saved, redis.KeepTTL).Err()
if err != nil { if err != nil {
fmt.Println("error on SET ", err) //fmt.Println("error on SET ", err)
} }
return err return err
@ -116,7 +116,7 @@ func (r *RedisCli) ExpireKey(key string) error {
// if cannot pass healthcheck, return err // if cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> EXPIRE error ", err) //fmt.Println("HS> EXPIRE error ", err)
return 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 cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> EXPIRE error ", err) //fmt.Println("HS> EXPIRE error ", err)
return err return err
} }
err := r.Client.Expire(context.Background(), key, time.Duration(timeout)*time.Second).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 return err
} }
func (r *RedisCli) KeyList() ([]string, error) { func (r *RedisCli) KeyList() ([]string, error) {
// if cannot pass healthcheck, return err // if cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> KEYS error ", err) //fmt.Println("HS> KEYS error ", err)
return nil, err return nil, err
} }
@ -153,7 +153,7 @@ func (r *RedisCli) KeyList() ([]string, error) {
func (r *RedisCli) GetList(key string) ([]string, error) { func (r *RedisCli) GetList(key string) ([]string, error) {
// if cannot pass healthcheck, return err // if cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> List.GET error ", err) //fmt.Println("HS> List.GET error ", err)
return nil, 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 { func (r *RedisCli) Add(key string, value interface{}) error {
// if cannot pass healthcheck, return err // if cannot pass healthcheck, return err
if err := r.HealthCheck(); err != nil { if err := r.HealthCheck(); err != nil {
fmt.Println("HS> List.ADD error ", err) //fmt.Println("HS> List.ADD error ", err)
return err return err
} }

View file

@ -11,11 +11,9 @@ import (
func NewSqliteDatabase() *sqlx.DB { func NewSqliteDatabase() *sqlx.DB {
// ensure that database exists // ensure that database exists
info, err := os.Stat("./data/database.db") _, err := os.Stat("./data/database.db")
if os.IsNotExist(err) { if os.IsNotExist(err) {
fmt.Println("No database found. Check path: ", err) fmt.Errorf("No database found. Check path: ", err)
} else {
fmt.Println("Database existed. ", info)
} }
db := sqlx.MustConnect("sqlite3", "./data/database.db") db := sqlx.MustConnect("sqlite3", "./data/database.db")

View file

@ -5,7 +5,6 @@ import (
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"recipe-manager/config" "recipe-manager/config"
@ -63,7 +62,7 @@ func (ar *AuthRouter) Route(r chi.Router) {
var kind string var kind string
state := r.URL.Query().Get("state") state := r.URL.Query().Get("state")
fmt.Println("url query", r.URL.Query()) //fmt.Println("url query", r.URL.Query())
if state == "" { if state == "" {
http.Error(w, "State not found", http.StatusBadRequest) http.Error(w, "State not found", http.StatusBadRequest)
@ -76,7 +75,7 @@ func (ar *AuthRouter) Route(r chi.Router) {
return return
} }
fmt.Println("val", val) //fmt.Println("val", val)
redirectTo = val["redirect_to"] redirectTo = val["redirect_to"]
kind = val["kind"] kind = val["kind"]

View file

@ -198,6 +198,8 @@ func (rr *RecipeRouter) Route(r chi.Router) {
return 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) 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)) rr.taoLogger.Log.Debug("RecipeRouter.getSavedRecipes", zap.Any("commits", commits))
if err != nil || len(commits) == 0 { if err != nil || len(commits) == 0 {
@ -836,3 +838,37 @@ func lockThenTimeout(mutex *sync.Mutex, timeout time.Duration) bool {
return false 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,
})
}

View file

@ -27,7 +27,7 @@ func NewToppingRouter(data *data.Data, taoLogger *logger.TaoLogger) *ToppingRout
func (tr *ToppingRouter) Route(r chi.Router) { func (tr *ToppingRouter) Route(r chi.Router) {
// check incoming request // check incoming request
// fmt.Println("topping router", r.Routes()) // //fmt.Println("topping router", r.Routes())
r.Route("/toppover", func(r chi.Router) { r.Route("/toppover", func(r chi.Router) {

View file

@ -32,11 +32,10 @@ func (tl *TaoLogger) initConfig() *zap.Logger {
EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder, EncodeTime: zapcore.ISO8601TimeEncoder,
}), zapcore.AddSync(&lumberjack.Logger{ }), zapcore.AddSync(&lumberjack.Logger{
Filename: "services/logger/serverlog.log", Filename: "services/logger/serverlog.log",
MaxSize: 500, // megabytes MaxSize: 10, // megabytes
MaxBackups: 3, MaxAge: 28, //days
MaxAge: 28, //days LocalTime: true,
LocalTime: true,
}), enableDebugMode), }), enableDebugMode),
zapcore.NewCore(zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ zapcore.NewCore(zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
TimeKey: "timestamp", TimeKey: "timestamp",

View file

@ -118,7 +118,7 @@ func (rs *recipeService) GetRecipeDetailMat(request *contracts.RecipeDetailReque
break break
} else if mat.MaterialName != "" { } else if mat.MaterialName != "" {
mat_name = mat.MaterialName mat_name = mat.MaterialName
// fmt.Println("SetMat", mat_name) // //fmt.Println("SetMat", mat_name)
break break
} }
} }