mirror of
https://github.com/zadam/trilium.git
synced 2025-12-22 16:20:08 +01:00
chore(react/launch_bar): port sync status
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import SyncStatusWidget from "../sync_status.js";
|
|
||||||
import BasicWidget, { wrapReactWidgets } from "../basic_widget.js";
|
import BasicWidget, { wrapReactWidgets } from "../basic_widget.js";
|
||||||
import utils, { isMobile } from "../../services/utils.js";
|
import utils, { isMobile } from "../../services/utils.js";
|
||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
@@ -16,6 +15,7 @@ import { ParentComponent } from "../react/react_utils.jsx";
|
|||||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
import { LaunchBarActionButton, useLauncherIconAndTitle } from "../launch_bar/launch_bar_widgets.jsx";
|
import { LaunchBarActionButton, useLauncherIconAndTitle } from "../launch_bar/launch_bar_widgets.jsx";
|
||||||
import CalendarWidget from "../launch_bar/CalendarWidget.jsx";
|
import CalendarWidget from "../launch_bar/CalendarWidget.jsx";
|
||||||
|
import SyncStatus from "../launch_bar/SyncStatus.jsx";
|
||||||
|
|
||||||
interface InnerWidget extends BasicWidget {
|
interface InnerWidget extends BasicWidget {
|
||||||
settings?: {
|
settings?: {
|
||||||
@@ -106,11 +106,11 @@ export default class LauncherWidget extends BasicWidget {
|
|||||||
|
|
||||||
return <SpacerWidget baseSize={baseSize} growthFactor={growthFactor} />;
|
return <SpacerWidget baseSize={baseSize} growthFactor={growthFactor} />;
|
||||||
case "bookmarks":
|
case "bookmarks":
|
||||||
return <BookmarkButtons isHorizontalLayout={this.isHorizontalLayout} />
|
return <BookmarkButtons isHorizontalLayout={this.isHorizontalLayout} />;
|
||||||
case "protectedSession":
|
case "protectedSession":
|
||||||
return <ProtectedSessionStatusWidget />
|
return <ProtectedSessionStatusWidget />;
|
||||||
case "syncStatus":
|
case "syncStatus":
|
||||||
return new SyncStatusWidget();
|
return <SyncStatus />;
|
||||||
case "backInHistoryButton":
|
case "backInHistoryButton":
|
||||||
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />
|
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />
|
||||||
case "forwardInHistoryButton":
|
case "forwardInHistoryButton":
|
||||||
|
|||||||
26
apps/client/src/widgets/launch_bar/SyncStatus.css
Normal file
26
apps/client/src/widgets/launch_bar/SyncStatus.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.sync-status {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status .sync-status-icon {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: -5px;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status .sync-status-sub-icon {
|
||||||
|
font-size: 40%;
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status .sync-status-icon span {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status-icon:not(.sync-status-in-progress):hover {
|
||||||
|
background-color: var(--hover-item-background-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
118
apps/client/src/widgets/launch_bar/SyncStatus.tsx
Normal file
118
apps/client/src/widgets/launch_bar/SyncStatus.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
import "./SyncStatus.css";
|
||||||
|
import { t } from "../../services/i18n";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { escapeQuotes } from "../../services/utils";
|
||||||
|
import { useStaticTooltip, useTriliumOption } from "../react/hooks";
|
||||||
|
import sync from "../../services/sync";
|
||||||
|
import ws, { subscribeToMessages, unsubscribeToMessage } from "../../services/ws";
|
||||||
|
import { WebSocketMessage } from "@triliumnext/commons";
|
||||||
|
|
||||||
|
type SyncState = "unknown" | "in-progress"
|
||||||
|
| "connected-with-changes" | "connected-no-changes"
|
||||||
|
| "disconnected-with-changes" | "disconnected-no-changes";
|
||||||
|
|
||||||
|
interface StateMapping {
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATE_MAPPINGS: Record<SyncState, StateMapping> = {
|
||||||
|
unknown: {
|
||||||
|
title: t("sync_status.unknown"),
|
||||||
|
icon: "bx bx-time"
|
||||||
|
},
|
||||||
|
"connected-with-changes": {
|
||||||
|
title: t("sync_status.connected_with_changes"),
|
||||||
|
icon: "bx bx-wifi",
|
||||||
|
hasChanges: true
|
||||||
|
},
|
||||||
|
"connected-no-changes": {
|
||||||
|
title: t("sync_status.connected_no_changes"),
|
||||||
|
icon: "bx bx-wifi"
|
||||||
|
},
|
||||||
|
"disconnected-with-changes": {
|
||||||
|
title: t("sync_status.disconnected_with_changes"),
|
||||||
|
icon: "bx bx-wifi-off",
|
||||||
|
hasChanges: true
|
||||||
|
},
|
||||||
|
"disconnected-no-changes": {
|
||||||
|
title: t("sync_status.disconnected_no_changes"),
|
||||||
|
icon: "bx bx-wifi-off"
|
||||||
|
},
|
||||||
|
"in-progress": {
|
||||||
|
title: t("sync_status.in_progress"),
|
||||||
|
icon: "bx bx-analyse bx-spin"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SyncStatus() {
|
||||||
|
const syncState = useSyncStatus();
|
||||||
|
const { title, icon, hasChanges } = STATE_MAPPINGS[syncState];
|
||||||
|
const spanRef = useRef<HTMLSpanElement>(null);
|
||||||
|
const [ syncServerHost ] = useTriliumOption("syncServerHost");
|
||||||
|
useStaticTooltip(spanRef, {
|
||||||
|
html: true
|
||||||
|
// TODO: Placement
|
||||||
|
});
|
||||||
|
|
||||||
|
return (syncServerHost &&
|
||||||
|
<div class="sync-status-widget launcher-button">
|
||||||
|
<div class="sync-status">
|
||||||
|
<span
|
||||||
|
ref={spanRef}
|
||||||
|
className={clsx("sync-status-icon", `sync-status-${syncState}`, icon)}
|
||||||
|
title={escapeQuotes(title)}
|
||||||
|
onClick={() => {
|
||||||
|
if (syncState === "in-progress") return;
|
||||||
|
sync.syncNow();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hasChanges && (
|
||||||
|
<span class="bx bxs-star sync-status-sub-icon"></span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSyncStatus() {
|
||||||
|
const [ syncState, setSyncState ] = useState<SyncState>("unknown");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let lastSyncedPush: number;
|
||||||
|
|
||||||
|
function onMessage(message: WebSocketMessage) {
|
||||||
|
// First, read last synced push.
|
||||||
|
if ("lastSyncedPush" in message) {
|
||||||
|
lastSyncedPush = message.lastSyncedPush;
|
||||||
|
} else if ("data" in message && message.data && "lastSyncedPush" in message.data && lastSyncedPush) {
|
||||||
|
lastSyncedPush = message.data.lastSyncedPush;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if all changes were pushed.
|
||||||
|
const allChangesPushed = lastSyncedPush === ws.getMaxKnownEntityChangeSyncId();
|
||||||
|
|
||||||
|
let syncState: SyncState = "unknown";
|
||||||
|
if (message.type === "sync-pull-in-progress") {
|
||||||
|
syncState = "in-progress";
|
||||||
|
} else if (message.type === "sync-push-in-progress") {
|
||||||
|
syncState = "in-progress";
|
||||||
|
} else if (message.type === "sync-finished") {
|
||||||
|
syncState = allChangesPushed ? "connected-no-changes" : "connected-with-changes";
|
||||||
|
} else if (message.type === "sync-failed") {
|
||||||
|
syncState = allChangesPushed ? "disconnected-no-changes" : "disconnected-with-changes";
|
||||||
|
} else if (message.type === "frontend-update") {
|
||||||
|
lastSyncedPush = message.data.lastSyncedPush;
|
||||||
|
}
|
||||||
|
setSyncState(syncState);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToMessages(onMessage);
|
||||||
|
return () => unsubscribeToMessage(onMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return syncState;
|
||||||
|
}
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import { t } from "../services/i18n.js";
|
|
||||||
import BasicWidget from "./basic_widget.js";
|
|
||||||
import ws from "../services/ws.js";
|
|
||||||
import options from "../services/options.js";
|
|
||||||
import syncService from "../services/sync.js";
|
|
||||||
import { escapeQuotes, handleRightToLeftPlacement } from "../services/utils.js";
|
|
||||||
import { Tooltip } from "bootstrap";
|
|
||||||
import { WebSocketMessage } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="sync-status-widget launcher-button">
|
|
||||||
<style>
|
|
||||||
.sync-status-widget {
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-status {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-status .sync-status-icon {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
top: -5px;
|
|
||||||
font-size: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-status .sync-status-sub-icon {
|
|
||||||
font-size: 40%;
|
|
||||||
position: absolute;
|
|
||||||
inset-inline-start: 0;
|
|
||||||
top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-status .sync-status-icon span {
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-status-icon:not(.sync-status-in-progress):hover {
|
|
||||||
background-color: var(--hover-item-background-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="sync-status">
|
|
||||||
<span class="sync-status-icon sync-status-unknown bx bx-time"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.unknown"))}">
|
|
||||||
</span>
|
|
||||||
<span class="sync-status-icon sync-status-connected-with-changes bx bx-wifi"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.connected_with_changes"))}">
|
|
||||||
<span class="bx bxs-star sync-status-sub-icon"></span>
|
|
||||||
</span>
|
|
||||||
<span class="sync-status-icon sync-status-connected-no-changes bx bx-wifi"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.connected_no_changes"))}">
|
|
||||||
</span>
|
|
||||||
<span class="sync-status-icon sync-status-disconnected-with-changes bx bx-wifi-off"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.disconnected_with_changes"))}">
|
|
||||||
<span class="bx bxs-star sync-status-sub-icon"></span>
|
|
||||||
</span>
|
|
||||||
<span class="sync-status-icon sync-status-disconnected-no-changes bx bx-wifi-off"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.disconnected_no_changes"))}">
|
|
||||||
</span>
|
|
||||||
<span class="sync-status-icon sync-status-in-progress bx bx-analyse bx-spin"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
title="${escapeQuotes(t("sync_status.in_progress"))}">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default class SyncStatusWidget extends BasicWidget {
|
|
||||||
|
|
||||||
syncState: "unknown" | "in-progress" | "connected" | "disconnected";
|
|
||||||
allChangesPushed: boolean;
|
|
||||||
lastSyncedPush!: number;
|
|
||||||
settings: {
|
|
||||||
// TriliumNextTODO: narrow types and use TitlePlacement Type
|
|
||||||
titlePlacement: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.syncState = "unknown";
|
|
||||||
this.allChangesPushed = false;
|
|
||||||
this.settings = {
|
|
||||||
titlePlacement: "right"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.$widget.hide();
|
|
||||||
|
|
||||||
this.$widget.find(".sync-status-icon:not(.sync-status-in-progress)").on("click", () => syncService.syncNow());
|
|
||||||
|
|
||||||
ws.subscribeToMessages((message) => this.processMessage(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
showIcon(className: string) {
|
|
||||||
if (!options.get("syncServerHost")) {
|
|
||||||
this.toggleInt(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`)[0], {
|
|
||||||
html: true,
|
|
||||||
placement: handleRightToLeftPlacement(this.settings.titlePlacement),
|
|
||||||
fallbackPlacements: [ handleRightToLeftPlacement(this.settings.titlePlacement) ]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$widget.show();
|
|
||||||
this.$widget.find(".sync-status-icon").hide();
|
|
||||||
this.$widget.find(`.sync-status-${className}`).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
processMessage(message: WebSocketMessage) {
|
|
||||||
if (message.type === "sync-pull-in-progress") {
|
|
||||||
this.syncState = "in-progress";
|
|
||||||
this.lastSyncedPush = message.lastSyncedPush;
|
|
||||||
} else if (message.type === "sync-push-in-progress") {
|
|
||||||
this.syncState = "in-progress";
|
|
||||||
this.lastSyncedPush = message.lastSyncedPush;
|
|
||||||
} else if (message.type === "sync-finished") {
|
|
||||||
this.syncState = "connected";
|
|
||||||
this.lastSyncedPush = message.lastSyncedPush;
|
|
||||||
} else if (message.type === "sync-failed") {
|
|
||||||
this.syncState = "disconnected";
|
|
||||||
this.lastSyncedPush = message.lastSyncedPush;
|
|
||||||
} else if (message.type === "frontend-update") {
|
|
||||||
this.lastSyncedPush = message.data.lastSyncedPush;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.allChangesPushed = this.lastSyncedPush === ws.getMaxKnownEntityChangeSyncId();
|
|
||||||
|
|
||||||
if (["unknown", "in-progress"].includes(this.syncState)) {
|
|
||||||
this.showIcon(this.syncState);
|
|
||||||
} else {
|
|
||||||
this.showIcon(`${this.syncState}-${this.allChangesPushed ? "no-changes" : "with-changes"}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user