-
Notifications
You must be signed in to change notification settings - Fork 19
Add fullscreen toggle #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
469edb8
a0e4c34
0f228bf
745e711
37833b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,26 @@ | ||
| <template> | ||
| <div :style="pdfContainerStyle" ref="pdfContainer" id="pdfContainer" class="container-wrapper"> | ||
| <div :style="pdfContainerStyle" ref="pdfContainer" id="pdfContainer" :class="['container-wrapper', { maximized: isMaximized }]"> | ||
| <div class="scrolling-container"> | ||
| <div id="pdfViewer"></div> | ||
| </div> | ||
| <div class="zoom-controls"> | ||
| <button class="zoom-button" @click.stop="toggleZoomPanel"> | ||
| {{ Math.round(currentZoom * 100) }}% | ||
| </button> | ||
| <div class="control-buttons"> | ||
| <div class="top-buttons"> | ||
| <button v-if="showFullscreen" class="control-button" @click.stop="toggleMaximized" :title="isMaximized ? 'Exit Maximized' : 'Enter Maximized'"> | ||
| <svg v-if="!isMaximized" style="width:16px; height:16px; vertical-align: middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg> | ||
| <svg v-else style="width:16px; height:16px; vertical-align: middle;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/></svg> | ||
| </button> | ||
| <button class="control-button" @click.stop="toggleZoomPanel"> | ||
| {{ Math.round(currentZoom * 100) }}% | ||
| </button> | ||
| </div> | ||
| <div v-if="showZoomPanel" class="zoom-panel"> | ||
| <div class="zoom-input-container"> | ||
| <input | ||
| type="number" | ||
| class="zoom-input" | ||
| v-model="manualZoomInput" | ||
| @keyup.enter="applyManualZoom" | ||
| @blur="applyManualZoom" | ||
| type="number" | ||
| class="zoom-input" | ||
| v-model="manualZoomInput" | ||
| @keyup.enter="applyManualZoom" | ||
| @blur="applyManualZoom" | ||
| /> | ||
| <span class="zoom-input-percent">%</span> | ||
| </div> | ||
|
|
@@ -76,6 +82,9 @@ export default { | |
| const localZoomLevel = ref(initialZoom); | ||
| const currentZoom = ref(1); // Will be updated on render | ||
|
|
||
| const isMaximized = ref(false); | ||
| const showFullscreen = props.args.show_fullscreen_toggle === true; | ||
|
|
||
| const zoomPresets = [0.5, 0.75, 1, 1.25, 1.5, 2]; | ||
| const pdfInstance = ref(null); | ||
| const pdfContainer = ref(null); | ||
|
|
@@ -100,23 +109,40 @@ export default { | |
|
|
||
| const pdfContainerStyle = computed(() => { | ||
| const result = parseWidthValue(props.args.width, window.innerWidth); | ||
| const widthCSS = result.type === "percent" ? `${result.value * 100}%` : `${result.value}px`; | ||
| let widthCSS, heightCSS; | ||
|
|
||
| if (isMaximized.value) { | ||
| // In maximized mode, use full viewport | ||
| widthCSS = '100vw'; | ||
| heightCSS = '100vh'; | ||
| } else { | ||
| widthCSS = result.type === "percent" ? `${result.value * 100}%` : `${result.value}px`; | ||
| heightCSS = props.args.height ? `${props.args.height}px` : 'auto'; | ||
| } | ||
|
|
||
| const style = { | ||
| width: widthCSS, | ||
| height: props.args.height ? `${props.args.height}px` : 'auto', | ||
| position: 'relative', | ||
| height: heightCSS, | ||
| position: isMaximized.value ? 'fixed' : 'relative', | ||
| top: isMaximized.value ? '0' : 'auto', | ||
| left: isMaximized.value ? '0' : 'auto', | ||
| zIndex: isMaximized.value ? '9999' : 'auto', | ||
| backgroundColor: isMaximized.value ? 'white' : 'transparent', | ||
| transition: 'all 0.3s ease-in-out', | ||
| }; | ||
|
|
||
| const align = props.args.viewer_align || 'center'; | ||
| if (align === 'center') { | ||
| style.marginLeft = 'auto'; | ||
| style.marginRight = 'auto'; | ||
| } else if (align === 'left') { | ||
| style.marginLeft = '0'; | ||
| style.marginRight = 'auto'; | ||
| } else if (align === 'right') { | ||
| style.marginLeft = 'auto'; | ||
| style.marginRight = '0'; | ||
| if (!isMaximized.value) { | ||
| const align = props.args.viewer_align || 'center'; | ||
| if (align === 'center') { | ||
| style.marginLeft = 'auto'; | ||
| style.marginRight = 'auto'; | ||
| } else if (align === 'left') { | ||
| style.marginLeft = '0'; | ||
| style.marginRight = 'auto'; | ||
| } else if (align === 'right') { | ||
| style.marginLeft = 'auto'; | ||
| style.marginRight = '0'; | ||
| } | ||
| } | ||
|
|
||
| return style; | ||
|
|
@@ -391,6 +417,33 @@ export default { | |
| } | ||
| }; | ||
|
|
||
| // Handle maximized state changes without full re-render | ||
| const handleMaximizedResize = () => { | ||
| if (isMaximized.value) { | ||
| // When maximized, adjust container dimensions without re-rendering PDF | ||
| const scrollingContainer = document.querySelector('.scrolling-container'); | ||
| if (scrollingContainer) { | ||
| scrollingContainer.style.height = 'calc(100vh - 120px)'; // Account for control buttons | ||
| } | ||
|
|
||
| // Adjust PDF viewer width to fit the maximized container | ||
| const pdfViewer = document.getElementById("pdfViewer"); | ||
| if (pdfViewer && maxWidth.value) { | ||
| pdfViewer.style.width = '100%'; | ||
| pdfViewer.style.maxWidth = 'none'; | ||
| } | ||
| } else { | ||
| // Restore original dimensions when exiting maximized mode | ||
| const scrollingContainer = document.querySelector('.scrolling-container'); | ||
| if (scrollingContainer) { | ||
| scrollingContainer.style.height = ''; | ||
| } | ||
|
|
||
| // Trigger a single re-render to restore original layout | ||
| handleResize(); | ||
| } | ||
| }; | ||
|
|
||
| watch(() => props.args.binary, () => { | ||
| handleResize(); | ||
| }); | ||
|
|
@@ -401,9 +454,48 @@ export default { | |
| }); | ||
|
|
||
| watch(() => props.args.viewer_align, () => { | ||
| handleResize(); | ||
| if (!isMaximized.value) { | ||
| handleResize(); | ||
| } | ||
| }); | ||
|
|
||
| // Watch for maximized state changes to handle edge cases | ||
| watch(isMaximized, (newVal) => { | ||
| if (newVal) { | ||
| // When entering maximized mode, ensure zoom controls work properly | ||
| setTimeout(() => { | ||
| const scrollingContainer = document.querySelector('.scrolling-container'); | ||
| if (scrollingContainer) { | ||
| scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop || 0); | ||
| scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft || 0); | ||
| } | ||
| }, 100); // Small delay to ensure DOM has updated | ||
| } | ||
| }); | ||
|
Comment on lines
+463
to
+474
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Avoid setTimeout for DOM synchronization. Lines 466-472 use Use Vue's watch(isMaximized, (newVal) => {
if (newVal) {
- setTimeout(() => {
+ nextTick(() => {
const scrollingContainer = document.querySelector('.scrolling-container');
if (scrollingContainer) {
scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop || 0);
scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft || 0);
}
- }, 100);
+ });
}
});Don't forget to import -import {onMounted, computed, ref, onUnmounted, watch} from "vue";
+import {onMounted, computed, ref, onUnmounted, watch, nextTick} from "vue";
🤖 Prompt for AI Agents |
||
|
|
||
| const toggleMaximized = () => { | ||
| isMaximized.value = !isMaximized.value; | ||
|
|
||
| // Save/restore scroll position and handle maximized state | ||
| const scrollingContainer = document.querySelector('.scrolling-container'); | ||
| if (scrollingContainer) { | ||
| if (isMaximized.value) { | ||
| // Save scroll position when entering maximized mode | ||
| scrollingContainer.dataset.savedScrollTop = scrollingContainer.scrollTop; | ||
| scrollingContainer.dataset.savedScrollLeft = scrollingContainer.scrollLeft; | ||
| } else { | ||
| // Restore scroll position when exiting maximized mode | ||
| if (scrollingContainer.dataset.savedScrollTop !== undefined) { | ||
| scrollingContainer.scrollTop = parseFloat(scrollingContainer.dataset.savedScrollTop); | ||
| scrollingContainer.scrollLeft = parseFloat(scrollingContainer.dataset.savedScrollLeft); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Handle the resize without full re-render | ||
| handleMaximizedResize(); | ||
| }; | ||
|
|
||
| const setZoom = (zoomLevel) => { | ||
| localZoomLevel.value = zoomLevel; | ||
| showZoomPanel.value = false; | ||
|
|
@@ -453,22 +545,30 @@ export default { | |
| }; | ||
|
|
||
| const handleClickOutside = (event) => { | ||
| if (showZoomPanel.value && !event.target.closest('.zoom-controls')) { | ||
| if (showZoomPanel.value && !event.target.closest('.control-buttons')) { | ||
| showZoomPanel.value = false; | ||
| } | ||
| }; | ||
|
|
||
| const handleKeyDown = (event) => { | ||
| if (event.key === 'Escape' && isMaximized.value) { | ||
| toggleMaximized(); | ||
| } | ||
| }; | ||
|
|
||
| const debouncedHandleResize = debounce(handleResize, 200); | ||
|
|
||
| onMounted(() => { | ||
| debouncedHandleResize(); | ||
| window.addEventListener("resize", debouncedHandleResize); | ||
| document.addEventListener('click', handleClickOutside); | ||
| document.addEventListener('keydown', handleKeyDown); | ||
| }); | ||
|
|
||
| onUnmounted(() => { | ||
| window.removeEventListener("resize", debouncedHandleResize); | ||
| document.removeEventListener('click', handleClickOutside); | ||
| document.removeEventListener('keydown', handleKeyDown); | ||
| }); | ||
|
|
||
| return { | ||
|
|
@@ -486,6 +586,9 @@ export default { | |
| fitToHeight, | ||
| toggleZoomPanel, | ||
| applyManualZoom, | ||
| showFullscreen, | ||
| isMaximized, | ||
| toggleMaximized, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
@@ -494,24 +597,46 @@ export default { | |
| <style scoped> | ||
| .container-wrapper { | ||
| position: relative; | ||
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | ||
| } | ||
|
|
||
| .scrolling-container { | ||
| height: 100%; | ||
| overflow: auto; | ||
| transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1); | ||
| } | ||
|
|
||
| .container-wrapper.maximized { | ||
| position: fixed; | ||
| top: 0; | ||
| left: 0; | ||
| width: 100vw !important; | ||
| height: 100vh !important; | ||
| z-index: 9999; | ||
| background: white; | ||
| border-radius: 0; | ||
| box-shadow: 0 0 40px rgba(0, 0, 0, 0.3); | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| .zoom-controls { | ||
| .control-buttons { | ||
| position: absolute; | ||
| top: 20px; | ||
| right: 20px; | ||
| z-index: 100; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: flex-end; | ||
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | ||
| } | ||
|
|
||
| .zoom-button { | ||
| .top-buttons { | ||
| display: flex; | ||
| gap: 8px; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .control-button { | ||
| background: rgba(40, 40, 40, 0.9); | ||
| border: 1px solid rgba(255, 255, 255, 0.2); | ||
| color: white; | ||
|
|
@@ -522,10 +647,9 @@ export default { | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); | ||
| font-weight: 500; | ||
| transition: background 0.2s; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .zoom-button:hover { | ||
| .control-button:hover { | ||
| background: rgba(50, 50, 50, 0.9); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full re-render on exit may be expensive for large PDFs.
Line 443 calls
handleResize()which re-renders the entire PDF when exiting maximized mode. For large documents, this could cause a noticeable delay or freeze.Consider alternatives:
Additionally, line 426 uses a magic number
120pxfor control button height. Consider extracting this to a constant or computing it dynamically:📝 Committable suggestion