Template update (#271)
* comment * update: assets scss * update: auth components * update: notifications list * update: common components * update: stores and composables * update: navbar * update: toolbar * update: layout components * update: auth page * update: entry point component * update: tailwind configuration * update: dependencies * improve: components scss * chore: remove unnecessary code for now --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>
taylor_socfortress committed
Jul 10, 2024 at 12:35 UTC
fc8d63d4f7b31df7ef994b96b0708b0047a26ab1
66 files changed
+1600
-1301
frontend/package-lock.json
+89
-131
@@ -79,7 +79,7 @@
79
"picocolors": "^1.0.1",
80
"postcss": "^8.4.39",
81
"prettier": "^3.3.2",
82
- "sass": "^1.77.6",
82
+ "sass": "^1.77.7",
83
"start-server-and-test": "^2.0.4",
84
"tailwind-config-viewer": "^2.0.4",
85
"tailwindcss": "^3.4.4",
@@ -91,7 +91,7 @@
91
"vite-bundle-visualizer": "^1.2.1",
92
"vite-plugin-vue-devtools": "^7.3.5",
93
"vite-svg-loader": "^5.1.0",
94
- "vitest": "^2.0.1",
94
+ "vitest": "^2.0.2",
95
"vue-tsc": "^2.0.26"
96
},
97
"engines": {
@@ -1647,18 +1647,6 @@
1647
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
1648
}
1649
},
1650
- "node_modules/@jest/schemas": {
1651
- "version": "29.6.3",
1652
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
1653
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
1654
- "dev": true,
1655
- "dependencies": {
1656
- "@sinclair/typebox": "^0.27.8"
1657
- },
1658
- "engines": {
1659
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
1660
- }
1661
- },
1650
"node_modules/@jridgewell/gen-mapping": {
1651
"version": "0.3.5",
1652
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@@ -1692,9 +1680,9 @@
1680
}
1681
},
1682
"node_modules/@jridgewell/sourcemap-codec": {
1695
- "version": "1.4.15",
1696
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
1697
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
1683
+ "version": "1.5.0",
1684
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
1685
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
1686
},
1687
"node_modules/@jridgewell/trace-mapping": {
1688
"version": "0.3.25",
@@ -2092,12 +2080,6 @@
2080
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
2081
"dev": true
2082
},
2095
- "node_modules/@sinclair/typebox": {
2096
- "version": "0.27.8",
2097
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
2098
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
2099
- "dev": true
2100
- },
2083
"node_modules/@trysound/sax": {
2084
"version": "0.2.0",
2085
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -2853,26 +2835,39 @@
2835
}
2836
},
2837
"node_modules/@vitest/expect": {
2856
- "version": "2.0.1",
2857
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.1.tgz",
2858
- "integrity": "sha512-yw70WL3ZwzbI2O3MOXYP2Shf4vqVkS3q5FckLJ6lhT9VMMtDyWdofD53COZcoeuHwsBymdOZp99r5bOr5g+oeA==",
2838
+ "version": "2.0.2",
2839
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.2.tgz",
2840
+ "integrity": "sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==",
2841
+ "dev": true,
2842
+ "dependencies": {
2843
+ "@vitest/spy": "2.0.2",
2844
+ "@vitest/utils": "2.0.2",
2845
+ "chai": "^5.1.1",
2846
+ "tinyrainbow": "^1.2.0"
2847
+ },
2848
+ "funding": {
2849
+ "url": "https://opencollective.com/vitest"
2850
+ }
2851
+ },
2852
+ "node_modules/@vitest/pretty-format": {
2853
+ "version": "2.0.2",
2854
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.2.tgz",
2855
+ "integrity": "sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==",
2856
"dev": true,
2857
"dependencies": {
2861
- "@vitest/spy": "2.0.1",
2862
- "@vitest/utils": "2.0.1",
2863
- "chai": "^5.1.1"
2858
+ "tinyrainbow": "^1.2.0"
2859
},
2860
"funding": {
2861
"url": "https://opencollective.com/vitest"
2862
}
2863
},
2864
"node_modules/@vitest/runner": {
2870
- "version": "2.0.1",
2871
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.1.tgz",
2872
- "integrity": "sha512-XfcSXOGGxgR2dQ466ZYqf0ZtDLLDx9mZeQcKjQDLQ9y6Cmk2Wl7wxMuhiYK4Fo1VxCtLcFEGW2XpcfMuiD1Maw==",
2865
+ "version": "2.0.2",
2866
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.2.tgz",
2867
+ "integrity": "sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==",
2868
"dev": true,
2869
"dependencies": {
2875
- "@vitest/utils": "2.0.1",
2870
+ "@vitest/utils": "2.0.2",
2871
"pathe": "^1.1.2"
2872
},
2873
"funding": {
@@ -2880,23 +2875,23 @@
2875
}
2876
},
2877
"node_modules/@vitest/snapshot": {
2883
- "version": "2.0.1",
2884
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.1.tgz",
2885
- "integrity": "sha512-rst79a4Q+J5vrvHRapdfK4BdqpMH0eF58jVY1vYeBo/1be+nkyenGI5SCSohmjf6MkCkI20/yo5oG+0R8qrAnA==",
2878
+ "version": "2.0.2",
2879
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.2.tgz",
2880
+ "integrity": "sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==",
2881
"dev": true,
2882
"dependencies": {
2883
+ "@vitest/pretty-format": "2.0.2",
2884
"magic-string": "^0.30.10",
2889
- "pathe": "^1.1.2",
2890
- "pretty-format": "^29.7.0"
2885
+ "pathe": "^1.1.2"
2886
},
2887
"funding": {
2888
"url": "https://opencollective.com/vitest"
2889
}
2890
},
2891
"node_modules/@vitest/spy": {
2897
- "version": "2.0.1",
2898
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.1.tgz",
2899
- "integrity": "sha512-NLkdxbSefAtJN56GtCNcB4GiHFb5i9q1uh4V229lrlTZt2fnwsTyjLuWIli1xwK2fQspJJmHXHyWx0Of3KTXWA==",
2892
+ "version": "2.0.2",
2893
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.2.tgz",
2894
+ "integrity": "sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==",
2895
"dev": true,
2896
"dependencies": {
2897
"tinyspy": "^3.0.0"
@@ -2906,15 +2901,15 @@
2901
}
2902
},
2903
"node_modules/@vitest/utils": {
2909
- "version": "2.0.1",
2910
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.1.tgz",
2911
- "integrity": "sha512-STH+2fHZxlveh1mpU4tKzNgRk7RZJyr6kFGJYCI5vocdfqfPsQrgVC6k7dBWHfin5QNB4TLvRS0Ckly3Dt1uWw==",
2904
+ "version": "2.0.2",
2905
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.2.tgz",
2906
+ "integrity": "sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==",
2907
"dev": true,
2908
"dependencies": {
2914
- "diff-sequences": "^29.6.3",
2909
+ "@vitest/pretty-format": "2.0.2",
2910
"estree-walker": "^3.0.3",
2911
"loupe": "^3.1.1",
2917
- "pretty-format": "^29.7.0"
2912
+ "tinyrainbow": "^1.2.0"
2913
},
2914
"funding": {
2915
"url": "https://opencollective.com/vitest"
@@ -3812,9 +3807,9 @@
3807
}
3808
},
3809
"node_modules/browserslist": {
3815
- "version": "4.23.1",
3816
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
3817
- "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
3810
+ "version": "4.23.2",
3811
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
3812
+ "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
3813
"dev": true,
3814
"funding": [
3815
{
@@ -3831,10 +3826,10 @@
3826
}
3827
],
3828
"dependencies": {
3834
- "caniuse-lite": "^1.0.30001629",
3835
- "electron-to-chromium": "^1.4.796",
3829
+ "caniuse-lite": "^1.0.30001640",
3830
+ "electron-to-chromium": "^1.4.820",
3831
"node-releases": "^2.0.14",
3837
- "update-browserslist-db": "^1.0.16"
3832
+ "update-browserslist-db": "^1.1.0"
3833
},
3834
"bin": {
3835
"browserslist": "cli.js"
@@ -4000,9 +3995,9 @@
3995
}
3996
},
3997
"node_modules/caniuse-lite": {
4003
- "version": "1.0.30001640",
4004
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
4005
- "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
3998
+ "version": "1.0.30001641",
3999
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz",
4000
+ "integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==",
4001
"dev": true,
4002
"funding": [
4003
{
@@ -4918,15 +4913,6 @@
4913
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
4914
"dev": true
4915
},
4921
- "node_modules/diff-sequences": {
4922
- "version": "29.6.3",
4923
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
4924
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
4925
- "dev": true,
4926
- "engines": {
4927
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
4928
- }
4929
- },
4916
"node_modules/dir-glob": {
4917
"version": "3.0.1",
4918
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5084,9 +5070,9 @@
5070
"dev": true
5071
},
5072
"node_modules/electron-to-chromium": {
5087
- "version": "1.4.820",
5088
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.820.tgz",
5089
- "integrity": "sha512-kK/4O/YunacfboFEk/BDf7VO1HoPmDudLTJAU9NmXIOSjsV7qVIX3OrI4REZo0VmdqhcpUcncQc6N8Q3aEXlHg==",
5073
+ "version": "1.4.823",
5074
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.823.tgz",
5075
+ "integrity": "sha512-4h+oPeAiGQOHFyUJOqpoEcPj/xxlicxBzOErVeYVMMmAiXUXsGpsFd0QXBMaUUbnD8hhSfLf9uw+MlsoIA7j5w==",
5076
"dev": true
5077
},
5078
"node_modules/emoji-regex": {
@@ -7270,16 +7256,13 @@
7256
"dev": true
7257
},
7258
"node_modules/jackspeak": {
7273
- "version": "3.4.2",
7274
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.2.tgz",
7275
- "integrity": "sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q==",
7259
+ "version": "3.4.3",
7260
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
7261
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
7262
"dev": true,
7263
"dependencies": {
7264
"@isaacs/cliui": "^8.0.2"
7265
},
7280
- "engines": {
7281
- "node": "14 >=14.21 || 16 >=16.20 || >=18"
7282
- },
7266
"funding": {
7267
"url": "https://github.com/sponsors/isaacs"
7268
},
@@ -8742,13 +8725,10 @@
8725
}
8726
},
8727
"node_modules/path-scurry/node_modules/lru-cache": {
8745
- "version": "10.4.2",
8746
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.2.tgz",
8747
- "integrity": "sha512-voV4dDrdVZVNz84n39LFKDaRzfwhdzJ7akpyXfTMxCgRUp07U3lcJUXRlhTKP17rgt09sUzLi5iCitpEAr+6ug==",
8748
- "dev": true,
8749
- "engines": {
8750
- "node": "14 || 16 || 18 || 20 || >=22"
8751
- }
8728
+ "version": "10.4.3",
8729
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
8730
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
8731
+ "dev": true
8732
},
8733
"node_modules/path-to-regexp": {
8734
"version": "6.2.2",
@@ -9145,32 +9125,6 @@
9125
"url": "https://github.com/sponsors/sindresorhus"
9126
}
9127
},
9148
- "node_modules/pretty-format": {
9149
- "version": "29.7.0",
9150
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
9151
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
9152
- "dev": true,
9153
- "dependencies": {
9154
- "@jest/schemas": "^29.6.3",
9155
- "ansi-styles": "^5.0.0",
9156
- "react-is": "^18.0.0"
9157
- },
9158
- "engines": {
9159
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
9160
- }
9161
- },
9162
- "node_modules/pretty-format/node_modules/ansi-styles": {
9163
- "version": "5.2.0",
9164
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
9165
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
9166
- "dev": true,
9167
- "engines": {
9168
- "node": ">=10"
9169
- },
9170
- "funding": {
9171
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
9172
- }
9173
- },
9128
"node_modules/process": {
9129
"version": "0.11.10",
9130
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -9279,12 +9233,6 @@
9233
}
9234
]
9235
},
9282
- "node_modules/react-is": {
9283
- "version": "18.3.1",
9284
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
9285
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
9286
- "dev": true
9287
- },
9236
"node_modules/read-cache": {
9237
"version": "1.0.0",
9238
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9752,9 +9700,9 @@
9700
"dev": true
9701
},
9702
"node_modules/sass": {
9755
- "version": "1.77.6",
9756
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz",
9757
- "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==",
9703
+ "version": "1.77.7",
9704
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.7.tgz",
9705
+ "integrity": "sha512-9ywH75cO+rLjbrZ6en3Gp8qAMwPGBapFtlsMJoDTkcMU/bSe5a6cjKVUn5Jr4Gzg5GbP3HE8cm+02pLCgcoMow==",
9706
"dev": true,
9707
"dependencies": {
9708
"chokidar": ">=3.0.0 <4.0.0",
@@ -10604,6 +10552,15 @@
10552
"node": "^18.0.0 || >=20.0.0"
10553
}
10554
},
10555
+ "node_modules/tinyrainbow": {
10556
+ "version": "1.2.0",
10557
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
10558
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
10559
+ "dev": true,
10560
+ "engines": {
10561
+ "node": ">=14.0.0"
10562
+ }
10563
+ },
10564
"node_modules/tinyspy": {
10565
"version": "3.0.0",
10566
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz",
@@ -11141,15 +11098,15 @@
11098
}
11099
},
11100
"node_modules/vite-node": {
11144
- "version": "2.0.1",
11145
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.1.tgz",
11146
- "integrity": "sha512-nVd6kyhPAql0s+xIVJzuF+RSRH8ZimNrm6U8ZvTA4MXv8CHI17TFaQwRaFiK75YX6XeFqZD4IoAaAfi9OR1XvQ==",
11101
+ "version": "2.0.2",
11102
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.2.tgz",
11103
+ "integrity": "sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==",
11104
"dev": true,
11105
"dependencies": {
11106
"cac": "^6.7.14",
11107
"debug": "^4.3.5",
11108
"pathe": "^1.1.2",
11152
- "picocolors": "^1.0.1",
11109
+ "tinyrainbow": "^1.2.0",
11110
"vite": "^5.0.0"
11111
},
11112
"bin": {
@@ -11426,28 +11383,29 @@
11383
}
11384
},
11385
"node_modules/vitest": {
11429
- "version": "2.0.1",
11430
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.1.tgz",
11431
- "integrity": "sha512-PBPvNXRJiywtI9NmbnEqHIhcXlk8mB0aKf6REQIaYGY4JtWF1Pg8Am+N0vAuxdg/wUSlxPSVJr8QdjwcVxc2Hg==",
11386
+ "version": "2.0.2",
11387
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.2.tgz",
11388
+ "integrity": "sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==",
11389
"dev": true,
11390
"dependencies": {
11391
"@ampproject/remapping": "^2.3.0",
11435
- "@vitest/expect": "2.0.1",
11436
- "@vitest/runner": "2.0.1",
11437
- "@vitest/snapshot": "2.0.1",
11438
- "@vitest/spy": "2.0.1",
11439
- "@vitest/utils": "2.0.1",
11392
+ "@vitest/expect": "2.0.2",
11393
+ "@vitest/pretty-format": "^2.0.2",
11394
+ "@vitest/runner": "2.0.2",
11395
+ "@vitest/snapshot": "2.0.2",
11396
+ "@vitest/spy": "2.0.2",
11397
+ "@vitest/utils": "2.0.2",
11398
"chai": "^5.1.1",
11399
"debug": "^4.3.5",
11400
"execa": "^8.0.1",
11401
"magic-string": "^0.30.10",
11402
"pathe": "^1.1.2",
11445
- "picocolors": "^1.0.1",
11403
"std-env": "^3.7.0",
11404
"tinybench": "^2.8.0",
11405
"tinypool": "^1.0.0",
11406
+ "tinyrainbow": "^1.2.0",
11407
"vite": "^5.0.0",
11450
- "vite-node": "2.0.1",
11408
+ "vite-node": "2.0.2",
11409
"why-is-node-running": "^2.2.2"
11410
},
11411
"bin": {
@@ -11462,8 +11420,8 @@
11420
"peerDependencies": {
11421
"@edge-runtime/vm": "*",
11422
"@types/node": "^18.0.0 || >=20.0.0",
11465
- "@vitest/browser": "2.0.1",
11466
- "@vitest/ui": "2.0.1",
11423
+ "@vitest/browser": "2.0.2",
11424
+ "@vitest/ui": "2.0.2",
11425
"happy-dom": "*",
11426
"jsdom": "*"
11427
},
frontend/package.json
+3
-3
@@ -101,7 +101,7 @@
101
"picocolors": "^1.0.1",
102
"postcss": "^8.4.39",
103
"prettier": "^3.3.2",
104
- "sass": "^1.77.6",
104
+ "sass": "^1.77.7",
105
"start-server-and-test": "^2.0.4",
106
"tailwind-config-viewer": "^2.0.4",
107
"tailwindcss": "^3.4.4",
@@ -113,7 +113,7 @@
113
"vite-bundle-visualizer": "^1.2.1",
114
"vite-plugin-vue-devtools": "^7.3.5",
115
"vite-svg-loader": "^5.1.0",
116
- "vitest": "^2.0.1",
116
+ "vitest": "^2.0.2",
117
"vue-tsc": "^2.0.26"
118
},
119
"optionalDependencies": {
@@ -137,4 +137,4 @@
137
"engines": {
138
"node": ">=18.0.0"
139
}
140
-}
140
+}
\ No newline at end of file
frontend/src/App.vue
+17
-15
@@ -1,58 +1,56 @@
1
<template>
2
<Provider>
3
- <component :is="layoutComponent" :class="[`theme-${themeName}`, `layout-${layout}`, themeName]">
3
+ <component
4
+ :is="layoutComponent"
5
+ :class="[`theme-${themeName}`, `layout-${layoutComponentName}`, themeName, { 'opacity-0': loading }]"
6
+ >
7
<RouterView v-slot="{ Component }">
8
<transition :name="`router-${routerTransition}`" mode="out-in" appear>
9
<component
10
:is="Component"
11
:key="forceRefresh"
9
- :class="[`theme-${themeName}`, `layout-${layout}`, themeName]"
12
+ :class="[`theme-${themeName}`, `layout-${layoutComponentName}`, themeName]"
13
/>
14
</transition>
15
</RouterView>
16
</component>
17
15
- <SplashScreen :loading="loading" />
18
+ <SplashScreen :show="loading" />
19
<SearchDialog v-if="isLogged" />
17
- <!--
18
- <LayoutSettings />
19
- -->
20
</Provider>
21
</template>
22
23
<script lang="ts" setup>
24
-import { computed, onBeforeMount, ref, type Component } from "vue"
24
+import { computed, onBeforeMount, ref, watch, type Component } from "vue"
25
import { useMainStore } from "@/stores/main"
26
import { useAuthStore } from "@/stores/auth"
27
import { useThemeStore } from "@/stores/theme"
28
-import VerticalNav from "@/layouts/VerticalNav/index.vue"
28
import HorizontalNav from "@/layouts/HorizontalNav/index.vue"
29
import Blank from "@/layouts/Blank/index.vue"
30
import Provider from "@/layouts/common/Provider.vue"
31
import SplashScreen from "@/layouts/common/SplashScreen.vue"
33
-// import LayoutSettings from "@/components/common/LayoutSettings.vue"
32
import SearchDialog from "@/components/common/SearchDialog.vue"
33
import { Layout, RouterTransition, type ThemeName } from "@/types/theme.d"
36
-import { type RouteLocationNormalized, useRouter, useRoute } from "vue-router"
34
+import { type RouteLocationNormalized, useRoute, useRouter } from "vue-router"
35
import "@/assets/scss/index.scss"
36
37
const router = useRouter()
38
const loading = ref(true)
39
40
const layoutComponents = {
43
- VerticalNav,
41
HorizontalNav,
42
Blank
43
}
44
48
-const mainStore = useMainStore()
45
const themeStore = useThemeStore()
46
+const mainStore = useMainStore()
47
const authStore = useAuthStore()
48
52
-const forceLayout = ref<Layout | null>(null)
49
const forceRefresh = computed<number>(() => mainStore.forceRefresh)
50
+const forceLayout = ref<Layout | null>(null)
51
const layout = computed<Layout>(() => themeStore.layout)
55
-const layoutComponent = computed<Component>(() => layoutComponents[forceLayout.value || layout.value])
52
+const layoutComponentName = computed<Layout>(() => forceLayout.value || layout.value)
53
+const layoutComponent = computed<Component>(() => layoutComponents[layoutComponentName.value])
54
const routerTransition = computed<RouterTransition>(() => themeStore.routerTransition)
55
const themeName = computed<ThemeName>(() => themeStore.themeName)
56
const isLogged = computed(() => authStore.isLogged)
@@ -65,7 +63,11 @@ function checkForcedLayout(route: RouteLocationNormalized) {
63
}
64
}
65
68
-router.beforeEach(route => {
66
+watch(layoutComponentName, () => {
67
+ loading.value = false
68
+})
69
+
70
+router.afterEach(route => {
71
checkForcedLayout(route)
72
})
73
frontend/src/assets/scss/apexchart-override.scss
+21
-1
@@ -9,5 +9,25 @@
9
}
10
11
.apexcharts-tooltip {
12
- backdrop-filter: blur(6px);
12
+ backdrop-filter: blur(3px);
13
+ box-shadow:
14
+ 0px 5px 10px -5px rgba(0, 0, 0, 0.2),
15
+ 0px 5px 20px 0px rgba(0, 0, 0, 0.2) !important;
16
+
17
+ &.apexcharts-theme-dark {
18
+ background: rgba(0, 0, 0, 0.5) !important;
19
+ }
20
+
21
+ &.apexcharts-theme-light {
22
+ background: rgba(255, 255, 255, 0.5) !important;
23
+ }
24
+}
25
+
26
+.direction-rtl {
27
+ .apexcharts-legend {
28
+ direction: ltr;
29
+ }
30
+ .apexcharts-title-text {
31
+ direction: ltr;
32
+ }
33
}
frontend/src/assets/scss/common.scss
+16
-6
@@ -1,6 +1,7 @@
1
+@import "@/assets/scss/mixin.scss";
2
+
3
.custom-label {
4
color: var(--label-color);
3
- margin-top: 12px;
5
font-size: 10px;
6
padding: 2px 6px;
7
border-radius: var(--border-radius-small);
@@ -8,10 +9,6 @@
9
display: inline-block;
10
font-weight: bold;
11
11
- &:not(:last-child) {
12
- margin-right: 10px;
13
- }
14
-
12
&::before {
13
content: "";
14
width: 100%;
@@ -21,7 +18,7 @@
18
position: absolute;
19
top: 0;
20
left: 0;
24
- z-index: -1;
21
+ z-index: 0;
22
opacity: 0.15;
23
}
24
}
@@ -32,6 +29,19 @@
29
.page-min-wrapped {
30
min-height: calc(100svh - var(--toolbar-height) - var(--view-padding) - (var(--view-padding) / 2));
31
}
32
+.page-mobile-full {
33
+ @media (max-width: 700px) {
34
+ @include page-full-view;
35
+ }
36
+}
37
+.direction-rtl {
38
+ .page-mobile-full {
39
+ @media (max-width: 700px) {
40
+ right: calc(var(--view-padding) * -1);
41
+ left: unset;
42
+ }
43
+ }
44
+}
45
46
.page {
47
.page-header {
frontend/src/assets/scss/functions.scss
new
+35
@@ -0,0 +1,35 @@
1
+/*
2
+A simple little SCSS mixin for creating scrim gradients
3
+Inspired by Andreas Larson - https://github.com/larsenwork
4
+https://css-tricks.com/easing-linear-gradients/
5
+*/
6
+
7
+@function scrimGradient($startColor: $color-black, $direction: "to bottom") {
8
+ $scrimCoordinates: (
9
+ 0: 1,
10
+ 19: 0.738,
11
+ 34: 0.541,
12
+ 47: 0.382,
13
+ 56.5: 0.278,
14
+ 65: 0.194,
15
+ 73: 0.126,
16
+ 80.2: 0.075,
17
+ 86.1: 0.042,
18
+ 91: 0.021,
19
+ 95.2: 0.008,
20
+ 98.2: 0.002,
21
+ 100: 0
22
+ );
23
+
24
+ $hue: hue($startColor);
25
+ $saturation: saturation($startColor);
26
+ $lightness: lightness($startColor);
27
+ $stops: ();
28
+
29
+ @each $colorStop, $alphaValue in $scrimCoordinates {
30
+ $stop: hsla($hue, $saturation, $lightness, $alphaValue) percentage($colorStop/100);
31
+ $stops: append($stops, $stop, comma);
32
+ }
33
+
34
+ @return linear-gradient(unquote($direction), $stops);
35
+}
frontend/src/assets/scss/helpers.scss
+3
-3
@@ -2,7 +2,7 @@
2
/* Works on Firefox */
3
4
scrollbar-width: thin;
5
- scrollbar-color: var(--hover-010-color) var(--bg-sidebar);
5
+ scrollbar-color: var(--hover-010-color) var(--bg-secondary-color);
6
7
/* Works on Chrome, Edge, and Safari */
8
&::-webkit-scrollbar,
@@ -12,14 +12,14 @@
12
13
&::-webkit-scrollbar-track,
14
::-webkit-scrollbar-track {
15
- background: var(--bg-sidebar);
15
+ background: var(--bg-secondary-color);
16
}
17
18
&::-webkit-scrollbar-thumb,
19
::-webkit-scrollbar-thumb {
20
background-color: var(--hover-010-color);
21
border-radius: 20px;
22
- border: 2px solid var(--bg-sidebar);
22
+ border: 2px solid var(--bg-secondary-color);
23
}
24
}
25
frontend/src/assets/scss/naive-override.scss
+257
@@ -64,3 +64,260 @@
64
.v-binder-follower-content {
65
max-width: calc(100vw - (var(--view-padding) * 2));
66
}
67
+
68
+.n-data-table {
69
+ .n-data-table-resize-button {
70
+ --n-merged-border-color: var(--divider-010-color) !important;
71
+ }
72
+}
73
+
74
+.n-space.n-space--rtl {
75
+ .n-layout {
76
+ aside.n-layout-sider.n-layout-sider--left-placement.n-layout-sider--bordered {
77
+ .n-layout-toggle-button {
78
+ right: unset;
79
+ left: 0;
80
+ transform: translateX(-50%) translateY(-50%) rotate(180deg);
81
+ }
82
+ .n-layout-sider__border {
83
+ right: unset;
84
+ left: 0;
85
+ }
86
+ }
87
+ }
88
+}
89
+
90
+.direction-rtl {
91
+ .v-binder-follower-container {
92
+ direction: ltr;
93
+
94
+ .v-binder-follower-content {
95
+ & > * {
96
+ direction: rtl;
97
+ }
98
+ }
99
+ }
100
+
101
+ .n-menu {
102
+ direction: rtl;
103
+
104
+ .n-menu-item {
105
+ .n-menu-item-content {
106
+ padding-left: unset;
107
+ padding-right: 32px;
108
+ gap: 8px;
109
+
110
+ .n-menu-item-content__icon {
111
+ margin-right: unset !important;
112
+ }
113
+ }
114
+ }
115
+
116
+ .n-menu-item-group {
117
+ .n-menu-item-group-title {
118
+ padding-right: 32px;
119
+ padding-left: 0 !important;
120
+ }
121
+ }
122
+
123
+ &.n-menu--collapsed {
124
+ .n-menu-item {
125
+ .n-menu-item-content {
126
+ padding-right: 0px;
127
+ direction: ltr;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ .n-base-select-menu {
134
+ .n-base-select-option {
135
+ .n-base-select-option__content {
136
+ .n-icon {
137
+ margin-right: unset !important;
138
+ margin-left: 8px;
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ .n-base-selection {
145
+ .n-base-selection-label {
146
+ .n-base-selection-input {
147
+ .n-base-selection-input__content {
148
+ .n-icon {
149
+ margin-right: unset !important;
150
+ margin-left: 8px;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ .n-color-picker-panel {
158
+ .n-color-picker-action {
159
+ gap: 8px;
160
+
161
+ & > * {
162
+ margin-left: 0;
163
+ }
164
+ }
165
+ }
166
+
167
+ .n-cascader-menu {
168
+ direction: ltr !important;
169
+ }
170
+
171
+ .n-rate {
172
+ direction: ltr;
173
+ }
174
+
175
+ .n-carousel {
176
+ direction: ltr;
177
+ }
178
+
179
+ .n-tabs {
180
+ .n-tabs-nav {
181
+ direction: ltr;
182
+ }
183
+ }
184
+
185
+ .n-timeline {
186
+ &:not(.n-timeline--horizontal) {
187
+ .n-timeline-item .n-timeline-item-timeline {
188
+ direction: ltr;
189
+ }
190
+ }
191
+
192
+ &.n-timeline--horizontal {
193
+ .n-timeline-item .n-timeline-item-timeline {
194
+ .n-timeline-item-timeline__line {
195
+ right: var(--n-icon-size);
196
+ left: 0;
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ .n-data-table-filter-menu__action {
203
+ gap: 8px;
204
+
205
+ .n-button--rtl {
206
+ margin: 0 !important;
207
+ }
208
+ }
209
+
210
+ .n-data-table-check-extra {
211
+ right: unset !important;
212
+ left: -3px;
213
+ }
214
+
215
+ .n-page-header {
216
+ .n-page-header__main {
217
+ gap: 16px;
218
+
219
+ & > * {
220
+ margin-right: 0;
221
+ margin-left: 0;
222
+ }
223
+ }
224
+ }
225
+
226
+ .n-date-panel-calendar {
227
+ direction: ltr;
228
+ }
229
+
230
+ .n-date-panel {
231
+ .n-date-panel-header {
232
+ gap: 10px;
233
+
234
+ & > *:not(:last-child) {
235
+ margin-right: 0;
236
+ }
237
+ }
238
+
239
+ .n-date-panel-actions {
240
+ .n-date-panel-actions__suffix {
241
+ margin-bottom: 0;
242
+ gap: 8px;
243
+ flex-wrap: wrap;
244
+
245
+ .n-button {
246
+ margin-bottom: 0;
247
+
248
+ &:not(:last-child) {
249
+ margin-right: 0;
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ .n-date-panel-month-calendar {
256
+ direction: ltr;
257
+ }
258
+ }
259
+
260
+ .n-time-picker-panel {
261
+ direction: ltr !important;
262
+ }
263
+
264
+ .n-form {
265
+ &.n-form--inline {
266
+ gap: 18px;
267
+
268
+ .n-form-item {
269
+ margin-right: 0;
270
+ }
271
+ }
272
+
273
+ .n-form-item.n-form-item--left-labelled {
274
+ .n-form-item-label {
275
+ padding-right: 0;
276
+ padding-left: 12px;
277
+ }
278
+ }
279
+ }
280
+
281
+ .n-input-group {
282
+ direction: ltr;
283
+ justify-content: flex-end;
284
+ }
285
+
286
+ .n-slider {
287
+ direction: ltr;
288
+ }
289
+
290
+ .n-transfer {
291
+ direction: ltr;
292
+
293
+ .n-transfer-list-item {
294
+ &.n-transfer-list-item--source {
295
+ gap: 8px;
296
+
297
+ & > * {
298
+ margin-right: 0 !important;
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ .n-popover {
305
+ .n-popover__content {
306
+ .n-popconfirm__panel {
307
+ .n-popconfirm__body {
308
+ .n-popconfirm__icon {
309
+ margin-right: 0;
310
+ margin-left: 8px;
311
+ }
312
+ }
313
+ .n-popconfirm__action {
314
+ gap: 8px;
315
+
316
+ .n-button:not(:last-child) {
317
+ margin-right: 0px;
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+}
frontend/src/components/auth/AuthForm.vue
+8
-7
@@ -37,24 +37,23 @@ import Logo from "@/layouts/common/Logo.vue"
37
import { NButton } from "naive-ui"
38
import { ref, onBeforeMount, computed } from "vue"
39
import { useRouter } from "vue-router"
40
-import type { FormType } from "./types"
40
+import type { FormType } from "./types.d"
41
42
const props = defineProps<{
43
type?: FormType
44
useOnlyRouter?: boolean
45
}>()
46
47
-const router = useRouter()
48
-
47
const typeRef = ref<FormType>("signin")
50
-
51
-const isDark = computed<boolean>(() => useThemeStore().isThemeDark)
48
+const router = useRouter()
49
+const themeStore = useThemeStore()
50
+const isDark = computed<boolean>(() => themeStore.isThemeDark)
51
const title = computed<string>(() =>
52
typeRef.value === "signin"
53
? "SOCFortress CoPilot"
54
: typeRef.value === "signup"
56
- ? "SOCFortress CoPilot"
57
- : "Forgot Password"
55
+ ? "SOCFortress CoPilot"
56
+ : "Forgot Password"
57
)
58
59
function gotoSignIn() {
@@ -63,12 +62,14 @@ function gotoSignIn() {
62
}
63
router.replace({ name: "Login" })
64
}
65
+
66
function gotoSignUp() {
67
if (!props.useOnlyRouter) {
68
typeRef.value = "signup"
69
}
70
router.replace({ name: "Register" })
71
}
72
+
73
function gotoForgotPassword() {
74
if (!props.useOnlyRouter) {
75
typeRef.value = "forgotpassword"
frontend/src/components/auth/ForgotPassword.vue
+1
-2
@@ -18,12 +18,11 @@
18
19
<script lang="ts" setup>
20
import { ref } from "vue"
21
-
21
import {
22
type FormInst,
23
type FormValidationError,
25
- useMessage,
24
type FormRules,
25
+ useMessage,
26
NForm,
27
NFormItem,
28
NInput,
frontend/src/components/auth/SignIn.vue
+3
-3
@@ -37,12 +37,11 @@
37
38
<script lang="ts" setup>
39
import { ref } from "vue"
40
-
40
import {
41
type FormInst,
42
type FormValidationError,
44
- useMessage,
43
type FormRules,
44
+ useMessage,
45
NForm,
46
NFormItem,
47
NInput,
@@ -71,6 +70,7 @@ const model = ref<ModelType>({
70
username: "",
71
password: ""
72
})
73
+const authStore = useAuthStore()
74
75
const rules: FormRules = {
76
username: [
@@ -100,7 +100,7 @@ function signIn(e: Event) {
100
password: model.value.password
101
}
102
103
- useAuthStore()
103
+ authStore
104
.login(payload)
105
.then(() => {
106
router.push({ path: "/", replace: true })
frontend/src/components/auth/SignUp.vue
+5
-5
@@ -146,8 +146,8 @@ import { computed, ref } from "vue"
146
import {
147
type FormInst,
148
type FormValidationError,
149
- useMessage,
149
type FormRules,
150
+ useMessage,
151
NForm,
152
NFormItem,
153
NInput,
@@ -177,16 +177,16 @@ interface ModelType {
177
*/
178
}
179
180
+const emit = defineEmits<{
181
+ (e: "goto-signin"): void
182
+}>()
183
+
184
const ArrowRightIcon = "carbon:arrow-right"
185
const ArrowLeftIcon = "carbon:arrow-left"
186
// const ImageIcon = "carbon:image"
187
// const RemoveImageIcon = "carbon:no-image"
188
const UserAddIcon = "carbon:user-admin"
189
186
-const emit = defineEmits<{
187
- (e: "goto-signin"): void
188
-}>()
189
-
190
const wizardCurrent = ref(1)
191
const loading = ref(false)
192
const formRef = ref<FormInst | null>(null)
frontend/src/components/cards/CardActions.vue
+5
-5
@@ -8,13 +8,13 @@
8
<div class="subtitle" v-if="!hideSubtitle && subtitle">{{ subtitle }}</div>
9
</template>
10
<template #header-extra>
11
- <n-dropdown :options="menuOptions" placement="bottom-end" @select="menuSelect" v-if="!hideMenu">
12
- <Icon :size="20" :name="MenuIcon" class="ml-3" />
11
+ <n-dropdown :options="menuOptions" placement="bottom-end" to="body" @select="menuSelect" v-if="!hideMenu">
12
+ <Icon :size="20" :name="MenuIcon" />
13
</n-dropdown>
14
</template>
15
<template #default>
16
<div class="overflow-hidden w-full">
17
- <n-scrollbar x-scrollable style="width: 100%">
17
+ <n-scrollbar x-scrollable class="!w-full" trigger="none">
18
<slot></slot>
19
</n-scrollbar>
20
</div>
@@ -71,7 +71,7 @@ const menuOptions = computed(() =>
71
key: "reload",
72
icon: renderIcon(ReloadIcon)
73
}
74
- ]
74
+ ]
75
: [
76
{
77
label: "Collapse",
@@ -83,7 +83,7 @@ const menuOptions = computed(() =>
83
key: "reload",
84
icon: renderIcon(ReloadIcon)
85
}
86
- ]
86
+ ]
87
)
88
89
function menuSelect(key: string) {
frontend/src/components/common/FileDrop.vue
-4
@@ -10,10 +10,6 @@
10
<script lang="ts" setup>
11
import { toRefs } from "vue"
12
13
-defineOptions({
14
- name: "FileDrop"
15
-})
16
-
13
const props = withDefaults(
14
defineProps<{
15
accept: string
frontend/src/components/common/ImageCropper.vue
+1
-1
@@ -3,7 +3,7 @@
3
<slot :openCropper="openCropper"></slot>
4
5
<n-modal v-model:show="showCropper">
6
- <n-card class="image-cropper-modal flex flex-col" content-style="padding:20px">
6
+ <n-card class="image-cropper-modal flex flex-col" content-class="!p-5">
7
<div class="aspect-square">
8
<div class="upload-box" v-if="!img">
9
<n-upload
frontend/src/components/common/ImageLoader.vue
+2
-6
@@ -18,6 +18,8 @@
18
</template>
19
20
<script setup lang="ts">
21
+import { ref, watch } from "vue"
22
+
23
export interface ImageEvent {
24
height: number
25
}
@@ -25,12 +27,6 @@ export interface ImageLoadEvent extends Event {
27
path?: HTMLElement[]
28
}
29
28
-import { ref, watch } from "vue"
29
-
30
-defineOptions({
31
- name: "ImageLoader"
32
-})
33
-
30
const emit = defineEmits(["image-error", "image-loading", "image-loaded", "image-fallback"])
31
32
const props = defineProps({
frontend/src/components/common/LayoutSettings.vue
+91
-58
@@ -24,7 +24,7 @@
24
/>
25
<n-color-picker v-model:value="lightColor" :modes="['hex']" :show-alpha="false" v-else />
26
</div>
27
- <div class="palette flex justify-between">
27
+ <div class="palette flex justify-around mt-3">
28
<n-button text v-for="color of palette" :key="color.light" @click="setPrimary(color)">
29
<template #icon>
30
<Icon
@@ -76,14 +76,16 @@
76
</div>
77
<div class="flex items-center gap-2">
78
<div class="basis-1/2">
79
- <n-button
79
+ <!--
80
+ <n-button
81
class="!w-full"
82
@click="layout = Layout.VerticalNav"
83
:type="layout === Layout.VerticalNav ? 'primary' : 'default'"
84
:disabled="isMobileView"
84
- >
85
- Vertical
86
- </n-button>
85
+ >
86
+ Vertical
87
+ </n-button>
88
+ -->
89
</div>
90
<div class="basis-1/2">
91
<n-button
@@ -102,14 +104,14 @@
104
<div class="flex justify-between items-center">
105
<div class="switch-label">
106
View boxed
105
- <span v-if="isMobileView" class="text-secondary-color">(desktop only)</span>
107
+ <span v-if="isMobileView" class="text-secondary-color px-1">desktop only</span>
108
</div>
109
<n-switch v-model:value="boxed" :disabled="isMobileView" size="small" />
110
</div>
111
<div class="flex justify-between items-center">
112
<div class="switch-label">
113
Toolbar boxed
112
- <span v-if="isMobileView" class="text-secondary-color">(desktop only)</span>
114
+ <span v-if="isMobileView" class="text-secondary-color px-1">desktop only</span>
115
</div>
116
<n-switch
117
v-model:value="toolbarBoxed"
@@ -121,6 +123,13 @@
123
<div class="switch-label">Footer visible</div>
124
<n-switch v-model:value="footerShown" size="small" />
125
</div>
126
+ <div class="flex justify-between items-center">
127
+ <div class="switch-label">
128
+ RTL
129
+ <span class="text-secondary-color px-1">beta</span>
130
+ </div>
131
+ <n-switch v-model:value="rtl" size="small" />
132
+ </div>
133
</div>
134
</div>
135
@@ -154,18 +163,9 @@ import { computed, ref } from "vue"
163
import { NColorPicker, NButton, NSelect, useOsTheme, NScrollbar, NSwitch } from "naive-ui"
164
import { useThemeStore } from "@/stores/theme"
165
import Icon from "@/components/common/Icon.vue"
157
-
166
import { Layout, RouterTransition, ThemeEnum } from "@/types/theme.d"
167
import { useWindowSize } from "@vueuse/core"
168
161
-const SettingsIcon = "carbon:settings-adjust"
162
-const CloseIcon = "carbon:close"
163
-const LightIcon = "ion:sunny"
164
-const DarkIcon = "ion:moon"
165
-const LightOutlineIcon = "ion:sunny-outline"
166
-const DarkOutlineIcon = "ion:moon-outline"
167
-const ColorIcon = "carbon:circle-solid"
168
-
169
interface ColorPalette {
170
light: string
171
dark: string
@@ -173,12 +173,17 @@ interface ColorPalette {
173
174
type Palette = ColorPalette[]
175
176
-const store = useThemeStore()
176
+const SettingsIcon = "carbon:settings-adjust"
177
+const CloseIcon = "carbon:close"
178
+const LightIcon = "ion:sunny"
179
+const DarkIcon = "ion:moon"
180
+const LightOutlineIcon = "ion:sunny-outline"
181
+const DarkOutlineIcon = "ion:moon-outline"
182
+const ColorIcon = "carbon:circle-solid"
183
184
+const themeStore = useThemeStore()
185
const { width: winWidth } = useWindowSize()
179
-
186
const isMobileView = computed<boolean>(() => winWidth.value < 700)
181
-
187
const open = ref(false)
188
const transitionOptions = [
189
{
@@ -204,43 +209,48 @@ const transitionOptions = [
209
]
210
211
const layout = computed({
207
- get: () => store.layout,
208
- set: val => store.setLayout(val)
212
+ get: () => themeStore.layout,
213
+ set: val => themeStore.setLayout(val)
214
})
215
216
const routerTransition = computed({
212
- get: () => store.routerTransition,
213
- set: val => store.setRouterTransition(val)
217
+ get: () => themeStore.routerTransition,
218
+ set: val => themeStore.setRouterTransition(val)
219
})
220
221
const theme = computed({
217
- get: () => store.themeName,
218
- set: val => store.setTheme(val)
222
+ get: () => themeStore.themeName,
223
+ set: val => themeStore.setTheme(val)
224
})
225
226
const darkColor = computed({
222
- get: () => store.darkPrimaryColor,
223
- set: val => store.setColor(ThemeEnum.Dark, "primary", val)
227
+ get: () => themeStore.darkPrimaryColor,
228
+ set: val => themeStore.setColor(ThemeEnum.Dark, "primary", val)
229
})
230
231
const lightColor = computed({
227
- get: () => store.lightPrimaryColor,
228
- set: val => store.setColor(ThemeEnum.Light, "primary", val)
232
+ get: () => themeStore.lightPrimaryColor,
233
+ set: val => themeStore.setColor(ThemeEnum.Light, "primary", val)
234
+})
235
+
236
+const rtl = computed({
237
+ get: () => themeStore.isRTL,
238
+ set: val => themeStore.setRTL(val)
239
})
240
241
const boxed = computed({
232
- get: () => store.isBoxed,
233
- set: val => store.setBoxed(val)
242
+ get: () => themeStore.isBoxed,
243
+ set: val => themeStore.setBoxed(val)
244
})
245
246
const toolbarBoxed = computed({
237
- get: () => store.isToolbarBoxed,
238
- set: val => store.setToolbarBoxed(val)
247
+ get: () => themeStore.isToolbarBoxed,
248
+ set: val => themeStore.setToolbarBoxed(val)
249
})
250
251
const footerShown = computed({
242
- get: () => store.isFooterShown,
243
- set: val => store.setFooterShow(val)
252
+ get: () => themeStore.isFooterShown,
253
+ set: val => themeStore.setFooterShow(val)
254
})
255
256
const palette = ref<Palette>([
@@ -252,25 +262,24 @@ const palette = ref<Palette>([
262
])
263
264
function setPrimary(color: ColorPalette) {
255
- store.setColor(ThemeEnum.Dark, "primary", color.dark)
256
- store.setColor(ThemeEnum.Light, "primary", color.light)
265
+ themeStore.setColor(ThemeEnum.Dark, "primary", color.dark)
266
+ themeStore.setColor(ThemeEnum.Light, "primary", color.light)
267
}
268
269
function reset() {
260
- store.setColor(ThemeEnum.Dark, "primary", "#00E19B")
261
- store.setColor(ThemeEnum.Light, "primary", "#00B27B")
262
- store.setTheme(useOsTheme().value || ThemeEnum.Light)
263
- store.setLayout(Layout.VerticalNav)
264
- store.setRouterTransition(RouterTransition.FadeUp)
265
- store.setBoxed(true)
266
- store.setToolbarBoxed(true)
267
- store.setFooterShow(true)
270
+ themeStore.setColor(ThemeEnum.Dark, "primary", "#00E19B")
271
+ themeStore.setColor(ThemeEnum.Light, "primary", "#00B27B")
272
+ themeStore.setTheme(useOsTheme().value || ThemeEnum.Light)
273
+ themeStore.setLayout(Layout.HorizontalNav)
274
+ themeStore.setRouterTransition(RouterTransition.FadeUp)
275
+ themeStore.setRTL(false)
276
+ themeStore.setBoxed(true)
277
+ themeStore.setToolbarBoxed(true)
278
+ themeStore.setFooterShow(true)
279
}
280
</script>
281
282
<style scoped lang="scss">
272
-@import "@/assets/scss/common.scss";
273
-
283
.layout-settings {
284
position: fixed;
285
right: 10px;
@@ -290,11 +299,13 @@ function reset() {
299
width: 100%;
300
height: 100%;
301
position: absolute;
302
+ will-change: opacity;
303
}
304
.ls-form {
305
position: absolute;
306
height: 100%;
307
width: 100%;
308
+ will-change: opacity;
309
310
.ls-header {
311
border-bottom: var(--border-small-050);
@@ -303,7 +314,6 @@ function reset() {
314
font-weight: 700;
315
padding: 10px 14px;
316
line-height: 1;
306
- padding-right: 8px;
317
transition: all 0.3s;
318
319
.ls-icon {
@@ -375,14 +385,6 @@ function reset() {
385
}
386
}
387
}
378
-
379
- .palette {
380
- margin: 8px 0;
381
- margin-bottom: 6px;
382
- padding: 0 4px;
383
- width: 94%;
384
- margin-left: 3%;
385
- }
388
}
389
390
&.ls-boxed-selection {
@@ -405,7 +407,7 @@ function reset() {
407
408
&.open {
409
width: 230px;
408
- height: 615px;
410
+ height: 645px;
411
right: 16px;
412
border-radius: var(--border-radius);
413
max-height: 90vh;
@@ -425,7 +427,38 @@ function reset() {
427
.anim-enter-from,
428
.anim-leave-to {
429
opacity: 0;
428
- transform: translateY(1%);
430
+ // transform: translateY(1%);
431
+ }
432
+}
433
+
434
+.direction-rtl {
435
+ .layout-settings {
436
+ right: unset;
437
+ left: 10px;
438
+
439
+ .ls-form {
440
+ .ls-header {
441
+ direction: rtl;
442
+ }
443
+
444
+ :deep() {
445
+ .n-color-picker {
446
+ .n-color-picker-trigger {
447
+ .n-color-picker-trigger__fill {
448
+ .n-color-picker-trigger__value {
449
+ margin-left: unset !important;
450
+ margin-right: 44px;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+
458
+ &.open {
459
+ right: unset;
460
+ left: 16px;
461
+ }
462
}
463
}
464
</style>
frontend/src/components/common/LtrContext.vue
new
+15
@@ -0,0 +1,15 @@
1
+<template>
2
+ <n-config-provider class="ltr-context" :rtl="[]">
3
+ <slot />
4
+ </n-config-provider>
5
+</template>
6
+
7
+<script setup lang="ts">
8
+import { NConfigProvider } from "naive-ui"
9
+</script>
10
+
11
+<style scoped lang="scss">
12
+.ltr-context {
13
+ direction: ltr;
14
+}
15
+</style>
frontend/src/components/common/Notifications/List.vue
+19
-7
@@ -9,11 +9,11 @@
9
>
10
<div class="icon-box">
11
<Icon :name="AlertIcon" :size="21" v-if="item.category === 'alert'"></Icon>
12
- <n-tooltip trigger="hover" style="padding: 0" placement="right" v-if="!item.read">
12
+ <n-tooltip trigger="hover" class="!p-0" content-class="!p-0" placement="right" v-if="!item.read">
13
<template #trigger>
14
<div class="read-badge" @click.stop="setRead(item.id)"></div>
15
</template>
16
- Set as read
16
+ <div class="px-3 py-2">Set as read</div>
17
</n-tooltip>
18
</div>
19
<div class="content grow">
@@ -78,11 +78,12 @@ function formatDatetime(date: Date | string) {
78
.notifications-list {
79
.item {
80
position: relative;
81
- padding: 14px 0;
81
+ padding: 14px 12px;
82
+ gap: 4px;
83
84
.icon-box {
84
- width: 70px;
85
- min-width: 70px;
85
+ width: 45px;
86
+ min-width: 45px;
87
display: flex;
88
justify-content: center;
89
position: relative;
@@ -100,7 +101,7 @@ function formatDatetime(date: Date | string) {
101
.read-badge {
102
position: absolute;
103
top: 5px;
103
- left: 14px;
104
+ left: 0px;
105
width: 10px;
106
height: 10px;
107
border-radius: 50%;
@@ -110,7 +111,7 @@ function formatDatetime(date: Date | string) {
111
}
112
113
.content {
113
- padding-right: 20px;
114
+ padding: 0 8px;
115
font-size: 14px;
116
117
.title {
@@ -204,4 +205,15 @@ function formatDatetime(date: Date | string) {
205
}
206
}
207
}
208
+
209
+.direction-rtl {
210
+ .notifications-list {
211
+ .item {
212
+ .delete-btn {
213
+ right: unset;
214
+ left: 8px;
215
+ }
216
+ }
217
+ }
218
+}
219
</style>
frontend/src/components/common/PageSplitted.vue
deleted
-249
@@ -1,249 +0,0 @@
1
-<template>
2
- <div class="wrapper flex grow" :class="[{ 'sidebar-open': sidebarOpen }, `sidebar-position-${sidebarPosition}`]">
3
- <div class="sidebar flex flex-col" ref="sidebar">
4
- <div class="sidebar-header flex items-center">
5
- <slot name="sidebar-header"></slot>
6
- </div>
7
- <div class="sidebar-main grow">
8
- <n-scrollbar style="max-height: 100%">
9
- <div class="sidebar-main-content">
10
- <slot name="sidebar-content"></slot>
11
- </div>
12
- </n-scrollbar>
13
- </div>
14
- <div class="sidebar-footer flex items-center">
15
- <slot name="sidebar-footer"></slot>
16
- </div>
17
- </div>
18
-
19
- <div class="main flex-grow flex flex-col">
20
- <div class="main-toolbar flex items-center">
21
- <div class="menu-btn flex justify-center opacity-50">
22
- <n-button text @click="sidebarOpen = true">
23
- <Icon :size="24" :name="MenuIcon"></Icon>
24
- </n-button>
25
- </div>
26
-
27
- <div>
28
- <slot name="main-toolbar"></slot>
29
- </div>
30
- </div>
31
- <div class="main-view grow">
32
- <n-scrollbar style="max-height: 100%">
33
- <div class="main-content">
34
- <slot name="main-content"></slot>
35
- </div>
36
- </n-scrollbar>
37
- </div>
38
- <div class="main-footer flex items-center">
39
- <slot name="main-footer"></slot>
40
- </div>
41
- </div>
42
- </div>
43
-</template>
44
-
45
-<script setup lang="ts">
46
-import { NScrollbar, NButton } from "naive-ui"
47
-import Icon from "@/components/common/Icon.vue"
48
-
49
-const MenuIcon = "ion:menu-sharp"
50
-import { ref, toRefs } from "vue"
51
-import { onClickOutside } from "@vueuse/core"
52
-
53
-type SidebarPosition = "left" | "right"
54
-
55
-const props = withDefaults(
56
- defineProps<{
57
- sidebarPosition?: SidebarPosition
58
- }>(),
59
- { sidebarPosition: "left" }
60
-)
61
-const { sidebarPosition } = toRefs(props)
62
-
63
-const sidebarOpen = ref(false)
64
-const sidebar = ref(null)
65
-
66
-onClickOutside(sidebar, () => (sidebarOpen.value = false))
67
-</script>
68
-
69
-<style lang="scss" scoped>
70
-.wrapper {
71
- --mb-toolbar-height: 70px;
72
- position: relative;
73
- height: 100%;
74
- overflow: hidden;
75
- border-radius: var(--border-radius);
76
- border: 1px solid var(--border-color);
77
- background-color: var(--bg-color);
78
-
79
- .sidebar {
80
- background-color: var(--bg-secondary-color);
81
- min-width: 250px;
82
- width: 40%;
83
- max-width: 350px;
84
- overflow: hidden;
85
-
86
- .sidebar-header {
87
- border-block-end: var(--border-small-050);
88
- min-height: var(--mb-toolbar-height);
89
- padding: 0 30px;
90
- }
91
-
92
- .sidebar-main {
93
- overflow: hidden;
94
-
95
- .sidebar-main-content {
96
- padding: 30px;
97
- }
98
- }
99
-
100
- .sidebar-footer {
101
- border-block-start: var(--border-small-050);
102
- min-height: var(--mb-toolbar-height);
103
- padding: 0 30px;
104
- }
105
- }
106
-
107
- .main {
108
- background-color: var(--bg-color);
109
- position: relative;
110
- container-type: inline-size;
111
-
112
- .main-toolbar {
113
- border-block-end: var(--border-small-050);
114
- min-height: var(--mb-toolbar-height);
115
- padding: 0 30px;
116
- gap: 18px;
117
- line-height: 1.3;
118
-
119
- .menu-btn {
120
- display: none;
121
- }
122
- }
123
-
124
- .main-view {
125
- overflow: hidden;
126
-
127
- .main-content {
128
- padding: 30px;
129
- }
130
- }
131
-
132
- .main-footer {
133
- border-block-start: var(--border-small-050);
134
- min-height: var(--mb-toolbar-height);
135
- padding: 0 30px;
136
- }
137
- }
138
-
139
- &.sidebar-position-right {
140
- flex-direction: row-reverse;
141
- }
142
-
143
- @media (max-width: 700px) {
144
- --mb-toolbar-height: 62px;
145
- height: 100%;
146
- overflow: hidden;
147
- border-radius: 0;
148
- border: none;
149
-
150
- &::before {
151
- content: "";
152
- width: 100vw;
153
- display: block;
154
- background-color: var(--bg-body);
155
- position: absolute;
156
- top: 0;
157
- left: 0;
158
- bottom: 0;
159
- transform: translateX(-100%);
160
- opacity: 0;
161
- transition:
162
- opacity 0.25s ease-in-out,
163
- transform 0s linear 0.3s;
164
- z-index: 1;
165
- }
166
-
167
- .sidebar {
168
- position: absolute;
169
- top: 0;
170
- left: 0;
171
- bottom: 0;
172
- transform: translateX(-100%);
173
- transition: transform 0.25s ease-in-out;
174
- z-index: 1;
175
-
176
- &::before {
177
- content: "";
178
- width: 100%;
179
- height: 100%;
180
- display: block;
181
- background-color: var(--bg-color);
182
- z-index: -1;
183
- position: absolute;
184
- }
185
-
186
- .sidebar-header,
187
- .sidebar-footer {
188
- padding: 0 20px;
189
- }
190
-
191
- .sidebar-main {
192
- .sidebar-main-content {
193
- padding: 20px;
194
- }
195
- }
196
- }
197
- .main {
198
- .main-toolbar {
199
- padding: 0 20px;
200
- gap: 14px;
201
-
202
- .menu-btn {
203
- display: flex;
204
- }
205
- }
206
-
207
- .main-view {
208
- .main-content {
209
- padding: 20px;
210
- }
211
- }
212
- .main-footer {
213
- padding: 0 20px;
214
- }
215
- }
216
-
217
- &.sidebar-position-right {
218
- &::before,
219
- .sidebar {
220
- left: initial;
221
- right: 0;
222
- transform: translateX(100%);
223
- }
224
-
225
- .main {
226
- .main-toolbar {
227
- flex-direction: row-reverse;
228
- justify-content: space-between;
229
- }
230
- }
231
- }
232
-
233
- &.sidebar-open {
234
- &::before {
235
- transform: translateX(0);
236
- opacity: 0.4;
237
- transition:
238
- opacity 0.25s ease-in-out,
239
- transform 0s linear 0s;
240
- }
241
-
242
- .sidebar {
243
- transform: translateX(0);
244
- box-shadow: 0px 0px 80px 0px rgba(0, 0, 0, 0.1);
245
- }
246
- }
247
- }
248
-}
249
-</style>
frontend/src/components/common/Percentage.vue
+8
-8
@@ -1,6 +1,6 @@
1
<template>
2
<div
3
- class="percentage flex items-center"
3
+ class="percentage flex items-center gap-2"
4
:class="[
5
{
6
color: useColor,
@@ -10,14 +10,14 @@
10
direction
11
]"
12
>
13
- <span class="progress mr-3" v-if="progress && progress === 'line'">
13
+ <span class="progress" v-if="progress && progress === 'line'">
14
<n-progress
15
type="line"
16
:status="direction === 'up' ? 'success' : 'error'"
17
:percentage="value"
18
:show-indicator="false"
19
:stroke-width="18"
20
- style="width: 50px"
20
+ class="!w-12"
21
/>
22
</span>
23
<span v-if="icon && icon === 'arrow'" class="flex items-center percentage-icon">
@@ -28,14 +28,14 @@
28
{{ direction === "up" ? "+" : "-" }}
29
</span>
30
<span>{{ value }}%</span>
31
- <span class="progress ml-3 flex items-center" v-if="progress && progress === 'circle'">
31
+ <span class="progress flex items-center" v-if="progress && progress === 'circle'">
32
<n-progress
33
type="circle"
34
:status="direction === 'up' ? 'success' : 'error'"
35
:percentage="value"
36
:show-indicator="false"
37
:stroke-width="18"
38
- style="width: 22px"
38
+ class="!w-5"
39
/>
40
</span>
41
</div>
@@ -46,9 +46,6 @@ import { toRefs } from "vue"
46
import { NProgress } from "naive-ui"
47
import Icon from "@/components/common/Icon.vue"
48
49
-const ChevronUp = "tabler:chevron-up"
50
-const ChevronDown = "tabler:chevron-down"
51
-
49
export interface PercentageProps {
50
value: number
51
useColor?: boolean
@@ -67,6 +64,9 @@ const props = withDefaults(defineProps<PercentageProps>(), {
64
progress: false
65
})
66
const { value, useColor, useBackground, useOpacity, icon, progress, direction } = toRefs(props)
67
+
68
+const ChevronUp = "tabler:chevron-up"
69
+const ChevronDown = "tabler:chevron-down"
70
</script>
71
72
<style scoped lang="scss">
frontend/src/components/common/SearchDialog.vue
+18
-25
@@ -1,13 +1,6 @@
1
<template>
2
<n-modal v-model:show="showSearchBox" class="search-box-modal">
3
- <n-card
4
- style="width: 600px"
5
- content-style="padding: 0;"
6
- :bordered="false"
7
- size="huge"
8
- role="dialog"
9
- aria-modal="true"
10
- >
3
+ <n-card content-class="!p-0" class="!w-150" :bordered="false" size="huge" role="dialog" aria-modal="true">
4
<div class="search-box" @keydown.up="prevItem()" @keydown.down="nextItem()">
5
<div class="search-input flex items-center">
6
<Icon :name="SearchIcon" :size="16"></Icon>
@@ -16,7 +9,7 @@
9
<Icon :name="CloseIcon" :size="20" @click="closeBox()" class="cursor-pointer"></Icon>
10
</div>
11
<n-divider />
19
- <n-scrollbar style="height: 400px" ref="scrollContent">
12
+ <n-scrollbar class="!h-96" ref="scrollContent">
13
<div class="conten-wrap">
14
<div class="group" v-for="group of filteredGroups" :key="group.name">
15
<div class="group-title">{{ group.name }}</div>
@@ -31,7 +24,7 @@
24
>
25
<div class="icon">
26
<n-avatar v-if="item.iconImage" round :size="28" :src="item.iconImage" />
34
- <Icon :name="item.iconName" v-if="item.iconName" :size="18"></Icon>
27
+ <Icon :name="item.iconName" v-if="item.iconName" :size="16"></Icon>
28
</div>
29
<div class="title grow">
30
<Highlighter
@@ -52,13 +45,13 @@
45
</n-scrollbar>
46
<n-divider />
47
<div class="hint-bar flex items-center justify-center">
55
- <div class="hint flex items-center justify-center">
48
+ <div class="hint flex items-center justify-center gap-1">
49
<div class="icon">
50
<Icon :name="ArrowEnterIcon" :size="12"></Icon>
51
</div>
52
<span class="label">to select</span>
53
</div>
61
- <div class="hint flex items-center justify-center">
54
+ <div class="hint flex items-center justify-center gap-1">
55
<div class="icon">
56
<Icon :name="ArrowSortIcon" :size="12"></Icon>
57
</div>
@@ -83,18 +76,6 @@ import Icon from "@/components/common/Icon.vue"
76
import { emitter } from "@/emitter"
77
import { useGoto } from "@/composables/useGoto"
78
86
-const SearchIcon = "ion:search-outline"
87
-const ArrowEnterIcon = "fluent:arrow-enter-left-24-regular"
88
-const ArrowSortIcon = "fluent:arrow-sort-24-regular"
89
-const FullScreenIcon = "fluent:full-screen-maximize-24-regular"
90
-const DarkModeIcon = "ion:moon-outline"
91
-const CloseIcon = "carbon:close"
92
-
93
-const ConnectorsIcon = "carbon:hybrid-networking"
94
-const AlertsIcon = "carbon:warning-hex"
95
-const SocAlertsIcon = "carbon:security"
96
-const CustomerIcon = "carbon:user-follow"
97
-
79
interface GroupItem {
80
iconName: string | null
81
iconImage: string | null
@@ -109,8 +90,20 @@ interface Group {
90
name: string
91
items: GroupItem[]
92
}
93
+
94
type Groups = Group[]
95
96
+const SearchIcon = "ion:search-outline"
97
+const ArrowEnterIcon = "fluent:arrow-enter-left-24-regular"
98
+const ArrowSortIcon = "fluent:arrow-sort-24-regular"
99
+const FullScreenIcon = "fluent:full-screen-maximize-24-regular"
100
+const DarkModeIcon = "ion:moon-outline"
101
+const CloseIcon = "carbon:close"
102
+const ConnectorsIcon = "carbon:hybrid-networking"
103
+const AlertsIcon = "carbon:warning-hex"
104
+const SocAlertsIcon = "carbon:security"
105
+const CustomerIcon = "carbon:user-follow"
106
+
107
const showSearchBox = ref(false)
108
const search = ref("")
109
const activeItem = ref<null | string | number>(null)
@@ -393,6 +386,7 @@ onMounted(() => {
386
font-size: 12px;
387
gap: 20px;
388
padding: 10px 0;
389
+
390
.icon {
391
background-color: var(--code-color);
392
width: 18px;
@@ -400,7 +394,6 @@ onMounted(() => {
394
padding-top: 1px;
395
text-align: center;
396
border-radius: 4px;
403
- margin-right: 5px;
397
display: flex;
398
align-items: center;
399
justify-content: center;
frontend/src/components/common/SegmentedPage.vue
new
+442
@@ -0,0 +1,442 @@
1
+<template>
2
+ <n-split
3
+ direction="horizontal"
4
+ :default-size="splitDefault"
5
+ :min="0"
6
+ :max="1"
7
+ :resize-trigger-size="0"
8
+ :disabled="!enableResize || splitDisabled"
9
+ ref="splitPane"
10
+ class="wrapper flex grow"
11
+ :class="[{ 'sidebar-open': sidebarOpen }, `sidebar-position-${sidebarPosition}`]"
12
+ >
13
+ <template #1>
14
+ <div class="sidebar flex flex-col" ref="sidebar" v-if="sidebarAvailable">
15
+ <div class="sidebar-header flex items-center" v-if="$slots['sidebar-header']">
16
+ <slot name="sidebar-header"></slot>
17
+ </div>
18
+ <div class="sidebar-main grow" v-if="$slots['sidebar-content']">
19
+ <n-scrollbar class="max-h-full">
20
+ <div class="sidebar-main-content" :style="sidebarContentStyle" :class="sidebarContentClass">
21
+ <slot name="sidebar-content"></slot>
22
+ </div>
23
+ </n-scrollbar>
24
+ </div>
25
+ <div class="sidebar-footer flex items-center" v-if="$slots['sidebar-footer']">
26
+ <slot name="sidebar-footer"></slot>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <template #resize-trigger>
32
+ <div class="split-trigger">
33
+ <div class="split-trigger-icon">
34
+ <Icon :name="SplitIcon" :size="12"></Icon>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <template #2>
40
+ <div class="main flex-grow flex flex-col">
41
+ <div class="main-toolbar flex items-center" v-if="$slots['main-toolbar']">
42
+ <div class="menu-btn flex justify-center opacity-50" v-if="sidebarAvailable && !hideMenuBtn">
43
+ <n-button text @click="sidebarOpen = true">
44
+ <Icon :size="24" :name="MenuIcon"></Icon>
45
+ </n-button>
46
+ </div>
47
+
48
+ <div class="grow">
49
+ <slot name="main-toolbar"></slot>
50
+ </div>
51
+ </div>
52
+ <div class="main-view grow" :class="{ 'no-container-query': disableContainerQuery }">
53
+ <n-scrollbar class="max-h-full" v-if="useMainScroll" ref="mainScrollbar">
54
+ <div class="main-content" :style="mainContentStyle" :class="mainContentClass">
55
+ <slot name="main-content"></slot>
56
+ </div>
57
+ </n-scrollbar>
58
+ <div class="main-content" :style="mainContentStyle" :class="mainContentClass" v-else>
59
+ <slot name="main-content"></slot>
60
+ </div>
61
+ </div>
62
+ <div class="main-footer flex items-center" v-if="$slots['main-footer']">
63
+ <div class="wrap">
64
+ <slot name="main-footer"></slot>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="main-overlay" v-if="sidebarOpen"></div>
69
+ </div>
70
+ </template>
71
+ </n-split>
72
+</template>
73
+
74
+<script setup lang="ts">
75
+import { computed, onMounted, ref, toRefs, useSlots, watch } from "vue"
76
+import { NScrollbar, NButton, NSplit } from "naive-ui"
77
+import { onClickOutside, useWindowSize } from "@vueuse/core"
78
+import Icon from "@/components/common/Icon.vue"
79
+
80
+type SidebarPosition = "left" | "right"
81
+
82
+export interface CtxSegmentedPage {
83
+ mainScrollbar: typeof NScrollbar | null
84
+ closeSidebar: () => void
85
+ openSidebar: () => void
86
+}
87
+
88
+const emit = defineEmits<{
89
+ (e: "mounted", value: CtxSegmentedPage): void
90
+ (e: "sidebar", value: boolean): void
91
+}>()
92
+
93
+const props = withDefaults(
94
+ defineProps<{
95
+ sidebarPosition?: SidebarPosition
96
+ hideMenuBtn?: boolean
97
+ useMainScroll?: boolean
98
+ mainContentStyle?: string
99
+ mainContentClass?: string
100
+ sidebarContentStyle?: string
101
+ sidebarContentClass?: string
102
+ enableResize?: boolean
103
+ disableContainerQuery?: boolean
104
+ }>(),
105
+ { sidebarPosition: "left", useMainScroll: true }
106
+)
107
+const {
108
+ sidebarPosition,
109
+ hideMenuBtn,
110
+ useMainScroll,
111
+ mainContentStyle,
112
+ mainContentClass,
113
+ sidebarContentStyle,
114
+ sidebarContentClass
115
+} = toRefs(props)
116
+
117
+const MenuIcon = "ph:list-light"
118
+const SplitIcon = "carbon:draggable"
119
+
120
+const splitPane = ref()
121
+const splitDefault = ref(0.3)
122
+const splitDisabled = ref(false)
123
+
124
+const slots = useSlots()
125
+const sidebarOpen = ref(false)
126
+const sidebar = ref(null)
127
+const mainScrollbar = ref<typeof NScrollbar | null>(null)
128
+const sidebarAvailable = computed(
129
+ () => !!slots["sidebar-header"] || !!slots["sidebar-content"] || !!slots["sidebar-footer"]
130
+)
131
+
132
+onClickOutside(sidebar, () => closeSidebar())
133
+
134
+function closeSidebar() {
135
+ sidebarOpen.value = false
136
+}
137
+
138
+function openSidebar() {
139
+ sidebarOpen.value = true
140
+}
141
+
142
+watch(
143
+ sidebarOpen,
144
+ val => {
145
+ emit("sidebar", val)
146
+ },
147
+ { immediate: true }
148
+)
149
+
150
+const { width } = useWindowSize()
151
+
152
+watch(
153
+ width,
154
+ val => {
155
+ splitDefault.value = val <= 700 ? 0 : 0.3
156
+ splitDisabled.value = val <= 700
157
+ },
158
+ { immediate: true }
159
+)
160
+
161
+onMounted(() => {
162
+ emit("mounted", {
163
+ mainScrollbar: mainScrollbar.value,
164
+ closeSidebar,
165
+ openSidebar
166
+ })
167
+})
168
+</script>
169
+
170
+<style lang="scss" scoped>
171
+.wrapper {
172
+ --mb-toolbar-height: 70px;
173
+ position: relative;
174
+ height: 100%;
175
+ overflow: hidden;
176
+ border-radius: var(--border-radius);
177
+ border: 1px solid var(--border-color);
178
+ background-color: var(--bg-color);
179
+ direction: ltr;
180
+
181
+ :deep() {
182
+ .n-split-pane-1,
183
+ .n-split-pane-2 {
184
+ overflow: hidden;
185
+ height: 100%;
186
+ }
187
+ .n-split-pane-1 {
188
+ min-width: 250px;
189
+ max-width: min(450px, calc(100% - 400px));
190
+ }
191
+ }
192
+
193
+ .split-trigger {
194
+ height: 100%;
195
+ width: 3px;
196
+ display: flex;
197
+ align-items: center;
198
+ position: relative;
199
+ z-index: 1;
200
+ left: -2px;
201
+ transition: background-color 0.3s var(--bezier-ease);
202
+
203
+ .split-trigger-icon {
204
+ background-color: var(--border-color);
205
+ border-radius: var(--border-radius-small);
206
+ height: 18px;
207
+ display: flex;
208
+ justify-content: center;
209
+ align-items: center;
210
+ position: relative;
211
+ margin-left: -5px;
212
+ z-index: 1;
213
+ transition: background-color 0.3s var(--bezier-ease);
214
+ }
215
+
216
+ &:hover {
217
+ background-color: var(--primary-010-color);
218
+
219
+ .split-trigger-icon {
220
+ background-color: var(--primary-010-color);
221
+ }
222
+ }
223
+ }
224
+
225
+ .sidebar {
226
+ background-color: var(--bg-secondary-color);
227
+ min-width: 250px;
228
+ //max-width: calc(100% - 400px);
229
+ //width: 35%;
230
+ height: 100%;
231
+ overflow: hidden;
232
+ border-right: 1px solid var(--border-color);
233
+
234
+ .sidebar-header {
235
+ border-block-end: var(--border-small-050);
236
+ min-height: var(--mb-toolbar-height);
237
+ height: var(--mb-toolbar-height);
238
+ padding: 0 30px;
239
+ }
240
+
241
+ .sidebar-main {
242
+ overflow: hidden;
243
+
244
+ .sidebar-main-content {
245
+ padding: 30px;
246
+ }
247
+ }
248
+
249
+ .sidebar-footer {
250
+ border-block-start: var(--border-small-050);
251
+ min-height: var(--mb-toolbar-height);
252
+ padding: 0 30px;
253
+ }
254
+ }
255
+
256
+ .main {
257
+ background-color: var(--bg-color);
258
+ position: relative;
259
+ height: 100%;
260
+
261
+ .main-overlay {
262
+ position: absolute;
263
+ width: 100%;
264
+ height: 100%;
265
+ z-index: 2;
266
+ top: 0;
267
+ left: 0;
268
+ }
269
+
270
+ .main-toolbar {
271
+ border-block-end: var(--border-small-050);
272
+ min-height: var(--mb-toolbar-height);
273
+ height: var(--mb-toolbar-height);
274
+ padding: 0 30px;
275
+ gap: 18px;
276
+ line-height: 1.3;
277
+ container-type: inline-size;
278
+
279
+ .menu-btn {
280
+ display: none;
281
+ }
282
+ }
283
+
284
+ .main-view {
285
+ overflow: hidden;
286
+ &:not(.no-container-query) {
287
+ container-type: inline-size;
288
+ }
289
+
290
+ .main-content {
291
+ padding: 30px;
292
+ }
293
+ }
294
+
295
+ .main-footer {
296
+ container-type: inline-size;
297
+ border-block-start: var(--border-small-050);
298
+ padding: 0 30px;
299
+
300
+ .wrap {
301
+ min-height: calc(var(--mb-toolbar-height) - 1px);
302
+ width: 100%;
303
+ display: flex;
304
+ align-items: center;
305
+ }
306
+ }
307
+ }
308
+
309
+ &.sidebar-position-right {
310
+ flex-direction: row-reverse;
311
+
312
+ .sidebar {
313
+ border-right: none;
314
+ border-left: 1px solid var(--border-color);
315
+ }
316
+ }
317
+
318
+ @media (max-width: 700px) {
319
+ --mb-toolbar-height: 62px;
320
+ height: 100%;
321
+ overflow: hidden;
322
+ border-radius: 0;
323
+ border: none;
324
+
325
+ :deep() {
326
+ .n-split-pane-1 {
327
+ min-width: 0;
328
+ max-width: 0;
329
+ }
330
+ }
331
+
332
+ &::before {
333
+ content: "";
334
+ width: 100vw;
335
+ display: block;
336
+ background-color: var(--bg-body);
337
+ position: absolute;
338
+ top: 0;
339
+ left: 0;
340
+ bottom: 0;
341
+ transform: translateX(-100%);
342
+ opacity: 0;
343
+ transition:
344
+ opacity 0.25s ease-in-out,
345
+ transform 0s linear 0.3s;
346
+ z-index: 1;
347
+ }
348
+
349
+ .sidebar {
350
+ position: absolute;
351
+ top: 0;
352
+ left: 0;
353
+ bottom: 0;
354
+ transform: translateX(-100%);
355
+ transition: transform 0.25s ease-in-out;
356
+ z-index: 3;
357
+ min-width: 300px;
358
+ max-width: min(450px, 80vw);
359
+
360
+ &::before {
361
+ content: "";
362
+ width: 100%;
363
+ height: 100%;
364
+ display: block;
365
+ background-color: var(--bg-color);
366
+ z-index: -1;
367
+ position: absolute;
368
+ }
369
+
370
+ .sidebar-header,
371
+ .sidebar-footer {
372
+ padding: 0 20px;
373
+ }
374
+
375
+ .sidebar-main {
376
+ .sidebar-main-content {
377
+ padding: 20px;
378
+ }
379
+ }
380
+ }
381
+ .main {
382
+ .main-toolbar {
383
+ padding: 0 20px;
384
+ gap: 14px;
385
+
386
+ .menu-btn {
387
+ display: flex;
388
+ }
389
+ }
390
+
391
+ .main-view {
392
+ .main-content {
393
+ padding: 20px;
394
+ }
395
+ }
396
+ .main-footer {
397
+ padding: 0 20px;
398
+ }
399
+ }
400
+
401
+ &.sidebar-position-right {
402
+ &::before,
403
+ .sidebar {
404
+ left: initial;
405
+ right: 0;
406
+ transform: translateX(100%);
407
+ }
408
+
409
+ .main {
410
+ .main-toolbar {
411
+ flex-direction: row-reverse;
412
+ justify-content: space-between;
413
+ }
414
+ }
415
+ }
416
+
417
+ &.sidebar-open {
418
+ &::before {
419
+ transform: translateX(0);
420
+ opacity: 0.4;
421
+ transition:
422
+ opacity 0.25s ease-in-out,
423
+ transform 0s linear 0s;
424
+ }
425
+
426
+ .sidebar {
427
+ transform: translateX(0);
428
+ box-shadow: 0px 0px 80px 0px rgba(0, 0, 0, 0.1);
429
+ }
430
+ }
431
+ }
432
+}
433
+
434
+.direction-rtl {
435
+ .wrapper {
436
+ .sidebar,
437
+ .main {
438
+ direction: rtl;
439
+ }
440
+ }
441
+}
442
+</style>
frontend/src/components/monitoringAlerts/Item.vue
+4
-5
@@ -92,11 +92,6 @@ const loading = computed(() => loadingDelete.value || loadingInvoke.value)
92
93
<style lang="scss" scoped>
94
.monitoring-alerts-item {
95
- &:not(.embedded) {
96
- border-radius: var(--border-radius);
97
- background-color: var(--bg-color);
98
- border: var(--border-small-050);
99
- }
95
border-top: var(--border-small-050);
96
transition: all 0.2s var(--bezier-ease);
97
min-height: 100px;
@@ -122,6 +117,10 @@ const loading = computed(() => loadingDelete.value || loadingInvoke.value)
117
}
118
119
&:not(.embedded) {
120
+ border-radius: var(--border-radius);
121
+ background-color: var(--bg-color);
122
+ border: var(--border-small-050);
123
+
124
&:hover {
125
border-color: var(--primary-color);
126
}
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItem.vue
+4
-5
@@ -253,11 +253,6 @@ onBeforeMount(() => {
253
254
<style lang="scss" scoped>
255
.soc-alert-item {
256
- &:not(.embedded) {
257
- border-radius: var(--border-radius);
258
- background-color: var(--bg-color);
259
- border: var(--border-small-050);
260
- }
256
transition: all 0.2s var(--bezier-ease);
257
258
.soc-alert-info {
@@ -311,6 +306,10 @@ onBeforeMount(() => {
306
}
307
308
&:not(.embedded) {
309
+ border-radius: var(--border-radius);
310
+ background-color: var(--bg-color);
311
+ border: var(--border-small-050);
312
+
313
&:hover,
314
&.highlight {
315
border-color: var(--primary-color);
frontend/src/components/soc/SocCases/SocCaseItem.vue
+4
-5
@@ -445,11 +445,6 @@ onBeforeMount(() => {
445
446
<style lang="scss" scoped>
447
.soc-case-item {
448
- &:not(.embedded) {
449
- border-radius: var(--border-radius);
450
- background-color: var(--bg-color);
451
- border: var(--border-small-050);
452
- }
448
border-top: var(--border-small-050);
449
transition: all 0.2s var(--bezier-ease);
450
min-height: 100px;
@@ -506,6 +501,10 @@ onBeforeMount(() => {
501
}
502
503
&:not(.embedded) {
504
+ border-radius: var(--border-radius);
505
+ background-color: var(--bg-color);
506
+ border: var(--border-small-050);
507
+
508
&:hover {
509
border-color: var(--primary-color);
510
}
frontend/src/composables/useHideLayoutFooter.ts
+4
-4
@@ -6,14 +6,14 @@ import { onBeforeMount, onBeforeUnmount } from "vue"
6
// at the moment this worker around permit to hide Layout Footer
7
8
export function useHideLayoutFooter() {
9
- const store = useThemeStore()
9
+ const themeStore = useThemeStore()
10
11
- if (store.isFooterShown) {
11
+ if (themeStore.isFooterShown) {
12
onBeforeMount(() => {
13
- store.setFooterShow(false)
13
+ themeStore.setFooterShow(false)
14
})
15
onBeforeUnmount(() => {
16
- store.setFooterShow(true)
16
+ themeStore.setFooterShow(true)
17
})
18
}
19
}
frontend/src/composables/useStoreI18n.ts
+5
-4
@@ -3,19 +3,20 @@ import { useLocalesStore } from "@/stores/i18n"
3
4
export function useStoreI18n() {
5
const { t } = useI18n()
6
+ const localesStore = useLocalesStore()
7
8
return {
9
initLocale: (): string => {
9
- return useLocalesStore().locale
10
+ return localesStore.locale
11
},
12
getAvailableLocales: (): string[] => {
12
- return useLocalesStore().available
13
+ return localesStore.available
14
},
15
getLocale: (): string => {
15
- return useLocalesStore().locale
16
+ return localesStore.locale
17
},
18
setLocale: (newLocale: string): string => {
18
- useLocalesStore().setLocale(newLocale)
19
+ localesStore.setLocale(newLocale)
20
return newLocale
21
},
22
t
frontend/src/composables/useThemeSwitch.ts
+3
-1
@@ -1,9 +1,11 @@
1
import { useThemeStore } from "@/stores/theme"
2
3
export function useThemeSwitch() {
4
+ const themeStore = useThemeStore()
5
+
6
return {
7
toggle: () => {
6
- useThemeStore().toggleTheme()
8
+ themeStore.toggleTheme()
9
}
10
}
11
}
frontend/src/design-tokens.json
+1
-1
@@ -164,4 +164,4 @@
164
"hover050": "rgba(255, 255, 255, 0.5)"
165
}
166
}
167
-}
167
+}
\ No newline at end of file
frontend/src/layouts/Blank/MainContainer.vue
+2
-5
@@ -14,14 +14,11 @@ import { NScrollbar } from "naive-ui"
14
import { useRoute, useRouter } from "vue-router"
15
import { useThemeStore } from "@/stores/theme"
16
17
-defineOptions({
18
- name: "MainContainer"
19
-})
20
-
17
+const themeStore = useThemeStore()
18
const router = useRouter()
19
const route = useRoute()
20
const routeName = computed<string>(() => route.name?.toString() || "")
24
-const boxed = computed(() => useThemeStore().isBoxed)
21
+const boxed = computed(() => themeStore.isBoxed)
22
const scrollbar = ref()
23
24
onMounted(() => {
frontend/src/layouts/Blank/index.vue
-4
@@ -8,10 +8,6 @@
8
9
<script lang="ts" setup>
10
import MainContainer from "./MainContainer.vue"
11
-
12
-defineOptions({
13
- name: "VerticalNav"
14
-})
11
</script>
12
13
<style lang="scss" scoped>
frontend/src/layouts/HorizontalNav/HeaderBar.vue
+15
-5
@@ -14,11 +14,8 @@ import Navbar from "@/layouts/common/Navbar/index.vue"
14
import Logo from "@/layouts/common/Logo.vue"
15
import { useThemeStore } from "@/stores/theme"
16
17
-defineOptions({
18
- name: "HeaderBar"
19
-})
20
-
21
-const isDark = computed<boolean>(() => useThemeStore().isThemeDark)
17
+const themeStore = useThemeStore()
18
+const isDark = computed<boolean>(() => themeStore.isThemeDark)
19
</script>
20
21
<style lang="scss" scoped>
@@ -44,4 +41,17 @@ const isDark = computed<boolean>(() => useThemeStore().isThemeDark)
41
display: none;
42
}
43
}
44
+
45
+.direction-rtl {
46
+ .header-bar {
47
+ direction: rtl;
48
+ padding-left: 0;
49
+ padding-right: var(--view-padding);
50
+
51
+ .nav {
52
+ margin-right: 0;
53
+ margin-left: var(--view-padding);
54
+ }
55
+ }
56
+}
57
</style>
frontend/src/layouts/HorizontalNav/MainContainer.vue
+11
-20
@@ -5,7 +5,7 @@
5
<div class="view" :class="[{ boxed }, `route-${routeName}`]">
6
<slot></slot>
7
</div>
8
- <FooterEL :boxed="boxed" v-if="footerShown" />
8
+ <MainFooter :boxed="boxed" v-if="footerShown" />
9
</n-scrollbar>
10
</div>
11
</template>
@@ -15,20 +15,17 @@ import { computed, ref, onMounted } from "vue"
15
import { NScrollbar } from "naive-ui"
16
import { useRoute, useRouter } from "vue-router"
17
import Toolbar from "@/layouts/common/Toolbar/index.vue"
18
-import FooterEL from "@/layouts/common/FooterEL.vue"
18
+import MainFooter from "@/layouts/common/MainFooter.vue"
19
import { useThemeStore } from "@/stores/theme"
20
21
-defineOptions({
22
- name: "MainContainer"
23
-})
24
-
21
+const themeStore = useThemeStore()
22
const router = useRouter()
23
const route = useRoute()
24
const routeName = computed<string>(() => route.name?.toString() || "")
28
-const sidebarCollapsed = computed<boolean>(() => useThemeStore().sidebar.collapsed)
29
-const footerShown = computed(() => useThemeStore().isFooterShown)
30
-const boxed = computed<boolean>(() => useThemeStore().isBoxed)
31
-const toolbarBoxed = computed(() => useThemeStore().isToolbarBoxed)
25
+const sidebarCollapsed = computed<boolean>(() => themeStore.sidebar.collapsed)
26
+const footerShown = computed(() => themeStore.isFooterShown)
27
+const boxed = computed<boolean>(() => themeStore.isBoxed)
28
+const toolbarBoxed = computed(() => themeStore.isToolbarBoxed)
29
const scrollbar = ref()
30
31
onMounted(() => {
@@ -82,22 +79,16 @@ onMounted(() => {
79
}
80
81
@media (max-width: $sidebar-bp) {
85
- padding-left: 0px;
82
+ transition: all var(--sidebar-anim-ease) var(--sidebar-anim-duration);
83
84
.view {
85
padding-top: calc(var(--view-padding) / 2);
86
}
87
91
- &.sidebar-collapsed {
92
- padding-left: 0px;
93
- }
94
-
95
- transition: all var(--sidebar-anim-ease) var(--sidebar-anim-duration);
96
-
88
&.sidebar-opened {
89
//transform: scale(0.8) translateX(100%) rotateY(35deg);
99
- transform-origin: center left;
100
- border-radius: 16px;
90
+ //transform-origin: center left;
91
+ //border-radius: 16px;
92
overflow: hidden;
93
opacity: 0.5;
94
}
@@ -106,7 +97,7 @@ onMounted(() => {
97
@media (min-width: ($sidebar-bp + 1px)) {
98
:deep() {
99
header.toolbar {
109
- .logo {
100
+ .logo-box {
101
display: none;
102
}
103
}
frontend/src/layouts/HorizontalNav/Sidebar.vue
+22
-11
@@ -24,13 +24,10 @@ import SidebarHeader from "./SidebarHeader.vue"
24
import SidebarFooter from "./SidebarFooter.vue"
25
import { useThemeStore } from "@/stores/theme"
26
27
-defineOptions({
28
- name: "Sidebar"
29
-})
30
-
27
+const themeStore = useThemeStore()
28
const sidebar = ref(null)
29
const sidebarHovered = useElementHover(sidebar)
33
-const sidebarCollapsed = computed<boolean>(() => useThemeStore().sidebar.collapsed)
30
+const sidebarCollapsed = computed<boolean>(() => themeStore.sidebar.collapsed)
31
const sidebarClosed = computed<boolean>(() => !sidebarHovered.value && sidebarCollapsed.value)
32
33
function clickListener() {
@@ -38,7 +35,7 @@ function clickListener() {
35
onClickOutside(sidebar, e => {
36
if (!sidebarCollapsed.value) {
37
e.stopPropagation()
41
- useThemeStore().closeSidebar()
38
+ themeStore.closeSidebar()
39
}
40
})
41
}
@@ -74,7 +71,7 @@ onMounted(() => {
71
left: 0;
72
//bottom: 0;
73
//border-right:var(--border-small-100);
77
- padding-right: 1px;
74
+ //padding-right: 1px;
75
width: var(--sidebar-open-width);
76
height: 100vh;
77
height: 100svh;
@@ -86,15 +83,14 @@ onMounted(() => {
83
box-shadow var(--sidebar-anim-ease) var(--sidebar-anim-duration),
84
color 0.3s var(--bezier-ease) 0s,
85
background-color 0.3s var(--bezier-ease) 0s;
86
+ z-index: -1;
87
+ transition: all 0.3s var(--bezier-ease) 0s;
88
+ transform: translateX(-100%);
89
90
.sidebar-wrap {
91
overflow: hidden;
92
}
93
94
- z-index: -1;
95
- transition: all 0.3s var(--bezier-ease) 0s;
96
- transform: translateX(-100%);
97
-
94
@media (max-width: $sidebar-bp) {
95
&.opened {
96
z-index: 4;
@@ -107,4 +103,19 @@ onMounted(() => {
103
opacity: 0.15;
104
}
105
}
106
+
107
+.direction-rtl {
108
+ .sidebar {
109
+ left: unset;
110
+ right: 0;
111
+
112
+ @media (max-width: $sidebar-bp) {
113
+ transform: translateX(100%);
114
+
115
+ &.opened {
116
+ transform: translateX(0%);
117
+ }
118
+ }
119
+ }
120
+}
121
</style>
frontend/src/layouts/HorizontalNav/SidebarFooter.vue
+3
-8
@@ -10,12 +10,6 @@ import { NMenu } from "naive-ui"
10
import { useThemeStore } from "@/stores/theme"
11
import { renderIcon } from "@/utils"
12
13
-const ContactIcon = "ic:outline-alternate-email"
14
-
15
-defineOptions({
16
- name: "SidebarFooter"
17
-})
18
-
13
const props = withDefaults(
14
defineProps<{
15
collapsed?: boolean
@@ -24,6 +18,7 @@ const props = withDefaults(
18
)
19
const { collapsed } = toRefs(props)
20
21
+const ContactIcon = "ic:outline-alternate-email"
22
const menuOptions = ref([
23
{
24
label: () =>
@@ -40,8 +35,8 @@ const menuOptions = ref([
35
icon: renderIcon(ContactIcon)
36
}
37
])
43
-
44
-const collapsedWidth = computed<number>(() => useThemeStore().sidebar.closeWidth - 16)
38
+const themeStore = useThemeStore()
39
+const collapsedWidth = computed<number>(() => themeStore.sidebar.closeWidth - 16)
40
</script>
41
42
<style lang="scss" scoped>
frontend/src/layouts/HorizontalNav/SidebarHeader.vue
+29
-10
@@ -31,26 +31,26 @@ import Icon from "@/components/common/Icon.vue"
31
import { Icon as Iconify } from "@iconify/vue"
32
import Logo from "@/layouts/common/Logo.vue"
33
34
-const CircleRegular = "fa6-regular:circle"
35
-const DotCircleRegular = "fa6-regular:circle-dot"
36
-const CloseOutline = "fa6-regular:circle-xmark"
37
-
34
const props = defineProps<{
35
logoMini?: boolean
36
}>()
37
const { logoMini } = toRefs(props)
38
39
+const CircleRegular = "fa6-regular:circle"
40
+const DotCircleRegular = "fa6-regular:circle-dot"
41
+const CloseOutline = "carbon:chevron-left"
42
+const themeStore = useThemeStore()
43
const showPin = computed<boolean>(() => !logoMini.value)
44
const sidebarCollapsed = computed({
45
get(): boolean {
46
- return useThemeStore().sidebar.collapsed
46
+ return themeStore.sidebar.collapsed
47
},
48
set() {
49
- useThemeStore().toggleSidebar()
49
+ themeStore.toggleSidebar()
50
}
51
})
52
-const isDark = computed<boolean>(() => useThemeStore().isThemeDark)
53
-const isLight = computed(() => useThemeStore().isThemeLight)
52
+const isDark = computed<boolean>(() => themeStore.isThemeDark)
53
+const isLight = computed(() => themeStore.isThemeLight)
54
</script>
55
56
<style lang="scss" scoped>
@@ -113,11 +113,18 @@ const isLight = computed(() => useThemeStore().isThemeLight)
113
:deep() {
114
.n-icon {
115
cursor: pointer;
116
- opacity: 0.3;
117
- transition: opacity var(--sidebar-anim-ease) var(--sidebar-anim-duration);
116
+
117
+ .i-large {
118
+ opacity: 0.3;
119
+ transition: opacity var(--sidebar-anim-ease) var(--sidebar-anim-duration);
120
+ }
121
122
&:hover {
123
opacity: 1;
124
+
125
+ .i-large {
126
+ opacity: 1;
127
+ }
128
}
129
}
130
}
@@ -143,4 +150,16 @@ const isLight = computed(() => useThemeStore().isThemeLight)
150
}
151
}
152
}
153
+
154
+.direction-rtl {
155
+ .sidebar-header {
156
+ .sidebar-pin {
157
+ .i-small {
158
+ svg {
159
+ transform: rotateY(180deg);
160
+ }
161
+ }
162
+ }
163
+ }
164
+}
165
</style>
frontend/src/layouts/HorizontalNav/index.vue
-4
@@ -13,10 +13,6 @@ import HeaderBar from "./HeaderBar.vue"
13
import Sidebar from "./Sidebar.vue"
14
import MainContainer from "./MainContainer.vue"
15
import "./main.scss"
16
-
17
-defineOptions({
18
- name: "VerticalNav"
19
-})
16
</script>
17
18
<style lang="scss" scoped>
frontend/src/layouts/VerticalNav/MainContainer.vue
deleted
-103
@@ -1,103 +0,0 @@
1
-<template>
2
- <div id="main" class="main" :class="{ 'sidebar-collapsed': sidebarCollapsed, 'sidebar-opened': !sidebarCollapsed }">
3
- <n-scrollbar ref="scrollbar">
4
- <Toolbar :boxed="toolbarBoxed" />
5
- <div class="view" :class="[{ boxed }, `route-${routeName}`]">
6
- <slot></slot>
7
- </div>
8
- <FooterEL :boxed="boxed" v-if="footerShown" />
9
- </n-scrollbar>
10
- </div>
11
-</template>
12
-
13
-<script lang="ts" setup>
14
-import { computed, ref, onMounted } from "vue"
15
-import { NScrollbar } from "naive-ui"
16
-import { useRoute, useRouter } from "vue-router"
17
-import Toolbar from "@/layouts/common/Toolbar/index.vue"
18
-import FooterEL from "@/layouts/common/FooterEL.vue"
19
-import { useThemeStore } from "@/stores/theme"
20
-
21
-defineOptions({
22
- name: "MainContainer"
23
-})
24
-
25
-const router = useRouter()
26
-const route = useRoute()
27
-const routeName = computed<string>(() => route.name?.toString() || "")
28
-const sidebarCollapsed = computed(() => useThemeStore().sidebar.collapsed)
29
-const boxed = computed(() => useThemeStore().isBoxed)
30
-const footerShown = computed(() => useThemeStore().isFooterShown)
31
-const toolbarBoxed = computed(() => useThemeStore().isToolbarBoxed)
32
-const scrollbar = ref()
33
-
34
-onMounted(() => {
35
- router.afterEach(() => {
36
- if (scrollbar?.value?.scrollTo) {
37
- scrollbar?.value.scrollTo({ top: 0 })
38
- }
39
- })
40
-})
41
-</script>
42
-
43
-<style lang="scss" scoped>
44
-@import "./variables";
45
-
46
-.main {
47
- width: 100%;
48
- padding-left: var(--sidebar-open-width);
49
- position: relative;
50
- transition: padding var(--sidebar-anim-ease) var(--sidebar-anim-duration);
51
- background-color: var(--bg-body);
52
-
53
- :deep() {
54
- & > .n-scrollbar {
55
- & > .n-scrollbar-rail {
56
- top: calc(var(--toolbar-height) + 2px);
57
- }
58
- }
59
- }
60
- &.sidebar-collapsed {
61
- padding-left: var(--sidebar-close-width);
62
- }
63
-
64
- .view {
65
- padding: var(--view-padding);
66
- padding-top: calc(var(--view-padding) / 2);
67
- width: 100%;
68
-
69
- &.boxed {
70
- max-width: var(--boxed-width);
71
- margin: 0 auto;
72
- }
73
- }
74
-
75
- @media (max-width: $sidebar-bp) {
76
- padding-left: 0px;
77
- &.sidebar-collapsed {
78
- padding-left: 0px;
79
- }
80
-
81
- transition: all var(--sidebar-anim-ease) var(--sidebar-anim-duration);
82
-
83
- &.sidebar-opened {
84
- //transform: scale(0.8) translateX(100%) rotateY(35deg);
85
- //transform: scale(0.8) translateX(100%);
86
- transform-origin: center left;
87
- border-radius: 16px;
88
- overflow: hidden;
89
- opacity: 0.5;
90
- }
91
- }
92
-
93
- @media (min-width: ($sidebar-bp + 1px)) {
94
- :deep() {
95
- header.toolbar {
96
- .logo {
97
- display: none;
98
- }
99
- }
100
- }
101
- }
102
-}
103
-</style>
frontend/src/layouts/VerticalNav/Sidebar.vue
deleted
-121
@@ -1,121 +0,0 @@
1
-<template>
2
- <aside
3
- id="sidebar"
4
- class="sidebar flex flex-col"
5
- :class="{ collapsed: sidebarCollapsed, opened: !sidebarCollapsed }"
6
- >
7
- <div class="sidebar-wrap grow flex flex-col" ref="sidebar">
8
- <SidebarHeader :logo-mini="sidebarClosed" />
9
- <n-scrollbar>
10
- <Navbar :collapsed="sidebarClosed"></Navbar>
11
- </n-scrollbar>
12
- <SidebarFooter :collapsed="sidebarClosed" />
13
- </div>
14
- </aside>
15
-</template>
16
-
17
-<script lang="ts" setup>
18
-import { computed, onMounted, ref, watch } from "vue"
19
-import { NScrollbar } from "naive-ui"
20
-import { isMobile } from "@/utils"
21
-import { onClickOutside, useElementHover } from "@vueuse/core"
22
-import Navbar from "@/layouts/common/Navbar/index.vue"
23
-import SidebarHeader from "./SidebarHeader.vue"
24
-import SidebarFooter from "./SidebarFooter.vue"
25
-import { useThemeStore } from "@/stores/theme"
26
-
27
-defineOptions({
28
- name: "Sidebar"
29
-})
30
-
31
-const sidebar = ref(null)
32
-const sidebarHovered = useElementHover(sidebar)
33
-
34
-const sidebarCollapsed = computed(() => useThemeStore().sidebar.collapsed)
35
-const sidebarClosed = computed(() => !sidebarHovered.value && sidebarCollapsed.value)
36
-
37
-function clickListener() {
38
- if (sidebar.value) {
39
- onClickOutside(sidebar, e => {
40
- if (!sidebarCollapsed.value) {
41
- e.stopPropagation()
42
- useThemeStore().closeSidebar()
43
- }
44
- })
45
- }
46
-}
47
-
48
-onMounted(() => {
49
- watch(
50
- sidebarCollapsed,
51
- val => {
52
- if (val) {
53
- if (isMobile()) {
54
- sidebarHovered.value = false
55
- }
56
- }
57
- },
58
- {
59
- immediate: true
60
- }
61
- )
62
-
63
- if (window.innerWidth <= 700) {
64
- clickListener()
65
- }
66
-})
67
-</script>
68
-
69
-<style lang="scss" scoped>
70
-@import "./variables";
71
-
72
-.sidebar {
73
- position: fixed;
74
- z-index: 4;
75
- top: 0;
76
- left: 0;
77
- //bottom: 0;
78
- //border-right:var(--border-small-100);
79
- padding-right: 1px;
80
- width: var(--sidebar-open-width);
81
- height: 100vh;
82
- height: 100svh;
83
- overflow-x: hidden;
84
- overflow-y: auto;
85
- background-color: var(--bg-sidebar);
86
- transition:
87
- width var(--sidebar-anim-ease) var(--sidebar-anim-duration),
88
- box-shadow var(--sidebar-anim-ease) var(--sidebar-anim-duration),
89
- color 0.3s var(--bezier-ease) 0s,
90
- background-color 0.3s var(--bezier-ease) 0s;
91
-
92
- .sidebar-wrap {
93
- overflow: hidden;
94
- }
95
-
96
- :deep(.n-scrollbar-rail) {
97
- opacity: 0.15;
98
- }
99
-
100
- &.collapsed {
101
- width: var(--sidebar-close-width);
102
-
103
- &:hover {
104
- width: var(--sidebar-open-width);
105
- box-shadow: 0px 0px 80px 0px rgba(0, 0, 0, 0.2);
106
- }
107
- }
108
-
109
- @media (max-width: $sidebar-bp) {
110
- z-index: -1;
111
- transition: all 0.3s var(--bezier-ease) 0s;
112
- transform: translateX(-100%);
113
-
114
- &.opened {
115
- z-index: 4;
116
- transform: translateX(0);
117
- box-shadow: 0px 0px 80px 0px rgba(0, 0, 0, 0.2);
118
- }
119
- }
120
-}
121
-</style>
frontend/src/layouts/VerticalNav/SidebarFooter.vue
deleted
-63
@@ -1,63 +0,0 @@
1
-<template>
2
- <div class="sidebar-footer" :class="{ collapsed }">
3
- <n-menu :options="menuOptions" :collapsed="collapsed" :collapsed-width="collapsedWidth" />
4
- </div>
5
-</template>
6
-
7
-<script lang="ts" setup>
8
-import { computed, h, ref, toRefs } from "vue"
9
-import { NMenu } from "naive-ui"
10
-import { useThemeStore } from "@/stores/theme"
11
-import { renderIcon } from "@/utils"
12
-
13
-defineOptions({
14
- name: "SidebarFooter"
15
-})
16
-
17
-const ContactIcon = "ic:outline-alternate-email"
18
-
19
-const props = withDefaults(
20
- defineProps<{
21
- collapsed?: boolean
22
- }>(),
23
- { collapsed: false }
24
-)
25
-const { collapsed } = toRefs(props)
26
-
27
-const menuOptions = ref([
28
- {
29
- label: () =>
30
- h(
31
- "a",
32
- {
33
- href: "https://www.socfortress.co/contact_form.html",
34
- target: "_blank",
35
- rel: "noopenner noreferrer"
36
- },
37
- "Contact SOCFortress"
38
- ),
39
- key: "contact-socfortress",
40
- icon: renderIcon(ContactIcon)
41
- }
42
-])
43
-
44
-const collapsedWidth = computed<number>(() => useThemeStore().sidebar.closeWidth - 16)
45
-</script>
46
-
47
-<style lang="scss" scoped>
48
-.sidebar-footer {
49
- margin: 8px;
50
- background-color: var(--bg-body);
51
- border-radius: var(--border-radius);
52
- padding: 3px 0;
53
- transition: all 0.3s;
54
-
55
- :deep() {
56
- .n-menu {
57
- .n-menu-item-content.n-menu-item-content--selected::before {
58
- background-color: transparent !important;
59
- }
60
- }
61
- }
62
-}
63
-</style>
frontend/src/layouts/VerticalNav/SidebarHeader.vue
deleted
-150
@@ -1,150 +0,0 @@
1
-<template>
2
- <div class="sidebar-header flex items-center justify-between">
3
- <div class="logo-box grow" :class="{ mini: logoMini }">
4
- <Transition name="fade" mode="out-in">
5
- <Logo :mini="false" :dark="true" class="anim-wrap" v-if="isDark && !logoMini" />
6
- <Logo :mini="false" :dark="false" class="anim-wrap" v-else-if="isLight && !logoMini" />
7
- <Logo :mini="true" :dark="true" class="anim-wrap" v-else-if="isDark && logoMini" />
8
- <Logo :mini="true" :dark="false" class="anim-wrap" v-else-if="isLight && logoMini" />
9
- </Transition>
10
- </div>
11
- <Transition name="fade" mode="out-in">
12
- <div class="sidebar-pin flex items-center" v-if="showPin">
13
- <Icon :size="20" @click="sidebarCollapsed = !sidebarCollapsed">
14
- <span class="i-large">
15
- <Iconify :icon="CircleRegular" v-if="sidebarCollapsed" />
16
- <Iconify :icon="DotCircleRegular" v-if="!sidebarCollapsed" />
17
- </span>
18
- <span class="i-small">
19
- <Iconify :icon="CloseOutline" v-if="!sidebarCollapsed" />
20
- </span>
21
- </Icon>
22
- </div>
23
- </Transition>
24
- </div>
25
-</template>
26
-
27
-<script lang="ts" setup>
28
-import { computed, toRefs } from "vue"
29
-import { useThemeStore } from "@/stores/theme"
30
-import Icon from "@/components/common/Icon.vue"
31
-import { Icon as Iconify } from "@iconify/vue"
32
-import Logo from "@/layouts/common/Logo.vue"
33
-
34
-defineOptions({
35
- name: "SidebarHeader"
36
-})
37
-
38
-const CircleRegular = "fa6-regular:circle"
39
-const DotCircleRegular = "fa6-regular:circle-dot"
40
-const CloseOutline = "fa6-regular:circle-xmark"
41
-
42
-const props = defineProps<{
43
- logoMini?: boolean
44
-}>()
45
-const { logoMini } = toRefs(props)
46
-
47
-const showPin = computed<boolean>(() => !logoMini.value)
48
-const sidebarCollapsed = computed({
49
- get(): boolean {
50
- return useThemeStore().sidebar.collapsed
51
- },
52
- set() {
53
- useThemeStore().toggleSidebar()
54
- }
55
-})
56
-const isDark = computed<boolean>(() => useThemeStore().isThemeDark)
57
-const isLight = computed(() => useThemeStore().isThemeLight)
58
-</script>
59
-
60
-<style lang="scss" scoped>
61
-@import "./variables";
62
-
63
-.sidebar-header {
64
- height: var(--toolbar-height);
65
- min-height: var(--toolbar-height);
66
-
67
- :deep() {
68
- .logo-box {
69
- height: 100%;
70
- width: 100%;
71
- position: relative;
72
-
73
- .anim-wrap {
74
- padding: 16px 0px;
75
- position: absolute;
76
- top: 0;
77
- left: 0;
78
- right: 0;
79
- bottom: 0;
80
- display: flex;
81
- align-items: center;
82
-
83
- img {
84
- max-height: 32px;
85
- display: block;
86
- height: calc(var(--toolbar-height) - 32px);
87
- transform: translateX(32px);
88
- transition: transform var(--sidebar-anim-ease) var(--sidebar-anim-duration);
89
- }
90
- }
91
-
92
- &.fade-enter-active,
93
- &.fade-leave-active {
94
- transition: opacity var(--sidebar-anim-ease) var(--sidebar-anim-duration);
95
- }
96
-
97
- &.fade-enter-from,
98
- &.fade-leave-to {
99
- opacity: 0;
100
- }
101
- }
102
-
103
- &.mini {
104
- width: 100%;
105
- .anim-wrap {
106
- img {
107
- transform: translateX(18px);
108
- }
109
- }
110
- }
111
- }
112
-
113
- .sidebar-pin {
114
- padding-right: 16px;
115
- height: 100%;
116
-
117
- :deep() {
118
- .n-icon {
119
- cursor: pointer;
120
- opacity: 0.3;
121
- transition: opacity var(--sidebar-anim-ease) var(--sidebar-anim-duration);
122
-
123
- &:hover {
124
- opacity: 1;
125
- }
126
- }
127
- }
128
-
129
- .i-large {
130
- display: block;
131
- }
132
- .i-small {
133
- display: none;
134
- }
135
- @media (max-width: $sidebar-bp) {
136
- .i-large {
137
- display: none;
138
- }
139
- .i-small {
140
- display: block;
141
- }
142
- }
143
-
144
- &.fade-enter-from,
145
- &.fade-leave-to {
146
- opacity: 0;
147
- }
148
- }
149
-}
150
-</style>
frontend/src/layouts/VerticalNav/_variables.scss
deleted
-1
@@ -1 +0,0 @@
1
-$sidebar-bp: 700px;
frontend/src/layouts/VerticalNav/index.vue
deleted
-27
@@ -1,27 +0,0 @@
1
-<template>
2
- <div class="layout flex">
3
- <Sidebar />
4
- <MainContainer class="grow">
5
- <slot></slot>
6
- </MainContainer>
7
- </div>
8
-</template>
9
-
10
-<script lang="ts" setup>
11
-import Sidebar from "./Sidebar.vue"
12
-import MainContainer from "./MainContainer.vue"
13
-
14
-defineOptions({
15
- name: "VerticalNav"
16
-})
17
-</script>
18
-
19
-<style lang="scss" scoped>
20
-.layout {
21
- width: 100vw;
22
- height: 100vh;
23
- height: 100svh;
24
- overflow: hidden;
25
- perspective: 1000px;
26
-}
27
-</style>
frontend/src/layouts/common/Logo.vue
+3
-6
@@ -17,10 +17,6 @@
17
import { useThemeStore } from "@/stores/theme"
18
import { computed, toRefs } from "vue"
19
20
-defineOptions({
21
- name: "Logo"
22
-})
23
-
20
const props = withDefaults(
21
defineProps<{
22
mini: boolean
@@ -30,8 +26,9 @@ const props = withDefaults(
26
)
27
const { mini, dark } = toRefs(props)
28
33
-const isDark = computed<boolean>(() => dark.value || useThemeStore().isThemeDark)
34
-const isLight = computed<boolean>(() => !dark.value || useThemeStore().isThemeLight)
29
+const themeStore = useThemeStore()
30
+const isDark = computed<boolean>(() => dark.value ?? themeStore.isThemeDark)
31
+const isLight = computed<boolean>(() => !dark.value || themeStore.isThemeLight)
32
</script>
33
34
<style lang="scss" scoped>
frontend/src/layouts/common/MainFooter.vue
renamed
+2
-5
@@ -28,16 +28,13 @@ import Icon from "@/components/common/Icon.vue"
28
import BrainIcon from "@/assets/icons/brain-icon.svg"
29
import { useThemeStore } from "@/stores/theme"
30
31
-defineOptions({
32
- name: "FooterEL"
33
-})
34
-
31
const props = defineProps<{
32
boxed: boolean
33
}>()
34
const { boxed } = toRefs(props)
39
-const style = computed(() => useThemeStore().style)
35
36
+const themeStore = useThemeStore()
37
+const style = computed(() => themeStore.style)
38
const year = ref(new Date().getFullYear())
39
</script>
40
frontend/src/layouts/common/Navbar/index.vue
+73
-19
@@ -29,13 +29,6 @@ import { computed, onBeforeMount, ref, toRefs } from "vue"
29
import { useRouter, useRoute, type RouteRecordNormalized } from "vue-router"
30
import _uniq from "lodash/uniq"
31
32
-defineOptions({
33
- name: "Navbar"
34
-})
35
-
36
-const route = useRoute()
37
-const router = useRouter()
38
-
32
const props = withDefaults(
33
defineProps<{
34
mode?: "vertical" | "horizontal"
@@ -45,13 +38,17 @@ const props = withDefaults(
38
)
39
const { mode, collapsed } = toRefs(props)
40
41
+const route = useRoute()
42
+const router = useRouter()
43
const selectedKey = ref<string | null>(null)
44
const menu = ref<MenuInst | null>(null)
45
const expandedKeys = ref<string[] | undefined>(undefined)
46
52
-const menuOptions = computed<MenuMixedOption[]>(() => getItems(mode.value, collapsed.value))
53
-const collapsedWidth = computed<number>(() => useThemeStore().sidebar.closeWidth)
54
-const sidebarCollapsed = computed<boolean>(() => useThemeStore().sidebar.collapsed)
47
+const themeStore = useThemeStore()
48
+
49
+const menuOptions = computed<MenuMixedOption[]>(() => getItems())
50
+const collapsedWidth = computed<number>(() => themeStore.sidebar.closeWidth)
51
+const sidebarCollapsed = computed<boolean>(() => themeStore.sidebar.collapsed)
52
53
function setMenuKey(matched: RouteRecordNormalized[]) {
54
for (const match of matched) {
@@ -72,7 +69,7 @@ onBeforeMount(() => {
69
setMenuKey(route.matched)
70
71
if (window.innerWidth <= 700 && !sidebarCollapsed.value) {
75
- useThemeStore().closeSidebar()
72
+ themeStore.closeSidebar()
73
}
74
}
75
})
@@ -100,6 +97,15 @@ function handleUpdateExpandedKeys(value: string[]) {
97
}
98
99
:deep() {
100
+ .n-menu-item {
101
+ .n-menu-item-content {
102
+ gap: 8px;
103
+
104
+ .n-menu-item-content__icon {
105
+ margin-right: 0 !important;
106
+ }
107
+ }
108
+ }
109
.n-menu-item-content,
110
.n-menu-item-group {
111
.item-badge {
@@ -117,7 +123,6 @@ function handleUpdateExpandedKeys(value: string[]) {
123
:nth-child(2) {
124
color: var(--fg-color);
125
background: var(--hover-005-color);
120
- margin-right: 10px;
126
height: 22px;
127
line-height: 24px;
128
border-radius: 15px;
@@ -158,18 +163,57 @@ function handleUpdateExpandedKeys(value: string[]) {
163
}
164
165
.n-submenu-children {
166
+ --dash-width: 12px;
167
+ --dash-height: 2px;
168
+ --dash-offset: 43px;
169
+
170
+ position: relative;
171
+
172
+ &::before {
173
+ content: "";
174
+ display: block;
175
+ background-color: var(--divider-010-color);
176
+ width: var(--dash-height);
177
+ position: absolute;
178
+ top: 0px;
179
+ bottom: 20px;
180
+ left: var(--dash-offset);
181
+ }
182
+
183
.n-menu-item-content {
184
&::after {
185
content: "";
186
display: block;
165
- opacity: 0.1;
166
- background-color: var(--fg-color);
167
- width: 12px;
168
- height: 2px;
187
+ background-color: var(--divider-010-color);
188
+ width: var(--dash-width);
189
+ height: var(--dash-height);
190
position: absolute;
170
- top: 50%;
171
- left: 38px;
172
- margin-top: -1px;
191
+ top: calc(50% - calc(ar(--dash-height) / 2));
192
+ left: calc(var(--dash-offset) + var(--dash-height));
193
+ }
194
+ }
195
+
196
+ .n-menu-item-group {
197
+ .n-menu-item-group-title {
198
+ padding-left: 64px !important;
199
+ }
200
+ }
201
+
202
+ .n-submenu-children {
203
+ &::before {
204
+ display: none;
205
+ }
206
+ .n-menu-item-content {
207
+ &::after {
208
+ width: calc(var(--dash-width) * 3);
209
+ background: repeating-linear-gradient(
210
+ 90deg,
211
+ var(--divider-010-color) 0px,
212
+ var(--divider-010-color) 5px,
213
+ transparent 5px,
214
+ transparent 8px
215
+ );
216
+ }
217
}
218
}
219
}
@@ -183,6 +227,16 @@ function handleUpdateExpandedKeys(value: string[]) {
227
}
228
}
229
}
230
+
231
+.direction-rtl {
232
+ .nav {
233
+ :deep() {
234
+ .n-submenu-children {
235
+ --dash-offset: 39px;
236
+ }
237
+ }
238
+ }
239
+}
240
</style>
241
242
<style lang="scss">
frontend/src/layouts/common/Navbar/items.tsx
+1
-2
@@ -18,8 +18,7 @@ const ExternalServicesIcon = "carbon:ibm-cloud-direct-link-2-dedicated"
18
const ReportCreationIcon = "carbon:report-data"
19
const SchedulerIcon = "material-symbols:autoplay"
20
21
-/*eslint @typescript-eslint/no-unused-vars: "off"*/
22
-export default function getItems(mode: "vertical" | "horizontal", collapsed: boolean): MenuMixedOption[] {
21
+export default function getItems(): MenuMixedOption[] {
22
return [
23
{
24
label: () =>
frontend/src/layouts/common/Provider.vue
+21
-7
@@ -1,6 +1,6 @@
1
<template>
2
- <n-config-provider :theme="theme" :theme-overrides="themeOverrides" preflight-style-disabled>
3
- <n-loading-bar-provider :container-style="{ height: '3px' }">
2
+ <n-config-provider :rtl="rtlOptions" :theme="theme" :theme-overrides="themeOverrides" preflight-style-disabled>
3
+ <n-loading-bar-provider container-class="h-0.75">
4
<n-message-provider>
5
<n-notification-provider>
6
<n-dialog-provider>
@@ -16,7 +16,6 @@
16
</template>
17
18
<script lang="ts" setup>
19
-import { computed, watch } from "vue"
19
import {
20
NGlobalStyle,
21
NConfigProvider,
@@ -28,25 +27,29 @@ import {
27
} from "naive-ui"
28
import { useThemeStore } from "@/stores/theme"
29
import { useWindowSize } from "@vueuse/core"
30
+import { computed, watch } from "vue"
31
import GlobalListener from "@/layouts/common/GlobalListener.vue"
32
+import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
33
+import { rtlStyles } from "./rtlProvider"
34
import type { ThemeName } from "@/types/theme.d"
35
36
const { width } = useWindowSize()
35
-
37
const themeStore = useThemeStore()
37
-
38
const theme = computed(() => themeStore.naiveTheme)
39
+const themeName = computed<ThemeName>(() => themeStore.themeName)
40
const themeOverrides = computed<GlobalThemeOverrides>(() => themeStore.themeOverrides)
41
const style = computed(() => themeStore.style)
41
-const themeName = computed<ThemeName>(() => themeStore.themeName)
42
+const isRTL = computed(() => themeStore.isRTL)
43
+const rtlOptions = computed<RtlItem[] | undefined>(() => (isRTL.value ? rtlStyles : undefined))
44
45
watch(
44
- style,
46
+ [isRTL, style],
47
() => {
48
setGlobalVars()
49
},
50
{ immediate: true }
51
)
52
+
53
watch(
54
themeName,
55
(val, old) => {
@@ -54,6 +57,7 @@ watch(
57
},
58
{ immediate: true }
59
)
60
+
61
watch(
62
width,
63
() => {
@@ -62,8 +66,18 @@ watch(
66
{ immediate: true }
67
)
68
69
+// This function allows you to utilize the values in the style object as variables within your CSS/SCSS code like: var(-–bg-color)
70
function setGlobalVars() {
71
const html = document.children[0] as HTMLElement
72
+ const body = document.getElementsByTagName("body")?.[0]
73
+ if (isRTL.value && body) {
74
+ body.classList.add("direction-rtl")
75
+ body.classList.remove("direction-ltr")
76
+ } else {
77
+ body.classList.remove("direction-rtl")
78
+ body.classList.add("direction-ltr")
79
+ }
80
+ //html.dir = isRTL.value ? "rtl" : "ltr"
81
const { style: htmlStyle } = html
82
for (const key in style.value) {
83
htmlStyle.setProperty("--" + key, style.value[key])
frontend/src/layouts/common/SplashScreen.vue
+3
-3
@@ -1,6 +1,6 @@
1
<template>
2
<transition name="loading-fade">
3
- <div class="splash-screen-loading-box" v-if="loading">
3
+ <div class="splash-screen-loading-box" v-if="show">
4
<n-spin :size="80" />
5
</div>
6
</transition>
@@ -11,9 +11,9 @@ import { NSpin } from "naive-ui"
11
import { toRefs } from "vue"
12
13
const props = defineProps<{
14
- loading: boolean
14
+ show: boolean
15
}>()
16
-const { loading } = toRefs(props)
16
+const { show } = toRefs(props)
17
</script>
18
19
<style scoped lang="scss">
frontend/src/layouts/common/Toolbar/Avatar.vue
+2
-5
@@ -17,13 +17,10 @@ const LogoutIcon = "ion:log-out-outline"
17
const LogsIcon = "carbon:cloud-logging"
18
const ContactIcon = "ic:outline-alternate-email"
19
20
-defineOptions({
21
- name: "Avatar"
22
-})
23
-
20
const router = useRouter()
21
+const authStore = useAuthStore()
22
26
-const userPic = useAuthStore().userPic
23
+const userPic = authStore.userPic
24
25
const options = ref([
26
{
frontend/src/layouts/common/Toolbar/Breadcrumb.vue
+1
-7
@@ -26,21 +26,15 @@ import { onBeforeMount, ref } from "vue"
26
import { useRouter, useRoute } from "vue-router"
27
import Icon from "@/components/common/Icon.vue"
28
29
-const HomeIcon = "fluent:home-24-regular"
30
-
29
interface Page {
30
name: string
31
path: string
32
key: string
33
}
34
37
-defineOptions({
38
- name: "Breadcrumb"
39
-})
40
-
35
+const HomeIcon = "fluent:home-24-regular"
36
const router = useRouter()
37
const route = useRoute()
43
-
38
const items = ref<Page[]>([])
39
40
function goto(page: Partial<Page>) {
frontend/src/layouts/common/Toolbar/FullscreenSwitch.vue
-5
@@ -10,14 +10,9 @@ import Icon from "@/components/common/Icon.vue"
10
import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
11
12
const { isFullscreen, toggle } = useFullscreenSwitch()
13
-
13
const OpenIcon = "fluent:full-screen-maximize-24-regular"
14
const CloseIcon = "fluent:full-screen-minimize-24-regular"
15
17
-defineOptions({
18
- name: "FullscreenSwitch"
19
-})
20
-
16
function toggleFullscreen(e?: MouseEvent) {
17
toggle()
18
return e
frontend/src/layouts/common/Toolbar/LocaleSwitch.vue
+3
-1
@@ -12,7 +12,9 @@ import { computed, h, type VNodeChild } from "vue"
12
13
const MultiLanguageIcon = "ion:language-outline"
14
15
-const { getAvailableLocales, getLocale, setLocale, t } = useStoreI18n()
15
+const storeI18n = useStoreI18n()
16
+
17
+const { getAvailableLocales, getLocale, setLocale, t } = storeI18n
18
19
const list = computed(() =>
20
getAvailableLocales().map(i => ({
frontend/src/layouts/common/Toolbar/Notifications.vue
+8
-6
@@ -1,5 +1,5 @@
1
<template>
2
- <n-popover :show-arrow="false" placement="bottom" content-style="padding:0" style="width: 280px">
2
+ <n-popover :show-arrow="false" placement="bottom" content-class="!p-0 w-72">
3
<template #trigger>
4
<n-badge :show="hasUnread" dot :color="primaryColor">
5
<Icon :name="BellIcon" :size="21" class="trigger-icon"></Icon>
@@ -9,9 +9,9 @@
9
<n-text strong depth="1">Notifications</n-text>
10
</template>
11
<template #default>
12
- <NotificationsList :max-items="MAX_ITEMS" style="max-height: 50vh">
12
+ <NotificationsList :max-items="MAX_ITEMS" class="max-h-50vh">
13
<template #last>
14
- <div class="p-4 flex justify-center" v-if="list.length > MAX_ITEMS">
14
+ <div class="flex justify-center p-4" v-if="list.length > MAX_ITEMS">
15
<n-button text @click="showDrawer = true">View all</n-button>
16
</div>
17
</template>
@@ -22,8 +22,8 @@
22
</template>
23
</n-popover>
24
25
- <n-drawer v-model:show="showDrawer" :width="400" style="max-width: 90vw" :trap-focus="false">
26
- <n-drawer-content title="Notifications" closable body-content-style="padding:0">
25
+ <n-drawer v-model:show="showDrawer" :width="400" class="max-w-90vw" :trap-focus="false">
26
+ <n-drawer-content title="Notifications" closable body-content-class="!p-0">
27
<NotificationsList />
28
<template #footer>
29
<NotificationsToolbar />
@@ -44,7 +44,9 @@ import { useHealthchecksNotify } from "@/composables/useHealthchecksNotify"
44
45
const BellIcon = "ph:bell"
46
47
-const primaryColor = computed(() => useThemeStore().primaryColor)
47
+const themeStore = useThemeStore()
48
+
49
+const primaryColor = computed(() => themeStore.primaryColor)
50
const hasUnread = useNotifications().hasUnread
51
52
const showDrawer = ref(false)
frontend/src/layouts/common/Toolbar/Search.vue
+27
-8
@@ -1,6 +1,6 @@
1
<template>
2
<div class="flex items-center search-btn" @click="openBox">
3
- <Icon :name="SearchIcon" :size="16"></Icon>
3
+ <Icon :name="SearchIcon" :size="16" class="search-btn-icon"></Icon>
4
<span>Search</span>
5
<n-text code class="search-command">
6
<span :class="{ win: commandIcon === 'CTRL' }">{{ commandIcon }}</span>
@@ -17,11 +17,6 @@ import Icon from "@/components/common/Icon.vue"
17
import { useSearchDialog } from "@/composables/useSearchDialog"
18
19
const SearchIcon = "ion:search-outline"
20
-
21
-defineOptions({
22
- name: "Search"
23
-})
24
-
20
const commandIcon = ref("⌘")
21
22
function openBox() {
@@ -42,7 +37,7 @@ onMounted(() => {
37
height: 32px;
38
cursor: pointer;
39
padding: 4px 10px;
45
- padding-right: 5px;
40
+ padding-right: 6px;
41
outline: none;
42
border: none;
43
@@ -96,7 +91,8 @@ onMounted(() => {
91
}
92
93
@media (max-width: 1000px) {
99
- padding-right: 10px;
94
+ padding-right: 8px !important;
95
+ padding-left: 8px !important;
96
97
& > span {
98
display: none;
@@ -106,4 +102,27 @@ onMounted(() => {
102
}
103
}
104
}
105
+
106
+.direction-rtl {
107
+ .search-btn {
108
+ padding-right: 10px;
109
+ padding-left: 6px;
110
+
111
+ .search-btn-icon {
112
+ transform: rotateY(180deg);
113
+ }
114
+
115
+ :deep() {
116
+ & > code {
117
+ border-top-left-radius: 10px;
118
+ border-bottom-left-radius: 10px;
119
+ padding-left: 10px;
120
+
121
+ border-top-right-radius: var(--n-code-border-radius);
122
+ border-bottom-right-radius: var(--n-code-border-radius);
123
+ padding-right: 0.35em;
124
+ }
125
+ }
126
+ }
127
+}
128
</style>
frontend/src/layouts/common/Toolbar/ThemeSwitch.vue
+4
-8
@@ -23,12 +23,8 @@ const Sunny = "ion:sunny"
23
const Moon = "ion:moon"
24
const SunnyOutline = "ion:sunny-outline"
25
const MoonOutline = "ion:moon-outline"
26
-
27
-defineOptions({
28
- name: "ThemeSwitch"
29
-})
30
-
31
-const isThemeDark = computed<boolean>(() => useThemeStore().isThemeDark)
26
+const themeStore = useThemeStore()
27
+const isThemeDark = computed<boolean>(() => themeStore.isThemeDark)
28
29
function toggleTheme(event?: MouseEvent) {
30
const isAppearanceTransition =
@@ -38,7 +34,7 @@ function toggleTheme(event?: MouseEvent) {
34
!window.matchMedia("(prefers-reduced-motion: reduce)").matches
35
36
if (!isAppearanceTransition || !event) {
41
- useThemeStore().toggleTheme()
37
+ themeStore.toggleTheme()
38
return
39
}
40
@@ -48,7 +44,7 @@ function toggleTheme(event?: MouseEvent) {
44
45
// @ts-expect-error: Transition API
46
const transition = document.startViewTransition(async () => {
51
- useThemeStore().toggleTheme()
47
+ themeStore.toggleTheme()
48
await nextTick()
49
})
50
frontend/src/layouts/common/Toolbar/index.vue
+86
-22
@@ -1,16 +1,16 @@
1
<template>
2
<header class="toolbar" :class="{ boxed }">
3
<div class="wrap flex items-center justify-end gap-3">
4
- <Logo mini class="cursor-pointer" @click="openNav()" />
4
+ <div class="logo-box flex items-center gap-2 cursor-pointer" @click="openNav()">
5
+ <Logo mini />
6
+ <Icon :size="20" name="carbon:chevron-right"></Icon>
7
+ </div>
8
9
<Breadcrumb class="grow" />
10
<PinnedPages />
11
12
<div class="bubble flex items-center">
13
<Search />
11
- <!--
12
- <LocaleSwitch />
13
- -->
14
<FullscreenSwitch />
15
<ThemeSwitch />
16
<Notifications />
@@ -29,62 +29,79 @@ import Search from "./Search.vue"
29
import PinnedPages from "./PinnedPages.vue"
30
import ThemeSwitch from "./ThemeSwitch.vue"
31
import Notifications from "./Notifications.vue"
32
-// import LocaleSwitch from "./LocaleSwitch.vue"
32
import FullscreenSwitch from "./FullscreenSwitch.vue"
33
+import Icon from "@/components/common/Icon.vue"
34
import { useLoadingBar } from "naive-ui"
35
import { useRouter } from "vue-router"
36
import { useMainStore } from "@/stores/main"
37
import { useThemeStore } from "@/stores/theme"
38
39
-const router = useRouter()
40
-
41
-defineOptions({
42
- name: "Toolbar"
43
-})
44
-
39
const props = defineProps<{
40
boxed: boolean
41
}>()
42
const { boxed } = toRefs(props)
43
44
+const router = useRouter()
45
+const themeStore = useThemeStore()
46
+const mainStore = useMainStore()
47
+
48
function openNav() {
51
- useThemeStore().openSidebar()
49
+ themeStore.openSidebar()
50
}
51
52
onMounted(() => {
53
const loadingBar = useLoadingBar()
54
router.beforeEach(() => loadingBar?.start())
55
router.afterEach(() => loadingBar?.finish())
58
- useMainStore().setLoadingBar(loadingBar)
56
+ mainStore.setLoadingBar(loadingBar)
57
})
58
</script>
59
60
<style lang="scss" scoped>
61
+@import "@/assets/scss/functions.scss";
62
+
63
.toolbar {
64
position: sticky;
65
top: 0;
66
left: 0;
67
- backdrop-filter: blur(15px);
67
height: var(--toolbar-height);
69
- // width: calc(100% - 1px);
68
width: 100%;
69
max-width: 100%;
70
padding: 0 var(--view-padding);
71
z-index: 3;
74
- //box-shadow: 0px 20px 20px 0px var(--bg-body);
72
overflow: hidden;
73
+ background: var(--bg-body);
74
75
&::before {
76
content: "";
77
width: 100%;
78
height: 100%;
81
- background-color: var(--bg-body);
82
- background: linear-gradient(var(--bg-body), rgba(255, 255, 255, 0) 100%);
79
position: absolute;
80
display: block;
81
top: 0;
82
left: 0;
83
z-index: -1;
84
+ backdrop-filter: blur(20px);
85
+ // mask-image: scrimGradient(#000);
86
+ mask-image: linear-gradient(
87
+ to bottom,
88
+ hsl(0, 0%, 0%) 0%,
89
+ hsla(0, 0%, 0%, 0.979) 0.6%,
90
+ hsla(0, 0%, 0%, 0.962) 2.2%,
91
+ hsla(0, 0%, 0%, 0.946) 4.9%,
92
+ hsla(0, 0%, 0%, 0.929) 8.6%,
93
+ hsla(0, 0%, 0%, 0.91) 13.3%,
94
+ hsla(0, 0%, 0%, 0.884) 18.8%,
95
+ hsla(0, 0%, 0%, 0.851) 25.1%,
96
+ hsla(0, 0%, 0%, 0.806) 32.3%,
97
+ hsla(0, 0%, 0%, 0.749) 40.2%,
98
+ hsla(0, 0%, 0%, 0.677) 48.7%,
99
+ hsla(0, 0%, 0%, 0.587) 57.9%,
100
+ hsla(0, 0%, 0%, 0.476) 67.7%,
101
+ hsla(0, 0%, 0%, 0.343) 78%,
102
+ hsla(0, 0%, 0%, 0.185) 88.8%,
103
+ hsla(0, 0%, 0%, 0) 100%
104
+ );
105
}
106
107
.wrap {
@@ -102,7 +119,7 @@ onMounted(() => {
119
gap: 14px;
120
}
121
105
- @media (max-width: 900px) {
122
+ @media (max-width: 850px) {
123
.pinned-pages {
124
display: none;
125
}
@@ -126,9 +143,56 @@ onMounted(() => {
143
}
144
145
&.gradient-bg-sidebar {
129
- &::before {
130
- background-color: var(--bg-sidebar);
131
- background: linear-gradient(var(--bg-sidebar), rgba(255, 255, 255, 0) 100%);
146
+ background-color: var(--bg-sidebar);
147
+ background: linear-gradient(
148
+ to bottom,
149
+ rgba(var(--bg-sidebar-rgb), 1) 0%,
150
+ rgba(var(--bg-sidebar-rgb), 0.945) 8.6%,
151
+ rgba(var(--bg-sidebar-rgb), 0.888) 16.2%,
152
+ rgba(var(--bg-sidebar-rgb), 0.83) 22.9%,
153
+ rgba(var(--bg-sidebar-rgb), 0.769) 28.9%,
154
+ rgba(var(--bg-sidebar-rgb), 0.707) 34.4%,
155
+ rgba(var(--bg-sidebar-rgb), 0.644) 39.5%,
156
+ rgba(var(--bg-sidebar-rgb), 0.578) 44.5%,
157
+ rgba(var(--bg-sidebar-rgb), 0.511) 49.5%,
158
+ rgba(var(--bg-sidebar-rgb), 0.443) 54.7%,
159
+ rgba(var(--bg-sidebar-rgb), 0.373) 60.3%,
160
+ rgba(var(--bg-sidebar-rgb), 0.301) 66.4%,
161
+ rgba(var(--bg-sidebar-rgb), 0.228) 73.3%,
162
+ rgba(var(--bg-sidebar-rgb), 0.153) 81%,
163
+ rgba(var(--bg-sidebar-rgb), 0.077) 89.9%,
164
+ rgba(var(--bg-sidebar-rgb), 0) 100%
165
+ );
166
+ }
167
+ &.gradient-bg-body {
168
+ background-color: var(--bg-body);
169
+ background: linear-gradient(
170
+ to bottom,
171
+ rgba(var(--bg-body-rgb), 1) 0%,
172
+ rgba(var(--bg-body-rgb), 0.738) 19%,
173
+ rgba(var(--bg-body-rgb), 0.541) 34%,
174
+ rgba(var(--bg-body-rgb), 0.382) 47%,
175
+ rgba(var(--bg-body-rgb), 0.278) 56.5%,
176
+ rgba(var(--bg-body-rgb), 0.194) 65%,
177
+ rgba(var(--bg-body-rgb), 0.126) 73%,
178
+ rgba(var(--bg-body-rgb), 0.075) 80.2%,
179
+ rgba(var(--bg-body-rgb), 0.042) 86.1%,
180
+ rgba(var(--bg-body-rgb), 0.021) 91%,
181
+ rgba(var(--bg-body-rgb), 0.008) 95.2%,
182
+ rgba(var(--bg-body-rgb), 0.002) 98.2%,
183
+ rgba(var(--bg-body-rgb), 0) 100%
184
+ );
185
+ }
186
+}
187
+
188
+.direction-rtl {
189
+ .toolbar {
190
+ .wrap {
191
+ .logo-box {
192
+ .n-icon {
193
+ transform: rotateY(180deg);
194
+ }
195
+ }
196
}
197
}
198
}
frontend/src/layouts/common/rtlProvider.ts
new
+70
@@ -0,0 +1,70 @@
1
+import {
2
+ unstableAlertRtl,
3
+ unstableAvatarGroupRtl,
4
+ // unstableBadgeRtl,
5
+ unstableButtonRtl,
6
+ unstableNotificationRtl,
7
+ unstableMessageRtl,
8
+ unstableDataTableRtl,
9
+ unstableButtonGroupRtl,
10
+ unstableCardRtl,
11
+ unstableCheckboxRtl,
12
+ unstableCollapseRtl,
13
+ unstableCollapseTransitionRtl,
14
+ unstableDialogRtl,
15
+ unstableDrawerRtl,
16
+ unstableDynamicInputRtl,
17
+ unstableFlexRtl,
18
+ unstableInputRtl,
19
+ unstableInputNumberRtl,
20
+ unstableListRtl,
21
+ unstablePaginationRtl,
22
+ unstableRadioRtl,
23
+ unstableSelectRtl,
24
+ unstableSpaceRtl,
25
+ unstableStatisticRtl,
26
+ unstableStepsRtl,
27
+ unstableTableRtl,
28
+ unstableTagRtl,
29
+ unstableThingRtl,
30
+ unstableTreeRtl,
31
+ unstableRowRtl,
32
+ unstableScrollbarRtl
33
+} from "naive-ui"
34
+import type { RtlItem } from "naive-ui/es/config-provider/src/internal-interface"
35
+
36
+const rtlStyles: RtlItem[] = [
37
+ unstableAlertRtl,
38
+ unstableAvatarGroupRtl,
39
+ // unstableBadgeRtl,
40
+ unstableButtonRtl,
41
+ unstableNotificationRtl,
42
+ unstableMessageRtl,
43
+ unstableDataTableRtl,
44
+ unstableButtonGroupRtl,
45
+ unstableCardRtl,
46
+ unstableCheckboxRtl,
47
+ unstableCollapseRtl,
48
+ unstableCollapseTransitionRtl,
49
+ unstableDialogRtl,
50
+ unstableDrawerRtl,
51
+ unstableDynamicInputRtl,
52
+ unstableFlexRtl,
53
+ unstableInputRtl,
54
+ unstableInputNumberRtl,
55
+ unstableListRtl,
56
+ unstablePaginationRtl,
57
+ unstableRadioRtl,
58
+ unstableSelectRtl,
59
+ unstableSpaceRtl,
60
+ unstableStatisticRtl,
61
+ unstableStepsRtl,
62
+ unstableTableRtl,
63
+ unstableTagRtl,
64
+ unstableThingRtl,
65
+ unstableTreeRtl,
66
+ unstableRowRtl,
67
+ unstableScrollbarRtl
68
+]
69
+
70
+export { rtlStyles }
frontend/src/stores/theme.ts
+11
-2
@@ -24,6 +24,7 @@ export const useThemeStore = defineStore("theme", {
24
themeName: osTheme.value || ThemeEnum.Dark,
25
routerTransition: RouterTransition.FadeUp,
26
routerTransitionDuration: 0.3,
27
+ rtl: false,
28
boxed: {
29
enabled: false,
30
toolbar: true,
@@ -68,6 +69,9 @@ export const useThemeStore = defineStore("theme", {
69
setLayout(layout: Layout): void {
70
this.layout = layout
71
},
72
+ setRTL(rtl: boolean): void {
73
+ this.rtl = rtl
74
+ },
75
setBoxed(boxed: boolean): void {
76
this.boxed.enabled = boxed
77
},
@@ -313,6 +317,7 @@ export const useThemeStore = defineStore("theme", {
317
const buttonColorSecondaryPressed = naive.buttonColor2Pressed
318
319
const bgSidebar = this.sidebarBackground
320
+ const bgSidebarRGB = hex2rgb(bgSidebar).join(", ")
321
const bgBody = this.bodyBackground
322
const bgBodyRGB = hex2rgb(bgBody).join(", ")
323
@@ -362,15 +367,16 @@ export const useThemeStore = defineStore("theme", {
367
const secondary4RGB = hex2rgb(secondary4).join(", ")
368
369
return {
365
- "bg-sidebar": `${bgSidebar}`,
370
"bg-body": `${bgBody}`,
371
"bg-body-rgb": `${bgBodyRGB}`,
372
+ "bg-sidebar": `${bgSidebar}`,
373
+ "bg-sidebar-rgb": `${bgSidebarRGB}`,
374
375
"fg-color": `${fgColor}`,
376
"fg-secondary-color": `${fgSecondaryColor}`,
377
"bg-color": `${bgColor}`,
372
- "bg-secondary-color": `${bgSecondaryColor}`,
378
"bg-color-rgb": `${bgColorRGB}`,
379
+ "bg-secondary-color": `${bgSecondaryColor}`,
380
381
"border-color": `${borderColor}`,
382
"bezier-ease": `${bezierEase}`,
@@ -463,6 +469,9 @@ export const useThemeStore = defineStore("theme", {
469
isBoxed(state): boolean {
470
return state.boxed.enabled
471
},
472
+ isRTL(state): boolean {
473
+ return state.rtl
474
+ },
475
isFooterShown(state): boolean {
476
return state.footer.show
477
},
frontend/src/types/theme.d.ts
-1
@@ -1,5 +1,4 @@
1
export enum Layout {
2
- VerticalNav = "VerticalNav",
2
HorizontalNav = "HorizontalNav",
3
Blank = "Blank"
4
}
frontend/src/utils/auth.ts
+7
-5
@@ -44,26 +44,28 @@ export function authCheck(route: RouteLocationNormalized) {
44
const meta: RouteMetaAuth = route.meta
45
const { checkAuth, authRedirect, auth, roles } = meta
46
47
+ const authStore = useAuthStore()
48
+
49
if (route?.redirectedFrom?.name === "Logout") {
48
- useAuthStore().setLogout()
50
+ authStore.setLogout()
51
}
52
53
if (auth === true) {
52
- if (!useAuthStore().isLogged) {
54
+ if (!authStore.isLogged) {
55
window.location.href = "/login" + window.location.search
56
return false
57
}
58
57
- if (roles && !useAuthStore().isRoleGranted(roles)) {
59
+ if (roles && !authStore.isRoleGranted(roles)) {
60
window.location.href = "/login" + window.location.search
61
return false
62
}
63
}
64
65
if (checkAuth === true) {
64
- if (useAuthStore().isLogged) {
66
+ if (authStore.isLogged) {
67
if (roles) {
66
- if (useAuthStore().isRoleGranted(roles)) {
68
+ if (authStore.isRoleGranted(roles)) {
69
return authRedirect || "/"
70
} else {
71
return route.path
frontend/src/views/Profile.vue
+12
-14
@@ -1,6 +1,6 @@
1
<template>
2
<div class="page">
3
- <n-card class="header flex flex-col" content-style="padding:0">
3
+ <n-card class="header flex flex-col" content-class="!p-0">
4
<div class="user-info flex flex-wrap">
5
<div class="propic">
6
<n-avatar :size="100" :src="userPic" round />
@@ -22,7 +22,7 @@
22
<div class="item">
23
<n-tooltip placement="top">
24
<template #trigger>
25
- <div class="item-wrap">
25
+ <div class="tooltip-wrap">
26
<Icon :name="RoleIcon"></Icon>
27
<span>{{ userRole }}</span>
28
</div>
@@ -31,7 +31,7 @@
31
</n-tooltip>
32
</div>
33
<div class="item" v-if="userEmail">
34
- <div class="item-wrap">
34
+ <div class="tooltip-wrap">
35
<Icon :name="EmailIcon"></Icon>
36
<span>{{ userEmail }}</span>
37
</div>
@@ -59,7 +59,7 @@
59
</div>
60
</n-card>
61
<div class="main">
62
- <n-tabs :tab-style="{ display: 'none' }" v-model:value="tabActive" animated>
62
+ <n-tabs tab-class="!hidden" v-model:value="tabActive" animated>
63
<n-tab-pane name="settings">
64
<ProfileSettings />
65
</n-tab-pane>
@@ -84,11 +84,12 @@ const EditIcon = "uil:image-edit"
84
const EmailIcon = "carbon:email"
85
86
const tabActive = ref("settings")
87
+const authStore = useAuthStore()
88
88
-const userRole = useAuthStore().userRoleName
89
-const userName = useAuthStore().userName
90
-const userEmail = useAuthStore().userEmail
91
-const userPic = ref(useAuthStore().userPic)
89
+const userRole = authStore.userRoleName
90
+const userName = authStore.userName
91
+const userEmail = authStore.userEmail
92
+const userPic = ref(authStore.userPic)
93
94
function setCroppedImage(result: ImageCropperResult) {
95
const canvas = result.canvas as HTMLCanvasElement
@@ -141,14 +142,11 @@ function setCroppedImage(result: ImageCropperResult) {
142
gap: 24px;
143
144
.item {
144
- .item-wrap {
145
+ .tooltip-wrap {
146
display: flex;
147
align-items: center;
147
-
148
- span {
149
- line-height: 1;
150
- margin-left: 8px;
151
- }
148
+ gap: 8px;
149
+ line-height: 1;
150
}
151
}
152
}
frontend/src/views/auth/Login.vue
+7
-79
@@ -1,5 +1,5 @@
1
<template>
2
- <div class="page">
2
+ <div class="page-auth">
3
<div class="flex wrapper justify-center" v-if="!isLogged">
4
<div class="image-box basis-2/3" v-if="align === 'right'"></div>
5
<div class="form-box basis-1/3 flex items-center justify-center" :class="{ centered: align === 'center' }">
@@ -32,46 +32,28 @@ const { formType } = toRefs(props)
32
33
const route = useRoute()
34
const align = ref<Align>("left")
35
-const activeColor = ref("")
35
const type = ref<FormType | undefined>(formType.value || undefined)
36
38
-const primaryColor = computed(() => useThemeStore().primaryColor)
39
-const isLogged = computed(() => useAuthStore().isLogged)
37
+const themeStore = useThemeStore()
38
+const activeColor = computed(() => themeStore.primaryColor)
39
+const authStore = useAuthStore()
40
+const isLogged = computed(() => authStore.isLogged)
41
42
onBeforeMount(() => {
43
if (route.query.step) {
44
const step = route.query.step as FormType
45
type.value = step
46
}
46
- activeColor.value = primaryColor.value
47
})
48
</script>
49
50
<style lang="scss" scoped>
51
-@import "@/assets/scss/common.scss";
52
-
53
-.page {
54
- min-height: 100vh;
55
-
56
- .settings {
57
- position: fixed;
58
- top: 10px;
59
- left: 50%;
60
- transform: translateX(-50%);
61
- background-color: var(--bg-secondary-color);
62
- height: 44px;
63
- width: 300px;
64
- border-radius: 50px;
65
- padding: 5px;
66
- z-index: 1;
67
- }
51
+@import "./main.scss";
52
53
+.page-auth {
54
.wrapper {
70
- min-height: 100vh;
71
-
55
.image-box {
56
background-color: v-bind(activeColor);
74
- position: relative;
57
58
video {
59
position: absolute;
@@ -82,60 +64,6 @@ onBeforeMount(() => {
64
object-fit: cover;
65
object-position: center;
66
}
85
-
86
- /*
87
- &::after {
88
- content: "";
89
- width: 100%;
90
- height: 100%;
91
- position: absolute;
92
- top: 0;
93
- left: 0;
94
- background-image: url(@/assets/images/pattern-onboard.png);
95
- background-size: 500px;
96
- background-position: center center;
97
- }
98
- */
99
- }
100
-
101
- .form-box {
102
- padding: 50px;
103
-
104
- &.centered {
105
- flex-basis: 100%;
106
- .form-wrap {
107
- padding: 60px;
108
- width: 100%;
109
- max-width: 500px;
110
- background-color: var(--bg-color);
111
- border-radius: 20px;
112
- @apply shadow-xl;
113
- }
114
-
115
- @media (max-width: 600px) {
116
- padding: 4%;
117
- .form-wrap {
118
- padding: 8%;
119
- }
120
- }
121
- }
122
- }
123
- }
124
- @media (max-width: 800px) {
125
- .settings {
126
- width: 112px;
127
- .colors {
128
- display: none;
129
- }
130
- }
131
- .wrapper {
132
- .image-box {
133
- display: none;
134
- }
135
-
136
- .form-box {
137
- flex-basis: 100%;
138
- }
67
}
68
}
69
}
frontend/src/views/auth/main.scss
new
+59
@@ -0,0 +1,59 @@
1
+.page-auth {
2
+ min-height: 100vh;
3
+
4
+ .wrapper {
5
+ min-height: 100vh;
6
+
7
+ .image-box {
8
+ position: relative;
9
+
10
+ /*
11
+ &::after {
12
+ content: "";
13
+ width: 100%;
14
+ height: 100%;
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ background-image: url(@/assets/images/pattern-onboard.png);
19
+ background-size: 500px;
20
+ background-position: center center;
21
+ }
22
+ */
23
+ }
24
+
25
+ .form-box {
26
+ padding: 50px;
27
+
28
+ &.centered {
29
+ flex-basis: 100%;
30
+ .form-wrap {
31
+ padding: 60px;
32
+ width: 100%;
33
+ max-width: 500px;
34
+ background-color: var(--bg-color);
35
+ border-radius: 20px;
36
+ @apply shadow-xl;
37
+ }
38
+
39
+ @media (max-width: 600px) {
40
+ padding: 4%;
41
+ .form-wrap {
42
+ padding: 8%;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ @media (max-width: 800px) {
49
+ .wrapper {
50
+ .image-box {
51
+ display: none;
52
+ }
53
+
54
+ .form-box {
55
+ flex-basis: 100%;
56
+ }
57
+ }
58
+ }
59
+}
frontend/tailwind.config.js
+34
@@ -20,9 +20,43 @@ export default {
20
extend: {
21
screens: {
22
xs: "460px"
23
+ },
24
+ height: {
25
+ 0.75: "0.1875rem",
26
+ 150: "37.5rem",
27
+ "60vh": "60vh"
28
+ },
29
+ width: {
30
+ 5.5: "1.375rem",
31
+ 150: "37.5rem"
32
+ },
33
+ spacing: {
34
+ "20vh": "20vh",
35
+ "50vw": "50vw"
36
+ },
37
+ maxHeight: {
38
+ "50vh": "50vh"
39
+ },
40
+ maxWidth: {
41
+ "90vw": "90vw"
42
}
43
}
44
},
45
+ safelist: [
46
+ "p-4",
47
+ "!p-1",
48
+ "flex",
49
+ "flex-col",
50
+ "!justify-start",
51
+ "opacity-50",
52
+ "hover:text-red-500",
53
+ "hover:opacity-100",
54
+ "items-center",
55
+ "gap-5",
56
+ "w-72",
57
+ "max-h-50vh",
58
+ "max-w-90vw"
59
+ ],
60
plugins: [
61
plugin(function ({ addBase, theme }) {
62
addBase({