2025-11-24 14:01:48 +01:00
|
|
|
<script setup>
|
|
|
|
|
import PagedJsWrapper from './components/PagedJsWrapper.vue';
|
2025-12-03 15:20:49 +01:00
|
|
|
import EditorPanel from './components/editor/EditorPanel.vue';
|
2025-11-24 16:51:55 +01:00
|
|
|
import ElementPopup from './components/ElementPopup.vue';
|
2026-03-23 11:13:50 +01:00
|
|
|
import ImagePopup from './components/ImagePopup.vue';
|
2026-02-24 13:37:07 +01:00
|
|
|
// import PagePopup from './components/PagePopup.vue'; // DISABLED: page template styling feature
|
2025-12-05 16:45:57 +01:00
|
|
|
import PreviewLoader from './components/PreviewLoader.vue';
|
2026-01-09 13:39:25 +01:00
|
|
|
import SaveButton from './components/SaveButton.vue';
|
2026-03-06 17:18:04 +01:00
|
|
|
import PrintButton from './components/PrintButton.vue';
|
2026-03-08 08:44:39 +01:00
|
|
|
import ZoomControls from './components/ui/ZoomControls.vue';
|
2026-01-09 17:20:10 +01:00
|
|
|
import { onMounted, ref, computed, provide } from 'vue';
|
2025-11-24 17:55:42 +01:00
|
|
|
import { useStylesheetStore } from './stores/stylesheet';
|
2026-01-09 10:34:10 +01:00
|
|
|
import { useNarrativeStore } from './stores/narrative';
|
2026-01-09 17:20:10 +01:00
|
|
|
import { useKeyboardShortcuts } from './composables/useKeyboardShortcuts';
|
|
|
|
|
import { useIframeInteractions } from './composables/useIframeInteractions';
|
|
|
|
|
import { usePreviewRenderer } from './composables/usePreviewRenderer';
|
|
|
|
|
import { usePrintPreview } from './composables/usePrintPreview';
|
2026-03-09 15:37:05 +01:00
|
|
|
import { useProjectFonts } from './composables/useProjectFonts';
|
2025-11-24 17:55:42 +01:00
|
|
|
|
|
|
|
|
const stylesheetStore = useStylesheetStore();
|
2026-01-09 10:34:10 +01:00
|
|
|
const narrativeStore = useNarrativeStore();
|
2026-03-09 15:37:05 +01:00
|
|
|
const { loadFontsFromCss } = useProjectFonts();
|
2025-12-08 18:01:01 +01:00
|
|
|
|
2025-12-04 15:27:23 +01:00
|
|
|
const previewFrame1 = ref(null);
|
|
|
|
|
const previewFrame2 = ref(null);
|
2025-11-24 18:18:27 +01:00
|
|
|
const elementPopup = ref(null);
|
2026-03-23 11:13:50 +01:00
|
|
|
const imagePopup = ref(null);
|
2026-02-24 13:37:07 +01:00
|
|
|
// const pagePopup = ref(null); // DISABLED: page template styling feature
|
2025-12-05 11:02:18 +01:00
|
|
|
const activeTab = ref('');
|
|
|
|
|
|
|
|
|
|
provide('activeTab', activeTab);
|
2025-11-24 18:18:27 +01:00
|
|
|
|
2026-01-09 17:20:10 +01:00
|
|
|
// Setup iframe interactions (hover, click, labels)
|
|
|
|
|
const {
|
2026-02-24 13:37:07 +01:00
|
|
|
// hoveredPage, // DISABLED: page template styling feature
|
|
|
|
|
// selectedPages, // DISABLED: page template styling feature
|
2026-01-09 17:20:10 +01:00
|
|
|
hoveredElement,
|
|
|
|
|
selectedElement,
|
|
|
|
|
handleIframeMouseMove,
|
|
|
|
|
handleIframeClick,
|
2026-02-24 13:37:07 +01:00
|
|
|
// handlePagePopupClose, // DISABLED: page template styling feature
|
2026-01-09 17:20:10 +01:00
|
|
|
handleElementPopupClose,
|
2026-03-23 11:13:50 +01:00
|
|
|
handleImagePopupClose,
|
|
|
|
|
} = useIframeInteractions({ elementPopup, imagePopup });
|
2026-01-09 17:20:10 +01:00
|
|
|
|
|
|
|
|
// Setup preview renderer with double buffering
|
|
|
|
|
const {
|
|
|
|
|
renderPreview,
|
|
|
|
|
currentFrameIndex,
|
|
|
|
|
isTransitioning,
|
|
|
|
|
setKeyboardShortcutHandler,
|
|
|
|
|
} = usePreviewRenderer({
|
|
|
|
|
previewFrame1,
|
|
|
|
|
previewFrame2,
|
|
|
|
|
stylesheetStore,
|
|
|
|
|
narrativeStore,
|
|
|
|
|
handleIframeMouseMove,
|
|
|
|
|
handleIframeClick,
|
|
|
|
|
});
|
2025-12-04 15:27:23 +01:00
|
|
|
|
|
|
|
|
const activeFrame = computed(() => {
|
2025-12-04 15:55:52 +01:00
|
|
|
return currentFrameIndex.value === 1
|
|
|
|
|
? previewFrame1.value
|
|
|
|
|
: previewFrame2.value;
|
2025-12-04 15:27:23 +01:00
|
|
|
});
|
2025-12-03 15:20:49 +01:00
|
|
|
|
2026-01-09 17:20:10 +01:00
|
|
|
// Setup print preview
|
|
|
|
|
const { printPreview } = usePrintPreview(activeFrame);
|
|
|
|
|
|
|
|
|
|
// Setup keyboard shortcuts (depends on printPreview)
|
2026-02-24 13:53:55 +01:00
|
|
|
const { handleKeyboardShortcut, isMac } = useKeyboardShortcuts({
|
2026-01-09 17:20:10 +01:00
|
|
|
stylesheetStore,
|
|
|
|
|
elementPopup,
|
2026-02-24 13:37:07 +01:00
|
|
|
// pagePopup, // DISABLED: page template styling feature
|
2026-01-09 17:20:10 +01:00
|
|
|
activeTab,
|
|
|
|
|
printPreview,
|
|
|
|
|
});
|
2025-12-08 14:05:16 +01:00
|
|
|
|
2026-01-09 17:20:10 +01:00
|
|
|
// Attach keyboard shortcut handler to renderer
|
|
|
|
|
setKeyboardShortcutHandler(handleKeyboardShortcut);
|
2025-12-08 14:05:16 +01:00
|
|
|
|
2026-03-08 08:44:39 +01:00
|
|
|
// Zoom
|
|
|
|
|
const zoomControls = ref(null);
|
|
|
|
|
const zoomStyle = computed(() => zoomControls.value?.zoomStyle ?? {});
|
|
|
|
|
|
2026-01-09 17:20:10 +01:00
|
|
|
// Lifecycle: Initialize app on mount
|
2025-12-08 18:01:01 +01:00
|
|
|
onMounted(async () => {
|
2026-01-09 13:39:25 +01:00
|
|
|
// Load narrative data (narrativeUrl constructed from location, always present)
|
2026-01-09 10:53:08 +01:00
|
|
|
await narrativeStore.loadNarrative(location.href + '.json');
|
2025-12-08 18:01:01 +01:00
|
|
|
|
2026-01-09 13:39:25 +01:00
|
|
|
// Initialize stylesheet with custom CSS
|
|
|
|
|
if (narrativeStore.data) {
|
|
|
|
|
await stylesheetStore.initializeFromNarrative(narrativeStore.data);
|
2026-03-09 15:37:05 +01:00
|
|
|
// Pre-load any fonts referenced in the saved CSS so @font-face rules are ready for the preview
|
|
|
|
|
await loadFontsFromCss(stylesheetStore.customCss);
|
2026-01-09 13:39:25 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 18:01:01 +01:00
|
|
|
// Render preview after data is loaded
|
|
|
|
|
renderPreview(true);
|
|
|
|
|
});
|
2025-11-24 14:01:48 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-11-24 16:51:55 +01:00
|
|
|
<div id="content-source" style="display: none">
|
2025-11-24 14:01:48 +01:00
|
|
|
<PagedJsWrapper />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-11-24 18:18:27 +01:00
|
|
|
<EditorPanel />
|
2025-11-24 14:01:48 +01:00
|
|
|
|
2025-12-05 11:02:18 +01:00
|
|
|
<iframe
|
|
|
|
|
ref="previewFrame1"
|
|
|
|
|
class="preview-frame"
|
|
|
|
|
:class="{ shifted: activeTab.length > 0 }"
|
2026-03-08 08:44:39 +01:00
|
|
|
:style="zoomStyle"
|
2025-12-05 11:02:18 +01:00
|
|
|
></iframe>
|
|
|
|
|
<iframe
|
|
|
|
|
ref="previewFrame2"
|
|
|
|
|
class="preview-frame"
|
|
|
|
|
:class="{ shifted: activeTab.length > 0 }"
|
2026-03-08 08:44:39 +01:00
|
|
|
:style="zoomStyle"
|
2025-12-05 11:02:18 +01:00
|
|
|
></iframe>
|
2025-11-24 14:01:48 +01:00
|
|
|
|
2025-12-05 16:45:57 +01:00
|
|
|
<PreviewLoader :isLoading="isTransitioning" :shifted="activeTab.length > 0" />
|
2025-12-05 16:41:07 +01:00
|
|
|
|
2026-03-06 17:18:04 +01:00
|
|
|
|
2026-01-09 13:39:25 +01:00
|
|
|
|
2025-12-08 16:35:28 +01:00
|
|
|
<ElementPopup
|
|
|
|
|
ref="elementPopup"
|
|
|
|
|
:iframeRef="activeFrame"
|
|
|
|
|
@close="handleElementPopupClose"
|
|
|
|
|
/>
|
2026-03-23 11:13:50 +01:00
|
|
|
|
|
|
|
|
<ImagePopup
|
|
|
|
|
ref="imagePopup"
|
|
|
|
|
@close="handleImagePopupClose"
|
|
|
|
|
/>
|
2026-02-24 13:37:07 +01:00
|
|
|
<!-- DISABLED: page template styling feature
|
2025-12-08 16:35:28 +01:00
|
|
|
<PagePopup
|
|
|
|
|
ref="pagePopup"
|
|
|
|
|
:iframeRef="activeFrame"
|
|
|
|
|
@close="handlePagePopupClose"
|
|
|
|
|
/>
|
2026-02-24 13:37:07 +01:00
|
|
|
-->
|
2025-12-08 14:05:16 +01:00
|
|
|
|
2026-03-06 17:18:04 +01:00
|
|
|
|
2026-03-08 08:44:39 +01:00
|
|
|
<ZoomControls ref="zoomControls" />
|
|
|
|
|
|
2026-03-09 14:31:56 +01:00
|
|
|
<div id="actions-btn">
|
2026-03-06 17:18:04 +01:00
|
|
|
<SaveButton />
|
|
|
|
|
<PrintButton :printPreview="printPreview" />
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-11-24 16:51:55 +01:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style>
|
2025-12-04 15:27:23 +01:00
|
|
|
.preview-frame {
|
2025-11-24 16:51:55 +01:00
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
2025-12-04 13:34:33 +01:00
|
|
|
left: 0;
|
|
|
|
|
width: 100vw;
|
2025-11-24 16:51:55 +01:00
|
|
|
height: 100vh;
|
|
|
|
|
border: none;
|
2026-03-08 08:44:39 +01:00
|
|
|
transform-origin: top center;
|
2025-12-05 11:02:18 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-08 08:44:39 +01:00
|
|
|
|
|
|
|
|
/* .preview-frame.shifted {
|
2025-12-05 17:32:39 +01:00
|
|
|
margin-left: 17.55rem;
|
|
|
|
|
transform: scale(0.65) translateY(-40vh);
|
|
|
|
|
height: 155vh;
|
2026-03-08 08:44:39 +01:00
|
|
|
} */
|
2025-12-04 15:27:23 +01:00
|
|
|
|
|
|
|
|
.preview-frame:nth-of-type(1) {
|
|
|
|
|
z-index: 1;
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-frame:nth-of-type(2) {
|
|
|
|
|
z-index: 0;
|
|
|
|
|
opacity: 0;
|
2025-11-24 14:01:48 +01:00
|
|
|
}
|
2025-12-08 13:59:57 +01:00
|
|
|
|
2026-01-09 16:54:10 +01:00
|
|
|
|
2025-12-08 13:59:57 +01:00
|
|
|
/* Hide UI elements when printing */
|
|
|
|
|
@media print {
|
|
|
|
|
#editor-panel,
|
|
|
|
|
#element-popup,
|
|
|
|
|
#page-popup,
|
2026-03-09 14:31:56 +01:00
|
|
|
#actions-btn,
|
2025-12-08 14:05:16 +01:00
|
|
|
.preview-loader,
|
|
|
|
|
.print-btn {
|
2025-12-08 13:59:57 +01:00
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-frame {
|
|
|
|
|
position: static !important;
|
|
|
|
|
margin-left: 0 !important;
|
|
|
|
|
transform: none !important;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
height: auto !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-frame:not(:first-of-type) {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-24 14:01:48 +01:00
|
|
|
</style>
|