Compare commits
No commits in common. "2abc6bfb38f48a72798512d8d5545a6adc7156b6" and "d0c1ee2adb312512632f8908752fa8da2b6af0b9" have entirely different histories.
2abc6bfb38
...
d0c1ee2adb
48
deno.lock
48
deno.lock
|
@ -11,7 +11,6 @@
|
||||||
"npm:@tauri-apps/plugin-os@2.2": "2.2.0",
|
"npm:@tauri-apps/plugin-os@2.2": "2.2.0",
|
||||||
"npm:@types/lodash-es@^4.17.12": "4.17.12",
|
"npm:@types/lodash-es@^4.17.12": "4.17.12",
|
||||||
"npm:@types/react-dom@19.0.4": "19.0.4_@types+react@19.0.10",
|
"npm:@types/react-dom@19.0.4": "19.0.4_@types+react@19.0.10",
|
||||||
"npm:@types/react-transition-group@^4.4.12": "4.4.12_@types+react@19.0.10",
|
|
||||||
"npm:@types/react@19.0.10": "19.0.10",
|
"npm:@types/react@19.0.10": "19.0.10",
|
||||||
"npm:@types/uuid@10": "10.0.0",
|
"npm:@types/uuid@10": "10.0.0",
|
||||||
"npm:@vitejs/plugin-react@^4.3.4": "4.3.4_vite@6.2.0__lightningcss@1.29.1_@babel+core@7.26.9_lightningcss@1.29.1",
|
"npm:@vitejs/plugin-react@^4.3.4": "4.3.4_vite@6.2.0__lightningcss@1.29.1_@babel+core@7.26.9_lightningcss@1.29.1",
|
||||||
|
@ -26,7 +25,6 @@
|
||||||
"npm:lodash-es@^4.17.21": "4.17.21",
|
"npm:lodash-es@^4.17.21": "4.17.21",
|
||||||
"npm:react-dom@19.0.0": "19.0.0_react@19.0.0",
|
"npm:react-dom@19.0.0": "19.0.0_react@19.0.0",
|
||||||
"npm:react-router-dom@^7.2.0": "7.2.0_react@19.0.0_react-dom@19.0.0__react@19.0.0",
|
"npm:react-router-dom@^7.2.0": "7.2.0_react@19.0.0_react-dom@19.0.0__react@19.0.0",
|
||||||
"npm:react-transition-group@^4.4.5": "4.4.5_react@19.0.0_react-dom@19.0.0__react@19.0.0",
|
|
||||||
"npm:react-use@^17.6.0": "17.6.0_react@19.0.0_react-dom@19.0.0__react@19.0.0_tslib@2.8.1",
|
"npm:react-use@^17.6.0": "17.6.0_react@19.0.0_react-dom@19.0.0__react@19.0.0_tslib@2.8.1",
|
||||||
"npm:react@19.0.0": "19.0.0",
|
"npm:react@19.0.0": "19.0.0",
|
||||||
"npm:sanitize.css@13": "13.0.0",
|
"npm:sanitize.css@13": "13.0.0",
|
||||||
|
@ -563,12 +561,6 @@
|
||||||
"@types/react"
|
"@types/react"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@types/react-transition-group@4.4.12_@types+react@19.0.10": {
|
|
||||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
|
||||||
"dependencies": [
|
|
||||||
"@types/react"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@types/react@19.0.10": {
|
"@types/react@19.0.10": {
|
||||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
@ -811,13 +803,6 @@
|
||||||
"detect-libc@1.0.3": {
|
"detect-libc@1.0.3": {
|
||||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
|
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
|
||||||
},
|
},
|
||||||
"dom-helpers@5.2.1": {
|
|
||||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
|
||||||
"dependencies": [
|
|
||||||
"@babel/runtime",
|
|
||||||
"csstype"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"electron-to-chromium@1.5.104": {
|
"electron-to-chromium@1.5.104": {
|
||||||
"integrity": "sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g=="
|
"integrity": "sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g=="
|
||||||
},
|
},
|
||||||
|
@ -1188,12 +1173,6 @@
|
||||||
"lodash.merge@4.6.2": {
|
"lodash.merge@4.6.2": {
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||||
},
|
},
|
||||||
"loose-envify@1.4.0": {
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dependencies": [
|
|
||||||
"js-tokens"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lru-cache@5.1.1": {
|
"lru-cache@5.1.1": {
|
||||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
@ -1252,9 +1231,6 @@
|
||||||
"node-releases@2.0.19": {
|
"node-releases@2.0.19": {
|
||||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
|
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
|
||||||
},
|
},
|
||||||
"object-assign@4.1.1": {
|
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
|
||||||
},
|
|
||||||
"optionator@0.9.4": {
|
"optionator@0.9.4": {
|
||||||
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
@ -1307,14 +1283,6 @@
|
||||||
"prelude-ls@1.2.1": {
|
"prelude-ls@1.2.1": {
|
||||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
|
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
|
||||||
},
|
},
|
||||||
"prop-types@15.8.1": {
|
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
|
||||||
"dependencies": [
|
|
||||||
"loose-envify",
|
|
||||||
"object-assign",
|
|
||||||
"react-is"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"punycode@2.3.1": {
|
"punycode@2.3.1": {
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
|
||||||
},
|
},
|
||||||
|
@ -1328,9 +1296,6 @@
|
||||||
"scheduler"
|
"scheduler"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"react-is@16.13.1": {
|
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
|
||||||
},
|
|
||||||
"react-refresh@0.14.2": {
|
"react-refresh@0.14.2": {
|
||||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="
|
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="
|
||||||
},
|
},
|
||||||
|
@ -1353,17 +1318,6 @@
|
||||||
"turbo-stream"
|
"turbo-stream"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"react-transition-group@4.4.5_react@19.0.0_react-dom@19.0.0__react@19.0.0": {
|
|
||||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
|
||||||
"dependencies": [
|
|
||||||
"@babel/runtime",
|
|
||||||
"dom-helpers",
|
|
||||||
"loose-envify",
|
|
||||||
"prop-types",
|
|
||||||
"react",
|
|
||||||
"react-dom"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"react-universal-interface@0.6.2_react@19.0.0_tslib@2.8.1": {
|
"react-universal-interface@0.6.2_react@19.0.0_tslib@2.8.1": {
|
||||||
"integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==",
|
"integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
@ -1622,7 +1576,6 @@
|
||||||
"npm:@tauri-apps/plugin-os@2.2",
|
"npm:@tauri-apps/plugin-os@2.2",
|
||||||
"npm:@types/lodash-es@^4.17.12",
|
"npm:@types/lodash-es@^4.17.12",
|
||||||
"npm:@types/react-dom@19.0.4",
|
"npm:@types/react-dom@19.0.4",
|
||||||
"npm:@types/react-transition-group@^4.4.12",
|
|
||||||
"npm:@types/react@19.0.10",
|
"npm:@types/react@19.0.10",
|
||||||
"npm:@types/uuid@10",
|
"npm:@types/uuid@10",
|
||||||
"npm:@vitejs/plugin-react@^4.3.4",
|
"npm:@vitejs/plugin-react@^4.3.4",
|
||||||
|
@ -1637,7 +1590,6 @@
|
||||||
"npm:lodash-es@^4.17.21",
|
"npm:lodash-es@^4.17.21",
|
||||||
"npm:react-dom@19.0.0",
|
"npm:react-dom@19.0.0",
|
||||||
"npm:react-router-dom@^7.2.0",
|
"npm:react-router-dom@^7.2.0",
|
||||||
"npm:react-transition-group@^4.4.5",
|
|
||||||
"npm:react-use@^17.6.0",
|
"npm:react-use@^17.6.0",
|
||||||
"npm:react@19.0.0",
|
"npm:react@19.0.0",
|
||||||
"npm:sanitize.css@13",
|
"npm:sanitize.css@13",
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-router-dom": "^7.2.0",
|
"react-router-dom": "^7.2.0",
|
||||||
"react-transition-group": "^4.4.5",
|
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"sanitize.css": "^13.0.0",
|
"sanitize.css": "^13.0.0",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.19.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react-transition-group": "^4.4.12",
|
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
|
|
|
@ -436,9 +436,6 @@
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
color: var(--color-on-surface);
|
color: var(--color-on-surface);
|
||||||
background-color: var(--color-surface-container-highest);
|
background-color: var(--color-surface-container-highest);
|
||||||
&.error {
|
|
||||||
border-color: var(--color-error);
|
|
||||||
}
|
|
||||||
.input_wrapper & {
|
.input_wrapper & {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -474,8 +471,5 @@
|
||||||
border-top-left-radius: calc(var(--border-radius) * 2);
|
border-top-left-radius: calc(var(--border-radius) * 2);
|
||||||
border-top-right-radius: calc(var(--border-radius) * 2);
|
border-top-right-radius: calc(var(--border-radius) * 2);
|
||||||
background-color: var(--color-surface-container-highest);
|
background-color: var(--color-surface-container-highest);
|
||||||
&.error {
|
|
||||||
border-color: var(--color-error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
@layer components {
|
|
||||||
.notification_positioner {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 20px;
|
|
||||||
right: 20px;
|
|
||||||
width: 25vw;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 10px;
|
|
||||||
z-index: 600;
|
|
||||||
}
|
|
||||||
.message_box_positioner {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.toast_positioner {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
bottom: 20vh;
|
|
||||||
z-index: 700;
|
|
||||||
}
|
|
||||||
.notification {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 2px;
|
|
||||||
.notification_icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.notificaiton_content {
|
|
||||||
flex-grow: 1;
|
|
||||||
.notification_title {
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.notification_close {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
align-self: flex-start;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toast {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 2px;
|
|
||||||
width: 35vw;
|
|
||||||
.toast_icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.toast_content {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: inline-block;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
.toast_close {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.kind_info {
|
|
||||||
color: var(--color-on-info-container);
|
|
||||||
background-color: var(--color-info-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.kind_promption {
|
|
||||||
color: var(--color-on-tertiary-container);
|
|
||||||
background-color: var(--color-tertiary-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.kind_success {
|
|
||||||
color: var(--color-on-success-container);
|
|
||||||
background-color: var(--color-success-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.kind_warning {
|
|
||||||
color: var(--color-on-warning-container);
|
|
||||||
background-color: var(--color-warning-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.kind_error {
|
|
||||||
color: var(--color-on-error-container);
|
|
||||||
background-color: var(--color-error-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.kind_custom {
|
|
||||||
color: var(--color-on-defensive-container);
|
|
||||||
background-color: var(--color-defensive-container);
|
|
||||||
box-shadow: var(--elevation-2-ambient) var(--elevation-2-umbra);
|
|
||||||
}
|
|
||||||
.slide_in_enter {
|
|
||||||
transform: translateX(100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.slide_in_enter_active {
|
|
||||||
transition: all 500ms ease-in-out;
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.slide_out_exit_active {
|
|
||||||
transition: all 500ms ease-in-out;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.fade_in_enter {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.fade_in_enter_active {
|
|
||||||
transition: all 500ms ease-in-out;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.fade_out_exit_active {
|
|
||||||
transition: all 500ms ease-in-out;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,296 +0,0 @@
|
||||||
import { Icon, IconifyIconProps } from '@iconify/react/dist/iconify.js';
|
|
||||||
import cx from 'clsx';
|
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
createRef,
|
|
||||||
FC,
|
|
||||||
ReactNode,
|
|
||||||
RefObject,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import styles from './Notifications.module.css';
|
|
||||||
|
|
||||||
export enum NotificationType {
|
|
||||||
INFO,
|
|
||||||
PROMPTION,
|
|
||||||
SUCCESS,
|
|
||||||
WARNING,
|
|
||||||
ERROR,
|
|
||||||
CUSTOM,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ToastDuration {
|
|
||||||
MANUAL = 0,
|
|
||||||
SHORT = 1500,
|
|
||||||
MEDIUM = 3000,
|
|
||||||
LONG = 5000,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NotificationFunctions {
|
|
||||||
addNotification(
|
|
||||||
kind: NotificationType,
|
|
||||||
title?: string,
|
|
||||||
message?: ReactNode,
|
|
||||||
icon?: IconifyIconProps['icon'],
|
|
||||||
duration?: number,
|
|
||||||
): string;
|
|
||||||
removeNotification(id: string): void;
|
|
||||||
showToast(
|
|
||||||
kind: NotificationType,
|
|
||||||
message: string,
|
|
||||||
icon?: IconifyIconProps['icon'],
|
|
||||||
duration?: ToastDuration,
|
|
||||||
): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NotificationStyleMap = {
|
|
||||||
[NotificationType.INFO]: styles.kind_info,
|
|
||||||
[NotificationType.PROMPTION]: styles.kind_promption,
|
|
||||||
[NotificationType.SUCCESS]: styles.kind_success,
|
|
||||||
[NotificationType.WARNING]: styles.kind_warning,
|
|
||||||
[NotificationType.ERROR]: styles.kind_error,
|
|
||||||
[NotificationType.CUSTOM]: styles.kind_custom,
|
|
||||||
};
|
|
||||||
|
|
||||||
const NotificationHostContext = createContext<NotificationFunctions>({
|
|
||||||
addNotification: () => '',
|
|
||||||
removeNotification: () => {},
|
|
||||||
showToast: () => '',
|
|
||||||
});
|
|
||||||
|
|
||||||
type NotificationProps = {
|
|
||||||
kind?: NotificationType;
|
|
||||||
nid: string;
|
|
||||||
icon?: IconifyIconProps['icon'];
|
|
||||||
title?: string;
|
|
||||||
message?: ReactNode;
|
|
||||||
duration?: number;
|
|
||||||
ref: RefObject<HTMLDivElement>;
|
|
||||||
closeAction?: (id: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Notification: FC<NotificationProps> = ({
|
|
||||||
kind = NotificationType.INFO,
|
|
||||||
nid,
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
message,
|
|
||||||
duration = 3000,
|
|
||||||
ref,
|
|
||||||
closeAction,
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (duration > 0) {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
closeAction?.(nid);
|
|
||||||
}, duration);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [nid, duration, closeAction]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cx(styles.notification, NotificationStyleMap[kind])} ref={ref}>
|
|
||||||
{icon && <Icon icon={icon} className={cx(styles.notification_icon)} />}
|
|
||||||
<div className={cx(styles.notificaiton_content)}>
|
|
||||||
{title && <div className={cx(styles.notification_title)}>{title}</div>}
|
|
||||||
{message && <div>{message}</div>}
|
|
||||||
</div>
|
|
||||||
{duration === 0 && (
|
|
||||||
<Icon
|
|
||||||
icon="material-symbols-light:close"
|
|
||||||
className={styles.notification_close}
|
|
||||||
onClick={() => closeAction?.(nid)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type ToastProps = {
|
|
||||||
kind: NotificationType;
|
|
||||||
tid: string;
|
|
||||||
message?: string;
|
|
||||||
icon?: string;
|
|
||||||
duration?: ToastDuration;
|
|
||||||
ref: RefObject<HTMLDivElement>;
|
|
||||||
closeAction: (tid?: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Toast: FC<ToastProps> = ({
|
|
||||||
kind,
|
|
||||||
tid,
|
|
||||||
message,
|
|
||||||
icon,
|
|
||||||
duration = ToastDuration.MEDIUM,
|
|
||||||
ref,
|
|
||||||
closeAction,
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (duration !== ToastDuration.MANUAL) {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
closeAction?.(tid);
|
|
||||||
}, duration);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [tid, duration, closeAction]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cx(styles.toast, NotificationStyleMap[kind])} ref={ref}>
|
|
||||||
{icon && <Icon icon={icon} className={styles.toast_icon} />}
|
|
||||||
<div className={styles.toast_content}>{message}</div>
|
|
||||||
{duration === ToastDuration.MANUAL && (
|
|
||||||
<Icon
|
|
||||||
icon="material-symbols-light:close"
|
|
||||||
className={styles.toast_close}
|
|
||||||
onClick={() => closeAction?.(tid)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useNotification() {
|
|
||||||
const functions = useContext<NotificationFunctions>(NotificationHostContext);
|
|
||||||
return functions;
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotificationElement = {
|
|
||||||
id: string;
|
|
||||||
element: ReactNode;
|
|
||||||
ref: RefObject<ReactNode | HTMLDivElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NotificationsProps = {
|
|
||||||
defaultDuration?: number;
|
|
||||||
maxNotifications?: number;
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Notifications: FC<NotificationsProps> = ({
|
|
||||||
defaultDuration = 3000,
|
|
||||||
maxNotifications = 5,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [notifications, setNotifications] = useState<NotificationElement[]>([]);
|
|
||||||
const [toasts, setToasts] = useState<NotificationElement[]>([]);
|
|
||||||
|
|
||||||
const removeNotification = useCallback((id: string) => {
|
|
||||||
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
||||||
}, []);
|
|
||||||
const addNotification = useCallback(
|
|
||||||
(
|
|
||||||
kind: NotificationType,
|
|
||||||
title?: string,
|
|
||||||
message?: ReactNode,
|
|
||||||
icon?: IconifyIconProps['icon'],
|
|
||||||
duration?: number,
|
|
||||||
) => {
|
|
||||||
const id = v4();
|
|
||||||
const ref = createRef<ReactNode | HTMLDivElement>();
|
|
||||||
const newNotify = (
|
|
||||||
<Notification
|
|
||||||
kind={kind}
|
|
||||||
nid={id}
|
|
||||||
icon={icon}
|
|
||||||
title={title}
|
|
||||||
message={message}
|
|
||||||
duration={duration ?? defaultDuration}
|
|
||||||
closeAction={removeNotification}
|
|
||||||
//@ts-expect-error TS2322
|
|
||||||
ref={ref}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
setNotifications((prev) => [...prev, { id, element: newNotify, ref } as NotificationElement]);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
[removeNotification, defaultDuration],
|
|
||||||
);
|
|
||||||
const removeToast = useCallback((id: string) => {
|
|
||||||
setToasts((prev) => prev.filter((n) => n.id !== id));
|
|
||||||
}, []);
|
|
||||||
const showToast = useCallback(
|
|
||||||
(kind: NotificationType, message?: string, icon?: string, duration?: ToastDuration) => {
|
|
||||||
const id = v4();
|
|
||||||
const ref = createRef<HTMLDivElement>();
|
|
||||||
const newToast = (
|
|
||||||
<Toast
|
|
||||||
kind={kind}
|
|
||||||
tid={id}
|
|
||||||
message={message ?? ''}
|
|
||||||
icon={icon}
|
|
||||||
duration={duration ?? ToastDuration.MEDIUM}
|
|
||||||
ref={ref}
|
|
||||||
closeAction={removeToast}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
setToasts((prev) => [...prev, { id, element: newToast, ref } as NotificationElement]);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
[removeToast],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NotificationHostContext
|
|
||||||
value={{
|
|
||||||
addNotification,
|
|
||||||
removeNotification,
|
|
||||||
showToast,
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
{createPortal(
|
|
||||||
<div className={cx(styles.notification_positioner)}>
|
|
||||||
<TransitionGroup component={null}>
|
|
||||||
{notifications.slice(0, maxNotifications).map(({ id, element, ref }) => (
|
|
||||||
<CSSTransition
|
|
||||||
key={id}
|
|
||||||
//@ts-expect-error TS2322
|
|
||||||
nodeRef={ref}
|
|
||||||
unmountOnExit
|
|
||||||
timeout={500}
|
|
||||||
classNames={{
|
|
||||||
enter: styles.slide_in_enter,
|
|
||||||
enterActive: styles.slide_in_enter_active,
|
|
||||||
exitActive: styles.slide_out_exit_active,
|
|
||||||
}}>
|
|
||||||
{element}
|
|
||||||
</CSSTransition>
|
|
||||||
))}
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
)}
|
|
||||||
{createPortal(
|
|
||||||
<div className={cx(styles.toast_positioner)}>
|
|
||||||
<TransitionGroup component={null}>
|
|
||||||
{toasts.slice(0, 1).map(({ id, element, ref }) => (
|
|
||||||
<CSSTransition
|
|
||||||
key={id}
|
|
||||||
//@ts-expect-error TS2322
|
|
||||||
nodeRef={ref}
|
|
||||||
unmountOnExit
|
|
||||||
timeout={500}
|
|
||||||
classNames={{
|
|
||||||
enter: styles.fade_in_enter,
|
|
||||||
enterActive: styles.fade_in_enter_active,
|
|
||||||
exitActive: styles.fade_out_exit_active,
|
|
||||||
}}>
|
|
||||||
{element}
|
|
||||||
</CSSTransition>
|
|
||||||
))}
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
)}
|
|
||||||
</NotificationHostContext>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Notifications;
|
|
35
src/main.tsx
35
src/main.tsx
|
@ -5,36 +5,27 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import Notifications from './components/Notifications';
|
|
||||||
import EstimWatchProvider from './context/EstimContext';
|
import EstimWatchProvider from './context/EstimContext';
|
||||||
import CreatePattern from './pages/CreatePattern';
|
|
||||||
import Device from './pages/Device';
|
import Device from './pages/Device';
|
||||||
import PatternEditor from './pages/PatternEditor';
|
import PatternEditor from './pages/PatternEditor';
|
||||||
import PatternLibrary from './pages/PatternLibrary';
|
import PatternLibrary from './pages/PatternLibrary';
|
||||||
import PatternNavigator from './pages/PatternNavigator';
|
|
||||||
import PlayControl from './pages/Play';
|
import PlayControl from './pages/Play';
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Notifications>
|
<EstimWatchProvider>
|
||||||
<EstimWatchProvider>
|
<BrowserRouter>
|
||||||
<BrowserRouter>
|
<Routes>
|
||||||
<Routes>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route path="/" element={<Layout />}>
|
<Route index element={<Device />} />
|
||||||
<Route index element={<Device />} />
|
<Route path="/play" element={<PlayControl />} />
|
||||||
<Route path="/play" element={<PlayControl />} />
|
<Route path="/library" element={<PatternLibrary />} />
|
||||||
<Route path="/library" element={<PatternLibrary />} />
|
<Route path="/pattern-editor" element={<PatternEditor />} />
|
||||||
<Route path="/pattern-editor">
|
<Route path="/settings" element={<Settings />} />
|
||||||
<Route index element={<PatternNavigator />} />
|
</Route>
|
||||||
<Route path="new" element={<CreatePattern />} />
|
</Routes>
|
||||||
<Route path=":pattern" element={<PatternEditor />} />
|
</BrowserRouter>
|
||||||
</Route>
|
</EstimWatchProvider>
|
||||||
<Route path="/settings" element={<Settings />} />
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</BrowserRouter>
|
|
||||||
</EstimWatchProvider>
|
|
||||||
</Notifications>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user