mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-10 03:53:37 -06:00
chore(toast): port toast to react
This commit is contained in:
parent
7a3092a23b
commit
f053587f09
@ -27,6 +27,7 @@
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mind-elixir/node-menu": "5.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@preact/signals": "2.5.1",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
|
||||
@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js";
|
||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
|
||||
import ToastContainer from "../widgets/Toast.jsx";
|
||||
|
||||
export function applyModals(rootContainer: RootContainer) {
|
||||
rootContainer
|
||||
@ -50,5 +51,6 @@ export function applyModals(rootContainer: RootContainer) {
|
||||
.child(<PromptDialog />)
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(<PopupEditorDialog />)
|
||||
.child(<CallToActionDialog />);
|
||||
.child(<CallToActionDialog />)
|
||||
.child(<ToastContainer />)
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
import utils from "./utils.js";
|
||||
|
||||
export interface ToastOptions {
|
||||
@ -11,83 +13,28 @@ export interface ToastOptions {
|
||||
progress?: number;
|
||||
}
|
||||
|
||||
function toast({ title, icon, message, id, delay, autohide, progress }: ToastOptions) {
|
||||
const $toast = $(title
|
||||
? `\
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
<span class="bx bx-${icon}"></span>
|
||||
<span class="toast-title"></span>
|
||||
</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>`
|
||||
: `
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-icon">
|
||||
<span class="bx bx-${icon}"></span>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
<div class="toast-header">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>`
|
||||
);
|
||||
export type ToastOptionsWithRequiredId = Omit<ToastOptions, "id"> & Required<Pick<ToastOptions, "id">>;
|
||||
|
||||
$toast.toggleClass("no-title", !title);
|
||||
$toast.find(".toast-title").text(title ?? "");
|
||||
$toast.find(".toast-body").html(message);
|
||||
$toast.find(".toast-progress").css("width", `${(progress ?? 0) * 100}%`);
|
||||
|
||||
if (id) {
|
||||
$toast.attr("id", `toast-${id}`);
|
||||
}
|
||||
|
||||
$("#toast-container").append($toast);
|
||||
|
||||
$toast.toast({
|
||||
delay: delay || 3000,
|
||||
autohide: !!autohide
|
||||
});
|
||||
|
||||
$toast.on("hidden.bs.toast", (e) => e.target.remove());
|
||||
|
||||
$toast.toast("show");
|
||||
|
||||
return $toast;
|
||||
}
|
||||
|
||||
function showPersistent(options: ToastOptions) {
|
||||
let $toast = $(`#toast-${options.id}`);
|
||||
|
||||
if ($toast.length > 0) {
|
||||
$toast.find(".toast-body").html(options.message);
|
||||
$toast.find(".toast-progress").css("width", `${(options.progress ?? 0) * 100}%`);
|
||||
function showPersistent(options: ToastOptionsWithRequiredId) {
|
||||
const existingToast = toasts.value.find(toast => toast.id === options.id);
|
||||
if (existingToast) {
|
||||
updateToast(options.id, options);
|
||||
} else {
|
||||
options.autohide = false;
|
||||
|
||||
$toast = toast(options);
|
||||
}
|
||||
|
||||
if (options.closeAfter) {
|
||||
setTimeout(() => $toast.remove(), options.closeAfter);
|
||||
addToast(options);
|
||||
}
|
||||
}
|
||||
|
||||
function closePersistent(id: string) {
|
||||
$(`#toast-${id}`).remove();
|
||||
removeToastFromStore(id);
|
||||
}
|
||||
|
||||
function showMessage(message: string, delay = 2000, icon = "check") {
|
||||
console.debug(utils.now(), "message:", message);
|
||||
|
||||
toast({
|
||||
addToast({
|
||||
icon,
|
||||
message: message,
|
||||
message,
|
||||
autohide: true,
|
||||
delay
|
||||
});
|
||||
@ -96,26 +43,50 @@ function showMessage(message: string, delay = 2000, icon = "check") {
|
||||
export function showError(message: string, delay = 10000) {
|
||||
console.log(utils.now(), "error: ", message);
|
||||
|
||||
toast({
|
||||
addToast({
|
||||
icon: "bx bx-error-circle",
|
||||
message: message,
|
||||
message,
|
||||
autohide: true,
|
||||
delay
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function showErrorTitleAndMessage(title: string, message: string, delay = 10000) {
|
||||
console.log(utils.now(), "error: ", message);
|
||||
|
||||
toast({
|
||||
title: title,
|
||||
addToast({
|
||||
title,
|
||||
icon: "bx bx-error-circle",
|
||||
message: message,
|
||||
message,
|
||||
autohide: true,
|
||||
delay
|
||||
});
|
||||
}
|
||||
|
||||
//#region Toast store
|
||||
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
|
||||
|
||||
function addToast(opts: ToastOptions) {
|
||||
const id = opts.id ?? crypto.randomUUID();
|
||||
const toast = { ...opts, id };
|
||||
toasts.value = [ ...toasts.value, toast ];
|
||||
return id;
|
||||
}
|
||||
|
||||
function updateToast(id: string, partial: Partial<ToastOptions>) {
|
||||
toasts.value = toasts.value.map(toast => {
|
||||
if (toast.id === id) {
|
||||
return { ...toast, ...partial }
|
||||
}
|
||||
return toast;
|
||||
})
|
||||
}
|
||||
|
||||
export function removeToastFromStore(id: string) {
|
||||
toasts.value = toasts.value.filter(toast => toast.id !== id);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export default {
|
||||
showMessage,
|
||||
showError,
|
||||
|
||||
@ -1135,13 +1135,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
#toast-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
--bs-toast-bg: var(--accented-background-color);
|
||||
--bs-toast-color: var(--main-text-color);
|
||||
|
||||
11
apps/client/src/widgets/Toast.css
Normal file
11
apps/client/src/widgets/Toast.css
Normal file
@ -0,0 +1,11 @@
|
||||
#toast-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 20px;
|
||||
pointer-events: none;
|
||||
contain: none;
|
||||
}
|
||||
58
apps/client/src/widgets/Toast.tsx
Normal file
58
apps/client/src/widgets/Toast.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import "./Toast.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect } from "preact/hooks";
|
||||
|
||||
import { removeToastFromStore, ToastOptionsWithRequiredId, toasts } from "../services/toast";
|
||||
import Icon from "./react/Icon";
|
||||
import { RawHtmlBlock } from "./react/RawHtml";
|
||||
|
||||
const DEFAULT_DELAY = 3_000;
|
||||
|
||||
export default function ToastContainer() {
|
||||
return (
|
||||
<div id="toast-container">
|
||||
{toasts.value.map(toast => <Toast key={toast.id} {...toast} />)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Toast({ id, title, autohide, delay, progress, message, icon }: ToastOptionsWithRequiredId) {
|
||||
// Autohide.
|
||||
useEffect(() => {
|
||||
if (!autohide || !id) return;
|
||||
const timeout = setTimeout(() => removeToastFromStore(id), delay || DEFAULT_DELAY);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ autohide, id, delay ]);
|
||||
|
||||
const closeButton = <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close" />;
|
||||
const toastIcon = <Icon icon={icon} />;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={clsx("toast", !title && "no-title")}
|
||||
role="alert" aria-live="assertive" aria-atomic="true"
|
||||
id={`toast-${id}`}
|
||||
>
|
||||
{title ? (
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
{toastIcon}
|
||||
<span class="toast-title">{title}</span>
|
||||
</strong>
|
||||
{closeButton}
|
||||
</div>
|
||||
) : (
|
||||
<div class="toast-icon">{toastIcon}</div>
|
||||
)}
|
||||
|
||||
<RawHtmlBlock className="toast-body" html={message} />
|
||||
|
||||
{!title && <div class="toast-header">{closeButton}</div>}
|
||||
<div
|
||||
class="toast-progress"
|
||||
style={{ width: `${(progress ?? 0) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -22,8 +22,6 @@
|
||||
document.getElementsByTagName("body")[0].style.display = "none";
|
||||
</script>
|
||||
|
||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||
|
||||
<%- include("./partials/windowGlobal.ejs", locals) %>
|
||||
|
||||
@ -105,8 +105,6 @@
|
||||
>
|
||||
<noscript><%= t("javascript-required") %></noscript>
|
||||
|
||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||
|
||||
<div id="context-menu-cover"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -190,6 +190,9 @@ importers:
|
||||
'@popperjs/core':
|
||||
specifier: 2.11.8
|
||||
version: 2.11.8
|
||||
'@preact/signals':
|
||||
specifier: 2.5.1
|
||||
version: 2.5.1(preact@10.28.0)
|
||||
'@triliumnext/ckeditor5':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ckeditor5
|
||||
@ -3897,6 +3900,14 @@ packages:
|
||||
'@babel/core': 7.x
|
||||
vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x
|
||||
|
||||
'@preact/signals-core@1.12.1':
|
||||
resolution: {integrity: sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==}
|
||||
|
||||
'@preact/signals@2.5.1':
|
||||
resolution: {integrity: sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==}
|
||||
peerDependencies:
|
||||
preact: 10.28.0
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2':
|
||||
resolution: {integrity: sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==}
|
||||
|
||||
@ -15268,8 +15279,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@ -15488,8 +15497,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
|
||||
dependencies:
|
||||
@ -15512,8 +15519,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-table': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-emoji@47.3.0':
|
||||
dependencies:
|
||||
@ -18466,6 +18471,13 @@ snapshots:
|
||||
- preact
|
||||
- supports-color
|
||||
|
||||
'@preact/signals-core@1.12.1': {}
|
||||
|
||||
'@preact/signals@2.5.1(preact@10.28.0)':
|
||||
dependencies:
|
||||
'@preact/signals-core': 1.12.1
|
||||
preact: 10.28.0
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2': {}
|
||||
|
||||
'@prefresh/core@1.5.5(preact@10.28.0)':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user