implement font-family module

This commit is contained in:
Julie Blanc 2026-03-05 16:29:42 +01:00
parent 6f5efb6fbc
commit cb9fd93e51
137 changed files with 1177 additions and 21 deletions

View file

@ -23,7 +23,8 @@
<label class="label-with-tooltip" data-css="font-family">Police</label>
<div class="field-font__options">
<select v-model="fontFamily">
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</option>
<option value="sans-serif">Police système (sans-serif)</option>
<option v-for="f in projectFonts" :key="f.name" :value="f.name" :style="{ fontFamily: `'${f.name}', ${f.category}` }">{{ f.name }}</option>
</select>
<div class="field-checkbox">
<input type="checkbox" v-model="italic" />
@ -264,11 +265,12 @@
</template>
<script setup>
import { ref, reactive, computed, watch, nextTick } from 'vue';
import { ref, reactive, computed, watch, nextTick, onMounted } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { useDebounce } from '../composables/useDebounce';
import { useCssSync } from '../composables/useCssSync';
import { useTextDefaults } from '../composables/useTextDefaults';
import { useProjectFonts } from '../composables/useProjectFonts';
import NumberInput from './ui/NumberInput.vue';
import InputWithUnit from './ui/InputWithUnit.vue';
import BasePopup from './ui/BasePopup.vue';
@ -277,6 +279,7 @@ import { convertUnit } from '../utils/unit-conversion';
const stylesheetStore = useStylesheetStore();
const { extractValue: cssExtractValue, extractNumericValue } = useCssSync();
const textDefaults = useTextDefaults();
const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts();
const props = defineProps({
iframeRef: Object,
@ -299,7 +302,7 @@ let isUpdatingFromStore = false;
const { debouncedUpdate } = useDebounce(500);
// Style properties flat refs for simple values, reactive for value+unit
const fontFamily = ref('Alegreya Sans');
const fontFamily = ref('sans-serif');
const italic = ref(false);
const bold = ref(false);
const textAlign = ref('left');
@ -357,12 +360,9 @@ const settingEnabled = reactive({
padding: false,
});
// Constants
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
// Style property descriptors (with group field)
const styleProps = [
{ css: 'font-family', group: 'font', get: () => fontFamily.value, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false },
{ css: 'font-family', group: 'font', get: () => fontFamily.value === 'sans-serif' ? 'sans-serif' : `"${fontFamily.value}"`, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false },
{ css: 'font-style', group: 'font', get: () => italic.value ? 'italic' : 'normal', set: v => italic.value = v === 'italic', debounce: false, skipWhenDefault: v => v !== 'italic' },
{ css: 'font-weight', group: 'font', get: () => bold.value ? 'bold' : 'normal', set: v => bold.value = v === 'bold' || parseInt(v) >= 700, debounce: false, skipWhenDefault: v => v === 'normal' },
{ css: 'text-align', group: 'textAlign', get: () => textAlign.value, set: v => textAlign.value = v, debounce: false },
@ -555,7 +555,7 @@ const displayedCss = computed(() => {
{ group: 'fontSize', css: 'font-size', getValue: () => `${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).value}${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).unit}` },
{ group: 'lineHeight', css: 'line-height', getValue: () => `${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).value}${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).unit}` },
{ group: 'color', css: 'color', getValue: () => settingEnabled.color ? color.value : textDefaults.color },
{ group: 'font', css: 'font-family', getValue: () => `"${settingEnabled.font ? fontFamily.value : textDefaults.fontFamily}"` },
{ group: 'font', css: 'font-family', getValue: () => { const val = settingEnabled.font ? fontFamily.value : textDefaults.fontFamily; return val === 'sans-serif' ? 'sans-serif' : `"${val}"`; } },
];
// For disabled special groups: annotate existing lines with comment
@ -662,7 +662,8 @@ const applyDefaultsForGroup = (group) => {
} else if (group === 'color') {
updateProp('color', textDefaults.color);
} else if (group === 'font') {
updateProp('font-family', `"${textDefaults.fontFamily}"`);
const fontVal = textDefaults.fontFamily === 'sans-serif' ? 'sans-serif' : `"${textDefaults.fontFamily}"`;
updateProp('font-family', fontVal);
removeProps(['font-style', 'font-weight']);
}
};
@ -686,6 +687,11 @@ const onToggleSetting = (group, enabled) => {
nextTick(() => { isUpdatingFromStore = false; });
};
// Load font when fontFamily changes
watch(fontFamily, async (val) => {
if (val && val !== 'sans-serif') await loadFont(val);
});
// Watchers simple props (with group guard)
for (const prop of styleProps) {
watch(prop.get, () => {
@ -967,6 +973,10 @@ const handleIframeClick = (event, targetElement = null, elementCount = null) =>
open(element, event, elementCount);
};
onMounted(() => {
loadAllFontPreviews();
});
defineExpose({ handleIframeClick, close, visible });
</script>

View file

@ -11,10 +11,11 @@
<!-- Police -->
<div class="settings-subsection">
<div class="field field-font">
<label for="text-font" class="label-with-tooltip field--view-only" data-css="font-family" title="Fonctionnalité à venir">Police</label>
<label for="text-font" class="label-with-tooltip" data-css="font-family">Police</label>
<div class="field-font__options">
<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>
<select id="text-font" v-model="font">
<option value="sans-serif">Police système (sans-serif)</option>
<option v-for="f in projectFonts" :key="f.name" :value="f.name" :style="{ fontFamily: `'${f.name}', ${f.category}` }">{{ f.name }}</option>
</select>
</div>
</div>
@ -78,17 +79,16 @@ import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce';
import { useTextDefaults } from '../../composables/useTextDefaults';
import { useProjectFonts } from '../../composables/useProjectFonts';
const { updateStyle } = useCssUpdater();
const { extractValue, extractNumericValue } = useCssSync();
const { debouncedUpdate } = useDebounce(500);
const textDefaults = useTextDefaults();
// Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts();
// State
const font = ref('Alegreya Sans');
const font = ref('sans-serif');
const fontSize = ref({ value: 16, unit: 'px' });
const lineHeight = ref({ value: 20, unit: 'px' });
const color = ref('rgb(0, 0, 0)');
@ -97,10 +97,12 @@ const colorInput = ref(null);
let isUpdatingFromStore = false;
// Watchers for body styles
watch(font, (val) => {
watch(font, async (val) => {
textDefaults.fontFamily = val;
if (val !== 'sans-serif') await loadFont(val);
if (isUpdatingFromStore) return;
updateStyle('body', 'font-family', `"${val}"`);
const cssValue = val === 'sans-serif' ? 'sans-serif' : `"${val}"`;
updateStyle('body', 'font-family', cssValue);
}, { immediate: true });
watch(color, (val) => {
@ -130,6 +132,12 @@ watch(lineHeight, (val) => {
const syncFromStore = () => {
isUpdatingFromStore = true;
const fontVal = extractValue('body', 'font-family');
if (fontVal) {
const cleaned = fontVal.replace(/['"]/g, '').trim();
font.value = cleaned || 'sans-serif';
}
const colorVal = extractValue('body', 'color');
if (colorVal) color.value = colorVal;
@ -155,6 +163,7 @@ onMounted(() => {
});
syncFromStore();
setTimeout(updateColorisButtons, 100);
loadAllFontPreviews();
});
</script>