design: upgrade global design of the app
This commit is contained in:
74
src/components/ImageViewerWindow.tsx
Normal file
74
src/components/ImageViewerWindow.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import ResizeHandles from "./ResizeHandles";
|
||||
|
||||
export default function ImageViewerWindow() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const [imageUrl, setImageUrl] = useState(decodeURIComponent(params.get("imageUrl") ?? ""));
|
||||
const win = getCurrentWindow();
|
||||
|
||||
useEffect(() => {
|
||||
let debounce: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const unlistenImage = win.listen<string>("set-viewer-image", (event) => {
|
||||
setImageUrl(event.payload);
|
||||
});
|
||||
|
||||
const unlistenResize = win.onResized(async () => {
|
||||
if (debounce) clearTimeout(debounce);
|
||||
debounce = setTimeout(async () => {
|
||||
const [size, factor] = await Promise.all([win.innerSize(), win.scaleFactor()]);
|
||||
await invoke("set_setting", { key: "viewer_width", value: String(Math.round(size.width / factor)) });
|
||||
await invoke("set_setting", { key: "viewer_height", value: String(Math.round(size.height / factor)) });
|
||||
}, 500);
|
||||
});
|
||||
|
||||
const unlistenMove = win.onMoved(async () => {
|
||||
if (debounce) clearTimeout(debounce);
|
||||
debounce = setTimeout(async () => {
|
||||
const [pos, factor] = await Promise.all([win.outerPosition(), win.scaleFactor()]);
|
||||
await invoke("set_setting", { key: "viewer_x", value: String(Math.round(pos.x / factor)) });
|
||||
await invoke("set_setting", { key: "viewer_y", value: String(Math.round(pos.y / factor)) });
|
||||
}, 500);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlistenImage.then(f => f());
|
||||
unlistenResize.then(f => f());
|
||||
unlistenMove.then(f => f());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ height: "100vh", display: "flex", flexDirection: "column", background: "#0d1117", overflow: "hidden" }}>
|
||||
<ResizeHandles />
|
||||
<div
|
||||
onMouseDown={e => { if (e.button === 0) win.startDragging(); }}
|
||||
style={{
|
||||
height: "28px",
|
||||
background: "#161b22",
|
||||
borderBottom: "1px solid #2d3748",
|
||||
cursor: "grab",
|
||||
flexShrink: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
paddingLeft: "12px",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: "11px", color: "#4a5568", pointerEvents: "none" }}>⠿ Image</span>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
|
||||
{imageUrl && (
|
||||
<img
|
||||
src={imageUrl}
|
||||
style={{ width: "100%", display: "block" }}
|
||||
draggable={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -155,19 +155,21 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
||||
if (done) {
|
||||
const firstLine = step.text.split('\n').find(l => l.trim().length > 0) ?? step.text;
|
||||
return (
|
||||
<div key={step.index} style={{
|
||||
<div key={step.index} onClick={() => toggleStep(step.index)} style={{
|
||||
marginBottom: "4px",
|
||||
padding: "6px 12px",
|
||||
border: "1px solid rgba(74,222,128,0.1)",
|
||||
borderRadius: "7px",
|
||||
background: "rgba(74,222,128,0.03)",
|
||||
opacity: 0.5,
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={true}
|
||||
onChange={() => toggleStep(step.index)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
style={{ flexShrink: 0, cursor: "pointer" }}
|
||||
/>
|
||||
<span style={{
|
||||
@ -190,18 +192,20 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
||||
: step.text;
|
||||
|
||||
return (
|
||||
<div key={step.index} style={{
|
||||
<div key={step.index} onClick={() => toggleStep(step.index)} style={{
|
||||
marginBottom: "8px",
|
||||
background: "rgba(255,255,255,0.02)",
|
||||
border: "1px solid #2d3748",
|
||||
borderRadius: "7px", padding: "10px 12px",
|
||||
transition: "all 0.15s",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "10px", alignItems: "flex-start" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={false}
|
||||
onChange={() => toggleStep(step.index)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
style={{ marginTop: "2px", flexShrink: 0, cursor: "pointer" }}
|
||||
/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
@ -214,7 +218,7 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
||||
|
||||
{needsTruncate && (
|
||||
<button
|
||||
onClick={() => toggleExpanded(step.index)}
|
||||
onClick={e => { e.stopPropagation(); toggleExpanded(step.index); }}
|
||||
style={{
|
||||
marginTop: "4px", background: "transparent", border: "none",
|
||||
color: "#4a9eff", fontSize: "11px", cursor: "pointer",
|
||||
@ -231,7 +235,7 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
|
||||
<img
|
||||
key={j}
|
||||
src={src}
|
||||
onClick={() => openUrl(src)}
|
||||
onClick={e => { e.stopPropagation(); invoke("open_image_viewer", { imageUrl: src }); }}
|
||||
style={{
|
||||
maxWidth: "100%", height: "auto",
|
||||
borderRadius: "6px", display: "block",
|
||||
@ -279,7 +283,7 @@ function QuestHeader({ step }: { step: QuestStep }) {
|
||||
<img
|
||||
key={j}
|
||||
src={src}
|
||||
onClick={() => openUrl(src)}
|
||||
onClick={() => invoke("open_image_viewer", { imageUrl: src })}
|
||||
style={{
|
||||
maxWidth: "100%", height: "auto",
|
||||
borderRadius: "6px", display: "block",
|
||||
|
||||
31
src/components/ResizeHandles.tsx
Normal file
31
src/components/ResizeHandles.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import type { ResizeEdge } from "@tauri-apps/api/window";
|
||||
|
||||
const S = 8; // grab zone size in px
|
||||
|
||||
const handles: { edge: ResizeEdge; style: React.CSSProperties }[] = [
|
||||
{ edge: "North", style: { top: 0, left: S, right: S, height: S, cursor: "n-resize" } },
|
||||
{ edge: "South", style: { bottom: 0, left: S, right: S, height: S, cursor: "s-resize" } },
|
||||
{ edge: "West", style: { top: S, left: 0, bottom: S, width: S, cursor: "w-resize" } },
|
||||
{ edge: "East", style: { top: S, right: 0, bottom: S, width: S, cursor: "e-resize" } },
|
||||
{ edge: "NorthWest", style: { top: 0, left: 0, width: S, height: S, cursor: "nw-resize" } },
|
||||
{ edge: "NorthEast", style: { top: 0, right: 0, width: S, height: S, cursor: "ne-resize" } },
|
||||
{ edge: "SouthWest", style: { bottom: 0, left: 0, width: S, height: S, cursor: "sw-resize" } },
|
||||
{ edge: "SouthEast", style: { bottom: 0, right: 0,width: S, height: S, cursor: "se-resize" } },
|
||||
];
|
||||
|
||||
export default function ResizeHandles() {
|
||||
const win = getCurrentWindow();
|
||||
|
||||
return (
|
||||
<>
|
||||
{handles.map(({ edge, style }) => (
|
||||
<div
|
||||
key={edge}
|
||||
onMouseDown={e => { if (e.button === 0) win.startResizeDragging(edge); }}
|
||||
style={{ position: "fixed", zIndex: 9999, ...style }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useStore } from "../store";
|
||||
|
||||
function useWindowWidth() {
|
||||
@ -27,6 +28,10 @@ export default function TitleBar({ onOpenSettings }: Props) {
|
||||
}
|
||||
|
||||
async function handleClose() {
|
||||
const viewer = await WebviewWindow.getByLabel("image-viewer");
|
||||
if (viewer) {
|
||||
try { await viewer.close(); } catch (_) {}
|
||||
}
|
||||
await getCurrentWindow().close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user