geoproject-app/src/components/editor/TextSettings.vue

333 lines
9.9 KiB
Vue
Raw Normal View History

<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>
<!-- Police -->
<div class="settings-subsection">
<div class="field">
<label for="text-font">Police</label>
<div class="field-with-option">
<select id="text-font" v-model="font">
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</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>
<!-- Graisse -->
<div class="settings-subsection">
<div class="field">
<label>Graisse</label>
<UnitToggle v-model="weight" :units="weights" />
</div>
</div>
<!-- Taille du texte -->
<div class="settings-subsection">
<div class="field">
<label for="text-size-range">Taille du texte</label>
<InputWithUnit
v-model="fontSize"
:units="['px', 'em', 'rem']"
:min="8"
:max="72"
showRange
/>
</div>
</div>
<!-- Alignement -->
<div class="settings-subsection">
<div class="field">
<label for="text-alignment">Alignement</label>
<select id="text-alignment" v-model="alignment">
<option v-for="a in alignments" :key="a.value" :value="a.value">
{{ a.label }}
</option>
</select>
</div>
</div>
<!-- Couleurs -->
<div class="settings-subsection">
<div class="field">
<label for="text-color">Couleur</label>
<div class="input-with-color">
<input
id="text-color"
type="text"
v-model="color"
class="color-input"
data-coloris
/>
</div>
</div>
<div class="field">
<label for="text-background">Arrière-plan</label>
<div class="input-with-color">
<input
id="text-background"
type="text"
v-model="background"
class="color-input"
data-coloris
/>
</div>
</div>
</div>
<!-- Marges extérieures -->
<div class="settings-subsection">
<MarginEditor
ref="marginOuterEditor"
id="margin-outer"
label="Marges extérieures"
v-model:simple="marginOuter"
v-model:detailed="marginOuterDetailed"
:units="['mm', 'px', 'rem']"
@change="handleMarginOuterChange"
/>
</div>
<!-- Marges intérieures -->
<div class="settings-subsection">
<MarginEditor
ref="marginInnerEditor"
id="margin-inner"
label="Marges intérieures"
v-model:simple="marginInner"
v-model:detailed="marginInnerDetailed"
:units="['mm', 'px', 'rem']"
@change="handleMarginInnerChange"
/>
</div>
</section>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue';
import Coloris from '@melloware/coloris';
import UnitToggle from '../ui/UnitToggle.vue';
import InputWithUnit from '../ui/InputWithUnit.vue';
import MarginEditor from '../ui/MarginEditor.vue';
import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
// Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
const weights = ['200', '300', '400', '600', '800', 'normal', 'bold'];
const alignments = [
{ value: 'left', label: 'Gauche' },
{ value: 'center', label: 'Centre' },
{ value: 'right', label: 'Droite' },
{ value: 'justify', label: 'Justifié' }
];
// State
const font = ref('Alegreya Sans');
const italic = ref(false);
const weight = ref('400');
const fontSize = ref({ value: 16, unit: 'px' });
const alignment = ref('left');
const color = ref('rgb(0, 0, 0)');
const background = ref('transparent');
const marginOuter = ref({ value: 0, unit: 'mm' });
const marginOuterDetailed = ref({
top: { value: 0, unit: 'mm' },
right: { value: 0, unit: 'mm' },
bottom: { value: 24, unit: 'mm' },
left: { value: 0, unit: 'mm' }
});
const marginInner = ref({ value: 0, unit: 'mm' });
const marginInnerDetailed = ref({
top: { value: 0, unit: 'mm' },
right: { value: 0, unit: 'mm' },
bottom: { value: 0, unit: 'mm' },
left: { value: 0, unit: 'mm' }
});
const marginOuterEditor = ref(null);
const marginInnerEditor = ref(null);
let isUpdatingFromStore = false;
// Watchers for body styles
watch(italic, (val) => {
if (isUpdatingFromStore) return;
updateStyle('body', 'font-style', val ? 'italic' : 'normal');
});
watch(alignment, (val) => {
if (isUpdatingFromStore) return;
updateStyle('body', 'text-align', val);
});
watch(color, (val) => {
if (isUpdatingFromStore) return;
updateStyle('body', 'color', val);
});
watch(background, (val) => {
if (isUpdatingFromStore) return;
updateStyle('body', 'background', val);
});
// Watchers for paragraph styles
watch(weight, (val) => {
if (isUpdatingFromStore) return;
updateStyle('p', 'font-weight', val);
});
watch(fontSize, (val) => {
if (isUpdatingFromStore) return;
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
}, { deep: true });
// Margin/Padding handlers
const handleMarginOuterChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
};
const handleMarginInnerChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
};
// Sync from store
const syncFromStore = () => {
isUpdatingFromStore = true;
// Body styles
const fontStyle = extractValue('body', 'font-style');
if (fontStyle) italic.value = fontStyle === 'italic';
const textAlign = extractValue('body', 'text-align');
if (textAlign) alignment.value = textAlign;
const colorVal = extractValue('body', 'color');
if (colorVal) color.value = colorVal;
const bgVal = extractValue('body', 'background');
if (bgVal) background.value = bgVal;
// Paragraph styles
const fontWeight = extractValue('p', 'font-weight');
if (fontWeight) weight.value = fontWeight;
const fontSizeVal = extractNumericValue('p', 'font-size', ['px', 'em', 'rem']);
if (fontSizeVal) fontSize.value = fontSizeVal;
// Margins
const margins = extractSpacing('p', 'margin');
if (margins) {
if (margins.simple) {
marginOuter.value = margins.simple;
// Sync detailed from simple
marginOuterDetailed.value = {
top: { ...margins.simple },
right: { ...margins.simple },
bottom: { ...margins.simple },
left: { ...margins.simple }
};
} else if (margins.detailed) {
marginOuterDetailed.value = margins.detailed;
// Check if all values are the same to set simple value
const allSame =
margins.detailed.top.value === margins.detailed.right.value &&
margins.detailed.top.value === margins.detailed.bottom.value &&
margins.detailed.top.value === margins.detailed.left.value &&
margins.detailed.top.unit === margins.detailed.right.unit &&
margins.detailed.top.unit === margins.detailed.bottom.unit &&
margins.detailed.top.unit === margins.detailed.left.unit;
if (allSame) {
marginOuter.value = margins.detailed.top;
} else {
// Values are different, open the detailed editor and use first value for simple
marginOuter.value = margins.detailed.top;
// Open detailed view after mount
setTimeout(() => {
if (marginOuterEditor.value) {
marginOuterEditor.value.expanded = true;
}
}, 0);
}
}
}
// Padding
const padding = extractSpacing('p', 'padding');
if (padding) {
if (padding.simple) {
marginInner.value = padding.simple;
// Sync detailed from simple
marginInnerDetailed.value = {
top: { ...padding.simple },
right: { ...padding.simple },
bottom: { ...padding.simple },
left: { ...padding.simple }
};
} else if (padding.detailed) {
marginInnerDetailed.value = padding.detailed;
// Check if all values are the same to set simple value
const allSame =
padding.detailed.top.value === padding.detailed.right.value &&
padding.detailed.top.value === padding.detailed.bottom.value &&
padding.detailed.top.value === padding.detailed.left.value &&
padding.detailed.top.unit === padding.detailed.right.unit &&
padding.detailed.top.unit === padding.detailed.bottom.unit &&
padding.detailed.top.unit === padding.detailed.left.unit;
if (allSame) {
marginInner.value = padding.detailed.top;
} else {
// Values are different, open the detailed editor and use first value for simple
marginInner.value = padding.detailed.top;
// Open detailed view after mount
setTimeout(() => {
if (marginInnerEditor.value) {
marginInnerEditor.value.expanded = true;
}
}, 0);
}
}
}
isUpdatingFromStore = false;
};
onMounted(() => {
Coloris.init();
Coloris({
themeMode: 'dark',
alpha: true,
format: 'auto',
formatToggle: true,
swatches: ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', 'transparent']
});
syncFromStore();
});
</script>