feat: add animated CSS loader during preview reload

Displays a spinning loader in the top-right corner of the iframe while PagedJS is rendering. The loader automatically adjusts position when the editor panel is open.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
isUnknown 2025-12-05 16:41:07 +01:00
parent b584a539fe
commit 9e02813d19

View file

@ -16,7 +16,7 @@ provide('activeTab', activeTab);
let savedScrollPercentage = 0; let savedScrollPercentage = 0;
const currentFrameIndex = ref(1); // 1 or 2, which iframe is currently visible const currentFrameIndex = ref(1); // 1 or 2, which iframe is currently visible
let isTransitioning = false; const isTransitioning = ref(false);
const activeFrame = computed(() => { const activeFrame = computed(() => {
return currentFrameIndex.value === 1 return currentFrameIndex.value === 1
@ -25,8 +25,8 @@ const activeFrame = computed(() => {
}); });
const renderPreview = async (shouldReloadFromFile = false) => { const renderPreview = async (shouldReloadFromFile = false) => {
if (isTransitioning) return; if (isTransitioning.value) return;
isTransitioning = true; isTransitioning.value = true;
// Determine which iframe is currently visible and which to render to // Determine which iframe is currently visible and which to render to
const visibleFrame = const visibleFrame =
@ -35,7 +35,7 @@ const renderPreview = async (shouldReloadFromFile = false) => {
currentFrameIndex.value === 1 ? previewFrame2.value : previewFrame1.value; currentFrameIndex.value === 1 ? previewFrame2.value : previewFrame1.value;
if (!hiddenFrame) { if (!hiddenFrame) {
isTransitioning = false; isTransitioning.value = false;
return; return;
} }
@ -112,7 +112,7 @@ const renderPreview = async (shouldReloadFromFile = false) => {
// Swap current frame // Swap current frame
currentFrameIndex.value = currentFrameIndex.value === 1 ? 2 : 1; currentFrameIndex.value = currentFrameIndex.value === 1 ? 2 : 1;
isTransitioning = false; isTransitioning.value = false;
}, 200); // Match CSS transition duration }, 200); // Match CSS transition duration
}, 50); // Small delay to ensure scroll is set }, 50); // Small delay to ensure scroll is set
}, 200); // Wait for PagedJS }, 200); // Wait for PagedJS
@ -147,6 +147,10 @@ onMounted(() => renderPreview(true));
:class="{ shifted: activeTab.length > 0 }" :class="{ shifted: activeTab.length > 0 }"
></iframe> ></iframe>
<div v-if="isTransitioning" class="preview-loader" :class="{ shifted: activeTab.length > 0 }">
<div class="spinner"></div>
</div>
<ElementPopup ref="elementPopup" :iframeRef="activeFrame" /> <ElementPopup ref="elementPopup" :iframeRef="activeFrame" />
</template> </template>
@ -179,4 +183,39 @@ onMounted(() => renderPreview(true));
z-index: 0; z-index: 0;
opacity: 0; opacity: 0;
} }
.preview-loader {
position: fixed;
top: 2rem;
right: 2rem;
z-index: 1000;
pointer-events: none;
transition: all 0.2s ease-in-out var(--curve);
}
.preview-loader.shifted {
right: calc(2rem + 19rem * 0.7);
top: calc(2rem - 30vh * 0.7);
transform: scale(0.7);
}
.spinner {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid #fff;
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style> </style>