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

808 lines
20 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>
<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="input-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="input-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="input-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="input-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="input-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="input-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="input-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="input-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="input-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="input-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="input-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, watch } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
const stylesheetStore = useStylesheetStore();
let isUpdatingFromStore = false;
let updateTimer = null;
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
// 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' },
});
// Watchers - Immediate updates for select/buttons/checkboxes
watch(font, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement font update
});
});
watch(italic, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement italic update
});
});
watch(weight, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement weight update
});
});
watch(alignment, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement alignment update
});
});
// Font size - debounced for value, immediate for unit
watch(
() => fontSize.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement font size update
});
}
);
watch(
() => fontSize.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement font size update
});
}
);
// Color - debounced for text value, immediate for format and picker
watch(
() => color.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement color update
});
}
);
watch(
() => [color.value.format, color.value.picker],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement color update
});
}
);
// Background - debounced for value, immediate for format and enabled
watch(
() => background.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement background update
});
}
);
watch(
() => [background.value.format, background.value.enabled],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement background update
});
}
);
// Margin outer - debounced for value, immediate for unit
watch(
() => marginOuter.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin outer update
});
}
);
watch(
() => marginOuter.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin outer update
});
}
);
// Margin outer detailed - debounced for values, immediate for units
watch(
() => [
marginOuterDetailed.value.top.value,
marginOuterDetailed.value.bottom.value,
marginOuterDetailed.value.left.value,
marginOuterDetailed.value.right.value,
],
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin outer detailed update
});
}
);
watch(
() => [
marginOuterDetailed.value.top.unit,
marginOuterDetailed.value.bottom.unit,
marginOuterDetailed.value.left.unit,
marginOuterDetailed.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin outer detailed update
});
}
);
// Margin inner - debounced for value, immediate for unit
watch(
() => marginInner.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin inner update
});
}
);
watch(
() => marginInner.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin inner update
});
}
);
// Margin inner detailed - debounced for values, immediate for units
watch(
() => [
marginInnerDetailed.value.top.value,
marginInnerDetailed.value.bottom.value,
marginInnerDetailed.value.left.value,
marginInnerDetailed.value.right.value,
],
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin inner detailed update
});
}
);
watch(
() => [
marginInnerDetailed.value.top.unit,
marginInnerDetailed.value.bottom.unit,
marginInnerDetailed.value.left.unit,
marginInnerDetailed.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin inner detailed update
});
}
);
</script>