merge
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 21s

This commit is contained in:
sarahgarcin1 2026-02-24 19:49:50 +01:00
commit 95efcac454
27 changed files with 700 additions and 429 deletions

View file

@ -0,0 +1,8 @@
button[disabled] {
cursor: not-allowed !important;
opacity: 0.6;
}
button[disabled]:hover {
background-color: inherit !important;
}

View file

@ -234,8 +234,8 @@ img {
--space-m: 2rem; --space-m: 2rem;
--space-big: 3em; --space-big: 3em;
--curve: cubic-bezier(0.86, 0, 0.07, 1); --curve: cubic-bezier(0.86, 0, 0.07, 1);
--sans-serif: "DM Sans", sans-serif; --sans-serif: 'DM Sans', sans-serif;
--mono: "Inconsolata", monospace; --mono: 'Inconsolata', monospace;
--input-h: 26px; --input-h: 26px;
--input-w: 160px; --input-w: 160px;
--input-w-small: 45px; --input-w-small: 45px;
@ -688,4 +688,13 @@ input[type=number] {
line-height: 1.5; line-height: 1.5;
resize: none; resize: none;
outline: none; outline: none;
}
button[disabled] {
cursor: not-allowed !important;
opacity: 0.6;
}
button[disabled]:hover {
background-color: inherit !important;
}/*# sourceMappingURL=style.css.map */ }/*# sourceMappingURL=style.css.map */

File diff suppressed because one or more lines are too long

View file

@ -8,3 +8,4 @@
@use "src/_forms-section.scss" as *; @use "src/_forms-section.scss" as *;
@use "src/_buttons.scss" as *; @use "src/_buttons.scss" as *;
@use "src/_settings-popup.scss" as *; @use "src/_settings-popup.scss" as *;
@use "src/_global.scss" as *;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Before After
Before After

View file

@ -15,9 +15,9 @@ Mapdata:
background: background:
type: osm type: osm
center: center:
lat: 43.82684265866453 lat: 43.484330002696964
lon: 4.3375882121084715 lon: 4.029111008525206
zoom: 11.37799302158894 zoom: 6.503825883663738
---- ----

View file

@ -19,7 +19,7 @@ Customcss:
@page { @page {
size: A4; size: A4;
margin: 50mm 15mm 26mm 15mm; margin: 50mm 15mm 26mm 15mm;
background: rgba(255, 255, 255, 1); background: rgb(227, 33, 33);
} }
body { body {
@ -36,10 +36,10 @@ p {
font-weight: 300; font-weight: 300;
font-family: Arial; font-family: Arial;
font-style: italic; font-style: italic;
padding-top: 20mm; padding-top: 0mm;
padding-right: 20mm; padding-right: 0mm;
padding-bottom: 20mm; padding-bottom: 0mm;
padding-left: 20mm; padding-left: 0mm;
} }
h1 { h1 {

View file

@ -35,6 +35,7 @@ columns:
template: template:
- map - map
- geoformat - geoformat
info: "{{ page.intendedTemplate }}"
sidebar: sidebar:
width: 1/3 width: 1/3
sections: sections:

View file

@ -17,7 +17,7 @@ columns:
multiple: false multiple: false
width: 1/2 width: 1/2
pages: pages:
label: Narratives label: Récits
type: pages type: pages
template: narrative template: narrative
sidebar: sidebar:
@ -26,6 +26,3 @@ columns:
files: files:
label: Fichiers label: Fichiers
type: files type: files

File diff suppressed because one or more lines are too long

View file

@ -56,6 +56,7 @@ Kirby::plugin('geoproject/map-editor', [
require __DIR__ . '/routes/markers/update.php', require __DIR__ . '/routes/markers/update.php',
require __DIR__ . '/routes/markers/delete.php', require __DIR__ . '/routes/markers/delete.php',
require __DIR__ . '/routes/position/update.php', require __DIR__ . '/routes/position/update.php',
require __DIR__ . '/routes/mapdata/save.php',
require __DIR__ . '/routes/image/capture.php', require __DIR__ . '/routes/image/capture.php',
require __DIR__ . '/routes/image/check-flag.php', require __DIR__ . '/routes/image/check-flag.php',
require __DIR__ . '/routes/image/clear-flag.php', require __DIR__ . '/routes/image/clear-flag.php',

View file

@ -0,0 +1,69 @@
<?php
/**
* PATCH save map data (center, zoom) directly to the page content
*/
return [
'pattern' => 'map-editor/pages/(:all)/mapdata',
'method' => 'PATCH',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$page = kirby()->page($pageId);
if (!$page) {
return [
'status' => 'error',
'message' => 'Page not found',
'code' => 404
];
}
if (!$page->permissions()->can('update')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['mapdata'])) {
return [
'status' => 'error',
'message' => 'mapdata is required',
'code' => 400
];
}
$page->update([
'mapdata' => $data['mapdata']
]);
return [
'status' => 'success',
'data' => [
'message' => 'Map data saved successfully'
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -22,10 +22,11 @@
v-if="mode === 'multi'" v-if="mode === 'multi'"
class="save-framing-button" class="save-framing-button"
type="button" type="button"
:disabled="isCapturing"
@click="saveCurrentFraming" @click="saveCurrentFraming"
title="Utiliser le niveau de zoom et centrage actuel comme cadrage par défaut" title="Utiliser le niveau de zoom et centrage actuel comme cadrage par défaut"
> >
Définir le cadrage {{ isCapturing ? 'Capture en cours…' : 'Définir le cadrage' }}
</button> </button>
<MapPreview <MapPreview
@ -55,6 +56,7 @@ import {
onBeforeUnmount, onBeforeUnmount,
nextTick, nextTick,
} from 'vue'; } from 'vue';
import yaml from 'js-yaml';
import MapPreview from '../map/MapPreview.vue'; import MapPreview from '../map/MapPreview.vue';
import MarkerList from '../map/MarkerList.vue'; import MarkerList from '../map/MarkerList.vue';
import { useMarkersApi } from '../../composables/useMarkersApi.js'; import { useMarkersApi } from '../../composables/useMarkersApi.js';
@ -112,6 +114,7 @@ export default {
const mapPreview = ref(null); const mapPreview = ref(null);
const selectedMarkerId = ref(null); const selectedMarkerId = ref(null);
const isInitialLoad = ref(true); const isInitialLoad = ref(true);
const isCapturing = ref(false);
// Extract page ID from field name // Extract page ID from field name
// For single mode, we don't need the API // For single mode, we don't need the API
@ -508,10 +511,19 @@ export default {
} }
} }
// Get CSRF token (same logic as useMarkersApi)
const getCsrfToken = () => {
if (window.panel && window.panel.csrf) return window.panel.csrf;
const meta = document.querySelector('meta[name="csrf"]');
if (meta && meta.content) return meta.content;
if (window.csrf) return window.csrf;
return '';
};
/** /**
* Save current map framing (center + zoom) * Save current map framing (center + zoom) and capture image immediately
*/ */
function saveCurrentFraming() { async function saveCurrentFraming() {
if (!mapPreview.value) return; if (!mapPreview.value) return;
// Get current center and zoom from the map // Get current center and zoom from the map
@ -530,8 +542,39 @@ export default {
}; };
zoom.value = currentZoom; zoom.value = currentZoom;
// Save immediately (not debounced) isCapturing.value = true;
saveMapData(); try {
// Save map data directly via API (bypasses emit('input') no draft state)
const yamlString = yaml.dump({
background: { type: 'osm' },
center: { lat: center.value.lat, lon: center.value.lon },
zoom: zoom.value,
}, { indent: 2, lineWidth: -1, noRefs: true });
await fetch(`/api/map-editor/pages/${pageId.value}/mapdata`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF': getCsrfToken(),
},
body: JSON.stringify({ mapdata: yamlString }),
});
// Capture image directly (map is already rendered)
await captureAndSaveMapImage();
// Clear the regeneration flag (set by the hook after page update)
await fetch(
`/api/map-editor/pages/${pageId.value}/clear-regenerate-flag`,
{ method: 'DELETE' }
);
// Reload to show the new image in the panel
window.location.reload();
} catch (error) {
console.error('Failed to save framing:', error);
isCapturing.value = false;
}
} }
/** /**
@ -648,6 +691,7 @@ export default {
mapReady, mapReady,
mapPreview, mapPreview,
canAddMarker, canAddMarker,
isCapturing,
loading: markersApi.loading, loading: markersApi.loading,
error: markersApi.error, error: markersApi.error,

View file

@ -2,7 +2,7 @@
import PagedJsWrapper from './components/PagedJsWrapper.vue'; import PagedJsWrapper from './components/PagedJsWrapper.vue';
import EditorPanel from './components/editor/EditorPanel.vue'; import EditorPanel from './components/editor/EditorPanel.vue';
import ElementPopup from './components/ElementPopup.vue'; import ElementPopup from './components/ElementPopup.vue';
import PagePopup from './components/PagePopup.vue'; // import PagePopup from './components/PagePopup.vue'; // DISABLED: page template styling feature
import PreviewLoader from './components/PreviewLoader.vue'; import PreviewLoader from './components/PreviewLoader.vue';
import SaveButton from './components/SaveButton.vue'; import SaveButton from './components/SaveButton.vue';
import { onMounted, ref, computed, provide } from 'vue'; import { onMounted, ref, computed, provide } from 'vue';
@ -19,22 +19,22 @@ const narrativeStore = useNarrativeStore();
const previewFrame1 = ref(null); const previewFrame1 = ref(null);
const previewFrame2 = ref(null); const previewFrame2 = ref(null);
const elementPopup = ref(null); const elementPopup = ref(null);
const pagePopup = ref(null); // const pagePopup = ref(null); // DISABLED: page template styling feature
const activeTab = ref(''); const activeTab = ref('');
provide('activeTab', activeTab); provide('activeTab', activeTab);
// Setup iframe interactions (hover, click, labels) // Setup iframe interactions (hover, click, labels)
const { const {
hoveredPage, // hoveredPage, // DISABLED: page template styling feature
selectedPages, // selectedPages, // DISABLED: page template styling feature
hoveredElement, hoveredElement,
selectedElement, selectedElement,
handleIframeMouseMove, handleIframeMouseMove,
handleIframeClick, handleIframeClick,
handlePagePopupClose, // handlePagePopupClose, // DISABLED: page template styling feature
handleElementPopupClose, handleElementPopupClose,
} = useIframeInteractions({ elementPopup, pagePopup }); } = useIframeInteractions({ elementPopup });
// Setup preview renderer with double buffering // Setup preview renderer with double buffering
const { const {
@ -61,13 +61,10 @@ const activeFrame = computed(() => {
const { printPreview } = usePrintPreview(activeFrame); const { printPreview } = usePrintPreview(activeFrame);
// Setup keyboard shortcuts (depends on printPreview) // Setup keyboard shortcuts (depends on printPreview)
const { const { handleKeyboardShortcut, isMac } = useKeyboardShortcuts({
handleKeyboardShortcut,
isMac
} = useKeyboardShortcuts({
stylesheetStore, stylesheetStore,
elementPopup, elementPopup,
pagePopup, // pagePopup, // DISABLED: page template styling feature
activeTab, activeTab,
printPreview, printPreview,
}); });
@ -117,13 +114,19 @@ onMounted(async () => {
:iframeRef="activeFrame" :iframeRef="activeFrame"
@close="handleElementPopupClose" @close="handleElementPopupClose"
/> />
<!-- DISABLED: page template styling feature
<PagePopup <PagePopup
ref="pagePopup" ref="pagePopup"
:iframeRef="activeFrame" :iframeRef="activeFrame"
@close="handlePagePopupClose" @close="handlePagePopupClose"
/> />
-->
<button class="print-btn" @click="printPreview" :title="`Imprimer (${isMac ? '⌘' : 'Ctrl'}+P)`"> <button
class="print-btn"
@click="printPreview"
:title="`Imprimer (${isMac ? '⌘' : 'Ctrl'}+P)`"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View file

@ -56,15 +56,15 @@
type="button" type="button"
:class="{ active: fontSize.unit === 'px' }" :class="{ active: fontSize.unit === 'px' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="fontSize.unit = 'px'" @click="updateFontSizeUnit('px')"
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: fontSize.unit === 'em' }" :class="{ active: fontSize.unit === 'em' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="fontSize.unit = 'em'" @click="updateFontSizeUnit('em')"
> >
em em
</button> </button>
@ -72,10 +72,10 @@
type="button" type="button"
:class="{ active: fontSize.unit === 'rem' }" :class="{ active: fontSize.unit === 'rem' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="fontSize.unit = 'rem'" @click="updateFontSizeUnit('rem')"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -142,7 +142,7 @@
type="button" type="button"
:class="{ active: marginOuter.unit === 'mm' }" :class="{ active: marginOuter.unit === 'mm' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="marginOuter.unit = 'mm'" @click="updateMarginOuterUnit('mm')"
> >
mm mm
</button> </button>
@ -150,7 +150,7 @@
type="button" type="button"
:class="{ active: marginOuter.unit === 'px' }" :class="{ active: marginOuter.unit === 'px' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="marginOuter.unit = 'px'" @click="updateMarginOuterUnit('px')"
> >
px px
</button> </button>
@ -175,7 +175,7 @@
type="button" type="button"
:class="{ active: paddingInner.unit === 'mm' }" :class="{ active: paddingInner.unit === 'mm' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="paddingInner.unit = 'mm'" @click="updatePaddingInnerUnit('mm')"
> >
mm mm
</button> </button>
@ -183,7 +183,7 @@
type="button" type="button"
:class="{ active: paddingInner.unit === 'px' }" :class="{ active: paddingInner.unit === 'px' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="paddingInner.unit = 'px'" @click="updatePaddingInnerUnit('px')"
> >
px px
</button> </button>
@ -264,6 +264,7 @@ import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce'; import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue'; import NumberInput from './ui/NumberInput.vue';
import UnitToggle from './ui/UnitToggle.vue'; import UnitToggle from './ui/UnitToggle.vue';
import { convertUnit } from '../utils/unit-conversion';
import Coloris from '@melloware/coloris'; import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css'; import '@melloware/coloris/dist/coloris.css';
import hljs from 'highlight.js/lib/core'; import hljs from 'highlight.js/lib/core';
@ -325,6 +326,21 @@ const immediateUpdate = (callback) => {
callback(); callback();
}; };
const updateFontSizeUnit = (newUnit) => {
fontSize.value.value = convertUnit(fontSize.value.value, fontSize.value.unit, newUnit);
fontSize.value.unit = newUnit;
};
const updateMarginOuterUnit = (newUnit) => {
marginOuter.value.value = convertUnit(marginOuter.value.value, marginOuter.value.unit, newUnit);
marginOuter.value.unit = newUnit;
};
const updatePaddingInnerUnit = (newUnit) => {
paddingInner.value.value = convertUnit(paddingInner.value.value, paddingInner.value.unit, newUnit);
paddingInner.value.unit = newUnit;
};
const getSelectorFromElement = (element) => { const getSelectorFromElement = (element) => {
// Try to build a meaningful selector // Try to build a meaningful selector
if (element.id) { if (element.id) {

View file

@ -50,14 +50,14 @@
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.top.unit === 'rem' }" :class="{ active: margins.top.unit === 'rem' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="margins.top.unit = 'rem'" @click="margins.top.unit = 'rem'"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -91,14 +91,14 @@
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.bottom.unit === 'rem' }" :class="{ active: margins.bottom.unit === 'rem' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="margins.bottom.unit = 'rem'" @click="margins.bottom.unit = 'rem'"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -132,14 +132,14 @@
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.left.unit === 'rem' }" :class="{ active: margins.left.unit === 'rem' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="margins.left.unit = 'rem'" @click="margins.left.unit = 'rem'"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -173,14 +173,14 @@
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.right.unit === 'rem' }" :class="{ active: margins.right.unit === 'rem' }"
:disabled="inheritanceLocked" :disabled="inheritanceLocked"
@click="margins.right.unit = 'rem'" @click="margins.right.unit = 'rem'"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>

View file

@ -24,7 +24,8 @@
class="tab" class="tab"
:class="{ active: activeTab === 'contenu' }" :class="{ active: activeTab === 'contenu' }"
@click="activeTab = 'contenu'" @click="activeTab = 'contenu'"
title="Ouvrir l'onglet Contenu" title="fonctionnalité à venir"
disabled
> >
Contenu Contenu
</button> </button>
@ -131,11 +132,9 @@ nav {
position: relative; position: relative;
left: calc(var(--panel-w) * -1); left: calc(var(--panel-w) * -1);
background-color: var(--color-panel-bg); background-color: var(--color-panel-bg);
box-shadow: -5px 0px 12px; box-shadow: -5px 0px 12px;
transition: left 0.3s var(--curve); transition: left 0.3s var(--curve);
pointer-events: all; pointer-events: all;
} }
@ -145,7 +144,7 @@ nav {
} }
.tab-panel { .tab-panel {
height: calc(100% - var(--panel-nav-h)*2); height: calc(100% - var(--panel-nav-h) * 2);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 0 2em; padding: 0 2em;

View file

@ -1,9 +1,11 @@
<template> <template>
<section class="settings-section" id="settings-section_page" data-color-type="page"> <section
class="settings-section"
id="settings-section_page"
data-color-type="page"
>
<h2>Réglage des pages</h2> <h2>Réglage des pages</h2>
<div class="container"> <div class="container">
<div class="settings-subsection"> <div class="settings-subsection">
<div class="field field-simple"> <div class="field field-simple">
<label for="page-format" class="label-with-tooltip" data-css="size" <label for="page-format" class="label-with-tooltip" data-css="size"
@ -12,38 +14,57 @@
<select id="page-format" v-model="pageFormat"> <select id="page-format" v-model="pageFormat">
<option value="A4">A4</option> <option value="A4">A4</option>
<option value="A5">A5</option> <option value="A5">A5</option>
<option value="A3">A3</option> <!-- <option value="A3">A3</option>
<option value="letter">Letter</option> <option value="letter">Letter</option>
<option value="legal">Legal</option> <option value="legal">Legal</option> -->
<option value="custom">Personnalisé</option>
</select> </select>
</div> </div>
</div> </div>
<div class="settings-subsection"> <div class="settings-subsection">
<div class="field field-size field--view-only"> <div
class="field field-margin"
:class="{ 'field--view-only': !isCustomFormat }"
>
<label for="page-width" class="label-with-tooltip" data-css="width" <label for="page-width" class="label-with-tooltip" data-css="width"
>Largeur</label >Largeur</label
> >
<input <div class="input-with-unit">
<NumberInput
id="page-width" id="page-width"
type="number" :modelValue="customWidth"
:value="parseInt(pageWidth)" @update:modelValue="(v) => (customWidth = v)"
disabled :min="50"
:step="1"
:disabled="!isCustomFormat"
/> />
<button type="button" disabled>mm</button> <div class="unit-toggle">
<button type="button" class="active">mm</button>
</div>
</div>
</div> </div>
<div class="field field-size field--view-only"> <div
class="field field-margin"
:class="{ 'field--view-only': !isCustomFormat }"
>
<label for="page-height" class="label-with-tooltip" data-css="height" <label for="page-height" class="label-with-tooltip" data-css="height"
>Hauteur</label >Hauteur</label
> >
<input <div class="input-with-unit">
<NumberInput
id="page-height" id="page-height"
type="number" :modelValue="customHeight"
:value="parseInt(pageHeight)" @update:modelValue="(v) => (customHeight = v)"
disabled :min="50"
:step="1"
:disabled="!isCustomFormat"
/> />
<button type="button" disabled>mm</button> <div class="unit-toggle">
<button type="button" class="active">mm</button>
</div>
</div>
</div> </div>
</div> </div>
@ -51,7 +72,10 @@
<h3>Marges</h3> <h3>Marges</h3>
<div class="field field-margin"> <div class="field field-margin">
<label for="margin-top" class="label-with-tooltip" data-css="margin-top" <label
for="margin-top"
class="label-with-tooltip"
data-css="margin-top"
>Haut</label >Haut</label
> >
<div class="input-with-unit"> <div class="input-with-unit">
@ -60,30 +84,30 @@
:modelValue="margins.top.value" :modelValue="margins.top.value"
:min="0" :min="0"
:step="1" :step="1"
@update:modelValue="(value) => margins.top.value = value" @update:modelValue="(value) => (margins.top.value = value)"
/> />
<div class="unit-toggle"> <div class="unit-toggle">
<button <button
type="button" type="button"
:class="{ active: margins.top.unit === 'mm' }" :class="{ active: margins.top.unit === 'mm' }"
@click="margins.top.unit = 'mm'" @click="updateMarginUnit('top', 'mm')"
> >
mm mm
</button> </button>
<button <button
type="button" type="button"
:class="{ active: margins.top.unit === 'px' }" :class="{ active: margins.top.unit === 'px' }"
@click="margins.top.unit = 'px'" @click="updateMarginUnit('top', 'px')"
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.top.unit === 'rem' }" :class="{ active: margins.top.unit === 'rem' }"
@click="margins.top.unit = 'rem'" @click="updateMarginUnit('top', 'rem')"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -101,30 +125,30 @@
:modelValue="margins.bottom.value" :modelValue="margins.bottom.value"
:min="0" :min="0"
:step="1" :step="1"
@update:modelValue="(value) => margins.bottom.value = value" @update:modelValue="(value) => (margins.bottom.value = value)"
/> />
<div class="unit-toggle"> <div class="unit-toggle">
<button <button
type="button" type="button"
:class="{ active: margins.bottom.unit === 'mm' }" :class="{ active: margins.bottom.unit === 'mm' }"
@click="margins.bottom.unit = 'mm'" @click="updateMarginUnit('bottom', 'mm')"
> >
mm mm
</button> </button>
<button <button
type="button" type="button"
:class="{ active: margins.bottom.unit === 'px' }" :class="{ active: margins.bottom.unit === 'px' }"
@click="margins.bottom.unit = 'px'" @click="updateMarginUnit('bottom', 'px')"
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.bottom.unit === 'rem' }" :class="{ active: margins.bottom.unit === 'rem' }"
@click="margins.bottom.unit = 'rem'" @click="updateMarginUnit('bottom', 'rem')"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -142,30 +166,30 @@
:modelValue="margins.left.value" :modelValue="margins.left.value"
:min="0" :min="0"
:step="1" :step="1"
@update:modelValue="(value) => margins.left.value = value" @update:modelValue="(value) => (margins.left.value = value)"
/> />
<div class="unit-toggle"> <div class="unit-toggle">
<button <button
type="button" type="button"
:class="{ active: margins.left.unit === 'mm' }" :class="{ active: margins.left.unit === 'mm' }"
@click="margins.left.unit = 'mm'" @click="updateMarginUnit('left', 'mm')"
> >
mm mm
</button> </button>
<button <button
type="button" type="button"
:class="{ active: margins.left.unit === 'px' }" :class="{ active: margins.left.unit === 'px' }"
@click="margins.left.unit = 'px'" @click="updateMarginUnit('left', 'px')"
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.left.unit === 'rem' }" :class="{ active: margins.left.unit === 'rem' }"
@click="margins.left.unit = 'rem'" @click="updateMarginUnit('left', 'rem')"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -183,30 +207,30 @@
:modelValue="margins.right.value" :modelValue="margins.right.value"
:min="0" :min="0"
:step="1" :step="1"
@update:modelValue="(value) => margins.right.value = value" @update:modelValue="(value) => (margins.right.value = value)"
/> />
<div class="unit-toggle"> <div class="unit-toggle">
<button <button
type="button" type="button"
:class="{ active: margins.right.unit === 'mm' }" :class="{ active: margins.right.unit === 'mm' }"
@click="margins.right.unit = 'mm'" @click="updateMarginUnit('right', 'mm')"
> >
mm mm
</button> </button>
<button <button
type="button" type="button"
:class="{ active: margins.right.unit === 'px' }" :class="{ active: margins.right.unit === 'px' }"
@click="margins.right.unit = 'px'" @click="updateMarginUnit('right', 'px')"
> >
px px
</button> </button>
<button <!-- <button
type="button" type="button"
:class="{ active: margins.right.unit === 'rem' }" :class="{ active: margins.right.unit === 'rem' }"
@click="margins.right.unit = 'rem'" @click="updateMarginUnit('right', 'rem')"
> >
rem rem
</button> </button> -->
</div> </div>
</div> </div>
</div> </div>
@ -214,7 +238,10 @@
<div class="settings-subsection"> <div class="settings-subsection">
<div class="field field-simple"> <div class="field field-simple">
<label for="background" class="label-with-tooltip" data-css="background" <label
for="background"
class="label-with-tooltip"
data-css="background"
>Arrière-plan</label >Arrière-plan</label
> >
<div class="input-with-color"> <div class="input-with-color">
@ -242,20 +269,19 @@
hex hex
</button> </button>
</div> </div>
--> --></div>
</div>
</div> </div>
</div> </div>
<div class="settings-subsection"> <div class="settings-subsection" title="Fonctionnalité à venir">
<div class="field field-simple"> <div class="field field-simple field--view-only">
<label <label
for="pattern" for="pattern"
class="label-with-tooltip" class="label-with-tooltip"
data-css="background-image" data-css="background-image"
>Motif</label >Motif</label
> >
<select id="pattern" v-model="pattern"> <select id="pattern" v-model="pattern" disabled>
<option value="">Choisissez</option> <option value="">Choisissez</option>
<option value="dots">Points</option> <option value="dots">Points</option>
<option value="lines">Lignes</option> <option value="lines">Lignes</option>
@ -295,6 +321,7 @@ import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce'; import { useDebounce } from '../../composables/useDebounce';
import Coloris from '@melloware/coloris'; import Coloris from '@melloware/coloris';
import NumberInput from '../ui/NumberInput.vue'; import NumberInput from '../ui/NumberInput.vue';
import { convertUnit } from '../../utils/unit-conversion';
import '@melloware/coloris/dist/coloris.css'; import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore(); const stylesheetStore = useStylesheetStore();
@ -307,15 +334,16 @@ let isUpdatingFromStore = false;
const pageFormat = ref('A4'); const pageFormat = ref('A4');
const pageFormats = { const pageFormats = {
A4: { width: '210mm', height: '297mm' }, A4: { width: 210, height: 297 },
A5: { width: '148mm', height: '210mm' }, A5: { width: 148, height: 210 },
A3: { width: '297mm', height: '420mm' }, // A3: { width: 297, height: 420 },
letter: { width: '8.5in', height: '11in' }, // letter: { width: 216, height: 279 },
legal: { width: '8.5in', height: '14in' }, // legal: { width: 216, height: 356 },
}; };
const pageWidth = computed(() => pageFormats[pageFormat.value].width); const customWidth = ref(210);
const pageHeight = computed(() => pageFormats[pageFormat.value].height); const customHeight = ref(297);
const isCustomFormat = computed(() => pageFormat.value === 'custom');
const margins = ref({ const margins = ref({
top: { value: 20, unit: 'mm' }, top: { value: 20, unit: 'mm' },
@ -324,6 +352,12 @@ const margins = ref({
right: { value: 20, unit: 'mm' }, right: { value: 20, unit: 'mm' },
}); });
const updateMarginUnit = (side, newUnit) => {
const m = margins.value[side];
m.value = convertUnit(m.value, m.unit, newUnit);
m.unit = newUnit;
};
const background = ref({ const background = ref({
value: '', value: '',
format: 'hex', format: 'hex',
@ -340,9 +374,37 @@ const immediateUpdate = (callback) => {
watch(pageFormat, (newFormat) => { watch(pageFormat, (newFormat) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
if (newFormat === 'custom') {
immediateUpdate(() => {
stylesheetStore.updateProperty(
'@page',
'size',
`${customWidth.value}mm ${customHeight.value}mm`,
''
);
});
} else {
const format = pageFormats[newFormat];
if (format) {
customWidth.value = format.width;
customHeight.value = format.height;
}
immediateUpdate(() => { immediateUpdate(() => {
stylesheetStore.updateProperty('@page', 'size', newFormat, ''); stylesheetStore.updateProperty('@page', 'size', newFormat, '');
}); });
}
});
watch([customWidth, customHeight], () => {
if (isUpdatingFromStore || !isCustomFormat.value) return;
debouncedUpdate(() => {
stylesheetStore.updateProperty(
'@page',
'size',
`${customWidth.value}mm ${customHeight.value}mm`,
''
);
});
}); });
const updateMargins = () => { const updateMargins = () => {
@ -539,9 +601,24 @@ const syncFromStore = () => {
try { try {
const pageBlock = stylesheetStore.extractBlock('@page'); const pageBlock = stylesheetStore.extractBlock('@page');
const sizeMatch = pageBlock.match(/size:\s*([A-Za-z0-9]+)/); // Try named format first (A4, A5), then custom dimensions
if (sizeMatch) { const namedSizeMatch = pageBlock.match(/size:\s*(A4|A5)\b/);
pageFormat.value = sizeMatch[1]; if (namedSizeMatch) {
pageFormat.value = namedSizeMatch[1];
const format = pageFormats[namedSizeMatch[1]];
if (format) {
customWidth.value = format.width;
customHeight.value = format.height;
}
} else {
const customSizeMatch = pageBlock.match(
/size:\s*([0-9.]+)mm\s+([0-9.]+)mm/
);
if (customSizeMatch) {
customWidth.value = parseFloat(customSizeMatch[1]);
customHeight.value = parseFloat(customSizeMatch[2]);
pageFormat.value = 'custom';
}
} }
const marginMatch = pageBlock.match( const marginMatch = pageBlock.match(

View file

@ -11,9 +11,9 @@
<!-- Police --> <!-- Police -->
<div class="settings-subsection"> <div class="settings-subsection">
<div class="field field-font"> <div class="field field-font">
<label for="text-font" class="label-with-tooltip" data-css="font-family">Police</label> <label for="text-font" class="label-with-tooltip field--view-only" data-css="font-family" title="Fonctionnalité à venir">Police</label>
<div class="field-with-option"> <div class="field-with-option">
<select id="text-font" v-model="font"> <select id="text-font" v-model="font" disabled class="field--view-only" title="Fonctionnalité à venir">
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</option> <option v-for="f in fonts" :key="f" :value="f">{{ f }}</option>
</select> </select>
<div class="field-checkbox"> <div class="field-checkbox">
@ -38,7 +38,7 @@
<label for="text-size-range" class="label-with-tooltip" data-css="font-size">Taille du texte</label> <label for="text-size-range" class="label-with-tooltip" data-css="font-size">Taille du texte</label>
<InputWithUnit <InputWithUnit
v-model="fontSize" v-model="fontSize"
:units="['px', 'em', 'rem']" :units="['px']"
:min="8" :min="8"
:max="72" :max="72"
showRange showRange
@ -64,6 +64,7 @@
<label for="text-color" class="label-with-tooltip" data-css="color">Couleur</label> <label for="text-color" class="label-with-tooltip" data-css="color">Couleur</label>
<div class="input-with-color"> <div class="input-with-color">
<input <input
ref="colorInput"
id="text-color" id="text-color"
type="text" type="text"
v-model="color" v-model="color"
@ -80,6 +81,7 @@
<label for="text-background" class="label-with-tooltip" data-css="background">Arrière-plan</label> <label for="text-background" class="label-with-tooltip" data-css="background">Arrière-plan</label>
<div class="input-with-color"> <div class="input-with-color">
<input <input
ref="backgroundInput"
id="text-background" id="text-background"
type="text" type="text"
v-model="background" v-model="background"
@ -127,11 +129,11 @@
:class="{ active: marginOuterDetailed.top.unit === 'px' }" :class="{ active: marginOuterDetailed.top.unit === 'px' }"
@click="updateMarginOuterUnit('px')" @click="updateMarginOuterUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginOuterDetailed.top.unit === 'rem' }" :class="{ active: marginOuterDetailed.top.unit === 'rem' }"
@click="updateMarginOuterUnit('rem')" @click="updateMarginOuterUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -157,11 +159,11 @@
:class="{ active: marginOuterDetailed.bottom.unit === 'px' }" :class="{ active: marginOuterDetailed.bottom.unit === 'px' }"
@click="updateMarginOuterUnit('px')" @click="updateMarginOuterUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginOuterDetailed.bottom.unit === 'rem' }" :class="{ active: marginOuterDetailed.bottom.unit === 'rem' }"
@click="updateMarginOuterUnit('rem')" @click="updateMarginOuterUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -187,11 +189,11 @@
:class="{ active: marginOuterDetailed.left.unit === 'px' }" :class="{ active: marginOuterDetailed.left.unit === 'px' }"
@click="updateMarginOuterUnit('px')" @click="updateMarginOuterUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginOuterDetailed.left.unit === 'rem' }" :class="{ active: marginOuterDetailed.left.unit === 'rem' }"
@click="updateMarginOuterUnit('rem')" @click="updateMarginOuterUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -217,11 +219,11 @@
:class="{ active: marginOuterDetailed.right.unit === 'px' }" :class="{ active: marginOuterDetailed.right.unit === 'px' }"
@click="updateMarginOuterUnit('px')" @click="updateMarginOuterUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginOuterDetailed.right.unit === 'rem' }" :class="{ active: marginOuterDetailed.right.unit === 'rem' }"
@click="updateMarginOuterUnit('rem')" @click="updateMarginOuterUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -264,11 +266,11 @@
:class="{ active: marginInnerDetailed.top.unit === 'px' }" :class="{ active: marginInnerDetailed.top.unit === 'px' }"
@click="updateMarginInnerUnit('px')" @click="updateMarginInnerUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginInnerDetailed.top.unit === 'rem' }" :class="{ active: marginInnerDetailed.top.unit === 'rem' }"
@click="updateMarginInnerUnit('rem')" @click="updateMarginInnerUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -294,11 +296,11 @@
:class="{ active: marginInnerDetailed.bottom.unit === 'px' }" :class="{ active: marginInnerDetailed.bottom.unit === 'px' }"
@click="updateMarginInnerUnit('px')" @click="updateMarginInnerUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginInnerDetailed.bottom.unit === 'rem' }" :class="{ active: marginInnerDetailed.bottom.unit === 'rem' }"
@click="updateMarginInnerUnit('rem')" @click="updateMarginInnerUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -324,11 +326,11 @@
:class="{ active: marginInnerDetailed.left.unit === 'px' }" :class="{ active: marginInnerDetailed.left.unit === 'px' }"
@click="updateMarginInnerUnit('px')" @click="updateMarginInnerUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginInnerDetailed.left.unit === 'rem' }" :class="{ active: marginInnerDetailed.left.unit === 'rem' }"
@click="updateMarginInnerUnit('rem')" @click="updateMarginInnerUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -354,11 +356,11 @@
:class="{ active: marginInnerDetailed.right.unit === 'px' }" :class="{ active: marginInnerDetailed.right.unit === 'px' }"
@click="updateMarginInnerUnit('px')" @click="updateMarginInnerUnit('px')"
>px</button> >px</button>
<button <!-- <button
type="button" type="button"
:class="{ active: marginInnerDetailed.right.unit === 'rem' }" :class="{ active: marginInnerDetailed.right.unit === 'rem' }"
@click="updateMarginInnerUnit('rem')" @click="updateMarginInnerUnit('rem')"
>rem</button> >rem</button> -->
</div> </div>
</div> </div>
</div> </div>
@ -377,6 +379,7 @@ import NumberInput from '../ui/NumberInput.vue';
import { useCssUpdater } from '../../composables/useCssUpdater'; import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync'; import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce'; import { useDebounce } from '../../composables/useDebounce';
import { convertUnit } from '../../utils/unit-conversion';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater(); const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync(); const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
@ -400,6 +403,8 @@ const fontSize = ref({ value: 16, unit: 'px' });
const alignment = ref('left'); const alignment = ref('left');
const color = ref('rgb(0, 0, 0)'); const color = ref('rgb(0, 0, 0)');
const background = ref('transparent'); const background = ref('transparent');
const colorInput = ref(null);
const backgroundInput = ref(null);
const marginOuterDetailed = ref({ const marginOuterDetailed = ref({
top: { value: 0, unit: 'mm' }, top: { value: 0, unit: 'mm' },
@ -435,20 +440,24 @@ const prevMarginInner = ref({
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
// Update margin outer unit for all sides // Update margin outer unit for all sides with conversion
const updateMarginOuterUnit = (unit) => { const updateMarginOuterUnit = (newUnit) => {
marginOuterDetailed.value.top.unit = unit; const sides = ['top', 'right', 'bottom', 'left'];
marginOuterDetailed.value.right.unit = unit; sides.forEach((side) => {
marginOuterDetailed.value.bottom.unit = unit; const s = marginOuterDetailed.value[side];
marginOuterDetailed.value.left.unit = unit; s.value = convertUnit(s.value, s.unit, newUnit);
s.unit = newUnit;
});
}; };
// Update margin inner unit for all sides // Update margin inner unit for all sides with conversion
const updateMarginInnerUnit = (unit) => { const updateMarginInnerUnit = (newUnit) => {
marginInnerDetailed.value.top.unit = unit; const sides = ['top', 'right', 'bottom', 'left'];
marginInnerDetailed.value.right.unit = unit; sides.forEach((side) => {
marginInnerDetailed.value.bottom.unit = unit; const s = marginInnerDetailed.value[side];
marginInnerDetailed.value.left.unit = unit; s.value = convertUnit(s.value, s.unit, newUnit);
s.unit = newUnit;
});
}; };
// Watchers for body styles // Watchers for body styles
@ -459,7 +468,7 @@ watch(font, (val) => {
watch(italic, (val) => { watch(italic, (val) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
updateStyle('body', 'font-style', val ? 'italic' : 'normal'); updateStyle('p', 'font-style', val ? 'italic' : 'normal');
}); });
watch(alignment, (val) => { watch(alignment, (val) => {
@ -474,7 +483,7 @@ watch(color, (val) => {
watch(background, (val) => { watch(background, (val) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
updateStyle('body', 'background', val); updateStyle('p', 'background', val);
}); });
// Watchers for paragraph styles // Watchers for paragraph styles
@ -650,7 +659,7 @@ const syncFromStore = () => {
isUpdatingFromStore = true; isUpdatingFromStore = true;
// Body styles // Body styles
const fontStyle = extractValue('body', 'font-style'); const fontStyle = extractValue('p', 'font-style');
if (fontStyle) italic.value = fontStyle === 'italic'; if (fontStyle) italic.value = fontStyle === 'italic';
const textAlign = extractValue('body', 'text-align'); const textAlign = extractValue('body', 'text-align');
@ -659,14 +668,14 @@ const syncFromStore = () => {
const colorVal = extractValue('body', 'color'); const colorVal = extractValue('body', 'color');
if (colorVal) color.value = colorVal; if (colorVal) color.value = colorVal;
const bgVal = extractValue('body', 'background'); const bgVal = extractValue('p', 'background');
if (bgVal) background.value = bgVal; if (bgVal) background.value = bgVal;
// Paragraph styles // Paragraph styles
const fontWeight = extractValue('p', 'font-weight'); const fontWeight = extractValue('p', 'font-weight');
if (fontWeight) weight.value = fontWeight; if (fontWeight) weight.value = fontWeight;
const fontSizeVal = extractNumericValue('p', 'font-size', ['px', 'em', 'rem']); const fontSizeVal = extractNumericValue('p', 'font-size', ['px']); // ['px', 'em', 'rem']
if (fontSizeVal) fontSize.value = fontSizeVal; if (fontSizeVal) fontSize.value = fontSizeVal;
// Margins // Margins
@ -739,6 +748,14 @@ const syncFromStore = () => {
isUpdatingFromStore = false; isUpdatingFromStore = false;
}; };
const updateColorisButtons = () => {
[colorInput.value, backgroundInput.value].forEach((input) => {
if (input) {
input.dispatchEvent(new Event('input', { bubbles: true }));
}
});
};
onMounted(() => { onMounted(() => {
Coloris.init(); Coloris.init();
Coloris({ Coloris({
@ -749,6 +766,7 @@ onMounted(() => {
swatches: ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', 'transparent'] swatches: ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', 'transparent']
}); });
syncFromStore(); syncFromStore();
setTimeout(updateColorisButtons, 100);
}); });
</script> </script>

View file

@ -29,6 +29,7 @@
<script setup> <script setup>
import NumberInput from './NumberInput.vue'; import NumberInput from './NumberInput.vue';
import UnitToggle from './UnitToggle.vue'; import UnitToggle from './UnitToggle.vue';
import { convertUnit } from '../../utils/unit-conversion';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -64,7 +65,8 @@ const updateValue = (value) => {
emit('update:modelValue', { ...props.modelValue, value }); emit('update:modelValue', { ...props.modelValue, value });
}; };
const updateUnit = (unit) => { const updateUnit = (newUnit) => {
emit('update:modelValue', { ...props.modelValue, unit }); const converted = convertUnit(props.modelValue.value, props.modelValue.unit, newUnit);
emit('update:modelValue', { value: converted, unit: newUnit });
}; };
</script> </script>

View file

@ -18,7 +18,7 @@ export function useCssSync() {
* Extract a numeric CSS value with unit * Extract a numeric CSS value with unit
* Returns { value: number, unit: string } or null * Returns { value: number, unit: string } or null
*/ */
const extractNumericValue = (selector, property, allowedUnits = ['px', 'em', 'rem', 'mm']) => { const extractNumericValue = (selector, property, allowedUnits = ['px', 'mm']) => { // ['px', 'em', 'rem', 'mm']
const block = store.extractBlock(selector); const block = store.extractBlock(selector);
if (!block) return null; if (!block) return null;
@ -38,7 +38,7 @@ export function useCssSync() {
* Extract margin/padding shorthand (handles 1 or 4 values) * Extract margin/padding shorthand (handles 1 or 4 values)
* Returns { simple: { value, unit } } or { detailed: { top, right, bottom, left } } * Returns { simple: { value, unit } } or { detailed: { top, right, bottom, left } }
*/ */
const extractSpacing = (selector, property, allowedUnits = ['mm', 'px', 'rem']) => { const extractSpacing = (selector, property, allowedUnits = ['mm', 'px']) => { // ['mm', 'px', 'rem']
const block = store.extractBlock(selector); const block = store.extractBlock(selector);
if (!block) return null; if (!block) return null;

View file

@ -4,13 +4,13 @@ import { ref } from 'vue';
* Composable for managing interactions with pages and elements in the iframe * Composable for managing interactions with pages and elements in the iframe
* Handles hover effects, labels, and click events for both pages and content elements * Handles hover effects, labels, and click events for both pages and content elements
*/ */
export function useIframeInteractions({ elementPopup, pagePopup }) { export function useIframeInteractions({ elementPopup /*, pagePopup // DISABLED: page template styling feature */ }) {
// Page interaction state // DISABLED: page template styling feature
const hoveredPage = ref(null); // const hoveredPage = ref(null);
const selectedPages = ref([]); // Pages with active border (when popup is open) // const selectedPages = ref([]); // Pages with active border (when popup is open)
const hoveredElement = ref(null); // Currently hovered content element const hoveredElement = ref(null); // Currently hovered content element
const selectedElement = ref(null); // Selected element (when popup is open) const selectedElement = ref(null); // Selected element (when popup is open)
const EDGE_THRESHOLD = 30; // px from edge to trigger hover // const EDGE_THRESHOLD = 30; // px from edge to trigger hover // DISABLED: page template styling feature
// Text elements that can trigger ElementPopup (excluding containers, images, etc.) // Text elements that can trigger ElementPopup (excluding containers, images, etc.)
const CONTENT_ELEMENTS = [ const CONTENT_ELEMENTS = [
@ -34,6 +34,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
'FIGCAPTION', 'FIGCAPTION',
]; ];
/* DISABLED: page template styling feature
// Check if mouse position is near the edges of a page element // Check if mouse position is near the edges of a page element
const isNearPageEdge = (pageElement, mouseX, mouseY) => { const isNearPageEdge = (pageElement, mouseX, mouseY) => {
const rect = pageElement.getBoundingClientRect(); const rect = pageElement.getBoundingClientRect();
@ -64,6 +65,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
(p) => (p.getAttribute('data-page-type') || 'default') === pageType (p) => (p.getAttribute('data-page-type') || 'default') === pageType
); );
}; };
*/
// Get selector for element (same logic as ElementPopup) // Get selector for element (same logic as ElementPopup)
const getSelectorFromElement = (element) => { const getSelectorFromElement = (element) => {
@ -117,6 +119,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
} }
}; };
/* DISABLED: page template styling feature
// Create and position page label on hover // Create and position page label on hover
const createPageLabel = (page) => { const createPageLabel = (page) => {
const doc = page.ownerDocument; const doc = page.ownerDocument;
@ -148,6 +151,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
label.remove(); label.remove();
} }
}; };
*/
// Check if element is a content element (or find closest content parent) // Check if element is a content element (or find closest content parent)
const getContentElement = (element) => { const getContentElement = (element) => {
@ -161,6 +165,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
return null; return null;
}; };
/* DISABLED: page template styling feature
// Clear selection highlight from all selected pages // Clear selection highlight from all selected pages
const clearSelectedPages = () => { const clearSelectedPages = () => {
selectedPages.value.forEach((page) => { selectedPages.value.forEach((page) => {
@ -168,6 +173,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
}); });
selectedPages.value = []; selectedPages.value = [];
}; };
*/
// Clear selected element highlight // Clear selected element highlight
const clearSelectedElement = () => { const clearSelectedElement = () => {
@ -188,6 +194,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
// Handle mouse movement in iframe // Handle mouse movement in iframe
const handleIframeMouseMove = (event) => { const handleIframeMouseMove = (event) => {
/* DISABLED: page template styling feature
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page'); const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
let foundPage = null; let foundPage = null;
@ -217,9 +224,9 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
hoveredPage.value = foundPage; hoveredPage.value = foundPage;
} }
*/
// If not near page edge, check for content element hover // Check for content element hover
if (!foundPage) {
const contentElement = getContentElement(event.target); const contentElement = getContentElement(event.target);
const doc = event.target.ownerDocument; const doc = event.target.ownerDocument;
@ -232,9 +239,8 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
hoveredElement.value.classList.remove('element-hovered'); hoveredElement.value.classList.remove('element-hovered');
} }
// Remove previous labels // Remove previous label
removeElementLabel(doc); removeElementLabel(doc);
removePageLabel(doc);
// Add highlight to new element (only if not already selected) // Add highlight to new element (only if not already selected)
if (contentElement && contentElement !== selectedElement.value) { if (contentElement && contentElement !== selectedElement.value) {
@ -244,24 +250,13 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
hoveredElement.value = contentElement; hoveredElement.value = contentElement;
} }
} else {
// Clear element hover when hovering page edge
if (
hoveredElement.value &&
hoveredElement.value !== selectedElement.value
) {
hoveredElement.value.classList.remove('element-hovered');
hoveredElement.value = null;
}
// Remove element label when hovering page edge
removeElementLabel(event.target.ownerDocument);
}
}; };
// Handle click in iframe // Handle click in iframe
const handleIframeClick = (event) => { const handleIframeClick = (event) => {
const element = event.target; const element = event.target;
/* DISABLED: page template styling feature
// Check if clicking near a page edge // Check if clicking near a page edge
if (hoveredPage.value) { if (hoveredPage.value) {
event.stopPropagation(); event.stopPropagation();
@ -286,35 +281,28 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
elementPopup.value.close(); elementPopup.value.close();
return; return;
} }
*/
// Only show popup for elements inside the page template // Only show popup for elements inside the page template
const isInsidePage = element.closest('.pagedjs_page'); const isInsidePage = element.closest('.pagedjs_page');
if (!isInsidePage) { if (!isInsidePage) {
clearSelectedPages();
clearSelectedElement(); clearSelectedElement();
elementPopup.value.close(); elementPopup.value.close();
pagePopup.value.close();
return; return;
} }
// Only show ElementPopup for content elements, not divs // Only show ElementPopup for content elements, not divs
const contentElement = getContentElement(element); const contentElement = getContentElement(element);
if (!contentElement) { if (!contentElement) {
clearSelectedPages();
clearSelectedElement(); clearSelectedElement();
elementPopup.value.close(); elementPopup.value.close();
pagePopup.value.close();
return; return;
} }
// Clear page selections
clearSelectedPages();
// If popup is already open and we're clicking another element, close it // If popup is already open and we're clicking another element, close it
if (elementPopup.value.visible) { if (elementPopup.value.visible) {
clearSelectedElement(); clearSelectedElement();
elementPopup.value.close(); elementPopup.value.close();
pagePopup.value.close();
return; return;
} }
@ -332,7 +320,6 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
// Get document and remove labels when opening popup // Get document and remove labels when opening popup
const doc = event.target.ownerDocument; const doc = event.target.ownerDocument;
removeElementLabel(doc); removeElementLabel(doc);
removePageLabel(doc);
// Select the new element // Select the new element
selectedElement.value = contentElement; selectedElement.value = contentElement;
@ -342,13 +329,14 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
const count = getSimilarElementsCount(contentElement, doc); const count = getSimilarElementsCount(contentElement, doc);
elementPopup.value.handleIframeClick(event, contentElement, count); elementPopup.value.handleIframeClick(event, contentElement, count);
pagePopup.value.close();
}; };
// Handlers for popup close events // Handlers for popup close events
/* DISABLED: page template styling feature
const handlePagePopupClose = () => { const handlePagePopupClose = () => {
clearSelectedPages(); clearSelectedPages();
}; };
*/
const handleElementPopupClose = () => { const handleElementPopupClose = () => {
clearSelectedElement(); clearSelectedElement();
@ -356,17 +344,17 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
return { return {
// State // State
hoveredPage, // hoveredPage, // DISABLED: page template styling feature
selectedPages, // selectedPages, // DISABLED: page template styling feature
hoveredElement, hoveredElement,
selectedElement, selectedElement,
// Handlers // Handlers
handleIframeMouseMove, handleIframeMouseMove,
handleIframeClick, handleIframeClick,
handlePagePopupClose, // handlePagePopupClose, // DISABLED: page template styling feature
handleElementPopupClose, handleElementPopupClose,
// Utilities // Utilities
clearSelectedPages, // clearSelectedPages, // DISABLED: page template styling feature
clearSelectedElement, clearSelectedElement,
}; };
} }

View file

@ -7,7 +7,7 @@ import { onMounted, onUnmounted } from 'vue';
export function useKeyboardShortcuts({ export function useKeyboardShortcuts({
stylesheetStore, stylesheetStore,
elementPopup, elementPopup,
pagePopup, // pagePopup, // DISABLED: page template styling feature
activeTab, activeTab,
printPreview printPreview
}) { }) {
@ -22,10 +22,12 @@ export function useKeyboardShortcuts({
elementPopup.value.close(); elementPopup.value.close();
return; return;
} }
/* DISABLED: page template styling feature
if (pagePopup.value?.visible) { if (pagePopup.value?.visible) {
pagePopup.value.close(); pagePopup.value.close();
return; return;
} }
*/
} }
// Backslash key - toggle editor panel // Backslash key - toggle editor panel

View file

@ -16,6 +16,7 @@ export function usePreviewRenderer({
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
const isTransitioning = ref(false); const isTransitioning = ref(false);
const initialized = ref(false);
let keyboardShortcutHandler = null; let keyboardShortcutHandler = null;
/** /**
@ -117,6 +118,9 @@ export function usePreviewRenderer({
// Swap current frame // Swap current frame
currentFrameIndex.value = currentFrameIndex.value === 1 ? 2 : 1; currentFrameIndex.value = currentFrameIndex.value === 1 ? 2 : 1;
isTransitioning.value = false; isTransitioning.value = false;
if (!initialized.value) {
initialized.value = true;
}
}, 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
@ -127,6 +131,7 @@ export function usePreviewRenderer({
watch( watch(
() => stylesheetStore.content, () => stylesheetStore.content,
() => { () => {
if (!initialized.value) return;
renderPreview(); renderPreview();
} }
); );
@ -135,6 +140,7 @@ export function usePreviewRenderer({
watch( watch(
() => narrativeStore.data, () => narrativeStore.data,
() => { () => {
if (!initialized.value) return;
if (narrativeStore.data) { if (narrativeStore.data) {
renderPreview(); renderPreview();
} }
@ -152,6 +158,7 @@ export function usePreviewRenderer({
renderPreview, renderPreview,
currentFrameIndex, currentFrameIndex,
isTransitioning, isTransitioning,
initialized,
setKeyboardShortcutHandler, setKeyboardShortcutHandler,
}; };
} }

View file

@ -1 +1 @@
@import '../public/assets/css/style.scss'; /* @import '../public/assets/css/style.scss'; */

View file

@ -0,0 +1,29 @@
const PX_PER_MM = 3.7795275591;
const PX_PER_REM = 16;
const PX_PER_EM = 16;
const toPx = {
px: (v) => v,
mm: (v) => v * PX_PER_MM,
rem: (v) => v * PX_PER_REM,
em: (v) => v * PX_PER_EM,
};
const fromPx = {
px: (v) => v,
mm: (v) => v / PX_PER_MM,
rem: (v) => v / PX_PER_REM,
em: (v) => v / PX_PER_EM,
};
export function convertUnit(value, fromUnit, toUnit) {
if (fromUnit === toUnit) return value;
const converterTo = toPx[fromUnit];
const converterFrom = fromPx[toUnit];
if (!converterTo || !converterFrom) return value;
const px = converterTo(value);
return Math.round(converterFrom(px) * 100) / 100;
}