From c833176b638eeb1cdc8b30d4aef632a25ede3777 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 14 Jul 2023 23:01:33 +0800 Subject: ... --- FrontEnd/src/views/common/index.css | 216 +----------------------------------- 1 file changed, 1 insertion(+), 215 deletions(-) (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 111a3ec0..06f2556b 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -1,218 +1,4 @@ -:root { - --cru-background-color: #f8f9fa; - --cru-background-1-color: #e9ecef; - --cru-background-2-color: #dee2e6; - - --cru-disable-color: #ced4da; - - /* - --cru-primary-color: rgb(0, 123, 255); - --cru-primary-l1-color: rgb(26, 136, 255); - --cru-primary-l2-color: rgb(51, 149, 255); - --cru-primary-l3-color: rgb(77, 163, 255); - --cru-primary-d1-color: rgb(0, 111, 230); - --cru-primary-d2-color: rgb(0, 98, 204); - --cru-primary-d3-color: rgb(0, 86, 179); - --cru-primary-f1-color: rgb(0, 111, 230); - --cru-primary-f2-color: rgb(0, 98, 204); - --cru-primary-f3-color: rgb(0, 86, 179); - --cru-primary-r1-color: rgb(26, 136, 255); - --cru-primary-r2-color: rgb(51, 149, 255); - --cru-primary-r3-color: rgb(77, 163, 255); - --cru-primary-t-color: rgb(255, 255, 255); - --cru-primary-t1-color: rgb(230, 230, 230); - --cru-primary-t2-color: rgb(204, 204, 204); - --cru-primary-t3-color: rgb(179, 179, 179); - --cru-primary-enhance-color: rgb(77, 163, 255); - --cru-primary-enhance-l1-color: rgb(94, 172, 255); - --cru-primary-enhance-l2-color: rgb(112, 181, 255); - --cru-primary-enhance-l3-color: rgb(130, 190, 255); - --cru-primary-enhance-d1-color: rgb(43, 145, 255); - --cru-primary-enhance-d2-color: rgb(10, 128, 255); - --cru-primary-enhance-d3-color: rgb(0, 112, 232); - --cru-primary-enhance-f1-color: rgb(94, 172, 255); - --cru-primary-enhance-f2-color: rgb(112, 181, 255); - --cru-primary-enhance-f3-color: rgb(130, 190, 255); - --cru-primary-enhance-r1-color: rgb(43, 145, 255); - --cru-primary-enhance-r2-color: rgb(10, 128, 255); - --cru-primary-enhance-r3-color: rgb(0, 112, 232); - --cru-primary-enhance-t-color: rgb(0, 0, 0); - --cru-primary-enhance-t1-color: rgb(26, 26, 26); - --cru-primary-enhance-t2-color: rgb(51, 51, 51); - --cru-primary-enhance-t3-color: rgb(77, 77, 77); - --cru-secondary-color: rgb(128, 128, 128); - --cru-secondary-l1-color: rgb(141, 141, 141); - --cru-secondary-l2-color: rgb(153, 153, 153); - --cru-secondary-l3-color: rgb(166, 166, 166); - --cru-secondary-d1-color: rgb(115, 115, 115); - --cru-secondary-d2-color: rgb(102, 102, 102); - --cru-secondary-d3-color: rgb(90, 90, 90); - --cru-secondary-f1-color: rgb(115, 115, 115); - --cru-secondary-f2-color: rgb(102, 102, 102); - --cru-secondary-f3-color: rgb(90, 90, 90); - --cru-secondary-r1-color: rgb(141, 141, 141); - --cru-secondary-r2-color: rgb(153, 153, 153); - --cru-secondary-r3-color: rgb(166, 166, 166); - --cru-secondary-t-color: rgb(255, 255, 255); - --cru-secondary-t1-color: rgb(230, 230, 230); - --cru-secondary-t2-color: rgb(204, 204, 204); - --cru-secondary-t3-color: rgb(179, 179, 179); - --cru-danger-color: rgb(255, 0, 0); - --cru-danger-l1-color: rgb(255, 26, 26); - --cru-danger-l2-color: rgb(255, 51, 51); - --cru-danger-l3-color: rgb(255, 77, 77); - --cru-danger-d1-color: rgb(230, 0, 0); - --cru-danger-d2-color: rgb(204, 0, 0); - --cru-danger-d3-color: rgb(179, 0, 0); - --cru-danger-f1-color: rgb(230, 0, 0); - --cru-danger-f2-color: rgb(204, 0, 0); - --cru-danger-f3-color: rgb(179, 0, 0); - --cru-danger-r1-color: rgb(255, 26, 26); - --cru-danger-r2-color: rgb(255, 51, 51); - --cru-danger-r3-color: rgb(255, 77, 77); - --cru-danger-t-color: rgb(255, 255, 255); - --cru-danger-t1-color: rgb(230, 230, 230); - --cru-danger-t2-color: rgb(204, 204, 204); - --cru-danger-t3-color: rgb(179, 179, 179); - --cru-success-color: rgb(0, 128, 0); - --cru-success-l1-color: rgb(0, 166, 0); - --cru-success-l2-color: rgb(0, 204, 0); - --cru-success-l3-color: rgb(0, 243, 0); - --cru-success-d1-color: rgb(0, 115, 0); - --cru-success-d2-color: rgb(0, 102, 0); - --cru-success-d3-color: rgb(0, 90, 0); - --cru-success-f1-color: rgb(0, 115, 0); - --cru-success-f2-color: rgb(0, 102, 0); - --cru-success-f3-color: rgb(0, 90, 0); - --cru-success-r1-color: rgb(0, 166, 0); - --cru-success-r2-color: rgb(0, 204, 0); - --cru-success-r3-color: rgb(0, 243, 0); - --cru-success-t-color: rgb(255, 255, 255); - --cru-success-t1-color: rgb(230, 230, 230); - --cru-success-t2-color: rgb(204, 204, 204); - --cru-success-t3-color: rgb(179, 179, 179); - */ -} - -.cru-primary { - --cru-theme-color: var(--cru-primary-color); - --cru-theme-l1-color: var(--cru-primary-l1-color); - --cru-theme-l2-color: var(--cru-primary-l2-color); - --cru-theme-l3-color: var(--cru-primary-l3-color); - --cru-theme-d1-color: var(--cru-primary-d1-color); - --cru-theme-d2-color: var(--cru-primary-d2-color); - --cru-theme-d3-color: var(--cru-primary-d3-color); - --cru-theme-f1-color: var(--cru-primary-f1-color); - --cru-theme-f2-color: var(--cru-primary-f2-color); - --cru-theme-f3-color: var(--cru-primary-f3-color); - --cru-theme-r1-color: var(--cru-primary-r1-color); - --cru-theme-r2-color: var(--cru-primary-r2-color); - --cru-theme-r3-color: var(--cru-primary-r3-color); - --cru-theme-t-color: var(--cru-primary-t-color); - --cru-theme-t1-color: var(--cru-primary-t1-color); - --cru-theme-t2-color: var(--cru-primary-t2-color); - --cru-theme-t3-color: var(--cru-primary-t3-color); -} - -.cru-primary-enhance { - --cru-theme-color: var(--cru-primary-enhance-color); - --cru-theme-l1-color: var(--cru-primary-enhance-l1-color); - --cru-theme-l2-color: var(--cru-primary-enhance-l2-color); - --cru-theme-l3-color: var(--cru-primary-enhance-l3-color); - --cru-theme-d1-color: var(--cru-primary-enhance-d1-color); - --cru-theme-d2-color: var(--cru-primary-enhance-d2-color); - --cru-theme-d3-color: var(--cru-primary-enhance-d3-color); - --cru-theme-f1-color: var(--cru-primary-enhance-f1-color); - --cru-theme-f2-color: var(--cru-primary-enhance-f2-color); - --cru-theme-f3-color: var(--cru-primary-enhance-f3-color); - --cru-theme-r1-color: var(--cru-primary-enhance-r1-color); - --cru-theme-r2-color: var(--cru-primary-enhance-r2-color); - --cru-theme-r3-color: var(--cru-primary-enhance-r3-color); - --cru-theme-t-color: var(--cru-primary-enhance-t-color); - --cru-theme-t1-color: var(--cru-primary-enhance-t1-color); - --cru-theme-t2-color: var(--cru-primary-enhance-t2-color); - --cru-theme-t3-color: var(--cru-primary-enhance-t3-color); -} - -.cru-secondary { - --cru-theme-color: var(--cru-secondary-color); - --cru-theme-l1-color: var(--cru-secondary-l1-color); - --cru-theme-l2-color: var(--cru-secondary-l2-color); - --cru-theme-l3-color: var(--cru-secondary-l3-color); - --cru-theme-d1-color: var(--cru-secondary-d1-color); - --cru-theme-d2-color: var(--cru-secondary-d2-color); - --cru-theme-d3-color: var(--cru-secondary-d3-color); - --cru-theme-f1-color: var(--cru-secondary-f1-color); - --cru-theme-f2-color: var(--cru-secondary-f2-color); - --cru-theme-f3-color: var(--cru-secondary-f3-color); - --cru-theme-r1-color: var(--cru-secondary-r1-color); - --cru-theme-r2-color: var(--cru-secondary-r2-color); - --cru-theme-r3-color: var(--cru-secondary-r3-color); - --cru-theme-t-color: var(--cru-secondary-t-color); - --cru-theme-t1-color: var(--cru-secondary-t1-color); - --cru-theme-t2-color: var(--cru-secondary-t2-color); - --cru-theme-t3-color: var(--cru-secondary-t3-color); -} - -.cru-success { - --cru-theme-color: var(--cru-success-color); - --cru-theme-l1-color: var(--cru-success-l1-color); - --cru-theme-l2-color: var(--cru-success-l2-color); - --cru-theme-l3-color: var(--cru-success-l3-color); - --cru-theme-d1-color: var(--cru-success-d1-color); - --cru-theme-d2-color: var(--cru-success-d2-color); - --cru-theme-d3-color: var(--cru-success-d3-color); - --cru-theme-f1-color: var(--cru-success-f1-color); - --cru-theme-f2-color: var(--cru-success-f2-color); - --cru-theme-f3-color: var(--cru-success-f3-color); - --cru-theme-r1-color: var(--cru-success-r1-color); - --cru-theme-r2-color: var(--cru-success-r2-color); - --cru-theme-r3-color: var(--cru-success-r3-color); - --cru-theme-t-color: var(--cru-success-t-color); - --cru-theme-t1-color: var(--cru-success-t1-color); - --cru-theme-t2-color: var(--cru-success-t2-color); - --cru-theme-t3-color: var(--cru-success-t3-color); -} - -.cru-danger { - --cru-theme-color: var(--cru-danger-color); - --cru-theme-l1-color: var(--cru-danger-l1-color); - --cru-theme-l2-color: var(--cru-danger-l2-color); - --cru-theme-l3-color: var(--cru-danger-l3-color); - --cru-theme-d1-color: var(--cru-danger-d1-color); - --cru-theme-d2-color: var(--cru-danger-d2-color); - --cru-theme-d3-color: var(--cru-danger-d3-color); - --cru-theme-f1-color: var(--cru-danger-f1-color); - --cru-theme-f2-color: var(--cru-danger-f2-color); - --cru-theme-f3-color: var(--cru-danger-f3-color); - --cru-theme-r1-color: var(--cru-danger-r1-color); - --cru-theme-r2-color: var(--cru-danger-r2-color); - --cru-theme-r3-color: var(--cru-danger-r3-color); - --cru-theme-t-color: var(--cru-danger-t-color); - --cru-theme-t1-color: var(--cru-danger-t1-color); - --cru-theme-t2-color: var(--cru-danger-t2-color); - --cru-theme-t3-color: var(--cru-danger-t3-color); -} - -.cru-color-primary { - color: var(--cru-primary-color); -} - -.cru-color-primary-enhance { - color: var(--cru-primary-enhance-color); -} - -.cru-color-secondary { - color: var(--cru-secondary-color); -} - -.cru-color-success { - color: var(--cru-success-color); -} - -.cru-color-danger { - color: var(--cru-danger-color); -} +@import "./theme.css"; .cru-text-center { text-align: center; -- cgit v1.2.3 From c456e1769d2f06860f3d08272dc72407f96a42b8 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 14 Jul 2023 23:21:54 +0800 Subject: ... --- FrontEnd/src/index.css | 5 +- FrontEnd/src/views/common/button/Button.css | 14 +- FrontEnd/src/views/common/index.css | 5 + FrontEnd/src/views/common/theme.css | 296 +++++++++++++++------------- FrontEnd/tools/theme-generator.ts | 24 ++- 5 files changed, 189 insertions(+), 155 deletions(-) (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index 2faecfae..925d9c26 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -4,9 +4,6 @@ @import "./views/common/index.css"; -body { - background: var(--cru-background-color); -} small { line-height: 1.2; @@ -24,7 +21,7 @@ small { textarea { resize: none; outline: none; - border-color: var(--cru-background-2-color); + border-color: var(--cru-bg-2-color); } textarea:hover { diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index e0b8e733..7a271446 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -1,5 +1,5 @@ .cru-button:not(.outline) { - color: var(--cru-text-color); + color: var(--cru-light-color); cursor: pointer; padding: 0.2em 0.5em; border-radius: 0.2em; @@ -9,11 +9,11 @@ } .cru-button:not(.outline):hover { - background-color: var(--cru-theme-f1-color); + background-color: var(--cru-theme-b1-color); } .cru-button:not(.outline):active { - background-color: var(--cru-theme-f2-color); + background-color: var(--cru-theme-b2-color); } .cru-button:not(.outline):disabled { @@ -32,14 +32,14 @@ } .cru-button.outline:hover { - color: var(--cru-theme-f1-color); - border-color: var(--cru-theme-f1-color); + color: var(--cru-theme-b1-color); + border-color: var(--cru-theme-b1-color); background-color: var(--cru-background-color); } .cru-button.outline:active { - color: var(--cru-theme-f2-color); - border-color: var(--cru-theme-f2-color); + color: var(--cru-theme-b2-color); + border-color: var(--cru-theme-b2-color); background-color: var(--cru-background-1-color); } diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 06f2556b..16a7f924 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -1,5 +1,10 @@ @import "./theme.css"; +body { + background: var(--cru-bg-color); + color: var(--cru-text-color); +} + .cru-text-center { text-align: center; } diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css index 816bd3d1..9dfa1d9a 100644 --- a/FrontEnd/src/views/common/theme.css +++ b/FrontEnd/src/views/common/theme.css @@ -2,163 +2,179 @@ :root { --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 60%); - --cru-primary-l2-color: hsl(210 100% 70%); - --cru-primary-l3-color: hsl(210 100% 80%); - --cru-primary-d1-color: hsl(210 100% 40%); - --cru-primary-d2-color: hsl(210 100% 30%); - --cru-primary-d3-color: hsl(210 100% 20%); - --cru-primary-f1-color: hsl(210 100% 60%); - --cru-primary-f2-color: hsl(210 100% 70%); - --cru-primary-f3-color: hsl(210 100% 80%); - --cru-primary-b1-color: hsl(210 100% 40%); - --cru-primary-b2-color: hsl(210 100% 30%); - --cru-primary-b3-color: hsl(210 100% 20%); + --cru-primary-l1-color: hsl(210 100% 55%); + --cru-primary-l2-color: hsl(210 100% 60%); + --cru-primary-l3-color: hsl(210 100% 65%); + --cru-primary-d1-color: hsl(210 100% 45%); + --cru-primary-d2-color: hsl(210 100% 40%); + --cru-primary-d3-color: hsl(210 100% 35%); + --cru-primary-f1-color: hsl(210 100% 55%); + --cru-primary-f2-color: hsl(210 100% 60%); + --cru-primary-f3-color: hsl(210 100% 65%); + --cru-primary-b1-color: hsl(210 100% 45%); + --cru-primary-b2-color: hsl(210 100% 40%); + --cru-primary-b3-color: hsl(210 100% 35%); --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 60%); - --cru-secondary-l2-color: hsl(40 100% 70%); - --cru-secondary-l3-color: hsl(40 100% 80%); - --cru-secondary-d1-color: hsl(40 100% 40%); - --cru-secondary-d2-color: hsl(40 100% 30%); - --cru-secondary-d3-color: hsl(40 100% 20%); - --cru-secondary-f1-color: hsl(40 100% 60%); - --cru-secondary-f2-color: hsl(40 100% 70%); - --cru-secondary-f3-color: hsl(40 100% 80%); - --cru-secondary-b1-color: hsl(40 100% 40%); - --cru-secondary-b2-color: hsl(40 100% 30%); - --cru-secondary-b3-color: hsl(40 100% 20%); + --cru-secondary-l1-color: hsl(40 100% 55%); + --cru-secondary-l2-color: hsl(40 100% 60%); + --cru-secondary-l3-color: hsl(40 100% 65%); + --cru-secondary-d1-color: hsl(40 100% 45%); + --cru-secondary-d2-color: hsl(40 100% 40%); + --cru-secondary-d3-color: hsl(40 100% 35%); + --cru-secondary-f1-color: hsl(40 100% 55%); + --cru-secondary-f2-color: hsl(40 100% 60%); + --cru-secondary-f3-color: hsl(40 100% 65%); + --cru-secondary-b1-color: hsl(40 100% 45%); + --cru-secondary-b2-color: hsl(40 100% 40%); + --cru-secondary-b3-color: hsl(40 100% 35%); --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 60%); - --cru-tertiary-l2-color: hsl(160 100% 70%); - --cru-tertiary-l3-color: hsl(160 100% 80%); - --cru-tertiary-d1-color: hsl(160 100% 40%); - --cru-tertiary-d2-color: hsl(160 100% 30%); - --cru-tertiary-d3-color: hsl(160 100% 20%); - --cru-tertiary-f1-color: hsl(160 100% 60%); - --cru-tertiary-f2-color: hsl(160 100% 70%); - --cru-tertiary-f3-color: hsl(160 100% 80%); - --cru-tertiary-b1-color: hsl(160 100% 40%); - --cru-tertiary-b2-color: hsl(160 100% 30%); - --cru-tertiary-b3-color: hsl(160 100% 20%); + --cru-tertiary-l1-color: hsl(160 100% 55%); + --cru-tertiary-l2-color: hsl(160 100% 60%); + --cru-tertiary-l3-color: hsl(160 100% 65%); + --cru-tertiary-d1-color: hsl(160 100% 45%); + --cru-tertiary-d2-color: hsl(160 100% 40%); + --cru-tertiary-d3-color: hsl(160 100% 35%); + --cru-tertiary-f1-color: hsl(160 100% 55%); + --cru-tertiary-f2-color: hsl(160 100% 60%); + --cru-tertiary-f3-color: hsl(160 100% 65%); + --cru-tertiary-b1-color: hsl(160 100% 45%); + --cru-tertiary-b2-color: hsl(160 100% 40%); + --cru-tertiary-b3-color: hsl(160 100% 35%); --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 60%); - --cru-danger-l2-color: hsl(0 100% 70%); - --cru-danger-l3-color: hsl(0 100% 80%); - --cru-danger-d1-color: hsl(0 100% 40%); - --cru-danger-d2-color: hsl(0 100% 30%); - --cru-danger-d3-color: hsl(0 100% 20%); - --cru-danger-f1-color: hsl(0 100% 60%); - --cru-danger-f2-color: hsl(0 100% 70%); - --cru-danger-f3-color: hsl(0 100% 80%); - --cru-danger-b1-color: hsl(0 100% 40%); - --cru-danger-b2-color: hsl(0 100% 30%); - --cru-danger-b3-color: hsl(0 100% 20%); - --cru-success-color: hsl(120 100% 50%); - --cru-success-l1-color: hsl(120 100% 60%); - --cru-success-l2-color: hsl(120 100% 70%); - --cru-success-l3-color: hsl(120 100% 80%); - --cru-success-d1-color: hsl(120 100% 40%); - --cru-success-d2-color: hsl(120 100% 30%); - --cru-success-d3-color: hsl(120 100% 20%); - --cru-success-f1-color: hsl(120 100% 60%); - --cru-success-f2-color: hsl(120 100% 70%); - --cru-success-f3-color: hsl(120 100% 80%); - --cru-success-b1-color: hsl(120 100% 40%); - --cru-success-b2-color: hsl(120 100% 30%); - --cru-success-b3-color: hsl(120 100% 20%); + --cru-danger-l1-color: hsl(0 100% 55%); + --cru-danger-l2-color: hsl(0 100% 60%); + --cru-danger-l3-color: hsl(0 100% 65%); + --cru-danger-d1-color: hsl(0 100% 45%); + --cru-danger-d2-color: hsl(0 100% 40%); + --cru-danger-d3-color: hsl(0 100% 35%); + --cru-danger-f1-color: hsl(0 100% 55%); + --cru-danger-f2-color: hsl(0 100% 60%); + --cru-danger-f3-color: hsl(0 100% 65%); + --cru-danger-b1-color: hsl(0 100% 45%); + --cru-danger-b2-color: hsl(0 100% 40%); + --cru-danger-b3-color: hsl(0 100% 35%); + --cru-success-color: hsl(120 60% 50%); + --cru-success-l1-color: hsl(120 60% 55%); + --cru-success-l2-color: hsl(120 60% 60%); + --cru-success-l3-color: hsl(120 60% 65%); + --cru-success-d1-color: hsl(120 60% 45%); + --cru-success-d2-color: hsl(120 60% 40%); + --cru-success-d3-color: hsl(120 60% 35%); + --cru-success-f1-color: hsl(120 60% 55%); + --cru-success-f2-color: hsl(120 60% 60%); + --cru-success-f3-color: hsl(120 60% 65%); + --cru-success-b1-color: hsl(120 60% 45%); + --cru-success-b2-color: hsl(120 60% 40%); + --cru-success-b3-color: hsl(120 60% 35%); --cru-text-color: hsl(0 0% 0%); - --cru-text-1-color: hsl(0 0% 10%); - --cru-text-2-color: hsl(0 0% 20%); - --cru-text-3-color: hsl(0 0% 30%); + --cru-text-1-color: hsl(0 0% 5%); + --cru-text-2-color: hsl(0 0% 10%); + --cru-text-3-color: hsl(0 0% 15%); --cru-bg-color: hsl(0 0% 100%); - --cru-bg-1-color: hsl(0 0% 90%); - --cru-bg-2-color: hsl(0 0% 80%); - --cru-bg-3-color: hsl(0 0% 70%); + --cru-bg-1-color: hsl(0 0% 95%); + --cru-bg-2-color: hsl(0 0% 90%); + --cru-bg-3-color: hsl(0 0% 85%); + --cru-light-color: hsl(0 0% 100%); + --cru-light-1-color: hsl(0 0% 95%); + --cru-light-2-color: hsl(0 0% 90%); + --cru-light-3-color: hsl(0 0% 85%); + --cru-dark-color: hsl(0 0% 0%); + --cru-dark-1-color: hsl(0 0% 5%); + --cru-dark-2-color: hsl(0 0% 10%); + --cru-dark-3-color: hsl(0 0% 15%); --cru-disabled-color: hsl(0 0% 75%); - --cru-disabled-1-color: hsl(0 0% 65%); - --cru-disabled-2-color: hsl(0 0% 55%); - --cru-disabled-3-color: hsl(0 0% 45%); + --cru-disabled-1-color: hsl(0 0% 70%); + --cru-disabled-2-color: hsl(0 0% 65%); + --cru-disabled-3-color: hsl(0 0% 60%); } @media (prefers-color-scheme: dark) { :root { --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 60%); - --cru-primary-l2-color: hsl(210 100% 70%); - --cru-primary-l3-color: hsl(210 100% 80%); - --cru-primary-d1-color: hsl(210 100% 40%); - --cru-primary-d2-color: hsl(210 100% 30%); - --cru-primary-d3-color: hsl(210 100% 20%); - --cru-primary-f1-color: hsl(210 100% 40%); - --cru-primary-f2-color: hsl(210 100% 30%); - --cru-primary-f3-color: hsl(210 100% 20%); - --cru-primary-b1-color: hsl(210 100% 60%); - --cru-primary-b2-color: hsl(210 100% 70%); - --cru-primary-b3-color: hsl(210 100% 80%); + --cru-primary-l1-color: hsl(210 100% 55%); + --cru-primary-l2-color: hsl(210 100% 60%); + --cru-primary-l3-color: hsl(210 100% 65%); + --cru-primary-d1-color: hsl(210 100% 45%); + --cru-primary-d2-color: hsl(210 100% 40%); + --cru-primary-d3-color: hsl(210 100% 35%); + --cru-primary-f1-color: hsl(210 100% 45%); + --cru-primary-f2-color: hsl(210 100% 40%); + --cru-primary-f3-color: hsl(210 100% 35%); + --cru-primary-b1-color: hsl(210 100% 55%); + --cru-primary-b2-color: hsl(210 100% 60%); + --cru-primary-b3-color: hsl(210 100% 65%); --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 60%); - --cru-secondary-l2-color: hsl(40 100% 70%); - --cru-secondary-l3-color: hsl(40 100% 80%); - --cru-secondary-d1-color: hsl(40 100% 40%); - --cru-secondary-d2-color: hsl(40 100% 30%); - --cru-secondary-d3-color: hsl(40 100% 20%); - --cru-secondary-f1-color: hsl(40 100% 40%); - --cru-secondary-f2-color: hsl(40 100% 30%); - --cru-secondary-f3-color: hsl(40 100% 20%); - --cru-secondary-b1-color: hsl(40 100% 60%); - --cru-secondary-b2-color: hsl(40 100% 70%); - --cru-secondary-b3-color: hsl(40 100% 80%); + --cru-secondary-l1-color: hsl(40 100% 55%); + --cru-secondary-l2-color: hsl(40 100% 60%); + --cru-secondary-l3-color: hsl(40 100% 65%); + --cru-secondary-d1-color: hsl(40 100% 45%); + --cru-secondary-d2-color: hsl(40 100% 40%); + --cru-secondary-d3-color: hsl(40 100% 35%); + --cru-secondary-f1-color: hsl(40 100% 45%); + --cru-secondary-f2-color: hsl(40 100% 40%); + --cru-secondary-f3-color: hsl(40 100% 35%); + --cru-secondary-b1-color: hsl(40 100% 55%); + --cru-secondary-b2-color: hsl(40 100% 60%); + --cru-secondary-b3-color: hsl(40 100% 65%); --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 60%); - --cru-tertiary-l2-color: hsl(160 100% 70%); - --cru-tertiary-l3-color: hsl(160 100% 80%); - --cru-tertiary-d1-color: hsl(160 100% 40%); - --cru-tertiary-d2-color: hsl(160 100% 30%); - --cru-tertiary-d3-color: hsl(160 100% 20%); - --cru-tertiary-f1-color: hsl(160 100% 40%); - --cru-tertiary-f2-color: hsl(160 100% 30%); - --cru-tertiary-f3-color: hsl(160 100% 20%); - --cru-tertiary-b1-color: hsl(160 100% 60%); - --cru-tertiary-b2-color: hsl(160 100% 70%); - --cru-tertiary-b3-color: hsl(160 100% 80%); + --cru-tertiary-l1-color: hsl(160 100% 55%); + --cru-tertiary-l2-color: hsl(160 100% 60%); + --cru-tertiary-l3-color: hsl(160 100% 65%); + --cru-tertiary-d1-color: hsl(160 100% 45%); + --cru-tertiary-d2-color: hsl(160 100% 40%); + --cru-tertiary-d3-color: hsl(160 100% 35%); + --cru-tertiary-f1-color: hsl(160 100% 45%); + --cru-tertiary-f2-color: hsl(160 100% 40%); + --cru-tertiary-f3-color: hsl(160 100% 35%); + --cru-tertiary-b1-color: hsl(160 100% 55%); + --cru-tertiary-b2-color: hsl(160 100% 60%); + --cru-tertiary-b3-color: hsl(160 100% 65%); --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 60%); - --cru-danger-l2-color: hsl(0 100% 70%); - --cru-danger-l3-color: hsl(0 100% 80%); - --cru-danger-d1-color: hsl(0 100% 40%); - --cru-danger-d2-color: hsl(0 100% 30%); - --cru-danger-d3-color: hsl(0 100% 20%); - --cru-danger-f1-color: hsl(0 100% 40%); - --cru-danger-f2-color: hsl(0 100% 30%); - --cru-danger-f3-color: hsl(0 100% 20%); - --cru-danger-b1-color: hsl(0 100% 60%); - --cru-danger-b2-color: hsl(0 100% 70%); - --cru-danger-b3-color: hsl(0 100% 80%); - --cru-success-color: hsl(120 100% 50%); - --cru-success-l1-color: hsl(120 100% 60%); - --cru-success-l2-color: hsl(120 100% 70%); - --cru-success-l3-color: hsl(120 100% 80%); - --cru-success-d1-color: hsl(120 100% 40%); - --cru-success-d2-color: hsl(120 100% 30%); - --cru-success-d3-color: hsl(120 100% 20%); - --cru-success-f1-color: hsl(120 100% 40%); - --cru-success-f2-color: hsl(120 100% 30%); - --cru-success-f3-color: hsl(120 100% 20%); - --cru-success-b1-color: hsl(120 100% 60%); - --cru-success-b2-color: hsl(120 100% 70%); - --cru-success-b3-color: hsl(120 100% 80%); + --cru-danger-l1-color: hsl(0 100% 55%); + --cru-danger-l2-color: hsl(0 100% 60%); + --cru-danger-l3-color: hsl(0 100% 65%); + --cru-danger-d1-color: hsl(0 100% 45%); + --cru-danger-d2-color: hsl(0 100% 40%); + --cru-danger-d3-color: hsl(0 100% 35%); + --cru-danger-f1-color: hsl(0 100% 45%); + --cru-danger-f2-color: hsl(0 100% 40%); + --cru-danger-f3-color: hsl(0 100% 35%); + --cru-danger-b1-color: hsl(0 100% 55%); + --cru-danger-b2-color: hsl(0 100% 60%); + --cru-danger-b3-color: hsl(0 100% 65%); + --cru-success-color: hsl(120 60% 50%); + --cru-success-l1-color: hsl(120 60% 55%); + --cru-success-l2-color: hsl(120 60% 60%); + --cru-success-l3-color: hsl(120 60% 65%); + --cru-success-d1-color: hsl(120 60% 45%); + --cru-success-d2-color: hsl(120 60% 40%); + --cru-success-d3-color: hsl(120 60% 35%); + --cru-success-f1-color: hsl(120 60% 45%); + --cru-success-f2-color: hsl(120 60% 40%); + --cru-success-f3-color: hsl(120 60% 35%); + --cru-success-b1-color: hsl(120 60% 55%); + --cru-success-b2-color: hsl(120 60% 60%); + --cru-success-b3-color: hsl(120 60% 65%); --cru-text-color: hsl(0 0% 100%); - --cru-text-1-color: hsl(0 0% 90%); - --cru-text-2-color: hsl(0 0% 80%); - --cru-text-3-color: hsl(0 0% 70%); + --cru-text-1-color: hsl(0 0% 95%); + --cru-text-2-color: hsl(0 0% 90%); + --cru-text-3-color: hsl(0 0% 85%); --cru-bg-color: hsl(0 0% 0%); - --cru-bg-1-color: hsl(0 0% 10%); - --cru-bg-2-color: hsl(0 0% 20%); - --cru-bg-3-color: hsl(0 0% 30%); + --cru-bg-1-color: hsl(0 0% 5%); + --cru-bg-2-color: hsl(0 0% 10%); + --cru-bg-3-color: hsl(0 0% 15%); + --cru-light-color: hsl(0 0% 100%); + --cru-light-1-color: hsl(0 0% 95%); + --cru-light-2-color: hsl(0 0% 90%); + --cru-light-3-color: hsl(0 0% 85%); + --cru-dark-color: hsl(0 0% 0%); + --cru-dark-1-color: hsl(0 0% 5%); + --cru-dark-2-color: hsl(0 0% 10%); + --cru-dark-3-color: hsl(0 0% 15%); --cru-disabled-color: hsl(0 0% 25%); - --cru-disabled-1-color: hsl(0 0% 35%); - --cru-disabled-2-color: hsl(0 0% 45%); - --cru-disabled-3-color: hsl(0 0% 55%); + --cru-disabled-1-color: hsl(0 0% 30%); + --cru-disabled-2-color: hsl(0 0% 35%); + --cru-disabled-3-color: hsl(0 0% 40%); } } diff --git a/FrontEnd/tools/theme-generator.ts b/FrontEnd/tools/theme-generator.ts index 27dd5d1d..704a1052 100644 --- a/FrontEnd/tools/theme-generator.ts +++ b/FrontEnd/tools/theme-generator.ts @@ -30,11 +30,11 @@ class HslColor implements Color { ) {} lighter(level: number): HslColor { - return new HslColor(this.h, this.s, this.l + level * 10); + return new HslColor(this.h, this.s, this.l + level * 5); } darker(level: number): HslColor { - return new HslColor(this.h, this.s, this.l - level * 10); + return new HslColor(this.h, this.s, this.l - level * 5); } toCssString(): string { @@ -317,6 +317,16 @@ class Theme { mode === "light" ? GrayscaleColorGroup.white(this.prefix, "bg", this.levels) : GrayscaleColorGroup.black(this.prefix, "bg", this.levels); + const lightGroup = GrayscaleColorGroup.white( + this.prefix, + "light", + this.levels, + ); + const darkGroup = GrayscaleColorGroup.black( + this.prefix, + "dark", + this.levels, + ); const disabledGroup = mode == "light" ? new GrayscaleColorGroup( @@ -333,7 +343,13 @@ class Theme { "lighter", this.levels, ); - return new CompositeColorGroup([textGroup, bgGroup, disabledGroup]); + return new CompositeColorGroup([ + textGroup, + bgGroup, + lightGroup, + darkGroup, + disabledGroup, + ]); } generateCss(print: (text: string, indent: number) => void): void { @@ -370,7 +386,7 @@ class Theme { { name: "secondary", color: new HslColor(40, 100, 50) }, { name: "tertiary", color: new HslColor(160, 100, 50) }, { name: "danger", color: new HslColor(0, 100, 50) }, - { name: "success", color: new HslColor(120, 100, 50) }, + { name: "success", color: new HslColor(120, 60, 50) }, ]; const theme = new Theme(prefix, themeColors); -- cgit v1.2.3 From 59c424a38809be6afb170f11eadb0e4d14f10f69 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 14 Jul 2023 23:35:46 +0800 Subject: ... --- FrontEnd/src/common.ts | 10 - FrontEnd/src/index.css | 1 - FrontEnd/src/views/common/Card.css | 7 +- FrontEnd/src/views/common/button/Button.css | 12 +- FrontEnd/src/views/common/button/FlatButton.css | 2 +- FrontEnd/src/views/common/index.css | 8 + FrontEnd/src/views/common/theme-color.css | 260 +++++++++++++++++++++++ FrontEnd/src/views/common/theme.css | 261 +----------------------- 8 files changed, 279 insertions(+), 282 deletions(-) create mode 100644 FrontEnd/src/views/common/theme-color.css (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/common.ts b/FrontEnd/src/common.ts index 6dcd2a9e..965f9933 100644 --- a/FrontEnd/src/common.ts +++ b/FrontEnd/src/common.ts @@ -8,13 +8,3 @@ export const highlightTimelineUsername = "crupest"; export type { I18nText } from "./i18n"; export { c, convertI18nText } from "./i18n"; export { default as useC } from "./utilities/hooks/use-c"; - -export const themeColors = [ - "primary", - "secondary", - "tertiary", - "danger", - "success", -] as const; - -export type ThemeColor = (typeof themeColors)[number]; diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index 925d9c26..3478db05 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -1,4 +1,3 @@ -@import "npm:bootstrap/dist/css/bootstrap-reboot.css"; @import "npm:bootstrap/dist/css/bootstrap-grid.css"; @import "npm:bootstrap-icons/font/bootstrap-icons.css"; diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css index 6de0dd8e..fa470130 100644 --- a/FrontEnd/src/views/common/Card.css +++ b/FrontEnd/src/views/common/Card.css @@ -1,15 +1,10 @@ -:root { - --cru-card-border-radius: 8px; -} - .cru-card { border: 1px solid; border-color: #e9ecef; border-radius: var(--cru-card-border-radius); - background: #fefeff; transition: all 0.3s; } .cru-card:hover { border-color: var(--cru-primary-color); -} +} \ No newline at end of file diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 7a271446..0df22ebe 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -9,11 +9,11 @@ } .cru-button:not(.outline):hover { - background-color: var(--cru-theme-b1-color); + background-color: var(--cru-theme-d1-color); } .cru-button:not(.outline):active { - background-color: var(--cru-theme-b2-color); + background-color: var(--cru-theme-d2-color); } .cru-button:not(.outline):disabled { @@ -32,14 +32,14 @@ } .cru-button.outline:hover { - color: var(--cru-theme-b1-color); - border-color: var(--cru-theme-b1-color); + color: var(--cru-theme-d1-color); + border-color: var(--cru-theme-d1-color); background-color: var(--cru-background-color); } .cru-button.outline:active { - color: var(--cru-theme-b2-color); - border-color: var(--cru-theme-b2-color); + color: var(--cru-theme-l2-color); + border-color: var(--cru-theme-l2-color); background-color: var(--cru-background-1-color); } diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css index ea45e783..01eabca9 100644 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -9,7 +9,7 @@ } .cru-flat-button.disabled { - color: var(--cru-theme-f1-color); + color: var(--cru-disabled-color); cursor: default; } diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 16a7f924..9d69997b 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -1,10 +1,18 @@ @import "./theme.css"; +* { + box-sizing: border-box; + margin-inline: 0; + margin-block: 0; +} + body { background: var(--cru-bg-color); color: var(--cru-text-color); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } + .cru-text-center { text-align: center; } diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css new file mode 100644 index 00000000..9dfa1d9a --- /dev/null +++ b/FrontEnd/src/views/common/theme-color.css @@ -0,0 +1,260 @@ +/* Generated by theme-generator.ts */ + +:root { + --cru-primary-color: hsl(210 100% 50%); + --cru-primary-l1-color: hsl(210 100% 55%); + --cru-primary-l2-color: hsl(210 100% 60%); + --cru-primary-l3-color: hsl(210 100% 65%); + --cru-primary-d1-color: hsl(210 100% 45%); + --cru-primary-d2-color: hsl(210 100% 40%); + --cru-primary-d3-color: hsl(210 100% 35%); + --cru-primary-f1-color: hsl(210 100% 55%); + --cru-primary-f2-color: hsl(210 100% 60%); + --cru-primary-f3-color: hsl(210 100% 65%); + --cru-primary-b1-color: hsl(210 100% 45%); + --cru-primary-b2-color: hsl(210 100% 40%); + --cru-primary-b3-color: hsl(210 100% 35%); + --cru-secondary-color: hsl(40 100% 50%); + --cru-secondary-l1-color: hsl(40 100% 55%); + --cru-secondary-l2-color: hsl(40 100% 60%); + --cru-secondary-l3-color: hsl(40 100% 65%); + --cru-secondary-d1-color: hsl(40 100% 45%); + --cru-secondary-d2-color: hsl(40 100% 40%); + --cru-secondary-d3-color: hsl(40 100% 35%); + --cru-secondary-f1-color: hsl(40 100% 55%); + --cru-secondary-f2-color: hsl(40 100% 60%); + --cru-secondary-f3-color: hsl(40 100% 65%); + --cru-secondary-b1-color: hsl(40 100% 45%); + --cru-secondary-b2-color: hsl(40 100% 40%); + --cru-secondary-b3-color: hsl(40 100% 35%); + --cru-tertiary-color: hsl(160 100% 50%); + --cru-tertiary-l1-color: hsl(160 100% 55%); + --cru-tertiary-l2-color: hsl(160 100% 60%); + --cru-tertiary-l3-color: hsl(160 100% 65%); + --cru-tertiary-d1-color: hsl(160 100% 45%); + --cru-tertiary-d2-color: hsl(160 100% 40%); + --cru-tertiary-d3-color: hsl(160 100% 35%); + --cru-tertiary-f1-color: hsl(160 100% 55%); + --cru-tertiary-f2-color: hsl(160 100% 60%); + --cru-tertiary-f3-color: hsl(160 100% 65%); + --cru-tertiary-b1-color: hsl(160 100% 45%); + --cru-tertiary-b2-color: hsl(160 100% 40%); + --cru-tertiary-b3-color: hsl(160 100% 35%); + --cru-danger-color: hsl(0 100% 50%); + --cru-danger-l1-color: hsl(0 100% 55%); + --cru-danger-l2-color: hsl(0 100% 60%); + --cru-danger-l3-color: hsl(0 100% 65%); + --cru-danger-d1-color: hsl(0 100% 45%); + --cru-danger-d2-color: hsl(0 100% 40%); + --cru-danger-d3-color: hsl(0 100% 35%); + --cru-danger-f1-color: hsl(0 100% 55%); + --cru-danger-f2-color: hsl(0 100% 60%); + --cru-danger-f3-color: hsl(0 100% 65%); + --cru-danger-b1-color: hsl(0 100% 45%); + --cru-danger-b2-color: hsl(0 100% 40%); + --cru-danger-b3-color: hsl(0 100% 35%); + --cru-success-color: hsl(120 60% 50%); + --cru-success-l1-color: hsl(120 60% 55%); + --cru-success-l2-color: hsl(120 60% 60%); + --cru-success-l3-color: hsl(120 60% 65%); + --cru-success-d1-color: hsl(120 60% 45%); + --cru-success-d2-color: hsl(120 60% 40%); + --cru-success-d3-color: hsl(120 60% 35%); + --cru-success-f1-color: hsl(120 60% 55%); + --cru-success-f2-color: hsl(120 60% 60%); + --cru-success-f3-color: hsl(120 60% 65%); + --cru-success-b1-color: hsl(120 60% 45%); + --cru-success-b2-color: hsl(120 60% 40%); + --cru-success-b3-color: hsl(120 60% 35%); + --cru-text-color: hsl(0 0% 0%); + --cru-text-1-color: hsl(0 0% 5%); + --cru-text-2-color: hsl(0 0% 10%); + --cru-text-3-color: hsl(0 0% 15%); + --cru-bg-color: hsl(0 0% 100%); + --cru-bg-1-color: hsl(0 0% 95%); + --cru-bg-2-color: hsl(0 0% 90%); + --cru-bg-3-color: hsl(0 0% 85%); + --cru-light-color: hsl(0 0% 100%); + --cru-light-1-color: hsl(0 0% 95%); + --cru-light-2-color: hsl(0 0% 90%); + --cru-light-3-color: hsl(0 0% 85%); + --cru-dark-color: hsl(0 0% 0%); + --cru-dark-1-color: hsl(0 0% 5%); + --cru-dark-2-color: hsl(0 0% 10%); + --cru-dark-3-color: hsl(0 0% 15%); + --cru-disabled-color: hsl(0 0% 75%); + --cru-disabled-1-color: hsl(0 0% 70%); + --cru-disabled-2-color: hsl(0 0% 65%); + --cru-disabled-3-color: hsl(0 0% 60%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-primary-color: hsl(210 100% 50%); + --cru-primary-l1-color: hsl(210 100% 55%); + --cru-primary-l2-color: hsl(210 100% 60%); + --cru-primary-l3-color: hsl(210 100% 65%); + --cru-primary-d1-color: hsl(210 100% 45%); + --cru-primary-d2-color: hsl(210 100% 40%); + --cru-primary-d3-color: hsl(210 100% 35%); + --cru-primary-f1-color: hsl(210 100% 45%); + --cru-primary-f2-color: hsl(210 100% 40%); + --cru-primary-f3-color: hsl(210 100% 35%); + --cru-primary-b1-color: hsl(210 100% 55%); + --cru-primary-b2-color: hsl(210 100% 60%); + --cru-primary-b3-color: hsl(210 100% 65%); + --cru-secondary-color: hsl(40 100% 50%); + --cru-secondary-l1-color: hsl(40 100% 55%); + --cru-secondary-l2-color: hsl(40 100% 60%); + --cru-secondary-l3-color: hsl(40 100% 65%); + --cru-secondary-d1-color: hsl(40 100% 45%); + --cru-secondary-d2-color: hsl(40 100% 40%); + --cru-secondary-d3-color: hsl(40 100% 35%); + --cru-secondary-f1-color: hsl(40 100% 45%); + --cru-secondary-f2-color: hsl(40 100% 40%); + --cru-secondary-f3-color: hsl(40 100% 35%); + --cru-secondary-b1-color: hsl(40 100% 55%); + --cru-secondary-b2-color: hsl(40 100% 60%); + --cru-secondary-b3-color: hsl(40 100% 65%); + --cru-tertiary-color: hsl(160 100% 50%); + --cru-tertiary-l1-color: hsl(160 100% 55%); + --cru-tertiary-l2-color: hsl(160 100% 60%); + --cru-tertiary-l3-color: hsl(160 100% 65%); + --cru-tertiary-d1-color: hsl(160 100% 45%); + --cru-tertiary-d2-color: hsl(160 100% 40%); + --cru-tertiary-d3-color: hsl(160 100% 35%); + --cru-tertiary-f1-color: hsl(160 100% 45%); + --cru-tertiary-f2-color: hsl(160 100% 40%); + --cru-tertiary-f3-color: hsl(160 100% 35%); + --cru-tertiary-b1-color: hsl(160 100% 55%); + --cru-tertiary-b2-color: hsl(160 100% 60%); + --cru-tertiary-b3-color: hsl(160 100% 65%); + --cru-danger-color: hsl(0 100% 50%); + --cru-danger-l1-color: hsl(0 100% 55%); + --cru-danger-l2-color: hsl(0 100% 60%); + --cru-danger-l3-color: hsl(0 100% 65%); + --cru-danger-d1-color: hsl(0 100% 45%); + --cru-danger-d2-color: hsl(0 100% 40%); + --cru-danger-d3-color: hsl(0 100% 35%); + --cru-danger-f1-color: hsl(0 100% 45%); + --cru-danger-f2-color: hsl(0 100% 40%); + --cru-danger-f3-color: hsl(0 100% 35%); + --cru-danger-b1-color: hsl(0 100% 55%); + --cru-danger-b2-color: hsl(0 100% 60%); + --cru-danger-b3-color: hsl(0 100% 65%); + --cru-success-color: hsl(120 60% 50%); + --cru-success-l1-color: hsl(120 60% 55%); + --cru-success-l2-color: hsl(120 60% 60%); + --cru-success-l3-color: hsl(120 60% 65%); + --cru-success-d1-color: hsl(120 60% 45%); + --cru-success-d2-color: hsl(120 60% 40%); + --cru-success-d3-color: hsl(120 60% 35%); + --cru-success-f1-color: hsl(120 60% 45%); + --cru-success-f2-color: hsl(120 60% 40%); + --cru-success-f3-color: hsl(120 60% 35%); + --cru-success-b1-color: hsl(120 60% 55%); + --cru-success-b2-color: hsl(120 60% 60%); + --cru-success-b3-color: hsl(120 60% 65%); + --cru-text-color: hsl(0 0% 100%); + --cru-text-1-color: hsl(0 0% 95%); + --cru-text-2-color: hsl(0 0% 90%); + --cru-text-3-color: hsl(0 0% 85%); + --cru-bg-color: hsl(0 0% 0%); + --cru-bg-1-color: hsl(0 0% 5%); + --cru-bg-2-color: hsl(0 0% 10%); + --cru-bg-3-color: hsl(0 0% 15%); + --cru-light-color: hsl(0 0% 100%); + --cru-light-1-color: hsl(0 0% 95%); + --cru-light-2-color: hsl(0 0% 90%); + --cru-light-3-color: hsl(0 0% 85%); + --cru-dark-color: hsl(0 0% 0%); + --cru-dark-1-color: hsl(0 0% 5%); + --cru-dark-2-color: hsl(0 0% 10%); + --cru-dark-3-color: hsl(0 0% 15%); + --cru-disabled-color: hsl(0 0% 25%); + --cru-disabled-1-color: hsl(0 0% 30%); + --cru-disabled-2-color: hsl(0 0% 35%); + --cru-disabled-3-color: hsl(0 0% 40%); + } +} + +.cru-primary { + --cru-theme-color: var(--cru-primary-color); + --cru-theme-l1-color: var(--cru-primary-l1-color); + --cru-theme-l2-color: var(--cru-primary-l2-color); + --cru-theme-l3-color: var(--cru-primary-l3-color); + --cru-theme-d1-color: var(--cru-primary-d1-color); + --cru-theme-d2-color: var(--cru-primary-d2-color); + --cru-theme-d3-color: var(--cru-primary-d3-color); + --cru-theme-f1-color: var(--cru-primary-f1-color); + --cru-theme-f2-color: var(--cru-primary-f2-color); + --cru-theme-f3-color: var(--cru-primary-f3-color); + --cru-theme-b1-color: var(--cru-primary-b1-color); + --cru-theme-b2-color: var(--cru-primary-b2-color); + --cru-theme-b3-color: var(--cru-primary-b3-color); +} + +.cru-secondary { + --cru-theme-color: var(--cru-secondary-color); + --cru-theme-l1-color: var(--cru-secondary-l1-color); + --cru-theme-l2-color: var(--cru-secondary-l2-color); + --cru-theme-l3-color: var(--cru-secondary-l3-color); + --cru-theme-d1-color: var(--cru-secondary-d1-color); + --cru-theme-d2-color: var(--cru-secondary-d2-color); + --cru-theme-d3-color: var(--cru-secondary-d3-color); + --cru-theme-f1-color: var(--cru-secondary-f1-color); + --cru-theme-f2-color: var(--cru-secondary-f2-color); + --cru-theme-f3-color: var(--cru-secondary-f3-color); + --cru-theme-b1-color: var(--cru-secondary-b1-color); + --cru-theme-b2-color: var(--cru-secondary-b2-color); + --cru-theme-b3-color: var(--cru-secondary-b3-color); +} + +.cru-tertiary { + --cru-theme-color: var(--cru-tertiary-color); + --cru-theme-l1-color: var(--cru-tertiary-l1-color); + --cru-theme-l2-color: var(--cru-tertiary-l2-color); + --cru-theme-l3-color: var(--cru-tertiary-l3-color); + --cru-theme-d1-color: var(--cru-tertiary-d1-color); + --cru-theme-d2-color: var(--cru-tertiary-d2-color); + --cru-theme-d3-color: var(--cru-tertiary-d3-color); + --cru-theme-f1-color: var(--cru-tertiary-f1-color); + --cru-theme-f2-color: var(--cru-tertiary-f2-color); + --cru-theme-f3-color: var(--cru-tertiary-f3-color); + --cru-theme-b1-color: var(--cru-tertiary-b1-color); + --cru-theme-b2-color: var(--cru-tertiary-b2-color); + --cru-theme-b3-color: var(--cru-tertiary-b3-color); +} + +.cru-danger { + --cru-theme-color: var(--cru-danger-color); + --cru-theme-l1-color: var(--cru-danger-l1-color); + --cru-theme-l2-color: var(--cru-danger-l2-color); + --cru-theme-l3-color: var(--cru-danger-l3-color); + --cru-theme-d1-color: var(--cru-danger-d1-color); + --cru-theme-d2-color: var(--cru-danger-d2-color); + --cru-theme-d3-color: var(--cru-danger-d3-color); + --cru-theme-f1-color: var(--cru-danger-f1-color); + --cru-theme-f2-color: var(--cru-danger-f2-color); + --cru-theme-f3-color: var(--cru-danger-f3-color); + --cru-theme-b1-color: var(--cru-danger-b1-color); + --cru-theme-b2-color: var(--cru-danger-b2-color); + --cru-theme-b3-color: var(--cru-danger-b3-color); +} + +.cru-success { + --cru-theme-color: var(--cru-success-color); + --cru-theme-l1-color: var(--cru-success-l1-color); + --cru-theme-l2-color: var(--cru-success-l2-color); + --cru-theme-l3-color: var(--cru-success-l3-color); + --cru-theme-d1-color: var(--cru-success-d1-color); + --cru-theme-d2-color: var(--cru-success-d2-color); + --cru-theme-d3-color: var(--cru-success-d3-color); + --cru-theme-f1-color: var(--cru-success-f1-color); + --cru-theme-f2-color: var(--cru-success-f2-color); + --cru-theme-f3-color: var(--cru-success-f3-color); + --cru-theme-b1-color: var(--cru-success-b1-color); + --cru-theme-b2-color: var(--cru-success-b2-color); + --cru-theme-b3-color: var(--cru-success-b3-color); +} + diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css index 9dfa1d9a..28c16627 100644 --- a/FrontEnd/src/views/common/theme.css +++ b/FrontEnd/src/views/common/theme.css @@ -1,260 +1,5 @@ -/* Generated by theme-generator.ts */ +@import "./theme-color.css"; :root { - --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 55%); - --cru-primary-l2-color: hsl(210 100% 60%); - --cru-primary-l3-color: hsl(210 100% 65%); - --cru-primary-d1-color: hsl(210 100% 45%); - --cru-primary-d2-color: hsl(210 100% 40%); - --cru-primary-d3-color: hsl(210 100% 35%); - --cru-primary-f1-color: hsl(210 100% 55%); - --cru-primary-f2-color: hsl(210 100% 60%); - --cru-primary-f3-color: hsl(210 100% 65%); - --cru-primary-b1-color: hsl(210 100% 45%); - --cru-primary-b2-color: hsl(210 100% 40%); - --cru-primary-b3-color: hsl(210 100% 35%); - --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 55%); - --cru-secondary-l2-color: hsl(40 100% 60%); - --cru-secondary-l3-color: hsl(40 100% 65%); - --cru-secondary-d1-color: hsl(40 100% 45%); - --cru-secondary-d2-color: hsl(40 100% 40%); - --cru-secondary-d3-color: hsl(40 100% 35%); - --cru-secondary-f1-color: hsl(40 100% 55%); - --cru-secondary-f2-color: hsl(40 100% 60%); - --cru-secondary-f3-color: hsl(40 100% 65%); - --cru-secondary-b1-color: hsl(40 100% 45%); - --cru-secondary-b2-color: hsl(40 100% 40%); - --cru-secondary-b3-color: hsl(40 100% 35%); - --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 55%); - --cru-tertiary-l2-color: hsl(160 100% 60%); - --cru-tertiary-l3-color: hsl(160 100% 65%); - --cru-tertiary-d1-color: hsl(160 100% 45%); - --cru-tertiary-d2-color: hsl(160 100% 40%); - --cru-tertiary-d3-color: hsl(160 100% 35%); - --cru-tertiary-f1-color: hsl(160 100% 55%); - --cru-tertiary-f2-color: hsl(160 100% 60%); - --cru-tertiary-f3-color: hsl(160 100% 65%); - --cru-tertiary-b1-color: hsl(160 100% 45%); - --cru-tertiary-b2-color: hsl(160 100% 40%); - --cru-tertiary-b3-color: hsl(160 100% 35%); - --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 55%); - --cru-danger-l2-color: hsl(0 100% 60%); - --cru-danger-l3-color: hsl(0 100% 65%); - --cru-danger-d1-color: hsl(0 100% 45%); - --cru-danger-d2-color: hsl(0 100% 40%); - --cru-danger-d3-color: hsl(0 100% 35%); - --cru-danger-f1-color: hsl(0 100% 55%); - --cru-danger-f2-color: hsl(0 100% 60%); - --cru-danger-f3-color: hsl(0 100% 65%); - --cru-danger-b1-color: hsl(0 100% 45%); - --cru-danger-b2-color: hsl(0 100% 40%); - --cru-danger-b3-color: hsl(0 100% 35%); - --cru-success-color: hsl(120 60% 50%); - --cru-success-l1-color: hsl(120 60% 55%); - --cru-success-l2-color: hsl(120 60% 60%); - --cru-success-l3-color: hsl(120 60% 65%); - --cru-success-d1-color: hsl(120 60% 45%); - --cru-success-d2-color: hsl(120 60% 40%); - --cru-success-d3-color: hsl(120 60% 35%); - --cru-success-f1-color: hsl(120 60% 55%); - --cru-success-f2-color: hsl(120 60% 60%); - --cru-success-f3-color: hsl(120 60% 65%); - --cru-success-b1-color: hsl(120 60% 45%); - --cru-success-b2-color: hsl(120 60% 40%); - --cru-success-b3-color: hsl(120 60% 35%); - --cru-text-color: hsl(0 0% 0%); - --cru-text-1-color: hsl(0 0% 5%); - --cru-text-2-color: hsl(0 0% 10%); - --cru-text-3-color: hsl(0 0% 15%); - --cru-bg-color: hsl(0 0% 100%); - --cru-bg-1-color: hsl(0 0% 95%); - --cru-bg-2-color: hsl(0 0% 90%); - --cru-bg-3-color: hsl(0 0% 85%); - --cru-light-color: hsl(0 0% 100%); - --cru-light-1-color: hsl(0 0% 95%); - --cru-light-2-color: hsl(0 0% 90%); - --cru-light-3-color: hsl(0 0% 85%); - --cru-dark-color: hsl(0 0% 0%); - --cru-dark-1-color: hsl(0 0% 5%); - --cru-dark-2-color: hsl(0 0% 10%); - --cru-dark-3-color: hsl(0 0% 15%); - --cru-disabled-color: hsl(0 0% 75%); - --cru-disabled-1-color: hsl(0 0% 70%); - --cru-disabled-2-color: hsl(0 0% 65%); - --cru-disabled-3-color: hsl(0 0% 60%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 55%); - --cru-primary-l2-color: hsl(210 100% 60%); - --cru-primary-l3-color: hsl(210 100% 65%); - --cru-primary-d1-color: hsl(210 100% 45%); - --cru-primary-d2-color: hsl(210 100% 40%); - --cru-primary-d3-color: hsl(210 100% 35%); - --cru-primary-f1-color: hsl(210 100% 45%); - --cru-primary-f2-color: hsl(210 100% 40%); - --cru-primary-f3-color: hsl(210 100% 35%); - --cru-primary-b1-color: hsl(210 100% 55%); - --cru-primary-b2-color: hsl(210 100% 60%); - --cru-primary-b3-color: hsl(210 100% 65%); - --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 55%); - --cru-secondary-l2-color: hsl(40 100% 60%); - --cru-secondary-l3-color: hsl(40 100% 65%); - --cru-secondary-d1-color: hsl(40 100% 45%); - --cru-secondary-d2-color: hsl(40 100% 40%); - --cru-secondary-d3-color: hsl(40 100% 35%); - --cru-secondary-f1-color: hsl(40 100% 45%); - --cru-secondary-f2-color: hsl(40 100% 40%); - --cru-secondary-f3-color: hsl(40 100% 35%); - --cru-secondary-b1-color: hsl(40 100% 55%); - --cru-secondary-b2-color: hsl(40 100% 60%); - --cru-secondary-b3-color: hsl(40 100% 65%); - --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 55%); - --cru-tertiary-l2-color: hsl(160 100% 60%); - --cru-tertiary-l3-color: hsl(160 100% 65%); - --cru-tertiary-d1-color: hsl(160 100% 45%); - --cru-tertiary-d2-color: hsl(160 100% 40%); - --cru-tertiary-d3-color: hsl(160 100% 35%); - --cru-tertiary-f1-color: hsl(160 100% 45%); - --cru-tertiary-f2-color: hsl(160 100% 40%); - --cru-tertiary-f3-color: hsl(160 100% 35%); - --cru-tertiary-b1-color: hsl(160 100% 55%); - --cru-tertiary-b2-color: hsl(160 100% 60%); - --cru-tertiary-b3-color: hsl(160 100% 65%); - --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 55%); - --cru-danger-l2-color: hsl(0 100% 60%); - --cru-danger-l3-color: hsl(0 100% 65%); - --cru-danger-d1-color: hsl(0 100% 45%); - --cru-danger-d2-color: hsl(0 100% 40%); - --cru-danger-d3-color: hsl(0 100% 35%); - --cru-danger-f1-color: hsl(0 100% 45%); - --cru-danger-f2-color: hsl(0 100% 40%); - --cru-danger-f3-color: hsl(0 100% 35%); - --cru-danger-b1-color: hsl(0 100% 55%); - --cru-danger-b2-color: hsl(0 100% 60%); - --cru-danger-b3-color: hsl(0 100% 65%); - --cru-success-color: hsl(120 60% 50%); - --cru-success-l1-color: hsl(120 60% 55%); - --cru-success-l2-color: hsl(120 60% 60%); - --cru-success-l3-color: hsl(120 60% 65%); - --cru-success-d1-color: hsl(120 60% 45%); - --cru-success-d2-color: hsl(120 60% 40%); - --cru-success-d3-color: hsl(120 60% 35%); - --cru-success-f1-color: hsl(120 60% 45%); - --cru-success-f2-color: hsl(120 60% 40%); - --cru-success-f3-color: hsl(120 60% 35%); - --cru-success-b1-color: hsl(120 60% 55%); - --cru-success-b2-color: hsl(120 60% 60%); - --cru-success-b3-color: hsl(120 60% 65%); - --cru-text-color: hsl(0 0% 100%); - --cru-text-1-color: hsl(0 0% 95%); - --cru-text-2-color: hsl(0 0% 90%); - --cru-text-3-color: hsl(0 0% 85%); - --cru-bg-color: hsl(0 0% 0%); - --cru-bg-1-color: hsl(0 0% 5%); - --cru-bg-2-color: hsl(0 0% 10%); - --cru-bg-3-color: hsl(0 0% 15%); - --cru-light-color: hsl(0 0% 100%); - --cru-light-1-color: hsl(0 0% 95%); - --cru-light-2-color: hsl(0 0% 90%); - --cru-light-3-color: hsl(0 0% 85%); - --cru-dark-color: hsl(0 0% 0%); - --cru-dark-1-color: hsl(0 0% 5%); - --cru-dark-2-color: hsl(0 0% 10%); - --cru-dark-3-color: hsl(0 0% 15%); - --cru-disabled-color: hsl(0 0% 25%); - --cru-disabled-1-color: hsl(0 0% 30%); - --cru-disabled-2-color: hsl(0 0% 35%); - --cru-disabled-3-color: hsl(0 0% 40%); - } -} - -.cru-primary { - --cru-theme-color: var(--cru-primary-color); - --cru-theme-l1-color: var(--cru-primary-l1-color); - --cru-theme-l2-color: var(--cru-primary-l2-color); - --cru-theme-l3-color: var(--cru-primary-l3-color); - --cru-theme-d1-color: var(--cru-primary-d1-color); - --cru-theme-d2-color: var(--cru-primary-d2-color); - --cru-theme-d3-color: var(--cru-primary-d3-color); - --cru-theme-f1-color: var(--cru-primary-f1-color); - --cru-theme-f2-color: var(--cru-primary-f2-color); - --cru-theme-f3-color: var(--cru-primary-f3-color); - --cru-theme-b1-color: var(--cru-primary-b1-color); - --cru-theme-b2-color: var(--cru-primary-b2-color); - --cru-theme-b3-color: var(--cru-primary-b3-color); -} - -.cru-secondary { - --cru-theme-color: var(--cru-secondary-color); - --cru-theme-l1-color: var(--cru-secondary-l1-color); - --cru-theme-l2-color: var(--cru-secondary-l2-color); - --cru-theme-l3-color: var(--cru-secondary-l3-color); - --cru-theme-d1-color: var(--cru-secondary-d1-color); - --cru-theme-d2-color: var(--cru-secondary-d2-color); - --cru-theme-d3-color: var(--cru-secondary-d3-color); - --cru-theme-f1-color: var(--cru-secondary-f1-color); - --cru-theme-f2-color: var(--cru-secondary-f2-color); - --cru-theme-f3-color: var(--cru-secondary-f3-color); - --cru-theme-b1-color: var(--cru-secondary-b1-color); - --cru-theme-b2-color: var(--cru-secondary-b2-color); - --cru-theme-b3-color: var(--cru-secondary-b3-color); -} - -.cru-tertiary { - --cru-theme-color: var(--cru-tertiary-color); - --cru-theme-l1-color: var(--cru-tertiary-l1-color); - --cru-theme-l2-color: var(--cru-tertiary-l2-color); - --cru-theme-l3-color: var(--cru-tertiary-l3-color); - --cru-theme-d1-color: var(--cru-tertiary-d1-color); - --cru-theme-d2-color: var(--cru-tertiary-d2-color); - --cru-theme-d3-color: var(--cru-tertiary-d3-color); - --cru-theme-f1-color: var(--cru-tertiary-f1-color); - --cru-theme-f2-color: var(--cru-tertiary-f2-color); - --cru-theme-f3-color: var(--cru-tertiary-f3-color); - --cru-theme-b1-color: var(--cru-tertiary-b1-color); - --cru-theme-b2-color: var(--cru-tertiary-b2-color); - --cru-theme-b3-color: var(--cru-tertiary-b3-color); -} - -.cru-danger { - --cru-theme-color: var(--cru-danger-color); - --cru-theme-l1-color: var(--cru-danger-l1-color); - --cru-theme-l2-color: var(--cru-danger-l2-color); - --cru-theme-l3-color: var(--cru-danger-l3-color); - --cru-theme-d1-color: var(--cru-danger-d1-color); - --cru-theme-d2-color: var(--cru-danger-d2-color); - --cru-theme-d3-color: var(--cru-danger-d3-color); - --cru-theme-f1-color: var(--cru-danger-f1-color); - --cru-theme-f2-color: var(--cru-danger-f2-color); - --cru-theme-f3-color: var(--cru-danger-f3-color); - --cru-theme-b1-color: var(--cru-danger-b1-color); - --cru-theme-b2-color: var(--cru-danger-b2-color); - --cru-theme-b3-color: var(--cru-danger-b3-color); -} - -.cru-success { - --cru-theme-color: var(--cru-success-color); - --cru-theme-l1-color: var(--cru-success-l1-color); - --cru-theme-l2-color: var(--cru-success-l2-color); - --cru-theme-l3-color: var(--cru-success-l3-color); - --cru-theme-d1-color: var(--cru-success-d1-color); - --cru-theme-d2-color: var(--cru-success-d2-color); - --cru-theme-d3-color: var(--cru-success-d3-color); - --cru-theme-f1-color: var(--cru-success-f1-color); - --cru-theme-f2-color: var(--cru-success-f2-color); - --cru-theme-f3-color: var(--cru-success-f3-color); - --cru-theme-b1-color: var(--cru-success-b1-color); - --cru-theme-b2-color: var(--cru-success-b2-color); - --cru-theme-b3-color: var(--cru-success-b3-color); -} - + --cru-card-border-radius: 8px; +} \ No newline at end of file -- cgit v1.2.3 From 85659d977ac501a13886c1c7098763935af416e2 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 15 Jul 2023 17:02:00 +0800 Subject: ... --- FrontEnd/src/views/common/AppBar.css | 15 +- FrontEnd/src/views/common/AppBar.tsx | 24 +- FrontEnd/src/views/common/alert/alert.css | 10 +- FrontEnd/src/views/common/button/Button.css | 52 ++-- FrontEnd/src/views/common/button/FlatButton.css | 31 +- FrontEnd/src/views/common/button/IconButton.css | 2 +- FrontEnd/src/views/common/index.css | 14 +- FrontEnd/src/views/common/menu/Menu.css | 6 +- FrontEnd/src/views/common/theme-color.css | 319 ++++++--------------- FrontEnd/src/views/common/theme.css | 31 ++ FrontEnd/tools/theme-generator.ts | 366 ++++++++++++------------ 11 files changed, 385 insertions(+), 485 deletions(-) (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index 3ec4fa36..272a99ad 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -16,16 +16,18 @@ } .app-bar a { - color: var(--cru-primary-t1-color); + color: var(--cru-primary-on-color); text-decoration: none; margin: 0 1em; transition: color 1s; } + .app-bar a:hover { - color: var(--cru-primary-t-color); + color: var(--cru-primary-on-color); } + .app-bar a.active { - color: var(--cru-primary-t-color); + color: var(--cru-primary-on-color); } .app-bar-brand { @@ -65,22 +67,27 @@ background-color: var(--cru-primary-color); flex-direction: column; } + .small-screen .app-bar-main-area.app-bar-collapse { transform: scale(1, 0); } + .small-screen .app-bar-main-area a { text-align: left; padding: 0.5em 0.5em; } + .small-screen .app-bar-link-area { flex-direction: column; align-items: stretch; } + .small-screen .app-bar-user-area { flex-direction: column; align-items: stretch; margin-left: unset; } + .small-screen .app-bar-avatar { align-self: flex-end; } @@ -92,4 +99,4 @@ color: var(--cru-primary-t-color); cursor: pointer; user-select: none; -} +} \ No newline at end of file diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx index 278c70fd..444d903e 100644 --- a/FrontEnd/src/views/common/AppBar.tsx +++ b/FrontEnd/src/views/common/AppBar.tsx @@ -1,9 +1,9 @@ import * as React from "react"; import classnames from "classnames"; -import { useTranslation } from "react-i18next"; import { Link, NavLink } from "react-router-dom"; import { useMediaQuery } from "react-responsive"; +import { useC } from "./common"; import { useUser } from "@/services/user"; import TimelineLogo from "./TimelineLogo"; @@ -11,8 +11,8 @@ import UserAvatar from "./user/UserAvatar"; import "./AppBar.css"; -const AppBar: React.FC = () => { - const { t } = useTranslation(); +export default function AppBar() { + const c = useC(); const user = useUser(); const hasAdministrationPermission = user && user.hasAdministrationPermission; @@ -26,7 +26,7 @@ const AppBar: React.FC = () => { const createLink = ( link: string, label: React.ReactNode, - className?: string + className?: string, ): React.ReactNode => ( {
- {createLink("/settings", t("nav.settings"))} - {createLink("/about", t("nav.about"))} + {createLink("/settings", c("nav.settings"))} + {createLink("/about", c("nav.about"))} {hasAdministrationPermission && - createLink("/admin", t("nav.administration"))} + createLink("/admin", c("nav.administration"))}
@@ -69,13 +69,11 @@ const AppBar: React.FC = () => { username={user.username} className="cru-avatar small cru-round cursor-pointer ml-auto" />, - "app-bar-avatar" + "app-bar-avatar", ) - : createLink("/login", t("nav.login"))} + : createLink("/login", c("nav.login"))}
); -}; - -export default AppBar; +} diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css index 83e1af28..54c2b87f 100644 --- a/FrontEnd/src/views/common/alert/alert.css +++ b/FrontEnd/src/views/common/alert/alert.css @@ -5,9 +5,9 @@ .cru-alert { border-radius: 5px; - border: var(--cru-theme-color) 1px solid; - color: var(--cru-theme-t-color); - background-color: var(--cru-theme-b1-color); + border: var(--cru-key-color) 1px solid; + color: var(--cru-key-t-color); + background-color: var(--cru-key-b1-color); display: flex; overflow: hidden; @@ -25,9 +25,9 @@ display: flex; align-items: center; justify-content: center; - background-color: var(--cru-theme-t-color); + background-color: var(--cru-key-t-color); } .cru-alert-close-button { - color: var(--cru-theme-color); + color: var(--cru-key-color); } diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 0df22ebe..dacea3e1 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -1,46 +1,52 @@ -.cru-button:not(.outline) { - color: var(--cru-light-color); - cursor: pointer; - padding: 0.2em 0.5em; - border-radius: 0.2em; - border: none; +.cru-button { + font-size: 1rem; + padding: 0.4em 0.8em; transition: all 0.5s; - background-color: var(--cru-theme-color); + border-radius: 0.2em; + border: 1px solid; + cursor: pointer; +} + +.cru-button:not(.outline) { + color: var(--cru-key-on-color); + background-color: var(--cru-key-color); + border-color: var(--cru-key-color); } .cru-button:not(.outline):hover { - background-color: var(--cru-theme-d1-color); + filter: brightness(var(--cru-hover-darkness)); +} + +.cru-button:not(.outline):focus { + filter: brightness(var(--cru-focus-darkness)); } .cru-button:not(.outline):active { - background-color: var(--cru-theme-d2-color); + filter: brightness(var(--cru-press-darkness)); } .cru-button:not(.outline):disabled { - background-color: var(--cru-disabled-color); + background-color: var(--cru-surface-on-color); cursor: auto; } + .cru-button.outline { - color: var(--cru-theme-color); - border: var(--cru-theme-color) 1px solid; - cursor: pointer; - padding: 0.2em 0.5em; - border-radius: 0.2em; - transition: all 0.6s; - background-color: var(--cru-background-color); + color: var(--cru-key-color); + border: var(--cru-key-color) 1px solid; + background-color: var(--cru-surface-color); } .cru-button.outline:hover { - color: var(--cru-theme-d1-color); - border-color: var(--cru-theme-d1-color); - background-color: var(--cru-background-color); + filter: brightness(var(--cru-hover-brightness)); +} + +.cru-button.outline:focus { + filter: brightness(var(--cru-focus-brightness)); } .cru-button.outline:active { - color: var(--cru-theme-l2-color); - border-color: var(--cru-theme-l2-color); - background-color: var(--cru-background-1-color); + filter: brightness(var(--cru-press-brightness)); } .cru-button.outline:disabled { diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css index 01eabca9..f9a7d772 100644 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -1,18 +1,27 @@ .cru-flat-button { - cursor: pointer; - padding: 0.2em 0.5em; + font-size: 1rem; + padding: 0.4em 0.8em; + transition: all 0.5s; border-radius: 0.2em; - border: none; - background-color: transparent; - transition: all 0.6s; - color: var(--cru-theme-color); + background-color: var(--cru-surface-color); + border: 1px none; + color: var(--cru-key-color); + cursor: pointer; } -.cru-flat-button.disabled { - color: var(--cru-disabled-color); - cursor: default; +.cru-flat-button:hover { + filter: brightness(var(--cru-hover-darkness)); } -.cru-flat-button:hover:not(.disabled) { - background-color: #e9ecef; +.cru-flat-button:focus { + filter: brightness(var(--cru-focus-darkness)); } + +.cru-flat-button:active { + filter: brightness(var(--cru-press-darkness)); +} + +.cru-flat-button.disabled { + color: var(--cru-disabled-color); + cursor: auto; +} \ No newline at end of file diff --git a/FrontEnd/src/views/common/button/IconButton.css b/FrontEnd/src/views/common/button/IconButton.css index 45fb103c..25c5f84c 100644 --- a/FrontEnd/src/views/common/button/IconButton.css +++ b/FrontEnd/src/views/common/button/IconButton.css @@ -1,5 +1,5 @@ .cru-icon-button { - color: var(--cru-theme-color); + color: var(--cru-key-color); font-size: 1.4rem; background: none; border: none; diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 9d69997b..bd556a81 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -6,10 +6,18 @@ margin-block: 0; } +*::before { + box-sizing: border-box; +} + +*::after { + box-sizing: border-box; +} + body { - background: var(--cru-bg-color); - color: var(--cru-text-color); - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-family: var(--cru-default-font-family); + background: var(--cru-surface-color); + color: var(--cru-surface-on-color); } diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css index c3fa82c4..93c229f0 100644 --- a/FrontEnd/src/views/common/menu/Menu.css +++ b/FrontEnd/src/views/common/menu/Menu.css @@ -7,12 +7,12 @@ padding: 0.5em 1.5em; cursor: pointer; transition: all 0.5s; - color: var(--cru-theme-color); + color: var(--cru-key-color); } .cru-menu-item:hover { - color: var(--cru-theme-t-color); - background-color: var(--cru-theme-color); + color: var(--cru-key-t-color); + background-color: var(--cru-key-color); } .cru-menu-item-icon { diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css index 9dfa1d9a..7cb9a8f8 100644 --- a/FrontEnd/src/views/common/theme-color.css +++ b/FrontEnd/src/views/common/theme-color.css @@ -1,260 +1,109 @@ /* Generated by theme-generator.ts */ :root { - --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 55%); - --cru-primary-l2-color: hsl(210 100% 60%); - --cru-primary-l3-color: hsl(210 100% 65%); - --cru-primary-d1-color: hsl(210 100% 45%); - --cru-primary-d2-color: hsl(210 100% 40%); - --cru-primary-d3-color: hsl(210 100% 35%); - --cru-primary-f1-color: hsl(210 100% 55%); - --cru-primary-f2-color: hsl(210 100% 60%); - --cru-primary-f3-color: hsl(210 100% 65%); - --cru-primary-b1-color: hsl(210 100% 45%); - --cru-primary-b2-color: hsl(210 100% 40%); - --cru-primary-b3-color: hsl(210 100% 35%); - --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 55%); - --cru-secondary-l2-color: hsl(40 100% 60%); - --cru-secondary-l3-color: hsl(40 100% 65%); - --cru-secondary-d1-color: hsl(40 100% 45%); - --cru-secondary-d2-color: hsl(40 100% 40%); - --cru-secondary-d3-color: hsl(40 100% 35%); - --cru-secondary-f1-color: hsl(40 100% 55%); - --cru-secondary-f2-color: hsl(40 100% 60%); - --cru-secondary-f3-color: hsl(40 100% 65%); - --cru-secondary-b1-color: hsl(40 100% 45%); - --cru-secondary-b2-color: hsl(40 100% 40%); - --cru-secondary-b3-color: hsl(40 100% 35%); - --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 55%); - --cru-tertiary-l2-color: hsl(160 100% 60%); - --cru-tertiary-l3-color: hsl(160 100% 65%); - --cru-tertiary-d1-color: hsl(160 100% 45%); - --cru-tertiary-d2-color: hsl(160 100% 40%); - --cru-tertiary-d3-color: hsl(160 100% 35%); - --cru-tertiary-f1-color: hsl(160 100% 55%); - --cru-tertiary-f2-color: hsl(160 100% 60%); - --cru-tertiary-f3-color: hsl(160 100% 65%); - --cru-tertiary-b1-color: hsl(160 100% 45%); - --cru-tertiary-b2-color: hsl(160 100% 40%); - --cru-tertiary-b3-color: hsl(160 100% 35%); - --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 55%); - --cru-danger-l2-color: hsl(0 100% 60%); - --cru-danger-l3-color: hsl(0 100% 65%); - --cru-danger-d1-color: hsl(0 100% 45%); - --cru-danger-d2-color: hsl(0 100% 40%); - --cru-danger-d3-color: hsl(0 100% 35%); - --cru-danger-f1-color: hsl(0 100% 55%); - --cru-danger-f2-color: hsl(0 100% 60%); - --cru-danger-f3-color: hsl(0 100% 65%); - --cru-danger-b1-color: hsl(0 100% 45%); - --cru-danger-b2-color: hsl(0 100% 40%); - --cru-danger-b3-color: hsl(0 100% 35%); - --cru-success-color: hsl(120 60% 50%); - --cru-success-l1-color: hsl(120 60% 55%); - --cru-success-l2-color: hsl(120 60% 60%); - --cru-success-l3-color: hsl(120 60% 65%); - --cru-success-d1-color: hsl(120 60% 45%); - --cru-success-d2-color: hsl(120 60% 40%); - --cru-success-d3-color: hsl(120 60% 35%); - --cru-success-f1-color: hsl(120 60% 55%); - --cru-success-f2-color: hsl(120 60% 60%); - --cru-success-f3-color: hsl(120 60% 65%); - --cru-success-b1-color: hsl(120 60% 45%); - --cru-success-b2-color: hsl(120 60% 40%); - --cru-success-b3-color: hsl(120 60% 35%); - --cru-text-color: hsl(0 0% 0%); - --cru-text-1-color: hsl(0 0% 5%); - --cru-text-2-color: hsl(0 0% 10%); - --cru-text-3-color: hsl(0 0% 15%); - --cru-bg-color: hsl(0 0% 100%); - --cru-bg-1-color: hsl(0 0% 95%); - --cru-bg-2-color: hsl(0 0% 90%); - --cru-bg-3-color: hsl(0 0% 85%); - --cru-light-color: hsl(0 0% 100%); - --cru-light-1-color: hsl(0 0% 95%); - --cru-light-2-color: hsl(0 0% 90%); - --cru-light-3-color: hsl(0 0% 85%); - --cru-dark-color: hsl(0 0% 0%); - --cru-dark-1-color: hsl(0 0% 5%); - --cru-dark-2-color: hsl(0 0% 10%); - --cru-dark-3-color: hsl(0 0% 15%); - --cru-disabled-color: hsl(0 0% 75%); - --cru-disabled-1-color: hsl(0 0% 70%); - --cru-disabled-2-color: hsl(0 0% 65%); - --cru-disabled-3-color: hsl(0 0% 60%); + --cru-primary-color: hsl(210 100% 40%); + --cru-primary-on-color: hsl(210 100% 100%); + --cru-primary-container-color: hsl(210 100% 90%); + --cru-primary-on-container-color: hsl(210 100% 10%); + --cru-secondary-color: hsl(40 100% 40%); + --cru-secondary-on-color: hsl(40 100% 100%); + --cru-secondary-container-color: hsl(40 100% 90%); + --cru-secondary-on-container-color: hsl(40 100% 10%); + --cru-tertiary-color: hsl(160 100% 40%); + --cru-tertiary-on-color: hsl(160 100% 100%); + --cru-tertiary-container-color: hsl(160 100% 90%); + --cru-tertiary-on-container-color: hsl(160 100% 10%); + --cru-danger-color: hsl(0 100% 40%); + --cru-danger-on-color: hsl(0 100% 100%); + --cru-danger-container-color: hsl(0 100% 90%); + --cru-danger-on-container-color: hsl(0 100% 10%); + --cru-success-color: hsl(120 60% 40%); + --cru-success-on-color: hsl(120 60% 100%); + --cru-success-container-color: hsl(120 60% 90%); + --cru-success-on-container-color: hsl(120 60% 10%); + --cru-surface-dim-color: hsl(0 0% 87%); + --cru-surface-color: hsl(0 0% 98%); + --cru-surface-bright-color: hsl(0 0% 98%); + --cru-surface-container-lowest-color: hsl(0 0% 100%); + --cru-surface-container-low-color: hsl(0 0% 96%); + --cru-surface-container-color: hsl(0 0% 94%); + --cru-surface-container-high-color: hsl(0 0% 92%); + --cru-surface-container-highest-color: hsl(0 0% 90%); + --cru-surface-on-color: hsl(0 0% 10%); + --cru-surface-on-variant-color: hsl(0 0% 30%); + --cru-surface-outline-color: hsl(0 0% 50%); + --cru-surface-outline-variant-color: hsl(0 0% 80%); } @media (prefers-color-scheme: dark) { :root { - --cru-primary-color: hsl(210 100% 50%); - --cru-primary-l1-color: hsl(210 100% 55%); - --cru-primary-l2-color: hsl(210 100% 60%); - --cru-primary-l3-color: hsl(210 100% 65%); - --cru-primary-d1-color: hsl(210 100% 45%); - --cru-primary-d2-color: hsl(210 100% 40%); - --cru-primary-d3-color: hsl(210 100% 35%); - --cru-primary-f1-color: hsl(210 100% 45%); - --cru-primary-f2-color: hsl(210 100% 40%); - --cru-primary-f3-color: hsl(210 100% 35%); - --cru-primary-b1-color: hsl(210 100% 55%); - --cru-primary-b2-color: hsl(210 100% 60%); - --cru-primary-b3-color: hsl(210 100% 65%); - --cru-secondary-color: hsl(40 100% 50%); - --cru-secondary-l1-color: hsl(40 100% 55%); - --cru-secondary-l2-color: hsl(40 100% 60%); - --cru-secondary-l3-color: hsl(40 100% 65%); - --cru-secondary-d1-color: hsl(40 100% 45%); - --cru-secondary-d2-color: hsl(40 100% 40%); - --cru-secondary-d3-color: hsl(40 100% 35%); - --cru-secondary-f1-color: hsl(40 100% 45%); - --cru-secondary-f2-color: hsl(40 100% 40%); - --cru-secondary-f3-color: hsl(40 100% 35%); - --cru-secondary-b1-color: hsl(40 100% 55%); - --cru-secondary-b2-color: hsl(40 100% 60%); - --cru-secondary-b3-color: hsl(40 100% 65%); - --cru-tertiary-color: hsl(160 100% 50%); - --cru-tertiary-l1-color: hsl(160 100% 55%); - --cru-tertiary-l2-color: hsl(160 100% 60%); - --cru-tertiary-l3-color: hsl(160 100% 65%); - --cru-tertiary-d1-color: hsl(160 100% 45%); - --cru-tertiary-d2-color: hsl(160 100% 40%); - --cru-tertiary-d3-color: hsl(160 100% 35%); - --cru-tertiary-f1-color: hsl(160 100% 45%); - --cru-tertiary-f2-color: hsl(160 100% 40%); - --cru-tertiary-f3-color: hsl(160 100% 35%); - --cru-tertiary-b1-color: hsl(160 100% 55%); - --cru-tertiary-b2-color: hsl(160 100% 60%); - --cru-tertiary-b3-color: hsl(160 100% 65%); - --cru-danger-color: hsl(0 100% 50%); - --cru-danger-l1-color: hsl(0 100% 55%); - --cru-danger-l2-color: hsl(0 100% 60%); - --cru-danger-l3-color: hsl(0 100% 65%); - --cru-danger-d1-color: hsl(0 100% 45%); - --cru-danger-d2-color: hsl(0 100% 40%); - --cru-danger-d3-color: hsl(0 100% 35%); - --cru-danger-f1-color: hsl(0 100% 45%); - --cru-danger-f2-color: hsl(0 100% 40%); - --cru-danger-f3-color: hsl(0 100% 35%); - --cru-danger-b1-color: hsl(0 100% 55%); - --cru-danger-b2-color: hsl(0 100% 60%); - --cru-danger-b3-color: hsl(0 100% 65%); - --cru-success-color: hsl(120 60% 50%); - --cru-success-l1-color: hsl(120 60% 55%); - --cru-success-l2-color: hsl(120 60% 60%); - --cru-success-l3-color: hsl(120 60% 65%); - --cru-success-d1-color: hsl(120 60% 45%); - --cru-success-d2-color: hsl(120 60% 40%); - --cru-success-d3-color: hsl(120 60% 35%); - --cru-success-f1-color: hsl(120 60% 45%); - --cru-success-f2-color: hsl(120 60% 40%); - --cru-success-f3-color: hsl(120 60% 35%); - --cru-success-b1-color: hsl(120 60% 55%); - --cru-success-b2-color: hsl(120 60% 60%); - --cru-success-b3-color: hsl(120 60% 65%); - --cru-text-color: hsl(0 0% 100%); - --cru-text-1-color: hsl(0 0% 95%); - --cru-text-2-color: hsl(0 0% 90%); - --cru-text-3-color: hsl(0 0% 85%); - --cru-bg-color: hsl(0 0% 0%); - --cru-bg-1-color: hsl(0 0% 5%); - --cru-bg-2-color: hsl(0 0% 10%); - --cru-bg-3-color: hsl(0 0% 15%); - --cru-light-color: hsl(0 0% 100%); - --cru-light-1-color: hsl(0 0% 95%); - --cru-light-2-color: hsl(0 0% 90%); - --cru-light-3-color: hsl(0 0% 85%); - --cru-dark-color: hsl(0 0% 0%); - --cru-dark-1-color: hsl(0 0% 5%); - --cru-dark-2-color: hsl(0 0% 10%); - --cru-dark-3-color: hsl(0 0% 15%); - --cru-disabled-color: hsl(0 0% 25%); - --cru-disabled-1-color: hsl(0 0% 30%); - --cru-disabled-2-color: hsl(0 0% 35%); - --cru-disabled-3-color: hsl(0 0% 40%); + --cru-primary-color: hsl(210 100% 80%); + --cru-primary-on-color: hsl(210 100% 20%); + --cru-primary-container-color: hsl(210 100% 30%); + --cru-primary-on-container-color: hsl(210 100% 90%); + --cru-secondary-color: hsl(40 100% 80%); + --cru-secondary-on-color: hsl(40 100% 20%); + --cru-secondary-container-color: hsl(40 100% 30%); + --cru-secondary-on-container-color: hsl(40 100% 90%); + --cru-tertiary-color: hsl(160 100% 80%); + --cru-tertiary-on-color: hsl(160 100% 20%); + --cru-tertiary-container-color: hsl(160 100% 30%); + --cru-tertiary-on-container-color: hsl(160 100% 90%); + --cru-danger-color: hsl(0 100% 80%); + --cru-danger-on-color: hsl(0 100% 20%); + --cru-danger-container-color: hsl(0 100% 30%); + --cru-danger-on-container-color: hsl(0 100% 90%); + --cru-success-color: hsl(120 60% 80%); + --cru-success-on-color: hsl(120 60% 20%); + --cru-success-container-color: hsl(120 60% 30%); + --cru-success-on-container-color: hsl(120 60% 90%); + --cru-surface-dim-color: hsl(0 0% 6%); + --cru-surface-color: hsl(0 0% 6%); + --cru-surface-bright-color: hsl(0 0% 24%); + --cru-surface-container-lowest-color: hsl(0 0% 4%); + --cru-surface-container-low-color: hsl(0 0% 10%); + --cru-surface-container-color: hsl(0 0% 12%); + --cru-surface-container-high-color: hsl(0 0% 17%); + --cru-surface-container-highest-color: hsl(0 0% 22%); + --cru-surface-on-color: hsl(0 0% 90%); + --cru-surface-on-variant-color: hsl(0 0% 80%); + --cru-surface-outline-color: hsl(0 0% 60%); + --cru-surface-outline-variant-color: hsl(0 0% 30%); } } .cru-primary { - --cru-theme-color: var(--cru-primary-color); - --cru-theme-l1-color: var(--cru-primary-l1-color); - --cru-theme-l2-color: var(--cru-primary-l2-color); - --cru-theme-l3-color: var(--cru-primary-l3-color); - --cru-theme-d1-color: var(--cru-primary-d1-color); - --cru-theme-d2-color: var(--cru-primary-d2-color); - --cru-theme-d3-color: var(--cru-primary-d3-color); - --cru-theme-f1-color: var(--cru-primary-f1-color); - --cru-theme-f2-color: var(--cru-primary-f2-color); - --cru-theme-f3-color: var(--cru-primary-f3-color); - --cru-theme-b1-color: var(--cru-primary-b1-color); - --cru-theme-b2-color: var(--cru-primary-b2-color); - --cru-theme-b3-color: var(--cru-primary-b3-color); + --cru-key-color: var(--cru-primary-color); + --cru-key-on-color: var(--cru-primary-on-color); + --cru-key-container-color: var(--cru-primary-container-color); + --cru-key-on-container-color: var(--cru-primary-on-container-color); } .cru-secondary { - --cru-theme-color: var(--cru-secondary-color); - --cru-theme-l1-color: var(--cru-secondary-l1-color); - --cru-theme-l2-color: var(--cru-secondary-l2-color); - --cru-theme-l3-color: var(--cru-secondary-l3-color); - --cru-theme-d1-color: var(--cru-secondary-d1-color); - --cru-theme-d2-color: var(--cru-secondary-d2-color); - --cru-theme-d3-color: var(--cru-secondary-d3-color); - --cru-theme-f1-color: var(--cru-secondary-f1-color); - --cru-theme-f2-color: var(--cru-secondary-f2-color); - --cru-theme-f3-color: var(--cru-secondary-f3-color); - --cru-theme-b1-color: var(--cru-secondary-b1-color); - --cru-theme-b2-color: var(--cru-secondary-b2-color); - --cru-theme-b3-color: var(--cru-secondary-b3-color); + --cru-key-color: var(--cru-secondary-color); + --cru-key-on-color: var(--cru-secondary-on-color); + --cru-key-container-color: var(--cru-secondary-container-color); + --cru-key-on-container-color: var(--cru-secondary-on-container-color); } .cru-tertiary { - --cru-theme-color: var(--cru-tertiary-color); - --cru-theme-l1-color: var(--cru-tertiary-l1-color); - --cru-theme-l2-color: var(--cru-tertiary-l2-color); - --cru-theme-l3-color: var(--cru-tertiary-l3-color); - --cru-theme-d1-color: var(--cru-tertiary-d1-color); - --cru-theme-d2-color: var(--cru-tertiary-d2-color); - --cru-theme-d3-color: var(--cru-tertiary-d3-color); - --cru-theme-f1-color: var(--cru-tertiary-f1-color); - --cru-theme-f2-color: var(--cru-tertiary-f2-color); - --cru-theme-f3-color: var(--cru-tertiary-f3-color); - --cru-theme-b1-color: var(--cru-tertiary-b1-color); - --cru-theme-b2-color: var(--cru-tertiary-b2-color); - --cru-theme-b3-color: var(--cru-tertiary-b3-color); + --cru-key-color: var(--cru-tertiary-color); + --cru-key-on-color: var(--cru-tertiary-on-color); + --cru-key-container-color: var(--cru-tertiary-container-color); + --cru-key-on-container-color: var(--cru-tertiary-on-container-color); } .cru-danger { - --cru-theme-color: var(--cru-danger-color); - --cru-theme-l1-color: var(--cru-danger-l1-color); - --cru-theme-l2-color: var(--cru-danger-l2-color); - --cru-theme-l3-color: var(--cru-danger-l3-color); - --cru-theme-d1-color: var(--cru-danger-d1-color); - --cru-theme-d2-color: var(--cru-danger-d2-color); - --cru-theme-d3-color: var(--cru-danger-d3-color); - --cru-theme-f1-color: var(--cru-danger-f1-color); - --cru-theme-f2-color: var(--cru-danger-f2-color); - --cru-theme-f3-color: var(--cru-danger-f3-color); - --cru-theme-b1-color: var(--cru-danger-b1-color); - --cru-theme-b2-color: var(--cru-danger-b2-color); - --cru-theme-b3-color: var(--cru-danger-b3-color); + --cru-key-color: var(--cru-danger-color); + --cru-key-on-color: var(--cru-danger-on-color); + --cru-key-container-color: var(--cru-danger-container-color); + --cru-key-on-container-color: var(--cru-danger-on-container-color); } .cru-success { - --cru-theme-color: var(--cru-success-color); - --cru-theme-l1-color: var(--cru-success-l1-color); - --cru-theme-l2-color: var(--cru-success-l2-color); - --cru-theme-l3-color: var(--cru-success-l3-color); - --cru-theme-d1-color: var(--cru-success-d1-color); - --cru-theme-d2-color: var(--cru-success-d2-color); - --cru-theme-d3-color: var(--cru-success-d3-color); - --cru-theme-f1-color: var(--cru-success-f1-color); - --cru-theme-f2-color: var(--cru-success-f2-color); - --cru-theme-f3-color: var(--cru-success-f3-color); - --cru-theme-b1-color: var(--cru-success-b1-color); - --cru-theme-b2-color: var(--cru-success-b2-color); - --cru-theme-b3-color: var(--cru-success-b3-color); + --cru-key-color: var(--cru-success-color); + --cru-key-on-color: var(--cru-success-on-color); + --cru-key-container-color: var(--cru-success-container-color); + --cru-key-on-container-color: var(--cru-success-on-container-color); } diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css index 28c16627..6cbc06b4 100644 --- a/FrontEnd/src/views/common/theme.css +++ b/FrontEnd/src/views/common/theme.css @@ -1,5 +1,36 @@ @import "./theme-color.css"; :root { + --cru-hover-lightness-change: 4%; + --cru-focus-lightness-change: 10%; + --cru-press-lightness-change: 10%; + --cru-drag-lightness-change: 15%; + + --cru-hover-darkness: calc(100% - var(--cru-hover-lightness-change)); + --cru-focus-darkness: calc(100% - var(--cru-focus-lightness-change)); + --cru-press-darkness: calc(100% - var(--cru-press-lightness-change)); + --cru-drag-darkness: calc(100% - var(--cru-drag-lightness-change)); + --cru-hover-brightness: calc(100% + var(--cru-hover-lightness-change)); + --cru-focus-brightness: calc(100% + var(--cru-focus-lightness-change)); + --cru-press-brightness: calc(100% + var(--cru-press-lightness-change)); + --cru-drag-brightness: calc(100% + var(--cru-drag-lightness-change)); +} + +/* +:hover { + filter: brightness(var(--cru-hover-darkness)); +} + +:focus { + filter: brightness(var(--cru-focus-darkness)); +} + +:active { + filter: brightness(var(--cru-press-darkness)); +} +*/ + +:root { + --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; --cru-card-border-radius: 8px; } \ No newline at end of file diff --git a/FrontEnd/tools/theme-generator.ts b/FrontEnd/tools/theme-generator.ts index 704a1052..c1036d80 100644 --- a/FrontEnd/tools/theme-generator.ts +++ b/FrontEnd/tools/theme-generator.ts @@ -2,11 +2,10 @@ /** * Color variable name scheme: - * has variant: --[prefix]-[name]-[variant]-color: [color]; * no variant: --[prefix]-[name]-color: [color]; - * Variant scheme: - * [variant-prefix][level] - * eg. --cru-primary-color: [color]; --cru-primary-l1-color: [color]; + * with variant: --[prefix]-[name]-[variant]-color: [color]; + * + * Lightness variants come from material design (https://m3.material.io/styles/color/the-color-system/tokens) */ import { stdout } from "process"; @@ -29,12 +28,8 @@ class HslColor implements Color { public l: number, ) {} - lighter(level: number): HslColor { - return new HslColor(this.h, this.s, this.l + level * 5); - } - - darker(level: number): HslColor { - return new HslColor(this.h, this.s, this.l - level * 5); + withLightness(lightness: number): HslColor { + return new HslColor(this.h, this.s, lightness); } toCssString(): string { @@ -53,11 +48,11 @@ class ColorVariable implements CssSegment { constructor( public prefix: string, public name: string, - public variant?: string | null, + public variant: string, ) {} toString(): string { - const variantPart = this.variant == null ? "" : `-${this.variant}`; + const variantPart = this.variant !== "" ? `-${this.variant}` : ""; return `--${this.prefix}-${this.name}${variantPart}-color`; } @@ -82,23 +77,15 @@ class CssVarColor implements Color { class ColorVariableDefinition implements CssSegment { constructor( - public name: ColorVariable, + public variable: ColorVariable, public color: Color, ) {} toCssString(): string { - return `${this.name.toCssString()}: ${this.color.toCssString()};`; + return `${this.variable.toCssString()}: ${this.color.toCssString()};`; } } -type LightnessVariantType = "lighter" | "darker"; - -interface LightnessVariantInfo { - prefix: string; - type: LightnessVariantType; - levels: number; -} - abstract class ColorGroup implements CssSegment { abstract getColorVariables(): ColorVariableDefinition[]; toCssString(): string { @@ -108,6 +95,11 @@ abstract class ColorGroup implements CssSegment { } } +interface LightnessVariantInfo { + variant: string; + lightness: number; +} + class LightnessVariantColorGroup extends ColorGroup { constructor( public prefix: string, @@ -119,27 +111,16 @@ class LightnessVariantColorGroup extends ColorGroup { } getColorVariables(): ColorVariableDefinition[] { - const result: ColorVariableDefinition[] = [ - new ColorVariableDefinition( - new ColorVariable(this.prefix, this.name), - this.baseColor, - ), - ]; + const result: ColorVariableDefinition[] = []; for (const lightnessVariant of this.lightnessVariants) { - for (let i = 1; i <= lightnessVariant.levels; i++) { - const color = - lightnessVariant.type === "lighter" - ? this.baseColor.lighter(i) - : this.baseColor.darker(i); - const colorVariant = `${lightnessVariant.prefix}${i}`; - result.push( - new ColorVariableDefinition( - new ColorVariable(this.prefix, this.name, colorVariant), - color, - ), - ); - } + const color = this.baseColor.withLightness(lightnessVariant.lightness); + result.push( + new ColorVariableDefinition( + new ColorVariable(this.prefix, this.name, lightnessVariant.variant), + color, + ), + ); } return result; @@ -157,12 +138,7 @@ class VarAliasColorGroup extends ColorGroup { } getColorVariables(): ColorVariableDefinition[] { - const result = [ - new ColorVariableDefinition( - new ColorVariable(this.prefix, this.newName), - new CssVarColor(new ColorVariable(this.prefix, this.oldName)), - ), - ]; + const result = []; for (const variant of this.variants) { result.push( new ColorVariableDefinition( @@ -177,51 +153,6 @@ class VarAliasColorGroup extends ColorGroup { } } -class GrayscaleColorGroup extends ColorGroup { - _delegate: LightnessVariantColorGroup; - - constructor( - public prefix: string, - public name: string, - public baseColor: HslColor, - public type: LightnessVariantType, - public levels = 3, - ) { - super(); - - this._delegate = new LightnessVariantColorGroup( - prefix, - name, - this.baseColor, - [{ prefix: "", type: this.type, levels }], - ); - } - - getColorVariables(): ColorVariableDefinition[] { - return this._delegate.getColorVariables(); - } - - static white(prefix: string, name: string, levels = 3): GrayscaleColorGroup { - return new GrayscaleColorGroup( - prefix, - name, - HslColor.white, - "darker", - levels, - ); - } - - static black(prefix: string, name: string, levels = 3): GrayscaleColorGroup { - return new GrayscaleColorGroup( - prefix, - name, - HslColor.black, - "lighter", - levels, - ); - } -} - class CompositeColorGroup extends ColorGroup { constructor(public groups: ColorGroup[]) { super(); @@ -234,49 +165,145 @@ class CompositeColorGroup extends ColorGroup { } } -type ThemeColors = { name: string; color: HslColor }[]; +interface ThemeColors { + keyColors: { name: string; color: HslColor }[]; + neutralColor: HslColor; +} type ColorMode = "light" | "dark"; +interface ColorModeColorVariant { + variant: string; + lightness: { light: number; dark: number }; +} + class Theme { - static getDefaultThemeColorLightnessVariants( - mode: ColorMode, - levels = 3, - ): LightnessVariantInfo[] { - return [ - { - prefix: "l", - type: "lighter", - levels, + static keyColorVariants: ColorModeColorVariant[] = [ + { + variant: "", + lightness: { + light: 40, + dark: 80, }, - { - prefix: "d", - type: "darker", - levels, + }, + { + variant: "on", + lightness: { + light: 100, + dark: 20, }, - { - prefix: "f", - type: mode === "light" ? "lighter" : "darker", - levels, + }, + { + variant: "container", + lightness: { + light: 90, + dark: 30, }, - { - prefix: "b", - type: mode === "light" ? "darker" : "lighter", - levels, + }, + { + variant: "on-container", + lightness: { + light: 10, + dark: 90, }, - ]; - } + }, + ]; - static getThemeColorAllVariants(): string[] { - const lightnessVariantInfos = - Theme.getDefaultThemeColorLightnessVariants("light"); - const result: string[] = []; - for (const { prefix, levels } of lightnessVariantInfos) { - for (let i = 1; i <= levels; i++) { - result.push(`${prefix}${i}`); - } - } - return result; + static surfaceColorVariants: ColorModeColorVariant[] = [ + { + variant: "dim", + lightness: { + light: 87, + dark: 6, + }, + }, + { + variant: "", + lightness: { + light: 98, + dark: 6, + }, + }, + { + variant: "bright", + lightness: { + light: 98, + dark: 24, + }, + }, + { + variant: "container-lowest", + lightness: { + light: 100, + dark: 4, + }, + }, + { + variant: "container-low", + lightness: { + light: 96, + dark: 10, + }, + }, + { + variant: "container", + lightness: { + light: 94, + dark: 12, + }, + }, + { + variant: "container-high", + lightness: { + light: 92, + dark: 17, + }, + }, + { + variant: "container-highest", + lightness: { + light: 90, + dark: 22, + }, + }, + { + variant: "on", + lightness: { + light: 10, + dark: 90, + }, + }, + { + variant: "on-variant", + lightness: { + light: 30, + dark: 80, + }, + }, + { + variant: "outline", + lightness: { + light: 50, + dark: 60, + }, + }, + { + variant: "outline-variant", + lightness: { + light: 80, + dark: 30, + }, + }, + ]; + + static getLightnessVariants( + mode: ColorMode, + colorModeColorVariants: ColorModeColorVariant[], + ): LightnessVariantInfo[] { + return colorModeColorVariants.map((v) => ({ + variant: v.variant, + lightness: v.lightness[mode], + })); } constructor( @@ -285,91 +312,53 @@ class Theme { public levels = 3, ) {} - getThemeColorDefinitions(mode: ColorMode): ColorGroup { + getColorModeColorDefinitions(mode: ColorMode): ColorGroup { const groups: ColorGroup[] = []; - for (const { name, color } of this.themeColors) { + for (const { name, color } of this.themeColors.keyColors) { const colorGroup = new LightnessVariantColorGroup( this.prefix, name, color, - Theme.getDefaultThemeColorLightnessVariants(mode, this.levels), + Theme.getLightnessVariants(mode, Theme.keyColorVariants), ); groups.push(colorGroup); } + groups.push( + new LightnessVariantColorGroup( + this.prefix, + "surface", + this.themeColors.neutralColor, + Theme.getLightnessVariants(mode, Theme.surfaceColorVariants), + ), + ); return new CompositeColorGroup(groups); } getAliasColorDefinitions(name: string): ColorGroup { return new VarAliasColorGroup( this.prefix, - "theme", + "key", name, - Theme.getThemeColorAllVariants(), + Theme.keyColorVariants.map((v) => v.variant), ); } - getGrayscaleDefinitions(mode: ColorMode): ColorGroup { - const textGroup = - mode === "light" - ? GrayscaleColorGroup.black(this.prefix, "text", this.levels) - : GrayscaleColorGroup.white(this.prefix, "text", this.levels); - const bgGroup = - mode === "light" - ? GrayscaleColorGroup.white(this.prefix, "bg", this.levels) - : GrayscaleColorGroup.black(this.prefix, "bg", this.levels); - const lightGroup = GrayscaleColorGroup.white( - this.prefix, - "light", - this.levels, - ); - const darkGroup = GrayscaleColorGroup.black( - this.prefix, - "dark", - this.levels, - ); - const disabledGroup = - mode == "light" - ? new GrayscaleColorGroup( - this.prefix, - "disabled", - new HslColor(0, 0, 75), - "darker", - this.levels, - ) - : new GrayscaleColorGroup( - this.prefix, - "disabled", - new HslColor(0, 0, 25), - "lighter", - this.levels, - ); - return new CompositeColorGroup([ - textGroup, - bgGroup, - lightGroup, - darkGroup, - disabledGroup, - ]); - } - generateCss(print: (text: string, indent: number) => void): void { print(":root {", 0); - print(this.getThemeColorDefinitions("light").toCssString(), 1); - print(this.getGrayscaleDefinitions("light").toCssString(), 1); + print(this.getColorModeColorDefinitions("light").toCssString(), 1); print("}", 0); print("", 0); print("@media (prefers-color-scheme: dark) {", 0); print(":root {", 1); - print(this.getThemeColorDefinitions("dark").toCssString(), 2); - print(this.getGrayscaleDefinitions("dark").toCssString(), 2); + print(this.getColorModeColorDefinitions("dark").toCssString(), 2); print("}", 1); print("}", 0); print("", 0); - for (const { name } of this.themeColors) { + for (const { name } of this.themeColors.keyColors) { print(`.${this.prefix}-${name} {`, 0); print(this.getAliasColorDefinitions(name).toCssString(), 1); print("}", 0); @@ -381,13 +370,16 @@ class Theme { (function main() { const prefix = "cru"; - const themeColors: ThemeColors = [ - { name: "primary", color: new HslColor(210, 100, 50) }, - { name: "secondary", color: new HslColor(40, 100, 50) }, - { name: "tertiary", color: new HslColor(160, 100, 50) }, - { name: "danger", color: new HslColor(0, 100, 50) }, - { name: "success", color: new HslColor(120, 60, 50) }, - ]; + const themeColors: ThemeColors = { + keyColors: [ + { name: "primary", color: new HslColor(210, 100, 50) }, + { name: "secondary", color: new HslColor(40, 100, 50) }, + { name: "tertiary", color: new HslColor(160, 100, 50) }, + { name: "danger", color: new HslColor(0, 100, 50) }, + { name: "success", color: new HslColor(120, 60, 50) }, + ], + neutralColor: new HslColor(0, 0, 50), + }; const theme = new Theme(prefix, themeColors); -- cgit v1.2.3 From 2514e709c3ad0a5b7fc62b67462c8ba668f89d2c Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 15 Jul 2023 23:54:25 +0800 Subject: ... --- FrontEnd/src/views/common/AppBar.css | 53 ++++++++++++----------- FrontEnd/src/views/common/AppBar.tsx | 73 +++++++++++++++++++++++++++++++- FrontEnd/src/views/common/breakpoints.ts | 3 ++ FrontEnd/src/views/common/common.ts | 3 ++ FrontEnd/src/views/common/hooks.ts | 14 ++++++ FrontEnd/src/views/common/index.css | 9 ---- 6 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 FrontEnd/src/views/common/breakpoints.ts create mode 100644 FrontEnd/src/views/common/hooks.ts (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index 272a99ad..67fd9516 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -1,6 +1,4 @@ .app-bar { - display: flex; - align-items: center; height: 56px; position: fixed; z-index: 1030; @@ -8,52 +6,53 @@ left: 0; right: 0; background-color: var(--cru-primary-color); - transition: background-color 1s; } -.app-bar .cru-avatar { - background-color: white; +.app-bar.desktop { + display: flex; } -.app-bar a { - color: var(--cru-primary-on-color); - text-decoration: none; - margin: 0 1em; - transition: color 1s; +.app-bar.desktop .app-bar-brand { + display: flex; + align-items: center; } -.app-bar a:hover { - color: var(--cru-primary-on-color); +.app-bar.desktop .app-bar-brand-icon { + height: 2em; } -.app-bar a.active { - color: var(--cru-primary-on-color); +.app-bar.desktop .app-bar-main-area { + display: flex; + flex-grow: 1; } -.app-bar-brand { +.app-bar.desktop .app-bar-link-area { + display: flex; +} + +.app-bar.desktop a { + background-color: var(--cru-primary-color); + color: var(--cru-primary-on-color); + text-decoration: none; display: flex; align-items: center; + padding: 0 1em; } -.app-bar-brand-icon { - height: 2em; +.app-bar.desktop a:hover { + filter: brightness(var(--cru-hover-brightness)); } -.app-bar-main-area { - display: flex; - flex-grow: 1; +.app-bar.desktop a:focus { + filter: brightness(var(--cru-focus-brightness)); } -.app-bar-link-area { - display: flex; - align-items: center; - flex-shrink: 0; +.app-bar.desktop a:active { + filter: brightness(var(--cru-press-brightness)); } -.app-bar-user-area { +.app-bar.desktop .app-bar-user-area { display: flex; - align-items: center; - flex-shrink: 0; margin-left: auto; } diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx index 444d903e..180d0db6 100644 --- a/FrontEnd/src/views/common/AppBar.tsx +++ b/FrontEnd/src/views/common/AppBar.tsx @@ -3,7 +3,7 @@ import classnames from "classnames"; import { Link, NavLink } from "react-router-dom"; import { useMediaQuery } from "react-responsive"; -import { useC } from "./common"; +import { I18nText, useC, useMobile } from "./common"; import { useUser } from "@/services/user"; import TimelineLogo from "./TimelineLogo"; @@ -11,7 +11,71 @@ import UserAvatar from "./user/UserAvatar"; import "./AppBar.css"; -export default function AppBar() { +function AppBarNavLink({ + link, + className, + label, + children, +}: { + link: string; + className?: string; + label?: I18nText; + children?: React.ReactNode; +}) { + if (label != null && children != null) { + throw new Error("AppBarNavLink: label and children cannot be both set"); + } + + const c = useC(); + + return ( + classnames(className, isActive && "active")} + > + {children != null ? children : c(label)} + + ); +} + +function DesktopAppBar() { + const user = useUser(); + const hasAdministrationPermission = user && user.hasAdministrationPermission; + + return ( + + ); +} + +// TODO: Go make this! +function MobileAppBar() { const c = useC(); const user = useUser(); @@ -77,3 +141,8 @@ export default function AppBar() { ); } + +export default function AppBar() { + const isMobile = useMobile(); + return isMobile ? : ; +} diff --git a/FrontEnd/src/views/common/breakpoints.ts b/FrontEnd/src/views/common/breakpoints.ts new file mode 100644 index 00000000..fb281610 --- /dev/null +++ b/FrontEnd/src/views/common/breakpoints.ts @@ -0,0 +1,3 @@ +export const breakpoints = { + sm: 576, +} as const; diff --git a/FrontEnd/src/views/common/common.ts b/FrontEnd/src/views/common/common.ts index e3a5a2a2..d3db9f93 100644 --- a/FrontEnd/src/views/common/common.ts +++ b/FrontEnd/src/views/common/common.ts @@ -10,3 +10,6 @@ export const themeColors = [ ] as const; export type ThemeColor = (typeof themeColors)[number]; + +export { breakpoints } from "./breakpoints"; +export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/views/common/hooks.ts b/FrontEnd/src/views/common/hooks.ts new file mode 100644 index 00000000..c1fa5774 --- /dev/null +++ b/FrontEnd/src/views/common/hooks.ts @@ -0,0 +1,14 @@ +// TODO: Migrate hooks + +export { + useIsSmallScreen, + useClickOutside, + useScrollToBottom, +} from "@/utilities/hooks"; + +import { useMediaQuery } from "react-responsive"; +import { breakpoints } from "./breakpoints"; + +export function useMobile(): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }); +} diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index bd556a81..f87fdfd2 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -6,21 +6,12 @@ margin-block: 0; } -*::before { - box-sizing: border-box; -} - -*::after { - box-sizing: border-box; -} - body { font-family: var(--cru-default-font-family); background: var(--cru-surface-color); color: var(--cru-surface-on-color); } - .cru-text-center { text-align: center; } -- cgit v1.2.3 From d712d3847506c5c2df62f741d9d2cb91e34f6bfd Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 17 Jul 2023 17:06:32 +0800 Subject: ... --- FrontEnd/src/views/common/AppBar.css | 7 +- FrontEnd/src/views/common/button/Button.css | 21 ++- FrontEnd/src/views/common/button/FlatButton.css | 6 +- FrontEnd/src/views/common/index.css | 4 + FrontEnd/src/views/common/theme-color.css | 64 +++++++ FrontEnd/src/views/common/theme.css | 30 ---- FrontEnd/tools/theme-generator.ts | 230 ++++++++++++++++-------- 7 files changed, 245 insertions(+), 117 deletions(-) (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index 67fd9516..c278aa1e 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -37,18 +37,19 @@ display: flex; align-items: center; padding: 0 1em; + transition: all 0.5s; } .app-bar.desktop a:hover { - filter: brightness(var(--cru-hover-brightness)); + background-color: var(--cru-primary-1-color); } .app-bar.desktop a:focus { - filter: brightness(var(--cru-focus-brightness)); + background-color: var(--cru-primary-1-color); } .app-bar.desktop a:active { - filter: brightness(var(--cru-press-brightness)); + background-color: var(--cru-primary-2-color); } .app-bar.desktop .app-bar-user-area { diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index dacea3e1..55563e00 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -14,15 +14,18 @@ } .cru-button:not(.outline):hover { - filter: brightness(var(--cru-hover-darkness)); + background-color: var(--cru-key-1-color); + border-color: var(--cru-key-1-color); } .cru-button:not(.outline):focus { - filter: brightness(var(--cru-focus-darkness)); + background-color: var(--cru-key-1-color); + border-color: var(--cru-key-1-color); } .cru-button:not(.outline):active { - filter: brightness(var(--cru-press-darkness)); + background-color: var(--cru-key-2-color); + border-color: var(--cru-key-2-color); } .cru-button:not(.outline):disabled { @@ -33,20 +36,22 @@ .cru-button.outline { color: var(--cru-key-color); - border: var(--cru-key-color) 1px solid; - background-color: var(--cru-surface-color); + border-color: var(--cru-key-color); } .cru-button.outline:hover { - filter: brightness(var(--cru-hover-brightness)); + color: var(--cru-key-1-color); + border-color: var(--cru-key-1-color); } .cru-button.outline:focus { - filter: brightness(var(--cru-focus-brightness)); + color: var(--cru-key-1-color); + border-color: var(--cru-key-1-color); } .cru-button.outline:active { - filter: brightness(var(--cru-press-brightness)); + color: var(--cru-key-2-color); + border-color: var(--cru-key-2-color); } .cru-button.outline:disabled { diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css index f9a7d772..921fafe4 100644 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -10,15 +10,15 @@ } .cru-flat-button:hover { - filter: brightness(var(--cru-hover-darkness)); + background-color: var(--cru-surface-1-color); } .cru-flat-button:focus { - filter: brightness(var(--cru-focus-darkness)); + background-color: var(--cru-surface-1-color); } .cru-flat-button:active { - filter: brightness(var(--cru-press-darkness)); + background-color: var(--cru-surface-2-color); } .cru-flat-button.disabled { diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index f87fdfd2..789a0f05 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -12,6 +12,10 @@ body { color: var(--cru-surface-on-color); } +button { + background-color: unset; +} + .cru-text-center { text-align: center; } diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css index 7cb9a8f8..24a7e267 100644 --- a/FrontEnd/src/views/common/theme-color.css +++ b/FrontEnd/src/views/common/theme-color.css @@ -2,27 +2,49 @@ :root { --cru-primary-color: hsl(210 100% 40%); + --cru-primary-1-color: hsl(210 100% 37%); + --cru-primary-2-color: hsl(210 100% 34%); --cru-primary-on-color: hsl(210 100% 100%); --cru-primary-container-color: hsl(210 100% 90%); + --cru-primary-container-1-color: hsl(210 100% 80%); + --cru-primary-container-2-color: hsl(210 100% 70%); --cru-primary-on-container-color: hsl(210 100% 10%); --cru-secondary-color: hsl(40 100% 40%); + --cru-secondary-1-color: hsl(40 100% 37%); + --cru-secondary-2-color: hsl(40 100% 34%); --cru-secondary-on-color: hsl(40 100% 100%); --cru-secondary-container-color: hsl(40 100% 90%); + --cru-secondary-container-1-color: hsl(40 100% 80%); + --cru-secondary-container-2-color: hsl(40 100% 70%); --cru-secondary-on-container-color: hsl(40 100% 10%); --cru-tertiary-color: hsl(160 100% 40%); + --cru-tertiary-1-color: hsl(160 100% 37%); + --cru-tertiary-2-color: hsl(160 100% 34%); --cru-tertiary-on-color: hsl(160 100% 100%); --cru-tertiary-container-color: hsl(160 100% 90%); + --cru-tertiary-container-1-color: hsl(160 100% 80%); + --cru-tertiary-container-2-color: hsl(160 100% 70%); --cru-tertiary-on-container-color: hsl(160 100% 10%); --cru-danger-color: hsl(0 100% 40%); + --cru-danger-1-color: hsl(0 100% 37%); + --cru-danger-2-color: hsl(0 100% 34%); --cru-danger-on-color: hsl(0 100% 100%); --cru-danger-container-color: hsl(0 100% 90%); + --cru-danger-container-1-color: hsl(0 100% 80%); + --cru-danger-container-2-color: hsl(0 100% 70%); --cru-danger-on-container-color: hsl(0 100% 10%); --cru-success-color: hsl(120 60% 40%); + --cru-success-1-color: hsl(120 60% 37%); + --cru-success-2-color: hsl(120 60% 34%); --cru-success-on-color: hsl(120 60% 100%); --cru-success-container-color: hsl(120 60% 90%); + --cru-success-container-1-color: hsl(120 60% 80%); + --cru-success-container-2-color: hsl(120 60% 70%); --cru-success-on-container-color: hsl(120 60% 10%); --cru-surface-dim-color: hsl(0 0% 87%); --cru-surface-color: hsl(0 0% 98%); + --cru-surface-1-color: hsl(0 0% 90%); + --cru-surface-2-color: hsl(0 0% 82%); --cru-surface-bright-color: hsl(0 0% 98%); --cru-surface-container-lowest-color: hsl(0 0% 100%); --cru-surface-container-low-color: hsl(0 0% 96%); @@ -38,27 +60,49 @@ @media (prefers-color-scheme: dark) { :root { --cru-primary-color: hsl(210 100% 80%); + --cru-primary-1-color: hsl(210 100% 75%); + --cru-primary-2-color: hsl(210 100% 68%); --cru-primary-on-color: hsl(210 100% 20%); --cru-primary-container-color: hsl(210 100% 30%); + --cru-primary-container-1-color: hsl(210 100% 25%); + --cru-primary-container-2-color: hsl(210 100% 20%); --cru-primary-on-container-color: hsl(210 100% 90%); --cru-secondary-color: hsl(40 100% 80%); + --cru-secondary-1-color: hsl(40 100% 75%); + --cru-secondary-2-color: hsl(40 100% 68%); --cru-secondary-on-color: hsl(40 100% 20%); --cru-secondary-container-color: hsl(40 100% 30%); + --cru-secondary-container-1-color: hsl(40 100% 25%); + --cru-secondary-container-2-color: hsl(40 100% 20%); --cru-secondary-on-container-color: hsl(40 100% 90%); --cru-tertiary-color: hsl(160 100% 80%); + --cru-tertiary-1-color: hsl(160 100% 75%); + --cru-tertiary-2-color: hsl(160 100% 68%); --cru-tertiary-on-color: hsl(160 100% 20%); --cru-tertiary-container-color: hsl(160 100% 30%); + --cru-tertiary-container-1-color: hsl(160 100% 25%); + --cru-tertiary-container-2-color: hsl(160 100% 20%); --cru-tertiary-on-container-color: hsl(160 100% 90%); --cru-danger-color: hsl(0 100% 80%); + --cru-danger-1-color: hsl(0 100% 75%); + --cru-danger-2-color: hsl(0 100% 68%); --cru-danger-on-color: hsl(0 100% 20%); --cru-danger-container-color: hsl(0 100% 30%); + --cru-danger-container-1-color: hsl(0 100% 25%); + --cru-danger-container-2-color: hsl(0 100% 20%); --cru-danger-on-container-color: hsl(0 100% 90%); --cru-success-color: hsl(120 60% 80%); + --cru-success-1-color: hsl(120 60% 75%); + --cru-success-2-color: hsl(120 60% 68%); --cru-success-on-color: hsl(120 60% 20%); --cru-success-container-color: hsl(120 60% 30%); + --cru-success-container-1-color: hsl(120 60% 25%); + --cru-success-container-2-color: hsl(120 60% 20%); --cru-success-on-container-color: hsl(120 60% 90%); --cru-surface-dim-color: hsl(0 0% 6%); --cru-surface-color: hsl(0 0% 6%); + --cru-surface-1-color: hsl(0 0% 25%); + --cru-surface-2-color: hsl(0 0% 40%); --cru-surface-bright-color: hsl(0 0% 24%); --cru-surface-container-lowest-color: hsl(0 0% 4%); --cru-surface-container-low-color: hsl(0 0% 10%); @@ -74,36 +118,56 @@ .cru-primary { --cru-key-color: var(--cru-primary-color); + --cru-key-1-color: var(--cru-primary-1-color); + --cru-key-2-color: var(--cru-primary-2-color); --cru-key-on-color: var(--cru-primary-on-color); --cru-key-container-color: var(--cru-primary-container-color); + --cru-key-container-1-color: var(--cru-primary-container-1-color); + --cru-key-container-2-color: var(--cru-primary-container-2-color); --cru-key-on-container-color: var(--cru-primary-on-container-color); } .cru-secondary { --cru-key-color: var(--cru-secondary-color); + --cru-key-1-color: var(--cru-secondary-1-color); + --cru-key-2-color: var(--cru-secondary-2-color); --cru-key-on-color: var(--cru-secondary-on-color); --cru-key-container-color: var(--cru-secondary-container-color); + --cru-key-container-1-color: var(--cru-secondary-container-1-color); + --cru-key-container-2-color: var(--cru-secondary-container-2-color); --cru-key-on-container-color: var(--cru-secondary-on-container-color); } .cru-tertiary { --cru-key-color: var(--cru-tertiary-color); + --cru-key-1-color: var(--cru-tertiary-1-color); + --cru-key-2-color: var(--cru-tertiary-2-color); --cru-key-on-color: var(--cru-tertiary-on-color); --cru-key-container-color: var(--cru-tertiary-container-color); + --cru-key-container-1-color: var(--cru-tertiary-container-1-color); + --cru-key-container-2-color: var(--cru-tertiary-container-2-color); --cru-key-on-container-color: var(--cru-tertiary-on-container-color); } .cru-danger { --cru-key-color: var(--cru-danger-color); + --cru-key-1-color: var(--cru-danger-1-color); + --cru-key-2-color: var(--cru-danger-2-color); --cru-key-on-color: var(--cru-danger-on-color); --cru-key-container-color: var(--cru-danger-container-color); + --cru-key-container-1-color: var(--cru-danger-container-1-color); + --cru-key-container-2-color: var(--cru-danger-container-2-color); --cru-key-on-container-color: var(--cru-danger-on-container-color); } .cru-success { --cru-key-color: var(--cru-success-color); + --cru-key-1-color: var(--cru-success-1-color); + --cru-key-2-color: var(--cru-success-2-color); --cru-key-on-color: var(--cru-success-on-color); --cru-key-container-color: var(--cru-success-container-color); + --cru-key-container-1-color: var(--cru-success-container-1-color); + --cru-key-container-2-color: var(--cru-success-container-2-color); --cru-key-on-container-color: var(--cru-success-on-container-color); } diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css index 6cbc06b4..9c9e1645 100644 --- a/FrontEnd/src/views/common/theme.css +++ b/FrontEnd/src/views/common/theme.css @@ -1,35 +1,5 @@ @import "./theme-color.css"; -:root { - --cru-hover-lightness-change: 4%; - --cru-focus-lightness-change: 10%; - --cru-press-lightness-change: 10%; - --cru-drag-lightness-change: 15%; - - --cru-hover-darkness: calc(100% - var(--cru-hover-lightness-change)); - --cru-focus-darkness: calc(100% - var(--cru-focus-lightness-change)); - --cru-press-darkness: calc(100% - var(--cru-press-lightness-change)); - --cru-drag-darkness: calc(100% - var(--cru-drag-lightness-change)); - --cru-hover-brightness: calc(100% + var(--cru-hover-lightness-change)); - --cru-focus-brightness: calc(100% + var(--cru-focus-lightness-change)); - --cru-press-brightness: calc(100% + var(--cru-press-lightness-change)); - --cru-drag-brightness: calc(100% + var(--cru-drag-lightness-change)); -} - -/* -:hover { - filter: brightness(var(--cru-hover-darkness)); -} - -:focus { - filter: brightness(var(--cru-focus-darkness)); -} - -:active { - filter: brightness(var(--cru-press-darkness)); -} -*/ - :root { --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; --cru-card-border-radius: 8px; diff --git a/FrontEnd/tools/theme-generator.ts b/FrontEnd/tools/theme-generator.ts index c1036d80..3583d240 100644 --- a/FrontEnd/tools/theme-generator.ts +++ b/FrontEnd/tools/theme-generator.ts @@ -96,7 +96,7 @@ abstract class ColorGroup implements CssSegment { } interface LightnessVariantInfo { - variant: string; + name: string; lightness: number; } @@ -105,7 +105,7 @@ class LightnessVariantColorGroup extends ColorGroup { public prefix: string, public name: string, public baseColor: HslColor, - public lightnessVariants: LightnessVariantInfo[], + public variants: LightnessVariantInfo[], ) { super(); } @@ -113,11 +113,11 @@ class LightnessVariantColorGroup extends ColorGroup { getColorVariables(): ColorVariableDefinition[] { const result: ColorVariableDefinition[] = []; - for (const lightnessVariant of this.lightnessVariants) { - const color = this.baseColor.withLightness(lightnessVariant.lightness); + for (const variant of this.variants) { + const color = this.baseColor.withLightness(variant.lightness); result.push( new ColorVariableDefinition( - new ColorVariable(this.prefix, this.name, lightnessVariant.variant), + new ColorVariable(this.prefix, this.name, variant.name), color, ), ); @@ -165,181 +165,265 @@ class CompositeColorGroup extends ColorGroup { } } -interface ThemeColors { +interface ThemeColorsInfo { keyColors: { name: string; color: HslColor }[]; neutralColor: HslColor; } type ColorMode = "light" | "dark"; -interface ColorModeColorVariant { - variant: string; - lightness: { light: number; dark: number }; +type ThemeColorVariantLightnessVariantsInfo = + | number + | number[] + | { + base: number; + direction: "darker" | "lighter"; + levels: number; + step: number; + }; + +interface ThemeColorVariantInfo { + name: string; + variants: { + light: ThemeColorVariantLightnessVariantsInfo; + dark: ThemeColorVariantLightnessVariantsInfo; + }; +} + +class ThemeColorVariant { + constructor( + public name: string, + public variants: { + light: ThemeColorVariantLightnessVariantsInfo; + dark: ThemeColorVariantLightnessVariantsInfo; + }, + ) {} + getLightnessVariants(mode: ColorMode): LightnessVariantInfo[] { + const { name, variants } = this; + const list = variants[mode]; + + function variantName(i: number) { + if (name.length === 0) { + return i === 0 ? "" : String(i); + } else { + return i === 0 ? name : `${name}-${i}`; + } + } + + function fromList(list: number[]): LightnessVariantInfo[] { + return list.map((l, i) => ({ + name: variantName(i), + lightness: l, + })); + } + + if (typeof list === "number") { + return fromList([list]); + } else if (Array.isArray(list)) { + return fromList(list); + } else { + const l = [list.base]; + for (let i = 1; i <= list.levels; i++) { + if (list.direction === "darker") { + l.push(list.base - i * list.step); + } else { + l.push(list.base + i * list.step); + } + } + return fromList(l); + } + } + + static from(info: ThemeColorVariantInfo): ThemeColorVariant { + return new ThemeColorVariant(info.name, info.variants); + } +} + +class ThemeColor { + variants: ThemeColorVariant[]; + + constructor( + public prefix: string, + public name: string, + public color: HslColor, + variants: ThemeColorVariantInfo[], + ) { + this.variants = variants.map((v) => ThemeColorVariant.from(v)); + } + + getLightnessVariants(mode: ColorMode): LightnessVariantInfo[] { + return this.variants.flatMap((v) => v.getLightnessVariants(mode)); + } + + getLightnessVariantColorGroup(mode: ColorMode): LightnessVariantColorGroup { + return new LightnessVariantColorGroup( + this.prefix, + this.name, + this.color, + this.getLightnessVariants(mode), + ); + } } class Theme { - static keyColorVariants: ColorModeColorVariant[] = [ + static keyColorVariants: ThemeColorVariantInfo[] = [ { - variant: "", - lightness: { - light: 40, - dark: 80, + name: "", + variants: { + light: [40, 37, 34], + dark: [80, 75, 68], }, }, { - variant: "on", - lightness: { + name: "on", + variants: { light: 100, dark: 20, }, }, { - variant: "container", - lightness: { - light: 90, - dark: 30, + name: "container", + variants: { + light: [90, 80, 70], + dark: [30, 25, 20], }, }, { - variant: "on-container", - lightness: { + name: "on-container", + variants: { light: 10, dark: 90, }, }, ]; - static surfaceColorVariants: ColorModeColorVariant[] = [ + static surfaceColorVariants: ThemeColorVariantInfo[] = [ { - variant: "dim", - lightness: { + name: "dim", + variants: { light: 87, dark: 6, }, }, { - variant: "", - lightness: { - light: 98, - dark: 6, + name: "", + variants: { + light: [98, 90, 82], + dark: [6, 25, 40], }, }, { - variant: "bright", - lightness: { + name: "bright", + variants: { light: 98, dark: 24, }, }, { - variant: "container-lowest", - lightness: { + name: "container-lowest", + variants: { light: 100, dark: 4, }, }, { - variant: "container-low", - lightness: { + name: "container-low", + variants: { light: 96, dark: 10, }, }, { - variant: "container", - lightness: { + name: "container", + variants: { light: 94, dark: 12, }, }, { - variant: "container-high", - lightness: { + name: "container-high", + variants: { light: 92, dark: 17, }, }, { - variant: "container-highest", - lightness: { + name: "container-highest", + variants: { light: 90, dark: 22, }, }, { - variant: "on", - lightness: { + name: "on", + variants: { light: 10, dark: 90, }, }, { - variant: "on-variant", - lightness: { + name: "on-variant", + variants: { light: 30, dark: 80, }, }, { - variant: "outline", - lightness: { + name: "outline", + variants: { light: 50, dark: 60, }, }, { - variant: "outline-variant", - lightness: { + name: "outline-variant", + variants: { light: 80, dark: 30, }, }, ]; - static getLightnessVariants( - mode: ColorMode, - colorModeColorVariants: ColorModeColorVariant[], - ): LightnessVariantInfo[] { - return colorModeColorVariants.map((v) => ({ - variant: v.variant, - lightness: v.lightness[mode], - })); - } - constructor( public prefix: string, - public themeColors: ThemeColors, - public levels = 3, + public themeColors: ThemeColorsInfo, ) {} getColorModeColorDefinitions(mode: ColorMode): ColorGroup { const groups: ColorGroup[] = []; for (const { name, color } of this.themeColors.keyColors) { - const colorGroup = new LightnessVariantColorGroup( + const themeColor = new ThemeColor( this.prefix, name, color, - Theme.getLightnessVariants(mode, Theme.keyColorVariants), + Theme.keyColorVariants, ); - groups.push(colorGroup); + groups.push(themeColor.getLightnessVariantColorGroup(mode)); } - groups.push( - new LightnessVariantColorGroup( - this.prefix, - "surface", - this.themeColors.neutralColor, - Theme.getLightnessVariants(mode, Theme.surfaceColorVariants), - ), + const neutralThemeColor = new ThemeColor( + this.prefix, + "surface", + this.themeColors.neutralColor, + Theme.surfaceColorVariants, ); + groups.push(neutralThemeColor.getLightnessVariantColorGroup(mode)); return new CompositeColorGroup(groups); } getAliasColorDefinitions(name: string): ColorGroup { + const sampleThemeColor = this.themeColors.keyColors[0]; + const themeColor = new ThemeColor( + this.prefix, + sampleThemeColor.name, + sampleThemeColor.color, + Theme.keyColorVariants, + ); + const sampleMode = "light"; return new VarAliasColorGroup( this.prefix, "key", name, - Theme.keyColorVariants.map((v) => v.variant), + themeColor.getLightnessVariants(sampleMode).map((v) => v.name), ); } @@ -370,7 +454,7 @@ class Theme { (function main() { const prefix = "cru"; - const themeColors: ThemeColors = { + const themeColors: ThemeColorsInfo = { keyColors: [ { name: "primary", color: new HslColor(210, 100, 50) }, { name: "secondary", color: new HslColor(40, 100, 50) }, -- cgit v1.2.3 From 0e183074b326cf04a23ae1f1ba8dcc56166df485 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 20 Jul 2023 20:44:15 +0800 Subject: ... --- FrontEnd/src/App.tsx | 20 +- FrontEnd/src/locales/en/translation.json | 18 +- FrontEnd/src/pages/404/index.css | 7 + FrontEnd/src/pages/404/index.tsx | 5 + FrontEnd/src/pages/about/index.css | 8 + FrontEnd/src/pages/about/index.tsx | 86 +++++ FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx | 354 +++++++++++++++++++++ .../src/pages/setting/ChangeNicknameDialog.tsx | 34 ++ .../src/pages/setting/ChangePasswordDialog.tsx | 69 ++++ FrontEnd/src/pages/setting/index.css | 31 ++ FrontEnd/src/pages/setting/index.tsx | 335 +++++++++++++++++++ FrontEnd/src/views/about/author-avatar.png | Bin 12038 -> 0 bytes FrontEnd/src/views/about/github.png | Bin 4268 -> 0 bytes FrontEnd/src/views/about/index.css | 4 - FrontEnd/src/views/about/index.tsx | 143 --------- FrontEnd/src/views/common/Card.css | 7 +- FrontEnd/src/views/common/Card.tsx | 26 +- FrontEnd/src/views/common/index.css | 5 +- FrontEnd/src/views/common/theme.css | 2 +- FrontEnd/src/views/home/TimelineListView.tsx | 97 ------ FrontEnd/src/views/home/WebsiteIntroduction.tsx | 77 ----- FrontEnd/src/views/home/index.css | 42 --- FrontEnd/src/views/home/index.tsx | 78 ----- 23 files changed, 953 insertions(+), 495 deletions(-) create mode 100644 FrontEnd/src/pages/404/index.css create mode 100644 FrontEnd/src/pages/404/index.tsx create mode 100644 FrontEnd/src/pages/about/index.css create mode 100644 FrontEnd/src/pages/about/index.tsx create mode 100644 FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx create mode 100644 FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx create mode 100644 FrontEnd/src/pages/setting/ChangePasswordDialog.tsx create mode 100644 FrontEnd/src/pages/setting/index.css create mode 100644 FrontEnd/src/pages/setting/index.tsx delete mode 100644 FrontEnd/src/views/about/author-avatar.png delete mode 100644 FrontEnd/src/views/about/github.png delete mode 100644 FrontEnd/src/views/about/index.css delete mode 100644 FrontEnd/src/views/about/index.tsx delete mode 100644 FrontEnd/src/views/home/TimelineListView.tsx delete mode 100644 FrontEnd/src/views/home/WebsiteIntroduction.tsx delete mode 100644 FrontEnd/src/views/home/index.css delete mode 100644 FrontEnd/src/views/home/index.tsx (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx index cfdab229..07a8780f 100644 --- a/FrontEnd/src/App.tsx +++ b/FrontEnd/src/App.tsx @@ -2,35 +2,25 @@ import * as React from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import AppBar from "./views/common/AppBar"; +import NotFoundPage from "./pages/404"; import LoadingPage from "./views/common/LoadingPage"; +import About from "./pages/about"; import Center from "./views/center"; -import Home from "./views/home"; import Login from "./views/login"; import Register from "./views/register"; import Settings from "./views/settings"; -import About from "./views/about"; import TimelinePage from "./views/timeline"; import Search from "./views/search"; import Admin from "./views/admin"; import AlertHost from "./views/common/alert/AlertHost"; -import { useUser } from "./services/user"; - -const NoMatch: React.FC = () => { - return
Ah-oh, 404!
; -}; - -function App(): JSX.Element { - const user = useUser(); - +export default function App() { return ( }>
- :
} /> - } /> } /> } /> } /> @@ -40,12 +30,10 @@ function App(): JSX.Element { } /> } /> } /> - } /> + } /> ); } - -export default App; diff --git a/FrontEnd/src/locales/en/translation.json b/FrontEnd/src/locales/en/translation.json index 21c826bd..95c722c9 100644 --- a/FrontEnd/src/locales/en/translation.json +++ b/FrontEnd/src/locales/en/translation.json @@ -224,23 +224,11 @@ } }, "about": { - "author": { - "title": "Site Developer", - "name": "Name: ", - "introduction": "Introduction: ", - "introductionContent": "A programmer coding based on coincidence", - "links": "Links: " - }, - "site": { - "title": "Site Information", - "content": "The name of this site is <1>Timeline, which is a Web App with <3>timeline as its core concept. Its frontend and backend are both developed by <5>me, and open source on GitHub. It is relatively easy to deploy it on your own server, which is also one of my goals. Welcome to comment anything in GitHub repository.", - "repo": "GitHub Repo" - }, "credits": { "title": "Credits", - "content": "Timeline is works standing on shoulders of gaints. Special appreciation for many open source projects listed below or not. Related licenses could be found in GitHub repository.", - "frontend": "Frontend: ", - "backend": "Backend: " + "content": "Timeline stands on shoulders of giants. Special appreciation for many open source projects listed below or not. Related licenses could be found in GitHub repository.", + "frontend": "Frontend", + "backend": "Backend" } }, "admin": { diff --git a/FrontEnd/src/pages/404/index.css b/FrontEnd/src/pages/404/index.css new file mode 100644 index 00000000..cf5efbe7 --- /dev/null +++ b/FrontEnd/src/pages/404/index.css @@ -0,0 +1,7 @@ +.page-404 { + width: 100%; + text-align: center; + padding-top: 1em; + font-size: 2em; + color: var(--cru-danger-color); +} \ No newline at end of file diff --git a/FrontEnd/src/pages/404/index.tsx b/FrontEnd/src/pages/404/index.tsx new file mode 100644 index 00000000..751a450b --- /dev/null +++ b/FrontEnd/src/pages/404/index.tsx @@ -0,0 +1,5 @@ +import "./index.css"; + +export default function NotFoundPage() { + return
Ah-oh, 404!
; +} diff --git a/FrontEnd/src/pages/about/index.css b/FrontEnd/src/pages/about/index.css new file mode 100644 index 00000000..487f4a0a --- /dev/null +++ b/FrontEnd/src/pages/about/index.css @@ -0,0 +1,8 @@ +.about-page { + padding: 1em 2em; + line-height: 1.5; +} + +.about-page a { + color: var(--cru-surface-on-color); +} diff --git a/FrontEnd/src/pages/about/index.tsx b/FrontEnd/src/pages/about/index.tsx new file mode 100644 index 00000000..afd4de34 --- /dev/null +++ b/FrontEnd/src/pages/about/index.tsx @@ -0,0 +1,86 @@ +import "./index.css"; + +import { useC } from "@/common"; + +interface Credit { + name: string; + url: string; +} + +type Credits = Credit[]; + +const frontendCredits: Credits = [ + { + name: "react.js", + url: "https://reactjs.org", + }, + { + name: "typescript", + url: "https://www.typescriptlang.org", + }, + { + name: "bootstrap", + url: "https://getbootstrap.com", + }, + { + name: "parcel.js", + url: "https://parceljs.org", + }, + { + name: "eslint", + url: "https://eslint.org", + }, + { + name: "prettier", + url: "https://prettier.io", + }, +]; + +const backendCredits: Credits = [ + { + name: "ASP.NET Core", + url: "https://dotnet.microsoft.com/learn/aspnet/what-is-aspnet-core", + }, + { name: "sqlite", url: "https://sqlite.org" }, + { + name: "ImageSharp", + url: "https://github.com/SixLabors/ImageSharp", + }, +]; + +export default function AboutPage() { + const c = useC(); + + return ( +
+

{c("about.credits.title")}

+

{c("about.credits.content")}

+

{c("about.credits.frontend")}

+
    + {frontendCredits.map((item, index) => { + return ( +
  • + + {item.name} + +
  • + ); + })} +
  • ...
  • +
+

{c("about.credits.backend")}

+
    + {backendCredits.map((item, index) => { + return ( +
  • + + {item.name} + +
  • + ); + })} +
  • ...
  • +
+
+ ); +} diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx new file mode 100644 index 00000000..44bd2c68 --- /dev/null +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -0,0 +1,354 @@ +import { useState, useEffect } from "react"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { AxiosError } from "axios"; + +import { convertI18nText, I18nText, UiLogicError } from "@/common"; + +import { useUser } from "@/services/user"; + +import { getHttpUserClient } from "@/http/user"; + +import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; +import Button from "../common/button/Button"; +import Dialog from "../common/dialog/Dialog"; + +export interface ChangeAvatarDialogProps { + open: boolean; + close: () => void; +} + +const ChangeAvatarDialog: React.FC = (props) => { + const { t } = useTranslation(); + + const user = useUser(); + + const [file, setFile] = React.useState(null); + const [fileUrl, setFileUrl] = React.useState(null); + const [clip, setClip] = React.useState(null); + const [cropImgElement, setCropImgElement] = + React.useState(null); + const [resultBlob, setResultBlob] = React.useState(null); + const [resultUrl, setResultUrl] = React.useState(null); + + const [state, setState] = React.useState< + | "select" + | "crop" + | "processcrop" + | "preview" + | "uploading" + | "success" + | "error" + >("select"); + + const [message, setMessage] = useState( + "settings.dialogChangeAvatar.prompt.select" + ); + + const trueMessage = convertI18nText(message, t); + + const closeDialog = props.close; + + const close = React.useCallback((): void => { + if (!(state === "uploading")) { + closeDialog(); + } + }, [state, closeDialog]); + + useEffect(() => { + if (file != null) { + const url = URL.createObjectURL(file); + setClip(null); + setFileUrl(url); + setState("crop"); + return () => { + URL.revokeObjectURL(url); + }; + } else { + setFileUrl(null); + setState("select"); + } + }, [file]); + + React.useEffect(() => { + if (resultBlob != null) { + const url = URL.createObjectURL(resultBlob); + setResultUrl(url); + setState("preview"); + return () => { + URL.revokeObjectURL(url); + }; + } else { + setResultUrl(null); + } + }, [resultBlob]); + + const onSelectFile = React.useCallback( + (e: React.ChangeEvent): void => { + const files = e.target.files; + if (files == null || files.length === 0) { + setFile(null); + } else { + setFile(files[0]); + } + }, + [] + ); + + const onCropNext = React.useCallback(() => { + if ( + cropImgElement == null || + clip == null || + clip.width === 0 || + file == null + ) { + throw new UiLogicError(); + } + + setState("processcrop"); + void applyClipToImage(cropImgElement, clip, file.type).then((b) => { + setResultBlob(b); + }); + }, [cropImgElement, clip, file]); + + const onCropPrevious = React.useCallback(() => { + setFile(null); + setState("select"); + }, []); + + const onPreviewPrevious = React.useCallback(() => { + setResultBlob(null); + setState("crop"); + }, []); + + const upload = React.useCallback(() => { + if (resultBlob == null) { + throw new UiLogicError(); + } + + if (user == null) { + throw new UiLogicError(); + } + + setState("uploading"); + getHttpUserClient() + .putAvatar(user.username, resultBlob) + .then( + () => { + setState("success"); + }, + (e: unknown) => { + setState("error"); + setMessage({ type: "custom", value: (e as AxiosError).message }); + } + ); + }, [user, resultBlob]); + + const createPreviewRow = (): React.ReactElement => { + if (resultUrl == null) { + throw new UiLogicError(); + } + return ( +
+
+ {t("settings.dialogChangeAvatar.previewImgAlt") +
+
+ ); + }; + + return ( + +

+ {t("settings.dialogChangeAvatar.title")} +

+
+ {(() => { + if (state === "select") { + return ( + <> +
+
+ {t("settings.dialogChangeAvatar.prompt.select")} +
+
+ +
+
+
+
+
+ + ); + } else if (state === "crop") { + if (fileUrl == null) { + throw new UiLogicError(); + } + return ( + <> +
+
+ +
+
+ {t("settings.dialogChangeAvatar.prompt.crop")} +
+
+
+
+
+ + ); + } else if (state === "processcrop") { + return ( + <> +
+
+ {t("settings.dialogChangeAvatar.prompt.processingCrop")} +
+
+
+
+
+ + ); + } else if (state === "preview") { + return ( + <> +
+ {createPreviewRow()} +
+ {t("settings.dialogChangeAvatar.prompt.preview")} +
+
+
+
+
+ + ); + } else if (state === "uploading") { + return ( + <> +
+ {createPreviewRow()} +
+ {t("settings.dialogChangeAvatar.prompt.uploading")} +
+
+ + ); + } else if (state === "success") { + return ( + <> +
+
+ {t("operationDialog.success")} +
+
+
+
+
+ + ); + } else { + return ( + <> +
+ {createPreviewRow()} +
{trueMessage}
+
+
+
+
+ + ); + } + })()} +
+ ); +}; + +export default ChangeAvatarDialog; diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx new file mode 100644 index 00000000..7ba12de8 --- /dev/null +++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx @@ -0,0 +1,34 @@ +import { getHttpUserClient } from "@/http/user"; +import { useUser } from "@/services/user"; +import * as React from "react"; + +import OperationDialog from "../common/dialog/OperationDialog"; + +export interface ChangeNicknameDialogProps { + open: boolean; + close: () => void; +} + +const ChangeNicknameDialog: React.FC = (props) => { + const user = useUser(); + + if (user == null) return null; + + return ( + { + return getHttpUserClient().patch(user.username, { + nickname: newNickname, + }); + }} + onClose={props.close} + /> + ); +}; + +export default ChangeNicknameDialog; diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx new file mode 100644 index 00000000..a34ca4a7 --- /dev/null +++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx @@ -0,0 +1,69 @@ +import { useState } from "react"; +import * as React from "react"; +import { useNavigate } from "react-router-dom"; + +import { userService } from "@/services/user"; + +import OperationDialog from "../common/dialog/OperationDialog"; + +export interface ChangePasswordDialogProps { + open: boolean; + close: () => void; +} + +const ChangePasswordDialog: React.FC = (props) => { + const navigate = useNavigate(); + + const [redirect, setRedirect] = useState(false); + + return ( + { + const result: Record = {}; + if (oldPassword === "") { + result[0] = "settings.dialogChangePassword.errorEmptyOldPassword"; + } + if (newPassword === "") { + result[1] = "settings.dialogChangePassword.errorEmptyNewPassword"; + } + if (retypedNewPassword !== newPassword) { + result[2] = "settings.dialogChangePassword.errorRetypeNotMatch"; + } + return result; + }} + onProcess={async ([oldPassword, newPassword]) => { + await userService.changePassword(oldPassword, newPassword); + setRedirect(true); + }} + onClose={() => { + props.close(); + if (redirect) { + navigate("/login"); + } + }} + /> + ); +}; + +export default ChangePasswordDialog; diff --git a/FrontEnd/src/pages/setting/index.css b/FrontEnd/src/pages/setting/index.css new file mode 100644 index 00000000..ccf7a97a --- /dev/null +++ b/FrontEnd/src/pages/setting/index.css @@ -0,0 +1,31 @@ +.change-avatar-cropper-row { + max-height: 400px; +} + +.change-avatar-img { + min-width: 50%; + max-width: 100%; + max-height: 400px; +} + +.settings-item { + padding: 0.5em 1em; + transition: background 0.3s; + border-bottom: 1px solid #e9ecef; + align-items: center; +} +.settings-item.first { + border-top: 1px solid #e9ecef; +} +.settings-item.clickable { + cursor: pointer; +} +.settings-item:hover { + background: #dee2e6; +} + +.register-code { + border: 1px solid black; + border-radius: 3px; + padding: 0.2em; +} \ No newline at end of file diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx new file mode 100644 index 00000000..00503dcf --- /dev/null +++ b/FrontEnd/src/pages/setting/index.tsx @@ -0,0 +1,335 @@ +import { useState, ReactNode } from "react"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import classNames from "classnames"; + +import { useC, I18nText } from "@/common"; +import { useUser, userService } from "@/services/user"; +import { getHttpUserClient } from "@/http/user"; +import { TimelineVisibility } from "@/http/timeline"; + +import ConfirmDialog from "../common/dialog/ConfirmDialog"; +import Card from "../common/Card"; +import Spinner from "../common/Spinner"; +import ChangePasswordDialog from "./ChangePasswordDialog"; +import ChangeAvatarDialog from "./ChangeAvatarDialog"; +import ChangeNicknameDialog from "./ChangeNicknameDialog"; + +import "./index.css"; +import { pushAlert } from "@/services/alert"; + +interface SettingSectionProps { + title: I18nText; + children: ReactNode; +} + +function SettingSection({ title, children }: SettingSectionProps) { + const c = useC(); + + return ( + +

{c(title)}

+ {children} +
+ ); +} + +interface SettingItemContainerWithoutChildrenProps { + title: I18nText; + subtext?: I18nText; + first?: boolean; + danger?: boolean; + style?: React.CSSProperties; + className?: string; + onClick?: () => void; +} + +interface SettingItemContainerProps + extends SettingItemContainerWithoutChildrenProps { + children?: React.ReactNode; +} + +function SettingItemContainer({ + title, + subtext, + first, + danger, + children, + style, + className, + onClick, +}: SettingItemContainerProps): JSX.Element { + const { t } = useTranslation(); + + return ( +
+
+
+ {convertI18nText(title, t)} +
+ + {convertI18nText(subtext, t)} + +
+
{children}
+
+ ); +} + +type ButtonSettingItemProps = SettingItemContainerWithoutChildrenProps; + +const ButtonSettingItem: React.FC = ({ ...props }) => { + return ; +}; + +interface SelectSettingItemProps + extends SettingItemContainerWithoutChildrenProps { + options: { + value: string; + label: I18nText; + }[]; + value?: string; + onSelect: (value: string) => void; +} + +const SelectSettingsItem: React.FC = ({ + options, + value, + onSelect, + ...props +}) => { + const { t } = useTranslation(); + + return ( + + {value == null ? ( + + ) : ( + + )} + + ); +}; + +const SettingsPage: React.FC = () => { + const { i18n } = useTranslation(); + const user = useUser(); + const navigate = useNavigate(); + + const [dialog, setDialog] = useState< + | null + | "changepassword" + | "changeavatar" + | "changenickname" + | "logout" + | "renewregistercode" + >(null); + + const [registerCode, setRegisterCode] = useState( + undefined, + ); + + const [bookmarkVisibility, setBookmarkVisibility] = + useState(); + + React.useEffect(() => { + if (user != null) { + void getHttpUserClient() + .getBookmarkVisibility(user.username) + .then(({ visibility }) => { + setBookmarkVisibility(visibility); + }); + } else { + setBookmarkVisibility(undefined); + } + }, [user]); + + React.useEffect(() => { + setRegisterCode(undefined); + }, [user]); + + React.useEffect(() => { + if (user != null && registerCode === undefined) { + void getHttpUserClient() + .getRegisterCode(user.username) + .then((code) => { + setRegisterCode(code.registerCode ?? null); + }); + } + }, [user, registerCode]); + + const language = i18n.language.slice(0, 2); + + return ( + <> +
+ {user ? ( + + setDialog("renewregistercode")} + > + {registerCode === undefined ? ( + + ) : registerCode === null ? ( + Noop + ) : ( + { + void navigator.clipboard + .writeText(registerCode) + .then(() => { + pushAlert({ + type: "success", + message: "settings.myRegisterCodeCopied", + }); + }); + event.stopPropagation(); + }} + > + {registerCode} + + )} + + setDialog("changeavatar")} + first + /> + setDialog("changenickname")} + /> + { + void getHttpUserClient() + .putBookmarkVisibility(user.username, { + visibility: value as TimelineVisibility, + }) + .then(() => { + setBookmarkVisibility(value as TimelineVisibility); + }); + }} + /> + setDialog("changepassword")} + danger + /> + { + setDialog("logout"); + }} + danger + /> + + ) : null} + + { + void i18n.changeLanguage(value); + }} + first + /> + +
+ setDialog(null)} + /> + setDialog(null)} + open={dialog === "logout"} + onConfirm={() => { + void userService.logout().then(() => { + navigate("/"); + }); + }} + /> + setDialog(null)} + open={dialog === "renewregistercode"} + onConfirm={() => { + if (user == null) throw new UiLogicError(); + void getHttpUserClient() + .renewRegisterCode(user.username) + .then(() => { + setRegisterCode(undefined); + }); + }} + /> + setDialog(null)} + /> + setDialog(null)} + /> + + ); +}; + +export default SettingsPage; diff --git a/FrontEnd/src/views/about/author-avatar.png b/FrontEnd/src/views/about/author-avatar.png deleted file mode 100644 index d890d8d0..00000000 Binary files a/FrontEnd/src/views/about/author-avatar.png and /dev/null differ diff --git a/FrontEnd/src/views/about/github.png b/FrontEnd/src/views/about/github.png deleted file mode 100644 index ea6ff545..00000000 Binary files a/FrontEnd/src/views/about/github.png and /dev/null differ diff --git a/FrontEnd/src/views/about/index.css b/FrontEnd/src/views/about/index.css deleted file mode 100644 index 2574f4b7..00000000 --- a/FrontEnd/src/views/about/index.css +++ /dev/null @@ -1,4 +0,0 @@ -.about-link-icon { - width: 1.2em; - height: 1.2em; -} diff --git a/FrontEnd/src/views/about/index.tsx b/FrontEnd/src/views/about/index.tsx deleted file mode 100644 index 093da894..00000000 --- a/FrontEnd/src/views/about/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useTranslation, Trans } from "react-i18next"; - -import authorAvatarUrl from "./author-avatar.png"; -import githubLogoUrl from "./github.png"; - -import Card from "../common/Card"; - -import "./index.css"; - -const frontendCredits: { - name: string; - url: string; -}[] = [ - { - name: "reactjs", - url: "https://reactjs.org", - }, - { - name: "typescript", - url: "https://www.typescriptlang.org", - }, - { - name: "bootstrap", - url: "https://getbootstrap.com", - }, - { - name: "vite", - url: "https://vitejs.dev", - }, - { - name: "eslint", - url: "https://eslint.org", - }, - { - name: "prettier", - url: "https://prettier.io", - }, - { - name: "pepjs", - url: "https://github.com/jquery/PEP", - }, -]; - -const backendCredits: { - name: string; - url: string; -}[] = [ - { - name: "ASP.NET Core", - url: "https://dotnet.microsoft.com/learn/aspnet/what-is-aspnet-core", - }, - { name: "sqlite", url: "https://sqlite.org" }, - { - name: "ImageSharp", - url: "https://github.com/SixLabors/ImageSharp", - }, -]; - -export default function AboutPage() { - const { t } = useTranslation(); - - return ( -
- -

{t("about.author.title")}

-
-
- -

- {t("about.author.name")} - crupest -

-

- {t("about.author.introduction")} - {t("about.author.introductionContent")} -

-
-

- {t("about.author.links")} - - - -

-
-
- -

{t("about.site.title")}

-

- - 01234 - 56 - -

-

- - {t("about.site.repo")} - -

-
- -

{t("about.credits.title")}

-

{t("about.credits.content")}

-

{t("about.credits.frontend")}

-
    - {frontendCredits.map((item, index) => { - return ( -
  • - - {item.name} - -
  • - ); - })} -
  • ...
  • -
-

{t("about.credits.backend")}

-
    - {backendCredits.map((item, index) => { - return ( -
  • - - {item.name} - -
  • - ); - })} -
  • ...
  • -
-
-
- ); -} diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css index fa470130..98cb4cdd 100644 --- a/FrontEnd/src/views/common/Card.css +++ b/FrontEnd/src/views/common/Card.css @@ -1,10 +1,9 @@ .cru-card { - border: 1px solid; - border-color: #e9ecef; border-radius: var(--cru-card-border-radius); + background-color: var(--cru-primary-container-color); transition: all 0.3s; } .cru-card:hover { - border-color: var(--cru-primary-color); -} \ No newline at end of file + border-color: var(--cru-primary-1-color); +} diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx index ebbce77e..50632006 100644 --- a/FrontEnd/src/views/common/Card.tsx +++ b/FrontEnd/src/views/common/Card.tsx @@ -1,19 +1,21 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; import classNames from "classnames"; -import * as React from "react"; import "./Card.css"; -function _Card( - { - className, - children, - ...otherProps - }: React.PropsWithChildren>, - ref: React.ForwardedRef -): React.ReactElement | null { +interface CardProps extends ComponentPropsWithoutRef<"div"> { + containerRef: Ref; +} + +export default function Card({ + className, + children, + containerRef, + ...otherProps +}: CardProps) { return (
@@ -21,7 +23,3 @@ function _Card(
); } - -const Card = React.forwardRef(_Card); - -export default Card; diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 789a0f05..eb82c4bf 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -10,10 +10,7 @@ body { font-family: var(--cru-default-font-family); background: var(--cru-surface-color); color: var(--cru-surface-on-color); -} - -button { - background-color: unset; + line-height: 1.2; } .cru-text-center { diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css index 9c9e1645..3ad45996 100644 --- a/FrontEnd/src/views/common/theme.css +++ b/FrontEnd/src/views/common/theme.css @@ -2,5 +2,5 @@ :root { --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - --cru-card-border-radius: 8px; + --cru-card-border-radius: 4px; } \ No newline at end of file diff --git a/FrontEnd/src/views/home/TimelineListView.tsx b/FrontEnd/src/views/home/TimelineListView.tsx deleted file mode 100644 index fbcdc9b0..00000000 --- a/FrontEnd/src/views/home/TimelineListView.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import * as React from "react"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -import { convertI18nText, I18nText } from "@/common"; - -import { TimelineBookmark } from "@/http/bookmark"; - -import IconButton from "../common/button/IconButton"; - -interface TimelineListItemProps { - timeline: TimelineBookmark; -} - -const TimelineListItem: React.FC = ({ timeline }) => { - return ( -
- - - -
- {timeline.timelineOwner}/{timeline.timelineName} -
- - - -
- ); -}; - -const TimelineListArrow: React.FC = () => { - return ( -
-
- - - -
-
- - - -
-
- ); -}; - -interface TimelineListViewProps { - headerText?: I18nText; - timelines?: TimelineBookmark[]; -} - -const TimelineListView: React.FC = ({ - headerText, - timelines, -}) => { - const { t } = useTranslation(); - - return ( -
-
- - - -

{convertI18nText(headerText, t)}

-
- {timelines != null - ? timelines.map((t) => ( - - )) - : null} - -
- ); -}; - -export default TimelineListView; diff --git a/FrontEnd/src/views/home/WebsiteIntroduction.tsx b/FrontEnd/src/views/home/WebsiteIntroduction.tsx deleted file mode 100644 index e843c325..00000000 --- a/FrontEnd/src/views/home/WebsiteIntroduction.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from "react"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -const WebsiteIntroduction: React.FC<{ - className?: string; - style?: React.CSSProperties; -}> = ({ className, style }) => { - const { i18n } = useTranslation(); - - if (i18n.language.startsWith("zh")) { - return ( -
-

- 欢迎来到时间线!🎉🎉🎉 -

-

- 本网站由无数个独立的时间线构成,每一个时间线都是一个消息列表,类似于一个聊天软件(比如QQ)。 -

-

- 如果你拥有一个账号,登陆 - 后你可以自由地在属于你的时间线中发送内容,支持markdown和上传图片哦!你可以创建一个新的时间线来开启一个新的话题。你也可以设置相关权限,只让一部分人能看到时间线的内容。 -

-

- 如果你没有账号,那么你可以去浏览一下公开的时间线,比如下面这些站长设置的高光时间线。 -

-

- 鉴于这个网站在我的小型服务器上部署,所以没有开放注册。如果你也想把这个服务部署到自己的服务器上,你可以在 - 关于页面找到一些信息。 -

-

- - 这一段介绍是我的对象抱怨多次我的网站他根本看不明白之后加的,希望你能顺利看懂这个网站的逻辑!😅 - -

-
- ); - } else { - return ( -
-

- Welcome to Timeline!🎉🎉🎉 -

-

- This website consists of many individual timelines. Each timeline is a - list of messages just like a chat app. -

-

- If you do have an account, you can login and - post messages, which supports Markdown and images, in your timelines. - You can also create a new timeline to open a new topic. You can set - the permission of a timeline to only allow specified people to see the - content of the timeline. -

-

- If you don't have an account, you can view some public timelines - like highlight timelines below set by website manager. -

-

- Since this website is hosted on my tiny server, so account registry is - not opened. If you want to host this service on your own server, you - can find some useful information on about{" "} - page. -

-

- - This introduction is added after my lover complained a lot of times - about the obscuration of my website. May you understand the logic of - it!😅 - -

-
- ); - } -}; - -export default WebsiteIntroduction; diff --git a/FrontEnd/src/views/home/index.css b/FrontEnd/src/views/home/index.css deleted file mode 100644 index 89d36f0d..00000000 --- a/FrontEnd/src/views/home/index.css +++ /dev/null @@ -1,42 +0,0 @@ -.home-timeline-list-item { - display: flex; - align-items: center; -} - -.home-timeline-list-item-timeline { - transition: background 0.8s; - animation: 0.8s home-timeline-list-item-timeline-enter; -} -.home-timeline-list-item-timeline:hover { - background: #e9ecef; -} - -@keyframes home-timeline-list-item-timeline-enter { - from { - transform: translate(-100%, 0); - opacity: 0; - } -} -.home-timeline-list-item-line { - width: 80px; - flex-shrink: 0; -} - -@keyframes home-timeline-list-loading-head-animation { - from { - transform: translate(0, -30px); - opacity: 1; - } - to { - opacity: 0; - } -} -.home-timeline-list-loading-head { - animation: 1s infinite home-timeline-list-loading-head-animation; -} - -@media (min-width: 576px) { - .home-search { - float: right; - } -} diff --git a/FrontEnd/src/views/home/index.tsx b/FrontEnd/src/views/home/index.tsx deleted file mode 100644 index 3c80fb0c..00000000 --- a/FrontEnd/src/views/home/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react"; -import { useNavigate } from "react-router-dom"; - -import { highlightTimelineUsername } from "@/common"; - -import { Page } from "@/http/common"; -import { getHttpBookmarkClient, TimelineBookmark } from "@/http/bookmark"; - -import SearchInput from "../common/SearchInput"; -import TimelineListView from "./TimelineListView"; -import WebsiteIntroduction from "./WebsiteIntroduction"; - -import "./index.css"; - -const highlightTimelineMessageMap = { - loading: "home.loadingHighlightTimelines", - done: "home.loadedHighlightTimelines", - error: "home.errorHighlightTimelines", -} as const; - -const HomeV2: React.FC = () => { - const navigate = useNavigate(); - - const [navText, setNavText] = React.useState(""); - - const [highlightTimelineState, setHighlightTimelineState] = React.useState< - "loading" | "done" | "error" - >("loading"); - const [highlightTimelines, setHighlightTimelines] = React.useState< - Page | undefined - >(); - - React.useEffect(() => { - if (highlightTimelineState === "loading") { - let subscribe = true; - void getHttpBookmarkClient() - .list(highlightTimelineUsername) - .then( - (data) => { - if (subscribe) { - setHighlightTimelineState("done"); - setHighlightTimelines(data); - } - }, - () => { - if (subscribe) { - setHighlightTimelineState("error"); - setHighlightTimelines(undefined); - } - } - ); - return () => { - subscribe = false; - }; - } - }, [highlightTimelineState]); - - return ( - <> - { - navigate(`search?q=${navText}`); - }} - alwaysOneline - /> - - - - ); -}; - -export default HomeV2; -- cgit v1.2.3 From d7b050ef7f047f841ffcda96fbdc9857574e97b9 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 21 Jul 2023 17:02:05 +0800 Subject: ... --- FrontEnd/src/App.tsx | 8 +- FrontEnd/src/index.css | 1 - FrontEnd/src/pages/about/index.css | 1 - FrontEnd/src/pages/about/index.tsx | 5 +- FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx | 6 +- .../src/pages/setting/ChangeNicknameDialog.tsx | 2 +- .../src/pages/setting/ChangePasswordDialog.tsx | 2 +- FrontEnd/src/pages/setting/index.css | 54 +++- FrontEnd/src/pages/setting/index.tsx | 171 +++++----- FrontEnd/src/views/common/Card.css | 8 +- FrontEnd/src/views/common/Card.tsx | 8 +- FrontEnd/src/views/common/Page.tsx | 15 + FrontEnd/src/views/common/index.css | 8 + FrontEnd/src/views/common/theme.css | 5 +- FrontEnd/src/views/settings/ChangeAvatarDialog.tsx | 354 --------------------- .../src/views/settings/ChangeNicknameDialog.tsx | 34 -- .../src/views/settings/ChangePasswordDialog.tsx | 69 ---- FrontEnd/src/views/settings/index.css | 31 -- FrontEnd/src/views/settings/index.tsx | 338 -------------------- 19 files changed, 182 insertions(+), 938 deletions(-) create mode 100644 FrontEnd/src/views/common/Page.tsx delete mode 100644 FrontEnd/src/views/settings/ChangeAvatarDialog.tsx delete mode 100644 FrontEnd/src/views/settings/ChangeNicknameDialog.tsx delete mode 100644 FrontEnd/src/views/settings/ChangePasswordDialog.tsx delete mode 100644 FrontEnd/src/views/settings/index.css delete mode 100644 FrontEnd/src/views/settings/index.tsx (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx index 07a8780f..92fe0652 100644 --- a/FrontEnd/src/App.tsx +++ b/FrontEnd/src/App.tsx @@ -4,11 +4,11 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import AppBar from "./views/common/AppBar"; import NotFoundPage from "./pages/404"; import LoadingPage from "./views/common/LoadingPage"; -import About from "./pages/about"; +import AboutPage from "./pages/about"; +import SettingPage from "./pages/setting"; import Center from "./views/center"; import Login from "./views/login"; import Register from "./views/register"; -import Settings from "./views/settings"; import TimelinePage from "./views/timeline"; import Search from "./views/search"; import Admin from "./views/admin"; @@ -24,8 +24,8 @@ export default function App() { } /> } /> } /> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index 3478db05..49791c23 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -1,4 +1,3 @@ -@import "npm:bootstrap/dist/css/bootstrap-grid.css"; @import "npm:bootstrap-icons/font/bootstrap-icons.css"; @import "./views/common/index.css"; diff --git a/FrontEnd/src/pages/about/index.css b/FrontEnd/src/pages/about/index.css index 487f4a0a..1ce7a7c8 100644 --- a/FrontEnd/src/pages/about/index.css +++ b/FrontEnd/src/pages/about/index.css @@ -1,5 +1,4 @@ .about-page { - padding: 1em 2em; line-height: 1.5; } diff --git a/FrontEnd/src/pages/about/index.tsx b/FrontEnd/src/pages/about/index.tsx index afd4de34..acec1735 100644 --- a/FrontEnd/src/pages/about/index.tsx +++ b/FrontEnd/src/pages/about/index.tsx @@ -1,6 +1,7 @@ import "./index.css"; import { useC } from "@/common"; +import Page from "@/views/common/Page"; interface Credit { name: string; @@ -52,7 +53,7 @@ export default function AboutPage() { const c = useC(); return ( -
+

{c("about.credits.title")}

{c("about.credits.content")}

{c("about.credits.frontend")}

@@ -81,6 +82,6 @@ export default function AboutPage() { })}
  • ...
  • -
    + ); } diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx index 44bd2c68..b2a4e2a8 100644 --- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -9,9 +9,9 @@ import { useUser } from "@/services/user"; import { getHttpUserClient } from "@/http/user"; -import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; -import Button from "../common/button/Button"; -import Dialog from "../common/dialog/Dialog"; +import ImageCropper, { Clip, applyClipToImage } from "@/views/common/ImageCropper"; +import Button from "@/views/common/button/Button"; +import Dialog from "@/views/common/dialog/Dialog"; export interface ChangeAvatarDialogProps { open: boolean; diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx index 7ba12de8..11c86222 100644 --- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx @@ -2,7 +2,7 @@ import { getHttpUserClient } from "@/http/user"; import { useUser } from "@/services/user"; import * as React from "react"; -import OperationDialog from "../common/dialog/OperationDialog"; +import OperationDialog from "@/views/common/dialog/OperationDialog"; export interface ChangeNicknameDialogProps { open: boolean; diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx index a34ca4a7..a523b454 100644 --- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { userService } from "@/services/user"; -import OperationDialog from "../common/dialog/OperationDialog"; +import OperationDialog from "@/views/common/dialog/OperationDialog"; export interface ChangePasswordDialogProps { open: boolean; diff --git a/FrontEnd/src/pages/setting/index.css b/FrontEnd/src/pages/setting/index.css index ccf7a97a..af5ccf20 100644 --- a/FrontEnd/src/pages/setting/index.css +++ b/FrontEnd/src/pages/setting/index.css @@ -1,3 +1,5 @@ +/* TODO: Make item prettier. */ + .change-avatar-cropper-row { max-height: 400px; } @@ -8,20 +10,56 @@ max-height: 400px; } -.settings-item { +.setting-section { + padding: 1em 0; + margin: 1em 0; +} + +.setting-section-title { + padding: 0 1em; +} + +.setting-section-item-area { + margin-top: 1em; + border-top: 1px solid var(--cru-key-container-color); +} + +.setting-item-container { padding: 0.5em 1em; - transition: background 0.3s; - border-bottom: 1px solid #e9ecef; + transition: background-color 0.3s; + color: var(--cru-surface-on-color); + background-color: var(--cru-surface-color); + border-bottom: 1px solid var(--cru-key-container-color); + display: flex; align-items: center; } -.settings-item.first { - border-top: 1px solid #e9ecef; + +.setting-item-container:hover { + background-color: var(--cru-key-container-1-color); + border-bottom-color: var(--cru-key-container-1-color); +} + +.setting-item-label-sub { + color: var(--cru-secondary-text-color); } -.settings-item.clickable { + +.setting-item-container.setting-type-button { cursor: pointer; } -.settings-item:hover { - background: #dee2e6; + +.setting-item-container.setting-type-button.danger { + color: var(--cru-danger-color); +} + + +@media (max-width: 576) { + .setting-item-container.setting-type-select { + flex-direction: column; + } + + .setting-item-container.setting-type-select .setting-item-value-area { + margin-top: 1em; + } } .register-code { diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 4e28585e..5d6dcbc0 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -11,11 +11,11 @@ import classNames from "classnames"; import { useC, Text } from "@/common"; import { useUser, userService } from "@/services/user"; import { getHttpUserClient } from "@/http/user"; -import { TimelineVisibility } from "@/http/timeline"; import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; import Card from "@/views/common/Card"; import Spinner from "@/views/common/Spinner"; +import Page from "@/views/common/Page"; import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; @@ -24,9 +24,9 @@ import "./index.css"; import { pushAlert } from "@/services/alert"; interface SettingSectionProps - extends Omit, "title"> { + extends Omit, "title"> { title: Text; - children: ReactNode; + children?: ReactNode; } function SettingSection({ @@ -40,7 +40,7 @@ function SettingSection({ return (

    {c(title)}

    - {children} +
    {children}
    ); } @@ -115,6 +115,7 @@ function SelectSettingsItem({ ) : ( -
    - -
    -
    -
    - - ); - } else if (state === "crop") { - if (fileUrl == null) { - throw new UiLogicError(); - } - return ( - <> -
    -
    - -
    -
    - {t("settings.dialogChangeAvatar.prompt.crop")} -
    -
    -
    -
    -
    - - ); - } else if (state === "processcrop") { - return ( - <> -
    -
    - {t("settings.dialogChangeAvatar.prompt.processingCrop")} -
    -
    -
    -
    -
    - - ); - } else if (state === "preview") { - return ( - <> -
    - {createPreviewRow()} -
    - {t("settings.dialogChangeAvatar.prompt.preview")} -
    -
    -
    -
    -
    - - ); - } else if (state === "uploading") { - return ( - <> -
    - {createPreviewRow()} -
    - {t("settings.dialogChangeAvatar.prompt.uploading")} -
    -
    - - ); - } else if (state === "success") { - return ( - <> -
    -
    - {t("operationDialog.success")} -
    -
    -
    -
    -
    - - ); - } else { - return ( - <> -
    - {createPreviewRow()} -
    {trueMessage}
    -
    -
    -
    -
    - - ); - } - })()} - - ); -}; - -export default ChangeAvatarDialog; diff --git a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx deleted file mode 100644 index 7ba12de8..00000000 --- a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { getHttpUserClient } from "@/http/user"; -import { useUser } from "@/services/user"; -import * as React from "react"; - -import OperationDialog from "../common/dialog/OperationDialog"; - -export interface ChangeNicknameDialogProps { - open: boolean; - close: () => void; -} - -const ChangeNicknameDialog: React.FC = (props) => { - const user = useUser(); - - if (user == null) return null; - - return ( - { - return getHttpUserClient().patch(user.username, { - nickname: newNickname, - }); - }} - onClose={props.close} - /> - ); -}; - -export default ChangeNicknameDialog; diff --git a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx b/FrontEnd/src/views/settings/ChangePasswordDialog.tsx deleted file mode 100644 index a34ca4a7..00000000 --- a/FrontEnd/src/views/settings/ChangePasswordDialog.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useState } from "react"; -import * as React from "react"; -import { useNavigate } from "react-router-dom"; - -import { userService } from "@/services/user"; - -import OperationDialog from "../common/dialog/OperationDialog"; - -export interface ChangePasswordDialogProps { - open: boolean; - close: () => void; -} - -const ChangePasswordDialog: React.FC = (props) => { - const navigate = useNavigate(); - - const [redirect, setRedirect] = useState(false); - - return ( - { - const result: Record = {}; - if (oldPassword === "") { - result[0] = "settings.dialogChangePassword.errorEmptyOldPassword"; - } - if (newPassword === "") { - result[1] = "settings.dialogChangePassword.errorEmptyNewPassword"; - } - if (retypedNewPassword !== newPassword) { - result[2] = "settings.dialogChangePassword.errorRetypeNotMatch"; - } - return result; - }} - onProcess={async ([oldPassword, newPassword]) => { - await userService.changePassword(oldPassword, newPassword); - setRedirect(true); - }} - onClose={() => { - props.close(); - if (redirect) { - navigate("/login"); - } - }} - /> - ); -}; - -export default ChangePasswordDialog; diff --git a/FrontEnd/src/views/settings/index.css b/FrontEnd/src/views/settings/index.css deleted file mode 100644 index ccf7a97a..00000000 --- a/FrontEnd/src/views/settings/index.css +++ /dev/null @@ -1,31 +0,0 @@ -.change-avatar-cropper-row { - max-height: 400px; -} - -.change-avatar-img { - min-width: 50%; - max-width: 100%; - max-height: 400px; -} - -.settings-item { - padding: 0.5em 1em; - transition: background 0.3s; - border-bottom: 1px solid #e9ecef; - align-items: center; -} -.settings-item.first { - border-top: 1px solid #e9ecef; -} -.settings-item.clickable { - cursor: pointer; -} -.settings-item:hover { - background: #dee2e6; -} - -.register-code { - border: 1px solid black; - border-radius: 3px; - padding: 0.2em; -} \ No newline at end of file diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx deleted file mode 100644 index 6647826f..00000000 --- a/FrontEnd/src/views/settings/index.tsx +++ /dev/null @@ -1,338 +0,0 @@ -import { useState } from "react"; -import * as React from "react"; -import { useNavigate } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import classNames from "classnames"; - -import { convertI18nText, I18nText, UiLogicError } from "@/common"; -import { useUser, userService } from "@/services/user"; -import { getHttpUserClient } from "@/http/user"; -import { TimelineVisibility } from "@/http/timeline"; - -import ConfirmDialog from "../common/dialog/ConfirmDialog"; -import Card from "../common/Card"; -import Spinner from "../common/Spinner"; -import ChangePasswordDialog from "./ChangePasswordDialog"; -import ChangeAvatarDialog from "./ChangeAvatarDialog"; -import ChangeNicknameDialog from "./ChangeNicknameDialog"; - -import "./index.css"; -import { pushAlert } from "@/services/alert"; - -interface SettingSectionProps { - title: I18nText; - children: React.ReactNode; -} - -const SettingSection: React.FC = ({ title, children }) => { - const { t } = useTranslation(); - - return ( - -

    - {convertI18nText(title, t)} -

    - {children} -
    - ); -}; - -interface SettingItemContainerWithoutChildrenProps { - title: I18nText; - subtext?: I18nText; - first?: boolean; - danger?: boolean; - style?: React.CSSProperties; - className?: string; - onClick?: () => void; -} - -interface SettingItemContainerProps - extends SettingItemContainerWithoutChildrenProps { - children?: React.ReactNode; -} - -function SettingItemContainer({ - title, - subtext, - first, - danger, - children, - style, - className, - onClick, -}: SettingItemContainerProps): JSX.Element { - const { t } = useTranslation(); - - return ( -
    -
    -
    - {convertI18nText(title, t)} -
    - - {convertI18nText(subtext, t)} - -
    -
    {children}
    -
    - ); -} - -type ButtonSettingItemProps = SettingItemContainerWithoutChildrenProps; - -const ButtonSettingItem: React.FC = ({ ...props }) => { - return ; -}; - -interface SelectSettingItemProps - extends SettingItemContainerWithoutChildrenProps { - options: { - value: string; - label: I18nText; - }[]; - value?: string; - onSelect: (value: string) => void; -} - -const SelectSettingsItem: React.FC = ({ - options, - value, - onSelect, - ...props -}) => { - const { t } = useTranslation(); - - return ( - - {value == null ? ( - - ) : ( - - )} - - ); -}; - -const SettingsPage: React.FC = () => { - const { i18n } = useTranslation(); - const user = useUser(); - const navigate = useNavigate(); - - const [dialog, setDialog] = useState< - | null - | "changepassword" - | "changeavatar" - | "changenickname" - | "logout" - | "renewregistercode" - >(null); - - const [registerCode, setRegisterCode] = useState( - undefined, - ); - - const [bookmarkVisibility, setBookmarkVisibility] = - useState(); - - React.useEffect(() => { - if (user != null) { - void getHttpUserClient() - .getBookmarkVisibility(user.username) - .then(({ visibility }) => { - setBookmarkVisibility(visibility); - }); - } else { - setBookmarkVisibility(undefined); - } - }, [user]); - - React.useEffect(() => { - setRegisterCode(undefined); - }, [user]); - - React.useEffect(() => { - if (user != null && registerCode === undefined) { - void getHttpUserClient() - .getRegisterCode(user.username) - .then((code) => { - setRegisterCode(code.registerCode ?? null); - }); - } - }, [user, registerCode]); - - const language = i18n.language.slice(0, 2); - - return ( - <> -
    - {user ? ( - - setDialog("renewregistercode")} - > - {registerCode === undefined ? ( - - ) : registerCode === null ? ( - Noop - ) : ( - { - void navigator.clipboard - .writeText(registerCode) - .then(() => { - pushAlert({ - type: "success", - message: "settings.myRegisterCodeCopied", - }); - }); - event.stopPropagation(); - }} - > - {registerCode} - - )} - - setDialog("changeavatar")} - first - /> - setDialog("changenickname")} - /> - { - void getHttpUserClient() - .putBookmarkVisibility(user.username, { - visibility: value as TimelineVisibility, - }) - .then(() => { - setBookmarkVisibility(value as TimelineVisibility); - }); - }} - /> - setDialog("changepassword")} - danger - /> - { - setDialog("logout"); - }} - danger - /> - - ) : null} - - { - void i18n.changeLanguage(value); - }} - first - /> - -
    - setDialog(null)} - /> - setDialog(null)} - open={dialog === "logout"} - onConfirm={() => { - void userService.logout().then(() => { - navigate("/"); - }); - }} - /> - setDialog(null)} - open={dialog === "renewregistercode"} - onConfirm={() => { - if (user == null) throw new UiLogicError(); - void getHttpUserClient() - .renewRegisterCode(user.username) - .then(() => { - setRegisterCode(undefined); - }); - }} - /> - setDialog(null)} - /> - setDialog(null)} - /> - - ); -}; - -export default SettingsPage; -- cgit v1.2.3 From 93bc2b9688ebc0dde2ae8d5cf3e3f2628d636ff0 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Aug 2023 01:16:37 +0800 Subject: ... --- FrontEnd/src/pages/setting/index.css | 26 ++++++----- FrontEnd/src/pages/setting/index.tsx | 6 +-- FrontEnd/src/pages/timeline/Timeline.css | 2 - FrontEnd/src/pages/timeline/Timeline.tsx | 4 +- FrontEnd/src/pages/timeline/TimelineCard.tsx | 2 +- FrontEnd/src/pages/timeline/TimelinePostCard.css | 3 +- FrontEnd/src/pages/timeline/TimelinePostCard.tsx | 2 +- FrontEnd/src/pages/timeline/index.tsx | 11 +---- FrontEnd/src/views/common/AppBar.css | 8 ++-- FrontEnd/src/views/common/Card.css | 3 +- FrontEnd/src/views/common/Card.tsx | 2 +- FrontEnd/src/views/common/button/Button.css | 3 +- FrontEnd/src/views/common/button/Button.tsx | 2 +- FrontEnd/src/views/common/button/IconButton.tsx | 2 +- FrontEnd/src/views/common/button/LoadingButton.tsx | 2 +- FrontEnd/src/views/common/index.css | 4 +- FrontEnd/src/views/common/theme.css | 50 ++++++++++++++++++++-- 17 files changed, 87 insertions(+), 45 deletions(-) (limited to 'FrontEnd/src/views/common/index.css') diff --git a/FrontEnd/src/pages/setting/index.css b/FrontEnd/src/pages/setting/index.css index d9cba24f..c1950f9c 100644 --- a/FrontEnd/src/pages/setting/index.css +++ b/FrontEnd/src/pages/setting/index.css @@ -17,29 +17,39 @@ .setting-section-title { padding: 0 1em; + color: white; } .setting-section-item-area { margin-top: 1em; - border-top: 1px solid var(--cru-key-container-color); + border-top: 1px solid var(--cru-secondary-color); } .setting-item-container { padding: 0.5em 1em; transition: background-color 0.3s; - color: var(--cru-surface-on-color); - background-color: var(--cru-surface-color); - border-bottom: 1px solid var(--cru-key-container-color); + background-color: var(--cru-background-color); + border-bottom: 1px solid var(--cru-secondary-color); display: flex; align-items: center; } .setting-item-container:hover { - background-color: var(--cru-key-container-1-color); + color: white; + background-color: var(--cru-secondary-color); +} + +.setting-item-container.danger { + color: var(--cru-danger-color); +} + +.setting-item-container.danger:hover { + color: white; + background-color: var(--cru-danger-color); } .setting-item-label-sub { - color: var(--cru-secondary-text-color); + color: var(--cru-text-secondary-color); } .setting-item-value-area { @@ -50,10 +60,6 @@ cursor: pointer; } -.setting-item-container.setting-type-button.danger { - color: var(--cru-danger-color); -} - .register-code { background: var(--cru-surface-container-highest-color); border: 1px solid var(--cru-surface-container-highest-color); diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 8e8ae488..8673d55a 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -172,7 +172,7 @@ function RegisterCodeSettingItem() { onClick={(event) => { void navigator.clipboard.writeText(registerCode).then(() => { pushAlert({ - type: "success", + type: "create", message: "settings.myRegisterCodeCopied", }); }); @@ -249,7 +249,7 @@ export default function SettingPage() { return ( {user ? ( - + ) : null} - + diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css index 30ad75c9..419ab9b1 100644 --- a/FrontEnd/src/pages/timeline/Timeline.css +++ b/FrontEnd/src/pages/timeline/Timeline.css @@ -1,5 +1,4 @@ .timeline { - --timeline-background-color: #f3f3f3; --timeline-shadow-color: #00000080; --timeline-post-line-color: #eadd2c; --timeline-post-line-shadow: 2px 1px 10px -1px var(--timeline-shadow-color); @@ -13,7 +12,6 @@ z-index: 0; position: relative; width: 100%; - background-color: var(--timeline-background-color); } @keyframes timeline-line-node { diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index d05336d7..3c9d73bf 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -156,7 +156,7 @@ export function Timeline(props: TimelineProps) { return
    Error.
    ; } return ( - <> +
    {timeline && (
    )} - + ); } diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx index 04b34ec1..b287c620 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx @@ -123,7 +123,7 @@ export default function TimelineCard(props: TimelinePageCardProps) { return ( + {children} ); diff --git a/FrontEnd/src/pages/timeline/index.tsx b/FrontEnd/src/pages/timeline/index.tsx index 1dffdcc1..51cc37f0 100644 --- a/FrontEnd/src/pages/timeline/index.tsx +++ b/FrontEnd/src/pages/timeline/index.tsx @@ -1,11 +1,10 @@ -import * as React from "react"; import { useParams } from "react-router-dom"; import { UiLogicError } from "@/common"; import Timeline from "./Timeline"; -const TimelinePage: React.FC = () => { +export default function TimelinePage() { const { owner, timeline: timelineNameParam } = useParams(); if (owner == null || owner == "") @@ -13,11 +12,5 @@ const TimelinePage: React.FC = () => { const timeline = timelineNameParam || "self"; - return ( -
    - -
    - ); + return ; }; - -export default TimelinePage; diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css index bd8d0986..2f2d4a1a 100644 --- a/FrontEnd/src/views/common/AppBar.css +++ b/FrontEnd/src/views/common/AppBar.css @@ -28,7 +28,7 @@ .app-bar a { background-color: var(--cru-primary-color); - color: var(--cru-primary-on-color); + color: var(--cru-push-button-text-color); text-decoration: none; display: flex; align-items: center; @@ -37,15 +37,15 @@ } .app-bar a:hover { - background-color: var(--cru-primary-1-color); + background-color: var(--cru-button-primary-hover-color); } .app-bar a:focus { - background-color: var(--cru-primary-1-color); + background-color: var(--cru-button-primary-focus-color); } .app-bar a:active { - background-color: var(--cru-primary-2-color); + background-color: var(--cru-button-primary-active-color); } .app-bar .app-bar-avatar img { diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css index c677abd7..f391abc8 100644 --- a/FrontEnd/src/views/common/Card.css +++ b/FrontEnd/src/views/common/Card.css @@ -1,5 +1,6 @@ .cru-card { border-radius: var(--cru-card-border-radius); - background-color: var(--cru-key-container-color); + border: 1px solid var(--cru-card-border-color); + background-color: var(--cru-card-background-color); transition: all 0.3s; } \ No newline at end of file diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx index 381e58e7..e1af13a9 100644 --- a/FrontEnd/src/views/common/Card.tsx +++ b/FrontEnd/src/views/common/Card.tsx @@ -19,7 +19,7 @@ export default function Card({ return (
    {children} diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css index 4e755dba..b5a4e2f5 100644 --- a/FrontEnd/src/views/common/button/Button.css +++ b/FrontEnd/src/views/common/button/Button.css @@ -1,7 +1,7 @@ .cru-button { font-size: 1rem; padding: 0.4em 0.8em; - transition: all 0.5s; + transition: all 0.3s; border-radius: 0.2em; border: 1px solid; cursor: pointer; @@ -39,6 +39,7 @@ .cru-button.outline { color: var(--cru-button-normal-color); border-color: var(--cru-button-normal-color); + background-color: transparent; } .cru-button.outline:hover { diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx index 02d00ee5..573055cf 100644 --- a/FrontEnd/src/views/common/button/Button.tsx +++ b/FrontEnd/src/views/common/button/Button.tsx @@ -33,8 +33,8 @@ export default function Button(props: ButtonProps) { + ); +} diff --git a/FrontEnd/src/components/button/ButtonRow.css b/FrontEnd/src/components/button/ButtonRow.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/components/button/ButtonRow.tsx b/FrontEnd/src/components/button/ButtonRow.tsx new file mode 100644 index 00000000..eea60cc4 --- /dev/null +++ b/FrontEnd/src/components/button/ButtonRow.tsx @@ -0,0 +1,62 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; + +import "./ButtonRow.css"; + +type ButtonRowButton = ( + | { + type: "normal"; + props: ComponentPropsWithoutRef; + } + | { + type: "flat"; + props: ComponentPropsWithoutRef; + } + | { + type: "icon"; + props: ComponentPropsWithoutRef; + } + | { type: "loading"; props: ComponentPropsWithoutRef } +) & { key: string | number }; + +interface ButtonRowProps { + className?: string; + containerRef?: Ref; + buttons: ButtonRowButton[]; + buttonsClassName?: string; +} + +export default function ButtonRow({ + className, + containerRef, + buttons, + buttonsClassName, +}: ButtonRowProps) { + return ( +
    + {buttons.map((button) => { + const { type, key, props } = button; + const newClassName = classNames(props.className, buttonsClassName); + switch (type) { + case "normal": + return
    + ); +} diff --git a/FrontEnd/src/components/button/ButtonRowV2.tsx b/FrontEnd/src/components/button/ButtonRowV2.tsx new file mode 100644 index 00000000..3467ad52 --- /dev/null +++ b/FrontEnd/src/components/button/ButtonRowV2.tsx @@ -0,0 +1,143 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; + +import "./ButtonRow.css"; +import { Text, ThemeColor } from "../common"; + +interface ButtonRowV2ButtonBase { + key: string | number; + action?: "primary" | "secondary"; + color?: ThemeColor; + disabled?: boolean; + onClick?: () => void; +} + +interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase { + type?: undefined | null; + text: Text; + outline?: boolean; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase { + type: "normal"; + text: Text; + outline?: boolean; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase { + type: "flat"; + text: Text; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase { + type: "icon"; + icon: string; + props?: ComponentPropsWithoutRef; +} + +interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase { + type: "loading"; + text: Text; + loading?: boolean; + props?: ComponentPropsWithoutRef; +} + +type ButtonRowV2Button = + | ButtonRowV2ButtonWithNoType + | ButtonRowV2NormalButton + | ButtonRowV2FlatButton + | ButtonRowV2IconButton + | ButtonRowV2LoadingButton; + +interface ButtonRowV2Props { + className?: string; + containerRef?: Ref; + buttons: ButtonRowV2Button[]; + buttonsClassName?: string; +} + +export default function ButtonRowV2({ + className, + containerRef, + buttons, + buttonsClassName, +}: ButtonRowV2Props) { + return ( +
    + {buttons.map((button) => { + const { key, action, color, disabled, onClick } = button; + + const realAction = action ?? "primary"; + const realColor = + color ?? (realAction === "primary" ? "primary" : "secondary"); + + const commonProps = { key, color: realColor, disabled, onClick }; + const newClassName = classNames( + button.props?.className, + buttonsClassName, + ); + + switch (button.type) { + case null: + case undefined: + case "normal": { + const { text, outline, props } = button; + return ( +
    + ); +} diff --git a/FrontEnd/src/components/button/FlatButton.css b/FrontEnd/src/components/button/FlatButton.css new file mode 100644 index 00000000..2050946c --- /dev/null +++ b/FrontEnd/src/components/button/FlatButton.css @@ -0,0 +1,27 @@ +.cru-flat-button { + font-size: 1rem; + padding: 0.4em 0.8em; + transition: all 0.5s; + border-radius: 0.2em; + background-color: var(--cru-clickable-grayscale-normal-color); + border: 1px none; + color: var(--cru-clickable-normal-color); + cursor: pointer; +} + +.cru-flat-button:hover { + background-color: var(--cru-clickable-grayscale-hover-color); +} + +.cru-flat-button:focus { + background-color: var(--cru-clickable-grayscale-focus-color); +} + +.cru-flat-button:active { + background-color: var(--cru-clickable-grayscale-active-color); +} + +.cru-flat-button:disabled { + color: var(--cru-clickable-disabled-color); + cursor: auto; +} \ No newline at end of file diff --git a/FrontEnd/src/components/button/FlatButton.tsx b/FrontEnd/src/components/button/FlatButton.tsx new file mode 100644 index 00000000..9f074dd6 --- /dev/null +++ b/FrontEnd/src/components/button/FlatButton.tsx @@ -0,0 +1,36 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import { Text, useC, ThemeColor } from "../common"; + +import "./FlatButton.css"; + +interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> { + color?: ThemeColor; + text?: Text; + buttonRef?: Ref | null; +} + +export default function FlatButton(props: FlatButtonProps) { + const { color, text, className, children, buttonRef, ...otherProps } = props; + + if (text != null && children != null) { + console.warn("You can't set both text and children props."); + } + + const c = useC(); + + return ( + + ); +} diff --git a/FrontEnd/src/components/button/IconButton.css b/FrontEnd/src/components/button/IconButton.css new file mode 100644 index 00000000..a3747201 --- /dev/null +++ b/FrontEnd/src/components/button/IconButton.css @@ -0,0 +1,30 @@ +.cru-icon-button { + color: var(--cru-clickable-normal-color); + font-size: 1.4rem; + background: none; + border: none; + transition: all 0.5s; + cursor: pointer; + user-select: none; +} + +.cru-icon-button:hover { + color: var(--cru-clickable-hover-color); +} + +.cru-icon-button:focus { + color: var(--cru-clickable-focus-color); +} + +.cru-icon-button:active { + color: var(--cru-clickable-active-color); +} + +.cru-flat-button:disabled { + color: var(--cru-clickable-disabled-color); + cursor: auto; +} + +.cru-icon-button.large { + font-size: 1.6rem; +} diff --git a/FrontEnd/src/components/button/IconButton.tsx b/FrontEnd/src/components/button/IconButton.tsx new file mode 100644 index 00000000..95c58887 --- /dev/null +++ b/FrontEnd/src/components/button/IconButton.tsx @@ -0,0 +1,30 @@ +import { ComponentPropsWithoutRef } from "react"; +import classNames from "classnames"; + +import { ThemeColor } from "../common"; + +import "./IconButton.css"; + +interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { + icon: string; + color?: ThemeColor | "grayscale"; + large?: boolean; + disabled?: boolean; // TODO: Not implemented +} + +export default function IconButton(props: IconButtonProps) { + const { icon, color, className, large, ...otherProps } = props; + + return ( + + ); +} diff --git a/FrontEnd/src/components/button/index.tsx b/FrontEnd/src/components/button/index.tsx new file mode 100644 index 00000000..b5aa5470 --- /dev/null +++ b/FrontEnd/src/components/button/index.tsx @@ -0,0 +1,15 @@ +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; +import ButtonRow from "./ButtonRow"; +import ButtonRowV2 from "./ButtonRowV2"; + +export { + Button, + FlatButton, + IconButton, + LoadingButton, + ButtonRow, + ButtonRowV2, +}; diff --git a/FrontEnd/src/components/common.ts b/FrontEnd/src/components/common.ts new file mode 100644 index 00000000..e6f7319f --- /dev/null +++ b/FrontEnd/src/components/common.ts @@ -0,0 +1,14 @@ +export type { Text, I18nText } from "~src/common"; +export { UiLogicError, c, convertI18nText, useC } from "~src/common"; + +export const themeColors = [ + "primary", + "secondary", + "danger", + "create", +] as const; + +export type ThemeColor = (typeof themeColors)[number]; + +export { breakpoints } from "./breakpoints"; +export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.css b/FrontEnd/src/components/dialog/ConfirmDialog.css new file mode 100644 index 00000000..e69de29b diff --git a/FrontEnd/src/components/dialog/ConfirmDialog.tsx b/FrontEnd/src/components/dialog/ConfirmDialog.tsx new file mode 100644 index 00000000..26939c9b --- /dev/null +++ b/FrontEnd/src/components/dialog/ConfirmDialog.tsx @@ -0,0 +1,59 @@ +import { useC, Text, ThemeColor } from "../common"; + +import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; + +export default function ConfirmDialog({ + open, + onClose, + onConfirm, + title, + body, + color, + bodyColor, +}: { + open: boolean; + onClose: () => void; + onConfirm: () => void; + title: Text; + body: Text; + color?: ThemeColor; + bodyColor?: ThemeColor; +}) { + const c = useC(); + + return ( + + { + onConfirm(); + onClose(); + }, + }, + }, + ]} + > +
    {c(body)}
    +
    +
    + ); +} diff --git a/FrontEnd/src/components/dialog/Dialog.css b/FrontEnd/src/components/dialog/Dialog.css new file mode 100644 index 00000000..e4c61440 --- /dev/null +++ b/FrontEnd/src/components/dialog/Dialog.css @@ -0,0 +1,60 @@ +.cru-dialog-overlay { + position: fixed; + z-index: 1040; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + overflow: auto; +} + +.cru-dialog-background { + position: absolute; + z-index: -1; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: var(--cru-surface-dim-color); + opacity: 0.8; +} + +.cru-dialog-container { + max-width: 100%; + min-width: 30vw; + + margin: 2em auto; + + border: var(--cru-key-container-color) 1px solid; + border-radius: 5px; + padding: 1.5em; + background-color: var(--cru-surface-color); +} + +@media (min-width: 576px) { + .cru-dialog-container { + max-width: 800px; + } +} + +.cru-dialog-enter .cru-dialog-container { + transform: scale(0, 0); + opacity: 0; + transform-origin: center; +} + +.cru-dialog-enter-active .cru-dialog-container { + transform: scale(1, 1); + opacity: 1; + transition: transform 0.3s, opacity 0.3s; + transform-origin: center; +} + +.cru-dialog-exit-active .cru-dialog-container { + transition: transform 0.3s, opacity 0.3s; + transform: scale(0, 0); + opacity: 0; + transform-origin: center; +} \ No newline at end of file diff --git a/FrontEnd/src/components/dialog/Dialog.tsx b/FrontEnd/src/components/dialog/Dialog.tsx new file mode 100644 index 00000000..2ff7bea8 --- /dev/null +++ b/FrontEnd/src/components/dialog/Dialog.tsx @@ -0,0 +1,63 @@ +import { ReactNode, useRef } from "react"; +import ReactDOM from "react-dom"; +import { CSSTransition } from "react-transition-group"; +import classNames from "classnames"; + +import { ThemeColor } from "../common"; + +import "./Dialog.css"; + +const optionalPortalElement = document.getElementById("portal"); +if (optionalPortalElement == null) { + throw new Error("Portal element not found"); +} +const portalElement = optionalPortalElement; + +interface DialogProps { + open: boolean; + onClose: () => void; + color?: ThemeColor; + children?: ReactNode; + disableCloseOnClickOnOverlay?: boolean; +} + +export default function Dialog({ + open, + onClose, + color, + children, + disableCloseOnClickOnOverlay, +}: DialogProps) { + color = color ?? "primary"; + + const nodeRef = useRef(null); + + return ReactDOM.createPortal( + +
    +
    { + onClose(); + } + } + /> +
    {children}
    +
    + , + portalElement, + ); +} diff --git a/FrontEnd/src/components/dialog/DialogContainer.css b/FrontEnd/src/components/dialog/DialogContainer.css new file mode 100644 index 00000000..fbb18e0d --- /dev/null +++ b/FrontEnd/src/components/dialog/DialogContainer.css @@ -0,0 +1,20 @@ +.cru-dialog-container-title { + font-size: 1.2em; + font-weight: bold; + color: var(--cru-key-color); + margin-bottom: 0.5em; +} + + +.cru-dialog-container-hr { + margin: 1em 0; +} + +.cru-dialog-container-button-row { + display: flex; + justify-content: flex-end; +} + +.cru-dialog-container-button { + margin-left: 1em; +} \ No newline at end of file diff --git a/FrontEnd/src/components/dialog/DialogContainer.tsx b/FrontEnd/src/components/dialog/DialogContainer.tsx new file mode 100644 index 00000000..afee2669 --- /dev/null +++ b/FrontEnd/src/components/dialog/DialogContainer.tsx @@ -0,0 +1,95 @@ +import { ComponentProps, Ref, ReactNode } from "react"; +import classNames from "classnames"; + +import { ThemeColor, Text, useC } from "../common"; +import { ButtonRow, ButtonRowV2 } from "../button"; + +import "./DialogContainer.css"; + +interface DialogContainerBaseProps { + className?: string; + title: Text; + titleColor?: ThemeColor; + titleClassName?: string; + titleRef?: Ref; + bodyContainerClassName?: string; + bodyContainerRef?: Ref; + buttonsClassName?: string; + buttonsContainerRef?: ComponentProps["containerRef"]; + children: ReactNode; +} + +interface DialogContainerWithButtonsProps extends DialogContainerBaseProps { + buttons: ComponentProps["buttons"]; +} + +interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps { + buttonsV2: ComponentProps["buttons"]; +} + +type DialogContainerProps = + | DialogContainerWithButtonsProps + | DialogContainerWithButtonsV2Props; + +export default function DialogContainer(props: DialogContainerProps) { + const { + className, + title, + titleColor, + titleClassName, + titleRef, + bodyContainerClassName, + bodyContainerRef, + buttonsClassName, + buttonsContainerRef, + children, + } = props; + + const c = useC(); + + return ( +
    +
    + {c(title)} +
    +
    +
    + {children} +
    +
    + {"buttons" in props ? ( + + ) : ( + + )} +
    + ); +} diff --git a/FrontEnd/src/components/dialog/FullPageDialog.css b/FrontEnd/src/components/dialog/FullPageDialog.css new file mode 100644 index 00000000..2f1fc636 --- /dev/null +++ b/FrontEnd/src/components/dialog/FullPageDialog.css @@ -0,0 +1,44 @@ +.cru-full-page { + position: fixed; + z-index: 1030; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: white; + padding-top: 56px; +} + +.cru-full-page-top-bar { + height: 56px; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; + background-color: var(--cru-primary-color); + display: flex; + align-items: center; +} + +.cru-full-page-content-container { + overflow: scroll; +} + +.cru-full-page-back-button { + color: var(--cru-primary-t-color); +} + +.cru-full-page-enter { + transform: translate(100%, 0); +} + +.cru-full-page-enter-active { + transform: none; + transition: transform 0.3s; +} + +.cru-full-page-exit-active { + transition: transform 0.3s; + transform: translate(100%, 0); +} diff --git a/FrontEnd/src/components/dialog/FullPageDialog.tsx b/FrontEnd/src/components/dialog/FullPageDialog.tsx new file mode 100644 index 00000000..6368fc0a --- /dev/null +++ b/FrontEnd/src/components/dialog/FullPageDialog.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { createPortal } from "react-dom"; +import classnames from "classnames"; +import { CSSTransition } from "react-transition-group"; + +import "./FullPageDialog.css"; +import IconButton from "../button/IconButton"; + +export interface FullPageDialogProps { + show: boolean; + onBack: () => void; + contentContainerClassName?: string; + children: React.ReactNode; +} + +const FullPageDialog: React.FC = ({ + show, + onBack, + children, + contentContainerClassName, +}) => { + return createPortal( + +
    +
    + +
    +
    + {children} +
    +
    +
    , + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")! + ); +}; + +export default FullPageDialog; diff --git a/FrontEnd/src/components/dialog/OperationDialog.css b/FrontEnd/src/components/dialog/OperationDialog.css new file mode 100644 index 00000000..f4b7237e --- /dev/null +++ b/FrontEnd/src/components/dialog/OperationDialog.css @@ -0,0 +1,8 @@ +.cru-operation-dialog-prompt { + color: var(--cru-surface-on-color); +} + +.cru-operation-dialog-input-group { + display: block; + margin: 0.5em 0; +} diff --git a/FrontEnd/src/components/dialog/OperationDialog.tsx b/FrontEnd/src/components/dialog/OperationDialog.tsx new file mode 100644 index 00000000..e5db7f4f --- /dev/null +++ b/FrontEnd/src/components/dialog/OperationDialog.tsx @@ -0,0 +1,230 @@ +import { useState, ReactNode, ComponentProps } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import { + useInputs, + InputGroup, + Initializer as InputInitializer, + InputValueDict, + InputErrorDict, + InputConfirmValueDict, +} from "../input"; +import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; +import { ButtonRow } from "../button"; + +import "./OperationDialog.css"; + +export type { InputInitializer, InputValueDict, InputErrorDict }; + +interface OperationDialogPromptProps { + message?: Text; + customMessage?: Text; + customMessageNode?: ReactNode; + className?: string; +} + +function OperationDialogPrompt(props: OperationDialogPromptProps) { + const { message, customMessage, customMessageNode, className } = props; + + const c = useC(); + + return ( +
    + {message &&

    {c(message)}

    } + {customMessageNode ?? (customMessage != null ? c(customMessage) : null)} +
    + ); +} + +export interface OperationDialogProps { + open: boolean; + onClose: () => void; + + color?: ThemeColor; + inputColor?: ThemeColor; + title: Text; + inputPrompt?: Text; + inputPromptNode?: ReactNode; + successPrompt?: (data: TData) => Text; + successPromptNode?: (data: TData) => ReactNode; + failurePrompt?: (error: unknown) => Text; + failurePromptNode?: (error: unknown) => ReactNode; + + inputs: InputInitializer; + + onProcess: (inputs: InputConfirmValueDict) => Promise; + onSuccessAndClose?: (data: TData) => void; +} + +function OperationDialog(props: OperationDialogProps) { + const { + open, + onClose, + color, + inputColor, + title, + inputPrompt, + inputPromptNode, + successPrompt, + successPromptNode, + failurePrompt, + failurePromptNode, + inputs, + onProcess, + onSuccessAndClose, + } = props; + + if (process.env.NODE_ENV === "development") { + if (inputPrompt && inputPromptNode) { + console.log("InputPrompt and inputPromptNode are both set."); + } + if (successPrompt && successPromptNode) { + console.log("SuccessPrompt and successPromptNode are both set."); + } + if (failurePrompt && failurePromptNode) { + console.log("FailurePrompt and failurePromptNode are both set."); + } + } + + type Step = + | { type: "input" } + | { type: "process" } + | { + type: "success"; + data: TData; + } + | { + type: "failure"; + data: unknown; + }; + + const [step, setStep] = useState({ type: "input" }); + + const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } = + useInputs({ + init: inputs, + }); + + function close() { + if (step.type !== "process") { + onClose(); + if (step.type === "success" && onSuccessAndClose) { + onSuccessAndClose?.(step.data); + } + } else { + console.log("Attempt to close modal dialog when processing."); + } + } + + function onConfirm() { + const result = confirm(); + if (result.type === "ok") { + setStep({ type: "process" }); + setAllDisabled(true); + onProcess(result.values).then( + (d) => { + setStep({ + type: "success", + data: d, + }); + }, + (e: unknown) => { + setStep({ + type: "failure", + data: e, + }); + }, + ); + } + } + + let body: ReactNode; + let buttons: ComponentProps["buttons"]; + + if (step.type === "input" || step.type === "process") { + const isProcessing = step.type === "process"; + + body = ( +
    + + +
    + ); + buttons = [ + { + key: "cancel", + type: "normal", + props: { + text: "operationDialog.cancel", + color: "secondary", + outline: true, + onClick: close, + disabled: isProcessing, + }, + }, + { + key: "confirm", + type: "loading", + props: { + text: "operationDialog.confirm", + color, + loading: isProcessing, + disabled: hasErrorAndDirty, + onClick: onConfirm, + }, + }, + ]; + } else { + const result = step; + + const promptProps: OperationDialogPromptProps = + result.type === "success" + ? { + message: "operationDialog.success", + customMessage: successPrompt?.(result.data), + customMessageNode: successPromptNode?.(result.data), + } + : { + message: "operationDialog.error", + customMessage: failurePrompt?.(result.data), + customMessageNode: failurePromptNode?.(result.data), + }; + body = ( +
    + +
    + ); + + buttons = [ + { + key: "ok", + type: "normal", + props: { + text: "operationDialog.ok", + color: "primary", + onClick: close, + }, + }, + ]; + } + + return ( + + + {body} + + + ); +} + +export default OperationDialog; diff --git a/FrontEnd/src/components/dialog/index.ts b/FrontEnd/src/components/dialog/index.ts new file mode 100644 index 00000000..59f15791 --- /dev/null +++ b/FrontEnd/src/components/dialog/index.ts @@ -0,0 +1,64 @@ +import { useState } from "react"; + +export { default as Dialog } from "./Dialog"; +export { default as FullPageDialog } from "./FullPageDialog"; +export { default as OperationDialog } from "./OperationDialog"; +export { default as ConfirmDialog } from "./ConfirmDialog"; + +type DialogMap = { + [K in D]: V; +}; + +type DialogKeyMap = DialogMap; + +type DialogPropsMap = DialogMap< + D, + { key: number | string; open: boolean; onClose: () => void } +>; + +export function useDialog( + dialogs: D[], + options?: { + initDialog?: D | null; + onClose?: { + [K in D]?: () => void; + }; + }, +): { + dialog: D | null; + switchDialog: (newDialog: D | null) => void; + dialogPropsMap: DialogPropsMap; + createDialogSwitch: (newDialog: D | null) => () => void; +} { + const [dialog, setDialog] = useState(options?.initDialog ?? null); + + const [dialogKeys, setDialogKeys] = useState>( + () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap, + ); + + const switchDialog = (newDialog: D | null) => { + if (dialog !== null) { + setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 }); + } + setDialog(newDialog); + }; + + return { + dialog, + switchDialog, + dialogPropsMap: Object.fromEntries( + dialogs.map((d) => [ + d, + { + key: `${d}-${dialogKeys[d]}`, + open: dialog === d, + onClose: () => { + switchDialog(null); + options?.onClose?.[d]?.(); + }, + }, + ]), + ) as DialogPropsMap, + createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog), + }; +} diff --git a/FrontEnd/src/components/hooks.ts b/FrontEnd/src/components/hooks.ts new file mode 100644 index 00000000..523a4538 --- /dev/null +++ b/FrontEnd/src/components/hooks.ts @@ -0,0 +1,14 @@ +// TODO: Migrate hooks + +export { + useIsSmallScreen, + useClickOutside, + useScrollToBottom, +} from "~src/utilities/hooks"; + +import { useMediaQuery } from "react-responsive"; +import { breakpoints } from "./breakpoints"; + +export function useMobile(): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }); +} diff --git a/FrontEnd/src/components/index.css b/FrontEnd/src/components/index.css new file mode 100644 index 00000000..a8f5e9a5 --- /dev/null +++ b/FrontEnd/src/components/index.css @@ -0,0 +1,100 @@ +@import "./theme.css"; + +* { + box-sizing: border-box; + margin-inline: 0; + margin-block: 0; +} + +body { + font-family: var(--cru-default-font-family); + background: var(--cru-body-background-color); + color: var(--cru-text-primary-color); + line-height: 1.2; +} + +.cru-page { + padding: var(--cru-page-padding); +} + +.cru-page-no-top-padding { + padding-top: 0; +} + +.cru-text-center { + text-align: center; +} + +.cru-text-end { + text-align: end; +} + +.cru-float-left { + float: left; +} + +.cru-float-right { + float: right; +} + +.cru-align-text-bottom { + vertical-align: text-bottom; +} + +.cru-align-middle { + vertical-align: middle; +} + +.cru-clearfix::after { + clear: both; +} + +.cru-fill-parent { + width: 100%; + height: 100%; +} + +.cru-avatar { + width: 60px; + height: 60px; +} + +.cru-avatar.large { + width: 100px; + height: 100px; +} + +.cru-avatar.small { + width: 40px; + height: 40px; +} + +.cru-round { + border-radius: 50%; +} + +.cru-tab-pages-action-area { + display: flex; + align-items: center; +} + +.alert-container { + position: fixed; + z-index: 1070; +} + +@media (min-width: 576px) { + .alert-container { + bottom: 0; + right: 0; + } +} + +@media (max-width: 575.98px) { + .alert-container { + bottom: 0; + right: 0; + left: 0; + text-align: center; + } +} \ No newline at end of file diff --git a/FrontEnd/src/components/input/InputGroup.css b/FrontEnd/src/components/input/InputGroup.css new file mode 100644 index 00000000..7e905b1e --- /dev/null +++ b/FrontEnd/src/components/input/InputGroup.css @@ -0,0 +1,54 @@ +.cru-input-group { + display: block; +} + +.cru-input-container { + margin: 0.4em 0; +} + +.cru-input-label { + display: block; + color: var(--cru-clickable-normal-color); + font-size: 0.9em; + margin-bottom: 0.3em; +} + +.cru-input-label-inline { + margin-inline-start: 0.5em; +} + +.cru-input-type-text input { + appearance: none; + display: block; + border: 1px solid; + /* color: var(--cru-surface-on-color); */ + /* background-color: var(--cru-surface-color); */ + margin: 0; + font-size: 1em; + padding: 0.2em; +} + +.cru-input-type-text input:hover { + border-color: var(--cru-clickable-hover-color); +} + +.cru-input-type-text input:focus { + border-color: var(--cru-clickable-focus-color); +} + +.cru-input-type-text input:disabled { + border-color: var(--cru-clickable-disabled-color); +} + +.cru-input-error { + display: block; + font-size: 0.8em; + color: var(--cru-danger-color); + margin-top: 0.4em; +} + +.cru-input-helper { + display: block; + font-size: 0.8em; + color: var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/components/input/InputGroup.tsx b/FrontEnd/src/components/input/InputGroup.tsx new file mode 100644 index 00000000..4f487344 --- /dev/null +++ b/FrontEnd/src/components/input/InputGroup.tsx @@ -0,0 +1,463 @@ +/** + * Some notes for InputGroup: + * This is one of the most complicated components in this project. + * Probably because the feature is complex and involved user inputs. + * + * I hope it contains following features: + * - Input features + * - Supports a wide range of input types. + * - Validator to validate user inputs. + * - Can set initial values. + * - Dirty, aka, has user touched this input. + * - Developer friendly + * - Easy to use APIs. + * - Type check as much as possible. + * - UI + * - Configurable appearance. + * - Can display helper and error messages. + * - Easy to extend, like new input types. + * + * So here is some design decisions: + * Inputs are identified by its _key_. + * `InputGroup` component takes care of only UI and no logic. + * `useInputs` hook takes care of logic and generate props for `InputGroup`. + */ + +import { useState, Ref, useId } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import "./InputGroup.css"; + +export interface InputBase { + key: string; + label: Text; + helper?: Text; + disabled?: boolean; + error?: Text; +} + +export interface TextInput extends InputBase { + type: "text"; + value: string; + password?: boolean; +} + +export interface BoolInput extends InputBase { + type: "bool"; + value: boolean; +} + +export interface SelectInputOption { + value: string; + label: Text; + icon?: string; +} + +export interface SelectInput extends InputBase { + type: "select"; + value: string; + options: SelectInputOption[]; +} + +export type Input = TextInput | BoolInput | SelectInput; + +export type InputValue = Input["value"]; + +export type InputValueDict = Record; +export type InputErrorDict = Record; +export type InputDisabledDict = Record; +export type InputDirtyDict = Record; +// use never so you don't have to cast everywhere +export type InputConfirmValueDict = Record; + +export type GeneralInputErrorDict = + | { + [key: string]: Text | null | undefined; + } + | null + | undefined; + +type MakeInputInfo = Omit; + +export type InputInfo = { + [I in Input as I["type"]]: MakeInputInfo; +}[Input["type"]]; + +export type Validator = ( + values: InputValueDict, + inputs: InputInfo[], +) => GeneralInputErrorDict; + +export type InputScheme = { + inputs: InputInfo[]; + validator?: Validator; +}; + +export type InputData = { + values: InputValueDict; + errors: InputErrorDict; + disabled: InputDisabledDict; + dirties: InputDirtyDict; +}; + +export type State = { + scheme: InputScheme; + data: InputData; +}; + +export type DataInitialization = { + values?: InputValueDict; + errors?: GeneralInputErrorDict; + disabled?: InputDisabledDict; + dirties?: InputDirtyDict; +}; + +export type Initialization = { + scheme: InputScheme; + dataInit?: DataInitialization; +}; + +export type GeneralInitialization = Initialization | InputScheme | InputInfo[]; + +export type Initializer = GeneralInitialization | (() => GeneralInitialization); + +export interface InputGroupProps { + color?: ThemeColor; + containerClassName?: string; + containerRef?: Ref; + + inputs: Input[]; + onChange: (index: number, value: Input["value"]) => void; +} + +function cleanObject(o: Record): Record> { + const result = { ...o }; + for (const key of Object.keys(result)) { + if (result[key] == null) { + delete result[key]; + } + } + return result as never; +} + +export type ConfirmResult = + | { + type: "ok"; + values: InputConfirmValueDict; + } + | { + type: "error"; + errors: InputErrorDict; + }; + +function validate( + validator: Validator | null | undefined, + values: InputValueDict, + inputs: InputInfo[], +): InputErrorDict { + return cleanObject(validator?.(values, inputs) ?? {}); +} + +export function useInputs(options: { init: Initializer }): { + inputGroupProps: InputGroupProps; + hasError: boolean; + hasErrorAndDirty: boolean; + confirm: () => ConfirmResult; + setAllDisabled: (disabled: boolean) => void; +} { + function initializeValue( + input: InputInfo, + value?: InputValue | null, + ): InputValue { + if (input.type === "text") { + return value ?? ""; + } else if (input.type === "bool") { + return value ?? false; + } else if (input.type === "select") { + return value ?? input.options[0].value; + } + throw new Error("Unknown input type"); + } + + function initialize(generalInitialization: GeneralInitialization): State { + const initialization: Initialization = Array.isArray(generalInitialization) + ? { scheme: { inputs: generalInitialization } } + : "scheme" in generalInitialization + ? generalInitialization + : { scheme: generalInitialization }; + + const { scheme, dataInit } = initialization; + const { inputs, validator } = scheme; + const keys = inputs.map((input) => input.key); + + if (process.env.NODE_ENV === "development") { + const checkKeys = (dict: Record | undefined) => { + if (dict != null) { + for (const key of Object.keys(dict)) { + if (!keys.includes(key)) { + console.warn(""); + } + } + } + }; + + checkKeys(dataInit?.values); + checkKeys(dataInit?.errors ?? {}); + checkKeys(dataInit?.disabled); + checkKeys(dataInit?.dirties); + } + + function clean( + dict: Record | null | undefined, + ): Record> { + return dict != null ? cleanObject(dict) : {}; + } + + const values: InputValueDict = {}; + const disabled: InputDisabledDict = clean(dataInit?.disabled); + const dirties: InputDirtyDict = clean(dataInit?.dirties); + const isErrorSet = dataInit?.errors != null; + let errors: InputErrorDict = clean(dataInit?.errors); + + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + const { key } = input; + + values[key] = initializeValue(input, dataInit?.values?.[key]); + } + + if (isErrorSet) { + if (process.env.NODE_ENV === "development") { + console.log( + "You explicitly set errors (not undefined) in initializer, so validator won't run.", + ); + } + } else { + errors = validate(validator, values, inputs); + } + + return { + scheme, + data: { + values, + errors, + disabled, + dirties, + }, + }; + } + + const { init } = options; + const initializer = typeof init === "function" ? init : () => init; + + const [state, setState] = useState(() => initialize(initializer())); + + const { scheme, data } = state; + const { validator } = scheme; + + function createAllBooleanDict(value: boolean): Record { + const result: InputDirtyDict = {}; + for (const key of scheme.inputs.map((input) => input.key)) { + result[key] = value; + } + return result; + } + + const createAllDirties = () => createAllBooleanDict(true); + + const componentInputs: Input[] = []; + + for (let i = 0; i < scheme.inputs.length; i++) { + const input = scheme.inputs[i]; + const value = data.values[input.key]; + const error = data.errors[input.key]; + const disabled = data.disabled[input.key] ?? false; + const dirty = data.dirties[input.key] ?? false; + const componentInput: Input = { + ...input, + value: value as never, + disabled, + error: dirty ? error : undefined, + }; + componentInputs.push(componentInput); + } + + const hasError = Object.keys(data.errors).length > 0; + const hasDirty = Object.keys(data.dirties).some((key) => data.dirties[key]); + + return { + inputGroupProps: { + inputs: componentInputs, + onChange: (index, value) => { + const input = scheme.inputs[index]; + const { key } = input; + const newValues = { ...data.values, [key]: value }; + const newDirties = { ...data.dirties, [key]: true }; + const newErrors = validate(validator, newValues, scheme.inputs); + setState({ + scheme, + data: { + ...data, + values: newValues, + errors: newErrors, + dirties: newDirties, + }, + }); + }, + }, + hasError, + hasErrorAndDirty: hasError && hasDirty, + confirm() { + const newDirties = createAllDirties(); + const newErrors = validate(validator, data.values, scheme.inputs); + + setState({ + scheme, + data: { + ...data, + dirties: newDirties, + errors: newErrors, + }, + }); + + if (Object.keys(newErrors).length !== 0) { + return { + type: "error", + errors: newErrors, + }; + } else { + return { + type: "ok", + values: data.values as InputConfirmValueDict, + }; + } + }, + setAllDisabled(disabled: boolean) { + setState({ + scheme, + data: { + ...data, + disabled: createAllBooleanDict(disabled), + }, + }); + }, + }; +} + +export function InputGroup({ + color, + inputs, + onChange, + containerRef, + containerClassName, +}: InputGroupProps) { + const c = useC(); + + const id = useId(); + + return ( +
    + {inputs.map((item, index) => { + const { key, type, value, label, error, helper, disabled } = item; + + const getContainerClassName = ( + ...additionalClassNames: classNames.ArgumentArray + ) => + classNames( + `cru-input-container cru-input-type-${type}`, + error && "error", + ...additionalClassNames, + ); + + const changeValue = (value: InputValue) => { + onChange(index, value); + }; + + const inputId = `${id}-${key}`; + + if (type === "text") { + const { password } = item; + return ( +
    + {label && ( + + )} + { + const v = event.target.value; + changeValue(v); + }} + disabled={disabled} + /> + {error &&
    {c(error)}
    } + {helper &&
    {c(helper)}
    } +
    + ); + } else if (type === "bool") { + return ( +
    + { + const v = event.currentTarget.checked; + changeValue(v); + }} + disabled={disabled} + /> + + {error &&
    {c(error)}
    } + {helper &&
    {c(helper)}
    } +
    + ); + } else if (type === "select") { + return ( +
    + + +
    + ); + } + })} +
    + ); +} diff --git a/FrontEnd/src/components/input/index.ts b/FrontEnd/src/components/input/index.ts new file mode 100644 index 00000000..ca183089 --- /dev/null +++ b/FrontEnd/src/components/input/index.ts @@ -0,0 +1,11 @@ +export { useInputs, InputGroup } from "./InputGroup"; + +export type { + InputValueDict, + InputErrorDict, + InputDirtyDict, + InputDisabledDict, + InputConfirmValueDict, + Validator, + Initializer, +} from "./InputGroup"; diff --git a/FrontEnd/src/components/list/ListContainer.css b/FrontEnd/src/components/list/ListContainer.css new file mode 100644 index 00000000..53781834 --- /dev/null +++ b/FrontEnd/src/components/list/ListContainer.css @@ -0,0 +1,4 @@ +.cru-list-container { + border: 1px solid var(--cru-clickable-primary-normal-color); + border-radius: 5px; +} diff --git a/FrontEnd/src/components/list/ListContainer.tsx b/FrontEnd/src/components/list/ListContainer.tsx new file mode 100644 index 00000000..aa00d12c --- /dev/null +++ b/FrontEnd/src/components/list/ListContainer.tsx @@ -0,0 +1,23 @@ +import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; +import classNames from "classnames"; + +import "./ListContainer.css" + +function _ListContainer( + { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, + ref: Ref, +) { + return ( +
    + {children} +
    + ); +} + +const ListContainer = forwardRef(_ListContainer); + +export default ListContainer; diff --git a/FrontEnd/src/components/list/ListItemContainer.css b/FrontEnd/src/components/list/ListItemContainer.css new file mode 100644 index 00000000..8d7afa9f --- /dev/null +++ b/FrontEnd/src/components/list/ListItemContainer.css @@ -0,0 +1,3 @@ +.cru-list-item-container { + border: 1px solid var(--cru-clickable-primary-normal-color); +} diff --git a/FrontEnd/src/components/list/ListItemContainer.tsx b/FrontEnd/src/components/list/ListItemContainer.tsx new file mode 100644 index 00000000..315cbd6e --- /dev/null +++ b/FrontEnd/src/components/list/ListItemContainer.tsx @@ -0,0 +1,23 @@ +import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; +import classNames from "classnames"; + +import "./ListItemContainer.css"; + +function _ListItemContainer( + { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, + ref: Ref, +) { + return ( +
    + {children} +
    + ); +} + +const ListItemContainer = forwardRef(_ListItemContainer); + +export default ListItemContainer; diff --git a/FrontEnd/src/components/list/index.ts b/FrontEnd/src/components/list/index.ts new file mode 100644 index 00000000..e183f7da --- /dev/null +++ b/FrontEnd/src/components/list/index.ts @@ -0,0 +1,4 @@ +import ListContainer from "./ListContainer"; +import ListItemContainer from "./ListItemContainer"; + +export { ListContainer, ListItemContainer }; diff --git a/FrontEnd/src/components/menu/Menu.css b/FrontEnd/src/components/menu/Menu.css new file mode 100644 index 00000000..75734533 --- /dev/null +++ b/FrontEnd/src/components/menu/Menu.css @@ -0,0 +1,36 @@ +.cru-menu { + min-width: 200px; +} + +.cru-menu-item { + display: block; + font-size: 1em; + width: 100%; + padding: 0.5em 1.5em; + transition: all 0.5s; + color: var(--cru-clickable-normal-color); + background-color: var(--cru-clickable-grayscale-normal-color); + border: none; + cursor: pointer; +} + +.cru-menu-item:hover { + background-color: var(--cru-clickable-grayscale-hover-color); +} + +.cru-menu-item:focus { + background-color: var(--cru-clickable-grayscale-focus-color); +} + +.cru-menu-item:active { + background-color: var(--cru-clickable-grayscale-active-color); +} + +.cru-menu-item-icon { + margin-right: 1em; +} + +.cru-menu-divider { + border-width: 0; + border-top: 1px solid var(--cru-primary-color); +} \ No newline at end of file diff --git a/FrontEnd/src/components/menu/Menu.tsx b/FrontEnd/src/components/menu/Menu.tsx new file mode 100644 index 00000000..e8099c76 --- /dev/null +++ b/FrontEnd/src/components/menu/Menu.tsx @@ -0,0 +1,62 @@ +import { CSSProperties } from "react"; +import classNames from "classnames"; + +import { useC, Text, ThemeColor } from "../common"; + +import "./Menu.css"; +import Icon from "../Icon"; + +export type MenuItem = + | { + type: "divider"; + } + | { + type: "button"; + text: Text; + icon?: string; + color?: ThemeColor; + onClick: () => void; + }; + +export type MenuItems = MenuItem[]; + +export type MenuProps = { + items: MenuItems; + onItemClicked?: () => void; + className?: string; + style?: CSSProperties; +}; + +export default function Menu({ + items, + onItemClicked, + className, + style, +}: MenuProps) { + const c = useC(); + + return ( +
    + {items.map((item, index) => { + if (item.type === "divider") { + return
    ; + } else { + const { text, color, icon, onClick } = item; + return ( + + ); + } + })} +
    + ); +} diff --git a/FrontEnd/src/components/menu/PopupMenu.css b/FrontEnd/src/components/menu/PopupMenu.css new file mode 100644 index 00000000..149e0699 --- /dev/null +++ b/FrontEnd/src/components/menu/PopupMenu.css @@ -0,0 +1,7 @@ +.cru-popup-menu-menu-container { + z-index: 1040; + border-radius: 3px; + border: var(--cru-clickable-normal-color) 1.5px solid; + background-color: var(--cru-background-color); + overflow: hidden; +} diff --git a/FrontEnd/src/components/menu/PopupMenu.tsx b/FrontEnd/src/components/menu/PopupMenu.tsx new file mode 100644 index 00000000..23a67f79 --- /dev/null +++ b/FrontEnd/src/components/menu/PopupMenu.tsx @@ -0,0 +1,73 @@ +import { useState, CSSProperties, ReactNode } from "react"; +import classNames from "classnames"; +import { createPortal } from "react-dom"; +import { usePopper } from "react-popper"; + +import { useClickOutside } from "~src/utilities/hooks"; + +import Menu, { MenuItems } from "./Menu"; + +import { ThemeColor } from "../common"; + +import "./PopupMenu.css"; + +export interface PopupMenuProps { + color?: ThemeColor; + items: MenuItems; + children?: ReactNode; + containerClassName?: string; + containerStyle?: CSSProperties; +} + +export default function PopupMenu({ + color, + items, + children, + containerClassName, + containerStyle, +}: PopupMenuProps) { + const [show, setShow] = useState(false); + + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null, + ); + const { styles, attributes } = usePopper(referenceElement, popperElement); + + useClickOutside(popperElement, () => setShow(false), true); + + return ( +
    setShow(true)} + > + {children} + {show && + createPortal( +
    + { + setShow(false); + }} + /> +
    , + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")!, + )} +
    + ); +} diff --git a/FrontEnd/src/components/tab/TabPages.tsx b/FrontEnd/src/components/tab/TabPages.tsx new file mode 100644 index 00000000..6a5f4469 --- /dev/null +++ b/FrontEnd/src/components/tab/TabPages.tsx @@ -0,0 +1,71 @@ +import * as React from "react"; + +import { I18nText, UiLogicError } from "~src/common"; + +import Tabs from "./Tabs"; + +export interface TabPage { + name: string; + text: I18nText; + page: React.ReactNode; +} + +export interface TabPagesProps { + pages: TabPage[]; + actions?: React.ReactNode; + dense?: boolean; + className?: string; + style?: React.CSSProperties; + navClassName?: string; + navStyle?: React.CSSProperties; + pageContainerClassName?: string; + pageContainerStyle?: React.CSSProperties; +} + +const TabPages: React.FC = ({ + pages, + actions, + dense, + className, + style, + navClassName, + navStyle, + pageContainerClassName, + pageContainerStyle, +}) => { + if (pages.length === 0) { + throw new UiLogicError("Page list can't be empty."); + } + + const [tab, setTab] = React.useState(pages[0].name); + + const currentPage = pages.find((p) => p.name === tab); + + if (currentPage == null) { + throw new UiLogicError("Current tab value is bad."); + } + + return ( +
    + ({ + name: page.name, + text: page.text, + onClick: () => { + setTab(page.name); + }, + }))} + dense={dense} + activeTabName={tab} + className={navClassName} + style={navStyle} + actions={actions} + /> +
    + {currentPage.page} +
    +
    + ); +}; + +export default TabPages; diff --git a/FrontEnd/src/components/tab/Tabs.css b/FrontEnd/src/components/tab/Tabs.css new file mode 100644 index 00000000..395d16a7 --- /dev/null +++ b/FrontEnd/src/components/tab/Tabs.css @@ -0,0 +1,33 @@ +.cru-nav { + border-bottom: var(--cru-primary-color) 1px solid; + display: flex; +} + +.cru-nav-item { + color: var(--cru-primary-color); + border: var(--cru-background-2-color) 0.5px solid; + border-bottom: none; + padding: 0.5em 1.5em; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + transition: all 0.5s; + cursor: pointer; +} + +.cru-nav.dense .cru-nav-item { + padding: 0.2em 1em; +} + +.cru-nav-item:hover { + background-color: var(--cru-background-1-color); +} + +.cru-nav-item.active { + color: var(--cru-primary-t-color); + background-color: var(--cru-primary-color); + border-color: var(--cru-primary-color); +} + +.cru-nav-action-area { + margin-left: auto; +} diff --git a/FrontEnd/src/components/tab/Tabs.tsx b/FrontEnd/src/components/tab/Tabs.tsx new file mode 100644 index 00000000..dc8d9c01 --- /dev/null +++ b/FrontEnd/src/components/tab/Tabs.tsx @@ -0,0 +1,62 @@ +import * as React from "react"; +import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import classnames from "classnames"; + +import { convertI18nText, I18nText } from "~src/common"; + +import "./Tabs.css"; + +export interface Tab { + name: string; + text: I18nText; + link?: string; + onClick?: () => void; +} + +export interface TabsProps { + activeTabName?: string; + actions?: React.ReactNode; + dense?: boolean; + tabs: Tab[]; + className?: string; + style?: React.CSSProperties; +} + +export default function Tabs(props: TabsProps): React.ReactElement | null { + const { tabs, activeTabName, className, style, dense, actions } = props; + + const { t } = useTranslation(); + + return ( +
    + {tabs.map((tab) => { + const active = activeTabName === tab.name; + const className = classnames("cru-nav-item", active && "active"); + + if (tab.link != null) { + return ( + + {convertI18nText(tab.text, t)} + + ); + } else { + return ( + + {convertI18nText(tab.text, t)} + + ); + } + })} +
    {actions}
    +
    + ); +} diff --git a/FrontEnd/src/components/theme-color.css b/FrontEnd/src/components/theme-color.css new file mode 100644 index 00000000..24a7e267 --- /dev/null +++ b/FrontEnd/src/components/theme-color.css @@ -0,0 +1,173 @@ +/* Generated by theme-generator.ts */ + +:root { + --cru-primary-color: hsl(210 100% 40%); + --cru-primary-1-color: hsl(210 100% 37%); + --cru-primary-2-color: hsl(210 100% 34%); + --cru-primary-on-color: hsl(210 100% 100%); + --cru-primary-container-color: hsl(210 100% 90%); + --cru-primary-container-1-color: hsl(210 100% 80%); + --cru-primary-container-2-color: hsl(210 100% 70%); + --cru-primary-on-container-color: hsl(210 100% 10%); + --cru-secondary-color: hsl(40 100% 40%); + --cru-secondary-1-color: hsl(40 100% 37%); + --cru-secondary-2-color: hsl(40 100% 34%); + --cru-secondary-on-color: hsl(40 100% 100%); + --cru-secondary-container-color: hsl(40 100% 90%); + --cru-secondary-container-1-color: hsl(40 100% 80%); + --cru-secondary-container-2-color: hsl(40 100% 70%); + --cru-secondary-on-container-color: hsl(40 100% 10%); + --cru-tertiary-color: hsl(160 100% 40%); + --cru-tertiary-1-color: hsl(160 100% 37%); + --cru-tertiary-2-color: hsl(160 100% 34%); + --cru-tertiary-on-color: hsl(160 100% 100%); + --cru-tertiary-container-color: hsl(160 100% 90%); + --cru-tertiary-container-1-color: hsl(160 100% 80%); + --cru-tertiary-container-2-color: hsl(160 100% 70%); + --cru-tertiary-on-container-color: hsl(160 100% 10%); + --cru-danger-color: hsl(0 100% 40%); + --cru-danger-1-color: hsl(0 100% 37%); + --cru-danger-2-color: hsl(0 100% 34%); + --cru-danger-on-color: hsl(0 100% 100%); + --cru-danger-container-color: hsl(0 100% 90%); + --cru-danger-container-1-color: hsl(0 100% 80%); + --cru-danger-container-2-color: hsl(0 100% 70%); + --cru-danger-on-container-color: hsl(0 100% 10%); + --cru-success-color: hsl(120 60% 40%); + --cru-success-1-color: hsl(120 60% 37%); + --cru-success-2-color: hsl(120 60% 34%); + --cru-success-on-color: hsl(120 60% 100%); + --cru-success-container-color: hsl(120 60% 90%); + --cru-success-container-1-color: hsl(120 60% 80%); + --cru-success-container-2-color: hsl(120 60% 70%); + --cru-success-on-container-color: hsl(120 60% 10%); + --cru-surface-dim-color: hsl(0 0% 87%); + --cru-surface-color: hsl(0 0% 98%); + --cru-surface-1-color: hsl(0 0% 90%); + --cru-surface-2-color: hsl(0 0% 82%); + --cru-surface-bright-color: hsl(0 0% 98%); + --cru-surface-container-lowest-color: hsl(0 0% 100%); + --cru-surface-container-low-color: hsl(0 0% 96%); + --cru-surface-container-color: hsl(0 0% 94%); + --cru-surface-container-high-color: hsl(0 0% 92%); + --cru-surface-container-highest-color: hsl(0 0% 90%); + --cru-surface-on-color: hsl(0 0% 10%); + --cru-surface-on-variant-color: hsl(0 0% 30%); + --cru-surface-outline-color: hsl(0 0% 50%); + --cru-surface-outline-variant-color: hsl(0 0% 80%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-primary-color: hsl(210 100% 80%); + --cru-primary-1-color: hsl(210 100% 75%); + --cru-primary-2-color: hsl(210 100% 68%); + --cru-primary-on-color: hsl(210 100% 20%); + --cru-primary-container-color: hsl(210 100% 30%); + --cru-primary-container-1-color: hsl(210 100% 25%); + --cru-primary-container-2-color: hsl(210 100% 20%); + --cru-primary-on-container-color: hsl(210 100% 90%); + --cru-secondary-color: hsl(40 100% 80%); + --cru-secondary-1-color: hsl(40 100% 75%); + --cru-secondary-2-color: hsl(40 100% 68%); + --cru-secondary-on-color: hsl(40 100% 20%); + --cru-secondary-container-color: hsl(40 100% 30%); + --cru-secondary-container-1-color: hsl(40 100% 25%); + --cru-secondary-container-2-color: hsl(40 100% 20%); + --cru-secondary-on-container-color: hsl(40 100% 90%); + --cru-tertiary-color: hsl(160 100% 80%); + --cru-tertiary-1-color: hsl(160 100% 75%); + --cru-tertiary-2-color: hsl(160 100% 68%); + --cru-tertiary-on-color: hsl(160 100% 20%); + --cru-tertiary-container-color: hsl(160 100% 30%); + --cru-tertiary-container-1-color: hsl(160 100% 25%); + --cru-tertiary-container-2-color: hsl(160 100% 20%); + --cru-tertiary-on-container-color: hsl(160 100% 90%); + --cru-danger-color: hsl(0 100% 80%); + --cru-danger-1-color: hsl(0 100% 75%); + --cru-danger-2-color: hsl(0 100% 68%); + --cru-danger-on-color: hsl(0 100% 20%); + --cru-danger-container-color: hsl(0 100% 30%); + --cru-danger-container-1-color: hsl(0 100% 25%); + --cru-danger-container-2-color: hsl(0 100% 20%); + --cru-danger-on-container-color: hsl(0 100% 90%); + --cru-success-color: hsl(120 60% 80%); + --cru-success-1-color: hsl(120 60% 75%); + --cru-success-2-color: hsl(120 60% 68%); + --cru-success-on-color: hsl(120 60% 20%); + --cru-success-container-color: hsl(120 60% 30%); + --cru-success-container-1-color: hsl(120 60% 25%); + --cru-success-container-2-color: hsl(120 60% 20%); + --cru-success-on-container-color: hsl(120 60% 90%); + --cru-surface-dim-color: hsl(0 0% 6%); + --cru-surface-color: hsl(0 0% 6%); + --cru-surface-1-color: hsl(0 0% 25%); + --cru-surface-2-color: hsl(0 0% 40%); + --cru-surface-bright-color: hsl(0 0% 24%); + --cru-surface-container-lowest-color: hsl(0 0% 4%); + --cru-surface-container-low-color: hsl(0 0% 10%); + --cru-surface-container-color: hsl(0 0% 12%); + --cru-surface-container-high-color: hsl(0 0% 17%); + --cru-surface-container-highest-color: hsl(0 0% 22%); + --cru-surface-on-color: hsl(0 0% 90%); + --cru-surface-on-variant-color: hsl(0 0% 80%); + --cru-surface-outline-color: hsl(0 0% 60%); + --cru-surface-outline-variant-color: hsl(0 0% 30%); + } +} + +.cru-primary { + --cru-key-color: var(--cru-primary-color); + --cru-key-1-color: var(--cru-primary-1-color); + --cru-key-2-color: var(--cru-primary-2-color); + --cru-key-on-color: var(--cru-primary-on-color); + --cru-key-container-color: var(--cru-primary-container-color); + --cru-key-container-1-color: var(--cru-primary-container-1-color); + --cru-key-container-2-color: var(--cru-primary-container-2-color); + --cru-key-on-container-color: var(--cru-primary-on-container-color); +} + +.cru-secondary { + --cru-key-color: var(--cru-secondary-color); + --cru-key-1-color: var(--cru-secondary-1-color); + --cru-key-2-color: var(--cru-secondary-2-color); + --cru-key-on-color: var(--cru-secondary-on-color); + --cru-key-container-color: var(--cru-secondary-container-color); + --cru-key-container-1-color: var(--cru-secondary-container-1-color); + --cru-key-container-2-color: var(--cru-secondary-container-2-color); + --cru-key-on-container-color: var(--cru-secondary-on-container-color); +} + +.cru-tertiary { + --cru-key-color: var(--cru-tertiary-color); + --cru-key-1-color: var(--cru-tertiary-1-color); + --cru-key-2-color: var(--cru-tertiary-2-color); + --cru-key-on-color: var(--cru-tertiary-on-color); + --cru-key-container-color: var(--cru-tertiary-container-color); + --cru-key-container-1-color: var(--cru-tertiary-container-1-color); + --cru-key-container-2-color: var(--cru-tertiary-container-2-color); + --cru-key-on-container-color: var(--cru-tertiary-on-container-color); +} + +.cru-danger { + --cru-key-color: var(--cru-danger-color); + --cru-key-1-color: var(--cru-danger-1-color); + --cru-key-2-color: var(--cru-danger-2-color); + --cru-key-on-color: var(--cru-danger-on-color); + --cru-key-container-color: var(--cru-danger-container-color); + --cru-key-container-1-color: var(--cru-danger-container-1-color); + --cru-key-container-2-color: var(--cru-danger-container-2-color); + --cru-key-on-container-color: var(--cru-danger-on-container-color); +} + +.cru-success { + --cru-key-color: var(--cru-success-color); + --cru-key-1-color: var(--cru-success-1-color); + --cru-key-2-color: var(--cru-success-2-color); + --cru-key-on-color: var(--cru-success-on-color); + --cru-key-container-color: var(--cru-success-container-color); + --cru-key-container-1-color: var(--cru-success-container-1-color); + --cru-key-container-2-color: var(--cru-success-container-2-color); + --cru-key-on-container-color: var(--cru-success-on-container-color); +} + diff --git a/FrontEnd/src/components/theme.css b/FrontEnd/src/components/theme.css new file mode 100644 index 00000000..6ceb369f --- /dev/null +++ b/FrontEnd/src/components/theme.css @@ -0,0 +1,146 @@ +@import "./theme-color.css"; + +:root { + --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --cru-page-padding: 1em 2em; + + --cru-border-radius: 4px; + --cru-card-border-radius: 4px; +} + +/* theme colors */ +:root { + --cru-primary-color: hsl(210, 100%, 50%); + --cru-secondary-color: hsl(30, 100%, 50%); + --cru-create-color: hsl(120, 100%, 25%); + --cru-danger-color: hsl(0, 100%, 50%); +} + +/* common colors */ +:root { + --cru-background-color: hsl(0, 0%, 100%); + --cru-container-background-color: hsl(0, 0%, 97%); + --cru-text-primary-color: hsl(0, 0%, 0%); + --cru-text-secondary-color: hsl(0, 0%, 38%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-background-color: hsl(0, 0%, 0%); + --cru-container-background-color: hsl(0, 0%, 2%); + --cru-text-primary-color: hsl(0, 0%, 100%); + --cru-text-secondary-color: hsl(0, 0%, 85%); + } +} + +:root { + --cru-body-background-color: var(--cru-background-color); +} + +/* clickable color */ +:root { + --cru-clickable-primary-normal-color: var(--cru-primary-color); + --cru-clickable-primary-hover-color: hsl(210, 100%, 60%); + --cru-clickable-primary-focus-color: hsl(210, 100%, 60%); + --cru-clickable-primary-active-color: hsl(210, 100%, 70%); + --cru-clickable-secondary-normal-color: var(--cru-secondary-color); + --cru-clickable-secondary-hover-color: hsl(30, 100%, 60%); + --cru-clickable-secondary-focus-color: hsl(30, 100%, 60%); + --cru-clickable-secondary-active-color: hsl(30, 100%, 70%); + --cru-clickable-create-normal-color: var(--cru-create-color); + --cru-clickable-create-hover-color: hsl(120, 100%, 35%); + --cru-clickable-create-focus-color: hsl(120, 100%, 35%); + --cru-clickable-create-active-color: hsl(120, 100%, 35%); + --cru-clickable-danger-normal-color: var(--cru-danger-color); + --cru-clickable-danger-hover-color: hsl(0, 100%, 60%); + --cru-clickable-danger-focus-color: hsl(0, 100%, 60%); + --cru-clickable-danger-active-color: hsl(0, 100%, 70%); + --cru-clickable-grayscale-normal-color: hsl(0, 0%, 100%); + --cru-clickable-grayscale-hover-color: hsl(0, 0%, 92%); + --cru-clickable-grayscale-focus-color: hsl(0, 0%, 92%); + --cru-clickable-grayscale-active-color: hsl(0, 0%, 88%); + --cru-clickable-disabled-color: hsl(0, 0%, 50%); +} + +@media (prefers-color-scheme: dark) { + :root { + --cru-clickable-grayscale-normal-color: hsl(0, 0%, 0%); + --cru-clickable-grayscale-hover-color: hsl(0, 0%, 10%); + --cru-clickable-grayscale-focus-color: hsl(0, 0%, 10%); + --cru-clickable-grayscale-active-color: hsl(0, 0%, 20%); + } +} + +.cru-clickable-primary { + --cru-clickable-normal-color: var(--cru-clickable-primary-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-primary-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-primary-focus-color); + --cru-clickable-active-color: var(--cru-clickable-primary-active-color); +} + +.cru-clickable-secondary { + --cru-clickable-normal-color: var(--cru-clickable-secondary-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-secondary-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-secondary-focus-color); + --cru-clickable-active-color: var(--cru-clickable-secondary-active-color); +} + +.cru-clickable-create { + --cru-clickable-normal-color: var(--cru-clickable-create-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-create-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-create-focus-color); + --cru-clickable-active-color: var(--cru-clickable-create-active-color); +} + +.cru-clickable-danger { + --cru-clickable-normal-color: var(--cru-clickable-danger-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-danger-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-danger-focus-color); + --cru-clickable-active-color: var(--cru-clickable-danger-active-color); +} + +.cru-clickable-grayscale { + --cru-clickable-normal-color: var(--cru-clickable-grayscale-normal-color); + --cru-clickable-hover-color: var(--cru-clickable-grayscale-hover-color); + --cru-clickable-focus-color: var(--cru-clickable-grayscale-focus-color); + --cru-clickable-active-color: var(--cru-clickable-grayscale-active-color); +} + +/* button colors */ +:root { + /* push button colors */ + --cru-push-button-text-color: #ffffff; + --cru-push-button-disabled-text-color: hsl(0, 0%, 80%); +} + +/* Card colors */ +:root { + --cru-card-background-primary-color: hsl(210, 100%, 50%); + --cru-card-border-primary-color: hsl(210, 100%, 50%); + --cru-card-background-secondary-color: hsl(30, 100%, 50%); + --cru-card-border-secondary-color: hsl(30, 100%, 50%); + --cru-card-background-create-color: hsl(120, 100%, 25%); + --cru-card-border-create-color: hsl(120, 100%, 25%); + --cru-card-background-danger-color: hsl(0, 100%, 50%); + --cru-card-border-danger-color: hsl(0, 100%, 50%); +} + +.cru-card-primary { + --cru-card-background-color: var(--cru-card-background-primary-color); + --cru-card-border-color: var(--cru-card-border-primary-color) +} + +.cru-card-secondary { + --cru-card-background-color: var(--cru-card-background-secondary-color); + --cru-card-border-color: var(--cru-card-border-secondary-color) +} + +.cru-card-create { + --cru-card-background-color: var(--cru-card-background-create-color); + --cru-card-border-color: var(--cru-card-border-create-color) +} + +.cru-card-danger { + --cru-card-background-color: var(--cru-card-background-danger-color); + --cru-card-border-color: var(--cru-card-border-danger-color) +} \ No newline at end of file diff --git a/FrontEnd/src/components/user/UserAvatar.tsx b/FrontEnd/src/components/user/UserAvatar.tsx new file mode 100644 index 00000000..8671f2d8 --- /dev/null +++ b/FrontEnd/src/components/user/UserAvatar.tsx @@ -0,0 +1,22 @@ +import { Ref, ComponentPropsWithoutRef } from "react"; + +import { getHttpUserClient } from "~src/http/user"; + +export interface UserAvatarProps extends ComponentPropsWithoutRef<"img"> { + username: string; + imgRef?: Ref | null; +} + +export default function UserAvatar({ + username, + imgRef, + ...otherProps +}: UserAvatarProps) { + return ( + + ); +} diff --git a/FrontEnd/src/http/bookmark.ts b/FrontEnd/src/http/bookmark.ts index 40e121cc..311f9a0f 100644 --- a/FrontEnd/src/http/bookmark.ts +++ b/FrontEnd/src/http/bookmark.ts @@ -1,4 +1,4 @@ -import { withQuery } from "@/utilities/url"; +import { withQuery } from "~src/utilities/url"; import { axios, apiBaseUrl, extractResponseData, Page } from "./common"; diff --git a/FrontEnd/src/http/timeline.ts b/FrontEnd/src/http/timeline.ts index 401ae116..255c786e 100644 --- a/FrontEnd/src/http/timeline.ts +++ b/FrontEnd/src/http/timeline.ts @@ -1,4 +1,4 @@ -import { withQuery } from "@/utilities/url"; +import { withQuery } from "~src/utilities/url"; import { axios, diff --git a/FrontEnd/src/migrating/center/CenterBoards.tsx b/FrontEnd/src/migrating/center/CenterBoards.tsx index a8be2c29..f1c3fc6a 100644 --- a/FrontEnd/src/migrating/center/CenterBoards.tsx +++ b/FrontEnd/src/migrating/center/CenterBoards.tsx @@ -1,13 +1,13 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; -import { highlightTimelineUsername } from "@/common"; +import { highlightTimelineUsername } from "~src/common"; -import { pushAlert } from "@/services/alert"; -import { useUserLoggedIn } from "@/services/user"; +import { pushAlert } from "~src/services/alert"; +import { useUserLoggedIn } from "~src/services/user"; -import { getHttpTimelineClient } from "@/http/timeline"; -import { getHttpBookmarkClient } from "@/http/bookmark"; +import { getHttpTimelineClient } from "~src/http/timeline"; +import { getHttpBookmarkClient } from "~src/http/bookmark"; import TimelineBoard from "./TimelineBoard"; diff --git a/FrontEnd/src/migrating/center/TimelineBoard.tsx b/FrontEnd/src/migrating/center/TimelineBoard.tsx index b3ccdf8c..8f4401bc 100644 --- a/FrontEnd/src/migrating/center/TimelineBoard.tsx +++ b/FrontEnd/src/migrating/center/TimelineBoard.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import classnames from "classnames"; import { Link } from "react-router-dom"; -import { TimelineBookmark } from "@/http/bookmark"; +import { TimelineBookmark } from "~src/http/bookmark"; import TimelineLogo from "../common/TimelineLogo"; import LoadFailReload from "../common/LoadFailReload"; diff --git a/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx b/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx index 63742936..340a08fe 100644 --- a/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx +++ b/FrontEnd/src/migrating/center/TimelineCreateDialog.tsx @@ -1,11 +1,11 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; -import { validateTimelineName } from "@/services/timeline"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { validateTimelineName } from "~src/services/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; import OperationDialog from "../common/dialog/OperationDialog"; -import { useUserLoggedIn } from "@/services/user"; +import { useUserLoggedIn } from "~src/services/user"; interface TimelineCreateDialogProps { open: boolean; diff --git a/FrontEnd/src/migrating/center/index.tsx b/FrontEnd/src/migrating/center/index.tsx index 77af2c20..11502517 100644 --- a/FrontEnd/src/migrating/center/index.tsx +++ b/FrontEnd/src/migrating/center/index.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; -import { useUserLoggedIn } from "@/services/user"; +import { useUserLoggedIn } from "~src/services/user"; import SearchInput from "../common/SearchInput"; import Button from "../common/button/Button"; diff --git a/FrontEnd/src/pages/about/index.tsx b/FrontEnd/src/pages/about/index.tsx index acec1735..bce64322 100644 --- a/FrontEnd/src/pages/about/index.tsx +++ b/FrontEnd/src/pages/about/index.tsx @@ -1,7 +1,7 @@ import "./index.css"; -import { useC } from "@/common"; -import Page from "@/views/common/Page"; +import { useC } from "~src/common"; +import Page from "~src/components/Page"; interface Credit { name: string; diff --git a/FrontEnd/src/pages/loading/index.tsx b/FrontEnd/src/pages/loading/index.tsx index e4c8edab..29d27adc 100644 --- a/FrontEnd/src/pages/loading/index.tsx +++ b/FrontEnd/src/pages/loading/index.tsx @@ -1,4 +1,4 @@ -import Spinner from "@/views/common/Spinner"; +import Spinner from "~src/components/Spinner"; import "./index.css"; diff --git a/FrontEnd/src/pages/login/index.tsx b/FrontEnd/src/pages/login/index.tsx index a09e32c3..582ebd0f 100644 --- a/FrontEnd/src/pages/login/index.tsx +++ b/FrontEnd/src/pages/login/index.tsx @@ -2,16 +2,16 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; -import { useUser, userService } from "@/services/user"; +import { useUser, userService } from "~src/services/user"; -import { useC } from "@/views/common/common"; -import LoadingButton from "@/views/common/button/LoadingButton"; +import { useC } from "~src/components/common"; +import LoadingButton from "~src/components/button/LoadingButton"; import { InputErrorDict, InputGroup, useInputs, -} from "@/views/common/input/InputGroup"; -import Page from "@/views/common/Page"; +} from "~src/components/input/InputGroup"; +import Page from "~src/components/Page"; import "./index.css"; diff --git a/FrontEnd/src/pages/register/index.tsx b/FrontEnd/src/pages/register/index.tsx index bc474adb..9e478612 100644 --- a/FrontEnd/src/pages/register/index.tsx +++ b/FrontEnd/src/pages/register/index.tsx @@ -2,16 +2,16 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { HttpBadRequestError } from "@/http/common"; -import { getHttpTokenClient } from "@/http/token"; -import { userService, useUser } from "@/services/user"; +import { HttpBadRequestError } from "~src/http/common"; +import { getHttpTokenClient } from "~src/http/token"; +import { userService, useUser } from "~src/services/user"; -import { LoadingButton } from "@/views/common/button"; +import { LoadingButton } from "~src/components/button"; import { useInputs, InputErrorDict, InputGroup, -} from "@/views/common/input/InputGroup"; +} from "~src/components/input/InputGroup"; import "./index.css"; diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx index f35fc5a7..c34bcf4f 100644 --- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -1,19 +1,19 @@ import { useState, ChangeEvent, ComponentPropsWithoutRef } from "react"; -import { useC, Text, UiLogicError } from "@/common"; +import { useC, Text, UiLogicError } from "~src/common"; -import { useUser } from "@/services/user"; +import { useUser } from "~src/services/user"; -import { getHttpUserClient } from "@/http/user"; +import { getHttpUserClient } from "~src/http/user"; import ImageCropper, { Clip, applyClipToImage, -} from "@/views/common/ImageCropper"; -import BlobImage from "@/views/common/BlobImage"; -import ButtonRowV2 from "@/views/common/button/ButtonRowV2"; -import Dialog from "@/views/common/dialog/Dialog"; -import DialogContainer from "@/views/common/dialog/DialogContainer"; +} from "~src/components/ImageCropper"; +import BlobImage from "~src/components/BlobImage"; +import ButtonRowV2 from "~src/components/button/ButtonRowV2"; +import Dialog from "~src/components/dialog/Dialog"; +import DialogContainer from "~src/components/dialog/DialogContainer"; import "./ChangeAvatarDialog.css"; diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx index 4d318543..843659ef 100644 --- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx @@ -1,7 +1,7 @@ -import { getHttpUserClient } from "@/http/user"; -import { useUserLoggedIn } from "@/services/user"; +import { getHttpUserClient } from "~src/http/user"; +import { useUserLoggedIn } from "~src/services/user"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; export interface ChangeNicknameDialogProps { open: boolean; @@ -26,7 +26,7 @@ export default function ChangeNicknameDialog(props: ChangeNicknameDialogProps) { ]} onProcess={({ newNickname }) => { return getHttpUserClient().patch(user.username, { - nickname: newNickname as string, + nickname: newNickname, }); }} onClose={onClose} diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx index 87a970a5..bfcea92d 100644 --- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { userService } from "@/services/user"; +import { userService } from "~src/services/user"; import OperationDialog, { InputErrorDict, -} from "@/views/common/dialog/OperationDialog"; +} from "~src/components/dialog/OperationDialog"; interface ChangePasswordDialogProps { open: boolean; @@ -65,10 +65,7 @@ export function ChangePasswordDialog(props: ChangePasswordDialogProps) { }, }} onProcess={async ({ oldPassword, newPassword }) => { - await userService.changePassword( - oldPassword as string, - newPassword as string, - ); + await userService.changePassword(oldPassword, newPassword); setRedirect(true); }} onSuccessAndClose={() => { diff --git a/FrontEnd/src/pages/setting/index.tsx b/FrontEnd/src/pages/setting/index.tsx index 50967a3c..67416a08 100644 --- a/FrontEnd/src/pages/setting/index.tsx +++ b/FrontEnd/src/pages/setting/index.tsx @@ -8,21 +8,21 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import classNames from "classnames"; -import { useC, Text } from "@/common"; -import { useUser, userService } from "@/services/user"; -import { getHttpUserClient } from "@/http/user"; +import { useC, Text } from "~src/common"; +import { useUser, userService } from "~src/services/user"; +import { getHttpUserClient } from "~src/http/user"; -import { useDialog } from "@/views/common/dialog"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; -import Card from "@/views/common/Card"; -import Spinner from "@/views/common/Spinner"; -import Page from "@/views/common/Page"; +import { useDialog } from "~src/components/dialog"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; +import Card from "~src/components/Card"; +import Spinner from "~src/components/Spinner"; +import Page from "~src/components/Page"; import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; import "./index.css"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; interface SettingSectionProps extends Omit, "title"> { diff --git a/FrontEnd/src/pages/timeline/CollapseButton.tsx b/FrontEnd/src/pages/timeline/CollapseButton.tsx index 14fc6bee..1c4fa2ba 100644 --- a/FrontEnd/src/pages/timeline/CollapseButton.tsx +++ b/FrontEnd/src/pages/timeline/CollapseButton.tsx @@ -1,6 +1,6 @@ import { CSSProperties } from "react"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; export default function CollapseButton({ collapse, diff --git a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx index 9c497108..43e81d67 100644 --- a/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx +++ b/FrontEnd/src/pages/timeline/MarkdownPostEdit.tsx @@ -2,15 +2,15 @@ import * as React from "react"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import TimelinePostBuilder from "@/services/TimelinePostBuilder"; +import TimelinePostBuilder from "~src/services/TimelinePostBuilder"; -import FlatButton from "@/views/common/button/FlatButton"; -import TabPages from "@/views/common/tab/TabPages"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; -import Spinner from "@/views/common/Spinner"; -import IconButton from "@/views/common/button/IconButton"; +import FlatButton from "~src/components/button/FlatButton"; +import TabPages from "~src/components/tab/TabPages"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; +import Spinner from "~src/components/Spinner"; +import IconButton from "~src/components/button/IconButton"; import "./MarkdownPostEdit.css"; diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx index 73e621c1..f266ec9d 100644 --- a/FrontEnd/src/pages/timeline/Timeline.tsx +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -1,20 +1,20 @@ import { useState, useEffect } from "react"; import classnames from "classnames"; -import { useScrollToBottom } from "@/utilities/hooks"; +import { useScrollToBottom } from "~src/utilities/hooks"; import { HubConnectionState } from "@microsoft/signalr"; import { HttpForbiddenError, HttpNetworkError, HttpNotFoundError, -} from "@/http/common"; +} from "~src/http/common"; import { getHttpTimelineClient, HttpTimelineInfo, HttpTimelinePostInfo, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import { getTimelinePostUpdate$ } from "@/services/timeline"; +import { getTimelinePostUpdate$ } from "~src/services/timeline"; import TimelinePostList from "./TimelinePostList"; import TimelinePostEdit from "./TimelinePostCreateView"; diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx index 2987aa74..82d6d350 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx @@ -1,24 +1,24 @@ import { useState } from "react"; import { HubConnectionState } from "@microsoft/signalr"; -import { useUser } from "@/services/user"; -import { pushAlert } from "@/services/alert"; +import { useUser } from "~src/services/user"; +import { pushAlert } from "~src/services/alert"; -import { HttpTimelineInfo } from "@/http/timeline"; -import { getHttpBookmarkClient } from "@/http/bookmark"; +import { HttpTimelineInfo } from "~src/http/timeline"; +import { getHttpBookmarkClient } from "~src/http/bookmark"; -import { useMobile } from "@/views/common/common"; -import { Dialog, useDialog } from "@/views/common/dialog"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import PopupMenu from "@/views/common/menu/PopupMenu"; -import FullPageDialog from "@/views/common/dialog/FullPageDialog"; -import Card from "@/views/common/Card"; +import { useMobile } from "~src/components/common"; +import { Dialog, useDialog } from "~src/components/dialog"; +import UserAvatar from "~src/components/user/UserAvatar"; +import PopupMenu from "~src/components/menu/PopupMenu"; +import FullPageDialog from "~src/components/dialog/FullPageDialog"; +import Card from "~src/components/Card"; import TimelineDeleteDialog from "./TimelineDeleteDialog"; import ConnectionStatusBadge from "./ConnectionStatusBadge"; import CollapseButton from "./CollapseButton"; import TimelineMember from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import "./TimelineCard.css"; diff --git a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx index 7d7b9527..7b7b8e8c 100644 --- a/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelineDeleteDialog.tsx @@ -2,9 +2,9 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; import { Trans } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; interface TimelineDeleteDialog { timeline: HttpTimelineInfo; diff --git a/FrontEnd/src/pages/timeline/TimelineMember.tsx b/FrontEnd/src/pages/timeline/TimelineMember.tsx index 4c1600f5..a25fe6a9 100644 --- a/FrontEnd/src/pages/timeline/TimelineMember.tsx +++ b/FrontEnd/src/pages/timeline/TimelineMember.tsx @@ -1,16 +1,16 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { convertI18nText, I18nText } from "@/common"; +import { convertI18nText, I18nText } from "~src/common"; -import { HttpUser } from "@/http/user"; -import { getHttpSearchClient } from "@/http/search"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; +import { HttpUser } from "~src/http/user"; +import { getHttpSearchClient } from "~src/http/search"; +import { getHttpTimelineClient, HttpTimelineInfo } from "~src/http/timeline"; -import SearchInput from "@/views/common/SearchInput"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import Button from "@/views/common/button/Button"; -import { ListContainer, ListItemContainer } from "@/views/common/list"; +import SearchInput from "~src/components/SearchInput"; +import UserAvatar from "~src/components/user/UserAvatar"; +import Button from "~src/components/button/Button"; +import { ListContainer, ListItemContainer } from "~src/components/list"; import "./TimelineMember.css"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx index 23dd141f..d3fd3215 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx @@ -1,7 +1,7 @@ import { ReactNode } from "react"; import classNames from "classnames"; -import Card from "@/views/common/Card"; +import Card from "~src/components/Card"; import "./TimelinePostCard.css"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx index ad5465c1..6c0d7387 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostContentView.tsx @@ -2,15 +2,15 @@ import * as React from "react"; import classnames from "classnames"; import { marked } from "marked"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import { HttpNetworkError } from "@/http/common"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { HttpNetworkError } from "~src/http/common"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import { useUser } from "@/services/user"; +import { useUser } from "~src/services/user"; -import Skeleton from "@/views/common/Skeleton"; -import LoadFailReload from "@/views/common/LoadFailReload"; +import Skeleton from "~src/components/Skeleton"; +import LoadFailReload from "~src/components/LoadFailReload"; const TextView: React.FC = (props) => { const { post, className, style } = props; diff --git a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx index 572a9119..3c41228a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostCreateView.tsx @@ -1,26 +1,26 @@ import { useState, useEffect, ChangeEventHandler } from "react"; import { useTranslation } from "react-i18next"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; import { getHttpTimelineClient, HttpTimelineInfo, HttpTimelinePostInfo, HttpTimelinePostPostRequestData, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; -import base64 from "@/utilities/base64"; +import base64 from "~src/utilities/base64"; -import BlobImage from "@/views/common/BlobImage"; -import LoadingButton from "@/views/common/button/LoadingButton"; -import PopupMenu from "@/views/common/menu/PopupMenu"; +import BlobImage from "~src/components/BlobImage"; +import LoadingButton from "~src/components/button/LoadingButton"; +import PopupMenu from "~src/components/menu/PopupMenu"; import MarkdownPostEdit from "./MarkdownPostEdit"; import TimelinePostCard from "./TimelinePostCard"; import TimelinePostContainer from "./TimelinePostContainer"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import "./TimelinePostCreateView.css"; import classNames from "classnames"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx index a3501b33..7912260a 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostList.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx @@ -1,6 +1,6 @@ import { useMemo, Fragment } from "react"; -import { HttpTimelinePostInfo } from "@/http/timeline"; +import { HttpTimelinePostInfo } from "~src/http/timeline"; import TimelinePostView from "./TimelinePostView"; import TimelineDateLabel from "./TimelineDateLabel"; diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx index afae5033..2a8c5947 100644 --- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx @@ -1,17 +1,17 @@ import { useState } from "react"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "~src/http/timeline"; -import { pushAlert } from "@/services/alert"; +import { pushAlert } from "~src/services/alert"; -import { useClickOutside } from "@/utilities/hooks"; +import { useClickOutside } from "~src/utilities/hooks"; -import UserAvatar from "@/views/common/user/UserAvatar"; -import { useDialog } from "@/views/common/dialog"; -import FlatButton from "@/views/common/button/FlatButton"; -import ConfirmDialog from "@/views/common/dialog/ConfirmDialog"; +import UserAvatar from "~src/components/user/UserAvatar"; +import { useDialog } from "~src/components/dialog"; +import FlatButton from "~src/components/button/FlatButton"; +import ConfirmDialog from "~src/components/dialog/ConfirmDialog"; import TimelinePostContentView from "./TimelinePostContentView"; -import IconButton from "@/views/common/button/IconButton"; +import IconButton from "~src/components/button/IconButton"; import TimelinePostContainer from "./TimelinePostContainer"; import TimelinePostCard from "./TimelinePostCard"; diff --git a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx index b57135bb..afd83a5f 100644 --- a/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx +++ b/FrontEnd/src/pages/timeline/TimelinePropertyChangeDialog.tsx @@ -6,9 +6,9 @@ import { HttpTimelinePatchRequest, kTimelineVisibilities, TimelineVisibility, -} from "@/http/timeline"; +} from "~src/http/timeline"; -import OperationDialog from "@/views/common/dialog/OperationDialog"; +import OperationDialog from "~src/components/dialog/OperationDialog"; export interface TimelinePropertyChangeDialogProps { open: boolean; @@ -68,13 +68,13 @@ const TimelinePropertyChangeDialog: React.FC< onProcess={({ title, visibility, description }) => { const req: HttpTimelinePatchRequest = {}; if (title !== timeline.title) { - req.title = title as string; + req.title = title; } if (visibility !== timeline.visibility) { - req.visibility = visibility as TimelineVisibility; + req.visibility = visibility; } if (description !== timeline.description) { - req.description = description as string; + req.description = description; } return getHttpTimelineClient() .patchTimeline(timeline.owner.username, timeline.nameV2, req) diff --git a/FrontEnd/src/pages/timeline/index.tsx b/FrontEnd/src/pages/timeline/index.tsx index 51cc37f0..6cd1ded0 100644 --- a/FrontEnd/src/pages/timeline/index.tsx +++ b/FrontEnd/src/pages/timeline/index.tsx @@ -1,6 +1,6 @@ import { useParams } from "react-router-dom"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; import Timeline from "./Timeline"; diff --git a/FrontEnd/src/services/TimelinePostBuilder.ts b/FrontEnd/src/services/TimelinePostBuilder.ts index 0440e136..919d4f55 100644 --- a/FrontEnd/src/services/TimelinePostBuilder.ts +++ b/FrontEnd/src/services/TimelinePostBuilder.ts @@ -1,10 +1,10 @@ import { marked } from "marked"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import base64 from "@/utilities/base64"; +import base64 from "~src/utilities/base64"; -import { HttpTimelinePostPostRequest } from "@/http/timeline"; +import { HttpTimelinePostPostRequest } from "~src/http/timeline"; class TimelinePostMarkedRenderer extends marked.Renderer { constructor(private _images: { file: File; url: string }[]) { diff --git a/FrontEnd/src/services/alert.ts b/FrontEnd/src/services/alert.ts index 2f66dccc..241deab1 100644 --- a/FrontEnd/src/services/alert.ts +++ b/FrontEnd/src/services/alert.ts @@ -1,7 +1,7 @@ import pull from "lodash/pull"; -import { I18nText } from "@/common"; -import { ThemeColor } from "@/views/common/common"; +import { I18nText } from "~src/common"; +import { ThemeColor } from "~src/views/common/common"; export interface AlertInfo { type?: ThemeColor; diff --git a/FrontEnd/src/services/timeline.ts b/FrontEnd/src/services/timeline.ts index 342f803a..41a7bff0 100644 --- a/FrontEnd/src/services/timeline.ts +++ b/FrontEnd/src/services/timeline.ts @@ -13,8 +13,8 @@ import { HubConnectionState, } from "@microsoft/signalr"; -import { TimelineVisibility } from "@/http/timeline"; -import { token$ } from "@/http/common"; +import { TimelineVisibility } from "~src/http/timeline"; +import { token$ } from "~src/http/common"; // cSpell:ignore onreconnected onreconnecting diff --git a/FrontEnd/src/services/user.ts b/FrontEnd/src/services/user.ts index c89ca893..ddba4dab 100644 --- a/FrontEnd/src/services/user.ts +++ b/FrontEnd/src/services/user.ts @@ -2,11 +2,11 @@ import { useState, useEffect } from "react"; import { BehaviorSubject, Observable } from "rxjs"; import { AxiosError } from "axios"; -import { UiLogicError } from "@/common"; +import { UiLogicError } from "~src/common"; -import { setHttpToken, axios, HttpBadRequestError } from "@/http/common"; -import { getHttpTokenClient } from "@/http/token"; -import { getHttpUserClient, HttpUser, UserPermission } from "@/http/user"; +import { setHttpToken, axios, HttpBadRequestError } from "~src/http/common"; +import { getHttpTokenClient } from "~src/http/token"; +import { getHttpUserClient, HttpUser, UserPermission } from "~src/http/user"; import { pushAlert } from "./alert"; diff --git a/FrontEnd/src/views/common/AppBar.css b/FrontEnd/src/views/common/AppBar.css deleted file mode 100644 index a0d975b5..00000000 --- a/FrontEnd/src/views/common/AppBar.css +++ /dev/null @@ -1,87 +0,0 @@ -.app-bar { - height: 56px; - position: fixed; - z-index: 1030; - top: 0; - left: 0; - right: 0; - background-color: var(--cru-primary-color); -} - -.app-bar { - display: flex; -} - -.app-bar .app-bar-brand { - display: flex; - align-items: center; -} - -.app-bar .app-bar-brand-icon { - height: 2em; -} - -.app-bar .app-bar-user-area { - display: flex; - margin-left: auto; -} - -.app-bar a { - background-color: var(--cru-primary-color); - color: var(--cru-push-button-text-color); - text-decoration: none; - display: flex; - align-items: center; - padding: 0 1em; - transition: all 0.5s; -} - -.app-bar a:hover { - background-color: var(--cru-clickable-primary-hover-color); -} - -.app-bar a:focus { - background-color: var(--cru-clickable-primary-focus-color); -} - -.app-bar a:active { - background-color: var(--cru-clickable-primary-active-color); -} - -/* the current page */ -.app-bar a.active { - background-color: var(--cru-clickable-primary-focus-color); -} - -.app-bar .app-bar-avatar img { - width: 45px; - height: 45px; - background-color: white; - border-radius: 50%; -} - -.app-bar.desktop .app-bar-link-area { - display: flex; -} - -.app-bar.mobile .app-bar-link-area { - position: absolute; - z-index: -1; - top: 56px; - left: 0; - right: 0; - transition: transform 0.5s; -} - -.app-bar.mobile a { - height: 56px; -} - -.app-bar.mobile.collapse .app-bar-link-area { - transform: translateY(-100%); -} - -.app-bar .toggler { - font-size: 2em; - margin-right: 0.5em; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx deleted file mode 100644 index b9ea825b..00000000 --- a/FrontEnd/src/views/common/AppBar.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useState } from "react"; -import classnames from "classnames"; -import { Link, NavLink } from "react-router-dom"; - -import { I18nText, useC, useMobile } from "./common"; -import { useUser } from "@/services/user"; - -import TimelineLogo from "./TimelineLogo"; -import { IconButton } from "./button"; -import UserAvatar from "./user/UserAvatar"; - -import "./AppBar.css"; - -function AppBarNavLink({ - link, - className, - label, - onClick, - children, -}: { - link: string; - className?: string; - label?: I18nText; - onClick?: () => void; - children?: React.ReactNode; -}) { - if (label != null && children != null) { - throw new Error("AppBarNavLink: label and children cannot be both set"); - } - - const c = useC(); - - return ( - classnames(className, isActive && "active")} - onClick={onClick} - > - {children != null ? children : c(label)} - - ); -} - -export default function AppBar() { - const isMobile = useMobile(); - - const [isCollapse, setIsCollapse] = useState(true); - const collapse = isMobile ? () => setIsCollapse(true) : undefined; - const toggleCollapse = () => setIsCollapse(!isCollapse); - - const user = useUser(); - const hasAdministrationPermission = user && user.hasAdministrationPermission; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/BlobImage.tsx b/FrontEnd/src/views/common/BlobImage.tsx deleted file mode 100644 index 259c2210..00000000 --- a/FrontEnd/src/views/common/BlobImage.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ComponentPropsWithoutRef, useState, useEffect } from "react"; - -type BlobImageProps = Omit, "src"> & { - imgRef?: React.Ref; - src?: Blob | string | null; -}; - -export default function BlobImage(props: BlobImageProps) { - const { imgRef, src, ...otherProps } = props; - - const [url, setUrl] = useState(undefined); - - useEffect(() => { - if (src instanceof Blob) { - const url = URL.createObjectURL(src); - setUrl(url); - return () => { - URL.revokeObjectURL(url); - }; - } else { - setUrl(src); - } - }, [src]); - - return ; -} diff --git a/FrontEnd/src/views/common/Card.css b/FrontEnd/src/views/common/Card.css deleted file mode 100644 index 6d655eb9..00000000 --- a/FrontEnd/src/views/common/Card.css +++ /dev/null @@ -1,20 +0,0 @@ -.cru-card { - border-radius: var(--cru-card-border-radius); - transition: all 0.3s; -} - -.cru-card-background-none { - background-color: transparent; -} - -.cru-card-background-solid { - background-color: var(--cru-background-color); -} - -.cru-card-background-grayscale { - background-color: var(--cru-container-background-color); -} - -.cru-card-border-color { - border: 2px solid var(--cru-card-border-color); -} diff --git a/FrontEnd/src/views/common/Card.tsx b/FrontEnd/src/views/common/Card.tsx deleted file mode 100644 index a8f0d3cc..00000000 --- a/FrontEnd/src/views/common/Card.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "./common"; -import "./Card.css"; - -interface CardProps extends ComponentPropsWithoutRef<"div"> { - containerRef?: Ref; - color?: ThemeColor; - border?: "color" | "none"; - background?: "color" | "solid" | "grayscale" | "none"; -} - -export default function Card({ - color, - background, - border, - className, - children, - containerRef, - ...otherProps -}: CardProps) { - return ( -
    - {children} -
    - ); -} diff --git a/FrontEnd/src/views/common/Icon.css b/FrontEnd/src/views/common/Icon.css deleted file mode 100644 index fe980d7b..00000000 --- a/FrontEnd/src/views/common/Icon.css +++ /dev/null @@ -1,3 +0,0 @@ -.cru-icon { - font-size: 1.4rem; -} diff --git a/FrontEnd/src/views/common/Icon.tsx b/FrontEnd/src/views/common/Icon.tsx deleted file mode 100644 index 2ac3a7ca..00000000 --- a/FrontEnd/src/views/common/Icon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ComponentPropsWithoutRef } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "./common"; - -import "./Icon.css"; - -interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { - icon: string; - color?: ThemeColor | "on-surface"; - size?: string | number; -} - -export default function Icon(props: IconButtonProps) { - const { icon, color, size, style, className, ...otherProps } = props; - - const colorName = color === "on-surface" ? "surface-on" : color; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/ImageCropper.css b/FrontEnd/src/views/common/ImageCropper.css deleted file mode 100644 index 2c4d0a8c..00000000 --- a/FrontEnd/src/views/common/ImageCropper.css +++ /dev/null @@ -1,38 +0,0 @@ -.image-cropper-container { - position: relative; - box-sizing: border-box; - user-select: none; -} - -.image-cropper-container img { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; -} - -.image-cropper-mask-container { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - overflow: hidden; -} - -.image-cropper-mask { - position: absolute; - box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8); - touch-action: none; -} - -.image-cropper-handler { - position: absolute; - width: 26px; - height: 26px; - border: black solid 2px; - border-radius: 50%; - background: white; - touch-action: none; -} diff --git a/FrontEnd/src/views/common/ImageCropper.tsx b/FrontEnd/src/views/common/ImageCropper.tsx deleted file mode 100644 index fcab74b0..00000000 --- a/FrontEnd/src/views/common/ImageCropper.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; - -import { UiLogicError } from "@/common"; - -import "./ImageCropper.css"; -import BlobImage from "./BlobImage"; - -export interface Clip { - left: number; - top: number; - width: number; -} - -interface NormailizedClip extends Clip { - height: number; -} - -interface ImageInfo { - width: number; - height: number; - landscape: boolean; - ratio: number; - maxClipWidth: number; - maxClipHeight: number; -} - -interface ImageCropperSavedState { - clip: NormailizedClip; - x: number; - y: number; - pointerId: number; -} - -export interface ImageCropperProps { - clip: Clip | null; - image: string | Blob; - onChange: (clip: Clip) => void; - imageElementCallback?: (element: HTMLImageElement | null) => void; - className?: string; -} - -const ImageCropper = (props: ImageCropperProps): React.ReactElement => { - const { clip, image, onChange, imageElementCallback, className } = props; - - const [oldState, setOldState] = React.useState( - null, - ); - const [imageInfo, setImageInfo] = React.useState(null); - - const normalizeClip = (c: Clip | null | undefined): NormailizedClip => { - if (c == null) { - return { left: 0, top: 0, width: 0, height: 0 }; - } - - return { - left: c.left || 0, - top: c.top || 0, - width: c.width || 0, - height: imageInfo != null ? (c.width || 0) / imageInfo.ratio : 0, - }; - }; - - const c = normalizeClip(clip); - - const imgElementRef = React.useRef(null); - - const onImageRef = React.useCallback( - (e: HTMLImageElement | null) => { - imgElementRef.current = e; - if (imageElementCallback != null && e == null) { - imageElementCallback(null); - } - }, - [imageElementCallback], - ); - - const onImageLoad = React.useCallback( - (e: React.SyntheticEvent) => { - const img = e.currentTarget; - const landscape = img.naturalWidth >= img.naturalHeight; - - const info = { - width: img.naturalWidth, - height: img.naturalHeight, - landscape, - ratio: img.naturalHeight / img.naturalWidth, - maxClipWidth: landscape ? img.naturalHeight / img.naturalWidth : 1, - maxClipHeight: landscape ? 1 : img.naturalWidth / img.naturalHeight, - }; - setImageInfo(info); - onChange({ left: 0, top: 0, width: info.maxClipWidth }); - if (imageElementCallback != null) { - imageElementCallback(img); - } - }, - [onChange, imageElementCallback], - ); - - const onPointerDown = React.useCallback( - (e: React.PointerEvent) => { - if (oldState != null) return; - e.currentTarget.setPointerCapture(e.pointerId); - setOldState({ - x: e.clientX, - y: e.clientY, - clip: c, - pointerId: e.pointerId, - }); - }, - [oldState, c], - ); - - const onPointerUp = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null || oldState.pointerId !== e.pointerId) return; - e.currentTarget.releasePointerCapture(e.pointerId); - setOldState(null); - }, - [oldState], - ); - - const onPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.y / imgElement.height, - }; - - const newRatio = { - x: oldClip.left + moveRatio.x, - y: oldClip.top + moveRatio.y, - }; - if (newRatio.x < 0) { - newRatio.x = 0; - } else if (newRatio.x > 1 - oldClip.width) { - newRatio.x = 1 - oldClip.width; - } - if (newRatio.y < 0) { - newRatio.y = 0; - } else if (newRatio.y > 1 - oldClip.height) { - newRatio.y = 1 - oldClip.height; - } - - onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width }); - }, - [oldState, onChange], - ); - - const onHandlerPointerMove = React.useCallback( - (e: React.PointerEvent) => { - if (oldState == null) return; - - const oldClip = oldState.clip; - - const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; - - const ratio = imageInfo == null ? 1 : imageInfo.ratio; - - const { current: imgElement } = imgElementRef; - - if (imgElement == null) throw new UiLogicError("Image element is null."); - - const moveRatio = { - x: movement.x / imgElement.width, - y: movement.x / imgElement.width / ratio, - }; - - const newRatio = { - x: oldClip.width + moveRatio.x, - y: oldClip.height + moveRatio.y, - }; - - const maxRatio = { - x: Math.min(1 - oldClip.left, newRatio.x), - y: Math.min(1 - oldClip.top, newRatio.y), - }; - - const maxWidthRatio = Math.min(maxRatio.x, maxRatio.y * ratio); - - let newWidth; - if (newRatio.x < 0) { - newWidth = 0; - } else if (newRatio.x > maxWidthRatio) { - newWidth = maxWidthRatio; - } else { - newWidth = newRatio.x; - } - - onChange({ left: oldClip.left, top: oldClip.top, width: newWidth }); - }, - [imageInfo, oldState, onChange], - ); - - const toPercentage = (n: number): string => `${n}%`; - - // fuck!!! I just can't find a better way to implement this in pure css - const containerStyle: React.CSSProperties = (() => { - if (imageInfo == null) { - return { width: "100%", paddingTop: "100%", height: 0 }; - } else { - if (imageInfo.ratio > 1) { - return { - width: toPercentage(100 / imageInfo.ratio), - paddingTop: "100%", - height: 0, - }; - } else { - return { - width: "100%", - paddingTop: toPercentage(100 * imageInfo.ratio), - height: 0, - }; - } - } - })(); - - return ( -
    - -
    -
    -
    -
    -
    - ); -}; - -export default ImageCropper; - -export function applyClipToImage( - image: HTMLImageElement, - clip: Clip, - mimeType: string, -): Promise { - return new Promise((resolve, reject) => { - const naturalSize = { - width: image.naturalWidth, - height: image.naturalHeight, - }; - const clipArea = { - x: naturalSize.width * clip.left, - y: naturalSize.height * clip.top, - length: naturalSize.width * clip.width, - }; - - const canvas = document.createElement("canvas"); - canvas.width = clipArea.length; - canvas.height = clipArea.length; - const context = canvas.getContext("2d"); - - if (context == null) throw new Error("Failed to create context."); - - context.drawImage( - image, - clipArea.x, - clipArea.y, - clipArea.length, - clipArea.length, - 0, - 0, - clipArea.length, - clipArea.length, - ); - - canvas.toBlob((blob) => { - if (blob == null) { - reject(new Error("canvas.toBlob returns null")); - } else { - resolve(blob); - } - }, mimeType); - }); -} diff --git a/FrontEnd/src/views/common/LoadFailReload.tsx b/FrontEnd/src/views/common/LoadFailReload.tsx deleted file mode 100644 index 81ba1f67..00000000 --- a/FrontEnd/src/views/common/LoadFailReload.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from "react"; -import { Trans } from "react-i18next"; - -export interface LoadFailReloadProps { - className?: string; - style?: React.CSSProperties; - onReload: () => void; -} - -const LoadFailReload: React.FC = ({ - onReload, - className, - style, -}) => { - return ( - - 0 - { - onReload(); - e.preventDefault(); - }} - > - 1 - - 2 - - ); -}; - -export default LoadFailReload; diff --git a/FrontEnd/src/views/common/LoadingPage.tsx b/FrontEnd/src/views/common/LoadingPage.tsx deleted file mode 100644 index 35ee1aa8..00000000 --- a/FrontEnd/src/views/common/LoadingPage.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from "react"; - -import Spinner from "./Spinner"; - -const LoadingPage: React.FC = () => { - return ( -
    - -
    - ); -}; - -export default LoadingPage; diff --git a/FrontEnd/src/views/common/Page.tsx b/FrontEnd/src/views/common/Page.tsx deleted file mode 100644 index 86fdb2f5..00000000 --- a/FrontEnd/src/views/common/Page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -interface PageProps extends ComponentPropsWithoutRef<"div"> { - noTopPadding?: boolean; - pageRef?: Ref; -} - -export default function Page({ noTopPadding, pageRef, className, children }: PageProps) { - return ( -
    - {children} -
    - ); -} diff --git a/FrontEnd/src/views/common/SearchInput.css b/FrontEnd/src/views/common/SearchInput.css deleted file mode 100644 index f0503016..00000000 --- a/FrontEnd/src/views/common/SearchInput.css +++ /dev/null @@ -1,8 +0,0 @@ -.cru-search-input { - display: flex; - flex-wrap: wrap; -} - -.cru-search-input-input { - width: 100%; -} diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx deleted file mode 100644 index e3216b86..00000000 --- a/FrontEnd/src/views/common/SearchInput.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import classNames from "classnames"; - -import { useC, Text } from "./common"; -import LoadingButton from "./button/LoadingButton"; - -import "./SearchInput.css"; - -interface SearchInputProps { - value: string; - onChange: (value: string) => void; - onButtonClick: () => void; - loading?: boolean; - className?: string; - buttonText?: Text; -} - -export default function SearchInput({ - value, - onChange, - onButtonClick, - loading, - className, - buttonText, -}: SearchInputProps) { - const c = useC(); - - return ( -
    - { - const { value } = event.currentTarget; - onChange(value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - onButtonClick(); - event.preventDefault(); - } - }} - /> - - - {c(buttonText ?? "search")} - -
    - ); -} diff --git a/FrontEnd/src/views/common/Skeleton.css b/FrontEnd/src/views/common/Skeleton.css deleted file mode 100644 index a571eead..00000000 --- a/FrontEnd/src/views/common/Skeleton.css +++ /dev/null @@ -1,14 +0,0 @@ -.cru-skeleton { - padding: 0 1em; -} - -.cru-skeleton-line { - height: 1em; - background-color: hsl(0, 0%, 90%); - margin: 0.7em 0; - border-radius: 0.2em; -} - -.cru-skeleton-line.last { - width: 50%; -} diff --git a/FrontEnd/src/views/common/Skeleton.tsx b/FrontEnd/src/views/common/Skeleton.tsx deleted file mode 100644 index 3b149db9..00000000 --- a/FrontEnd/src/views/common/Skeleton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import classnames from "classnames"; -import range from "lodash/range"; - -import "./Skeleton.css"; - -export interface SkeletonProps { - lineNumber?: number; - className?: string; - style?: React.CSSProperties; -} - -const Skeleton: React.FC = (props) => { - const { lineNumber: lineNumberProps, className, style } = props; - const lineNumber = lineNumberProps ?? 3; - - return ( -
    - {range(lineNumber).map((i) => ( -
    - ))} -
    - ); -}; - -export default Skeleton; diff --git a/FrontEnd/src/views/common/Spinner.css b/FrontEnd/src/views/common/Spinner.css deleted file mode 100644 index a1de68d2..00000000 --- a/FrontEnd/src/views/common/Spinner.css +++ /dev/null @@ -1,13 +0,0 @@ -@keyframes cru-spinner-animation { - from { - transform: scale(0,0); - } -} - -.cru-spinner { - display: inline-block; - animation: cru-spinner-animation 0.5s infinite alternate; - background-color: currentColor; - border-radius: 50%; - transform-origin: center; -} diff --git a/FrontEnd/src/views/common/Spinner.tsx b/FrontEnd/src/views/common/Spinner.tsx deleted file mode 100644 index ec0c2c35..00000000 --- a/FrontEnd/src/views/common/Spinner.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { CSSProperties } from "react"; -import classnames from "classnames"; - -import { ThemeColor } from "./common"; - -import "./Spinner.css"; - -export interface SpinnerProps { - size?: "sm" | "md" | "lg" | number | string; - color?: ThemeColor; - className?: string; - style?: CSSProperties; -} - -export default function Spinner(props: SpinnerProps) { - const { size, color, className, style } = props; - const calculatedSize = - size === "sm" - ? "18px" - : size === "md" - ? "30px" - : size === "lg" - ? "42px" - : typeof size === "number" - ? size - : size == null - ? "20px" - : size; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/TimelineLogo.tsx b/FrontEnd/src/views/common/TimelineLogo.tsx deleted file mode 100644 index e06ed0f5..00000000 --- a/FrontEnd/src/views/common/TimelineLogo.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { SVGAttributes } from "react"; -import * as React from "react"; - -export interface TimelineLogoProps extends SVGAttributes { - color?: string; -} - -const TimelineLogo: React.FC = (props) => { - const { color, ...forwardProps } = props; - const coercedColor = color ?? "currentcolor"; - return ( - - - - - - ); -}; - -export default TimelineLogo; diff --git a/FrontEnd/src/views/common/alert/AlertHost.tsx b/FrontEnd/src/views/common/alert/AlertHost.tsx deleted file mode 100644 index 42074781..00000000 --- a/FrontEnd/src/views/common/alert/AlertHost.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as React from "react"; -import without from "lodash/without"; -import { useTranslation } from "react-i18next"; -import classNames from "classnames"; - -import { alertService, AlertInfoEx, AlertInfo } from "@/services/alert"; -import { convertI18nText } from "@/common"; - -import IconButton from "../button/IconButton"; - -import "./alert.css"; - -interface AutoCloseAlertProps { - alert: AlertInfo; - close: () => void; -} - -export const AutoCloseAlert: React.FC = (props) => { - const { alert, close } = props; - const { dismissTime } = alert; - - const { t } = useTranslation(); - - const timerTag = React.useRef(null); - const closeHandler = React.useRef<(() => void) | null>(null); - - React.useEffect(() => { - closeHandler.current = close; - }, [close]); - - React.useEffect(() => { - const tag = - dismissTime === "never" - ? null - : typeof dismissTime === "number" - ? window.setTimeout(() => closeHandler.current?.(), dismissTime) - : window.setTimeout(() => closeHandler.current?.(), 5000); - timerTag.current = tag; - return () => { - if (tag != null) { - window.clearTimeout(tag); - } - }; - }, [dismissTime]); - - const cancelTimer = (): void => { - const { current: tag } = timerTag; - if (tag != null) { - window.clearTimeout(tag); - } - }; - - return ( -
    -
    - {(() => { - const { message, customMessage } = alert; - if (customMessage != null) { - return customMessage; - } else { - return convertI18nText(message, t); - } - })()} -
    -
    - -
    -
    - ); -}; - -const AlertHost: React.FC = () => { - const [alerts, setAlerts] = React.useState([]); - - React.useEffect(() => { - const consume = (alert: AlertInfoEx): void => { - setAlerts((old) => [...old, alert]); - }; - - alertService.registerConsumer(consume); - return () => { - alertService.unregisterConsumer(consume); - }; - }, []); - - return ( -
    - {alerts.map((alert) => { - return ( - { - setAlerts((old) => without(old, alert)); - }} - /> - ); - })} -
    - ); -}; - -export default AlertHost; diff --git a/FrontEnd/src/views/common/alert/alert.css b/FrontEnd/src/views/common/alert/alert.css deleted file mode 100644 index 54c2b87f..00000000 --- a/FrontEnd/src/views/common/alert/alert.css +++ /dev/null @@ -1,33 +0,0 @@ -.alert-container { - position: fixed; - z-index: 1040; -} - -.cru-alert { - border-radius: 5px; - border: var(--cru-key-color) 1px solid; - color: var(--cru-key-t-color); - background-color: var(--cru-key-b1-color); - - display: flex; - overflow: hidden; -} - -.cru-alert-content { - padding: 0.5em 2em; -} - -.cru-alert-close-button-container { - flex-shrink: 0; - margin-left: auto; - width: 2em; - text-align: center; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--cru-key-t-color); -} - -.cru-alert-close-button { - color: var(--cru-key-color); -} diff --git a/FrontEnd/src/views/common/breakpoints.ts b/FrontEnd/src/views/common/breakpoints.ts deleted file mode 100644 index fb281610..00000000 --- a/FrontEnd/src/views/common/breakpoints.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const breakpoints = { - sm: 576, -} as const; diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css deleted file mode 100644 index 1da70f0e..00000000 --- a/FrontEnd/src/views/common/button/Button.css +++ /dev/null @@ -1,64 +0,0 @@ -.cru-button { - font-size: 1rem; - padding: 0.4em 0.8em; - transition: all 0.3s; - border-radius: 0.2em; - border: 1px solid; - cursor: pointer; -} - -.cru-button:not(.outline) { - color: var(--cru-push-button-text-color); - background-color: var(--cru-clickable-normal-color); - border-color: var(--cru-clickable-normal-color); -} - -.cru-button:not(.outline):hover { - background-color: var(--cru-clickable-hover-color); - border-color: var(--cru-clickable-hover-color); -} - -.cru-button:not(.outline):focus { - background-color: var(--cru-clickable-focus-color); - border-color: var(--cru-clickable-focus-color); -} - -.cru-button:not(.outline):active { - background-color: var(--cru-clickable-active-color); - border-color: var(--cru-clickable-active-color); -} - -.cru-button:not(.outline):disabled { - color: var(--cru-push-button-disabled-text-color); - background-color: var(--cru-push-button-disabled-color); - border-color: var(--cru-push-button-disabled-color); - cursor: auto; -} - - -.cru-button.outline { - color: var(--cru-clickable-normal-color); - border-color: var(--cru-clickable-normal-color); - background-color: transparent; -} - -.cru-button.outline:hover { - color: var(--cru-clickable-hover-color); - border-color: var(--cru-clickable-hover-color); -} - -.cru-button.outline:focus { - color: var(--cru-clickable-focus-color); - border-color: var(--cru-clickable-focus-color); -} - -.cru-button.outline:active { - color: var(--cru-clickable-active-color); - border-color: var(--cru-clickable-active-color); -} - -.cru-button.outline:disabled { - color: var(--cru-clickable-disabled-color); - border-color: var(--cru-clickable-disabled-color); - cursor: auto; -} diff --git a/FrontEnd/src/views/common/button/Button.tsx b/FrontEnd/src/views/common/button/Button.tsx deleted file mode 100644 index 6c38e130..00000000 --- a/FrontEnd/src/views/common/button/Button.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { Text, useC, ThemeColor } from "../common"; - -import "./Button.css"; - -interface ButtonProps extends ComponentPropsWithoutRef<"button"> { - color?: ThemeColor; - text?: Text; - outline?: boolean; - buttonRef?: Ref | null; -} - -export default function Button(props: ButtonProps) { - const { - buttonRef, - color, - text, - outline, - className, - children, - ...otherProps - } = props; - - if (text != null && children != null) { - console.warn("You can't set both text and children props."); - } - - const c = useC(); - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/ButtonRow.css b/FrontEnd/src/views/common/button/ButtonRow.css deleted file mode 100644 index e69de29b..00000000 diff --git a/FrontEnd/src/views/common/button/ButtonRow.tsx b/FrontEnd/src/views/common/button/ButtonRow.tsx deleted file mode 100644 index eea60cc4..00000000 --- a/FrontEnd/src/views/common/button/ButtonRow.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; - -import "./ButtonRow.css"; - -type ButtonRowButton = ( - | { - type: "normal"; - props: ComponentPropsWithoutRef; - } - | { - type: "flat"; - props: ComponentPropsWithoutRef; - } - | { - type: "icon"; - props: ComponentPropsWithoutRef; - } - | { type: "loading"; props: ComponentPropsWithoutRef } -) & { key: string | number }; - -interface ButtonRowProps { - className?: string; - containerRef?: Ref; - buttons: ButtonRowButton[]; - buttonsClassName?: string; -} - -export default function ButtonRow({ - className, - containerRef, - buttons, - buttonsClassName, -}: ButtonRowProps) { - return ( -
    - {buttons.map((button) => { - const { type, key, props } = button; - const newClassName = classNames(props.className, buttonsClassName); - switch (type) { - case "normal": - return
    - ); -} diff --git a/FrontEnd/src/views/common/button/ButtonRowV2.tsx b/FrontEnd/src/views/common/button/ButtonRowV2.tsx deleted file mode 100644 index 3467ad52..00000000 --- a/FrontEnd/src/views/common/button/ButtonRowV2.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; - -import "./ButtonRow.css"; -import { Text, ThemeColor } from "../common"; - -interface ButtonRowV2ButtonBase { - key: string | number; - action?: "primary" | "secondary"; - color?: ThemeColor; - disabled?: boolean; - onClick?: () => void; -} - -interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase { - type?: undefined | null; - text: Text; - outline?: boolean; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase { - type: "normal"; - text: Text; - outline?: boolean; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase { - type: "flat"; - text: Text; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase { - type: "icon"; - icon: string; - props?: ComponentPropsWithoutRef; -} - -interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase { - type: "loading"; - text: Text; - loading?: boolean; - props?: ComponentPropsWithoutRef; -} - -type ButtonRowV2Button = - | ButtonRowV2ButtonWithNoType - | ButtonRowV2NormalButton - | ButtonRowV2FlatButton - | ButtonRowV2IconButton - | ButtonRowV2LoadingButton; - -interface ButtonRowV2Props { - className?: string; - containerRef?: Ref; - buttons: ButtonRowV2Button[]; - buttonsClassName?: string; -} - -export default function ButtonRowV2({ - className, - containerRef, - buttons, - buttonsClassName, -}: ButtonRowV2Props) { - return ( -
    - {buttons.map((button) => { - const { key, action, color, disabled, onClick } = button; - - const realAction = action ?? "primary"; - const realColor = - color ?? (realAction === "primary" ? "primary" : "secondary"); - - const commonProps = { key, color: realColor, disabled, onClick }; - const newClassName = classNames( - button.props?.className, - buttonsClassName, - ); - - switch (button.type) { - case null: - case undefined: - case "normal": { - const { text, outline, props } = button; - return ( -
    - ); -} diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css deleted file mode 100644 index 2050946c..00000000 --- a/FrontEnd/src/views/common/button/FlatButton.css +++ /dev/null @@ -1,27 +0,0 @@ -.cru-flat-button { - font-size: 1rem; - padding: 0.4em 0.8em; - transition: all 0.5s; - border-radius: 0.2em; - background-color: var(--cru-clickable-grayscale-normal-color); - border: 1px none; - color: var(--cru-clickable-normal-color); - cursor: pointer; -} - -.cru-flat-button:hover { - background-color: var(--cru-clickable-grayscale-hover-color); -} - -.cru-flat-button:focus { - background-color: var(--cru-clickable-grayscale-focus-color); -} - -.cru-flat-button:active { - background-color: var(--cru-clickable-grayscale-active-color); -} - -.cru-flat-button:disabled { - color: var(--cru-clickable-disabled-color); - cursor: auto; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx deleted file mode 100644 index 9f074dd6..00000000 --- a/FrontEnd/src/views/common/button/FlatButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { ComponentPropsWithoutRef, Ref } from "react"; -import classNames from "classnames"; - -import { Text, useC, ThemeColor } from "../common"; - -import "./FlatButton.css"; - -interface FlatButtonProps extends ComponentPropsWithoutRef<"button"> { - color?: ThemeColor; - text?: Text; - buttonRef?: Ref | null; -} - -export default function FlatButton(props: FlatButtonProps) { - const { color, text, className, children, buttonRef, ...otherProps } = props; - - if (text != null && children != null) { - console.warn("You can't set both text and children props."); - } - - const c = useC(); - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/IconButton.css b/FrontEnd/src/views/common/button/IconButton.css deleted file mode 100644 index a3747201..00000000 --- a/FrontEnd/src/views/common/button/IconButton.css +++ /dev/null @@ -1,30 +0,0 @@ -.cru-icon-button { - color: var(--cru-clickable-normal-color); - font-size: 1.4rem; - background: none; - border: none; - transition: all 0.5s; - cursor: pointer; - user-select: none; -} - -.cru-icon-button:hover { - color: var(--cru-clickable-hover-color); -} - -.cru-icon-button:focus { - color: var(--cru-clickable-focus-color); -} - -.cru-icon-button:active { - color: var(--cru-clickable-active-color); -} - -.cru-flat-button:disabled { - color: var(--cru-clickable-disabled-color); - cursor: auto; -} - -.cru-icon-button.large { - font-size: 1.6rem; -} diff --git a/FrontEnd/src/views/common/button/IconButton.tsx b/FrontEnd/src/views/common/button/IconButton.tsx deleted file mode 100644 index 95c58887..00000000 --- a/FrontEnd/src/views/common/button/IconButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ComponentPropsWithoutRef } from "react"; -import classNames from "classnames"; - -import { ThemeColor } from "../common"; - -import "./IconButton.css"; - -interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { - icon: string; - color?: ThemeColor | "grayscale"; - large?: boolean; - disabled?: boolean; // TODO: Not implemented -} - -export default function IconButton(props: IconButtonProps) { - const { icon, color, className, large, ...otherProps } = props; - - return ( - - ); -} diff --git a/FrontEnd/src/views/common/button/index.tsx b/FrontEnd/src/views/common/button/index.tsx deleted file mode 100644 index b5aa5470..00000000 --- a/FrontEnd/src/views/common/button/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Button from "./Button"; -import FlatButton from "./FlatButton"; -import IconButton from "./IconButton"; -import LoadingButton from "./LoadingButton"; -import ButtonRow from "./ButtonRow"; -import ButtonRowV2 from "./ButtonRowV2"; - -export { - Button, - FlatButton, - IconButton, - LoadingButton, - ButtonRow, - ButtonRowV2, -}; diff --git a/FrontEnd/src/views/common/common.ts b/FrontEnd/src/views/common/common.ts deleted file mode 100644 index 7af2643b..00000000 --- a/FrontEnd/src/views/common/common.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type { Text, I18nText } from "@/common"; -export { c, convertI18nText, useC } from "@/common"; - -export const themeColors = [ - "primary", - "secondary", - "danger", - "create", -] as const; - -export type ThemeColor = (typeof themeColors)[number]; - -export { breakpoints } from "./breakpoints"; -export { useMobile } from "./hooks"; diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.css b/FrontEnd/src/views/common/dialog/ConfirmDialog.css deleted file mode 100644 index e69de29b..00000000 diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx deleted file mode 100644 index dbbd15c6..00000000 --- a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useC, Text, ThemeColor } from "@/views/common/common"; - -import Dialog from "./Dialog"; -import DialogContainer from "./DialogContainer"; - -export default function ConfirmDialog({ - open, - onClose, - onConfirm, - title, - body, - color, - bodyColor, -}: { - open: boolean; - onClose: () => void; - onConfirm: () => void; - title: Text; - body: Text; - color?: ThemeColor; - bodyColor?: ThemeColor; -}) { - const c = useC(); - - return ( - - { - onConfirm(); - onClose(); - }, - }, - }, - ]} - > -
    {c(body)}
    -
    -
    - ); -} diff --git a/FrontEnd/src/views/common/dialog/Dialog.css b/FrontEnd/src/views/common/dialog/Dialog.css deleted file mode 100644 index e4c61440..00000000 --- a/FrontEnd/src/views/common/dialog/Dialog.css +++ /dev/null @@ -1,60 +0,0 @@ -.cru-dialog-overlay { - position: fixed; - z-index: 1040; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - overflow: auto; -} - -.cru-dialog-background { - position: absolute; - z-index: -1; - left: 0; - right: 0; - top: 0; - bottom: 0; - background-color: var(--cru-surface-dim-color); - opacity: 0.8; -} - -.cru-dialog-container { - max-width: 100%; - min-width: 30vw; - - margin: 2em auto; - - border: var(--cru-key-container-color) 1px solid; - border-radius: 5px; - padding: 1.5em; - background-color: var(--cru-surface-color); -} - -@media (min-width: 576px) { - .cru-dialog-container { - max-width: 800px; - } -} - -.cru-dialog-enter .cru-dialog-container { - transform: scale(0, 0); - opacity: 0; - transform-origin: center; -} - -.cru-dialog-enter-active .cru-dialog-container { - transform: scale(1, 1); - opacity: 1; - transition: transform 0.3s, opacity 0.3s; - transform-origin: center; -} - -.cru-dialog-exit-active .cru-dialog-container { - transition: transform 0.3s, opacity 0.3s; - transform: scale(0, 0); - opacity: 0; - transform-origin: center; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/dialog/Dialog.tsx b/FrontEnd/src/views/common/dialog/Dialog.tsx deleted file mode 100644 index 2ff7bea8..00000000 --- a/FrontEnd/src/views/common/dialog/Dialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { ReactNode, useRef } from "react"; -import ReactDOM from "react-dom"; -import { CSSTransition } from "react-transition-group"; -import classNames from "classnames"; - -import { ThemeColor } from "../common"; - -import "./Dialog.css"; - -const optionalPortalElement = document.getElementById("portal"); -if (optionalPortalElement == null) { - throw new Error("Portal element not found"); -} -const portalElement = optionalPortalElement; - -interface DialogProps { - open: boolean; - onClose: () => void; - color?: ThemeColor; - children?: ReactNode; - disableCloseOnClickOnOverlay?: boolean; -} - -export default function Dialog({ - open, - onClose, - color, - children, - disableCloseOnClickOnOverlay, -}: DialogProps) { - color = color ?? "primary"; - - const nodeRef = useRef(null); - - return ReactDOM.createPortal( - -
    -
    { - onClose(); - } - } - /> -
    {children}
    -
    - , - portalElement, - ); -} diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.css b/FrontEnd/src/views/common/dialog/DialogContainer.css deleted file mode 100644 index fbb18e0d..00000000 --- a/FrontEnd/src/views/common/dialog/DialogContainer.css +++ /dev/null @@ -1,20 +0,0 @@ -.cru-dialog-container-title { - font-size: 1.2em; - font-weight: bold; - color: var(--cru-key-color); - margin-bottom: 0.5em; -} - - -.cru-dialog-container-hr { - margin: 1em 0; -} - -.cru-dialog-container-button-row { - display: flex; - justify-content: flex-end; -} - -.cru-dialog-container-button { - margin-left: 1em; -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.tsx b/FrontEnd/src/views/common/dialog/DialogContainer.tsx deleted file mode 100644 index afee2669..00000000 --- a/FrontEnd/src/views/common/dialog/DialogContainer.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ComponentProps, Ref, ReactNode } from "react"; -import classNames from "classnames"; - -import { ThemeColor, Text, useC } from "../common"; -import { ButtonRow, ButtonRowV2 } from "../button"; - -import "./DialogContainer.css"; - -interface DialogContainerBaseProps { - className?: string; - title: Text; - titleColor?: ThemeColor; - titleClassName?: string; - titleRef?: Ref; - bodyContainerClassName?: string; - bodyContainerRef?: Ref; - buttonsClassName?: string; - buttonsContainerRef?: ComponentProps["containerRef"]; - children: ReactNode; -} - -interface DialogContainerWithButtonsProps extends DialogContainerBaseProps { - buttons: ComponentProps["buttons"]; -} - -interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps { - buttonsV2: ComponentProps["buttons"]; -} - -type DialogContainerProps = - | DialogContainerWithButtonsProps - | DialogContainerWithButtonsV2Props; - -export default function DialogContainer(props: DialogContainerProps) { - const { - className, - title, - titleColor, - titleClassName, - titleRef, - bodyContainerClassName, - bodyContainerRef, - buttonsClassName, - buttonsContainerRef, - children, - } = props; - - const c = useC(); - - return ( -
    -
    - {c(title)} -
    -
    -
    - {children} -
    -
    - {"buttons" in props ? ( - - ) : ( - - )} -
    - ); -} diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.css b/FrontEnd/src/views/common/dialog/FullPageDialog.css deleted file mode 100644 index 2f1fc636..00000000 --- a/FrontEnd/src/views/common/dialog/FullPageDialog.css +++ /dev/null @@ -1,44 +0,0 @@ -.cru-full-page { - position: fixed; - z-index: 1030; - left: 0; - top: 0; - right: 0; - bottom: 0; - background-color: white; - padding-top: 56px; -} - -.cru-full-page-top-bar { - height: 56px; - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: 1; - background-color: var(--cru-primary-color); - display: flex; - align-items: center; -} - -.cru-full-page-content-container { - overflow: scroll; -} - -.cru-full-page-back-button { - color: var(--cru-primary-t-color); -} - -.cru-full-page-enter { - transform: translate(100%, 0); -} - -.cru-full-page-enter-active { - transform: none; - transition: transform 0.3s; -} - -.cru-full-page-exit-active { - transition: transform 0.3s; - transform: translate(100%, 0); -} diff --git a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx b/FrontEnd/src/views/common/dialog/FullPageDialog.tsx deleted file mode 100644 index 6368fc0a..00000000 --- a/FrontEnd/src/views/common/dialog/FullPageDialog.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react"; -import { createPortal } from "react-dom"; -import classnames from "classnames"; -import { CSSTransition } from "react-transition-group"; - -import "./FullPageDialog.css"; -import IconButton from "../button/IconButton"; - -export interface FullPageDialogProps { - show: boolean; - onBack: () => void; - contentContainerClassName?: string; - children: React.ReactNode; -} - -const FullPageDialog: React.FC = ({ - show, - onBack, - children, - contentContainerClassName, -}) => { - return createPortal( - -
    -
    - -
    -
    - {children} -
    -
    -
    , - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")! - ); -}; - -export default FullPageDialog; diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.css b/FrontEnd/src/views/common/dialog/OperationDialog.css deleted file mode 100644 index f4b7237e..00000000 --- a/FrontEnd/src/views/common/dialog/OperationDialog.css +++ /dev/null @@ -1,8 +0,0 @@ -.cru-operation-dialog-prompt { - color: var(--cru-surface-on-color); -} - -.cru-operation-dialog-input-group { - display: block; - margin: 0.5em 0; -} diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx deleted file mode 100644 index 4335b2b0..00000000 --- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { useState, ReactNode, ComponentProps } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import { - useInputs, - InputGroup, - Initializer as InputInitializer, - InputValueDict, - InputErrorDict, -} from "../input/InputGroup"; -import Dialog from "./Dialog"; -import DialogContainer from "./DialogContainer"; - -import "./OperationDialog.css"; - -export type { InputInitializer, InputValueDict, InputErrorDict }; - -interface OperationDialogPromptProps { - message?: Text; - customMessage?: Text; - customMessageNode?: ReactNode; - className?: string; -} - -function OperationDialogPrompt(props: OperationDialogPromptProps) { - const { message, customMessage, customMessageNode, className } = props; - - const c = useC(); - - return ( -
    - {message &&

    {c(message)}

    } - {customMessageNode ?? (customMessage != null ? c(customMessage) : null)} -
    - ); -} - -export interface OperationDialogProps { - open: boolean; - onClose: () => void; - - color?: ThemeColor; - inputColor?: ThemeColor; - title: Text; - inputPrompt?: Text; - inputPromptNode?: ReactNode; - successPrompt?: (data: TData) => Text; - successPromptNode?: (data: TData) => ReactNode; - failurePrompt?: (error: unknown) => Text; - failurePromptNode?: (error: unknown) => ReactNode; - - inputs: InputInitializer; - - onProcess: (inputs: InputValueDict) => Promise; - onSuccessAndClose?: (data: TData) => void; -} - -function OperationDialog(props: OperationDialogProps) { - const { - open, - onClose, - color, - inputColor, - title, - inputPrompt, - inputPromptNode, - successPrompt, - successPromptNode, - failurePrompt, - failurePromptNode, - inputs, - onProcess, - onSuccessAndClose, - } = props; - - if (process.env.NODE_ENV === "development") { - if (inputPrompt && inputPromptNode) { - console.log("InputPrompt and inputPromptNode are both set."); - } - if (successPrompt && successPromptNode) { - console.log("SuccessPrompt and successPromptNode are both set."); - } - if (failurePrompt && failurePromptNode) { - console.log("FailurePrompt and failurePromptNode are both set."); - } - } - - type Step = - | { type: "input" } - | { type: "process" } - | { - type: "success"; - data: TData; - } - | { - type: "failure"; - data: unknown; - }; - - const [step, setStep] = useState({ type: "input" }); - - const { inputGroupProps, hasErrorAndDirty, setAllDisabled, confirm } = - useInputs({ - init: inputs, - }); - - function close() { - if (step.type !== "process") { - onClose(); - if (step.type === "success" && onSuccessAndClose) { - onSuccessAndClose?.(step.data); - } - } else { - console.log("Attempt to close modal dialog when processing."); - } - } - - function onConfirm() { - const result = confirm(); - if (result.type === "ok") { - setStep({ type: "process" }); - setAllDisabled(true); - onProcess(result.values).then( - (d) => { - setStep({ - type: "success", - data: d, - }); - }, - (e: unknown) => { - setStep({ - type: "failure", - data: e, - }); - }, - ); - } - } - - let body: ReactNode; - let buttons: ComponentProps["buttons"]; - - if (step.type === "input" || step.type === "process") { - const isProcessing = step.type === "process"; - - body = ( -
    - - -
    - ); - buttons = [ - { - key: "cancel", - type: "normal", - props: { - text: "operationDialog.cancel", - color: "secondary", - outline: true, - onClick: close, - disabled: isProcessing, - }, - }, - { - key: "confirm", - type: "loading", - props: { - text: "operationDialog.confirm", - color, - loading: isProcessing, - disabled: hasErrorAndDirty, - onClick: onConfirm, - }, - }, - ]; - } else { - const result = step; - - const promptProps: OperationDialogPromptProps = - result.type === "success" - ? { - message: "operationDialog.success", - customMessage: successPrompt?.(result.data), - customMessageNode: successPromptNode?.(result.data), - } - : { - message: "operationDialog.error", - customMessage: failurePrompt?.(result.data), - customMessageNode: failurePromptNode?.(result.data), - }; - body = ( -
    - -
    - ); - - buttons = [ - { - key: "ok", - type: "normal", - props: { - text: "operationDialog.ok", - color: "primary", - onClick: close, - }, - }, - ]; - } - - return ( - - - {body} - - - ); -} - -export default OperationDialog; diff --git a/FrontEnd/src/views/common/dialog/index.ts b/FrontEnd/src/views/common/dialog/index.ts deleted file mode 100644 index 59f15791..00000000 --- a/FrontEnd/src/views/common/dialog/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from "react"; - -export { default as Dialog } from "./Dialog"; -export { default as FullPageDialog } from "./FullPageDialog"; -export { default as OperationDialog } from "./OperationDialog"; -export { default as ConfirmDialog } from "./ConfirmDialog"; - -type DialogMap = { - [K in D]: V; -}; - -type DialogKeyMap = DialogMap; - -type DialogPropsMap = DialogMap< - D, - { key: number | string; open: boolean; onClose: () => void } ->; - -export function useDialog( - dialogs: D[], - options?: { - initDialog?: D | null; - onClose?: { - [K in D]?: () => void; - }; - }, -): { - dialog: D | null; - switchDialog: (newDialog: D | null) => void; - dialogPropsMap: DialogPropsMap; - createDialogSwitch: (newDialog: D | null) => () => void; -} { - const [dialog, setDialog] = useState(options?.initDialog ?? null); - - const [dialogKeys, setDialogKeys] = useState>( - () => Object.fromEntries(dialogs.map((d) => [d, 0])) as DialogKeyMap, - ); - - const switchDialog = (newDialog: D | null) => { - if (dialog !== null) { - setDialogKeys({ ...dialogKeys, [dialog]: dialogKeys[dialog] + 1 }); - } - setDialog(newDialog); - }; - - return { - dialog, - switchDialog, - dialogPropsMap: Object.fromEntries( - dialogs.map((d) => [ - d, - { - key: `${d}-${dialogKeys[d]}`, - open: dialog === d, - onClose: () => { - switchDialog(null); - options?.onClose?.[d]?.(); - }, - }, - ]), - ) as DialogPropsMap, - createDialogSwitch: (newDialog: D | null) => () => switchDialog(newDialog), - }; -} diff --git a/FrontEnd/src/views/common/hooks.ts b/FrontEnd/src/views/common/hooks.ts deleted file mode 100644 index c1fa5774..00000000 --- a/FrontEnd/src/views/common/hooks.ts +++ /dev/null @@ -1,14 +0,0 @@ -// TODO: Migrate hooks - -export { - useIsSmallScreen, - useClickOutside, - useScrollToBottom, -} from "@/utilities/hooks"; - -import { useMediaQuery } from "react-responsive"; -import { breakpoints } from "./breakpoints"; - -export function useMobile(): boolean { - return useMediaQuery({ maxWidth: breakpoints.sm }); -} diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css deleted file mode 100644 index a8f5e9a5..00000000 --- a/FrontEnd/src/views/common/index.css +++ /dev/null @@ -1,100 +0,0 @@ -@import "./theme.css"; - -* { - box-sizing: border-box; - margin-inline: 0; - margin-block: 0; -} - -body { - font-family: var(--cru-default-font-family); - background: var(--cru-body-background-color); - color: var(--cru-text-primary-color); - line-height: 1.2; -} - -.cru-page { - padding: var(--cru-page-padding); -} - -.cru-page-no-top-padding { - padding-top: 0; -} - -.cru-text-center { - text-align: center; -} - -.cru-text-end { - text-align: end; -} - -.cru-float-left { - float: left; -} - -.cru-float-right { - float: right; -} - -.cru-align-text-bottom { - vertical-align: text-bottom; -} - -.cru-align-middle { - vertical-align: middle; -} - -.cru-clearfix::after { - clear: both; -} - -.cru-fill-parent { - width: 100%; - height: 100%; -} - -.cru-avatar { - width: 60px; - height: 60px; -} - -.cru-avatar.large { - width: 100px; - height: 100px; -} - -.cru-avatar.small { - width: 40px; - height: 40px; -} - -.cru-round { - border-radius: 50%; -} - -.cru-tab-pages-action-area { - display: flex; - align-items: center; -} - -.alert-container { - position: fixed; - z-index: 1070; -} - -@media (min-width: 576px) { - .alert-container { - bottom: 0; - right: 0; - } -} - -@media (max-width: 575.98px) { - .alert-container { - bottom: 0; - right: 0; - left: 0; - text-align: center; - } -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/input/InputGroup.css b/FrontEnd/src/views/common/input/InputGroup.css deleted file mode 100644 index 7e905b1e..00000000 --- a/FrontEnd/src/views/common/input/InputGroup.css +++ /dev/null @@ -1,54 +0,0 @@ -.cru-input-group { - display: block; -} - -.cru-input-container { - margin: 0.4em 0; -} - -.cru-input-label { - display: block; - color: var(--cru-clickable-normal-color); - font-size: 0.9em; - margin-bottom: 0.3em; -} - -.cru-input-label-inline { - margin-inline-start: 0.5em; -} - -.cru-input-type-text input { - appearance: none; - display: block; - border: 1px solid; - /* color: var(--cru-surface-on-color); */ - /* background-color: var(--cru-surface-color); */ - margin: 0; - font-size: 1em; - padding: 0.2em; -} - -.cru-input-type-text input:hover { - border-color: var(--cru-clickable-hover-color); -} - -.cru-input-type-text input:focus { - border-color: var(--cru-clickable-focus-color); -} - -.cru-input-type-text input:disabled { - border-color: var(--cru-clickable-disabled-color); -} - -.cru-input-error { - display: block; - font-size: 0.8em; - color: var(--cru-danger-color); - margin-top: 0.4em; -} - -.cru-input-helper { - display: block; - font-size: 0.8em; - color: var(--cru-primary-color); -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/input/InputGroup.tsx b/FrontEnd/src/views/common/input/InputGroup.tsx deleted file mode 100644 index d95bb29e..00000000 --- a/FrontEnd/src/views/common/input/InputGroup.tsx +++ /dev/null @@ -1,461 +0,0 @@ -/** - * Some notes for InputGroup: - * This is one of the most complicated components in this project. - * Probably because the feature is complex and involved user inputs. - * - * I hope it contains following features: - * - Input features - * - Supports a wide range of input types. - * - Validator to validate user inputs. - * - Can set initial values. - * - Dirty, aka, has user touched this input. - * - Developer friendly - * - Easy to use APIs. - * - Type check as much as possible. - * - UI - * - Configurable appearance. - * - Can display helper and error messages. - * - Easy to extend, like new input types. - * - * So here is some design decisions: - * Inputs are identified by its _key_. - * `InputGroup` component takes care of only UI and no logic. - * `useInputs` hook takes care of logic and generate props for `InputGroup`. - */ - -import { useState, Ref, useId } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import "./InputGroup.css"; - -export interface InputBase { - key: string; - label: Text; - helper?: Text; - disabled?: boolean; - error?: Text; -} - -export interface TextInput extends InputBase { - type: "text"; - value: string; - password?: boolean; -} - -export interface BoolInput extends InputBase { - type: "bool"; - value: boolean; -} - -export interface SelectInputOption { - value: string; - label: Text; - icon?: string; -} - -export interface SelectInput extends InputBase { - type: "select"; - value: string; - options: SelectInputOption[]; -} - -export type Input = TextInput | BoolInput | SelectInput; - -export type InputValue = Input["value"]; - -export type InputValueDict = Record; -export type InputErrorDict = Record; -export type InputDisabledDict = Record; -export type InputDirtyDict = Record; - -export type GeneralInputErrorDict = - | { - [key: string]: Text | null | undefined; - } - | null - | undefined; - -type MakeInputInfo = Omit; - -export type InputInfo = { - [I in Input as I["type"]]: MakeInputInfo; -}[Input["type"]]; - -export type Validator = ( - values: InputValueDict, - inputs: InputInfo[], -) => GeneralInputErrorDict; - -export type InputScheme = { - inputs: InputInfo[]; - validator?: Validator; -}; - -export type InputData = { - values: InputValueDict; - errors: InputErrorDict; - disabled: InputDisabledDict; - dirties: InputDirtyDict; -}; - -export type State = { - scheme: InputScheme; - data: InputData; -}; - -export type DataInitialization = { - values?: InputValueDict; - errors?: GeneralInputErrorDict; - disabled?: InputDisabledDict; - dirties?: InputDirtyDict; -}; - -export type Initialization = { - scheme: InputScheme; - dataInit?: DataInitialization; -}; - -export type GeneralInitialization = Initialization | InputScheme | InputInfo[]; - -export type Initializer = GeneralInitialization | (() => GeneralInitialization); - -export interface InputGroupProps { - color?: ThemeColor; - containerClassName?: string; - containerRef?: Ref; - - inputs: Input[]; - onChange: (index: number, value: Input["value"]) => void; -} - -function cleanObject(o: Record): Record> { - const result = { ...o }; - for (const key of Object.keys(result)) { - if (result[key] == null) { - delete result[key]; - } - } - return result as never; -} - -export type ConfirmResult = - | { - type: "ok"; - values: InputValueDict; - } - | { - type: "error"; - errors: InputErrorDict; - }; - -function validate( - validator: Validator | null | undefined, - values: InputValueDict, - inputs: InputInfo[], -): InputErrorDict { - return cleanObject(validator?.(values, inputs) ?? {}); -} - -export function useInputs(options: { init: Initializer }): { - inputGroupProps: InputGroupProps; - hasError: boolean; - hasErrorAndDirty: boolean; - confirm: () => ConfirmResult; - setAllDisabled: (disabled: boolean) => void; -} { - function initializeValue( - input: InputInfo, - value?: InputValue | null, - ): InputValue { - if (input.type === "text") { - return value ?? ""; - } else if (input.type === "bool") { - return value ?? false; - } else if (input.type === "select") { - return value ?? input.options[0].value; - } - throw new Error("Unknown input type"); - } - - function initialize(generalInitialization: GeneralInitialization): State { - const initialization: Initialization = Array.isArray(generalInitialization) - ? { scheme: { inputs: generalInitialization } } - : "scheme" in generalInitialization - ? generalInitialization - : { scheme: generalInitialization }; - - const { scheme, dataInit } = initialization; - const { inputs, validator } = scheme; - const keys = inputs.map((input) => input.key); - - if (process.env.NODE_ENV === "development") { - const checkKeys = (dict: Record | undefined) => { - if (dict != null) { - for (const key of Object.keys(dict)) { - if (!keys.includes(key)) { - console.warn(""); - } - } - } - }; - - checkKeys(dataInit?.values); - checkKeys(dataInit?.errors ?? {}); - checkKeys(dataInit?.disabled); - checkKeys(dataInit?.dirties); - } - - function clean( - dict: Record | null | undefined, - ): Record> { - return dict != null ? cleanObject(dict) : {}; - } - - const values: InputValueDict = {}; - const disabled: InputDisabledDict = clean(dataInit?.disabled); - const dirties: InputDirtyDict = clean(dataInit?.dirties); - const isErrorSet = dataInit?.errors != null; - let errors: InputErrorDict = clean(dataInit?.errors); - - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i]; - const { key } = input; - - values[key] = initializeValue(input, dataInit?.values?.[key]); - } - - if (isErrorSet) { - if (process.env.NODE_ENV === "development") { - console.log( - "You explicitly set errors (not undefined) in initializer, so validator won't run.", - ); - } - } else { - errors = validate(validator, values, inputs); - } - - return { - scheme, - data: { - values, - errors, - disabled, - dirties, - }, - }; - } - - const { init } = options; - const initializer = typeof init === "function" ? init : () => init; - - const [state, setState] = useState(() => initialize(initializer())); - - const { scheme, data } = state; - const { validator } = scheme; - - function createAllBooleanDict(value: boolean): Record { - const result: InputDirtyDict = {}; - for (const key of scheme.inputs.map((input) => input.key)) { - result[key] = value; - } - return result; - } - - const createAllDirties = () => createAllBooleanDict(true); - - const componentInputs: Input[] = []; - - for (let i = 0; i < scheme.inputs.length; i++) { - const input = scheme.inputs[i]; - const value = data.values[input.key]; - const error = data.errors[input.key]; - const disabled = data.disabled[input.key] ?? false; - const dirty = data.dirties[input.key] ?? false; - const componentInput: Input = { - ...input, - value: value as never, - disabled, - error: dirty ? error : undefined, - }; - componentInputs.push(componentInput); - } - - const hasError = Object.keys(data.errors).length > 0; - const hasDirty = Object.keys(data.dirties).some((key) => data.dirties[key]); - - return { - inputGroupProps: { - inputs: componentInputs, - onChange: (index, value) => { - const input = scheme.inputs[index]; - const { key } = input; - const newValues = { ...data.values, [key]: value }; - const newDirties = { ...data.dirties, [key]: true }; - const newErrors = validate(validator, newValues, scheme.inputs); - setState({ - scheme, - data: { - ...data, - values: newValues, - errors: newErrors, - dirties: newDirties, - }, - }); - }, - }, - hasError, - hasErrorAndDirty: hasError && hasDirty, - confirm() { - const newDirties = createAllDirties(); - const newErrors = validate(validator, data.values, scheme.inputs); - - setState({ - scheme, - data: { - ...data, - dirties: newDirties, - errors: newErrors, - }, - }); - - if (Object.keys(newErrors).length !== 0) { - return { - type: "error", - errors: newErrors, - }; - } else { - return { - type: "ok", - values: data.values, - }; - } - }, - setAllDisabled(disabled: boolean) { - setState({ - scheme, - data: { - ...data, - disabled: createAllBooleanDict(disabled), - }, - }); - }, - }; -} - -export function InputGroup({ - color, - inputs, - onChange, - containerRef, - containerClassName, -}: InputGroupProps) { - const c = useC(); - - const id = useId(); - - return ( -
    - {inputs.map((item, index) => { - const { key, type, value, label, error, helper, disabled } = item; - - const getContainerClassName = ( - ...additionalClassNames: classNames.ArgumentArray - ) => - classNames( - `cru-input-container cru-input-type-${type}`, - error && "error", - ...additionalClassNames, - ); - - const changeValue = (value: InputValue) => { - onChange(index, value); - }; - - const inputId = `${id}-${key}`; - - if (type === "text") { - const { password } = item; - return ( -
    - {label && ( - - )} - { - const v = event.target.value; - changeValue(v); - }} - disabled={disabled} - /> - {error &&
    {c(error)}
    } - {helper &&
    {c(helper)}
    } -
    - ); - } else if (type === "bool") { - return ( -
    - { - const v = event.currentTarget.checked; - changeValue(v); - }} - disabled={disabled} - /> - - {error &&
    {c(error)}
    } - {helper &&
    {c(helper)}
    } -
    - ); - } else if (type === "select") { - return ( -
    - - -
    - ); - } - })} -
    - ); -} diff --git a/FrontEnd/src/views/common/list/ListContainer.css b/FrontEnd/src/views/common/list/ListContainer.css deleted file mode 100644 index 53781834..00000000 --- a/FrontEnd/src/views/common/list/ListContainer.css +++ /dev/null @@ -1,4 +0,0 @@ -.cru-list-container { - border: 1px solid var(--cru-clickable-primary-normal-color); - border-radius: 5px; -} diff --git a/FrontEnd/src/views/common/list/ListContainer.tsx b/FrontEnd/src/views/common/list/ListContainer.tsx deleted file mode 100644 index aa00d12c..00000000 --- a/FrontEnd/src/views/common/list/ListContainer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; -import classNames from "classnames"; - -import "./ListContainer.css" - -function _ListContainer( - { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, - ref: Ref, -) { - return ( -
    - {children} -
    - ); -} - -const ListContainer = forwardRef(_ListContainer); - -export default ListContainer; diff --git a/FrontEnd/src/views/common/list/ListItemContainer.css b/FrontEnd/src/views/common/list/ListItemContainer.css deleted file mode 100644 index 8d7afa9f..00000000 --- a/FrontEnd/src/views/common/list/ListItemContainer.css +++ /dev/null @@ -1,3 +0,0 @@ -.cru-list-item-container { - border: 1px solid var(--cru-clickable-primary-normal-color); -} diff --git a/FrontEnd/src/views/common/list/ListItemContainer.tsx b/FrontEnd/src/views/common/list/ListItemContainer.tsx deleted file mode 100644 index 315cbd6e..00000000 --- a/FrontEnd/src/views/common/list/ListItemContainer.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentPropsWithoutRef, forwardRef, Ref } from "react"; -import classNames from "classnames"; - -import "./ListItemContainer.css"; - -function _ListItemContainer( - { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">, - ref: Ref, -) { - return ( -
    - {children} -
    - ); -} - -const ListItemContainer = forwardRef(_ListItemContainer); - -export default ListItemContainer; diff --git a/FrontEnd/src/views/common/list/index.ts b/FrontEnd/src/views/common/list/index.ts deleted file mode 100644 index e183f7da..00000000 --- a/FrontEnd/src/views/common/list/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import ListContainer from "./ListContainer"; -import ListItemContainer from "./ListItemContainer"; - -export { ListContainer, ListItemContainer }; diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css deleted file mode 100644 index 75734533..00000000 --- a/FrontEnd/src/views/common/menu/Menu.css +++ /dev/null @@ -1,36 +0,0 @@ -.cru-menu { - min-width: 200px; -} - -.cru-menu-item { - display: block; - font-size: 1em; - width: 100%; - padding: 0.5em 1.5em; - transition: all 0.5s; - color: var(--cru-clickable-normal-color); - background-color: var(--cru-clickable-grayscale-normal-color); - border: none; - cursor: pointer; -} - -.cru-menu-item:hover { - background-color: var(--cru-clickable-grayscale-hover-color); -} - -.cru-menu-item:focus { - background-color: var(--cru-clickable-grayscale-focus-color); -} - -.cru-menu-item:active { - background-color: var(--cru-clickable-grayscale-active-color); -} - -.cru-menu-item-icon { - margin-right: 1em; -} - -.cru-menu-divider { - border-width: 0; - border-top: 1px solid var(--cru-primary-color); -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx deleted file mode 100644 index e8099c76..00000000 --- a/FrontEnd/src/views/common/menu/Menu.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { CSSProperties } from "react"; -import classNames from "classnames"; - -import { useC, Text, ThemeColor } from "../common"; - -import "./Menu.css"; -import Icon from "../Icon"; - -export type MenuItem = - | { - type: "divider"; - } - | { - type: "button"; - text: Text; - icon?: string; - color?: ThemeColor; - onClick: () => void; - }; - -export type MenuItems = MenuItem[]; - -export type MenuProps = { - items: MenuItems; - onItemClicked?: () => void; - className?: string; - style?: CSSProperties; -}; - -export default function Menu({ - items, - onItemClicked, - className, - style, -}: MenuProps) { - const c = useC(); - - return ( -
    - {items.map((item, index) => { - if (item.type === "divider") { - return
    ; - } else { - const { text, color, icon, onClick } = item; - return ( - - ); - } - })} -
    - ); -} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.css b/FrontEnd/src/views/common/menu/PopupMenu.css deleted file mode 100644 index 149e0699..00000000 --- a/FrontEnd/src/views/common/menu/PopupMenu.css +++ /dev/null @@ -1,7 +0,0 @@ -.cru-popup-menu-menu-container { - z-index: 1040; - border-radius: 3px; - border: var(--cru-clickable-normal-color) 1.5px solid; - background-color: var(--cru-background-color); - overflow: hidden; -} diff --git a/FrontEnd/src/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx deleted file mode 100644 index 5c8d5e98..00000000 --- a/FrontEnd/src/views/common/menu/PopupMenu.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useState, CSSProperties, ReactNode } from "react"; -import classNames from "classnames"; -import { createPortal } from "react-dom"; -import { usePopper } from "react-popper"; - -import { useClickOutside } from "@/utilities/hooks"; - -import Menu, { MenuItems } from "./Menu"; - -import { ThemeColor } from "../common"; - -import "./PopupMenu.css"; - -export interface PopupMenuProps { - color?: ThemeColor; - items: MenuItems; - children?: ReactNode; - containerClassName?: string; - containerStyle?: CSSProperties; -} - -export default function PopupMenu({ - color, - items, - children, - containerClassName, - containerStyle, -}: PopupMenuProps) { - const [show, setShow] = useState(false); - - const [referenceElement, setReferenceElement] = - useState(null); - const [popperElement, setPopperElement] = useState( - null, - ); - const { styles, attributes } = usePopper(referenceElement, popperElement); - - useClickOutside(popperElement, () => setShow(false), true); - - return ( -
    setShow(true)} - > - {children} - {show && - createPortal( -
    - { - setShow(false); - }} - /> -
    , - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")!, - )} -
    - ); -} diff --git a/FrontEnd/src/views/common/tab/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx deleted file mode 100644 index cdb988e0..00000000 --- a/FrontEnd/src/views/common/tab/TabPages.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from "react"; - -import { I18nText, UiLogicError } from "@/common"; - -import Tabs from "./Tabs"; - -export interface TabPage { - name: string; - text: I18nText; - page: React.ReactNode; -} - -export interface TabPagesProps { - pages: TabPage[]; - actions?: React.ReactNode; - dense?: boolean; - className?: string; - style?: React.CSSProperties; - navClassName?: string; - navStyle?: React.CSSProperties; - pageContainerClassName?: string; - pageContainerStyle?: React.CSSProperties; -} - -const TabPages: React.FC = ({ - pages, - actions, - dense, - className, - style, - navClassName, - navStyle, - pageContainerClassName, - pageContainerStyle, -}) => { - if (pages.length === 0) { - throw new UiLogicError("Page list can't be empty."); - } - - const [tab, setTab] = React.useState(pages[0].name); - - const currentPage = pages.find((p) => p.name === tab); - - if (currentPage == null) { - throw new UiLogicError("Current tab value is bad."); - } - - return ( -
    - ({ - name: page.name, - text: page.text, - onClick: () => { - setTab(page.name); - }, - }))} - dense={dense} - activeTabName={tab} - className={navClassName} - style={navStyle} - actions={actions} - /> -
    - {currentPage.page} -
    -
    - ); -}; - -export default TabPages; diff --git a/FrontEnd/src/views/common/tab/Tabs.css b/FrontEnd/src/views/common/tab/Tabs.css deleted file mode 100644 index 395d16a7..00000000 --- a/FrontEnd/src/views/common/tab/Tabs.css +++ /dev/null @@ -1,33 +0,0 @@ -.cru-nav { - border-bottom: var(--cru-primary-color) 1px solid; - display: flex; -} - -.cru-nav-item { - color: var(--cru-primary-color); - border: var(--cru-background-2-color) 0.5px solid; - border-bottom: none; - padding: 0.5em 1.5em; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - transition: all 0.5s; - cursor: pointer; -} - -.cru-nav.dense .cru-nav-item { - padding: 0.2em 1em; -} - -.cru-nav-item:hover { - background-color: var(--cru-background-1-color); -} - -.cru-nav-item.active { - color: var(--cru-primary-t-color); - background-color: var(--cru-primary-color); - border-color: var(--cru-primary-color); -} - -.cru-nav-action-area { - margin-left: auto; -} diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx deleted file mode 100644 index 3e3ef6fa..00000000 --- a/FrontEnd/src/views/common/tab/Tabs.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import * as React from "react"; -import { Link } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import classnames from "classnames"; - -import { convertI18nText, I18nText } from "@/common"; - -import "./Tabs.css"; - -export interface Tab { - name: string; - text: I18nText; - link?: string; - onClick?: () => void; -} - -export interface TabsProps { - activeTabName?: string; - actions?: React.ReactNode; - dense?: boolean; - tabs: Tab[]; - className?: string; - style?: React.CSSProperties; -} - -export default function Tabs(props: TabsProps): React.ReactElement | null { - const { tabs, activeTabName, className, style, dense, actions } = props; - - const { t } = useTranslation(); - - return ( -
    - {tabs.map((tab) => { - const active = activeTabName === tab.name; - const className = classnames("cru-nav-item", active && "active"); - - if (tab.link != null) { - return ( - - {convertI18nText(tab.text, t)} - - ); - } else { - return ( - - {convertI18nText(tab.text, t)} - - ); - } - })} -
    {actions}
    -
    - ); -} diff --git a/FrontEnd/src/views/common/theme-color.css b/FrontEnd/src/views/common/theme-color.css deleted file mode 100644 index 24a7e267..00000000 --- a/FrontEnd/src/views/common/theme-color.css +++ /dev/null @@ -1,173 +0,0 @@ -/* Generated by theme-generator.ts */ - -:root { - --cru-primary-color: hsl(210 100% 40%); - --cru-primary-1-color: hsl(210 100% 37%); - --cru-primary-2-color: hsl(210 100% 34%); - --cru-primary-on-color: hsl(210 100% 100%); - --cru-primary-container-color: hsl(210 100% 90%); - --cru-primary-container-1-color: hsl(210 100% 80%); - --cru-primary-container-2-color: hsl(210 100% 70%); - --cru-primary-on-container-color: hsl(210 100% 10%); - --cru-secondary-color: hsl(40 100% 40%); - --cru-secondary-1-color: hsl(40 100% 37%); - --cru-secondary-2-color: hsl(40 100% 34%); - --cru-secondary-on-color: hsl(40 100% 100%); - --cru-secondary-container-color: hsl(40 100% 90%); - --cru-secondary-container-1-color: hsl(40 100% 80%); - --cru-secondary-container-2-color: hsl(40 100% 70%); - --cru-secondary-on-container-color: hsl(40 100% 10%); - --cru-tertiary-color: hsl(160 100% 40%); - --cru-tertiary-1-color: hsl(160 100% 37%); - --cru-tertiary-2-color: hsl(160 100% 34%); - --cru-tertiary-on-color: hsl(160 100% 100%); - --cru-tertiary-container-color: hsl(160 100% 90%); - --cru-tertiary-container-1-color: hsl(160 100% 80%); - --cru-tertiary-container-2-color: hsl(160 100% 70%); - --cru-tertiary-on-container-color: hsl(160 100% 10%); - --cru-danger-color: hsl(0 100% 40%); - --cru-danger-1-color: hsl(0 100% 37%); - --cru-danger-2-color: hsl(0 100% 34%); - --cru-danger-on-color: hsl(0 100% 100%); - --cru-danger-container-color: hsl(0 100% 90%); - --cru-danger-container-1-color: hsl(0 100% 80%); - --cru-danger-container-2-color: hsl(0 100% 70%); - --cru-danger-on-container-color: hsl(0 100% 10%); - --cru-success-color: hsl(120 60% 40%); - --cru-success-1-color: hsl(120 60% 37%); - --cru-success-2-color: hsl(120 60% 34%); - --cru-success-on-color: hsl(120 60% 100%); - --cru-success-container-color: hsl(120 60% 90%); - --cru-success-container-1-color: hsl(120 60% 80%); - --cru-success-container-2-color: hsl(120 60% 70%); - --cru-success-on-container-color: hsl(120 60% 10%); - --cru-surface-dim-color: hsl(0 0% 87%); - --cru-surface-color: hsl(0 0% 98%); - --cru-surface-1-color: hsl(0 0% 90%); - --cru-surface-2-color: hsl(0 0% 82%); - --cru-surface-bright-color: hsl(0 0% 98%); - --cru-surface-container-lowest-color: hsl(0 0% 100%); - --cru-surface-container-low-color: hsl(0 0% 96%); - --cru-surface-container-color: hsl(0 0% 94%); - --cru-surface-container-high-color: hsl(0 0% 92%); - --cru-surface-container-highest-color: hsl(0 0% 90%); - --cru-surface-on-color: hsl(0 0% 10%); - --cru-surface-on-variant-color: hsl(0 0% 30%); - --cru-surface-outline-color: hsl(0 0% 50%); - --cru-surface-outline-variant-color: hsl(0 0% 80%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-primary-color: hsl(210 100% 80%); - --cru-primary-1-color: hsl(210 100% 75%); - --cru-primary-2-color: hsl(210 100% 68%); - --cru-primary-on-color: hsl(210 100% 20%); - --cru-primary-container-color: hsl(210 100% 30%); - --cru-primary-container-1-color: hsl(210 100% 25%); - --cru-primary-container-2-color: hsl(210 100% 20%); - --cru-primary-on-container-color: hsl(210 100% 90%); - --cru-secondary-color: hsl(40 100% 80%); - --cru-secondary-1-color: hsl(40 100% 75%); - --cru-secondary-2-color: hsl(40 100% 68%); - --cru-secondary-on-color: hsl(40 100% 20%); - --cru-secondary-container-color: hsl(40 100% 30%); - --cru-secondary-container-1-color: hsl(40 100% 25%); - --cru-secondary-container-2-color: hsl(40 100% 20%); - --cru-secondary-on-container-color: hsl(40 100% 90%); - --cru-tertiary-color: hsl(160 100% 80%); - --cru-tertiary-1-color: hsl(160 100% 75%); - --cru-tertiary-2-color: hsl(160 100% 68%); - --cru-tertiary-on-color: hsl(160 100% 20%); - --cru-tertiary-container-color: hsl(160 100% 30%); - --cru-tertiary-container-1-color: hsl(160 100% 25%); - --cru-tertiary-container-2-color: hsl(160 100% 20%); - --cru-tertiary-on-container-color: hsl(160 100% 90%); - --cru-danger-color: hsl(0 100% 80%); - --cru-danger-1-color: hsl(0 100% 75%); - --cru-danger-2-color: hsl(0 100% 68%); - --cru-danger-on-color: hsl(0 100% 20%); - --cru-danger-container-color: hsl(0 100% 30%); - --cru-danger-container-1-color: hsl(0 100% 25%); - --cru-danger-container-2-color: hsl(0 100% 20%); - --cru-danger-on-container-color: hsl(0 100% 90%); - --cru-success-color: hsl(120 60% 80%); - --cru-success-1-color: hsl(120 60% 75%); - --cru-success-2-color: hsl(120 60% 68%); - --cru-success-on-color: hsl(120 60% 20%); - --cru-success-container-color: hsl(120 60% 30%); - --cru-success-container-1-color: hsl(120 60% 25%); - --cru-success-container-2-color: hsl(120 60% 20%); - --cru-success-on-container-color: hsl(120 60% 90%); - --cru-surface-dim-color: hsl(0 0% 6%); - --cru-surface-color: hsl(0 0% 6%); - --cru-surface-1-color: hsl(0 0% 25%); - --cru-surface-2-color: hsl(0 0% 40%); - --cru-surface-bright-color: hsl(0 0% 24%); - --cru-surface-container-lowest-color: hsl(0 0% 4%); - --cru-surface-container-low-color: hsl(0 0% 10%); - --cru-surface-container-color: hsl(0 0% 12%); - --cru-surface-container-high-color: hsl(0 0% 17%); - --cru-surface-container-highest-color: hsl(0 0% 22%); - --cru-surface-on-color: hsl(0 0% 90%); - --cru-surface-on-variant-color: hsl(0 0% 80%); - --cru-surface-outline-color: hsl(0 0% 60%); - --cru-surface-outline-variant-color: hsl(0 0% 30%); - } -} - -.cru-primary { - --cru-key-color: var(--cru-primary-color); - --cru-key-1-color: var(--cru-primary-1-color); - --cru-key-2-color: var(--cru-primary-2-color); - --cru-key-on-color: var(--cru-primary-on-color); - --cru-key-container-color: var(--cru-primary-container-color); - --cru-key-container-1-color: var(--cru-primary-container-1-color); - --cru-key-container-2-color: var(--cru-primary-container-2-color); - --cru-key-on-container-color: var(--cru-primary-on-container-color); -} - -.cru-secondary { - --cru-key-color: var(--cru-secondary-color); - --cru-key-1-color: var(--cru-secondary-1-color); - --cru-key-2-color: var(--cru-secondary-2-color); - --cru-key-on-color: var(--cru-secondary-on-color); - --cru-key-container-color: var(--cru-secondary-container-color); - --cru-key-container-1-color: var(--cru-secondary-container-1-color); - --cru-key-container-2-color: var(--cru-secondary-container-2-color); - --cru-key-on-container-color: var(--cru-secondary-on-container-color); -} - -.cru-tertiary { - --cru-key-color: var(--cru-tertiary-color); - --cru-key-1-color: var(--cru-tertiary-1-color); - --cru-key-2-color: var(--cru-tertiary-2-color); - --cru-key-on-color: var(--cru-tertiary-on-color); - --cru-key-container-color: var(--cru-tertiary-container-color); - --cru-key-container-1-color: var(--cru-tertiary-container-1-color); - --cru-key-container-2-color: var(--cru-tertiary-container-2-color); - --cru-key-on-container-color: var(--cru-tertiary-on-container-color); -} - -.cru-danger { - --cru-key-color: var(--cru-danger-color); - --cru-key-1-color: var(--cru-danger-1-color); - --cru-key-2-color: var(--cru-danger-2-color); - --cru-key-on-color: var(--cru-danger-on-color); - --cru-key-container-color: var(--cru-danger-container-color); - --cru-key-container-1-color: var(--cru-danger-container-1-color); - --cru-key-container-2-color: var(--cru-danger-container-2-color); - --cru-key-on-container-color: var(--cru-danger-on-container-color); -} - -.cru-success { - --cru-key-color: var(--cru-success-color); - --cru-key-1-color: var(--cru-success-1-color); - --cru-key-2-color: var(--cru-success-2-color); - --cru-key-on-color: var(--cru-success-on-color); - --cru-key-container-color: var(--cru-success-container-color); - --cru-key-container-1-color: var(--cru-success-container-1-color); - --cru-key-container-2-color: var(--cru-success-container-2-color); - --cru-key-on-container-color: var(--cru-success-on-container-color); -} - diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css deleted file mode 100644 index 6ceb369f..00000000 --- a/FrontEnd/src/views/common/theme.css +++ /dev/null @@ -1,146 +0,0 @@ -@import "./theme-color.css"; - -:root { - --cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - --cru-page-padding: 1em 2em; - - --cru-border-radius: 4px; - --cru-card-border-radius: 4px; -} - -/* theme colors */ -:root { - --cru-primary-color: hsl(210, 100%, 50%); - --cru-secondary-color: hsl(30, 100%, 50%); - --cru-create-color: hsl(120, 100%, 25%); - --cru-danger-color: hsl(0, 100%, 50%); -} - -/* common colors */ -:root { - --cru-background-color: hsl(0, 0%, 100%); - --cru-container-background-color: hsl(0, 0%, 97%); - --cru-text-primary-color: hsl(0, 0%, 0%); - --cru-text-secondary-color: hsl(0, 0%, 38%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-background-color: hsl(0, 0%, 0%); - --cru-container-background-color: hsl(0, 0%, 2%); - --cru-text-primary-color: hsl(0, 0%, 100%); - --cru-text-secondary-color: hsl(0, 0%, 85%); - } -} - -:root { - --cru-body-background-color: var(--cru-background-color); -} - -/* clickable color */ -:root { - --cru-clickable-primary-normal-color: var(--cru-primary-color); - --cru-clickable-primary-hover-color: hsl(210, 100%, 60%); - --cru-clickable-primary-focus-color: hsl(210, 100%, 60%); - --cru-clickable-primary-active-color: hsl(210, 100%, 70%); - --cru-clickable-secondary-normal-color: var(--cru-secondary-color); - --cru-clickable-secondary-hover-color: hsl(30, 100%, 60%); - --cru-clickable-secondary-focus-color: hsl(30, 100%, 60%); - --cru-clickable-secondary-active-color: hsl(30, 100%, 70%); - --cru-clickable-create-normal-color: var(--cru-create-color); - --cru-clickable-create-hover-color: hsl(120, 100%, 35%); - --cru-clickable-create-focus-color: hsl(120, 100%, 35%); - --cru-clickable-create-active-color: hsl(120, 100%, 35%); - --cru-clickable-danger-normal-color: var(--cru-danger-color); - --cru-clickable-danger-hover-color: hsl(0, 100%, 60%); - --cru-clickable-danger-focus-color: hsl(0, 100%, 60%); - --cru-clickable-danger-active-color: hsl(0, 100%, 70%); - --cru-clickable-grayscale-normal-color: hsl(0, 0%, 100%); - --cru-clickable-grayscale-hover-color: hsl(0, 0%, 92%); - --cru-clickable-grayscale-focus-color: hsl(0, 0%, 92%); - --cru-clickable-grayscale-active-color: hsl(0, 0%, 88%); - --cru-clickable-disabled-color: hsl(0, 0%, 50%); -} - -@media (prefers-color-scheme: dark) { - :root { - --cru-clickable-grayscale-normal-color: hsl(0, 0%, 0%); - --cru-clickable-grayscale-hover-color: hsl(0, 0%, 10%); - --cru-clickable-grayscale-focus-color: hsl(0, 0%, 10%); - --cru-clickable-grayscale-active-color: hsl(0, 0%, 20%); - } -} - -.cru-clickable-primary { - --cru-clickable-normal-color: var(--cru-clickable-primary-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-primary-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-primary-focus-color); - --cru-clickable-active-color: var(--cru-clickable-primary-active-color); -} - -.cru-clickable-secondary { - --cru-clickable-normal-color: var(--cru-clickable-secondary-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-secondary-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-secondary-focus-color); - --cru-clickable-active-color: var(--cru-clickable-secondary-active-color); -} - -.cru-clickable-create { - --cru-clickable-normal-color: var(--cru-clickable-create-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-create-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-create-focus-color); - --cru-clickable-active-color: var(--cru-clickable-create-active-color); -} - -.cru-clickable-danger { - --cru-clickable-normal-color: var(--cru-clickable-danger-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-danger-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-danger-focus-color); - --cru-clickable-active-color: var(--cru-clickable-danger-active-color); -} - -.cru-clickable-grayscale { - --cru-clickable-normal-color: var(--cru-clickable-grayscale-normal-color); - --cru-clickable-hover-color: var(--cru-clickable-grayscale-hover-color); - --cru-clickable-focus-color: var(--cru-clickable-grayscale-focus-color); - --cru-clickable-active-color: var(--cru-clickable-grayscale-active-color); -} - -/* button colors */ -:root { - /* push button colors */ - --cru-push-button-text-color: #ffffff; - --cru-push-button-disabled-text-color: hsl(0, 0%, 80%); -} - -/* Card colors */ -:root { - --cru-card-background-primary-color: hsl(210, 100%, 50%); - --cru-card-border-primary-color: hsl(210, 100%, 50%); - --cru-card-background-secondary-color: hsl(30, 100%, 50%); - --cru-card-border-secondary-color: hsl(30, 100%, 50%); - --cru-card-background-create-color: hsl(120, 100%, 25%); - --cru-card-border-create-color: hsl(120, 100%, 25%); - --cru-card-background-danger-color: hsl(0, 100%, 50%); - --cru-card-border-danger-color: hsl(0, 100%, 50%); -} - -.cru-card-primary { - --cru-card-background-color: var(--cru-card-background-primary-color); - --cru-card-border-color: var(--cru-card-border-primary-color) -} - -.cru-card-secondary { - --cru-card-background-color: var(--cru-card-background-secondary-color); - --cru-card-border-color: var(--cru-card-border-secondary-color) -} - -.cru-card-create { - --cru-card-background-color: var(--cru-card-background-create-color); - --cru-card-border-color: var(--cru-card-border-create-color) -} - -.cru-card-danger { - --cru-card-background-color: var(--cru-card-background-danger-color); - --cru-card-border-color: var(--cru-card-border-danger-color) -} \ No newline at end of file diff --git a/FrontEnd/src/views/common/user/UserAvatar.tsx b/FrontEnd/src/views/common/user/UserAvatar.tsx deleted file mode 100644 index aea7bd48..00000000 --- a/FrontEnd/src/views/common/user/UserAvatar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Ref, ComponentPropsWithoutRef } from "react"; - -import { getHttpUserClient } from "@/http/user"; - -export interface UserAvatarProps extends ComponentPropsWithoutRef<"img"> { - username: string; - imgRef?: Ref | null; -} - -export default function UserAvatar({ - username, - imgRef, - ...otherProps -}: UserAvatarProps) { - return ( - - ); -} diff --git a/FrontEnd/tsconfig.json b/FrontEnd/tsconfig.json index cd6aa25d..bf106d95 100644 --- a/FrontEnd/tsconfig.json +++ b/FrontEnd/tsconfig.json @@ -21,9 +21,6 @@ "~*": [ "./*" ], - "@/*": [ - "./src/*" - ] }, "noEmit": true }, -- cgit v1.2.3