diff --git a/editors/vscode/src/features/preview.ts b/editors/vscode/src/features/preview.ts index 0e948469..cb6f527f 100644 --- a/editors/vscode/src/features/preview.ts +++ b/editors/vscode/src/features/preview.ts @@ -96,7 +96,7 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool ); const launchBrowsingPreview = launch("webview", "doc", { isBrowsing: true }); - const launchDevPreview = launch("webview", "doc", { isDev: true }); + const launchDevPreview = (mode: "doc" | "slide") => launch("webview", mode, { isDev: true }); // Registers preview commands, check `package.json` for descriptions. context.subscriptions.push( vscode.commands.registerCommand("tinymist.browsingPreview", launchBrowsingPreview), @@ -104,16 +104,19 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool vscode.commands.registerCommand("typst-preview.browser", launch("browser", "doc")), vscode.commands.registerCommand("typst-preview.preview-slide", launch("webview", "slide")), vscode.commands.registerCommand("typst-preview.browser-slide", launch("browser", "slide")), - vscode.commands.registerCommand("typst-preview.eject", isCompat ? ejectPreviewPanelCompat : ejectPreviewPanelLsp), - vscode.commands.registerCommand("tinymist.previewDev", launchDevPreview), - vscode.commands.registerCommand( - "typst-preview.revealDocument", - isCompat ? revealDocumentCompat : revealDocumentLsp, - ), - vscode.commands.registerCommand( - "typst-preview.sync", - isCompat ? panelSyncScrollCompat : panelSyncScrollLsp, - ), + vscode.commands.registerCommand("tinymist.previewDev", launchDevPreview("doc")), + vscode.commands.registerCommand("tinymist.previewDevSlide", launchDevPreview("slide")), + ...(isCompat + ? [ + vscode.commands.registerCommand("typst-preview.eject", ejectPreviewPanelCompat), + vscode.commands.registerCommand("typst-preview.revealDocument", revealDocumentCompat), + vscode.commands.registerCommand("typst-preview.sync", panelSyncScrollCompat), + ] + : [ + vscode.commands.registerCommand("typst-preview.eject", ejectPreviewPanelLsp), + vscode.commands.registerCommand("typst-preview.revealDocument", revealDocumentLsp), + vscode.commands.registerCommand("typst-preview.sync", panelSyncScrollLsp), + ]), vscode.commands.registerCommand("tinymist.doInspectPreviewState", () => { const tasks = Array.from(activeTask.values()).map((t) => { return { @@ -186,18 +189,23 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool }; } - async function launchForURI(uri: vscode.Uri, kind: "browser" | "webview", mode: "doc" | "slide", opts?: LaunchOpts) { + async function launchForURI( + uri: vscode.Uri, + kind: "browser" | "webview", + mode: "doc" | "slide", + opts?: LaunchOpts, + ) { const doc = vscode.workspace.textDocuments.find((doc) => { return doc.uri.toString() === uri.toString(); }) || (await vscode.workspace.openTextDocument(uri)); const editor = await vscode.window.showTextDocument(doc, getSensibleTextEditorColumn(), true); - + const bindDocument = editor.document; const isBrowsing = opts?.isBrowsing; const isDev = opts?.isDev; const isNotPrimary = opts?.isNotPrimary; - + await launchImpl({ kind, context, @@ -209,7 +217,7 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool isNotPrimary, }); } - + /** * Ejects the preview panel to the external browser. */ @@ -220,10 +228,10 @@ export function previewActivate(context: vscode.ExtensionContext, isCompat: bool return; } const { panel, state } = focusingContext; - + // Close the preview panel, basically kill the previous preview task. panel.dispose(); - + await launchForURI(vscode.Uri.parse(state.uri), "browser", state.mode, { isBrowsing: state.isBrowsing, isDev: state.isDev, @@ -318,7 +326,7 @@ export async function openPreviewInWebView({ uri: activeEditor.document.uri.toString(), }; - const updateActivePanel =() => { + const updateActivePanel = () => { if (panel.active) { extensionState.mut.focusingPreviewPanelContext = { panel, diff --git a/tests/workspaces/slides/touying.typ b/tests/workspaces/slides/touying.typ new file mode 100644 index 00000000..590a8383 --- /dev/null +++ b/tests/workspaces/slides/touying.typ @@ -0,0 +1,20 @@ +#import "@preview/touying:0.6.1": * +#import themes.dewdrop: * + +#show: dewdrop-theme.with(aspect-ratio: "16-9") + += Section 1 + +== Subsection 1 + +=== Title + +Hello, Touying! + += Section 2 + +== Subsection 2 + +=== Title + +Hello, Touying! diff --git a/tools/typst-dom/src/typst-debug-info.mts b/tools/typst-dom/src/typst-debug-info.mts index 31b2d806..25945126 100644 --- a/tools/typst-dom/src/typst-debug-info.mts +++ b/tools/typst-dom/src/typst-debug-info.mts @@ -1,5 +1,5 @@ import { triggerRipple } from "./typst-animation.mjs"; -import type { GConstructor, TypstDocumentContext } from "./typst-doc.mjs"; +import { PreviewMode, type GConstructor, type TypstDocumentContext } from "./typst-doc.mjs"; const enum SourceMappingType { Text = 0, @@ -225,5 +225,83 @@ export function provideDebugJumpDoc= 90 || widthOccupied < 50) { + window.scrollTo({ behavior: "smooth", left: xOffset, top: yOffset }); + } else { + // for double-column layout + // console.log('occupied adjustment', widthOccupied, page); + + const xOffsetAdjsut = xOffset > pageAdjust ? pageAdjust : pageAdjustLeft; + + window.scrollTo({ behavior: "smooth", left: xOffsetAdjsut, top: yOffset }); + } + + // grid ripple for debug vw + // triggerRipple( + // windowRoot, + // svgRect.left + 50 * vw, + // svgRect.top + 1 * vh, + // "typst-jump-ripple", + // "typst-jump-ripple-effect .4s linear", + // "green", + // ); + + // triggerRipple( + // windowRoot, + // pageRect.left - basePos.left + vw, + // pageRect.top - basePos.top + vh, + // "typst-jump-ripple", + // "typst-jump-ripple-effect .4s linear", + // "red", + // ); + + // triggerRipple( + // windowRoot, + // pageAdjust, + // pageRect.top - basePos.top + vh, + // "typst-jump-ripple", + // "typst-jump-ripple-effect .4s linear", + // "red", + // ); + + triggerRipple( + windowRoot, + left, + top, + "typst-jump-ripple", + "typst-jump-ripple-effect .4s linear", + ); + } }; } + +type ScrollRect = Pick; diff --git a/tools/typst-dom/src/typst-doc.mts b/tools/typst-dom/src/typst-doc.mts index ee480fb3..4a56dbe9 100644 --- a/tools/typst-dom/src/typst-doc.mts +++ b/tools/typst-dom/src/typst-doc.mts @@ -463,6 +463,19 @@ export class TypstDocumentContext { addViewportChange() { this.addChangement(["viewport-change", ""]); } + + setPartialPageNumber(page: number): boolean { + if (page <= 0 || page > this.kModule.retrievePagesInfo().length) { + return false; + } + this.partialRenderPage = page - 1; + this.addViewportChange(); + return true; + } + + getPartialPageNumber(): number { + return this.partialRenderPage + 1; + } } export interface TypstDocument { @@ -536,16 +549,11 @@ export function provideDoc( } setPartialPageNumber(page: number): boolean { - if (page <= 0 || page > this.kModule.retrievePagesInfo().length) { - return false; - } - this.impl.partialRenderPage = page - 1; - this.addViewportChange(); - return true; + return this.impl.setPartialPageNumber(page); } getPartialPageNumber(): number { - return this.impl.partialRenderPage + 1; + return this.impl.getPartialPageNumber(); } setOutineData(outline: any) { diff --git a/tools/typst-dom/src/typst-doc.svg.mts b/tools/typst-dom/src/typst-doc.svg.mts index c91b5554..fe53e031 100644 --- a/tools/typst-dom/src/typst-doc.svg.mts +++ b/tools/typst-dom/src/typst-doc.svg.mts @@ -350,7 +350,7 @@ export function provideSvgDoc< const height = Number.parseFloat(elem.getAttribute("data-page-height")!); maxWidth = Math.max(maxWidth, width); return { - index, + index: mode === PreviewMode.Slide ? this.partialRenderPage : index, elem, width, height, diff --git a/tools/typst-preview-frontend/src/global.d.ts b/tools/typst-preview-frontend/src/global.d.ts index 48e11dab..42722383 100644 --- a/tools/typst-preview-frontend/src/global.d.ts +++ b/tools/typst-preview-frontend/src/global.d.ts @@ -8,6 +8,7 @@ interface Window { initTypstSvg(docRoot: SVGElement): void; currentPosition(elem: Element): TypstPosition | undefined; handleTypstLocation(elem: Element, page: number, x: number, y: number); + documents: any[]; typstWebsocket: WebSocket; } const acquireVsCodeApi: any; diff --git a/tools/typst-preview-frontend/src/main.js b/tools/typst-preview-frontend/src/main.js index 94e22978..6056d670 100644 --- a/tools/typst-preview-frontend/src/main.js +++ b/tools/typst-preview-frontend/src/main.js @@ -9,6 +9,8 @@ import "./styles/outline.css"; import { wsMain, PreviewMode } from "./ws"; import { setupDrag } from "./drag"; +window.documents = []; + /// Main entry point of the frontend program. main(); diff --git a/tools/typst-preview-frontend/src/typst.ts b/tools/typst-preview-frontend/src/typst.ts index 6c11e958..dab59c38 100644 --- a/tools/typst-preview-frontend/src/typst.ts +++ b/tools/typst-preview-frontend/src/typst.ts @@ -1,5 +1,3 @@ -import { triggerRipple } from "typst-dom/typst-animation.mjs"; - // debounce https://stackoverflow.com/questions/23181243/throttling-a-mousemove-event-to-fire-no-more-than-5-times-a-second // ignore fast events, good for capturing double click // @param (callback): function to be run when done @@ -240,6 +238,7 @@ window.currentPosition = function (elem: Element) { return result; }; +type ScrollRect = Pick; window.handleTypstLocation = function (elem: Element, pageNo: number, x: number, y: number) { const docRoot = findAncestor(elem, "typst-doc"); if (!docRoot) { @@ -247,76 +246,12 @@ window.handleTypstLocation = function (elem: Element, pageNo: number, x: number, return; } - type ScrollRect = Pick; - const scrollTo = (pageRect: ScrollRect, innerLeft: number, innerTop: number) => { - const windowRoot = document.body || document.firstElementChild; - const basePos = windowRoot.getBoundingClientRect(); + // scrollTo(pageRect: ScrollRect, pageNo: number, innerLeft: number, innerTop: number) - const left = innerLeft - basePos.left; - const top = innerTop - basePos.top; - - // evaluate window viewport 1vw - const pw = window.innerWidth * 0.01; - const ph = window.innerHeight * 0.01; - - const xOffsetInnerFix = 7 * pw; - const yOffsetInnerFix = 38.2 * ph; - - const xOffset = left - xOffsetInnerFix; - const yOffset = top - yOffsetInnerFix; - - const widthOccupied = (100 * 100 * pw) / pageRect.width; - - const pageAdjustLeft = pageRect.left - basePos.left - 5 * pw; - const pageAdjust = pageRect.left - basePos.left + pageRect.width - 95 * pw; - - // default single-column or multi-column layout - if (widthOccupied >= 90 || widthOccupied < 50) { - window.scrollTo({ behavior: "smooth", left: xOffset, top: yOffset }); - } else { - // for double-column layout - // console.log('occupied adjustment', widthOccupied, page); - - const xOffsetAdjsut = xOffset > pageAdjust ? pageAdjust : pageAdjustLeft; - - window.scrollTo({ behavior: "smooth", left: xOffsetAdjsut, top: yOffset }); + const scrollTo = (pageRect: ScrollRect, pageNo: number, innerLeft: number, innerTop: number) => { + for (const doc of window.documents) { + doc.impl.scrollTo(pageRect, pageNo, innerLeft, innerTop); } - - // grid ripple for debug vw - // triggerRipple( - // windowRoot, - // svgRect.left + 50 * vw, - // svgRect.top + 1 * vh, - // "typst-jump-ripple", - // "typst-jump-ripple-effect .4s linear", - // "green", - // ); - - // triggerRipple( - // windowRoot, - // pageRect.left - basePos.left + vw, - // pageRect.top - basePos.top + vh, - // "typst-jump-ripple", - // "typst-jump-ripple-effect .4s linear", - // "red", - // ); - - // triggerRipple( - // windowRoot, - // pageAdjust, - // pageRect.top - basePos.top + vh, - // "typst-jump-ripple", - // "typst-jump-ripple-effect .4s linear", - // "red", - // ); - - triggerRipple( - windowRoot, - left, - top, - "typst-jump-ripple", - "typst-jump-ripple-effect .4s linear", - ); }; const renderMode = docRoot.getAttribute("data-render-mode"); @@ -359,7 +294,7 @@ window.handleTypstLocation = function (elem: Element, pageNo: number, x: number, console.log("canvas mode jump", left, top, canvasRect, dataWidth, dataHeight, x, y); - scrollTo(canvasRect, left, top); + scrollTo(canvasRect, pageNo, left, top); return; } @@ -394,7 +329,7 @@ window.handleTypstLocation = function (elem: Element, pageNo: number, x: number, const left = svgRect.left + (x / dataWidth) * svgRect.width; const top = svgRect.top + (y / dataHeight) * svgRect.height; - scrollTo(pageRect, left, top); + scrollTo(pageRect, pageNo, left, top); return; } } diff --git a/tools/typst-preview-frontend/src/ws.ts b/tools/typst-preview-frontend/src/ws.ts index 792ce0d4..2aa9134a 100644 --- a/tools/typst-preview-frontend/src/ws.ts +++ b/tools/typst-preview-frontend/src/ws.ts @@ -218,6 +218,8 @@ export async function wsMain({ url, previewMode, isContentPreview }: WsArgs) { } function setupSocket(svgDoc: TypstDocument): () => void { + window.documents.push(svgDoc); + // todo: reconnect setTimeout(() => setupSocket(svgDoc), 1000); $ws = webSocket({ url, @@ -249,6 +251,10 @@ export async function wsMain({ url, previewMode, isContentPreview }: WsArgs) { const dispose = () => { disposed = true; svgDoc.dispose(); + const index = window.documents.indexOf(svgDoc); + if (index >= 0) { + window.documents.splice(index, 1); + } for (const sub of subsribes.splice(0, subsribes.length)) { sub.unsubscribe(); }