feat: implement reactive EditorPanel with bidirectional sync
- Reorganize editor components into dedicated folder - Create PageSettings component with page format, margins, background controls - Create TextSettings component (structure only, to be populated) - Implement debounced updates (1s delay) to stylesheet store - Add bidirectional sync between EditorPanel and StylesheetViewer - Preserve scroll position as percentage when reloading preview - Move @page rules from App.vue to stylesheet.css for unified management - Extend css-parsing utils to handle text values (e.g., 'A4', 'portrait') - Remove unnecessary comments, use explicit naming instead 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b8cb77c0e5
commit
9f10971041
7 changed files with 1104 additions and 166 deletions
604
src/components/editor/TextSettings.vue
Normal file
604
src/components/editor/TextSettings.vue
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
<template>
|
||||
<section class="settings-section">
|
||||
<h2>Réglage du texte</h2>
|
||||
<p class="infos">
|
||||
Ces réglages s'appliquent à l'ensemble des éléments du document.
|
||||
Vous pouvez modifier ensuite les éléments indépendamment.
|
||||
</p>
|
||||
|
||||
<div class="field">
|
||||
<label for="text-font">Police</label>
|
||||
<div class="field-with-option">
|
||||
<select id="text-font" v-model="font">
|
||||
<option value="Alegreya Sans">Alegreya Sans</option>
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Helvetica">Helvetica</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
</select>
|
||||
<div class="field-checkbox">
|
||||
<input
|
||||
id="text-italic"
|
||||
type="checkbox"
|
||||
v-model="italic"
|
||||
/>
|
||||
<label for="text-italic">Italique</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Graisse</label>
|
||||
<div class="weight-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === '200' }"
|
||||
@click="weight = '200'"
|
||||
>
|
||||
200
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === '300' }"
|
||||
@click="weight = '300'"
|
||||
>
|
||||
300
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === '400' }"
|
||||
@click="weight = '400'"
|
||||
>
|
||||
400
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === '600' }"
|
||||
@click="weight = '600'"
|
||||
>
|
||||
600
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === '800' }"
|
||||
@click="weight = '800'"
|
||||
>
|
||||
800
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === 'normal' }"
|
||||
@click="weight = 'normal'"
|
||||
>
|
||||
normal
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: weight === 'bold' }"
|
||||
@click="weight = 'bold'"
|
||||
>
|
||||
bold
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="text-size-range">Taille du texte</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="text-size-range"
|
||||
type="range"
|
||||
v-model.number="fontSize.value"
|
||||
min="8"
|
||||
max="72"
|
||||
step="1"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="fontSize.value"
|
||||
min="8"
|
||||
max="72"
|
||||
step="1"
|
||||
class="size-input"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: fontSize.unit === 'px' }"
|
||||
@click="fontSize.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: fontSize.unit === 'em' }"
|
||||
@click="fontSize.unit = 'em'"
|
||||
>
|
||||
em
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: fontSize.unit === 'rem' }"
|
||||
@click="fontSize.unit = 'rem'"
|
||||
>
|
||||
rem
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="text-alignment">Alignement</label>
|
||||
<select id="text-alignment" v-model="alignment">
|
||||
<option value="left">Gauche</option>
|
||||
<option value="center">Centre</option>
|
||||
<option value="right">Droite</option>
|
||||
<option value="justify">Justifié</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="text-color">Couleur</label>
|
||||
<div class="field-with-color">
|
||||
<input
|
||||
type="color"
|
||||
v-model="color.picker"
|
||||
class="color-picker"
|
||||
/>
|
||||
<input
|
||||
id="text-color"
|
||||
type="text"
|
||||
v-model="color.value"
|
||||
class="color-input"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: color.format === 'rgb' }"
|
||||
@click="color.format = 'rgb'"
|
||||
>
|
||||
rgb
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: color.format === 'hex' }"
|
||||
@click="color.format = 'hex'"
|
||||
>
|
||||
hex
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="clear-btn"
|
||||
@click="clearColor"
|
||||
title="Réinitialiser"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="text-background">Arrière-plan</label>
|
||||
<div class="field-with-color">
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-btn"
|
||||
:class="{ active: background.enabled }"
|
||||
@click="background.enabled = !background.enabled"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<input
|
||||
id="text-background"
|
||||
type="text"
|
||||
v-model="background.value"
|
||||
:disabled="!background.enabled"
|
||||
class="color-input"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'rgb' }"
|
||||
:disabled="!background.enabled"
|
||||
@click="background.format = 'rgb'"
|
||||
>
|
||||
rgb
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: background.format === 'hex' }"
|
||||
:disabled="!background.enabled"
|
||||
@click="background.format = 'hex'"
|
||||
>
|
||||
hex
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="clear-btn"
|
||||
:disabled="!background.enabled"
|
||||
@click="clearBackground"
|
||||
title="Effacer"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-outer">Marges extérieures</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-outer"
|
||||
type="number"
|
||||
v-model.number="marginOuter.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuter.unit === 'mm' }"
|
||||
@click="marginOuter.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuter.unit === 'px' }"
|
||||
@click="marginOuter.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="collapse-toggle"
|
||||
:class="{ expanded: marginOuterExpanded }"
|
||||
@click="marginOuterExpanded = !marginOuterExpanded"
|
||||
title="Réglages détaillés"
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="marginOuterExpanded" class="subsection collapsed-section">
|
||||
<div class="field">
|
||||
<label for="margin-outer-top">Haut</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-outer-top"
|
||||
type="number"
|
||||
v-model.number="marginOuterDetailed.top.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.top.unit === 'mm' }"
|
||||
@click="marginOuterDetailed.top.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.top.unit === 'px' }"
|
||||
@click="marginOuterDetailed.top.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-outer-bottom">Bas</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-outer-bottom"
|
||||
type="number"
|
||||
v-model.number="marginOuterDetailed.bottom.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.bottom.unit === 'mm' }"
|
||||
@click="marginOuterDetailed.bottom.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.bottom.unit === 'px' }"
|
||||
@click="marginOuterDetailed.bottom.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-outer-left">Gauche</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-outer-left"
|
||||
type="number"
|
||||
v-model.number="marginOuterDetailed.left.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.left.unit === 'mm' }"
|
||||
@click="marginOuterDetailed.left.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.left.unit === 'px' }"
|
||||
@click="marginOuterDetailed.left.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-outer-right">Droite</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-outer-right"
|
||||
type="number"
|
||||
v-model.number="marginOuterDetailed.right.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.right.unit === 'mm' }"
|
||||
@click="marginOuterDetailed.right.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginOuterDetailed.right.unit === 'px' }"
|
||||
@click="marginOuterDetailed.right.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-inner">Marges intérieures</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-inner"
|
||||
type="number"
|
||||
v-model.number="marginInner.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInner.unit === 'mm' }"
|
||||
@click="marginInner.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInner.unit === 'px' }"
|
||||
@click="marginInner.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="collapse-toggle"
|
||||
:class="{ expanded: marginInnerExpanded }"
|
||||
@click="marginInnerExpanded = !marginInnerExpanded"
|
||||
title="Réglages détaillés"
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="marginInnerExpanded" class="subsection collapsed-section">
|
||||
<div class="field">
|
||||
<label for="margin-inner-top">Haut</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-inner-top"
|
||||
type="number"
|
||||
v-model.number="marginInnerDetailed.top.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.top.unit === 'mm' }"
|
||||
@click="marginInnerDetailed.top.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.top.unit === 'px' }"
|
||||
@click="marginInnerDetailed.top.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-inner-bottom">Bas</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-inner-bottom"
|
||||
type="number"
|
||||
v-model.number="marginInnerDetailed.bottom.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.bottom.unit === 'mm' }"
|
||||
@click="marginInnerDetailed.bottom.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.bottom.unit === 'px' }"
|
||||
@click="marginInnerDetailed.bottom.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-inner-left">Gauche</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-inner-left"
|
||||
type="number"
|
||||
v-model.number="marginInnerDetailed.left.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.left.unit === 'mm' }"
|
||||
@click="marginInnerDetailed.left.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.left.unit === 'px' }"
|
||||
@click="marginInnerDetailed.left.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="margin-inner-right">Droite</label>
|
||||
<div class="field-with-unit">
|
||||
<input
|
||||
id="margin-inner-right"
|
||||
type="number"
|
||||
v-model.number="marginInnerDetailed.right.value"
|
||||
min="0"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.right.unit === 'mm' }"
|
||||
@click="marginInnerDetailed.right.unit = 'mm'"
|
||||
>
|
||||
mm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: marginInnerDetailed.right.unit === 'px' }"
|
||||
@click="marginInnerDetailed.right.unit = 'px'"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// Font
|
||||
const font = ref('Alegreya Sans');
|
||||
const italic = ref(false);
|
||||
|
||||
// Weight
|
||||
const weight = ref('400');
|
||||
|
||||
// Font size
|
||||
const fontSize = ref({
|
||||
value: 23,
|
||||
unit: 'px'
|
||||
});
|
||||
|
||||
// Alignment
|
||||
const alignment = ref('left');
|
||||
|
||||
// Color
|
||||
const color = ref({
|
||||
picker: '#000000',
|
||||
value: 'rgb(250, 250, 250)',
|
||||
format: 'rgb'
|
||||
});
|
||||
|
||||
const clearColor = () => {
|
||||
color.value.picker = '#000000';
|
||||
color.value.value = '';
|
||||
};
|
||||
|
||||
// Background
|
||||
const background = ref({
|
||||
enabled: false,
|
||||
value: 'transparent',
|
||||
format: 'hex'
|
||||
});
|
||||
|
||||
const clearBackground = () => {
|
||||
background.value.value = 'transparent';
|
||||
};
|
||||
|
||||
// Margin outer
|
||||
const marginOuter = ref({
|
||||
value: 23,
|
||||
unit: 'mm'
|
||||
});
|
||||
|
||||
const marginOuterExpanded = ref(false);
|
||||
|
||||
const marginOuterDetailed = ref({
|
||||
top: { value: 23, unit: 'mm' },
|
||||
bottom: { value: 23, unit: 'mm' },
|
||||
left: { value: 23, unit: 'mm' },
|
||||
right: { value: 23, unit: 'mm' }
|
||||
});
|
||||
|
||||
// Margin inner
|
||||
const marginInner = ref({
|
||||
value: 23,
|
||||
unit: 'mm'
|
||||
});
|
||||
|
||||
const marginInnerExpanded = ref(false);
|
||||
|
||||
const marginInnerDetailed = ref({
|
||||
top: { value: 23, unit: 'mm' },
|
||||
bottom: { value: 23, unit: 'mm' },
|
||||
left: { value: 23, unit: 'mm' },
|
||||
right: { value: 23, unit: 'mm' }
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue