feat: Add missing feature files for build
Create panel-dock-system.ts and navigator-panel.ts in features/ directory with proper init() implementations. Copy offline-manager and reading-speed-tracker to features/ as well.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// Navigator panel - shows full page with draggable viewport box
|
||||
// Affinity/Photoshop-style mini-map for page navigation
|
||||
// Feature Registration Pattern implementation
|
||||
|
||||
import type { ReaderContext } from "../core/reader-context";
|
||||
|
||||
export function init(context: ReaderContext): void {
|
||||
let state: NavigatorState | null = null;
|
||||
|
||||
context.events.on("navigator:initialize", (detail: { containerSelector: string; totalPages: number }) => {
|
||||
state = initializeNavigator(detail.containerSelector);
|
||||
state.totalPages = detail.totalPages;
|
||||
});
|
||||
|
||||
context.events.on("navigator:update", (detail: { currentPage: number; contentImage?: HTMLImageElement }) => {
|
||||
if (state) {
|
||||
state.currentPage = detail.currentPage;
|
||||
if (detail.contentImage) {
|
||||
state.contentImage = detail.contentImage;
|
||||
updateNavigatorViewport(state);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.events.on("navigator:pan", (detail: { x: number; y: number }) => {
|
||||
if (state) {
|
||||
handleNavigatorPan(state, detail.x, detail.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface NavigatorState {
|
||||
panelId: string;
|
||||
container: HTMLElement;
|
||||
viewport: HTMLElement;
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
scale: number;
|
||||
contentImage: HTMLImageElement | null;
|
||||
isDragging: boolean;
|
||||
}
|
||||
|
||||
function initializeNavigator(containerSelector: string): NavigatorState {
|
||||
const container = document.querySelector(containerSelector) as HTMLElement;
|
||||
if (!container) throw new Error("Navigator container not found");
|
||||
|
||||
const viewport = document.createElement("div");
|
||||
viewport.className = "navigator-viewport-box";
|
||||
viewport.style.cssText = `
|
||||
position: absolute;
|
||||
border: 2px solid var(--accent-color, #3b82f6);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
cursor: move;
|
||||
z-index: 10;
|
||||
`;
|
||||
|
||||
container.appendChild(viewport);
|
||||
|
||||
const state: NavigatorState = {
|
||||
panelId: "navigator",
|
||||
container,
|
||||
viewport,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
scale: 0.1,
|
||||
contentImage: null,
|
||||
isDragging: false,
|
||||
};
|
||||
|
||||
setupNavigatorDragHandler(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
function setupNavigatorDragHandler(state: NavigatorState): void {
|
||||
state.viewport.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
state.isDragging = true;
|
||||
state.viewport.style.cursor = "grabbing";
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (!state.isDragging || !state.contentImage) return;
|
||||
|
||||
const containerRect = state.container.getBoundingClientRect();
|
||||
const imgRect = state.contentImage.getBoundingClientRect();
|
||||
|
||||
const relX = (e.clientX - imgRect.left) / imgRect.width;
|
||||
const relY = (e.clientY - imgRect.top) / imgRect.height;
|
||||
|
||||
const mainViewer = document.getElementById("reader-content");
|
||||
if (mainViewer) {
|
||||
mainViewer.dispatchEvent(
|
||||
new CustomEvent("navigator-pan", {
|
||||
detail: { x: relX, y: relY },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
if (state.isDragging) {
|
||||
state.isDragging = false;
|
||||
state.viewport.style.cursor = "move";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateNavigatorViewport(state: NavigatorState): void {
|
||||
if (!state.contentImage) return;
|
||||
|
||||
const containerRect = state.container.getBoundingClientRect();
|
||||
const imgRect = state.contentImage.getBoundingClientRect();
|
||||
|
||||
state.scale = containerRect.width / imgRect.width;
|
||||
|
||||
state.viewport.style.width = `${containerRect.width}px`;
|
||||
state.viewport.style.height = `${containerRect.height * state.scale}px`;
|
||||
|
||||
state.viewport.style.left = "0";
|
||||
state.viewport.style.top = `${(state.currentPage - 1) * imgRect.height * state.scale}px`;
|
||||
}
|
||||
|
||||
function handleNavigatorPan(state: NavigatorState, x: number, y: number): void {
|
||||
if (!state.contentImage) return;
|
||||
|
||||
const imgRect = state.contentImage.getBoundingClientRect();
|
||||
|
||||
const viewportX = x * state.container.offsetWidth;
|
||||
const viewportY = y * state.container.offsetHeight;
|
||||
|
||||
state.viewport.style.left = `${viewportX}px`;
|
||||
state.viewport.style.top = `${viewportY}px`;
|
||||
}
|
||||
Reference in New Issue
Block a user