Compare commits
345 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f13fa62d3 | ||
|
|
4ed7215292 | ||
|
|
1ea220b489 | ||
|
|
fc8be33dc6 | ||
|
|
58382ddfee | ||
|
|
0d20afcf27 | ||
|
|
aa5fc54576 | ||
|
|
e5c4fd7d3c | ||
|
|
a4e3ed8854 | ||
|
|
acabdba657 | ||
|
|
0e6e4f7b09 | ||
|
|
7bb24c4f91 | ||
|
|
e21c225ddd | ||
|
|
8582f2e6be | ||
|
|
b929f1d136 | ||
|
|
55b21c4989 | ||
|
|
911516add2 | ||
|
|
cbdfcf4d8f | ||
|
|
72e760fff8 | ||
|
|
ae0eebcc6f | ||
|
|
7c6f9fc25e | ||
|
|
310cc4bb51 | ||
|
|
99facc0434 | ||
|
|
ebaf1709b1 | ||
|
|
17d2e8c530 | ||
|
|
b9c01a287c | ||
|
|
6c7865a8ec | ||
|
|
9e457a13b4 | ||
|
|
fba9b330dd | ||
|
|
d7ad450ca1 | ||
|
|
a5d10ad148 | ||
|
|
e92bd55cfc | ||
|
|
f36b1fa8e2 | ||
|
|
3f214d9726 | ||
|
|
ba85260cc4 | ||
|
|
e88929f0d4 | ||
|
|
32de775859 | ||
|
|
3c89b8f7e7 | ||
|
|
34387d49d2 | ||
|
|
304657d259 | ||
|
|
835cb7820e | ||
|
|
3724a6c0b6 | ||
|
|
c71a4995a6 | ||
|
|
c644eaeba2 | ||
|
|
2faecb44a9 | ||
|
|
81513d34dc | ||
|
|
9bc05f1165 | ||
|
|
007e761bd5 | ||
|
|
0ca5e7e2ad | ||
|
|
6547eb2ad5 | ||
|
|
17943f2e8d | ||
|
|
c8b3e3eb3c | ||
|
|
c331a61309 | ||
|
|
5b51eb85a1 | ||
|
|
f079b6c157 | ||
|
|
a8b5cfce14 | ||
|
|
85c6d91b2e | ||
|
|
7f8336a64f | ||
|
|
1155c16f8a | ||
|
|
66277814c5 | ||
|
|
0fa39cf900 | ||
|
|
28acee573d | ||
|
|
80fceb45dd | ||
|
|
fb4b58a496 | ||
|
|
9bbaecb7e5 | ||
|
|
9df87dc218 | ||
|
|
646e16557f | ||
|
|
98ac49fbc8 | ||
|
|
845c819392 | ||
|
|
c33ae3c918 | ||
|
|
88743acacc | ||
|
|
9b1179d99a | ||
|
|
01c244aa07 | ||
|
|
ce334dbdeb | ||
|
|
1c48cea02e | ||
|
|
ca0db25484 | ||
|
|
f872158c5f | ||
|
|
f24e5dbb82 | ||
|
|
590ebcbae2 | ||
|
|
f3faa6a6cc | ||
|
|
d595a22db1 | ||
|
|
04ae68891c | ||
|
|
f60ea513ac | ||
|
|
0b64044f57 | ||
|
|
0738d8c4ef | ||
|
|
76771023c2 | ||
|
|
213cabce7a | ||
|
|
e165ba7500 | ||
|
|
4c0437d7fc | ||
|
|
aec5da15df | ||
|
|
40c6f881fc | ||
|
|
a764ab1050 | ||
|
|
960b434ef6 | ||
|
|
748cce4032 | ||
|
|
4ccde998e0 | ||
|
|
04eaca1600 | ||
|
|
e733adf374 | ||
|
|
5eef6d403a | ||
|
|
5b7dd6a55a | ||
|
|
3a9f36e71b | ||
|
|
a668b23118 | ||
|
|
e7fca876bb | ||
|
|
e8f5bc8f2c | ||
|
|
1674055943 | ||
|
|
4811d301e6 | ||
|
|
174d77d2ea | ||
|
|
848e46cf6a | ||
|
|
b7c8634407 | ||
|
|
5cc09f251e | ||
|
|
ee1d7b2445 | ||
|
|
9473a7f5bd | ||
|
|
217a44c2e0 | ||
|
|
0021307c71 | ||
|
|
6f288516e3 | ||
|
|
252c261621 | ||
|
|
786d235de0 | ||
|
|
e737d134ad | ||
|
|
3b0b6542cd | ||
|
|
61d4d4f5c6 | ||
|
|
12c5c1cb4e | ||
|
|
02a9cb49e9 | ||
|
|
fb6951a7ba | ||
|
|
e7a04dda9e | ||
|
|
696e7f6f6d | ||
|
|
d4b69a18a9 | ||
|
|
4cf42c8943 | ||
|
|
fae7d912d2 | ||
|
|
fc06bcc31d | ||
|
|
005167487d | ||
|
|
c12ed0c968 | ||
|
|
cbd9fd055b | ||
|
|
301ea99abe | ||
|
|
8a2033740e | ||
|
|
c12b6f4d35 | ||
|
|
e08e28e095 | ||
|
|
957dc404a8 | ||
|
|
0dd210b04f | ||
|
|
b6813bccf3 | ||
|
|
48a5160d6f | ||
|
|
d67798bd60 | ||
|
|
292573f3b3 | ||
|
|
55dcb40dcd | ||
|
|
ecf9c8912e | ||
|
|
9b427b0cac | ||
|
|
47e371a41e | ||
|
|
9faca532c6 | ||
|
|
bf628b33a2 | ||
|
|
3ea4e45b87 | ||
|
|
94e0021ed4 | ||
|
|
a2d30f0a65 | ||
|
|
7b29cc5f31 | ||
|
|
59f2dde9ec | ||
|
|
0010eba410 | ||
|
|
5d31262fb3 | ||
|
|
a37287f13c | ||
|
|
124ecee0bf | ||
|
|
6f255d68b4 | ||
|
|
82b11d2eba | ||
|
|
42cc494e2f | ||
|
|
67485fdac2 | ||
|
|
0885ad776d | ||
|
|
b4007f3fe5 | ||
|
|
37c9d7a65d | ||
|
|
7c7cdfda51 | ||
|
|
6dbc7a4621 | ||
|
|
dadafa6117 | ||
|
|
03d4bd5c8b | ||
|
|
3f23c6d66e | ||
|
|
6d97f7588b | ||
|
|
773171f4af | ||
|
|
9ce0ec27ca | ||
|
|
bd77067dc7 | ||
|
|
da086cdaa6 | ||
|
|
7f09e57248 | ||
|
|
c4bdba4683 | ||
|
|
68dbdfb8e7 | ||
|
|
986d552e49 | ||
|
|
fa574115aa | ||
|
|
d1c0559099 | ||
|
|
59fbd1f78f | ||
|
|
2bb459961a | ||
|
|
87e1c88452 | ||
|
|
8ba271bb6c | ||
|
|
63df71a6aa | ||
|
|
4b656564fe | ||
|
|
052bf3a5d7 | ||
|
|
ecb5fd63ac | ||
|
|
5f0904e6a9 | ||
|
|
99b2c83d61 | ||
|
|
a6f8a35765 | ||
|
|
6eef9aba4b | ||
|
|
54d60b6c97 | ||
|
|
f09ef68442 | ||
|
|
85fba4a1dd | ||
|
|
a6383c1dbc | ||
|
|
bfaae5adf6 | ||
|
|
df01d0a910 | ||
|
|
bd532cd28f | ||
|
|
a9d6e5e728 | ||
|
|
14f4f00898 | ||
|
|
6dc910c4ca | ||
|
|
1b76808917 | ||
|
|
890cf417a1 | ||
|
|
2f2ee530b1 | ||
|
|
8d4a68f92c | ||
|
|
6cb63fcf43 | ||
|
|
9fba181eb9 | ||
|
|
a4132b82f5 | ||
|
|
d1df30b56e | ||
|
|
f0c51915b2 | ||
|
|
4f4123cd5d | ||
|
|
c83eb568db | ||
|
|
da0e057721 | ||
|
|
523f2c999e | ||
|
|
d4d593d24a | ||
|
|
c7a15d2a93 | ||
|
|
60ace01151 | ||
|
|
cbbc21609e | ||
|
|
41ca2a28ec | ||
|
|
3cf27e38c1 | ||
|
|
21d387912c | ||
|
|
f140f1fc16 | ||
|
|
45622e7fd6 | ||
|
|
c9abddb301 | ||
|
|
5a14bf79fa | ||
|
|
1e4247c247 | ||
|
|
6f3833a7f0 | ||
|
|
f5f86d7e60 | ||
|
|
1fc2820742 | ||
|
|
867037cc0a | ||
|
|
03eb179298 | ||
|
|
db37aa6257 | ||
|
|
fed7c6a180 | ||
|
|
6f05c8fc86 | ||
|
|
625603def6 | ||
|
|
e553ba3272 | ||
|
|
ed00a87a7a | ||
|
|
a52f55b5d4 | ||
|
|
04a69f5e68 | ||
|
|
c851ab1394 | ||
|
|
5a4ed98aaf | ||
|
|
fdaf591751 | ||
|
|
5d8b8ee5cf | ||
|
|
60b0d502ae | ||
|
|
b1b8ce2e49 | ||
|
|
78b74d5a9a | ||
|
|
5d40f20a32 | ||
|
|
3b84e73f80 | ||
|
|
28badd45cd | ||
|
|
bbd0cef55b | ||
|
|
0fd87149ae | ||
|
|
e1323070c6 | ||
|
|
b368fa6fe8 | ||
|
|
8f4cc53d0f | ||
|
|
5aa064ac65 | ||
|
|
7ff7864767 | ||
|
|
d40f839365 | ||
|
|
2524896f46 | ||
|
|
fd4491b2a3 | ||
|
|
e69f8b3f0d | ||
|
|
699e74a867 | ||
|
|
ae52093b5d | ||
|
|
66662a0314 | ||
|
|
d3ac14497d | ||
|
|
dc5f14c696 | ||
|
|
83f13eda4f | ||
|
|
a8f5fb2e25 | ||
|
|
eb58e96d01 | ||
|
|
a4e425d373 | ||
|
|
137a62338c | ||
|
|
f33ada72f4 | ||
|
|
da18c68a86 | ||
|
|
30846478ed | ||
|
|
f631395f61 | ||
|
|
4ee4842f74 | ||
|
|
a71e094e55 | ||
|
|
8ac4bbfd9f | ||
|
|
cc8d040654 | ||
|
|
7efa3b7ad2 | ||
|
|
2d6ee6655c | ||
|
|
aa4516286e | ||
|
|
02d0089d0d | ||
|
|
7d2f5c8a77 | ||
|
|
8e5376bd4f | ||
|
|
3cef35f3bf | ||
|
|
0ae0fa819e | ||
|
|
75356e2328 | ||
|
|
d02dadc56a | ||
|
|
01d06f1c87 | ||
|
|
7821b5a2f6 | ||
|
|
1a8ab7b4b9 | ||
|
|
afbfaf2959 | ||
|
|
88babfd1f6 | ||
|
|
2fe65233bc | ||
|
|
063f019de1 | ||
|
|
39b4ff1f97 | ||
|
|
1522d08a32 | ||
|
|
a4692e8ee3 | ||
|
|
58d4ece28d | ||
|
|
8ebd5f760d | ||
|
|
140ad55646 | ||
|
|
cfaddff1f7 | ||
|
|
31586bca72 | ||
|
|
daf516e9fe | ||
|
|
47080bcd8b | ||
|
|
efa5229391 | ||
|
|
3d9a34b778 | ||
|
|
9e8a1fc12e | ||
|
|
a71a09463f | ||
|
|
4714856531 | ||
|
|
b50bd54227 | ||
|
|
b50019b01c | ||
|
|
95b528deb6 | ||
|
|
2ec18cc91d | ||
|
|
e884d8a513 | ||
|
|
226ce2e73a | ||
|
|
0b9db19708 | ||
|
|
3590385c9c | ||
|
|
6b70e92a0e | ||
|
|
a06124ab4e | ||
|
|
5449cacc2b | ||
|
|
223fbe688c | ||
|
|
721d3e3b1a | ||
|
|
d0551742ec | ||
|
|
dbfde290c8 | ||
|
|
2868ebe506 | ||
|
|
ba4d7f6d05 | ||
|
|
e46649c656 | ||
|
|
4c87375ee5 | ||
|
|
cc90d9a561 | ||
|
|
b024e3ef0b | ||
|
|
77a84882cc | ||
|
|
7e4e51f82c | ||
|
|
b57998fcb4 | ||
|
|
8158e22991 | ||
|
|
d8eff726c9 | ||
|
|
5c442ced9f | ||
|
|
75c851bd52 | ||
|
|
4aef8c608d | ||
|
|
6deb24dc41 | ||
|
|
03084293b2 | ||
|
|
1d354ac868 | ||
|
|
8a61630a37 | ||
|
|
6c63f851cf | ||
|
|
97470bb723 |
269
.eslintrc.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
NodeJS: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['!**/*', 'jest.config.ts'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
extends: ['plugin:@typescript-eslint/recommended'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
accessibility: 'no-public',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/unbound-method': 'error',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'off',
|
||||||
|
'@typescript-eslint/naming-convention': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'memberLike',
|
||||||
|
modifiers: ['private'],
|
||||||
|
format: ['camelCase'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'memberLike',
|
||||||
|
modifiers: ['protected'],
|
||||||
|
format: ['camelCase'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'memberLike',
|
||||||
|
modifiers: ['private'],
|
||||||
|
format: ['UPPER_CASE', 'camelCase'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
'@typescript-eslint/member-ordering': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
default: [
|
||||||
|
// Index signature
|
||||||
|
'signature',
|
||||||
|
'call-signature',
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
'#private-static-field',
|
||||||
|
'private-static-field',
|
||||||
|
'protected-static-field',
|
||||||
|
'public-static-field',
|
||||||
|
|
||||||
|
'#private-instance-field',
|
||||||
|
'private-instance-field',
|
||||||
|
'protected-instance-field',
|
||||||
|
'public-instance-field',
|
||||||
|
|
||||||
|
'private-decorated-field',
|
||||||
|
'protected-decorated-field',
|
||||||
|
'public-decorated-field',
|
||||||
|
|
||||||
|
'protected-abstract-field',
|
||||||
|
'public-abstract-field',
|
||||||
|
|
||||||
|
'#private-field',
|
||||||
|
'private-field',
|
||||||
|
'protected-field',
|
||||||
|
'public-field',
|
||||||
|
|
||||||
|
'static-field',
|
||||||
|
'instance-field',
|
||||||
|
'abstract-field',
|
||||||
|
|
||||||
|
'decorated-field',
|
||||||
|
|
||||||
|
'field',
|
||||||
|
|
||||||
|
// Static initialization
|
||||||
|
'static-initialization',
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
'public-constructor',
|
||||||
|
'protected-constructor',
|
||||||
|
'private-constructor',
|
||||||
|
|
||||||
|
'constructor',
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
'public-static-get',
|
||||||
|
'protected-static-get',
|
||||||
|
'private-static-get',
|
||||||
|
'#private-static-get',
|
||||||
|
|
||||||
|
'public-decorated-get',
|
||||||
|
'protected-decorated-get',
|
||||||
|
'private-decorated-get',
|
||||||
|
|
||||||
|
'public-instance-get',
|
||||||
|
'protected-instance-get',
|
||||||
|
'private-instance-get',
|
||||||
|
'#private-instance-get',
|
||||||
|
|
||||||
|
'public-abstract-get',
|
||||||
|
'protected-abstract-get',
|
||||||
|
|
||||||
|
'public-get',
|
||||||
|
'protected-get',
|
||||||
|
'private-get',
|
||||||
|
'#private-get',
|
||||||
|
|
||||||
|
'static-get',
|
||||||
|
'instance-get',
|
||||||
|
'abstract-get',
|
||||||
|
|
||||||
|
'decorated-get',
|
||||||
|
|
||||||
|
'get',
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
'public-static-set',
|
||||||
|
'protected-static-set',
|
||||||
|
'private-static-set',
|
||||||
|
'#private-static-set',
|
||||||
|
|
||||||
|
'public-decorated-set',
|
||||||
|
'protected-decorated-set',
|
||||||
|
'private-decorated-set',
|
||||||
|
|
||||||
|
'public-instance-set',
|
||||||
|
'protected-instance-set',
|
||||||
|
'private-instance-set',
|
||||||
|
'#private-instance-set',
|
||||||
|
|
||||||
|
'public-abstract-set',
|
||||||
|
'protected-abstract-set',
|
||||||
|
|
||||||
|
'public-set',
|
||||||
|
'protected-set',
|
||||||
|
'private-set',
|
||||||
|
'#private-set',
|
||||||
|
|
||||||
|
'static-set',
|
||||||
|
'instance-set',
|
||||||
|
'abstract-set',
|
||||||
|
|
||||||
|
'decorated-set',
|
||||||
|
|
||||||
|
'set',
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
'public-static-method',
|
||||||
|
'protected-static-method',
|
||||||
|
'private-static-method',
|
||||||
|
'#private-static-method',
|
||||||
|
|
||||||
|
'public-decorated-method',
|
||||||
|
'protected-decorated-method',
|
||||||
|
'private-decorated-method',
|
||||||
|
|
||||||
|
'public-instance-method',
|
||||||
|
'protected-instance-method',
|
||||||
|
'private-instance-method',
|
||||||
|
'#private-instance-method',
|
||||||
|
|
||||||
|
'public-abstract-method',
|
||||||
|
'protected-abstract-method',
|
||||||
|
|
||||||
|
'public-method',
|
||||||
|
'protected-method',
|
||||||
|
'private-method',
|
||||||
|
'#private-method',
|
||||||
|
|
||||||
|
'static-method',
|
||||||
|
'instance-method',
|
||||||
|
'abstract-method',
|
||||||
|
|
||||||
|
'decorated-method',
|
||||||
|
|
||||||
|
'method',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@angular-eslint/recommended',
|
||||||
|
'plugin:@angular-eslint/template/process-inline-templates',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:rxjs/recommended',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'rxjs/no-ignored-subscription': 'error',
|
||||||
|
'@angular-eslint/prefer-standalone': 'off',
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'iqser',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'iqser',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/prefer-on-push-component-change-detection': 'error',
|
||||||
|
'@angular-eslint/use-lifecycle-interface': 'error',
|
||||||
|
'@angular-eslint/no-input-prefix': 'error',
|
||||||
|
'@angular-eslint/no-input-rename': 'error',
|
||||||
|
'@angular-eslint/no-output-on-prefix': 'error',
|
||||||
|
'@angular-eslint/no-output-rename': 'error',
|
||||||
|
'@angular-eslint/prefer-output-readonly': 'error',
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
'no-param-reassign': 'error',
|
||||||
|
'no-dupe-class-members': 'off',
|
||||||
|
'no-redeclare': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'consistent-return': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
extends: ['plugin:@angular-eslint/template/recommended'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
excludedFiles: ['*inline-template-*.component.html'],
|
||||||
|
extends: ['plugin:prettier/recommended'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
parser: 'angular',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.spec.ts'],
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/prefer-on-push-component-change-detection': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
122
.eslintrc.json
@ -1,122 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true
|
|
||||||
},
|
|
||||||
"ignorePatterns": ["!**/*"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts"],
|
|
||||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"project": "./tsconfig.json"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"accessibility": "no-public"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/unbound-method": "error",
|
|
||||||
"@typescript-eslint/no-floating-promises": "off",
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "memberLike",
|
|
||||||
"modifiers": ["private"],
|
|
||||||
"format": ["camelCase"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "memberLike",
|
|
||||||
"modifiers": ["protected"],
|
|
||||||
"format": ["camelCase"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "memberLike",
|
|
||||||
"modifiers": ["private"],
|
|
||||||
"format": ["UPPER_CASE", "camelCase"],
|
|
||||||
"leadingUnderscore": "allow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
|
||||||
"@typescript-eslint/lines-between-class-members": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@angular-eslint/recommended",
|
|
||||||
"plugin:@angular-eslint/recommended--extra",
|
|
||||||
"plugin:@angular-eslint/template/process-inline-templates",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:rxjs/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"rxjs/no-ignored-subscription": "error",
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "attribute",
|
|
||||||
"prefix": "iqser",
|
|
||||||
"style": "camelCase"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "element",
|
|
||||||
"prefix": "iqser",
|
|
||||||
"style": "kebab-case"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/prefer-on-push-component-change-detection": "error",
|
|
||||||
"@angular-eslint/use-lifecycle-interface": "error",
|
|
||||||
"@angular-eslint/no-input-prefix": "error",
|
|
||||||
"@angular-eslint/no-input-rename": "error",
|
|
||||||
"@angular-eslint/no-output-on-prefix": "error",
|
|
||||||
"@angular-eslint/no-output-rename": "error",
|
|
||||||
"@angular-eslint/prefer-output-readonly": "error",
|
|
||||||
"no-underscore-dangle": "off",
|
|
||||||
"no-param-reassign": "error",
|
|
||||||
"no-dupe-class-members": "off",
|
|
||||||
"no-redeclare": "off",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"consistent-return": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.html"],
|
|
||||||
"extends": ["plugin:@angular-eslint/template/recommended"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// https://github.com/angular-eslint/angular-eslint#notes-for-eslint-plugin-prettier-users
|
|
||||||
"files": ["*.html"],
|
|
||||||
"excludedFiles": ["*inline-template-*.component.html"],
|
|
||||||
"extends": ["plugin:prettier/recommended"],
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"parser": "angular"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["**/*.spec.ts"],
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"jest": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/prefer-on-push-component-change-detection": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
19
.gitlab-ci.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
sonarqube:
|
||||||
|
stage: test
|
||||||
|
image:
|
||||||
|
name: sonarsource/sonar-scanner-cli:11.1
|
||||||
|
entrypoint:
|
||||||
|
- ''
|
||||||
|
variables:
|
||||||
|
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
|
||||||
|
GIT_DEPTH: '0'
|
||||||
|
cache:
|
||||||
|
key: "${CI_JOB_NAME}"
|
||||||
|
paths:
|
||||||
|
- ".sonar/cache"
|
||||||
|
script:
|
||||||
|
- sonar-scanner
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
||||||
|
- if: "$CI_COMMIT_BRANCH =~ /^release/"
|
||||||
2
sonar-project.properties
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
sonar.projectKey=common-ui
|
||||||
|
sonar.qualitygate.wait=false
|
||||||
14
src/assets/icons/alert-circle.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="12px" viewBox="0 0 12 12" width="12px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="User-Management" stroke="none" stroke-width="1">
|
||||||
|
<g id="01.2-Bulk-Actions" transform="translate(-876.000000, -468.000000)">
|
||||||
|
<rect fill="#FFFFFF" height="900" width="1440" x="0" y="0"></rect>
|
||||||
|
<polygon fill="#FFFFFF" id="Rectangle" points="201 449 1087 449 1087 499 201 499"></polygon>
|
||||||
|
<g fill="#DD4D50" id="Group-19" transform="translate(876.000000, 171.000000)">
|
||||||
|
<path
|
||||||
|
d="M6.00002308,297 C9.30875217,297 12.0000462,299.690683 12.0000462,302.999359 C12.0000462,306.309082 9.30898674,309 6.00002308,309 C2.69105942,309 2.27373675e-13,306.309082 2.27373675e-13,302.999359 C2.27373675e-13,299.690683 2.69129399,297 6.00002308,297 Z M6.00002308,298.846154 C3.71080282,298.846154 1.84615385,300.71038 1.84615385,302.999359 C1.84615385,305.289458 3.71064029,307.153846 6.00002308,307.153846 C8.28940586,307.153846 10.1538923,305.289458 10.1538923,302.999359 C10.1538923,300.71038 8.28924333,298.846154 6.00002308,298.846154 Z M6.89921108,303.700197 L6.89921108,305.678219 L5.10100111,305.678219 L5.10100111,303.700197 L6.89921108,303.700197 Z M6.89921108,300.103957 L6.89921108,302.801137 L5.10100111,302.801137 L5.10100111,300.103957 L6.89921108,300.103957 Z"
|
||||||
|
id="Combined-Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
15
src/assets/icons/calendar.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="calendar" stroke="none" stroke-width="1">
|
||||||
|
<path
|
||||||
|
d="M35,70 L35,80 L25,80 L25,70 L35,70 Z M55,70 L55,80 L45,80 L45,70 L55,70 Z M35,55 L35,65 L25,65 L25,55 L35,55 Z M55,55 L55,65 L45,65 L45,55 L55,55 Z M75,55 L75,65 L65,65 L65,55 L75,55 Z M55,40 L55,50 L45,50 L45,40 L55,40 Z M75,40 L75,50 L65,50 L65,40 L75,40 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||||
|
<path
|
||||||
|
d="M90,0 L10,0 C4.5,0 0,4.5 0,10 L0,90 C0,95.5 4.5,100 10,100 L90,100 C95.5,100 100,95.5 100,90 L100,10 C100,4.5 95.5,0 90,0 Z M10,90 L10,10 L90,10 L90,90 L10,90 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||||
|
<path
|
||||||
|
d="M90,20 L10,20 C4.5,20 0,24.5 0,30 L0,90 C0,95.5 4.5,100 10,100 L90,100 C95.5,100 100,95.5 100,90 L100,30 C100,24.5 95.5,20 90,20 Z M10,90 L10,30 L90,30 L90,90 L10,90 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/icons/chevron-down.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 7.4L0 1.4L1.4 0L6 4.6L10.6 0L12 1.4L6 7.4Z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 184 B |
3
src/assets/icons/chevron-up.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 2.8L1.4 7.4L0 6L6 0L12 6L10.6 7.4L6 2.8Z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 182 B |
15
src/assets/icons/color-picker.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg id="Capa_1" style="enable-background:new 0 0 464.736 464.736;" viewBox="0 0 464.736 464.736"
|
||||||
|
x="0px"
|
||||||
|
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" y="0px">
|
||||||
|
<g>
|
||||||
|
<path d="M446.598,18.143c-24.183-24.184-63.393-24.191-87.592-0.008l-16.717,16.717c-8.98-8.979-23.525-8.979-32.504,0
|
||||||
|
c-8.981,8.972-8.981,23.533,0,32.505l5.416,5.419L134.613,253.377h-0.016l-62.685,62.691c-4.982,4.982-7.919,11.646-8.235,18.684
|
||||||
|
l-0.15,3.344c0,0.016,0,0.03,0,0.046l-2.529,56.704c-0.104,2.633,0.883,5.185,2.739,7.048c1.751,1.759,4.145,2.738,6.63,2.738
|
||||||
|
c0.135,0,0.269,0,0.42-0.008l30.064-1.331h0.016l18.318-0.815l8.318-0.366c9.203-0.412,17.944-4.259,24.469-10.776l240.898-240.891
|
||||||
|
l4.506,4.505c4.49,4.488,10.372,6.733,16.252,6.733c5.881,0,11.764-2.245,16.253-6.733c8.98-8.973,8.98-23.534,0-32.505
|
||||||
|
l16.716-16.718C470.782,81.544,470.782,42.334,446.598,18.143z M272.639,227.33l-84.6,15.96l137.998-138.004l34.332,34.316
|
||||||
|
L272.639,227.33z" fill="currentColor" />
|
||||||
|
<path d="M64.5,423.872c-35.617,0-64.5,9.145-64.5,20.435c0,11.284,28.883,20.428,64.5,20.428s64.486-9.143,64.486-20.428
|
||||||
|
C128.986,433.016,100.117,423.872,64.5,423.872z" fill="currentColor" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -3,11 +3,11 @@
|
|||||||
<svg id="Layer_1" style="enable-background:new 0 0 14 14;" version="1.1" viewBox="0 0 14 14" x="0px"
|
<svg id="Layer_1" style="enable-background:new 0 0 14 14;" version="1.1" viewBox="0 0 14 14" x="0px"
|
||||||
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" y="0px">
|
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" y="0px">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0 {
|
#csv-svg .st0 {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<g id="Styleguide">
|
<g id="csv-svg">
|
||||||
<g id="Export-just-icons" transform="translate(-979.000000, -252.000000)">
|
<g id="Export-just-icons" transform="translate(-979.000000, -252.000000)">
|
||||||
<g id="Group" transform="translate(979.000000, 252.000000)">
|
<g id="Group" transform="translate(979.000000, 252.000000)">
|
||||||
<g id="list-view">
|
<g id="list-view">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 806 B After Width: | Height: | Size: 812 B |
9
src/assets/icons/exit-fullscreen.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="minimize" stroke="none" stroke-width="1">
|
||||||
|
<path
|
||||||
|
d="M40,60 L40,90 L30,90 L30,77 L7,100 L0,93 L23,70 L10,70 L10,60 L40,60 Z M90,60 L90,70 L77,70 L100,93 L93,100 L70,77 L70,90 L60,90 L60,60 L90,60 Z M93,0 L100,7 L77,30 L90,30 L90,40 L60,40 L60,10 L70,10 L70,23 L93,0 Z M7,0 L30,23 L30,10 L40,10 L40,40 L10,40 L10,30 L23,30 L0,7 L7,0 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 642 B |
3
src/assets/icons/filter-list.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7 12V10H11V12H7ZM3 7V5H15V7H3ZM0 2V0H18V2H0Z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 186 B |
9
src/assets/icons/fullscreen.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="full-screen" stroke="none" stroke-width="1">
|
||||||
|
<path
|
||||||
|
d="M36.5,56.5 L43.5,63.5 L17,90 L30,90 L30,100 L0,100 L0,70 L10,70 L10,83 L36.5,56.5 Z M63.5,56.5 L90,83 L90,70 L100,70 L100,100 L70,100 L70,90 L83,90 L56.5,63.5 L63.5,56.5 Z M100,0 L100,30 L90,30 L90,17 L63.5,43.5 L56.5,36.5 L83,10 L70,10 L70,0 L100,0 Z M30,0 L30,10 L17,10 L43.5,36.5 L36.5,43.5 L10,17 L10,30 L0,30 L0,0 L30,0 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 691 B |
14
src/assets/icons/help-outline-active.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="20px" version="1.1" viewBox="0 0 20 20" width="20px" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Help-Mode" stroke="none" stroke-width="1">
|
||||||
|
<g fill="currentColor" fill-rule="nonzero" id="01.-Help-button" transform="translate(-1408.000000, -645.000000)">
|
||||||
|
<g id="help-button" transform="translate(1294.000000, 635.000000)">
|
||||||
|
<g id="help" transform="translate(114.000000, 10.000000)">
|
||||||
|
<path
|
||||||
|
d="M10,0 C15.5,1.01033361e-15 20,4.5 20,10 C20,15.5 15.5,20 10,20 C4.5,20 3.55271368e-15,15.5 3.55271368e-15,10 C7.10542736e-15,4.5 4.5,-1.01033361e-15 10,0 Z M10,2 C5.6,2 2,5.6 2,10 C2,14.4 5.6,18 10,18 C14.4,18 18,14.4 18,10 C18,5.6 14.4,2 10,2 Z M10.86,12.9 L10.86,14.9 L8.86,14.9 L8.86,12.9 L10.86,12.9 Z M9.86,4.9 C11.56,4.9 12.86,6.2 12.86,7.9 C12.86,8.8 12.36,9.7 11.66,10.3 C11.3830769,10.4846154 10.9357396,10.839645 10.8685571,11.4437415 L10.86,11.6 L10.86,11.9 L8.86,11.9 L8.86,11.6 C8.86,10.5 9.46,9.4 10.46,8.7 C10.76,8.5 10.86,8.2 10.86,7.9 C10.86,7.3 10.46,6.9 9.86,6.9 C9.30285714,6.9 8.91816327,7.24489796 8.86604956,7.77456268 L8.86,7.9 L6.86,7.9 C6.86,6.2 8.16,4.9 9.86,4.9 Z"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -3,11 +3,11 @@
|
|||||||
<svg id="Layer_1" style="enable-background:new 0 0 14 14;" version="1.1" viewBox="0 0 14 14" x="0px"
|
<svg id="Layer_1" style="enable-background:new 0 0 14 14;" version="1.1" viewBox="0 0 14 14" x="0px"
|
||||||
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" y="0px">
|
xml:space="preserve" xmlns="http://www.w3.org/2000/svg" y="0px">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0 {
|
#lanes-svg .st0 {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<g id="Styleguide">
|
<g id="lanes-svg">
|
||||||
<g id="Export-just-icons" transform="translate(-979.000000, -294.000000)">
|
<g id="Export-just-icons" transform="translate(-979.000000, -294.000000)">
|
||||||
<g id="Group" transform="translate(979.000000, 294.000000)">
|
<g id="Group" transform="translate(979.000000, 294.000000)">
|
||||||
<g id="lanes-view">
|
<g id="lanes-view">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 726 B After Width: | Height: | Size: 736 B |
22
src/assets/icons/nav-first.svg
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Annotations-Jump-to-" stroke="none" stroke-width="1">
|
||||||
|
<g id="03.Jump-to-first-annotation" transform="translate(-1109.000000, -181.000000)">
|
||||||
|
<rect height="750" width="1440" x="0" y="0"></rect>
|
||||||
|
<rect height="32" id="Rectangle" width="60" x="1087" y="173"></rect>
|
||||||
|
<g fill="currentColor" fill-rule="nonzero" id="right_white"
|
||||||
|
transform="translate(1109.000000, 181.000000)">
|
||||||
|
<polygon id="Path"
|
||||||
|
points="12 6.24 10.88 5.12 8 8 7.2 7.2 5.12 5.12 4 6.24 8 10.24"
|
||||||
|
transform="translate(8.000000, 7.680000) scale(1, -1) translate(-8.000000, -7.680000) "></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="12 10.4 10.88 9.28 8 12.16 7.2 11.36 5.12 9.28 4 10.4 8 14.4"
|
||||||
|
transform="translate(8.000000, 11.840000) scale(1, -1) translate(-8.000000, -11.840000) "></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="14.4 1.92 1.6 1.92 1.22124533e-14 1.92 1.22124533e-14 3.52 16 3.52 16 1.92"
|
||||||
|
transform="translate(8.000000, 2.720000) scale(1, -1) translate(-8.000000, -2.720000) "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
24
src/assets/icons/nav-last.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Annotations-Jump-to-" stroke="none" stroke-width="1">
|
||||||
|
<g id="03.Jump-to-first-annotation" transform="translate(-1109.000000, -726.000000)">
|
||||||
|
<rect height="750" width="1440" x="0" y="0"></rect>
|
||||||
|
<rect height="32" id="Rectangle" width="60" x="1087" y="718"></rect>
|
||||||
|
<g fill="currentColor"
|
||||||
|
fill-rule="nonzero"
|
||||||
|
id="right_white"
|
||||||
|
transform="translate(1117.000000, 734.000000) scale(1, -1) translate(-1117.000000, -734.000000) translate(1109.000000, 726.000000)">
|
||||||
|
<polygon id="Path"
|
||||||
|
points="12 6.24 10.88 5.12 8 8 7.2 7.2 5.12 5.12 4 6.24 8 10.24"
|
||||||
|
transform="translate(8.000000, 7.680000) scale(1, -1) translate(-8.000000, -7.680000) "></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="12 10.4 10.88 9.28 8 12.16 7.2 11.36 5.12 9.28 4 10.4 8 14.4"
|
||||||
|
transform="translate(8.000000, 11.840000) scale(1, -1) translate(-8.000000, -11.840000) "></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="14.4 1.92 1.6 1.92 1.22124533e-14 1.92 1.22124533e-14 3.52 16 3.52 16 1.92"
|
||||||
|
transform="translate(8.000000, 2.720000) scale(1, -1) translate(-8.000000, -2.720000) "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
24
src/assets/icons/nav-next.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Annotations-Jump-to-" stroke="none" stroke-width="1">
|
||||||
|
<g id="01.Jump-to-first-last-annotation" transform="translate(-1222.000000, -486.000000)">
|
||||||
|
<rect height="750" width="1440" x="0" y="0"></rect>
|
||||||
|
<rect height="544" id="Rectangle" width="282" x="1148" y="206"></rect>
|
||||||
|
<g id="Page-05" transform="translate(1147.000000, 173.000000)"></g>
|
||||||
|
<g id="Group" transform="translate(1212.000000, 476.000000)">
|
||||||
|
<rect height="34" id="Rectangle" rx="17" width="151" x="0" y="0"></rect>
|
||||||
|
<g fill="currentColor" fill-rule="nonzero" id="collapse"
|
||||||
|
transform="translate(10.000000, 10.000000)">
|
||||||
|
<g id="Group" transform="translate(1.400000, 3.360000)">
|
||||||
|
<polygon id="Path"
|
||||||
|
points="9.1 0.98 8.12 0 5.6 2.52 4.9 1.82 3.08 0 2.1 0.98 5.6 4.48"></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="9.8 5.88 1.4 5.88 7.34412531e-15 5.88 7.34412531e-15 7.28 11.2 7.28 11.2 5.88"
|
||||||
|
transform="translate(5.600000, 6.580000) scale(1, -1) translate(-5.600000, -6.580000) "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
25
src/assets/icons/nav-prev.svg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Annotations-Jump-to-" stroke="none" stroke-width="1">
|
||||||
|
<g id="01.Jump-to-first-last-annotation" transform="translate(-1222.000000, -444.000000)">
|
||||||
|
<rect height="750" width="1440" x="0" y="0"></rect>
|
||||||
|
<rect height="544" id="Rectangle" width="282" x="1148" y="206"></rect>
|
||||||
|
<g id="Page-05" transform="translate(1147.000000, 173.000000)"></g>
|
||||||
|
<g id="Group" transform="translate(1212.000000, 434.000000)">
|
||||||
|
<rect height="34" id="Rectangle" rx="17" width="151" x="0" y="0"></rect>
|
||||||
|
<g fill="currentColor" fill-rule="nonzero" id="collapse"
|
||||||
|
transform="translate(10.000000, 10.000000)">
|
||||||
|
<g id="Group" transform="translate(1.400000, 3.360000)">
|
||||||
|
<polygon id="Path"
|
||||||
|
points="9.1 3.78 8.12 2.8 5.6 5.32 4.9 4.62 3.08 2.8 2.1 3.78 5.6 7.28"
|
||||||
|
transform="translate(5.600000, 5.040000) scale(1, -1) translate(-5.600000, -5.040000) "></polygon>
|
||||||
|
<polygon id="Path"
|
||||||
|
points="9.8 0 1.4 0 7.34412531e-15 0 7.34412531e-15 1.4 11.2 1.4 11.2 0"
|
||||||
|
transform="translate(5.600000, 0.700000) scale(1, -1) translate(-5.600000, -0.700000) "></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
26
src/assets/icons/resize.svg
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Styleguide" stroke="none" stroke-width="1">
|
||||||
|
<g fill="currentColor" id="Styleguide-Actions" transform="translate(-979.000000, -630.000000)">
|
||||||
|
<g id="reference" transform="translate(969.000000, 620.000000)">
|
||||||
|
<g id="status" transform="translate(10.000000, 10.000000)">
|
||||||
|
<path
|
||||||
|
d="M1.4,9.8 L1.4,12.6 L4.2,12.6 L4.2,14 L0,14 L0,9.8 L1.4,9.8 Z M14,9.8 L14,14 L9.8,14 L9.8,12.6 L12.6,12.6 L12.6,9.8 L14,9.8 Z M4.2,0 L4.2,1.4 L1.4,1.4 L1.4,4.2 L0,4.2 L0,0 L4.2,0 Z M14,0 L14,4.2 L12.6,4.2 L12.6,1.4 L9.8,1.4 L9.8,0 L14,0 Z"
|
||||||
|
fill-rule="nonzero" id="OCR"></path>
|
||||||
|
<path d="M4.2,0 L0,0 L0,4.2 L4.2,4.2 L4.2,0 Z M2.8,1.4 L2.8,2.8 L1.4,2.8 L1.4,1.4 L2.8,1.4 Z" fill-rule="nonzero"
|
||||||
|
id="Path"></path>
|
||||||
|
<path d="M4.2,9.8 L0,9.8 L0,14 L4.2,14 L4.2,9.8 Z M2.8,11.2 L2.8,12.6 L1.4,12.6 L1.4,11.2 L2.8,11.2 Z" fill-rule="nonzero"
|
||||||
|
id="Path"></path>
|
||||||
|
<path d="M14,0 L9.8,0 L9.8,4.2 L14,4.2 L14,0 Z M12.6,1.4 L12.6,2.8 L11.2,2.8 L11.2,1.4 L12.6,1.4 Z" fill-rule="nonzero"
|
||||||
|
id="Path"></path>
|
||||||
|
<path d="M14,9.8 L9.8,9.8 L9.8,14 L14,14 L14,9.8 Z M12.6,11.2 L12.6,12.6 L11.2,12.6 L11.2,11.2 L12.6,11.2 Z" fill-rule="nonzero"
|
||||||
|
id="Path"></path>
|
||||||
|
<rect height="1.4" id="Rectangle" width="5.6" x="4.2" y="1.4"></rect>
|
||||||
|
<rect height="1.4" id="Rectangle" width="5.6" x="4.2" y="11.2"></rect>
|
||||||
|
<rect height="5.6" id="Rectangle" width="1.4" x="11.2" y="4.2"></rect>
|
||||||
|
<rect height="5.6" id="Rectangle" width="1.4" x="1.4" y="4.2"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
9
src/assets/icons/thumb-down.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="false_positive" stroke="none" stroke-width="1">
|
||||||
|
<path
|
||||||
|
d="M70.0540046,8 L70.0540046,13 L18.1476125,13 C13.6557132,13 9.16381388,16.5 8.16561403,21 L0.180015248,61 C-0.319084676,64 0.180015248,67 2.17641494,69.5 C4.17281464,71.5 7.16741418,73 10.1620137,73 L23.1386117,73 C18.6467124,78.5 18.1476125,86 22.1404119,91.5 C24.6359115,95.5 29.1278108,98 34.1188101,98 C38.1116095,98 42.1044088,96.5 44.5999085,93.5 L65.0630053,73 L70.0540046,73 L70.0540046,78 L100,78 L100,8 L70.0540046,8 Z M61.0702059,63 L37.6125095,86.5 C36.6143097,87.5 35.6161098,88 34.1188101,88 C32.6215103,88 31.1242105,87 30.1260107,86 C28.6287109,84 29.1278108,81 31.1242105,79 L47.0954081,63 L10.1620137,63 L18.1476125,23 L70.0540046,23 L70.0540046,63 L61.0702059,63 Z M90.0180015,68 L80.036003,68 L80.036003,18 L90.0180015,18 L90.0180015,68 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
9
src/assets/icons/thumb-up.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="false_negative" stroke="none" stroke-width="1">
|
||||||
|
<path
|
||||||
|
d="M29.9459954,92 L29.9459954,87 L81.8523875,87 C86.3442868,87 90.8361861,83.5 91.834386,79 L99.8199848,39 C100.319085,36 99.8199848,33 97.8235851,30.5 C95.8271854,28.5 92.8325858,27 89.8379863,27 L76.8613883,27 C81.3532876,21.5 81.8523875,14 77.8595881,8.5 C75.3640885,4.5 70.8721892,2 65.8811899,2 C61.8883905,2 57.8955912,3.5 55.4000915,6.5 L34.9369947,27 L29.9459954,27 L29.9459954,22 L0,22 L0,92 L29.9459954,92 Z M38.9297941,37 L62.3874905,13.5 C63.3856903,12.5 64.3838902,12 65.8811899,12 C67.3784897,12 68.8757895,13 69.8739893,14 C71.3712891,16 70.8721892,19 68.8757895,21 L52.9045919,37 L89.8379863,37 L81.8523875,77 L29.9459954,77 L29.9459954,37 L38.9297941,37 Z M9.98199848,32 L19.963997,32 L19.963997,82 L9.98199848,82 L9.98199848,32 Z"
|
||||||
|
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
5
src/assets/icons/visibility-off.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0zm0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 704 B |
5
src/assets/icons/visibility.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path
|
||||||
|
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
@ -43,12 +43,12 @@ body {
|
|||||||
--iqser-pink-1: #f125de;
|
--iqser-pink-1: #f125de;
|
||||||
--iqser-helpmode-primary: green;
|
--iqser-helpmode-primary: green;
|
||||||
--iqser-font-size: 13px;
|
--iqser-font-size: 13px;
|
||||||
|
--iqser-inputs-font-size: 13px;
|
||||||
--iqser-button-radius: 17px;
|
--iqser-button-radius: 17px;
|
||||||
--iqser-button-font-size: 13px;
|
--iqser-button-font-size: 13px;
|
||||||
--iqser-button-height: 34px;
|
--iqser-button-height: 34px;
|
||||||
--iqser-font-family: 'some placeholder value that should be overridden when configuring a theme';
|
|
||||||
--iqser-app-name-font-family: 'some placeholder value that should be overridden when configuring a theme';
|
|
||||||
--iqser-app-name-font-size: 18px;
|
--iqser-app-name-font-size: 18px;
|
||||||
|
--iqser-logo-size: 28px;
|
||||||
--iqser-app-name-color: black;
|
--iqser-app-name-color: black;
|
||||||
--iqser-top-bar-height: 61px;
|
--iqser-top-bar-height: 61px;
|
||||||
--iqser-menu-margin-top: 10px;
|
--iqser-menu-margin-top: 10px;
|
||||||
@ -57,9 +57,17 @@ body {
|
|||||||
--iqser-menu-padding-bottom: 24px;
|
--iqser-menu-padding-bottom: 24px;
|
||||||
--iqser-menu-min-height: 24px;
|
--iqser-menu-min-height: 24px;
|
||||||
--iqser-menu-item-margin: 0 8px 2px 8px;
|
--iqser-menu-item-margin: 0 8px 2px 8px;
|
||||||
|
--iqser-inputs-height: 36px;
|
||||||
|
--iqser-textarea-padding-y: 7px;
|
||||||
|
--iqser-font-family: Inter, sans-serif;
|
||||||
|
--iqser-app-name-font-family: Inter, sans-serif;
|
||||||
|
--iqser-circle-button-radius: 50%;
|
||||||
|
--iqser-side-nav-item-radius: 20px;
|
||||||
|
--iqser-dot-overlay-background: rgba(var(--iqser-primary-rgb), 0.1);
|
||||||
|
--iqser-chevron-button-bg: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
$required-variables: 'iqser-font-family' 'iqser-primary';
|
$required-variables: 'iqser-primary';
|
||||||
|
|
||||||
@mixin checkRequiredVariables($args, $theme) {
|
@mixin checkRequiredVariables($args, $theme) {
|
||||||
@each $var in $required-variables {
|
@each $var in $required-variables {
|
||||||
|
|||||||
@ -15,20 +15,6 @@
|
|||||||
margin-right: 50px;
|
margin-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
|
||||||
@include common-mixins.clear-a;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-name {
|
|
||||||
font-family: var(--iqser-app-name-font-family);
|
|
||||||
font-size: var(--iqser-app-name-font-size);
|
|
||||||
color: var(--iqser-app-name-color);
|
|
||||||
font-weight: 800;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -46,6 +32,7 @@
|
|||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
> *:not(:last-child) {
|
> *:not(:last-child) {
|
||||||
margin-right: 14px;
|
margin-right: 14px;
|
||||||
@ -53,18 +40,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
@include common-mixins.clear-a;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
font-family: var(--iqser-app-name-font-family);
|
||||||
|
font-size: var(--iqser-app-name-font-size);
|
||||||
|
color: var(--iqser-app-name-color);
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.dev-mode {
|
.dev-mode {
|
||||||
background-color: var(--iqser-primary);
|
background-color: var(--iqser-primary);
|
||||||
color: var(--iqser-white);
|
color: var(--iqser-white);
|
||||||
font-size: 22px;
|
|
||||||
line-height: 16px;
|
|
||||||
text-align: center;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
height: var(--iqser-top-bar-height);
|
height: var(--iqser-top-bar-height);
|
||||||
word-break: break-all;
|
writing-mode: vertical-rl;
|
||||||
|
text-orientation: upright;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -13,13 +13,13 @@
|
|||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-breadcrumb {
|
.dropdown-breadcrumb .mdc-button {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--iqser-primary);
|
--mdc-text-button-label-text-color: var(--iqser-primary);
|
||||||
|
}
|
||||||
|
|
||||||
&[aria-expanded='true'] .mat-button-wrapper > span {
|
.dropdown-breadcrumb[aria-expanded='true'] .mdc-button {
|
||||||
color: var(--iqser-text);
|
--mdc-text-button-label-text-color: var(--iqser-text);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,120 +1,20 @@
|
|||||||
.mat-button,
|
@mixin iconSize14 {
|
||||||
.mat-flat-button {
|
.mat-icon {
|
||||||
border-radius: var(--iqser-button-radius) !important;
|
width: 14px;
|
||||||
font-size: var(--iqser-button-font-size) !important;
|
height: 14px;
|
||||||
display: flex !important;
|
font-size: 14px;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:not(.text) {
|
|
||||||
height: var(--iqser-button-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-button-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: var(--iqser-font-size);
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-button,
|
@mixin labelNoWrap {
|
||||||
.mat-icon-button,
|
.mdc-button__label {
|
||||||
.mat-flat-button {
|
white-space: nowrap;
|
||||||
&.mat-button-disabled .mat-button-wrapper {
|
|
||||||
color: var(--iqser-text);
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-flat-button.mat-primary,
|
@mixin ariaExpanded {
|
||||||
.mat-button.primary {
|
|
||||||
padding: 0 14px;
|
|
||||||
transition: background-color 0.2s, color 0.2s;
|
|
||||||
|
|
||||||
background-color: var(--iqser-primary);
|
|
||||||
|
|
||||||
&.mat-button-disabled {
|
|
||||||
background-color: var(--iqser-primary);
|
|
||||||
|
|
||||||
.mat-button-wrapper {
|
|
||||||
color: var(--iqser-white);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.mat-button-disabled):hover {
|
|
||||||
background-color: var(--iqser-primary-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iqser-icon-button,
|
|
||||||
iqser-chevron-button,
|
|
||||||
.user-button,
|
|
||||||
iqser-circle-button {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&.overlay {
|
|
||||||
background: rgba(var(--iqser-primary-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.overlay):not(.mat-button-disabled):not(.primary):not(.dark-bg):not(.warn):not(.help):not(.text):hover {
|
|
||||||
background-color: var(--iqser-btn-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.text {
|
|
||||||
color: var(--iqser-primary);
|
|
||||||
font-weight: 500 !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(var(--iqser-primary-rgb), 0.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
font-weight: 500 !important;
|
|
||||||
background-color: var(--iqser-primary);
|
|
||||||
color: var(--iqser-white);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--iqser-primary-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark-bg:hover {
|
|
||||||
background-color: var(--iqser-btn-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.help:hover {
|
|
||||||
background-color: var(--iqser-helpmode-primary);
|
|
||||||
color: var(--iqser-grey-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
top: 1px;
|
|
||||||
left: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iqser-chevron-button,
|
|
||||||
iqser-circle-button,
|
|
||||||
iqser-icon-button {
|
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
button {
|
.mat-mdc-button-base {
|
||||||
background: rgba(var(--iqser-primary-rgb), 0.1);
|
background: rgba(var(--iqser-primary-rgb), 0.1);
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
@ -127,3 +27,184 @@ iqser-icon-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin dotOverlay {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
background: var(--iqser-dot-overlay-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin buttonShape {
|
||||||
|
.mat-mdc-button {
|
||||||
|
--mdc-text-button-container-shape: var(--iqser-button-radius);
|
||||||
|
|
||||||
|
height: var(--iqser-button-height);
|
||||||
|
|
||||||
|
.mat-mdc-button-touch-target {
|
||||||
|
height: var(--iqser-button-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iqser-icon-button {
|
||||||
|
@include buttonShape;
|
||||||
|
@include ariaExpanded;
|
||||||
|
@include dotOverlay;
|
||||||
|
@include labelNoWrap;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.mdc-button.mat-mdc-button {
|
||||||
|
@include iconSize14;
|
||||||
|
|
||||||
|
--mdc-text-button-label-text-color: var(--iqser-text);
|
||||||
|
padding: 0 14px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover:not([disabled]) {
|
||||||
|
.mat-mdc-button-persistent-ripple::before {
|
||||||
|
background-color: #000;
|
||||||
|
opacity: 0.04;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
--mdc-text-button-disabled-label-text-color: rgba(var(--iqser-text-rgb), 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-icon {
|
||||||
|
padding: 0 14px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
--mdc-text-button-label-text-color: var(--iqser-white);
|
||||||
|
--mdc-text-button-disabled-label-text-color: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
|
background: var(--iqser-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
background: var(--iqser-btn-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #fcebeb;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--iqser-primary);
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iqser-circle-button {
|
||||||
|
@include ariaExpanded;
|
||||||
|
@include dotOverlay;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.mat-mdc-icon-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--iqser-circle-button-radius);
|
||||||
|
|
||||||
|
.mat-mdc-button-touch-target {
|
||||||
|
width: var(--circle-button-size);
|
||||||
|
height: var(--circle-button-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-button-persistent-ripple {
|
||||||
|
border-radius: var(--iqser-circle-button-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-mdc-button-base {
|
||||||
|
height: var(--circle-button-size);
|
||||||
|
width: var(--circle-button-size);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
vertical-align: bottom;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
--mdc-icon-button-icon-color: var(--iqser-white);
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
--mdc-icon-button-disabled-icon-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
background: var(--iqser-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark-bg {
|
||||||
|
background: var(--iqser-btn-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warn:not([disabled]) {
|
||||||
|
--mdc-icon-button-icon-color: var(--iqser-accent);
|
||||||
|
background-color: var(--iqser-warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
width: var(--circle-button-icon-size);
|
||||||
|
height: var(--circle-button-icon-size);
|
||||||
|
line-height: var(--circle-button-icon-size);
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
line-height: var(--circle-button-icon-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iqser-chevron-button {
|
||||||
|
@include buttonShape;
|
||||||
|
@include ariaExpanded;
|
||||||
|
@include labelNoWrap;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.mat-mdc-button {
|
||||||
|
@include iconSize14;
|
||||||
|
|
||||||
|
background-color: var(--iqser-chevron-button-bg);
|
||||||
|
|
||||||
|
&:not([disabled]) {
|
||||||
|
--mdc-text-button-label-text-color: var(--iqser-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dotOverlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
iqser-user-button {
|
||||||
|
@include buttonShape;
|
||||||
|
@include ariaExpanded;
|
||||||
|
@include dotOverlay;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.mdc-button.mat-mdc-button {
|
||||||
|
--mdc-text-button-label-text-color: var(--iqser-text);
|
||||||
|
|
||||||
|
@include iconSize14;
|
||||||
|
padding: 0 10px 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,39 +1,76 @@
|
|||||||
.mat-checkbox .mat-checkbox-frame {
|
$checkbox-size: 16px;
|
||||||
border: 1px solid var(--iqser-grey-5);
|
$ripple-size: 26px;
|
||||||
|
|
||||||
|
.mat-mdc-checkbox .mdc-checkbox {
|
||||||
|
flex: 0 0 $checkbox-size;
|
||||||
|
width: $checkbox-size;
|
||||||
|
height: $checkbox-size;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-checkbox-indeterminate.mat-accent .mat-checkbox-background,
|
.mat-mdc-checkbox,
|
||||||
.mat-checkbox-checked.mat-accent .mat-checkbox-background {
|
.mdc-checkbox {
|
||||||
margin-top: 1px;
|
--mdc-checkbox-state-layer-size: $checkbox-size;
|
||||||
margin-left: 1px;
|
--mdc-checkbox-unselected-icon-color: var(--iqser-grey-5);
|
||||||
width: 18px;
|
--mdc-checkbox-unselected-hover-icon-color: var(--iqser-grey-5);
|
||||||
height: 18px;
|
--mdc-checkbox-unselected-pressed-icon-color: var(--iqser-grey-5);
|
||||||
}
|
--mdc-checkbox-unselected-focus-icon-color: var(--iqser-grey-5);
|
||||||
|
--mdc-checkbox-disabled-selected-icon-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-disabled-unselected-icon-color: var(--iqser-grey-5);
|
||||||
|
--mdc-checkbox-selected-focus-icon-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-hover-icon-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-icon-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-pressed-icon-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-focus-state-layer-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-hover-state-layer-color: var(--iqser-primary);
|
||||||
|
--mdc-checkbox-selected-pressed-state-layer-color: var(--iqser-primary);
|
||||||
|
|
||||||
.mat-checkbox-layout {
|
.mdc-form-field {
|
||||||
align-items: center !important;
|
align-items: start;
|
||||||
|
|
||||||
.mat-checkbox-inner-container {
|
& > label {
|
||||||
margin-left: 0;
|
padding-left: 8px;
|
||||||
}
|
line-height: 24px;
|
||||||
|
white-space: normal;
|
||||||
.mat-checkbox-label {
|
|
||||||
font-size: var(--iqser-font-size);
|
|
||||||
color: var(--iqser-text);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-checkbox__ripple {
|
||||||
|
--mdc-checkbox-unselected-focus-state-layer-color: var(--iqser-alt-background);
|
||||||
|
|
||||||
|
width: $ripple-size;
|
||||||
|
height: $ripple-size;
|
||||||
|
left: calc(($checkbox-size - $ripple-size) / 2);
|
||||||
|
top: calc(($checkbox-size - $ripple-size) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-checkbox__background {
|
||||||
|
border-width: 1px;
|
||||||
|
width: $checkbox-size;
|
||||||
|
height: $checkbox-size;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-checkbox-touch-target {
|
||||||
|
height: $ripple-size;
|
||||||
|
width: $ripple-size;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-checkbox.error .mat-checkbox-label {
|
.mdc-checkbox label {
|
||||||
color: var(--iqser-primary);
|
font-size: var(--iqser-font-size);
|
||||||
|
color: var(--iqser-text);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-checkbox-disabled {
|
.mat-mdc-checkbox-disabled .mdc-checkbox {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,13 @@ mat-chip-listbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-mdc-standard-chip,
|
||||||
|
.mat-mdc-standard-chip.mat-primary.mat-mdc-chip-selected {
|
||||||
|
--mdc-chip-label-text-color: var(--iqser-text);
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-evolution-chip-set mat-chip-option.mat-mdc-standard-chip {
|
.mdc-evolution-chip-set mat-chip-option.mat-mdc-standard-chip {
|
||||||
background-color: var(--iqser-background);
|
background-color: var(--iqser-background);
|
||||||
color: var(--iqser-text);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0 0 2px 0;
|
margin: 0 0 2px 0;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
@ -35,6 +39,10 @@ mat-chip-listbox {
|
|||||||
&.mat-mdc-chip-selected {
|
&.mat-mdc-chip-selected {
|
||||||
background-color: var(--iqser-btn-bg);
|
background-color: var(--iqser-btn-bg);
|
||||||
|
|
||||||
|
.mdc-evolution-chip__text-label {
|
||||||
|
color: #212121;
|
||||||
|
}
|
||||||
|
|
||||||
.selected-mark {
|
.selected-mark {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -64,3 +72,7 @@ mat-chip-listbox {
|
|||||||
.mdc-evolution-chip-set__chips {
|
.mdc-evolution-chip-set__chips {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-mdc-chip-focus-overlay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|||||||
@ -18,9 +18,18 @@
|
|||||||
&.large {
|
&.large {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
min-width: 32px;
|
||||||
font-size: var(--iqser-font-size);
|
font-size: var(--iqser-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.extra-small {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
&.gray-dark {
|
&.gray-dark {
|
||||||
background-color: var(--iqser-user-avatar-1);
|
background-color: var(--iqser-user-avatar-1);
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
.mat-mdc-dialog-container .mdc-dialog__surface {
|
.mat-mdc-dialog-container .mdc-dialog__surface {
|
||||||
|
--mdc-dialog-container-color: var(--iqser-background);
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
background-color: var(--iqser-background);
|
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
@ -15,6 +15,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.use-backslash-n-as-line-break {
|
||||||
|
white-space: pre-line !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
@ -37,6 +41,20 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24px 32px 40px;
|
padding: 24px 32px 40px;
|
||||||
|
|
||||||
|
&.redaction {
|
||||||
|
.selected-text {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.redaction,
|
||||||
|
&.force-annotation {
|
||||||
|
iqser-details-radio {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-actions {
|
.dialog-actions {
|
||||||
@ -57,3 +75,7 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.large-form-dialog .dialog > form {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|||||||
59
src/assets/styles/common-file-upload.scss
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.iqser-upload-file {
|
||||||
|
.upload-area,
|
||||||
|
.file-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--iqser-alt-background);
|
||||||
|
height: 68px;
|
||||||
|
|
||||||
|
&.drag-over {
|
||||||
|
background-color: var(--iqser-file-drop-drag-over);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area {
|
||||||
|
gap: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 32px;
|
||||||
|
|
||||||
|
mat-icon,
|
||||||
|
div {
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-area {
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
mat-icon:first-child {
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon:last-child {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 490px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,11 +7,11 @@
|
|||||||
.full-page-section {
|
.full-page-section {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
background: var(--iqser-background);
|
background: var(--iqser-background);
|
||||||
z-index: 900;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-page-content {
|
.full-page-content {
|
||||||
z-index: 1000;
|
z-index: 1002;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -9,4 +9,9 @@
|
|||||||
background: rgba(92, 229, 148, 0.5);
|
background: rgba(92, 229, 148, 0.5);
|
||||||
box-shadow: 0 0 0 2px var(--iqser-helpmode-primary) inset;
|
box-shadow: 0 0 0 2px var(--iqser-helpmode-primary) inset;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
|
||||||
|
&.documine-theme {
|
||||||
|
background: rgba(253, 189, 0, 0.5);
|
||||||
|
box-shadow: 0 0 0 2px var(--iqser-yellow-2) inset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,12 @@ iqser-dynamic-input {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
min-height: var(--iqser-inputs-height);
|
||||||
|
}
|
||||||
|
|
||||||
.iqser-input-group {
|
.iqser-input-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -61,8 +67,8 @@ iqser-dynamic-input {
|
|||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: calc((var(--iqser-inputs-height) - 14px) / 2 - 1px);
|
||||||
right: 10px;
|
right: calc((var(--iqser-inputs-height) - 14px) / 2 - 1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-row {
|
.slider-row {
|
||||||
@ -85,32 +91,53 @@ iqser-dynamic-input {
|
|||||||
background: var(--iqser-background);
|
background: var(--iqser-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input:not([type='checkbox']),
|
||||||
textarea {
|
textarea {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-top: 3px;
|
|
||||||
min-height: var(--iqser-inputs-height);
|
min-height: var(--iqser-inputs-height);
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
padding-left: 11px;
|
padding-left: calc((var(--iqser-inputs-height) - 14px) / 2);
|
||||||
padding-right: 11px;
|
padding-right: calc((var(--iqser-inputs-height) - 14px) / 2);
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-form-field {
|
|
||||||
margin-top: 3px;
|
|
||||||
|
|
||||||
input {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-form-field-subscript-wrapper {
|
.mat-mdc-form-field-subscript-wrapper {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-notched-outline__leading,
|
.mdc-text-field--outlined {
|
||||||
.mdc-notched-outline__trailing {
|
--mdc-outlined-text-field-focus-outline-width: 1px;
|
||||||
--mdc-shape-small: 8px; // border-radius
|
--mdc-shape-small: 8px;
|
||||||
border-color: var(--iqser-inputs-outline);
|
--mdc-outlined-text-field-container-shape: 8px;
|
||||||
|
border-bottom-left-radius: var(--mdc-shape-small);
|
||||||
|
border-bottom-right-radius: var(--mdc-shape-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused) {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--iqser-inputs-outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--iqser-inputs-outline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-text-field--outlined:not(.mdc-text-field--disabled).mdc-text-field--focused {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--iqser-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--iqser-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-text-field--focused .mdc-notched-outline__leading,
|
.mdc-text-field--focused .mdc-notched-outline__leading,
|
||||||
@ -123,7 +150,7 @@ iqser-dynamic-input {
|
|||||||
textarea {
|
textarea {
|
||||||
border: 1px solid var(--iqser-inputs-outline);
|
border: 1px solid var(--iqser-inputs-outline);
|
||||||
font-family: var(--iqser-font-family);
|
font-family: var(--iqser-font-family);
|
||||||
font-size: var(--iqser-font-size);
|
font-size: var(--iqser-inputs-font-size);
|
||||||
background-color: var(--iqser-background);
|
background-color: var(--iqser-background);
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -159,7 +186,7 @@ iqser-dynamic-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
line-height: 18px;
|
line-height: calc(var(--iqser-inputs-font-size) * 1.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hex-color-input {
|
.hex-color-input {
|
||||||
@ -169,8 +196,8 @@ iqser-dynamic-input {
|
|||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
padding-top: 7px;
|
padding-top: var(--iqser-textarea-padding-y);
|
||||||
padding-bottom: 7px;
|
padding-bottom: var(--iqser-textarea-padding-y);
|
||||||
@include mixins.scroll-bar;
|
@include mixins.scroll-bar;
|
||||||
|
|
||||||
&.has-scrollbar {
|
&.has-scrollbar {
|
||||||
@ -179,12 +206,12 @@ iqser-dynamic-input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label:not(.mat-slide-toggle-label):not(.mat-radio-label):not(.details-radio-label) {
|
> label:not(.mat-slide-toggle-label):not(.mat-radio-label):not(.details-radio-label) {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 5px;
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
|
|
||||||
&.mat-checkbox-layout {
|
&.mat-checkbox-layout {
|
||||||
@ -195,7 +222,7 @@ iqser-dynamic-input {
|
|||||||
|
|
||||||
&.required label:after {
|
&.required label:after {
|
||||||
content: ' *';
|
content: ' *';
|
||||||
color: var(--iqser-primary);
|
color: var(--iqser-red-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.datepicker-wrapper {
|
&.datepicker-wrapper {
|
||||||
@ -212,7 +239,7 @@ iqser-dynamic-input {
|
|||||||
.mat-datepicker-toggle {
|
.mat-datepicker-toggle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 1px;
|
bottom: -4px;
|
||||||
color: var(--iqser-accent);
|
color: var(--iqser-accent);
|
||||||
|
|
||||||
&.mat-datepicker-toggle-active {
|
&.mat-datepicker-toggle-active {
|
||||||
@ -229,6 +256,18 @@ iqser-dynamic-input {
|
|||||||
width: 14px;
|
width: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.cdk-focused,
|
||||||
|
button:hover {
|
||||||
|
span {
|
||||||
|
&::before {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
top: 10px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mat-mdc-icon-button svg {
|
.mat-mdc-icon-button svg {
|
||||||
width: unset;
|
width: unset;
|
||||||
height: unset;
|
height: unset;
|
||||||
|
|||||||
@ -86,17 +86,13 @@ section.settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen {
|
.fullscreen {
|
||||||
.page-header {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-inner {
|
.content-inner {
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-shadow {
|
.right-container {
|
||||||
top: 50px;
|
transform: translateY(61px);
|
||||||
|
height: calc(100% - 61px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +145,6 @@ section.settings {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
// TODO: Shouldn't use `redaction-` here
|
|
||||||
iqser-initials-avatar .username {
|
iqser-initials-avatar .username {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -161,11 +156,15 @@ section.settings {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--iqser-background);
|
background: var(--iqser-background);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: width ease-in-out 0.2s, min-width ease-in-out 0.2s;
|
&.with-transition {
|
||||||
|
transition:
|
||||||
|
width ease-in-out 0.2s,
|
||||||
|
min-width ease-in-out 0.2s;
|
||||||
|
}
|
||||||
|
@include common-mixins.scroll-bar;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@include common-mixins.scroll-bar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed-wrapper {
|
.collapsed-wrapper {
|
||||||
@ -214,6 +213,11 @@ section.settings {
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-column {
|
||||||
|
@extend .flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-end {
|
.flex-end {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -252,6 +256,10 @@ section.settings {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cursor-default {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.fit-content {
|
.fit-content {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,22 +7,33 @@
|
|||||||
0%,
|
0%,
|
||||||
20% {
|
20% {
|
||||||
color: rgba(var(--iqser-accent-rgb), 0);
|
color: rgba(var(--iqser-accent-rgb), 0);
|
||||||
text-shadow: 0.25em 0 0 rgba(var(--iqser-accent-rgb), 0), 0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
text-shadow:
|
||||||
|
0.25em 0 0 rgba(var(--iqser-accent-rgb), 0),
|
||||||
|
0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
||||||
}
|
}
|
||||||
40% {
|
40% {
|
||||||
color: #283241;
|
color: var(--iqser-accent);
|
||||||
text-shadow: 0.25em 0 0 rgba(var(--iqser-accent-rgb), 0), 0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
text-shadow:
|
||||||
|
0.25em 0 0 rgba(var(--iqser-accent-rgb), 0),
|
||||||
|
0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
||||||
}
|
}
|
||||||
60% {
|
60% {
|
||||||
text-shadow: 0.25em 0 0 #283241, 0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
text-shadow:
|
||||||
|
0.25em 0 0 var(--iqser-accent),
|
||||||
|
0.5em 0 0 rgba(var(--iqser-accent-rgb), 0);
|
||||||
}
|
}
|
||||||
80%,
|
80%,
|
||||||
100% {
|
100% {
|
||||||
text-shadow: 0.25em 0 0 #283241, 0.5em 0 0 #283241;
|
text-shadow:
|
||||||
|
0.25em 0 0 var(--iqser-accent),
|
||||||
|
0.5em 0 0 var(--iqser-accent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinning-icon {
|
.spinning-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
animation-name: spin;
|
animation-name: spin;
|
||||||
animation-duration: 5000ms;
|
animation-duration: 5000ms;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
|
|||||||
@ -1,32 +1,31 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
.mat-menu-panel {
|
.mat-mdc-menu-panel {
|
||||||
border-radius: var(--iqser-menu-border-radius) !important;
|
border-radius: var(--iqser-menu-border-radius) !important;
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
min-width: 180px !important;
|
min-width: 180px !important;
|
||||||
margin-top: var(--iqser-menu-margin-top);
|
margin-top: var(--iqser-menu-margin-top);
|
||||||
background-color: var(--iqser-popup-background);
|
|
||||||
min-height: unset !important;
|
min-height: unset !important;
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
@include common-mixins.drop-shadow;
|
@include common-mixins.drop-shadow;
|
||||||
|
|
||||||
.mat-menu-content:not(:empty) {
|
.mat-mdc-menu-content:not(:empty) {
|
||||||
padding-top: var(--iqser-menu-padding-top);
|
padding-top: var(--iqser-menu-padding-top);
|
||||||
padding-bottom: var(--iqser-menu-padding-bottom);
|
padding-bottom: var(--iqser-menu-padding-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.padding-bottom-0 .mat-menu-content:not(:empty) {
|
&.padding-bottom-0 .mat-mdc-menu-content:not(:empty) {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.padding-bottom-8 .mat-menu-content:not(:empty) {
|
&.padding-bottom-8 .mat-mdc-menu-content:not(:empty) {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-menu-item {
|
.mat-mdc-menu-item {
|
||||||
font-size: var(--iqser-font-size);
|
font-size: var(--iqser-font-size);
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
padding: 0 26px 0 8px;
|
padding: 0 26px 0 8px !important;
|
||||||
margin: var(--iqser-menu-item-margin);
|
margin: var(--iqser-menu-item-margin);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
@ -36,6 +35,7 @@
|
|||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
|
width: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.padding-left {
|
&.padding-left {
|
||||||
padding-left: 56px;
|
padding-left: 56px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
@ -77,13 +77,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
font-weight: 600;
|
> span {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.checkmark {
|
.checkmark {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
|
order: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,18 @@
|
|||||||
.mat-progress-bar {
|
.mat-mdc-progress-bar {
|
||||||
height: 6px;
|
--mdc-linear-progress-track-height: 6px;
|
||||||
|
--mdc-linear-progress-active-indicator-color: var(--iqser-white);
|
||||||
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
.mat-progress-bar-buffer {
|
.mdc-linear-progress__buffer-bar {
|
||||||
background-color: var(--iqser-grey-5);
|
background-color: var(--iqser-grey-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.white {
|
&.primary {
|
||||||
.mat-progress-bar-fill::after {
|
--mdc-linear-progress-active-indicator-color: var(--iqser-primary);
|
||||||
background-color: var(--iqser-white);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.green {
|
&.green {
|
||||||
.mat-progress-bar-fill::after {
|
--mdc-linear-progress-active-indicator-color: var(--iqser-green-2);
|
||||||
background-color: var(--iqser-green-2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/assets/styles/common-progress-spinner.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.mat-mdc-progress-spinner {
|
||||||
|
--mdc-circular-progress-active-indicator-color: var(--iqser-primary);
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
.mat-mdc-select {
|
.mat-mdc-select {
|
||||||
padding: 0 11px;
|
padding: 0 calc(var(--iqser-inputs-height) - 25px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
line-height: var(--iqser-inputs-height);
|
--mat-select-trigger-text-line-height: var(--iqser-inputs-height);
|
||||||
|
--mat-select-trigger-text-size: var(--iqser-inputs-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-select-panel {
|
.mat-mdc-select-panel {
|
||||||
@ -12,6 +13,7 @@
|
|||||||
background-color: var(--iqser-background);
|
background-color: var(--iqser-background);
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
@include common-mixins.drop-shadow;
|
@include common-mixins.drop-shadow;
|
||||||
|
--mat-option-selected-state-label-text-color: var(--iqser-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-select-arrow-wrapper {
|
.mat-mdc-select-arrow-wrapper {
|
||||||
@ -29,11 +31,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.mdc-list-item--selected {
|
&.mdc-list-item--selected {
|
||||||
background-color: rgba(var(--iqser-primary-rgb), 0.2);
|
background-color: rgba(var(--iqser-primary-rgb), 0.2) !important;
|
||||||
color: var(--iqser-primary);
|
color: var(--iqser-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mdc-list-item--disabled {
|
&.mdc-list-item--disabled {
|
||||||
color: rgba(var(--iqser-text-rgb), 0.7);
|
color: rgba(var(--iqser-text-rgb), 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select-value {
|
||||||
|
color: var(--iqser-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-disabled {
|
||||||
|
.mat-mdc-select-value {
|
||||||
|
color: var(--iqser-grey-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-option:not(.mat-mdc-option-multiple) .mat-mdc-option-pseudo-checkbox {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ iqser-side-nav {
|
|||||||
|
|
||||||
.item {
|
.item {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
border-radius: 20px;
|
border-radius: var(--iqser-side-nav-item-radius);
|
||||||
padding: 9px 16px;
|
padding: 9px 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
|||||||
@ -1,48 +1,26 @@
|
|||||||
.mat-slider-horizontal {
|
.mat-mdc-slider .mdc-slider__track .mdc-slider__track--inactive {
|
||||||
width: 140px;
|
--mdc-slider-inactive-track-color: var(--iqser-toggle-bg);
|
||||||
height: 32px !important;
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.mat-slider-wrapper {
|
.mat-mdc-slider.mdc-slider {
|
||||||
left: 0 !important;
|
margin-left: 0;
|
||||||
top: 16px !important;
|
margin-right: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slider-track-wrapper,
|
--mdc-slider-handle-width: 16px;
|
||||||
.mat-slider-track-fill {
|
--mdc-slider-handle-height: 16px;
|
||||||
height: 6px !important;
|
--mdc-slider-handle-elevation: none;
|
||||||
border-radius: 3px;
|
--mdc-slider-disabled-handle-color: var(--iqser-primary);
|
||||||
}
|
--mdc-slider-disabled-active-track-color: var(--iqser-grey-5);
|
||||||
|
--mdc-slider-disabled-inactive-track-color: var(--iqser-grey-7);
|
||||||
|
--mdc-slider-label-container-color: var(--iqser-accent);
|
||||||
|
--mat-mdc-slider-value-indicator-opacity: 1;
|
||||||
|
|
||||||
// For disabled state
|
&.mdc-slider--disabled {
|
||||||
.mat-slider-track-fill {
|
opacity: 1;
|
||||||
background-color: var(--iqser-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slider-track-background {
|
|
||||||
height: 4px !important;
|
|
||||||
margin-top: 1px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: var(--iqser-toggle-bg) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slider-focus-ring {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-slider-thumb {
|
.mdc-slider__value-indicator-text {
|
||||||
width: 16px !important;
|
white-space: nowrap;
|
||||||
height: 16px !important;
|
|
||||||
border-width: 0 !important;
|
|
||||||
background-color: var(--iqser-primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slider:not(.mat-slider-disabled):not(.mat-slider-sliding) .mat-slider-thumb {
|
|
||||||
transform: scale(1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slider-horizontal.mat-slider-disabled {
|
|
||||||
.mat-slider-thumb {
|
|
||||||
transform: translateX(-3px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use 'ngx-toastr/toastr';
|
||||||
|
|
||||||
@use 'common-utilities';
|
@use 'common-utilities';
|
||||||
@use 'common-inputs';
|
@use 'common-inputs';
|
||||||
@use 'common-buttons';
|
@use 'common-buttons';
|
||||||
@ -19,6 +21,7 @@
|
|||||||
@use 'common-loading';
|
@use 'common-loading';
|
||||||
@use 'common-media-queries';
|
@use 'common-media-queries';
|
||||||
@use 'common-progress-bar';
|
@use 'common-progress-bar';
|
||||||
|
@use 'common-progress-spinner';
|
||||||
@use 'common-select';
|
@use 'common-select';
|
||||||
@use 'common-slider';
|
@use 'common-slider';
|
||||||
@use 'common-tabs';
|
@use 'common-tabs';
|
||||||
@ -27,6 +30,7 @@
|
|||||||
@use 'common-toggle-button';
|
@use 'common-toggle-button';
|
||||||
@use 'common-tooltips';
|
@use 'common-tooltips';
|
||||||
@use 'common-file-drop';
|
@use 'common-file-drop';
|
||||||
|
@use 'common-file-upload';
|
||||||
@use 'common-side-nav';
|
@use 'common-side-nav';
|
||||||
@use 'common-color-picker';
|
@use 'common-color-picker';
|
||||||
@use 'common-skeleton';
|
@use 'common-skeleton';
|
||||||
|
|||||||
@ -8,10 +8,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-item {
|
.header-item {
|
||||||
@ -23,6 +20,7 @@
|
|||||||
border-bottom: 1px solid var(--iqser-separator);
|
border-bottom: 1px solid var(--iqser-separator);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -34,17 +32,10 @@
|
|||||||
padding: 0 24px 0 10px;
|
padding: 0 24px 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
@use 'common-mixins' as mixins;
|
@use 'common-mixins' as mixins;
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
|
-moz-osx-font-smoothing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
@ -73,6 +78,12 @@ pre {
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading-md {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
@ -138,6 +149,10 @@ pre {
|
|||||||
@include mixins.line-clamp(4);
|
@include mixins.line-clamp(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clamp-5 {
|
||||||
|
@include mixins.line-clamp(5);
|
||||||
|
}
|
||||||
|
|
||||||
.no-wrap {
|
.no-wrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,11 +84,11 @@ $toast-width: 400px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toast-success {
|
.toast-success {
|
||||||
background-color: var(--iqser-green-2);
|
background-color: #5ce594;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-error {
|
.toast-error {
|
||||||
background-color: var(--iqser-red-1);
|
background-color: #dd4d50;
|
||||||
color: var(--iqser-white);
|
color: var(--iqser-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,41 +1,59 @@
|
|||||||
.mat-slide-toggle {
|
.mat-mdc-slide-toggle {
|
||||||
.mat-slide-toggle-bar {
|
.mdc-switch {
|
||||||
height: 16px !important;
|
--mdc-switch-handle-elevation: none;
|
||||||
width: 30px !important;
|
--mdc-switch-handle-elevation-shadow: none;
|
||||||
border-radius: 16px !important;
|
|
||||||
background-color: var(--iqser-toggle-bg);
|
--mdc-switch-selected-track-color: var(--iqser-primary);
|
||||||
|
--mdc-switch-selected-hover-track-color: var(--iqser-primary);
|
||||||
|
--mdc-switch-selected-pressed-track-color: var(--iqser-primary);
|
||||||
|
--mdc-switch-selected-focus-track-color: var(--iqser-primary);
|
||||||
|
--mdc-switch-disabled-selected-track-color: var(--iqser-primary);
|
||||||
|
|
||||||
|
--mdc-switch-unselected-track-color: var(--iqser-toggle-bg);
|
||||||
|
--mdc-switch-unselected-hover-track-color: var(--iqser-toggle-bg);
|
||||||
|
--mdc-switch-unselected-pressed-track-color: var(--iqser-toggle-bg);
|
||||||
|
--mdc-switch-unselected-focus-track-color: var(--iqser-toggle-bg);
|
||||||
|
--mdc-switch-disabled-unselected-track-color: var(--iqser-toggle-bg);
|
||||||
|
|
||||||
|
--mdc-switch-selected-handle-color: var(--iqser-background);
|
||||||
|
--mdc-switch-selected-pressed-handle-color: var(--iqser-background);
|
||||||
|
--mdc-switch-selected-hover-handle-color: var(--iqser-background);
|
||||||
|
--mdc-switch-selected-focus-handle-color: var(--iqser-background);
|
||||||
|
--mdc-switch-disabled-selected-handle-color: var(--iqser-background);
|
||||||
|
|
||||||
|
--mdc-switch-unselected-handle-color: var(--iqser-alt-background);
|
||||||
|
--mdc-switch-unselected-pressed-handle-color: var(--iqser-alt-background);
|
||||||
|
--mdc-switch-unselected-hover-handle-color: var(--iqser-alt-background);
|
||||||
|
--mdc-switch-unselected-focus-handle-color: var(--iqser-alt-background);
|
||||||
|
--mdc-switch-disabled-unselected-handle-color: var(--iqser-alt-background);
|
||||||
|
|
||||||
|
--mdc-switch-disabled-track-opacity: 0.38;
|
||||||
|
|
||||||
|
--mdc-switch-track-width: 30px;
|
||||||
|
--mdc-switch-track-height: 16px;
|
||||||
|
--mdc-switch-track-shape: 8px;
|
||||||
|
--mat-switch-with-icon-handle-size: 12px;
|
||||||
|
--mdc-switch-handle-shape: 6px;
|
||||||
|
|
||||||
|
--mat-switch-unselected-with-icon-handle-horizontal-margin: 0 2px;
|
||||||
|
--mat-switch-unselected-pressed-handle-horizontal-margin: 0 2px;
|
||||||
|
|
||||||
|
--mat-switch-selected-with-icon-handle-horizontal-margin: 0 6px;
|
||||||
|
--mat-switch-selected-pressed-handle-horizontal-margin: 0 6px;
|
||||||
|
--mat-switch-selected-handle-horizontal-margin: 0 6px;
|
||||||
|
|
||||||
|
--mat-switch-unselected-handle-size: 12px;
|
||||||
|
--mat-switch-selected-handle-size: 12px;
|
||||||
|
--mat-switch-pressed-handle-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-slide-toggle-thumb-container {
|
.mdc-form-field > label {
|
||||||
top: 2px !important;
|
margin-left: 8px;
|
||||||
left: 2px !important;
|
padding-left: 0;
|
||||||
height: 12px !important;
|
|
||||||
width: 12px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-slide-toggle-thumb {
|
.mdc-switch__icons,
|
||||||
height: 12px !important;
|
.mdc-switch__ripple {
|
||||||
width: 12px !important;
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: var(--iqser-alt-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-ripple {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mat-primary.mat-checked {
|
|
||||||
.mat-slide-toggle-bar {
|
|
||||||
background-color: var(--iqser-primary);
|
|
||||||
border: 1px solid var(--iqser-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slide-toggle-thumb {
|
|
||||||
background-color: var(--iqser-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-slide-toggle-thumb-container {
|
|
||||||
transform: translate3d(14px, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
.mdc-tooltip {
|
.mdc-tooltip {
|
||||||
.mdc-tooltip__surface {
|
.mdc-tooltip__surface {
|
||||||
|
--mdc-plain-tooltip-supporting-text-tracking: 0;
|
||||||
|
|
||||||
background-color: var(--iqser-accent);
|
background-color: var(--iqser-accent);
|
||||||
border-radius: 3px !important;
|
border-radius: 3px !important;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use 'sass:string';
|
||||||
|
@use 'sass:list';
|
||||||
/* Margins, paddings */
|
/* Margins, paddings */
|
||||||
|
|
||||||
$start: 0;
|
$start: 0;
|
||||||
@ -7,19 +9,19 @@ $values: '';
|
|||||||
$sides: (top, bottom, left, right);
|
$sides: (top, bottom, left, right);
|
||||||
|
|
||||||
@for $i from $start + 1 through $end {
|
@for $i from $start + 1 through $end {
|
||||||
$values: append($values, $i, comma);
|
$values: list.append($values, $i, comma);
|
||||||
$values: set-nth($values, 1, $start);
|
$values: list.set-nth($values, 1, $start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check if !important can be avoided
|
// TODO: Check if !important can be avoided
|
||||||
|
|
||||||
@each $space in $values {
|
@each $space in $values {
|
||||||
@each $side in $sides {
|
@each $side in $sides {
|
||||||
.m#{str-slice($side, 0, 1)}-#{$space} {
|
.m#{string.slice($side, 0, 1)}-#{$space} {
|
||||||
margin-#{$side}: #{$space}px !important;
|
margin-#{$side}: #{$space}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p#{str-slice($side, 0, 1)}-#{$space} {
|
.p#{string.slice($side, 0, 1)}-#{$space} {
|
||||||
padding-#{$side}: #{$space}px !important;
|
padding-#{$side}: #{$space}px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,15 @@
|
|||||||
export * from './lib/common-ui.module';
|
|
||||||
export * from './lib/buttons';
|
export * from './lib/buttons';
|
||||||
export * from './lib/dialog';
|
export * from './lib/dialog';
|
||||||
export * from './lib/form';
|
export * from './lib/form';
|
||||||
export * from './lib/listing';
|
export * from './lib/listing';
|
||||||
export * from './lib/filtering';
|
|
||||||
export * from './lib/help-mode';
|
export * from './lib/help-mode';
|
||||||
export * from './lib/inputs';
|
|
||||||
export * from './lib/utils';
|
|
||||||
export * from './lib/sorting';
|
|
||||||
export * from './lib/services';
|
export * from './lib/services';
|
||||||
export * from './lib/shared';
|
|
||||||
export * from './lib/loading';
|
export * from './lib/loading';
|
||||||
export * from './lib/error';
|
export * from './lib/error';
|
||||||
export * from './lib/search';
|
export * from './lib/search';
|
||||||
export * from './lib/upload-file';
|
export * from './lib/upload-file';
|
||||||
export * from './lib/empty-state';
|
|
||||||
export * from './lib/caching';
|
export * from './lib/caching';
|
||||||
export * from './lib/users';
|
|
||||||
export * from './lib/translations';
|
export * from './lib/translations';
|
||||||
export * from './lib/pipes';
|
export * from './lib/pipes';
|
||||||
export * from './lib/permissions';
|
export * from './lib/permissions';
|
||||||
export * from './lib/directives';
|
export * from './lib/directives';
|
||||||
export * from './lib/tenants';
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<button [class.overlay]="showDot" [class.primary]="primary" [disabled]="disabled" [id]="buttonId" mat-button>
|
<button [class.overlay]="showDot()" [class.primary]="primary()" [disabled]="disabled()" [id]="buttonId()" mat-button>
|
||||||
<span>{{ label }}</span>
|
<span>{{ label() }}</span>
|
||||||
<mat-icon class="chevron-icon" svgIcon="iqser:arrow-down"></mat-icon>
|
<mat-icon class="chevron-icon" iconPositionEnd svgIcon="iqser:arrow-down"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div *ngIf="showDot" class="dot"></div>
|
@if (showDot()) {
|
||||||
|
<div class="dot"></div>
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
button {
|
button {
|
||||||
padding: 0 10px 0 14px;
|
--mat-text-button-with-icon-horizontal-padding: 10px 0 14px;
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
import { booleanAttribute, ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
import { randomString } from '../../utils';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { NgIf } from '@angular/common';
|
|
||||||
import { MatLegacyButtonModule } from '@angular/material/legacy-button';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { randomString } from '../../utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-chevron-button [label]',
|
selector: 'iqser-chevron-button',
|
||||||
templateUrl: './chevron-button.component.html',
|
templateUrl: './chevron-button.component.html',
|
||||||
styleUrls: ['./chevron-button.component.scss'],
|
styleUrls: ['./chevron-button.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
imports: [MatIconModule, MatButtonModule],
|
||||||
imports: [NgIf, MatIconModule, MatLegacyButtonModule],
|
|
||||||
})
|
})
|
||||||
export class ChevronButtonComponent {
|
export class ChevronButtonComponent {
|
||||||
@Input() label!: string;
|
readonly label = input.required<string>();
|
||||||
@Input() showDot = false;
|
readonly showDot = input(false, { transform: booleanAttribute });
|
||||||
@Input() primary = false;
|
readonly primary = input(false, { transform: booleanAttribute });
|
||||||
@Input() disabled = false;
|
readonly disabled = input(false, { transform: booleanAttribute });
|
||||||
@Input() buttonId = `${randomString()}-chevron-button`;
|
readonly buttonId = input(`${randomString()}-chevron-button`);
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/lib/buttons/chevron-button/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './chevron-button.component';
|
||||||
@ -1,20 +1,25 @@
|
|||||||
<div [matTooltipClass]="tooltipClass" [matTooltipPosition]="tooltipPosition" [matTooltip]="tooltip">
|
<div [matTooltipClass]="tooltipClass()" [matTooltipPosition]="tooltipPosition()" [matTooltip]="tooltip()">
|
||||||
<button
|
<button
|
||||||
(click)="performAction($event)"
|
(click)="performAction($event)"
|
||||||
[class.dark-bg]="type === _circleButtonTypes.dark"
|
[class.dark-bg]="type() === _circleButtonTypes.dark"
|
||||||
[class.grey-selected]="greySelected"
|
[class.grey-selected]="greySelected()"
|
||||||
[class.help]="type === _circleButtonTypes.help"
|
[class.overlay]="showDot()"
|
||||||
[class.overlay]="showDot"
|
[class.primary]="type() === _circleButtonTypes.primary"
|
||||||
[class.primary]="type === _circleButtonTypes.primary"
|
[class.warn]="type() === _circleButtonTypes.warn"
|
||||||
[class.warn]="type === _circleButtonTypes.warn"
|
[disabled]="disabled()"
|
||||||
[disabled]="disabled"
|
[id]="buttonId()"
|
||||||
[id]="buttonId"
|
[iqserStopPropagation]="action.observed && !_hasRouterLink"
|
||||||
[stopPropagation]="action.observed && !_hasRouterLink"
|
[type]="isSubmit() ? 'submit' : 'button'"
|
||||||
[type]="isSubmit ? 'submit' : 'button'"
|
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
>
|
>
|
||||||
<mat-icon [svgIcon]="icon"></mat-icon>
|
<mat-icon [svgIcon]="icon()"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div *ngIf="showDot" class="dot"></div>
|
@if (showDot()) {
|
||||||
|
<div class="dot"></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (dropdownButton()) {
|
||||||
|
<div [class.disabled]="disabled()" class="arrow-down"></div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,47 +1,16 @@
|
|||||||
:host {
|
:host > div {
|
||||||
height: var(--size);
|
width: var(--circle-button-size);
|
||||||
width: var(--size);
|
height: var(--circle-button-size);
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
button {
|
.arrow-down {
|
||||||
height: var(--size);
|
border: 5px solid transparent;
|
||||||
width: var(--size);
|
border-top-color: black;
|
||||||
line-height: var(--size);
|
position: fixed;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: -8px;
|
||||||
|
|
||||||
mat-icon {
|
&.disabled {
|
||||||
width: var(--iconSize);
|
border-top-color: var(--iqser-grey-3);
|
||||||
height: var(--iconSize);
|
|
||||||
line-height: var(--iconSize);
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
line-height: var(--iconSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mat-button-disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
|
|
||||||
&:not(.primary):not(.warn):not(.dark-bg):hover {
|
|
||||||
background-color: var(--iqser-btn-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.primary.mat-button-disabled {
|
|
||||||
color: #ffffff80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warn:not([disabled]) {
|
|
||||||
background-color: var(--iqser-warn);
|
|
||||||
color: var(--iqser-accent);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--iqser-warn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.primary mat-icon {
|
|
||||||
color: var(--iqser-primary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +1,62 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core';
|
import {
|
||||||
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
booleanAttribute,
|
||||||
import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type';
|
ChangeDetectionStrategy,
|
||||||
import { IqserTooltipPosition, IqserTooltipPositions, randomString } from '../../utils';
|
Component,
|
||||||
import { NgIf } from '@angular/common';
|
effect,
|
||||||
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
inject,
|
||||||
|
input,
|
||||||
|
numberAttribute,
|
||||||
|
Output,
|
||||||
|
viewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { StopPropagationDirective } from '../../directives';
|
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { StopPropagationDirective } from '../../directives';
|
||||||
|
import { IqserTooltipPosition, IqserTooltipPositions, randomString } from '../../utils';
|
||||||
|
import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-circle-button [icon]',
|
selector: 'iqser-circle-button',
|
||||||
templateUrl: './circle-button.component.html',
|
templateUrl: './circle-button.component.html',
|
||||||
styleUrls: ['./circle-button.component.scss'],
|
styleUrls: ['./circle-button.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
imports: [MatTooltipModule, MatIconModule, MatButtonModule, StopPropagationDirective],
|
||||||
imports: [MatTooltipModule, MatIconModule, NgIf, MatButtonModule, StopPropagationDirective],
|
|
||||||
})
|
})
|
||||||
export class CircleButtonComponent implements OnInit {
|
export class CircleButtonComponent {
|
||||||
@Input() buttonId = `${randomString()}-circle-button`;
|
readonly #elementRef = inject(ElementRef<HTMLElement>);
|
||||||
@Input() icon!: string;
|
protected readonly _matTooltip = viewChild.required(MatTooltip);
|
||||||
@Input() tooltip?: string;
|
|
||||||
@Input() tooltipClass?: string;
|
|
||||||
@Input() showDot = false;
|
|
||||||
@Input() tooltipPosition: IqserTooltipPosition = IqserTooltipPositions.above;
|
|
||||||
@Input() disabled = false;
|
|
||||||
@Input() type: CircleButtonType = CircleButtonTypes.default;
|
|
||||||
@Input() greySelected = false;
|
|
||||||
@Input() helpModeButton = false;
|
|
||||||
@Input() removeTooltip = false;
|
|
||||||
@Input() isSubmit = false;
|
|
||||||
@Input() size = 34;
|
|
||||||
@Input() iconSize = 14;
|
|
||||||
@Output() readonly action = new EventEmitter<MouseEvent>();
|
|
||||||
protected readonly _circleButtonTypes = CircleButtonTypes;
|
protected readonly _circleButtonTypes = CircleButtonTypes;
|
||||||
protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true });
|
protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true });
|
||||||
@ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip;
|
readonly buttonId = input(`${randomString()}-circle-button`);
|
||||||
readonly #elementRef = inject(ElementRef<HTMLElement>);
|
readonly icon = input.required<string>();
|
||||||
|
readonly tooltip = input('');
|
||||||
|
readonly tooltipClass = input('');
|
||||||
|
readonly showDot = input(false, { transform: booleanAttribute });
|
||||||
|
readonly tooltipPosition = input<IqserTooltipPosition>(IqserTooltipPositions.above);
|
||||||
|
readonly disabled = input(false, { transform: booleanAttribute });
|
||||||
|
readonly type = input<CircleButtonType>(CircleButtonTypes.default);
|
||||||
|
readonly greySelected = input(false, { transform: booleanAttribute });
|
||||||
|
readonly removeTooltip = input(false, { transform: booleanAttribute });
|
||||||
|
readonly isSubmit = input(false, { transform: booleanAttribute });
|
||||||
|
readonly dropdownButton = input(false, { transform: booleanAttribute });
|
||||||
|
readonly size = input(34, { transform: numberAttribute });
|
||||||
|
readonly iconSize = input(14, { transform: numberAttribute });
|
||||||
|
@Output() readonly action = new EventEmitter<MouseEvent>();
|
||||||
|
|
||||||
ngOnInit(): void {
|
constructor() {
|
||||||
this.#elementRef.nativeElement.style.setProperty('--size', `${this.size}px`);
|
effect(() => {
|
||||||
this.#elementRef.nativeElement.style.setProperty('--iconSize', `${this.iconSize}px`);
|
this.#elementRef.nativeElement.style.setProperty('--circle-button-size', `${this.size()}px`);
|
||||||
|
this.#elementRef.nativeElement.style.setProperty('--circle-button-icon-size', `${this.iconSize()}px`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
performAction($event: MouseEvent) {
|
performAction($event: MouseEvent) {
|
||||||
if (this.removeTooltip) {
|
if (this.removeTooltip()) {
|
||||||
this._matTooltip.hide();
|
this._matTooltip().hide();
|
||||||
// Timeout to allow tooltip to disappear first,
|
// Timeout to allow tooltip to disappear first,
|
||||||
// useful when removing an item from the list without a confirmation dialog
|
// useful when removing an item from the list without a confirmation dialog
|
||||||
setTimeout(() => this.action.emit($event));
|
setTimeout(() => this.action.emit($event));
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
<button
|
<button
|
||||||
(click)="!disabled && action.emit($event)"
|
(click)="!disabled() && emitAction($event)"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled()"
|
||||||
[id]="buttonId"
|
[id]="buttonId()"
|
||||||
[ngClass]="classes"
|
[iqserStopPropagation]="action.observed && !_hasRouterLink"
|
||||||
[stopPropagation]="action.observed && !_hasRouterLink"
|
[ngClass]="_classes()"
|
||||||
[type]="submit ? 'submit' : 'button'"
|
[type]="submit() ? 'submit' : 'button'"
|
||||||
mat-button
|
mat-button
|
||||||
>
|
>
|
||||||
<mat-icon *ngIf="icon" [svgIcon]="icon"></mat-icon>
|
@if (icon(); as icon) {
|
||||||
<span>{{ label }}</span>
|
<mat-icon [svgIcon]="icon"></mat-icon>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>{{ label() }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div *ngIf="showDot" class="dot"></div>
|
@if (showDot()) {
|
||||||
|
<div class="dot"></div>
|
||||||
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
button {
|
|
||||||
padding: 0 14px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.has-icon {
|
|
||||||
padding: 0 14px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
background-color: var(--iqser-btn-bg);
|
|
||||||
|
|
||||||
&:not(.mat-button-disabled):hover {
|
|
||||||
background-color: var(--iqser-btn-bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-icon {
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +1,42 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
import { NgClass } from '@angular/common';
|
||||||
import { IconButtonType, IconButtonTypes } from '../types/icon-button.type';
|
import { booleanAttribute, ChangeDetectionStrategy, Component, computed, EventEmitter, inject, input, Output } from '@angular/core';
|
||||||
import { randomString } from '../../utils';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { NgClass, NgIf } from '@angular/common';
|
|
||||||
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { StopPropagationDirective } from '../../directives';
|
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { StopPropagationDirective } from '../../directives';
|
||||||
|
import { randomString } from '../../utils';
|
||||||
|
import { IconButtonType, IconButtonTypes } from '../types/icon-button.type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-icon-button [label]',
|
selector: 'iqser-icon-button',
|
||||||
templateUrl: './icon-button.component.html',
|
templateUrl: './icon-button.component.html',
|
||||||
styleUrls: ['./icon-button.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
imports: [NgClass, MatButtonModule, MatIconModule, StopPropagationDirective],
|
||||||
imports: [NgClass, MatButtonModule, NgIf, MatIconModule, StopPropagationDirective],
|
|
||||||
})
|
})
|
||||||
export class IconButtonComponent {
|
export class IconButtonComponent {
|
||||||
@Input() label!: string;
|
|
||||||
@Input() buttonId = `${randomString()}-icon-button`;
|
|
||||||
@Input() icon?: string;
|
|
||||||
@Input() showDot = false;
|
|
||||||
@Input() disabled = false;
|
|
||||||
@Input() submit = false;
|
|
||||||
@Input() type: IconButtonType = IconButtonTypes.default;
|
|
||||||
@Output() readonly action = new EventEmitter<MouseEvent>();
|
|
||||||
protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true });
|
protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true });
|
||||||
|
readonly label = input.required<string>();
|
||||||
get classes(): Record<string, boolean> {
|
readonly buttonId = input(`${randomString()}-icon-button`);
|
||||||
|
readonly icon = input<string>();
|
||||||
|
readonly showDot = input(false, { transform: booleanAttribute });
|
||||||
|
readonly active = input(false, { transform: booleanAttribute });
|
||||||
|
readonly disabled = input(false, { transform: booleanAttribute });
|
||||||
|
readonly submit = input(false, { transform: booleanAttribute });
|
||||||
|
readonly type = input<IconButtonType>(IconButtonTypes.default);
|
||||||
|
protected readonly _classes = computed(() => {
|
||||||
return {
|
return {
|
||||||
overlay: this.showDot,
|
overlay: this.showDot(),
|
||||||
[this.type]: true,
|
[this.type()]: true,
|
||||||
'has-icon': !!this.icon,
|
'has-icon': !!this.icon(),
|
||||||
|
active: this.active(),
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
@Output() readonly action = new EventEmitter<MouseEvent>();
|
||||||
|
|
||||||
|
emitAction($event: MouseEvent) {
|
||||||
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
|
if (activeElement.tagName?.toLowerCase() === 'button') {
|
||||||
|
this.action.emit($event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,4 +3,3 @@ export * from './types/circle-button.type';
|
|||||||
|
|
||||||
export * from './icon-button/icon-button.component';
|
export * from './icon-button/icon-button.component';
|
||||||
export * from './circle-button/circle-button.component';
|
export * from './circle-button/circle-button.component';
|
||||||
export * from './chevron-button/chevron-button.component';
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ export const CircleButtonTypes = {
|
|||||||
primary: 'primary',
|
primary: 'primary',
|
||||||
warn: 'warn',
|
warn: 'warn',
|
||||||
dark: 'dark',
|
dark: 'dark',
|
||||||
help: 'help',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type CircleButtonType = keyof typeof CircleButtonTypes;
|
export type CircleButtonType = keyof typeof CircleButtonTypes;
|
||||||
|
|||||||
@ -2,8 +2,6 @@ export const IconButtonTypes = {
|
|||||||
default: 'default',
|
default: 'default',
|
||||||
dark: 'dark',
|
dark: 'dark',
|
||||||
primary: 'primary',
|
primary: 'primary',
|
||||||
help: 'help',
|
|
||||||
text: 'text',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type IconButtonType = keyof typeof IconButtonTypes;
|
export type IconButtonType = keyof typeof IconButtonTypes;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { List } from '../utils';
|
import { List } from '../utils/types/iqser-types';
|
||||||
|
|
||||||
export interface DynamicCache {
|
export interface DynamicCache {
|
||||||
readonly urls: List;
|
readonly urls: List;
|
||||||
|
|||||||
@ -2,13 +2,12 @@ import { inject, ModuleWithProviders, NgModule, Optional, Provider, SkipSelf } f
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
|
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
|
|
||||||
import { CommonUiOptions, IqserAppConfig, ModuleOptions } from './utils';
|
import { CommonUiOptions, IqserAppConfig, ModuleOptions } from './utils';
|
||||||
import { ConnectionStatusComponent, FullPageErrorComponent } from './error';
|
import { ConnectionStatusComponent, FullPageErrorComponent } from './error';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar';
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { ApiPathInterceptor, DefaultUserPreferenceService, IqserConfigService, IqserUserPreferenceService } from './services';
|
import { ApiPathInterceptor, DefaultUserPreferenceService, IqserConfigService, IqserUserPreferenceService } from './services';
|
||||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
@ -19,15 +18,7 @@ import { ICONS } from './utils/constants';
|
|||||||
import { StopPropagationDirective } from './directives';
|
import { StopPropagationDirective } from './directives';
|
||||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||||
|
|
||||||
const matModules = [
|
const matModules = [MatIconModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule, MatProgressBarModule];
|
||||||
MatIconModule,
|
|
||||||
MatProgressSpinnerModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatDialogModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
MatProgressBarModule,
|
|
||||||
];
|
|
||||||
const components = [ConnectionStatusComponent, FullPageErrorComponent];
|
const components = [ConnectionStatusComponent, FullPageErrorComponent];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@ -1,36 +1,42 @@
|
|||||||
import { Directive, HostListener, inject, OnDestroy, OnInit } from '@angular/core';
|
import { AfterViewInit, Directive, HostListener, inject, OnDestroy, signal } from '@angular/core';
|
||||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { hasFormChanged, IqserEventTarget } from '../utils';
|
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { ConfirmOptions } from '.';
|
import { debounceTime, firstValueFrom, fromEvent, merge, of, Subscription } from 'rxjs';
|
||||||
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
|
||||||
import { debounceTime, firstValueFrom, fromEvent, merge, Subscription } from 'rxjs';
|
|
||||||
import { LoadingService } from '../loading';
|
|
||||||
import { Toaster } from '../services';
|
|
||||||
import { IconButtonTypes } from '../buttons';
|
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { IconButtonTypes } from '../buttons/types/icon-button.type';
|
||||||
|
import { LoadingService } from '../loading/loading.service';
|
||||||
|
import { Toaster } from '../services/toaster.service';
|
||||||
|
import { hasFormChanged } from '../utils/functions';
|
||||||
|
import { IqserEventTarget } from '../utils/types/events.type';
|
||||||
|
import { ConfirmationDialogService } from './confirmation-dialog.service';
|
||||||
|
import { ConfirmOptions } from './confirmation-dialog/confirmation-dialog.component';
|
||||||
|
|
||||||
const TARGET_NODE = 'mat-dialog-container';
|
const DIALOG_CONTAINER = 'mat-dialog-container';
|
||||||
|
const TEXT_INPUT = 'text';
|
||||||
|
|
||||||
export interface SaveOptions {
|
export interface SaveOptions {
|
||||||
closeAfterSave?: boolean;
|
closeAfterSave?: boolean;
|
||||||
|
nextAction?: boolean;
|
||||||
addMembers?: boolean;
|
addMembers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class BaseDialogComponent implements OnInit, OnDestroy {
|
export abstract class BaseDialogComponent implements AfterViewInit, OnDestroy {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly #confirmationDialogService = inject(ConfirmationDialogService);
|
||||||
form?: UntypedFormGroup;
|
readonly #dialog = inject(MatDialog);
|
||||||
initialFormValue!: Record<string, string>;
|
protected readonly _hasErrors = signal(true);
|
||||||
protected readonly _formBuilder = inject(UntypedFormBuilder);
|
protected readonly _formBuilder = inject(UntypedFormBuilder);
|
||||||
protected readonly _loadingService = inject(LoadingService);
|
protected readonly _loadingService = inject(LoadingService);
|
||||||
protected readonly _toaster = inject(Toaster);
|
protected readonly _toaster = inject(Toaster);
|
||||||
readonly #confirmationDialogService = inject(ConfirmationDialogService);
|
protected readonly _subscriptions = new Subscription();
|
||||||
readonly #dialog = inject(MatDialog);
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly #subscriptions: Subscription = new Subscription();
|
form?: UntypedFormGroup;
|
||||||
#hasErrors = false;
|
initialFormValue!: Record<string, string>;
|
||||||
|
|
||||||
protected constructor(protected readonly _dialogRef: MatDialogRef<BaseDialogComponent>, private readonly _isInEditMode = false) {}
|
protected constructor(
|
||||||
|
protected readonly _dialogRef: MatDialogRef<BaseDialogComponent>,
|
||||||
|
private readonly _isInEditMode = false,
|
||||||
|
) {}
|
||||||
|
|
||||||
get valid(): boolean {
|
get valid(): boolean {
|
||||||
return !this.form || this.form.valid;
|
return !this.form || this.form.valid;
|
||||||
@ -41,24 +47,27 @@ export abstract class BaseDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get disabled(): boolean {
|
get disabled(): boolean {
|
||||||
return !this.valid || !this.changed || this.#hasErrors;
|
return !this.valid || !this.changed || this._hasErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngAfterViewInit() {
|
||||||
this.#subscriptions.add(this._dialogRef.backdropClick().subscribe(() => this.close()));
|
this._subscriptions.add(this._dialogRef.backdropClick().subscribe(() => this.close()));
|
||||||
|
const valueChanges = this.form?.valueChanges ?? of(null);
|
||||||
const events = [fromEvent(window, 'keyup'), fromEvent(window, 'input'), this.form?.valueChanges];
|
const events = [fromEvent(window, 'keyup'), fromEvent(window, 'input'), valueChanges];
|
||||||
|
this._hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]);
|
||||||
const events$ = merge(...events).pipe(
|
const events$ = merge(...events).pipe(
|
||||||
debounceTime(10),
|
debounceTime(10),
|
||||||
tap(() => (this.#hasErrors = !!document.getElementsByClassName('ng-invalid')[0])),
|
tap(() => {
|
||||||
|
this._hasErrors.set(!!document.getElementsByClassName('ng-invalid')[0]);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
this.#subscriptions.add(events$.subscribe());
|
this._subscriptions.add(events$.subscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract save(options?: SaveOptions): void;
|
abstract save(options?: SaveOptions): void;
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.#subscriptions.unsubscribe();
|
this._subscriptions.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
@ -79,9 +88,18 @@ export abstract class BaseDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
@HostListener('window:keydown.Enter', ['$event'])
|
@HostListener('window:keydown.Enter', ['$event'])
|
||||||
onEnter(event: KeyboardEvent): void {
|
onEnter(event: KeyboardEvent): void {
|
||||||
event?.stopImmediatePropagation();
|
const target = event.target as IqserEventTarget;
|
||||||
const node = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase();
|
const isDialogSelected = target.localName?.trim()?.toLowerCase() === DIALOG_CONTAINER;
|
||||||
if (this.valid && !this.disabled && this.changed && node === TARGET_NODE) {
|
const isTextInputSelected = target.type?.trim()?.toLowerCase() === TEXT_INPUT;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.valid &&
|
||||||
|
!this.disabled &&
|
||||||
|
(this.changed || !this._isInEditMode) &&
|
||||||
|
this.#dialog.openDialogs.length === 1 &&
|
||||||
|
(isDialogSelected || isTextInputSelected)
|
||||||
|
) {
|
||||||
|
event?.stopImmediatePropagation();
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { ConfirmationDialogComponent, ConfirmOption, defaultDialogConfig, IConfirmationDialogData, TitleColors } from '.';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import {
|
||||||
|
ConfirmationDialogComponent,
|
||||||
|
ConfirmOption,
|
||||||
|
IConfirmationDialogData,
|
||||||
|
TitleColors,
|
||||||
|
} from './confirmation-dialog/confirmation-dialog.component';
|
||||||
|
import { defaultDialogConfig } from './dialog.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|||||||
@ -1,56 +1,83 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div [class.warn]="isDeleteAction" [innerHTML]="config.title" class="dialog-header heading-l"></div>
|
<div [class.warn]="isDeleteAction" [innerHTML]="config.title" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div *ngIf="showToast && config.toastMessage" class="inline-dialog-toast toast-error">
|
@if (showToast && config.toastMessage) {
|
||||||
<div [translate]="config.toastMessage"></div>
|
<div class="inline-dialog-toast toast-error">
|
||||||
<a (click)="showToast = false" class="toast-close-button">
|
<div [translate]="config.toastMessage"></div>
|
||||||
<mat-icon svgIcon="iqser:close"></mat-icon>
|
<a (click)="showToast = false" class="toast-close-button">
|
||||||
</a>
|
<mat-icon svgIcon="iqser:close"></mat-icon>
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
@if (config.component) {
|
||||||
|
<ng-container #detailsComponent></ng-container>
|
||||||
|
}
|
||||||
|
|
||||||
<p [class.heading]="isDeleteAction" [innerHTML]="config.question" class="mt-0 mb-8"></p>
|
<p [class.heading]="isDeleteAction" [innerHTML]="config.question" class="mt-0 mb-8"></p>
|
||||||
<p *ngIf="config.details" [innerHTML]="config.details" class="mt-0"></p>
|
@if (config.details) {
|
||||||
|
<p [innerHTML]="config.details" class="mt-0"></p>
|
||||||
|
}
|
||||||
|
|
||||||
<div *ngIf="config.requireInput" class="iqser-input-group required w-300 mt-24">
|
@if (config.requireInput) {
|
||||||
<label>{{ inputLabel }}</label>
|
<div class="iqser-input-group required w-300 mt-24">
|
||||||
<input [(ngModel)]="inputValue" id="confirmation-input" />
|
<label>{{ inputLabel }}</label>
|
||||||
</div>
|
<input [(ngModel)]="inputValue" id="confirmation-input" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div *ngIf="config.checkboxes.length > 0" class="mt-24 checkboxes-wrapper">
|
@if (config.checkboxes.length > 0) {
|
||||||
<ng-container *ngFor="let checkbox of config.checkboxes">
|
<div class="mt-24 checkboxes-wrapper">
|
||||||
<mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary">
|
@for (checkbox of config.checkboxes; track checkbox) {
|
||||||
{{ checkbox.label | translate : config.translateParams }}
|
<mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary">
|
||||||
</mat-checkbox>
|
{{ checkbox.label | translate: config.translateParams }}
|
||||||
<ng-container *ngTemplateOutlet="checkbox.extraContent; context: { data: checkbox.extraContentData }"></ng-container>
|
</mat-checkbox>
|
||||||
</ng-container>
|
<ng-container *ngTemplateOutlet="checkbox.extraContent; context: { data: checkbox.extraContentData }"></ng-container>
|
||||||
</div>
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions" [class.reverse]="config.cancelButtonPrimary">
|
||||||
<iqser-icon-button
|
@if (!config.cancelButtonPrimary) {
|
||||||
(action)="confirm(confirmOption)"
|
<iqser-icon-button
|
||||||
[disabled]="(config.requireInput && confirmationDoesNotMatch()) || config.disableConfirm"
|
(action)="confirm(confirmOption)"
|
||||||
[label]="config.confirmationText"
|
[disabled]="(config.requireInput && confirmationDoesNotMatch()) || config.disableConfirm"
|
||||||
[type]="iconButtonTypes.primary"
|
[label]="config.confirmationText"
|
||||||
buttonId="confirm"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
buttonId="confirm"
|
||||||
|
></iqser-icon-button>
|
||||||
|
} @else {
|
||||||
|
<div (click)="confirm(confirmOption)" class="all-caps-label cancel no-uppercase" id="confirm">
|
||||||
|
{{ config.confirmationText }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<iqser-icon-button
|
@if (config.alternativeConfirmationText) {
|
||||||
(action)="confirm(confirmOptions.SECOND_CONFIRM)"
|
<iqser-icon-button
|
||||||
*ngIf="config.alternativeConfirmationText"
|
(action)="confirm(confirmOptions.CONFIRM_WITH_ACTION)"
|
||||||
[disabled]="config.requireInput && confirmationDoesNotMatch()"
|
[disabled]="config.requireInput && confirmationDoesNotMatch()"
|
||||||
[label]="config.alternativeConfirmationText"
|
[label]="config.alternativeConfirmationText"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
}
|
||||||
|
|
||||||
<div (click)="confirm(confirmOptions.DISCARD_CHANGES)" *ngIf="config.discardChangesText" class="all-caps-label cancel">
|
@if (config.discardChangesText) {
|
||||||
{{ config.discardChangesText }}
|
<div (click)="confirm(confirmOptions.DISCARD_CHANGES)" class="all-caps-label cancel">
|
||||||
</div>
|
{{ config.discardChangesText }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div (click)="deny()" *ngIf="!config.discardChangesText" class="all-caps-label cancel">
|
@if (!config.discardChangesText) {
|
||||||
{{ config.denyText }}
|
@if (config.cancelButtonPrimary) {
|
||||||
</div>
|
<iqser-icon-button (click)="deny()" [label]="config.denyText" [type]="iconButtonTypes.primary"></iqser-icon-button>
|
||||||
|
} @else {
|
||||||
|
<div (click)="deny()" class="all-caps-label cancel">
|
||||||
|
{{ config.denyText }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||||
|
|||||||
@ -6,3 +6,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reverse {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-uppercase {
|
||||||
|
text-transform: unset;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,23 @@
|
|||||||
import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core';
|
import { NgTemplateOutlet } from '@angular/common';
|
||||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
import {
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
AfterViewInit,
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
ChangeDetectionStrategy,
|
||||||
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '../../buttons';
|
Component,
|
||||||
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
HostListener,
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
inject,
|
||||||
|
TemplateRef,
|
||||||
|
Type,
|
||||||
|
viewChild,
|
||||||
|
ViewContainerRef,
|
||||||
|
} from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CircleButtonComponent, IconButtonTypes } from '../../buttons';
|
||||||
|
import { IconButtonComponent } from '../../buttons';
|
||||||
import { ValuesOf } from '../../utils';
|
import { ValuesOf } from '../../utils';
|
||||||
|
|
||||||
export const TitleColors = {
|
export const TitleColors = {
|
||||||
@ -18,7 +29,7 @@ export type TitleColor = ValuesOf<typeof TitleColors>;
|
|||||||
|
|
||||||
export const ConfirmOptions = {
|
export const ConfirmOptions = {
|
||||||
CONFIRM: 1,
|
CONFIRM: 1,
|
||||||
SECOND_CONFIRM: 2,
|
CONFIRM_WITH_ACTION: 2,
|
||||||
DISCARD_CHANGES: 3,
|
DISCARD_CHANGES: 3,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -46,6 +57,9 @@ interface InternalConfirmationDialogData {
|
|||||||
readonly checkboxes: CheckBox[];
|
readonly checkboxes: CheckBox[];
|
||||||
readonly checkboxesValidation: boolean;
|
readonly checkboxesValidation: boolean;
|
||||||
readonly toastMessage?: string;
|
readonly toastMessage?: string;
|
||||||
|
readonly component?: Type<unknown>;
|
||||||
|
readonly componentInputs?: { [key: string]: unknown };
|
||||||
|
readonly cancelButtonPrimary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IConfirmationDialogData = Partial<InternalConfirmationDialogData>;
|
export type IConfirmationDialogData = Partial<InternalConfirmationDialogData>;
|
||||||
@ -63,6 +77,9 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
denyText: options?.denyText ?? _('common.confirmation-dialog.deny'),
|
denyText: options?.denyText ?? _('common.confirmation-dialog.deny'),
|
||||||
checkboxes: options?.checkboxes ?? [],
|
checkboxes: options?.checkboxes ?? [],
|
||||||
checkboxesValidation: typeof options?.checkboxesValidation === 'boolean' ? options.checkboxesValidation : true,
|
checkboxesValidation: typeof options?.checkboxesValidation === 'boolean' ? options.checkboxesValidation : true,
|
||||||
|
component: options?.component,
|
||||||
|
componentInputs: options?.componentInputs,
|
||||||
|
cancelButtonPrimary: options?.cancelButtonPrimary ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,12 +87,9 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
templateUrl: './confirmation-dialog.component.html',
|
templateUrl: './confirmation-dialog.component.html',
|
||||||
styleUrls: ['./confirmation-dialog.component.scss'],
|
styleUrls: ['./confirmation-dialog.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
|
||||||
imports: [
|
imports: [
|
||||||
NgIf,
|
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgForOf,
|
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
@ -84,13 +98,14 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ConfirmationDialogComponent {
|
export class ConfirmationDialogComponent implements AfterViewInit {
|
||||||
readonly config = getConfig(inject(MAT_DIALOG_DATA));
|
readonly config = getConfig(inject(MAT_DIALOG_DATA));
|
||||||
inputValue = '';
|
inputValue = '';
|
||||||
showToast = false;
|
showToast = false;
|
||||||
readonly inputLabel: string;
|
readonly inputLabel: string;
|
||||||
readonly confirmOptions = ConfirmOptions;
|
readonly confirmOptions = ConfirmOptions;
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
readonly detailsComponentRef = viewChild.required('detailsComponent', { read: ViewContainerRef });
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _dialogRef: MatDialogRef<ConfirmationDialogComponent, ConfirmOption>,
|
private readonly _dialogRef: MatDialogRef<ConfirmationDialogComponent, ConfirmOption>,
|
||||||
@ -110,18 +125,24 @@ export class ConfirmationDialogComponent {
|
|||||||
|
|
||||||
get confirmOption(): ConfirmOption {
|
get confirmOption(): ConfirmOption {
|
||||||
if (!this.config.checkboxesValidation && this.config.checkboxes[0]?.value) {
|
if (!this.config.checkboxesValidation && this.config.checkboxes[0]?.value) {
|
||||||
return ConfirmOptions.SECOND_CONFIRM;
|
return ConfirmOptions.CONFIRM_WITH_ACTION;
|
||||||
}
|
}
|
||||||
return ConfirmOptions.CONFIRM;
|
return ConfirmOptions.CONFIRM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keyup.enter')
|
@HostListener('window:keyup.enter', ['$event'])
|
||||||
onKeyupEnter(): void {
|
onKeyupEnter(event: KeyboardEvent): void {
|
||||||
if (this.config.requireInput && !this.confirmationDoesNotMatch()) {
|
event?.stopImmediatePropagation();
|
||||||
this.confirm(ConfirmOptions.CONFIRM);
|
if (!this.config.requireInput || !this.confirmationDoesNotMatch()) {
|
||||||
|
if (!this.config.cancelButtonPrimary) this.confirm(ConfirmOptions.CONFIRM);
|
||||||
|
else this.deny();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.#initializeDetailsComponent();
|
||||||
|
}
|
||||||
|
|
||||||
confirmationDoesNotMatch(): boolean {
|
confirmationDoesNotMatch(): boolean {
|
||||||
return this.inputValue.toLowerCase() !== this.config.confirmationText.toLowerCase();
|
return this.inputValue.toLowerCase() !== this.config.confirmationText.toLowerCase();
|
||||||
}
|
}
|
||||||
@ -155,4 +176,14 @@ export class ConfirmationDialogComponent {
|
|||||||
Object.assign(obj, { [key]: value });
|
Object.assign(obj, { [key]: value });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#initializeDetailsComponent() {
|
||||||
|
if (!this.config.component) return;
|
||||||
|
const component = this.detailsComponentRef().createComponent(this.config.component);
|
||||||
|
if (this.config.componentInputs) {
|
||||||
|
for (const [key, value] of Object.entries(this.config.componentInputs)) {
|
||||||
|
(component.instance as any)[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import { ComponentType } from '@angular/cdk/portal';
|
import { ComponentType } from '@angular/cdk/portal';
|
||||||
import { mergeMap } from 'rxjs/operators';
|
import { Injectable, Type } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { from } from 'rxjs';
|
import { from } from 'rxjs';
|
||||||
|
import { mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
export const largeDialogConfig: MatDialogConfig = {
|
export const largeDialogConfig: MatDialogConfig = {
|
||||||
width: '90vw',
|
width: '90vw',
|
||||||
@ -64,4 +64,34 @@ export abstract class DialogService<T extends string> {
|
|||||||
|
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open(
|
||||||
|
type: Type<unknown>,
|
||||||
|
data?: unknown,
|
||||||
|
config?: object,
|
||||||
|
cb?: (...params: unknown[]) => Promise<unknown> | void,
|
||||||
|
finallyCb?: (...params: unknown[]) => void | Promise<unknown>,
|
||||||
|
): MatDialogRef<unknown> {
|
||||||
|
const ref = this._dialog.open(type, {
|
||||||
|
...defaultDialogConfig,
|
||||||
|
...(config || {}),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fn = async (result: unknown) => {
|
||||||
|
if (result && cb) {
|
||||||
|
await cb(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finallyCb) {
|
||||||
|
await finallyCb(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.afterClosed()
|
||||||
|
.pipe(mergeMap(result => from(fn(result))))
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,5 @@ export * from './base-dialog.component';
|
|||||||
export * from './confirmation-dialog.service';
|
export * from './confirmation-dialog.service';
|
||||||
export * from './confirmation-dialog/confirmation-dialog.component';
|
export * from './confirmation-dialog/confirmation-dialog.component';
|
||||||
export * from './dialog.service';
|
export * from './dialog.service';
|
||||||
|
export * from './iqser-dialog-component.directive';
|
||||||
|
export * from './iqser-dialog.service';
|
||||||
|
|||||||
82
src/lib/dialog/iqser-dialog-component.directive.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Directive, HostListener, inject } from '@angular/core';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { IconButtonTypes } from '../buttons';
|
||||||
|
import { hasFormChanged, IqserEventTarget } from '../utils';
|
||||||
|
|
||||||
|
const DIALOG_CONTAINER = 'mat-dialog-container';
|
||||||
|
const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE');
|
||||||
|
const RETURN_TYPE_SYMBOL = Symbol.for('RETURN_TYPE');
|
||||||
|
|
||||||
|
export type DATA_TYPE = typeof DATA_TYPE_SYMBOL;
|
||||||
|
export type RETURN_TYPE = typeof RETURN_TYPE_SYMBOL;
|
||||||
|
|
||||||
|
@Directive()
|
||||||
|
export abstract class IqserDialogComponent<ComponentType, DataType = null, ReturnType = void> {
|
||||||
|
readonly [DATA_TYPE_SYMBOL]!: DataType;
|
||||||
|
readonly [RETURN_TYPE_SYMBOL]!: ReturnType;
|
||||||
|
|
||||||
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
readonly dialogRef = inject(MatDialogRef<ComponentType, ReturnType>);
|
||||||
|
readonly data = inject<DataType>(MAT_DIALOG_DATA);
|
||||||
|
readonly dialog = inject(MatDialog);
|
||||||
|
readonly form?: FormGroup;
|
||||||
|
readonly ignoredKeys: string[] = [];
|
||||||
|
|
||||||
|
initialFormValue: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
constructor(private readonly _editMode = false) {
|
||||||
|
this.dialogRef
|
||||||
|
.backdropClick()
|
||||||
|
.pipe(takeUntilDestroyed())
|
||||||
|
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||||
|
.subscribe(() => this.dialogRef.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
get valid(): boolean {
|
||||||
|
return !this.form || this.form.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
get changed(): boolean {
|
||||||
|
return !this.form || hasFormChanged(this.form, this.initialFormValue, this.ignoredKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
get disabled(): boolean {
|
||||||
|
return !this.valid || !this.changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.Escape', ['$event'])
|
||||||
|
onEscape(): void {
|
||||||
|
if (this.dialog.openDialogs.length === 1) {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.Enter', ['$event'])
|
||||||
|
onEnter(event: KeyboardEvent): void {
|
||||||
|
event?.stopImmediatePropagation();
|
||||||
|
if (this.onEnterValidator(event)) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnterValidator(event: KeyboardEvent) {
|
||||||
|
const targetElement = (event.target as IqserEventTarget).localName?.trim()?.toLowerCase();
|
||||||
|
const canClose = targetElement === DIALOG_CONTAINER && this.valid;
|
||||||
|
if (this._editMode) {
|
||||||
|
return canClose && this.changed;
|
||||||
|
}
|
||||||
|
return canClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(dialogResult?: ReturnType) {
|
||||||
|
this.dialogRef.close(dialogResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for testing only
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
class DialogComponentImpl extends IqserDialogComponent<DialogComponentImpl, unknown, unknown> {}
|
||||||
44
src/lib/dialog/iqser-dialog.service.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { ComponentType } from '@angular/cdk/portal';
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { defaultDialogConfig, largeDialogConfig } from './dialog.service';
|
||||||
|
import { DATA_TYPE, IqserDialogComponent, RETURN_TYPE } from './iqser-dialog-component.directive';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class IqserDialog {
|
||||||
|
protected readonly _dialog = inject(MatDialog);
|
||||||
|
|
||||||
|
open<
|
||||||
|
Component extends IqserDialogComponent<Component, Component[DATA_TYPE], Component[RETURN_TYPE]>,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
Data extends Component[DATA_TYPE] = Component[DATA_TYPE],
|
||||||
|
Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE],
|
||||||
|
>(dialog: ComponentType<Component>, config?: MatDialogConfig<Component[DATA_TYPE]>) {
|
||||||
|
const ref = this._dialog.open<Component, Component[DATA_TYPE], Return>(dialog, config);
|
||||||
|
return {
|
||||||
|
...ref,
|
||||||
|
result() {
|
||||||
|
return firstValueFrom(ref.afterClosed());
|
||||||
|
},
|
||||||
|
} as MatDialogRef<Component, Return> & { result(): Promise<Return> };
|
||||||
|
}
|
||||||
|
|
||||||
|
openLarge<
|
||||||
|
Component extends IqserDialogComponent<Component, Component[DATA_TYPE], Component[RETURN_TYPE]>,
|
||||||
|
Data extends Component[DATA_TYPE] = Component[DATA_TYPE],
|
||||||
|
Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE],
|
||||||
|
>(dialog: ComponentType<Component>, config?: MatDialogConfig<Component[DATA_TYPE]>) {
|
||||||
|
return this.open<Component, Data, Return>(dialog, { ...largeDialogConfig, ...config });
|
||||||
|
}
|
||||||
|
|
||||||
|
openDefault<
|
||||||
|
Component extends IqserDialogComponent<Component, Component[DATA_TYPE], Component[RETURN_TYPE]>,
|
||||||
|
Data extends Component[DATA_TYPE] = Component[DATA_TYPE],
|
||||||
|
Return extends Component[RETURN_TYPE] = Component[RETURN_TYPE],
|
||||||
|
>(dialog: ComponentType<Component>, config?: MatDialogConfig<Component[DATA_TYPE]>) {
|
||||||
|
return this.open<Component, Data, Return>(dialog, { ...defaultDialogConfig, ...config });
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/lib/directives/disable-stop-propagation.directive.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { booleanAttribute, Directive, input } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[iqserDisableStopPropagation]',
|
||||||
|
})
|
||||||
|
export class DisableStopPropagationDirective {
|
||||||
|
readonly iqserDisableStopPropagation = input(true, { transform: booleanAttribute });
|
||||||
|
}
|
||||||
@ -1,15 +1,24 @@
|
|||||||
import { ChangeDetectorRef, Directive, ElementRef, HostBinding, HostListener, OnChanges, OnInit } from '@angular/core';
|
import { Directive, ElementRef, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserHasScrollbar]',
|
selector: '[iqserHasScrollbar]',
|
||||||
standalone: true,
|
host: {
|
||||||
|
'[class]': '_class()',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export class HasScrollbarDirective implements OnInit, OnChanges {
|
export class HasScrollbarDirective implements OnInit, OnDestroy {
|
||||||
@HostBinding('class') class = '';
|
private readonly _resizeObserver: ResizeObserver;
|
||||||
|
protected readonly _class = signal('');
|
||||||
|
|
||||||
constructor(protected readonly _elementRef: ElementRef, protected readonly _changeDetector: ChangeDetectorRef) {}
|
constructor(protected readonly _elementRef: ElementRef) {
|
||||||
|
this._resizeObserver = new ResizeObserver(() => {
|
||||||
|
this.process();
|
||||||
|
});
|
||||||
|
|
||||||
get hasScrollbar() {
|
this._resizeObserver.observe(this._elementRef.nativeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _hasScrollbar() {
|
||||||
const element = this._elementRef?.nativeElement as HTMLElement;
|
const element = this._elementRef?.nativeElement as HTMLElement;
|
||||||
return element.clientHeight < element.scrollHeight;
|
return element.clientHeight < element.scrollHeight;
|
||||||
}
|
}
|
||||||
@ -18,16 +27,12 @@ export class HasScrollbarDirective implements OnInit, OnChanges {
|
|||||||
setTimeout(() => this.process(), 0);
|
setTimeout(() => this.process(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize')
|
|
||||||
process() {
|
process() {
|
||||||
const newClass = this.hasScrollbar ? 'has-scrollbar' : '';
|
const newClass = this._hasScrollbar ? 'has-scrollbar' : '';
|
||||||
if (this.class !== newClass) {
|
this._class.set(newClass);
|
||||||
this.class = newClass;
|
|
||||||
this._changeDetector.markForCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnDestroy() {
|
||||||
this.process();
|
this._resizeObserver.unobserve(this._elementRef.nativeElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/c
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserHiddenAction]',
|
selector: '[iqserHiddenAction]',
|
||||||
standalone: true,
|
|
||||||
})
|
})
|
||||||
export class HiddenActionDirective {
|
export class HiddenActionDirective {
|
||||||
@Input() requiredClicks = 4;
|
@Input() requiredClicks = 4;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from './hidden-action.directive';
|
export * from './hidden-action.directive';
|
||||||
export * from './stop-propagation.directive';
|
export * from './stop-propagation.directive';
|
||||||
|
export * from './disable-stop-propagation.directive';
|
||||||
export * from './prevent-default.directive';
|
export * from './prevent-default.directive';
|
||||||
export * from './has-scrollbar.directive';
|
export * from './has-scrollbar.directive';
|
||||||
export * from './sync-width.directive';
|
export * from './sync-width.directive';
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
import { Directive, HostListener, Input } from '@angular/core';
|
import { booleanAttribute, Directive, HostListener, inject, Input } from '@angular/core';
|
||||||
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[preventDefault]',
|
selector: '[iqserPreventDefault]',
|
||||||
standalone: true,
|
|
||||||
})
|
})
|
||||||
export class PreventDefaultDirective {
|
export class PreventDefaultDirective {
|
||||||
@Input() preventDefault: boolean | string = true;
|
readonly #logger = inject(NGXLogger);
|
||||||
|
@Input({ transform: booleanAttribute }) iqserPreventDefault = true;
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
@HostListener('click', ['$event'])
|
||||||
onClick($event: Event) {
|
onClick($event: Event) {
|
||||||
const stop = this.preventDefault === '' || this.preventDefault === 'true' || this.preventDefault === true;
|
if (this.iqserPreventDefault) {
|
||||||
if (stop) {
|
this.#logger.info('[CLICK] iqserPreventDefault');
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
import { Directive, HostListener, Input } from '@angular/core';
|
import { booleanAttribute, Directive, HostListener, inject, Input } from '@angular/core';
|
||||||
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
import { DisableStopPropagationDirective } from './disable-stop-propagation.directive';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[stopPropagation]',
|
selector: '[iqserStopPropagation]',
|
||||||
standalone: true,
|
|
||||||
})
|
})
|
||||||
export class StopPropagationDirective {
|
export class StopPropagationDirective {
|
||||||
@Input() stopPropagation: boolean | string = true;
|
readonly #disableStopPropagation = inject(DisableStopPropagationDirective, { optional: true });
|
||||||
|
readonly #logger = inject(NGXLogger);
|
||||||
|
@Input({ transform: booleanAttribute }) iqserStopPropagation = true;
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
@HostListener('click', ['$event'])
|
||||||
onClick($event: Event) {
|
onClick($event: Event) {
|
||||||
const stop = this.stopPropagation === '' || this.stopPropagation === 'true' || this.stopPropagation === true;
|
if (this.#disableStopPropagation?.iqserDisableStopPropagation()) {
|
||||||
if (stop) {
|
this.#logger.info('[CLICK] iqserStopPropagation is disabled by iqserDisableStopPropagation');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.iqserStopPropagation) {
|
||||||
|
this.#logger.info('[CLICK] iqserStopPropagation');
|
||||||
|
$event.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserSyncWidth]',
|
selector: '[iqserSyncWidth]',
|
||||||
standalone: true,
|
|
||||||
})
|
})
|
||||||
export class SyncWidthDirective implements OnDestroy {
|
export class SyncWidthDirective implements OnDestroy {
|
||||||
@Input() iqserSyncWidth!: string;
|
@Input() iqserSyncWidth!: string;
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
<div
|
<div [ngStyle]="styles()" class="empty-state">
|
||||||
[ngStyle]="{
|
@if (icon(); as icon) {
|
||||||
'padding-top': verticalPadding + 'px',
|
<mat-icon [svgIcon]="icon"></mat-icon>
|
||||||
'padding-left': horizontalPadding + 'px',
|
}
|
||||||
'padding-right': horizontalPadding + 'px'
|
|
||||||
}"
|
|
||||||
class="empty-state"
|
|
||||||
>
|
|
||||||
<mat-icon *ngIf="icon" [svgIcon]="icon"></mat-icon>
|
|
||||||
|
|
||||||
<div class="ng-content-wrapper heading-l">
|
<div class="ng-content-wrapper heading-l">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [innerHTML]="text" class="heading-l"></div>
|
<div [innerHTML]="text()" class="heading-l"></div>
|
||||||
|
|
||||||
<iqser-icon-button
|
@if (showButton() && this.action.observed) {
|
||||||
(action)="action.emit()"
|
<iqser-icon-button
|
||||||
*ngIf="showButton"
|
(action)="action.emit()"
|
||||||
[buttonId]="buttonId"
|
[buttonId]="buttonId()"
|
||||||
[icon]="buttonIcon"
|
[icon]="buttonIcon()"
|
||||||
[iqserHelpMode]="helpModeKey"
|
[attr.help-mode-key]="helpModeKey()"
|
||||||
[label]="buttonLabel"
|
[label]="buttonLabel()"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,33 +1,42 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { NgStyle } from '@angular/common';
|
||||||
import { IconButtonComponent, IconButtonTypes } from '../buttons';
|
import {
|
||||||
import { randomString } from '../utils';
|
booleanAttribute,
|
||||||
import { NgIf, NgStyle } from '@angular/common';
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
EventEmitter,
|
||||||
|
input,
|
||||||
|
numberAttribute,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { IqserHelpModeModule } from '../help-mode';
|
import { IconButtonComponent } from '../buttons/icon-button/icon-button.component';
|
||||||
|
import { IconButtonTypes } from '../buttons/types/icon-button.type';
|
||||||
|
import { randomString } from '../utils/functions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-empty-state [text]',
|
selector: 'iqser-empty-state',
|
||||||
templateUrl: './empty-state.component.html',
|
templateUrl: './empty-state.component.html',
|
||||||
styleUrls: ['./empty-state.component.scss'],
|
styleUrls: ['./empty-state.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
imports: [NgStyle, MatIconModule, IconButtonComponent],
|
||||||
imports: [NgStyle, MatIconModule, NgIf, IconButtonComponent, IqserHelpModeModule],
|
|
||||||
})
|
})
|
||||||
export class EmptyStateComponent implements OnInit {
|
export class EmptyStateComponent {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
|
||||||
@Input() text!: string;
|
readonly text = input.required<string>();
|
||||||
@Input() icon?: string;
|
readonly icon = input<string>();
|
||||||
@Input() showButton = true;
|
readonly showButton = input(true, { transform: booleanAttribute });
|
||||||
@Input() buttonIcon = 'iqser:plus';
|
readonly buttonIcon = input('iqser:plus');
|
||||||
@Input() buttonLabel?: string;
|
readonly buttonLabel = input<string>();
|
||||||
@Input() buttonId = `${randomString()}-icon-button`;
|
readonly buttonId = input(`${randomString()}-icon-button`);
|
||||||
@Input() horizontalPadding = 100;
|
readonly horizontalPadding = input(100, { transform: numberAttribute });
|
||||||
@Input() verticalPadding = 120;
|
readonly verticalPadding = input(120, { transform: numberAttribute });
|
||||||
@Input() helpModeKey?: string;
|
protected readonly styles = computed(() => ({
|
||||||
|
'padding-top': this.verticalPadding() + 'px',
|
||||||
|
'padding-left': this.horizontalPadding() + 'px',
|
||||||
|
'padding-right': this.horizontalPadding() + 'px',
|
||||||
|
}));
|
||||||
|
readonly helpModeKey = input<string>();
|
||||||
@Output() readonly action = new EventEmitter();
|
@Output() readonly action = new EventEmitter();
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.showButton = this.showButton && this.action.observed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
<div
|
@if (connectionStatus(); as status) {
|
||||||
*ngIf="errorService.connectionStatus$ | async as status"
|
<div [@animateOpenClose]="status" [ngClass]="status" class="indicator flex-align-items-center">
|
||||||
[@animateOpenClose]="status"
|
<span [translate]="connectionStatusTranslations[status]"></span>
|
||||||
[ngClass]="status"
|
</div>
|
||||||
class="indicator flex-align-items-center"
|
}
|
||||||
>
|
|
||||||
<span [translate]="connectionStatusTranslations[status]"></span>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
||||||
import { connectionStatusTranslations } from '../../translations';
|
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { connectionStatusTranslations } from '../../translations';
|
||||||
import { ErrorService } from '../error.service';
|
import { ErrorService } from '../error.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,9 +17,9 @@ import { ErrorService } from '../error.service';
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class ConnectionStatusComponent {
|
export class ConnectionStatusComponent {
|
||||||
connectionStatusTranslations = connectionStatusTranslations;
|
protected readonly connectionStatusTranslations = connectionStatusTranslations;
|
||||||
|
protected readonly connectionStatus = toSignal(inject(ErrorService).connectionStatus$);
|
||||||
constructor(readonly errorService: ErrorService) {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { fromEvent, merge, Observable, Subject } from 'rxjs';
|
import { fromEvent, merge, Observable, Subject } from 'rxjs';
|
||||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||||
import { LoadingService } from '../loading';
|
import { LoadingService } from '../loading';
|
||||||
import { delay, filter, map } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
|
||||||
import { NavigationStart, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { shareLast } from '../utils';
|
import { shareLast } from '../utils';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
export class CustomError {
|
export class CustomError {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
@ -33,47 +34,83 @@ const OFFLINE_STATUSES = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const isOffline = (error?: ErrorType) => error instanceof HttpErrorResponse && OFFLINE_STATUSES.includes(error.status);
|
const isOffline = (error?: ErrorType) => error instanceof HttpErrorResponse && OFFLINE_STATUSES.includes(error.status);
|
||||||
|
const isSameEventType = (previous: Event | string | undefined, current: Event | string | undefined) =>
|
||||||
|
previous instanceof Event && current instanceof Event ? previous.type === current.type : false;
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ErrorService {
|
export class ErrorService {
|
||||||
readonly offline$: Observable<Event>;
|
readonly offline$: Observable<Event>;
|
||||||
readonly online$: Observable<Event>;
|
readonly online$: Observable<Event>;
|
||||||
readonly connectionStatus$: Observable<string | undefined>;
|
readonly connectionStatus$: Observable<string | undefined>;
|
||||||
private readonly _error$ = new Subject<ErrorType>();
|
readonly #error$ = new Subject<ErrorType>();
|
||||||
readonly error$ = this._error$.pipe(filter(error => !error || !isOffline(error)));
|
readonly error$ = this.#error$.pipe(filter(error => !error || !isOffline(error)));
|
||||||
private readonly _online$ = new Subject();
|
readonly #online$ = new Subject();
|
||||||
|
readonly #loadingService = inject(LoadingService);
|
||||||
|
readonly #router = inject(Router);
|
||||||
|
readonly #displayNotification$ = new Subject<string | undefined>();
|
||||||
|
#notificationTimeout: Record<string, NodeJS.Timeout | undefined> = {};
|
||||||
|
#displayedNotificationType: string | undefined;
|
||||||
|
|
||||||
constructor(private readonly _loadingService: LoadingService, private readonly _router: Router) {
|
constructor() {
|
||||||
_router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe(() => {
|
this.#router.events
|
||||||
this.clear();
|
.pipe(
|
||||||
});
|
filter(event => event instanceof NavigationStart),
|
||||||
this.offline$ = this._offline();
|
tap(() => this.clear()),
|
||||||
this.online$ = this._online();
|
takeUntilDestroyed(),
|
||||||
const removeIndicator$ = this.online$.pipe(
|
)
|
||||||
delay(3000),
|
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||||
map(() => undefined),
|
.subscribe();
|
||||||
|
|
||||||
|
this.offline$ = this.#offline();
|
||||||
|
this.online$ = this.#online();
|
||||||
|
|
||||||
|
this.connectionStatus$ = merge(this.online$, this.offline$, this.#displayNotification$).pipe(
|
||||||
|
distinctUntilChanged(isSameEventType),
|
||||||
|
filter(value => {
|
||||||
|
if (!(value instanceof Event)) return true;
|
||||||
|
this.#clearNotificationTimeouts();
|
||||||
|
this.#setNotificationTimeout(value.type);
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
map(value => value as string | undefined),
|
||||||
|
tap(value => (this.#displayedNotificationType = value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.connectionStatus$ = merge(this.online$, this.offline$, removeIndicator$).pipe(map(event => event?.type));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(error: ErrorType): void {
|
set(error: ErrorType): void {
|
||||||
this._loadingService.stop();
|
this.#loadingService.stop();
|
||||||
this._error$.next(error);
|
this.#error$.next(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnline(): void {
|
setOnline(): void {
|
||||||
this._online$.next(true);
|
this.#online$.next(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this._error$.next(undefined);
|
this.#error$.next(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _offline() {
|
#clearNotificationTimeouts() {
|
||||||
|
Object.keys(this.#notificationTimeout).forEach(key => {
|
||||||
|
clearTimeout(this.#notificationTimeout[key]);
|
||||||
|
this.#notificationTimeout[key] = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#setNotificationTimeout(status: string) {
|
||||||
|
if (status === 'online' && this.#displayedNotificationType !== 'offline') return;
|
||||||
|
this.#notificationTimeout[status] ??= setTimeout(() => {
|
||||||
|
this.#displayNotification$.next(status);
|
||||||
|
if (status === 'online') {
|
||||||
|
setTimeout(() => this.#displayNotification$.next(undefined), 3000);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#offline() {
|
||||||
return merge(
|
return merge(
|
||||||
fromEvent(window, 'offline'),
|
fromEvent(window, 'offline'),
|
||||||
this._error$.pipe(
|
this.#error$.pipe(
|
||||||
filter(isOffline),
|
filter(isOffline),
|
||||||
map(() => new Event('offline')),
|
map(() => new Event('offline')),
|
||||||
shareLast(),
|
shareLast(),
|
||||||
@ -81,7 +118,9 @@ export class ErrorService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _online() {
|
#online() {
|
||||||
return merge(fromEvent(window, 'online'), this._online$.pipe(map(() => new Event('online')))).pipe(shareLast());
|
return merge(fromEvent(window, 'online'), this.#online$.pipe(map(v => (v instanceof Event ? v : new Event('online'))))).pipe(
|
||||||
|
shareLast(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
<ng-container *ngIf="errorService.error$ | async as error">
|
@if (errorService.error$ | async; as error) {
|
||||||
<section class="full-page-section"></section>
|
<section class="full-page-section"></section>
|
||||||
|
|
||||||
<section class="full-page-content flex-align-items-center">
|
<section class="full-page-content flex-align-items-center">
|
||||||
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
||||||
|
|
||||||
<div [translate]="errorTitle(error)" class="heading-l mt-24"></div>
|
<div [translate]="errorTitle(error)" class="heading-l mt-24"></div>
|
||||||
|
@if (error.message) {
|
||||||
<div *ngIf="error.message" class="mt-16 error">{{ error.message }}</div>
|
<div class="mt-16 error">{{ error.message }}</div>
|
||||||
|
}
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="action(error)"
|
(action)="action(error)"
|
||||||
[icon]="actionIcon(error)"
|
[icon]="actionIcon(error)"
|
||||||
@ -16,4 +14,4 @@
|
|||||||
class="mt-20"
|
class="mt-20"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
</section>
|
</section>
|
||||||
</ng-container>
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { IconButtonTypes } from '../../buttons';
|
import { IconButtonTypes } from '../../buttons';
|
||||||
import { CustomError, ErrorService, ErrorType } from '../error.service';
|
import { CustomError, ErrorService, ErrorType } from '../error.service';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-full-page-error',
|
selector: 'iqser-full-page-error',
|
||||||
templateUrl: './full-page-error.component.html',
|
templateUrl: './full-page-error.component.html',
|
||||||
styleUrls: ['./full-page-error.component.scss'],
|
styleUrls: ['./full-page-error.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class FullPageErrorComponent {
|
export class FullPageErrorComponent {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
protected readonly errorService = inject(ErrorService);
|
||||||
constructor(readonly errorService: ErrorService) {}
|
|
||||||
|
|
||||||
errorTitle(error: ErrorType): string {
|
errorTitle(error: ErrorType): string {
|
||||||
return error instanceof CustomError ? error.label : _('error.title');
|
return error instanceof CustomError ? error.label : _('error.title');
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpStatusCode } from '@angular/common/http';
|
import {
|
||||||
|
HttpErrorResponse,
|
||||||
|
HttpEvent,
|
||||||
|
HttpHandler,
|
||||||
|
HttpInterceptor,
|
||||||
|
HttpRequest,
|
||||||
|
HttpResponse,
|
||||||
|
HttpStatusCode,
|
||||||
|
} from '@angular/common/http';
|
||||||
import { Inject, Injectable, Optional } from '@angular/core';
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
import { MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs';
|
import { finalize, MonoTypeOperatorFunction, Observable, retry, throwError, timer } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens';
|
import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens';
|
||||||
import { ErrorService } from './error.service';
|
import { ErrorService } from './error.service';
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakStatusService } from '../tenants';
|
||||||
import { IqserConfigService } from '../services';
|
import { LoadingService } from '../loading';
|
||||||
import { KeycloakStatusService } from '../users';
|
|
||||||
|
|
||||||
function updateSeconds(seconds: number) {
|
function updateSeconds(seconds: number) {
|
||||||
if (seconds === 0 || seconds === 1) {
|
if (seconds === 0 || seconds === 1) {
|
||||||
@ -48,8 +55,7 @@ export class ServerErrorInterceptor implements HttpInterceptor {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _errorService: ErrorService,
|
private readonly _errorService: ErrorService,
|
||||||
private readonly _keycloakService: KeycloakService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _configService: IqserConfigService,
|
|
||||||
private readonly _keycloakStatusService: KeycloakStatusService,
|
private readonly _keycloakStatusService: KeycloakStatusService,
|
||||||
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number,
|
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number,
|
||||||
@Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[],
|
@Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[],
|
||||||
@ -57,7 +63,14 @@ export class ServerErrorInterceptor implements HttpInterceptor {
|
|||||||
|
|
||||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
return next.handle(req).pipe(
|
return next.handle(req).pipe(
|
||||||
catchError((error: HttpErrorResponse) => {
|
tap(event => {
|
||||||
|
if (event instanceof HttpResponse && this._urlsWithError.has(req.url)) {
|
||||||
|
this._errorService.setOnline();
|
||||||
|
this._urlsWithError.delete(req.url);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError((err: unknown) => {
|
||||||
|
const error = err as HttpErrorResponse;
|
||||||
// token expired
|
// token expired
|
||||||
if (error.status === HttpStatusCode.Unauthorized) {
|
if (error.status === HttpStatusCode.Unauthorized) {
|
||||||
this._keycloakStatusService.createLoginUrlAndExecute();
|
this._keycloakStatusService.createLoginUrlAndExecute();
|
||||||
@ -69,15 +82,13 @@ export class ServerErrorInterceptor implements HttpInterceptor {
|
|||||||
this._urlsWithError.add(req.url);
|
this._urlsWithError.add(req.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return throwError(() => error);
|
return throwError(() => error).pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this._loadingService.stop();
|
||||||
|
}),
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
backoffOnServerError(this._maxRetries, this._skippedPaths),
|
backoffOnServerError(this._maxRetries, this._skippedPaths),
|
||||||
tap(() => {
|
|
||||||
if (this._urlsWithError.has(req.url)) {
|
|
||||||
this._errorService.setOnline();
|
|
||||||
this._urlsWithError.delete(req.url);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +1,51 @@
|
|||||||
<ng-container *ngIf="primaryFilterGroup$ | async as primaryGroup">
|
@if (primaryFilterGroup$ | async; as primaryGroup) {
|
||||||
<iqser-input-with-action
|
@if (primaryGroup.filterceptionPlaceholder) {
|
||||||
*ngIf="primaryGroup.filterceptionPlaceholder"
|
<div class="input-wrapper">
|
||||||
[(value)]="searchService.searchValue"
|
<iqser-input-with-action
|
||||||
[id]="'filterception-' + primaryGroup.slug"
|
[(value)]="searchService.searchValue"
|
||||||
[placeholder]="primaryGroup.filterceptionPlaceholder"
|
[id]="'filterception-' + primaryGroup.slug"
|
||||||
[width]="'full'"
|
[placeholder]="primaryGroup.filterceptionPlaceholder"
|
||||||
></iqser-input-with-action>
|
[width]="'full'"
|
||||||
|
></iqser-input-with-action>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container *ngTemplateOutlet="filterHeader"></ng-container>
|
<ng-container *ngTemplateOutlet="filterHeader"></ng-container>
|
||||||
|
|
||||||
<div *ngIf="primaryFilters$ | async as filters" class="filter-content">
|
@if (primaryFilters$ | async; as filters) {
|
||||||
<ng-container
|
<div class="filter-content">
|
||||||
*ngFor="let filter of filters"
|
@for (filter of filters; track filter.id) {
|
||||||
[ngTemplateOutletContext]="{
|
<ng-container
|
||||||
filter: filter,
|
[ngTemplateOutletContext]="{
|
||||||
filterGroup: primaryGroup,
|
filter: filter,
|
||||||
atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async
|
filterGroup: primaryGroup,
|
||||||
}"
|
atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async,
|
||||||
[ngTemplateOutlet]="defaultFilterTemplate"
|
}"
|
||||||
></ng-container>
|
[ngTemplateOutlet]="defaultFilterTemplate"
|
||||||
</div>
|
></ng-container>
|
||||||
|
}
|
||||||
<div *ngIf="secondaryFilterGroup$ | async as secondaryGroup" class="filter-options">
|
|
||||||
<div class="filter-menu-options">
|
|
||||||
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container
|
@if (secondaryFilterGroup$ | async; as secondaryGroup) {
|
||||||
*ngFor="let filter of secondaryGroup.filters"
|
<div class="filter-options">
|
||||||
[ngTemplateOutletContext]="{
|
<div class="filter-menu-options">
|
||||||
filter: filter,
|
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
|
||||||
filterGroup: secondaryGroup,
|
</div>
|
||||||
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async
|
|
||||||
}"
|
@for (filter of secondaryGroup.filters; track filter.id) {
|
||||||
[ngTemplateOutlet]="defaultFilterTemplate"
|
<ng-container
|
||||||
></ng-container>
|
[ngTemplateOutletContext]="{
|
||||||
</div>
|
filter: filter,
|
||||||
</ng-container>
|
filterGroup: secondaryGroup,
|
||||||
|
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async,
|
||||||
|
}"
|
||||||
|
[ngTemplateOutlet]="defaultFilterTemplate"
|
||||||
|
></ng-container>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<ng-template #defaultFilterLabelTemplate let-filter="filter">
|
<ng-template #defaultFilterLabelTemplate let-filter="filter">
|
||||||
{{ filter?.label }}
|
{{ filter?.label }}
|
||||||
@ -44,30 +53,51 @@
|
|||||||
|
|
||||||
<!--TODO: move to separate component-->
|
<!--TODO: move to separate component-->
|
||||||
<ng-template #filterHeader>
|
<ng-template #filterHeader>
|
||||||
<div *ngIf="primaryFilterGroup$ | async as primaryGroup" class="filter-menu-header">
|
@if (primaryFilterGroup$ | async; as primaryGroup) {
|
||||||
<div [translateParams]="{ count: primaryGroup.filters.length }" [translate]="primaryFiltersLabel" class="all-caps-label"></div>
|
<div class="filter-menu-header">
|
||||||
<div class="actions">
|
|
||||||
<div
|
<div
|
||||||
(click)="activatePrimaryFilters()"
|
[translateParams]="{ count: primaryGroup.filters.length }"
|
||||||
*ngIf="!primaryGroup.singleSelect"
|
[translate]="primaryFiltersLabel()"
|
||||||
class="all-caps-label primary pointer"
|
class="all-caps-label"
|
||||||
stopPropagation
|
|
||||||
translate="actions.all"
|
|
||||||
></div>
|
></div>
|
||||||
<div (click)="deactivateFilters()" class="all-caps-label primary pointer" stopPropagation translate="actions.none"></div>
|
<div class="actions">
|
||||||
|
@if (!primaryGroup.singleSelect) {
|
||||||
|
<div
|
||||||
|
(click)="activatePrimaryFilters()"
|
||||||
|
class="all-caps-label primary pointer"
|
||||||
|
iqserStopPropagation
|
||||||
|
translate="actions.all"
|
||||||
|
></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div
|
||||||
|
(click)="deactivatePrimaryFilters()"
|
||||||
|
class="all-caps-label primary pointer"
|
||||||
|
iqserStopPropagation
|
||||||
|
translate="actions.none"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!--TODO: move to separate component-->
|
<!--TODO: move to separate component-->
|
||||||
<ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter" let-filterGroup="filterGroup">
|
<ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter" let-filterGroup="filterGroup">
|
||||||
<div (click)="toggleFilterExpanded(filter)" class="mat-menu-item flex" stopPropagation>
|
<div (click)="toggleFilterExpanded(filter)" class="mat-mdc-menu-item flex" iqserStopPropagation>
|
||||||
<div *ngIf="filter.children?.length > 0" class="arrow-wrapper">
|
@if (filter.children?.length > 0) {
|
||||||
<mat-icon *ngIf="filter.expanded" color="accent" svgIcon="iqser:arrow-down"></mat-icon>
|
<div class="arrow-wrapper">
|
||||||
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="iqser:arrow-right"></mat-icon>
|
@if (filter.expanded) {
|
||||||
</div>
|
<mat-icon color="accent" svgIcon="iqser:arrow-down"></mat-icon>
|
||||||
|
}
|
||||||
|
@if (!filter.expanded) {
|
||||||
|
<mat-icon color="accent" svgIcon="iqser:arrow-right"></mat-icon>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div *ngIf="atLeastOneIsExpandable && filter.children?.length === 0" class="arrow-wrapper spacer"> </div>
|
@if (atLeastOneIsExpandable && filter.children?.length === 0) {
|
||||||
|
<div class="arrow-wrapper spacer"> </div>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
(click)="filterCheckboxClicked(filter, filterGroup)"
|
(click)="filterCheckboxClicked(filter, filterGroup)"
|
||||||
@ -75,7 +105,7 @@
|
|||||||
[id]="'filter-checkbox-' + (filter.id ? filter.id.replaceAll(' ', '-').replaceAll('.', '-') : 'none')"
|
[id]="'filter-checkbox-' + (filter.id ? filter.id.replaceAll(' ', '-').replaceAll('.', '-') : 'none')"
|
||||||
[indeterminate]="filter.indeterminate"
|
[indeterminate]="filter.indeterminate"
|
||||||
class="filter-menu-checkbox"
|
class="filter-menu-checkbox"
|
||||||
stopPropagation
|
iqserStopPropagation
|
||||||
>
|
>
|
||||||
<ng-container
|
<ng-container
|
||||||
[ngTemplateOutletContext]="{ filter: filter }"
|
[ngTemplateOutletContext]="{ filter: filter }"
|
||||||
@ -83,19 +113,28 @@
|
|||||||
></ng-container>
|
></ng-container>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|
||||||
<ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate"></ng-container>
|
<ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate()"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="filter.children?.length && filter.expanded">
|
@if (filter.children?.length && filter.expanded) {
|
||||||
<div *ngFor="let child of filter.children" class="padding-left mat-menu-item" stopPropagation>
|
<div>
|
||||||
<mat-checkbox (click)="filterCheckboxClicked(child, filterGroup, filter)" [checked]="child.checked" stopPropagation>
|
@for (child of filter.children; track child) {
|
||||||
<ng-container
|
@if (!child.hidden) {
|
||||||
[ngTemplateOutletContext]="{ filter: child }"
|
<div class="padding-left mat-mdc-menu-item" iqserStopPropagation>
|
||||||
[ngTemplateOutlet]="filterGroup.filterTemplate ?? defaultFilterLabelTemplate"
|
<mat-checkbox
|
||||||
></ng-container>
|
(click)="filterCheckboxClicked(child, filterGroup, filter)"
|
||||||
</mat-checkbox>
|
[checked]="child.checked"
|
||||||
|
iqserStopPropagation
|
||||||
<ng-container [ngTemplateOutletContext]="{ filter: child }" [ngTemplateOutlet]="actionsTemplate"></ng-container>
|
>
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutletContext]="{ filter: child }"
|
||||||
|
[ngTemplateOutlet]="filterGroup.filterTemplate ?? defaultFilterLabelTemplate"
|
||||||
|
></ng-container>
|
||||||
|
</mat-checkbox>
|
||||||
|
<ng-container [ngTemplateOutletContext]="{ filter: child }" [ngTemplateOutlet]="actionsTemplate()"></ng-container>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
iqser-input-with-action {
|
.input-wrapper {
|
||||||
padding: 0 8px 8px 8px;
|
padding: 0 8px 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, TemplateRef } from '@angular/core';
|
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, effect, ElementRef, inject, input, numberAttribute, OnInit, TemplateRef } from '@angular/core';
|
||||||
|
import { MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { combineLatest, Observable, pipe } from 'rxjs';
|
import { combineLatest, Observable, pipe } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { StopPropagationDirective } from '../../directives/stop-propagation.directive';
|
||||||
|
import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component';
|
||||||
|
import { SearchService } from '../../search/search.service';
|
||||||
|
import { shareDistinctLast, shareLast } from '../../utils/operators';
|
||||||
|
import { FilterService } from '../filter.service';
|
||||||
|
import { Filter } from '../models/filter';
|
||||||
|
import { IFilterGroup } from '../models/filter-group.model';
|
||||||
import { IFilter } from '../models/filter.model';
|
import { IFilter } from '../models/filter.model';
|
||||||
import { INestedFilter } from '../models/nested-filter.model';
|
import { INestedFilter } from '../models/nested-filter.model';
|
||||||
import { IFilterGroup } from '../models/filter-group.model';
|
|
||||||
import { handleCheckedValue } from '../filter-utils';
|
|
||||||
import { FilterService } from '../filter.service';
|
|
||||||
import { SearchService } from '../../search';
|
|
||||||
import { Filter } from '../models/filter';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { shareDistinctLast, shareLast } from '../../utils';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { MAT_LEGACY_CHECKBOX_DEFAULT_OPTIONS as MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/legacy-checkbox';
|
|
||||||
|
|
||||||
const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
|
const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
|
||||||
const atLeastOneIsExpandable = pipe(
|
const atLeastOneIsExpandable = pipe(
|
||||||
@ -19,7 +23,7 @@ const atLeastOneIsExpandable = pipe(
|
|||||||
);
|
);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-filter-card [primaryFiltersSlug]',
|
selector: 'iqser-filter-card',
|
||||||
templateUrl: './filter-card.component.html',
|
templateUrl: './filter-card.component.html',
|
||||||
styleUrls: ['./filter-card.component.scss'],
|
styleUrls: ['./filter-card.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@ -33,26 +37,29 @@ const atLeastOneIsExpandable = pipe(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
imports: [AsyncPipe, InputWithActionComponent, NgTemplateOutlet, TranslateModule, MatIcon, MatCheckbox, StopPropagationDirective],
|
||||||
})
|
})
|
||||||
export class FilterCardComponent implements OnInit {
|
export class FilterCardComponent implements OnInit {
|
||||||
@Input() primaryFiltersSlug!: string;
|
readonly #filterService = inject(FilterService);
|
||||||
@Input() actionsTemplate?: TemplateRef<unknown>;
|
readonly #elementRef = inject(ElementRef);
|
||||||
@Input() secondaryFiltersSlug = '';
|
protected readonly searchService = inject<SearchService<Filter>>(SearchService);
|
||||||
@Input() primaryFiltersLabel: string = _('filter-menu.filter-types');
|
readonly primaryFiltersSlug = input.required<string>();
|
||||||
@Input() minWidth = 350;
|
readonly fileId = input<string>();
|
||||||
|
readonly actionsTemplate = input<TemplateRef<unknown>>();
|
||||||
|
readonly secondaryFiltersSlug = input('');
|
||||||
|
readonly primaryFiltersLabel = input<string>(_('filter-menu.filter-types'));
|
||||||
|
readonly minWidth = input(350, { transform: numberAttribute });
|
||||||
primaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
primaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
||||||
secondaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
secondaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
||||||
primaryFilters$!: Observable<IFilter[] | undefined>;
|
primaryFilters$!: Observable<IFilter[] | undefined>;
|
||||||
|
|
||||||
atLeastOneFilterIsExpandable$?: Observable<boolean>;
|
atLeastOneFilterIsExpandable$?: Observable<boolean>;
|
||||||
atLeastOneSecondaryFilterIsExpandable$?: Observable<boolean>;
|
atLeastOneSecondaryFilterIsExpandable$?: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor() {
|
||||||
readonly filterService: FilterService,
|
effect(() => {
|
||||||
readonly searchService: SearchService<Filter>,
|
(this.#elementRef.nativeElement as HTMLElement).style.setProperty('--filter-card-min-width', `${this.minWidth()}px`);
|
||||||
private readonly _elementRef: ElementRef,
|
});
|
||||||
) {}
|
}
|
||||||
|
|
||||||
private get _primaryFilters$(): Observable<IFilter[]> {
|
private get _primaryFilters$(): Observable<IFilter[]> {
|
||||||
return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
|
return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
|
||||||
@ -62,69 +69,37 @@ export class FilterCardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug).pipe(shareLast());
|
this.primaryFilterGroup$ = this.#filterService.getGroup$(this.primaryFiltersSlug()).pipe(shareLast());
|
||||||
this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug).pipe(shareLast());
|
this.secondaryFilterGroup$ = this.#filterService.getGroup$(this.secondaryFiltersSlug()).pipe(shareLast());
|
||||||
this.primaryFilters$ = this._primaryFilters$;
|
this.primaryFilters$ = this._primaryFilters$;
|
||||||
|
|
||||||
this.atLeastOneFilterIsExpandable$ = atLeastOneIsExpandable(this.primaryFilterGroup$);
|
this.atLeastOneFilterIsExpandable$ = atLeastOneIsExpandable(this.primaryFilterGroup$);
|
||||||
this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
|
this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
|
||||||
|
|
||||||
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--filter-card-min-width', `${this.minWidth}px`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterCheckboxClicked(nestedFilter: INestedFilter, filterGroup: IFilterGroup, parent?: INestedFilter): void {
|
filterCheckboxClicked(nestedFilter: INestedFilter, filterGroup: IFilterGroup, parent?: INestedFilter): void {
|
||||||
if (filterGroup.singleSelect) {
|
this.#filterService.filterCheckboxClicked({
|
||||||
this.deactivateFilters(nestedFilter.id);
|
nestedFilter,
|
||||||
}
|
filterGroup,
|
||||||
|
parent,
|
||||||
|
primaryFiltersSlug: this.primaryFiltersSlug(),
|
||||||
|
});
|
||||||
|
this.#filterService.updateFiltersInLocalStorage(this.fileId());
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
deactivatePrimaryFilters() {
|
||||||
nestedFilter.checked = !nestedFilter.checked;
|
this.#filterService.deactivateFilters({ primaryFiltersSlug: this.primaryFiltersSlug() });
|
||||||
|
this.#filterService.updateFiltersInLocalStorage(this.fileId());
|
||||||
if (parent) {
|
|
||||||
handleCheckedValue(parent);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
if (nestedFilter.indeterminate) {
|
|
||||||
nestedFilter.checked = false;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
nestedFilter.indeterminate = false;
|
|
||||||
// eslint-disable-next-line no-return-assign,no-param-reassign
|
|
||||||
nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filterService.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activatePrimaryFilters(): void {
|
activatePrimaryFilters(): void {
|
||||||
this._setFilters(this.primaryFiltersSlug, true);
|
this.#filterService.setFilters(this.primaryFiltersSlug(), true);
|
||||||
}
|
this.#filterService.updateFiltersInLocalStorage(this.fileId());
|
||||||
|
|
||||||
deactivateFilters(exceptedFilterId?: string): void {
|
|
||||||
this._setFilters(this.primaryFiltersSlug, false, exceptedFilterId);
|
|
||||||
if (this.secondaryFiltersSlug) {
|
|
||||||
this._setFilters(this.secondaryFiltersSlug, false, exceptedFilterId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilterExpanded(nestedFilter: INestedFilter): void {
|
toggleFilterExpanded(nestedFilter: INestedFilter): void {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
nestedFilter.expanded = !nestedFilter.expanded;
|
nestedFilter.expanded = !nestedFilter.expanded;
|
||||||
this.filterService.refresh();
|
this.#filterService.refresh();
|
||||||
}
|
|
||||||
|
|
||||||
private _setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) {
|
|
||||||
const filters = this.filterService.getGroup(filterGroup)?.filters;
|
|
||||||
filters?.forEach(f => {
|
|
||||||
if (f.id !== exceptedFilterId) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
f.checked = checked;
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
f.indeterminate = false;
|
|
||||||
// eslint-disable-next-line no-return-assign,no-param-reassign
|
|
||||||
f.children?.forEach(ff => (ff.checked = checked));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.filterService.refresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { INestedFilter } from './models/nested-filter.model';
|
import { IListable } from '../listing/models/listable';
|
||||||
|
import { Id } from '../listing/models/trackable';
|
||||||
import { IFilterGroup } from './models/filter-group.model';
|
import { IFilterGroup } from './models/filter-group.model';
|
||||||
import { IFilter } from './models/filter.model';
|
import { IFilter } from './models/filter.model';
|
||||||
|
import { LocalStorageFilter } from './models/local-filters.model';
|
||||||
import { NestedFilter } from './models/nested-filter';
|
import { NestedFilter } from './models/nested-filter';
|
||||||
import { Id, IListable } from '../listing';
|
import { INestedFilter } from './models/nested-filter.model';
|
||||||
|
|
||||||
function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[]) {
|
function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[]) {
|
||||||
if (!oldFilters || !newFilters) {
|
if (!oldFilters || !newFilters) {
|
||||||
@ -14,6 +16,7 @@ function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[])
|
|||||||
const newFilter = newFilters.find(f => f.id === filter.id);
|
const newFilter = newFilters.find(f => f.id === filter.id);
|
||||||
if (newFilter) {
|
if (newFilter) {
|
||||||
newFilter.checked = filter.checked;
|
newFilter.checked = filter.checked;
|
||||||
|
newFilter.expanded = filter.expanded;
|
||||||
newFilter.indeterminate = filter.indeterminate;
|
newFilter.indeterminate = filter.indeterminate;
|
||||||
if (filter.children && newFilter.children) {
|
if (filter.children && newFilter.children) {
|
||||||
copySettings(filter.children, newFilter.children);
|
copySettings(filter.children, newFilter.children);
|
||||||
@ -102,3 +105,25 @@ export function flatChildren(filters: INestedFilter[]): IFilter[] {
|
|||||||
export function toFlatFilters(groups: IFilterGroup[], condition = (filters: IFilter[]) => filters): IFilter[] {
|
export function toFlatFilters(groups: IFilterGroup[], condition = (filters: IFilter[]) => filters): IFilter[] {
|
||||||
return groups.reduce((acc: IFilter[], f) => [...acc, ...condition(f.filters), ...condition(flatChildren(f.filters))], []);
|
return groups.reduce((acc: IFilter[], f) => [...acc, ...condition(f.filters), ...condition(flatChildren(f.filters))], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractFilterValues(filters: INestedFilter[] | undefined): LocalStorageFilter[] | null {
|
||||||
|
const extractedValues: LocalStorageFilter[] = [];
|
||||||
|
filters?.forEach(filter => {
|
||||||
|
extractedValues.push({
|
||||||
|
id: filter.id,
|
||||||
|
checked: filter.checked ?? false,
|
||||||
|
children: extractFilterValues(filter.children),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return extractedValues.length ? extractedValues : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyLocalStorageFiltersValues(primaryFilters: INestedFilter[], localStorageFilters: LocalStorageFilter[]) {
|
||||||
|
primaryFilters?.forEach(filter => {
|
||||||
|
const localStorageFilter = localStorageFilters.find(f => f.id === filter.id);
|
||||||
|
filter.checked = localStorageFilter?.checked;
|
||||||
|
if (filter.children && localStorageFilter?.children) {
|
||||||
|
copyLocalStorageFiltersValues(filter.children, localStorageFilter.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,22 +1,39 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { processFilters, toFlatFilters } from './filter-utils';
|
|
||||||
import { IFilterGroup } from './models/filter-group.model';
|
|
||||||
import { INestedFilter } from './models/nested-filter.model';
|
|
||||||
import { get, shareDistinctLast, shareLast, some } from '../utils';
|
import { get, shareDistinctLast, shareLast, some } from '../utils';
|
||||||
import { NestedFilter } from './models/nested-filter';
|
import { extractFilterValues, handleCheckedValue, processFilters, toFlatFilters } from './filter-utils';
|
||||||
import { Filter } from './models/filter';
|
import { Filter } from './models/filter';
|
||||||
|
import { IFilterGroup } from './models/filter-group.model';
|
||||||
import { IFilter } from './models/filter.model';
|
import { IFilter } from './models/filter.model';
|
||||||
|
import { LocalStorageFilters } from './models/local-filters.model';
|
||||||
|
import { NestedFilter } from './models/nested-filter';
|
||||||
|
import { INestedFilter } from './models/nested-filter.model';
|
||||||
|
|
||||||
|
export interface CheckboxClickedParams {
|
||||||
|
nestedFilter: INestedFilter;
|
||||||
|
filterGroup: IFilterGroup;
|
||||||
|
parent?: INestedFilter;
|
||||||
|
primaryFiltersSlug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeactivateFiltersParams {
|
||||||
|
primaryFiltersSlug: string;
|
||||||
|
secondaryFiltersSlug?: string;
|
||||||
|
exceptedFilterId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIMARY_FILTERS = 'primaryFilters';
|
||||||
|
const SECONDARY_FILTERS = 'secondaryFilters';
|
||||||
|
const WORKLOAD_FILTERS_KEY = 'workload-filters';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilterService {
|
export class FilterService {
|
||||||
readonly showResetFilters$: Observable<boolean>;
|
|
||||||
readonly filterGroups$: Observable<IFilterGroup[]>;
|
|
||||||
|
|
||||||
readonly #singleFilters = new Map<string, BehaviorSubject<IFilter | undefined>>();
|
readonly #singleFilters = new Map<string, BehaviorSubject<IFilter | undefined>>();
|
||||||
readonly #filterGroups$ = new BehaviorSubject<IFilterGroup[]>([]);
|
readonly #filterGroups$ = new BehaviorSubject<IFilterGroup[]>([]);
|
||||||
readonly #refresh$ = new Subject();
|
readonly #refresh$ = new Subject();
|
||||||
|
readonly showResetFilters$: Observable<boolean>;
|
||||||
|
readonly filterGroups$: Observable<IFilterGroup[]>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.filterGroups$ = this.#refresh$.pipe(
|
this.filterGroups$ = this.#refresh$.pipe(
|
||||||
@ -197,4 +214,70 @@ export class FilterService {
|
|||||||
this.addSingleFilter(filter);
|
this.addSingleFilter(filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) {
|
||||||
|
const filters = this.getGroup(filterGroup)?.filters;
|
||||||
|
filters?.forEach(f => {
|
||||||
|
if (f.id !== exceptedFilterId) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
f.checked = checked;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
f.indeterminate = false;
|
||||||
|
// eslint-disable-next-line no-return-assign,no-param-reassign
|
||||||
|
f.children?.forEach(ff => (ff.checked = checked));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateFilters(params: DeactivateFiltersParams) {
|
||||||
|
const { primaryFiltersSlug, secondaryFiltersSlug, exceptedFilterId } = params;
|
||||||
|
this.setFilters(primaryFiltersSlug, false, exceptedFilterId);
|
||||||
|
if (secondaryFiltersSlug) {
|
||||||
|
this.setFilters(secondaryFiltersSlug, false, exceptedFilterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterCheckboxClicked(params: CheckboxClickedParams) {
|
||||||
|
const { filterGroup, nestedFilter, parent, primaryFiltersSlug } = params;
|
||||||
|
|
||||||
|
if (filterGroup.singleSelect) {
|
||||||
|
this.deactivateFilters({ primaryFiltersSlug, exceptedFilterId: nestedFilter.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
nestedFilter.checked = !nestedFilter.checked;
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
handleCheckedValue(parent);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
if (nestedFilter.indeterminate) {
|
||||||
|
nestedFilter.checked = false;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
nestedFilter.indeterminate = false;
|
||||||
|
// eslint-disable-next-line no-return-assign,no-param-reassign
|
||||||
|
nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFiltersInLocalStorage(fileId?: string) {
|
||||||
|
if (fileId) {
|
||||||
|
const primaryFilters = this.getGroup(PRIMARY_FILTERS);
|
||||||
|
const secondaryFilters = this.getGroup(SECONDARY_FILTERS);
|
||||||
|
|
||||||
|
const filters: LocalStorageFilters = {
|
||||||
|
primaryFilters: extractFilterValues(primaryFilters?.filters),
|
||||||
|
secondaryFilters: extractFilterValues(secondaryFilters?.filters),
|
||||||
|
};
|
||||||
|
|
||||||
|
const workloadFiltersString = localStorage.getItem(WORKLOAD_FILTERS_KEY) ?? '{}';
|
||||||
|
const workloadFilters = JSON.parse(workloadFiltersString);
|
||||||
|
workloadFilters[fileId] = filters;
|
||||||
|
localStorage.setItem(WORKLOAD_FILTERS_KEY, JSON.stringify(workloadFilters));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,27 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
|
import { NgModule } from '@angular/core';
|
||||||
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { ChevronButtonComponent, IconButtonComponent } from '../buttons';
|
|
||||||
import { PopupFilterComponent } from './popup-filter/popup-filter.component';
|
|
||||||
import { QuickFiltersComponent } from './quick-filters/quick-filters.component';
|
|
||||||
import { IqserHelpModeModule } from '../help-mode';
|
|
||||||
import { SingleFilterComponent } from './single-filter/single-filter.component';
|
|
||||||
import { FilterCardComponent } from './filter-card/filter-card.component';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { PreventDefaultDirective, StopPropagationDirective } from '../directives';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { InputWithActionComponent } from '../inputs';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChevronButtonComponent } from '../buttons/chevron-button/chevron-button.component';
|
||||||
|
import { IconButtonComponent } from '../buttons/icon-button/icon-button.component';
|
||||||
|
import { PreventDefaultDirective } from '../directives/prevent-default.directive';
|
||||||
|
import { StopPropagationDirective } from '../directives/stop-propagation.directive';
|
||||||
|
import { InputWithActionComponent } from '../inputs/input-with-action/input-with-action.component';
|
||||||
|
import { QuickFiltersComponent } from './quick-filters/quick-filters.component';
|
||||||
|
import { SingleFilterComponent } from './single-filter/single-filter.component';
|
||||||
|
|
||||||
const matModules = [MatCheckboxModule, MatMenuModule];
|
const components = [QuickFiltersComponent, SingleFilterComponent];
|
||||||
const components = [QuickFiltersComponent, PopupFilterComponent, SingleFilterComponent, FilterCardComponent];
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components],
|
declarations: [...components],
|
||||||
exports: [...components],
|
exports: [...components],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
...matModules,
|
MatCheckboxModule,
|
||||||
|
MatMenuModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
IqserHelpModeModule,
|
|
||||||
IconButtonComponent,
|
IconButtonComponent,
|
||||||
ChevronButtonComponent,
|
ChevronButtonComponent,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
|
|||||||
@ -11,3 +11,4 @@ export * from './models/nested-filter.model';
|
|||||||
|
|
||||||
export * from './popup-filter/popup-filter.component';
|
export * from './popup-filter/popup-filter.component';
|
||||||
export * from './quick-filters/quick-filters.component';
|
export * from './quick-filters/quick-filters.component';
|
||||||
|
export * from './simple-popup-filter/simple-popup-filter.component';
|
||||||
|
|||||||
@ -10,5 +10,6 @@ export interface IFilter {
|
|||||||
readonly required?: boolean;
|
readonly required?: boolean;
|
||||||
readonly disabled?: boolean;
|
readonly disabled?: boolean;
|
||||||
readonly helpModeKey?: string;
|
readonly helpModeKey?: string;
|
||||||
|
readonly hidden?: boolean;
|
||||||
readonly metadata?: Record<string, any>;
|
readonly metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import { IListable } from '../../listing/models/listable';
|
||||||
import { IFilter } from './filter.model';
|
import { IFilter } from './filter.model';
|
||||||
import { IListable } from '../../listing';
|
|
||||||
|
|
||||||
export class Filter implements IFilter, IListable {
|
export class Filter implements IFilter, IListable {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@ -10,6 +10,7 @@ export class Filter implements IFilter, IListable {
|
|||||||
readonly checker?: (obj?: unknown) => boolean;
|
readonly checker?: (obj?: unknown) => boolean;
|
||||||
readonly skipTranslation?: boolean;
|
readonly skipTranslation?: boolean;
|
||||||
readonly metadata?: Record<string, any>;
|
readonly metadata?: Record<string, any>;
|
||||||
|
readonly hidden?: boolean;
|
||||||
|
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
matches?: number;
|
matches?: number;
|
||||||
@ -25,6 +26,7 @@ export class Filter implements IFilter, IListable {
|
|||||||
this.required = !!filter.required;
|
this.required = !!filter.required;
|
||||||
this.skipTranslation = !!filter.skipTranslation;
|
this.skipTranslation = !!filter.skipTranslation;
|
||||||
this.metadata = filter.metadata;
|
this.metadata = filter.metadata;
|
||||||
|
this.hidden = !!filter.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
get searchKey(): string {
|
get searchKey(): string {
|
||||||
|
|||||||