feat: add custom page format with editable width/height fields
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 20s
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 20s
Keep only A4/A5 + "Personnalisé" option. Width/height fields use same layout as margin fields and are editable only in custom mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b808e22274
commit
9f62d3ae5d
1 changed files with 305 additions and 235 deletions
|
|
@ -1,231 +1,258 @@
|
|||
<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>
|
||||
<div class="container">
|
||||
|
||||
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple">
|
||||
<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>
|
||||
<option value="A5">A5</option>
|
||||
<option value="A3">A3</option>
|
||||
<div class="container">
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple">
|
||||
<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>
|
||||
<option value="A5">A5</option>
|
||||
<!-- <option value="A3">A3</option>
|
||||
<option value="letter">Letter</option>
|
||||
<option value="legal">Legal</option>
|
||||
</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>
|
||||
<option value="legal">Legal</option> -->
|
||||
<option value="custom">Personnalisé</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field field-size field--view-only">
|
||||
<label for="page-height" class="label-with-tooltip" data-css="height"
|
||||
>Hauteur</label
|
||||
<div class="settings-subsection">
|
||||
<div
|
||||
class="field field-margin"
|
||||
:class="{ 'field--view-only': !isCustomFormat }"
|
||||
>
|
||||
<input
|
||||
id="page-height"
|
||||
type="number"
|
||||
:value="parseInt(pageHeight)"
|
||||
disabled
|
||||
/>
|
||||
<button type="button" disabled>mm</button>
|
||||
<label for="page-width" class="label-with-tooltip" data-css="width"
|
||||
>Largeur</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="page-width"
|
||||
: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 class="settings-subsection margins">
|
||||
<h3>Marges</h3>
|
||||
<div class="settings-subsection margins">
|
||||
<h3>Marges</h3>
|
||||
|
||||
<div class="field field-margin">
|
||||
<label for="margin-top" class="label-with-tooltip" data-css="margin-top"
|
||||
>Haut</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-top"
|
||||
:modelValue="margins.top.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => margins.top.value = value"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.top.unit === 'mm' }"
|
||||
@click="updateMarginUnit('top', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.top.unit === 'px' }"
|
||||
@click="updateMarginUnit('top', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-top"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-top"
|
||||
>Haut</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-top"
|
||||
:modelValue="margins.top.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => (margins.top.value = value)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.top.unit === 'mm' }"
|
||||
@click="updateMarginUnit('top', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.top.unit === 'px' }"
|
||||
@click="updateMarginUnit('top', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
:class="{ active: margins.top.unit === 'rem' }"
|
||||
@click="updateMarginUnit('top', 'rem')"
|
||||
>
|
||||
rem
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-bottom"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-bottom"
|
||||
>Bas</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-bottom"
|
||||
:modelValue="margins.bottom.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => margins.bottom.value = value"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.bottom.unit === 'mm' }"
|
||||
@click="updateMarginUnit('bottom', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.bottom.unit === 'px' }"
|
||||
@click="updateMarginUnit('bottom', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-bottom"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-bottom"
|
||||
>Bas</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-bottom"
|
||||
:modelValue="margins.bottom.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => (margins.bottom.value = value)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.bottom.unit === 'mm' }"
|
||||
@click="updateMarginUnit('bottom', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.bottom.unit === 'px' }"
|
||||
@click="updateMarginUnit('bottom', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
:class="{ active: margins.bottom.unit === 'rem' }"
|
||||
@click="updateMarginUnit('bottom', 'rem')"
|
||||
>
|
||||
rem
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-left"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-left"
|
||||
>Gauche</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-left"
|
||||
:modelValue="margins.left.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => margins.left.value = value"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.left.unit === 'mm' }"
|
||||
@click="updateMarginUnit('left', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.left.unit === 'px' }"
|
||||
@click="updateMarginUnit('left', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-left"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-left"
|
||||
>Gauche</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-left"
|
||||
:modelValue="margins.left.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => (margins.left.value = value)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.left.unit === 'mm' }"
|
||||
@click="updateMarginUnit('left', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.left.unit === 'px' }"
|
||||
@click="updateMarginUnit('left', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
:class="{ active: margins.left.unit === 'rem' }"
|
||||
@click="updateMarginUnit('left', 'rem')"
|
||||
>
|
||||
rem
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-right"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-right"
|
||||
>Droite</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-right"
|
||||
:modelValue="margins.right.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => margins.right.value = value"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.right.unit === 'mm' }"
|
||||
@click="updateMarginUnit('right', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.right.unit === 'px' }"
|
||||
@click="updateMarginUnit('right', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
<div class="field field-margin">
|
||||
<label
|
||||
for="margin-right"
|
||||
class="label-with-tooltip"
|
||||
data-css="margin-right"
|
||||
>Droite</label
|
||||
>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
id="margin-right"
|
||||
:modelValue="margins.right.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
@update:modelValue="(value) => (margins.right.value = value)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.right.unit === 'mm' }"
|
||||
@click="updateMarginUnit('right', 'mm')"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margins.right.unit === 'px' }"
|
||||
@click="updateMarginUnit('right', 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
:class="{ active: margins.right.unit === 'rem' }"
|
||||
@click="updateMarginUnit('right', 'rem')"
|
||||
>
|
||||
rem
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple">
|
||||
<label for="background" class="label-with-tooltip" data-css="background"
|
||||
>Arrière-plan</label
|
||||
>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="backgroundColorInput"
|
||||
type="text"
|
||||
id="background"
|
||||
v-model="background.value"
|
||||
data-coloris
|
||||
/>
|
||||
<!-- Temporarily commented out
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple">
|
||||
<label
|
||||
for="background"
|
||||
class="label-with-tooltip"
|
||||
data-css="background"
|
||||
>Arrière-plan</label
|
||||
>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="backgroundColorInput"
|
||||
type="text"
|
||||
id="background"
|
||||
v-model="background.value"
|
||||
data-coloris
|
||||
/>
|
||||
<!-- Temporarily commented out
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -242,50 +269,49 @@
|
|||
hex
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
@ -308,15 +334,16 @@ let isUpdatingFromStore = false;
|
|||
const pageFormat = ref('A4');
|
||||
|
||||
const pageFormats = {
|
||||
A4: { width: '210mm', height: '297mm' },
|
||||
A5: { width: '148mm', height: '210mm' },
|
||||
A3: { width: '297mm', height: '420mm' },
|
||||
letter: { width: '8.5in', height: '11in' },
|
||||
legal: { width: '8.5in', height: '14in' },
|
||||
A4: { width: 210, height: 297 },
|
||||
A5: { width: 148, height: 210 },
|
||||
// A3: { width: 297, height: 420 },
|
||||
// letter: { width: 216, height: 279 },
|
||||
// legal: { width: 216, height: 356 },
|
||||
};
|
||||
|
||||
const pageWidth = computed(() => pageFormats[pageFormat.value].width);
|
||||
const pageHeight = computed(() => pageFormats[pageFormat.value].height);
|
||||
const customWidth = ref(210);
|
||||
const customHeight = ref(297);
|
||||
const isCustomFormat = computed(() => pageFormat.value === 'custom');
|
||||
|
||||
const margins = ref({
|
||||
top: { value: 20, unit: 'mm' },
|
||||
|
|
@ -347,8 +374,36 @@ const immediateUpdate = (callback) => {
|
|||
watch(pageFormat, (newFormat) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
|
||||
immediateUpdate(() => {
|
||||
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
|
||||
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(() => {
|
||||
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch([customWidth, customHeight], () => {
|
||||
if (isUpdatingFromStore || !isCustomFormat.value) return;
|
||||
debouncedUpdate(() => {
|
||||
stylesheetStore.updateProperty(
|
||||
'@page',
|
||||
'size',
|
||||
`${customWidth.value}mm ${customHeight.value}mm`,
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -546,9 +601,24 @@ const syncFromStore = () => {
|
|||
try {
|
||||
const pageBlock = stylesheetStore.extractBlock('@page');
|
||||
|
||||
const sizeMatch = pageBlock.match(/size:\s*([A-Za-z0-9]+)/);
|
||||
if (sizeMatch) {
|
||||
pageFormat.value = sizeMatch[1];
|
||||
// Try named format first (A4, A5), then custom dimensions
|
||||
const namedSizeMatch = pageBlock.match(/size:\s*(A4|A5)\b/);
|
||||
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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue