This commit is contained in:
commit
95efcac454
27 changed files with 700 additions and 429 deletions
8
public/assets/css/src/_global.scss
Normal file
8
public/assets/css/src/_global.scss
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
button[disabled] {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[disabled]:hover {
|
||||||
|
background-color: inherit !important;
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
@ -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 |
|
|
@ -1 +1 @@
|
||||||
Uuid: f9jqf7vus7w0dx6z
|
Uuid: kmmswqjqh5mxyecl
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ columns:
|
||||||
template:
|
template:
|
||||||
- map
|
- map
|
||||||
- geoformat
|
- geoformat
|
||||||
|
info: "{{ page.intendedTemplate }}"
|
||||||
sidebar:
|
sidebar:
|
||||||
width: 1/3
|
width: 1/3
|
||||||
sections:
|
sections:
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,17 @@ columns:
|
||||||
fields:
|
fields:
|
||||||
type: fields
|
type: fields
|
||||||
fields:
|
fields:
|
||||||
subtitle:
|
subtitle:
|
||||||
label: Sous-titre
|
label: Sous-titre
|
||||||
type: text
|
type: text
|
||||||
width: 1/2
|
width: 1/2
|
||||||
cover:
|
cover:
|
||||||
label: Image de couverture
|
label: Image de couverture
|
||||||
type: files
|
type: files
|
||||||
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
|
|
@ -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',
|
||||||
|
|
|
||||||
69
public/site/plugins/map-editor/routes/mapdata/save.php
Normal file
69
public/site/plugins/map-editor/routes/mapdata/save.php
Normal 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
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
||||||
27
src/App.vue
27
src/App.vue
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,231 +1,258 @@
|
||||||
<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="field field-simple">
|
||||||
<div class="settings-subsection">
|
<label for="page-format" class="label-with-tooltip" data-css="size"
|
||||||
<div class="field field-simple">
|
>Format d'impression</label
|
||||||
<label for="page-format" class="label-with-tooltip" data-css="size"
|
>
|
||||||
>Format d'impression</label
|
<select id="page-format" v-model="pageFormat">
|
||||||
>
|
<option value="A4">A4</option>
|
||||||
<select id="page-format" v-model="pageFormat">
|
<option value="A5">A5</option>
|
||||||
<option value="A4">A4</option>
|
<!-- <option value="A3">A3</option>
|
||||||
<option value="A5">A5</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> -->
|
||||||
</select>
|
<option value="custom">Personnalisé</option>
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-subsection">
|
|
||||||
<div class="field field-size field--view-only">
|
|
||||||
<label for="page-width" class="label-with-tooltip" data-css="width"
|
|
||||||
>Largeur</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="page-width"
|
|
||||||
type="number"
|
|
||||||
:value="parseInt(pageWidth)"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<button type="button" disabled>mm</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field field-size field--view-only">
|
<div class="settings-subsection">
|
||||||
<label for="page-height" class="label-with-tooltip" data-css="height"
|
<div
|
||||||
>Hauteur</label
|
class="field field-margin"
|
||||||
|
:class="{ 'field--view-only': !isCustomFormat }"
|
||||||
>
|
>
|
||||||
<input
|
<label for="page-width" class="label-with-tooltip" data-css="width"
|
||||||
id="page-height"
|
>Largeur</label
|
||||||
type="number"
|
>
|
||||||
:value="parseInt(pageHeight)"
|
<div class="input-with-unit">
|
||||||
disabled
|
<NumberInput
|
||||||
/>
|
id="page-width"
|
||||||
<button type="button" disabled>mm</button>
|
:modelValue="customWidth"
|
||||||
|
@update:modelValue="(v) => (customWidth = v)"
|
||||||
|
:min="50"
|
||||||
|
:step="1"
|
||||||
|
:disabled="!isCustomFormat"
|
||||||
|
/>
|
||||||
|
<div class="unit-toggle">
|
||||||
|
<button type="button" class="active">mm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="field field-margin"
|
||||||
|
:class="{ 'field--view-only': !isCustomFormat }"
|
||||||
|
>
|
||||||
|
<label for="page-height" class="label-with-tooltip" data-css="height"
|
||||||
|
>Hauteur</label
|
||||||
|
>
|
||||||
|
<div class="input-with-unit">
|
||||||
|
<NumberInput
|
||||||
|
id="page-height"
|
||||||
|
:modelValue="customHeight"
|
||||||
|
@update:modelValue="(v) => (customHeight = v)"
|
||||||
|
:min="50"
|
||||||
|
:step="1"
|
||||||
|
:disabled="!isCustomFormat"
|
||||||
|
/>
|
||||||
|
<div class="unit-toggle">
|
||||||
|
<button type="button" class="active">mm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-subsection margins">
|
<div class="settings-subsection margins">
|
||||||
<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
|
||||||
>Haut</label
|
for="margin-top"
|
||||||
>
|
class="label-with-tooltip"
|
||||||
<div class="input-with-unit">
|
data-css="margin-top"
|
||||||
<NumberInput
|
>Haut</label
|
||||||
id="margin-top"
|
>
|
||||||
:modelValue="margins.top.value"
|
<div class="input-with-unit">
|
||||||
:min="0"
|
<NumberInput
|
||||||
:step="1"
|
id="margin-top"
|
||||||
@update:modelValue="(value) => margins.top.value = value"
|
:modelValue="margins.top.value"
|
||||||
/>
|
:min="0"
|
||||||
<div class="unit-toggle">
|
:step="1"
|
||||||
<button
|
@update:modelValue="(value) => (margins.top.value = value)"
|
||||||
type="button"
|
/>
|
||||||
:class="{ active: margins.top.unit === 'mm' }"
|
<div class="unit-toggle">
|
||||||
@click="margins.top.unit = 'mm'"
|
<button
|
||||||
>
|
type="button"
|
||||||
mm
|
:class="{ active: margins.top.unit === 'mm' }"
|
||||||
</button>
|
@click="updateMarginUnit('top', 'mm')"
|
||||||
<button
|
>
|
||||||
type="button"
|
mm
|
||||||
:class="{ active: margins.top.unit === 'px' }"
|
</button>
|
||||||
@click="margins.top.unit = 'px'"
|
<button
|
||||||
>
|
type="button"
|
||||||
px
|
:class="{ active: margins.top.unit === 'px' }"
|
||||||
</button>
|
@click="updateMarginUnit('top', 'px')"
|
||||||
<button
|
>
|
||||||
|
px
|
||||||
|
</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>
|
|
||||||
|
|
||||||
<div class="field field-margin">
|
<div class="field field-margin">
|
||||||
<label
|
<label
|
||||||
for="margin-bottom"
|
for="margin-bottom"
|
||||||
class="label-with-tooltip"
|
class="label-with-tooltip"
|
||||||
data-css="margin-bottom"
|
data-css="margin-bottom"
|
||||||
>Bas</label
|
>Bas</label
|
||||||
>
|
>
|
||||||
<div class="input-with-unit">
|
<div class="input-with-unit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="margin-bottom"
|
id="margin-bottom"
|
||||||
: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>
|
|
||||||
|
|
||||||
<div class="field field-margin">
|
<div class="field field-margin">
|
||||||
<label
|
<label
|
||||||
for="margin-left"
|
for="margin-left"
|
||||||
class="label-with-tooltip"
|
class="label-with-tooltip"
|
||||||
data-css="margin-left"
|
data-css="margin-left"
|
||||||
>Gauche</label
|
>Gauche</label
|
||||||
>
|
>
|
||||||
<div class="input-with-unit">
|
<div class="input-with-unit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="margin-left"
|
id="margin-left"
|
||||||
: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>
|
|
||||||
|
|
||||||
<div class="field field-margin">
|
<div class="field field-margin">
|
||||||
<label
|
<label
|
||||||
for="margin-right"
|
for="margin-right"
|
||||||
class="label-with-tooltip"
|
class="label-with-tooltip"
|
||||||
data-css="margin-right"
|
data-css="margin-right"
|
||||||
>Droite</label
|
>Droite</label
|
||||||
>
|
>
|
||||||
<div class="input-with-unit">
|
<div class="input-with-unit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="margin-right"
|
id="margin-right"
|
||||||
: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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<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
|
||||||
>Arrière-plan</label
|
for="background"
|
||||||
>
|
class="label-with-tooltip"
|
||||||
<div class="input-with-color">
|
data-css="background"
|
||||||
<input
|
>Arrière-plan</label
|
||||||
ref="backgroundColorInput"
|
>
|
||||||
type="text"
|
<div class="input-with-color">
|
||||||
id="background"
|
<input
|
||||||
v-model="background.value"
|
ref="backgroundColorInput"
|
||||||
data-coloris
|
type="text"
|
||||||
/>
|
id="background"
|
||||||
<!-- Temporarily commented out
|
v-model="background.value"
|
||||||
|
data-coloris
|
||||||
|
/>
|
||||||
|
<!-- Temporarily commented out
|
||||||
<div class="unit-toggle">
|
<div class="unit-toggle">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -242,50 +269,49 @@
|
||||||
hex
|
hex
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
-->
|
--></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-subsection" title="Fonctionnalité à venir">
|
||||||
|
<div class="field field-simple field--view-only">
|
||||||
|
<label
|
||||||
|
for="pattern"
|
||||||
|
class="label-with-tooltip"
|
||||||
|
data-css="background-image"
|
||||||
|
>Motif</label
|
||||||
|
>
|
||||||
|
<select id="pattern" v-model="pattern" disabled>
|
||||||
|
<option value="">Choisissez</option>
|
||||||
|
<option value="dots">Points</option>
|
||||||
|
<option value="lines">Lignes</option>
|
||||||
|
<option value="grid">Grille</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-subsection">
|
||||||
|
<div class="field checkbox-field">
|
||||||
|
<input id="page-numbers" type="checkbox" v-model="pageNumbers" />
|
||||||
|
<label
|
||||||
|
for="page-numbers"
|
||||||
|
class="label-with-tooltip"
|
||||||
|
data-css="@bottom-left/right"
|
||||||
|
>Numéro de page</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field checkbox-field">
|
||||||
|
<input id="running-title" type="checkbox" v-model="runningTitle" />
|
||||||
|
<label
|
||||||
|
for="running-title"
|
||||||
|
class="label-with-tooltip"
|
||||||
|
data-css="string-set"
|
||||||
|
>Titre courant</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-subsection">
|
|
||||||
<div class="field field-simple">
|
|
||||||
<label
|
|
||||||
for="pattern"
|
|
||||||
class="label-with-tooltip"
|
|
||||||
data-css="background-image"
|
|
||||||
>Motif</label
|
|
||||||
>
|
|
||||||
<select id="pattern" v-model="pattern">
|
|
||||||
<option value="">Choisissez</option>
|
|
||||||
<option value="dots">Points</option>
|
|
||||||
<option value="lines">Lignes</option>
|
|
||||||
<option value="grid">Grille</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-subsection">
|
|
||||||
<div class="field checkbox-field">
|
|
||||||
<input id="page-numbers" type="checkbox" v-model="pageNumbers" />
|
|
||||||
<label
|
|
||||||
for="page-numbers"
|
|
||||||
class="label-with-tooltip"
|
|
||||||
data-css="@bottom-left/right"
|
|
||||||
>Numéro de page</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field checkbox-field">
|
|
||||||
<input id="running-title" type="checkbox" v-model="runningTitle" />
|
|
||||||
<label
|
|
||||||
for="running-title"
|
|
||||||
class="label-with-tooltip"
|
|
||||||
data-css="string-set"
|
|
||||||
>Titre courant</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -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,8 +374,36 @@ const immediateUpdate = (callback) => {
|
||||||
watch(pageFormat, (newFormat) => {
|
watch(pageFormat, (newFormat) => {
|
||||||
if (isUpdatingFromStore) return;
|
if (isUpdatingFromStore) return;
|
||||||
|
|
||||||
immediateUpdate(() => {
|
if (newFormat === 'custom') {
|
||||||
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
|
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(() => {
|
||||||
|
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([customWidth, customHeight], () => {
|
||||||
|
if (isUpdatingFromStore || !isCustomFormat.value) return;
|
||||||
|
debouncedUpdate(() => {
|
||||||
|
stylesheetStore.updateProperty(
|
||||||
|
'@page',
|
||||||
|
'size',
|
||||||
|
`${customWidth.value}mm ${customHeight.value}mm`,
|
||||||
|
''
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,44 +224,31 @@ 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;
|
|
||||||
|
|
||||||
if (contentElement !== hoveredElement.value) {
|
if (contentElement !== hoveredElement.value) {
|
||||||
// Remove highlight from previous element (only if not selected)
|
// Remove highlight from previous element (only if not selected)
|
||||||
if (
|
|
||||||
hoveredElement.value &&
|
|
||||||
hoveredElement.value !== selectedElement.value
|
|
||||||
) {
|
|
||||||
hoveredElement.value.classList.remove('element-hovered');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove previous labels
|
|
||||||
removeElementLabel(doc);
|
|
||||||
removePageLabel(doc);
|
|
||||||
|
|
||||||
// Add highlight to new element (only if not already selected)
|
|
||||||
if (contentElement && contentElement !== selectedElement.value) {
|
|
||||||
contentElement.classList.add('element-hovered');
|
|
||||||
createElementLabel(contentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
hoveredElement.value = contentElement;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clear element hover when hovering page edge
|
|
||||||
if (
|
if (
|
||||||
hoveredElement.value &&
|
hoveredElement.value &&
|
||||||
hoveredElement.value !== selectedElement.value
|
hoveredElement.value !== selectedElement.value
|
||||||
) {
|
) {
|
||||||
hoveredElement.value.classList.remove('element-hovered');
|
hoveredElement.value.classList.remove('element-hovered');
|
||||||
hoveredElement.value = null;
|
|
||||||
}
|
}
|
||||||
// Remove element label when hovering page edge
|
|
||||||
removeElementLabel(event.target.ownerDocument);
|
// Remove previous label
|
||||||
|
removeElementLabel(doc);
|
||||||
|
|
||||||
|
// Add highlight to new element (only if not already selected)
|
||||||
|
if (contentElement && contentElement !== selectedElement.value) {
|
||||||
|
contentElement.classList.add('element-hovered');
|
||||||
|
createElementLabel(contentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoveredElement.value = contentElement;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -262,6 +256,7 @@ export function useIframeInteractions({ elementPopup, pagePopup }) {
|
||||||
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
@import '../public/assets/css/style.scss';
|
/* @import '../public/assets/css/style.scss'; */
|
||||||
|
|
|
||||||
29
src/utils/unit-conversion.js
Normal file
29
src/utils/unit-conversion.js
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue