position relative to images
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 22s
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 22s
This commit is contained in:
parent
a85a810b1e
commit
bf877c23d2
2 changed files with 522 additions and 358 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
:display-css="displayedCss"
|
:display-css="displayedCss"
|
||||||
:editable-css="editableCss"
|
:editable-css="editableCss"
|
||||||
:popup-width="440"
|
:popup-width="440"
|
||||||
:popup-height="320"
|
:popup-height="550"
|
||||||
:show-inheritance="false"
|
:show-inheritance="false"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
@css-input="handleCssInput"
|
@css-input="handleCssInput"
|
||||||
|
|
@ -18,35 +18,19 @@
|
||||||
<template #controls>
|
<template #controls>
|
||||||
|
|
||||||
<!-- Largeur -->
|
<!-- Largeur -->
|
||||||
<div
|
<div class="setting__section" data-setting="width" :class="{ 'setting-disabled': !widthEnabled }" @click="onWidthSectionClick">
|
||||||
class="setting__section"
|
|
||||||
data-setting="width"
|
|
||||||
:class="{ 'setting-disabled': !widthEnabled }"
|
|
||||||
@click="onWidthSectionClick"
|
|
||||||
>
|
|
||||||
<div class="setting__header">
|
<div class="setting__header">
|
||||||
<input type="checkbox" id="carte-toggle-width" class="toggle-setting" :checked="widthEnabled" @change="onToggleWidth($event.target.checked)" /><label for="carte-toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
<input type="checkbox" id="carte-toggle-width" class="toggle-setting" :checked="widthEnabled" @change="onToggleWidth($event.target.checked)" /><label for="carte-toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
||||||
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting__body">
|
<div class="setting__body">
|
||||||
<InputWithUnit
|
<InputWithUnit v-model="widthModel" :units="['%', 'px']" :min="1" :max="width.unit === 'px' ? 2000 : 200" showRange />
|
||||||
v-model="widthModel"
|
|
||||||
:units="['%', 'px']"
|
|
||||||
:min="1"
|
|
||||||
:max="width.unit === 'px' ? 2000 : 200"
|
|
||||||
showRange
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="info-default">Valeur par défaut : {{ carteDefaults.width.value }}{{ carteDefaults.width.unit }}</p>
|
<p class="info-default">Valeur par défaut : {{ carteDefaults.width.value }}{{ carteDefaults.width.unit }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hauteur -->
|
<!-- Hauteur -->
|
||||||
<div
|
<div class="setting__section" data-setting="height" :class="{ 'setting-disabled': !heightEnabled }" @click="onHeightSectionClick">
|
||||||
class="setting__section"
|
|
||||||
data-setting="height"
|
|
||||||
:class="{ 'setting-disabled': !heightEnabled }"
|
|
||||||
@click="onHeightSectionClick"
|
|
||||||
>
|
|
||||||
<div class="setting__header">
|
<div class="setting__header">
|
||||||
<input type="checkbox" id="carte-toggle-height" class="toggle-setting" :checked="heightEnabled" @change="onToggleHeight($event.target.checked)" /><label for="carte-toggle-height" aria-label="Activer le réglage de hauteur" @click.stop></label>
|
<input type="checkbox" id="carte-toggle-height" class="toggle-setting" :checked="heightEnabled" @change="onToggleHeight($event.target.checked)" /><label for="carte-toggle-height" aria-label="Activer le réglage de hauteur" @click.stop></label>
|
||||||
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
||||||
|
|
@ -56,19 +40,46 @@
|
||||||
<input type="checkbox" id="carte-height-auto" :checked="heightAuto" @change="onToggleHeightAuto(!heightAuto)" />
|
<input type="checkbox" id="carte-height-auto" :checked="heightAuto" @change="onToggleHeightAuto(!heightAuto)" />
|
||||||
<label for="carte-height-auto">Auto</label>
|
<label for="carte-height-auto">Auto</label>
|
||||||
</div>
|
</div>
|
||||||
<InputWithUnit
|
<InputWithUnit v-if="!heightAuto" :model-value="{ value: heightPx.value, unit: heightPx.unit }" @update:model-value="onHeightValueChange" :units="['px']" :min="1" :max="800" showRange />
|
||||||
v-if="!heightAuto"
|
|
||||||
:model-value="{ value: heightPx.value, unit: heightPx.unit }"
|
|
||||||
@update:model-value="onHeightValueChange"
|
|
||||||
:units="['px']"
|
|
||||||
:min="1"
|
|
||||||
:max="800"
|
|
||||||
showRange
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="info-default">Valeur par défaut : {{ carteDefaults.height.auto ? 'auto' : carteDefaults.height.value + carteDefaults.height.unit }}</p>
|
<p class="info-default">Valeur par défaut : {{ carteDefaults.height.auto ? 'auto' : carteDefaults.height.value + carteDefaults.height.unit }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Marges extérieures -->
|
||||||
|
<div class="setting__section" data-setting="margin" :class="{ 'setting-disabled': !marginEnabled }" @click="onMarginSectionClick">
|
||||||
|
<div class="setting__header">
|
||||||
|
<input type="checkbox" id="carte-toggle-margin" class="toggle-setting" :checked="marginEnabled" @change="onToggleMargin($event.target.checked)" /><label for="carte-toggle-margin" aria-label="Activer les marges" @click.stop></label>
|
||||||
|
<label class="label-with-tooltip" data-css="margin">Marges extérieures</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div v-for="side in marginSides" :key="side.key" class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" :data-css="`margin-${side.key}`">{{ side.label }}</label>
|
||||||
|
<div class="input-with-unit">
|
||||||
|
<NumberInput :modelValue="margin[side.key]" :min="0" :max="500" :step="1" @update:modelValue="onMarginChange(side.key, $event)" />
|
||||||
|
<div class="unit-toggle"><button type="button" class="active">px</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Position relative -->
|
||||||
|
<div class="setting__section" data-setting="position" :class="{ 'setting-disabled': !positionEnabled }" @click="onPositionSectionClick">
|
||||||
|
<div class="setting__header">
|
||||||
|
<input type="checkbox" id="carte-toggle-position" class="toggle-setting" :checked="positionEnabled" @change="onTogglePosition($event.target.checked)" /><label for="carte-toggle-position" aria-label="Activer la position relative" @click.stop></label>
|
||||||
|
<label class="label-with-tooltip" data-css="position">Position relative</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" data-css="top">Haut</label>
|
||||||
|
<InputWithUnit v-model="posTopModel" :units="['px']" :min="-500" :max="500" showRange />
|
||||||
|
</div>
|
||||||
|
<div class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" data-css="left">Gauche</label>
|
||||||
|
<InputWithUnit v-model="posLeftModel" :units="['px']" :min="-500" :max="500" showRange />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</BasePopup>
|
</BasePopup>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -76,6 +87,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
||||||
import InputWithUnit from './ui/InputWithUnit.vue';
|
import InputWithUnit from './ui/InputWithUnit.vue';
|
||||||
|
import NumberInput from './ui/NumberInput.vue';
|
||||||
import BasePopup from './ui/BasePopup.vue';
|
import BasePopup from './ui/BasePopup.vue';
|
||||||
import { useStylesheetStore } from '../stores/stylesheet';
|
import { useStylesheetStore } from '../stores/stylesheet';
|
||||||
import { useCarteDefaults } from '../composables/useCarteDefaults';
|
import { useCarteDefaults } from '../composables/useCarteDefaults';
|
||||||
|
|
@ -93,21 +105,35 @@ const { debouncedUpdate } = useDebounce(400);
|
||||||
const basePopup = ref(null);
|
const basePopup = ref(null);
|
||||||
const visible = computed(() => basePopup.value?.visible ?? false);
|
const visible = computed(() => basePopup.value?.visible ?? false);
|
||||||
const currentFigure = ref(null);
|
const currentFigure = ref(null);
|
||||||
|
|
||||||
const selector = ref('');
|
const selector = ref('');
|
||||||
|
|
||||||
// --- Width state ---
|
// --- Width ---
|
||||||
const width = reactive({ value: 100, unit: '%' });
|
const width = reactive({ value: 100, unit: '%' });
|
||||||
const widthEnabled = ref(false);
|
const widthEnabled = ref(false);
|
||||||
const widthCache = ref(null);
|
const widthCache = ref(null);
|
||||||
|
|
||||||
// --- Height state ---
|
// --- Height ---
|
||||||
const heightAuto = ref(true);
|
const heightAuto = ref(true);
|
||||||
const heightPx = reactive({ value: CARTE_DEFAULTS.height.value, unit: CARTE_DEFAULTS.height.unit });
|
const heightPx = reactive({ value: CARTE_DEFAULTS.height.value, unit: CARTE_DEFAULTS.height.unit });
|
||||||
const heightEnabled = ref(false);
|
const heightEnabled = ref(false);
|
||||||
const heightCache = ref(null);
|
const heightCache = ref(null);
|
||||||
const measuredHeight = ref(null);
|
const measuredHeight = ref(null);
|
||||||
|
|
||||||
|
// --- Margin ---
|
||||||
|
const margin = reactive({ top: 0, right: 0, bottom: 0, left: 0 });
|
||||||
|
const marginEnabled = ref(false);
|
||||||
|
const marginCache = ref(null);
|
||||||
|
const marginSides = [
|
||||||
|
{ key: 'top', label: 'Haut' }, { key: 'right', label: 'Droite' },
|
||||||
|
{ key: 'bottom', label: 'Bas' }, { key: 'left', label: 'Gauche' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Position ---
|
||||||
|
const posTop = ref(0);
|
||||||
|
const posLeft = ref(0);
|
||||||
|
const positionEnabled = ref(false);
|
||||||
|
const positionCache = ref(null);
|
||||||
|
|
||||||
const elementStates = new Map();
|
const elementStates = new Map();
|
||||||
let isUpdatingFromStore = false;
|
let isUpdatingFromStore = false;
|
||||||
|
|
||||||
|
|
@ -120,94 +146,29 @@ const widthModel = computed({
|
||||||
// --- Watchers ---
|
// --- Watchers ---
|
||||||
watch(() => [width.value, width.unit], () => {
|
watch(() => [width.value, width.unit], () => {
|
||||||
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
||||||
debouncedUpdate(() => {
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit); saveState(); });
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
|
||||||
saveState();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => carteDefaults.width, (val) => {
|
watch(() => carteDefaults.width, (val) => {
|
||||||
if (!widthEnabled.value && widthCache.value === null) {
|
if (!widthEnabled.value && widthCache.value === null) {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true; width.value = val.value; width.unit = val.unit;
|
||||||
width.value = val.value; width.unit = val.unit;
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
watch(() => carteDefaults.height, (val) => {
|
watch(() => carteDefaults.height, (val) => {
|
||||||
if (!heightEnabled.value && heightCache.value === null) {
|
if (!heightEnabled.value && heightCache.value === null) {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true; heightAuto.value = val.auto; heightPx.value = val.value; heightPx.unit = val.unit;
|
||||||
heightAuto.value = val.auto; heightPx.value = val.value; heightPx.unit = val.unit;
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
// --- Width toggle ---
|
// --- Height handlers ---
|
||||||
const onWidthSectionClick = () => { if (!widthEnabled.value) onToggleWidth(true); };
|
|
||||||
|
|
||||||
const onToggleWidth = (enabled) => {
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
widthEnabled.value = enabled;
|
|
||||||
if (enabled) {
|
|
||||||
if (widthCache.value) { width.value = widthCache.value.value; width.unit = widthCache.value.unit; widthCache.value = null; }
|
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
|
||||||
} else {
|
|
||||||
widthCache.value = { value: width.value, unit: width.unit };
|
|
||||||
removeProp('width');
|
|
||||||
width.value = carteDefaults.width.value; width.unit = carteDefaults.width.unit;
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Height toggle ---
|
|
||||||
const onHeightSectionClick = () => { if (!heightEnabled.value) onToggleHeight(true); };
|
|
||||||
|
|
||||||
const onToggleHeight = (enabled) => {
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
heightEnabled.value = enabled;
|
|
||||||
if (enabled) {
|
|
||||||
if (heightCache.value) {
|
|
||||||
heightAuto.value = heightCache.value.auto;
|
|
||||||
heightPx.value = heightCache.value.value; heightPx.unit = heightCache.value.unit;
|
|
||||||
heightCache.value = null;
|
|
||||||
}
|
|
||||||
applyHeight();
|
|
||||||
if (!heightAuto.value) updateStyle(`${selector.value} img`, 'height', '100%');
|
|
||||||
} else {
|
|
||||||
heightCache.value = { auto: heightAuto.value, value: heightPx.value, unit: heightPx.unit };
|
|
||||||
removeProp('height');
|
|
||||||
removeProperty(`${selector.value} img`, 'height');
|
|
||||||
heightAuto.value = carteDefaults.height.auto;
|
|
||||||
heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit;
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggleHeightAuto = (auto) => {
|
|
||||||
if (!heightEnabled.value) return;
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
if (!auto && heightAuto.value) {
|
|
||||||
if (measuredHeight.value === null) measuredHeight.value = getActualImageHeight();
|
|
||||||
heightPx.value = measuredHeight.value; heightPx.unit = 'px';
|
|
||||||
}
|
|
||||||
heightAuto.value = auto;
|
|
||||||
applyHeight();
|
|
||||||
if (!auto) { updateStyle(`${selector.value} img`, 'height', '100%'); }
|
|
||||||
else { removeProperty(`${selector.value} img`, 'height'); }
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onHeightValueChange = (v) => {
|
const onHeightValueChange = (v) => {
|
||||||
heightPx.value = v.value; heightPx.unit = v.unit;
|
heightPx.value = v.value; heightPx.unit = v.unit;
|
||||||
if (isUpdatingFromStore || !heightEnabled.value || heightAuto.value || !selector.value) return;
|
if (isUpdatingFromStore || !heightEnabled.value || heightAuto.value || !selector.value) return;
|
||||||
measuredHeight.value = v.value;
|
measuredHeight.value = v.value;
|
||||||
debouncedUpdate(() => {
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit); saveState(); });
|
||||||
stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit);
|
|
||||||
saveState();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActualImageHeight = () => {
|
const getActualImageHeight = () => {
|
||||||
|
|
@ -220,13 +181,119 @@ const getActualImageHeight = () => {
|
||||||
return CARTE_DEFAULTS.height.value;
|
return CARTE_DEFAULTS.height.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Toggle Width ---
|
||||||
|
const onWidthSectionClick = () => { if (!widthEnabled.value) onToggleWidth(true); };
|
||||||
|
const onToggleWidth = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; widthEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (widthCache.value) { width.value = widthCache.value.value; width.unit = widthCache.value.unit; widthCache.value = null; }
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
||||||
|
} else {
|
||||||
|
widthCache.value = { value: width.value, unit: width.unit };
|
||||||
|
removeProp('width'); width.value = carteDefaults.width.value; width.unit = carteDefaults.width.unit;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Toggle Height ---
|
||||||
|
const onHeightSectionClick = () => { if (!heightEnabled.value) onToggleHeight(true); };
|
||||||
|
const onToggleHeight = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; heightEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (heightCache.value) { heightAuto.value = heightCache.value.auto; heightPx.value = heightCache.value.value; heightPx.unit = heightCache.value.unit; heightCache.value = null; }
|
||||||
|
applyHeight();
|
||||||
|
if (!heightAuto.value) updateStyle(`${selector.value} img`, 'height', '100%');
|
||||||
|
} else {
|
||||||
|
heightCache.value = { auto: heightAuto.value, value: heightPx.value, unit: heightPx.unit };
|
||||||
|
removeProp('height'); removeProperty(`${selector.value} img`, 'height');
|
||||||
|
heightAuto.value = carteDefaults.height.auto; heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
const onToggleHeightAuto = (auto) => {
|
||||||
|
if (!heightEnabled.value) return;
|
||||||
|
isUpdatingFromStore = true;
|
||||||
|
if (!auto && heightAuto.value) { if (measuredHeight.value === null) measuredHeight.value = getActualImageHeight(); heightPx.value = measuredHeight.value; heightPx.unit = 'px'; }
|
||||||
|
heightAuto.value = auto; applyHeight();
|
||||||
|
if (!auto) { updateStyle(`${selector.value} img`, 'height', '100%'); } else { removeProperty(`${selector.value} img`, 'height'); }
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Toggle Margin ---
|
||||||
|
const onMarginSectionClick = () => { if (!marginEnabled.value) onToggleMargin(true); };
|
||||||
|
const onToggleMargin = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; marginEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (marginCache.value) { Object.assign(margin, marginCache.value); marginCache.value = null; }
|
||||||
|
applyMargin();
|
||||||
|
} else {
|
||||||
|
marginCache.value = { ...margin };
|
||||||
|
removeMarginProps();
|
||||||
|
margin.top = 0; margin.right = 0; margin.bottom = 0; margin.left = 0;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
const onMarginChange = (side, val) => {
|
||||||
|
margin[side] = val;
|
||||||
|
if (isUpdatingFromStore || !marginEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, `margin-${side}`, val, 'px'); saveState(); });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Position models ---
|
||||||
|
const posTopModel = computed({
|
||||||
|
get: () => ({ value: posTop.value, unit: 'px' }),
|
||||||
|
set: (v) => { posTop.value = v.value; },
|
||||||
|
});
|
||||||
|
const posLeftModel = computed({
|
||||||
|
get: () => ({ value: posLeft.value, unit: 'px' }),
|
||||||
|
set: (v) => { posLeft.value = v.value; },
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(posTop, (val) => {
|
||||||
|
if (isUpdatingFromStore || !positionEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'top', val, 'px'); saveState(); });
|
||||||
|
});
|
||||||
|
watch(posLeft, (val) => {
|
||||||
|
if (isUpdatingFromStore || !positionEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'left', val, 'px'); saveState(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Toggle Position ---
|
||||||
|
const onPositionSectionClick = () => { if (!positionEnabled.value) onTogglePosition(true); };
|
||||||
|
const onTogglePosition = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; positionEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (positionCache.value) { posTop.value = positionCache.value.top; posLeft.value = positionCache.value.left; positionCache.value = null; }
|
||||||
|
applyPosition();
|
||||||
|
} else {
|
||||||
|
positionCache.value = { top: posTop.value, left: posLeft.value };
|
||||||
|
removeProp('position'); removeProp('top'); removeProp('left');
|
||||||
|
posTop.value = 0; posLeft.value = 0;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
// --- CSS helpers ---
|
// --- CSS helpers ---
|
||||||
const applyHeight = () => {
|
const applyHeight = () => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
if (heightAuto.value) { stylesheetStore.updateProperty(selector.value, 'height', 'auto'); }
|
if (heightAuto.value) { stylesheetStore.updateProperty(selector.value, 'height', 'auto'); }
|
||||||
else { stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit); }
|
else { stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit); }
|
||||||
};
|
};
|
||||||
|
const applyMargin = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
|
stylesheetStore.updateProperty(selector.value, `margin-${side}`, margin[side], 'px');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const applyPosition = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'position', 'relative');
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'top', posTop.value, 'px');
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'left', posLeft.value, 'px');
|
||||||
|
};
|
||||||
|
const removeMarginProps = () => {
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) removeProp(`margin-${side}`);
|
||||||
|
};
|
||||||
const removeProp = (prop) => {
|
const removeProp = (prop) => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
const block = stylesheetStore.extractBlock(selector.value);
|
const block = stylesheetStore.extractBlock(selector.value);
|
||||||
|
|
@ -234,22 +301,34 @@ const removeProp = (prop) => {
|
||||||
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
const newBlock = block.replace(new RegExp(`[ \\t]*${escaped}\\s*:[^;]*;[ \\t]*\\n?`, 'g'), '');
|
const newBlock = block.replace(new RegExp(`[ \\t]*${escaped}\\s*:[^;]*;[ \\t]*\\n?`, 'g'), '');
|
||||||
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
||||||
if (!inner.trim()) { stylesheetStore.replaceInCustomCss(block, ''); }
|
if (!inner.trim()) { stylesheetStore.replaceInCustomCss(block, ''); } else { stylesheetStore.replaceBlock(block, newBlock); }
|
||||||
else { stylesheetStore.replaceBlock(block, newBlock); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Displayed CSS ---
|
// --- Displayed CSS ---
|
||||||
const displayedCss = computed(() => {
|
const displayedCss = computed(() => {
|
||||||
if (!selector.value) return '';
|
if (!selector.value) return '';
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
const widthVal = `${width.value}${width.unit}`;
|
const widthVal = `${width.value}${width.unit}`;
|
||||||
const defaultWidthVal = `${carteDefaults.width.value}${carteDefaults.width.unit}`;
|
const defaultWidthVal = `${carteDefaults.width.value}${carteDefaults.width.unit}`;
|
||||||
const wComment = (!widthEnabled.value && widthVal === defaultWidthVal) ? ' /* hérité de .block-carte */' : '';
|
const wComment = (!widthEnabled.value && widthVal === defaultWidthVal) ? ' /* hérité de .block-carte */' : '';
|
||||||
|
lines.push(` width: ${widthVal};${wComment}`);
|
||||||
|
|
||||||
const heightVal = heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`;
|
const heightVal = heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`;
|
||||||
const defaultHeightVal = carteDefaults.height.auto ? 'auto' : `${carteDefaults.height.value}${carteDefaults.height.unit}`;
|
const defaultHeightVal = carteDefaults.height.auto ? 'auto' : `${carteDefaults.height.value}${carteDefaults.height.unit}`;
|
||||||
const hComment = (!heightEnabled.value && heightVal === defaultHeightVal) ? ' /* hérité de .block-carte */' : '';
|
const hComment = (!heightEnabled.value && heightVal === defaultHeightVal) ? ' /* hérité de .block-carte */' : '';
|
||||||
|
lines.push(` height: ${heightVal};${hComment}`);
|
||||||
|
|
||||||
return `${selector.value} {\n width: ${widthVal};${wComment}\n height: ${heightVal};${hComment}\n}`;
|
if (marginEnabled.value) {
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) lines.push(` margin-${side}: ${margin[side]}px;`);
|
||||||
|
}
|
||||||
|
if (positionEnabled.value) {
|
||||||
|
lines.push(` position: relative;`);
|
||||||
|
lines.push(` top: ${posTop.value}px;`);
|
||||||
|
lines.push(` left: ${posLeft.value}px;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${selector.value} {\n${lines.join('\n')}\n}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const editableCss = computed(() => {
|
const editableCss = computed(() => {
|
||||||
|
|
@ -259,18 +338,38 @@ const editableCss = computed(() => {
|
||||||
// --- CSS input ---
|
// --- CSS input ---
|
||||||
const handleCssInput = (newCss) => {
|
const handleCssInput = (newCss) => {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
|
|
||||||
const wm = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
const wm = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
||||||
if (wm) { width.value = parseFloat(wm[1]); width.unit = wm[2]; }
|
if (wm) { width.value = parseFloat(wm[1]); width.unit = wm[2]; }
|
||||||
|
|
||||||
const hm = newCss.match(/height\s*:\s*([^;]+)/i);
|
const hm = newCss.match(/height\s*:\s*([^;]+)/i);
|
||||||
if (hm) {
|
if (hm) {
|
||||||
const hVal = hm[1].trim();
|
const hVal = hm[1].trim();
|
||||||
if (hVal === 'auto') { heightAuto.value = true; }
|
if (hVal === 'auto') { heightAuto.value = true; }
|
||||||
else { const m = hVal.match(/([\d.]+)px/i); if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; } }
|
else { const m = hVal.match(/([\d.]+)px/i); if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; } }
|
||||||
}
|
}
|
||||||
const newBlock = `${selector.value} {\n width: ${width.value}${width.unit};\n height: ${heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`};\n}\n`;
|
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
|
const mm = newCss.match(new RegExp(`margin-${side}\\s*:\\s*([\\d.]+)px`, 'i'));
|
||||||
|
if (mm) margin[side] = parseFloat(mm[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const posCss = newCss.match(/(?:^|\n)\s*position\s*:\s*([^;]+)/i);
|
||||||
|
if (posCss) positionEnabled.value = posCss[1].trim() === 'relative';
|
||||||
|
const tm = newCss.match(/(?:^|\n)\s*top\s*:\s*([-\d.]+)px/i);
|
||||||
|
if (tm) posTop.value = parseFloat(tm[1]);
|
||||||
|
const bm = newCss.match(/(?:^|\n)\s*left\s*:\s*([-\d.]+)px/i);
|
||||||
|
if (bm) posLeft.value = parseFloat(bm[1]);
|
||||||
|
|
||||||
|
const lines = [` width: ${width.value}${width.unit};`, ` height: ${heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`};`];
|
||||||
|
if (marginEnabled.value) for (const s of ['top','right','bottom','left']) lines.push(` margin-${s}: ${margin[s]}px;`);
|
||||||
|
if (positionEnabled.value) { lines.push(` position: relative;`); lines.push(` top: ${posTop.value}px;`); lines.push(` left: ${posLeft.value}px;`); }
|
||||||
|
|
||||||
|
const newBlock = `${selector.value} {\n${lines.join('\n')}\n}\n`;
|
||||||
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
||||||
if (oldBlock) { stylesheetStore.replaceInCustomCss(oldBlock, newBlock); }
|
if (oldBlock) { stylesheetStore.replaceInCustomCss(oldBlock, newBlock); }
|
||||||
else { stylesheetStore.setCustomCss((stylesheetStore.customCss || '') + '\n' + newBlock); }
|
else { stylesheetStore.setCustomCss((stylesheetStore.customCss || '') + '\n' + newBlock); }
|
||||||
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -282,6 +381,10 @@ const saveState = () => {
|
||||||
widthValue: { value: width.value, unit: width.unit },
|
widthValue: { value: width.value, unit: width.unit },
|
||||||
heightToggle: heightEnabled.value, heightCache: heightCache.value ? { ...heightCache.value } : null,
|
heightToggle: heightEnabled.value, heightCache: heightCache.value ? { ...heightCache.value } : null,
|
||||||
heightAuto: heightAuto.value, heightValue: { value: heightPx.value, unit: heightPx.unit },
|
heightAuto: heightAuto.value, heightValue: { value: heightPx.value, unit: heightPx.unit },
|
||||||
|
marginToggle: marginEnabled.value, marginCache: marginCache.value ? { ...marginCache.value } : null,
|
||||||
|
marginValue: { ...margin },
|
||||||
|
positionToggle: positionEnabled.value, positionCache: positionCache.value ? { ...positionCache.value } : null,
|
||||||
|
posTop: posTop.value, posLeft: posLeft.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -289,7 +392,6 @@ const saveState = () => {
|
||||||
const handleCarteClick = (figure, event) => {
|
const handleCarteClick = (figure, event) => {
|
||||||
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-carte--'));
|
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-carte--'));
|
||||||
const sel = uniqueClass ? `.block-carte.${uniqueClass}` : '.block-carte';
|
const sel = uniqueClass ? `.block-carte.${uniqueClass}` : '.block-carte';
|
||||||
if (!sel) return;
|
|
||||||
|
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
selector.value = sel;
|
selector.value = sel;
|
||||||
|
|
@ -307,26 +409,48 @@ const handleCarteClick = (figure, event) => {
|
||||||
heightAuto.value = stored.heightAuto;
|
heightAuto.value = stored.heightAuto;
|
||||||
heightPx.value = stored.heightToggle ? stored.heightValue.value : (stored.heightCache ? stored.heightCache.value : carteDefaults.height.value);
|
heightPx.value = stored.heightToggle ? stored.heightValue.value : (stored.heightCache ? stored.heightCache.value : carteDefaults.height.value);
|
||||||
heightPx.unit = stored.heightToggle ? stored.heightValue.unit : (stored.heightCache ? stored.heightCache.unit : carteDefaults.height.unit);
|
heightPx.unit = stored.heightToggle ? stored.heightValue.unit : (stored.heightCache ? stored.heightCache.unit : carteDefaults.height.unit);
|
||||||
|
marginEnabled.value = stored.marginToggle || false;
|
||||||
|
marginCache.value = stored.marginCache ? { ...stored.marginCache } : null;
|
||||||
|
Object.assign(margin, stored.marginToggle ? stored.marginValue : (stored.marginCache || { top: 0, right: 0, bottom: 0, left: 0 }));
|
||||||
|
positionEnabled.value = stored.positionToggle || false;
|
||||||
|
positionCache.value = stored.positionCache ? { ...stored.positionCache } : null;
|
||||||
|
posTop.value = stored.positionToggle ? stored.posTop : (stored.positionCache ? stored.positionCache.top : 0);
|
||||||
|
posLeft.value = stored.positionToggle ? stored.posLeft : (stored.positionCache ? stored.positionCache.left : 0);
|
||||||
} else {
|
} else {
|
||||||
widthEnabled.value = false; widthCache.value = null;
|
widthEnabled.value = false; widthCache.value = null;
|
||||||
const wCss = stylesheetStore.extractValue(sel, 'width');
|
const wCss = stylesheetStore.extractValue(sel, 'width');
|
||||||
if (wCss && wCss.value !== undefined) {
|
if (wCss && wCss.value !== undefined) {
|
||||||
width.value = wCss.value; width.unit = wCss.unit;
|
width.value = wCss.value; width.unit = wCss.unit;
|
||||||
const isDefaultWidth = wCss.value === carteDefaults.width.value && wCss.unit === carteDefaults.width.unit;
|
if (!(wCss.value === carteDefaults.width.value && wCss.unit === carteDefaults.width.unit)) widthEnabled.value = true;
|
||||||
if (!isDefaultWidth) widthEnabled.value = true;
|
|
||||||
} else { width.value = carteDefaults.width.value; width.unit = carteDefaults.width.unit; }
|
} else { width.value = carteDefaults.width.value; width.unit = carteDefaults.width.unit; }
|
||||||
|
|
||||||
heightEnabled.value = false; heightCache.value = null;
|
heightEnabled.value = false; heightCache.value = null;
|
||||||
const hCss = stylesheetStore.extractValue(sel, 'height');
|
const hCss = stylesheetStore.extractValue(sel, 'height');
|
||||||
const hStr = typeof hCss === 'string' ? hCss : null;
|
const hStr = typeof hCss === 'string' ? hCss : null;
|
||||||
if (hStr === 'auto') { heightAuto.value = true; }
|
if (hStr === 'auto') { heightAuto.value = true; }
|
||||||
else if (hStr) {
|
else if (hStr) { const m = hStr.match(/([\d.]+)px/i); if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; heightEnabled.value = true; } else { heightAuto.value = carteDefaults.height.auto; heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit; } }
|
||||||
const m = hStr.match(/([\d.]+)px/i);
|
else if (hCss && hCss.value !== undefined) { heightAuto.value = false; heightPx.value = hCss.value; heightPx.unit = hCss.unit; heightEnabled.value = true; }
|
||||||
if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; heightEnabled.value = true; }
|
else { heightAuto.value = carteDefaults.height.auto; heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit; }
|
||||||
else { heightAuto.value = carteDefaults.height.auto; heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit; }
|
|
||||||
} else if (hCss && hCss.value !== undefined) {
|
marginEnabled.value = false; marginCache.value = null;
|
||||||
heightAuto.value = false; heightPx.value = hCss.value; heightPx.unit = hCss.unit; heightEnabled.value = true;
|
margin.top = 0; margin.right = 0; margin.bottom = 0; margin.left = 0;
|
||||||
} else { heightAuto.value = carteDefaults.height.auto; heightPx.value = carteDefaults.height.value; heightPx.unit = carteDefaults.height.unit; }
|
let hasMargin = false;
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
|
const mCss = stylesheetStore.extractValue(sel, `margin-${side}`);
|
||||||
|
if (mCss) { const v = mCss.value !== undefined ? mCss.value : parseFloat(mCss); if (!isNaN(v) && v !== 0) { margin[side] = v; hasMargin = true; } }
|
||||||
|
}
|
||||||
|
if (hasMargin) marginEnabled.value = true;
|
||||||
|
|
||||||
|
positionEnabled.value = false; positionCache.value = null; posTop.value = 0; posLeft.value = 0;
|
||||||
|
const posCss = stylesheetStore.extractValue(sel, 'position');
|
||||||
|
const posStr = typeof posCss === 'string' ? posCss : posCss?.value;
|
||||||
|
if (posStr === 'relative') {
|
||||||
|
positionEnabled.value = true;
|
||||||
|
const tCss = stylesheetStore.extractValue(sel, 'top');
|
||||||
|
const bCss = stylesheetStore.extractValue(sel, 'left');
|
||||||
|
posTop.value = tCss ? (tCss.value !== undefined ? tCss.value : parseFloat(tCss)) || 0 : 0;
|
||||||
|
posLeft.value = bCss ? (bCss.value !== undefined ? bCss.value : parseFloat(bCss)) || 0 : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basePopup.value.open(event);
|
basePopup.value.open(event);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
:display-css="displayedCss"
|
:display-css="displayedCss"
|
||||||
:editable-css="editableCss"
|
:editable-css="editableCss"
|
||||||
:popup-width="440"
|
:popup-width="440"
|
||||||
:popup-height="320"
|
:popup-height="550"
|
||||||
:show-inheritance="false"
|
:show-inheritance="false"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
@css-input="handleCssInput"
|
@css-input="handleCssInput"
|
||||||
|
|
@ -18,62 +18,68 @@
|
||||||
<template #controls>
|
<template #controls>
|
||||||
|
|
||||||
<!-- Largeur -->
|
<!-- Largeur -->
|
||||||
<div
|
<div class="setting__section" data-setting="width" :class="{ 'setting-disabled': !widthEnabled }" @click="onWidthSectionClick">
|
||||||
class="setting__section"
|
|
||||||
data-setting="width"
|
|
||||||
:class="{ 'setting-disabled': !widthEnabled }"
|
|
||||||
@click="onWidthSectionClick"
|
|
||||||
>
|
|
||||||
<div class="setting__header">
|
<div class="setting__header">
|
||||||
<input type="checkbox" id="toggle-width" class="toggle-setting" :checked="widthEnabled" @change="onToggleWidth($event.target.checked)" /><label for="toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
<input type="checkbox" id="img-toggle-width" class="toggle-setting" :checked="widthEnabled" @change="onToggleWidth($event.target.checked)" /><label for="img-toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
||||||
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting__body">
|
<div class="setting__body">
|
||||||
<InputWithUnit
|
<InputWithUnit v-model="widthModel" :units="['%', 'px']" :min="1" :max="width.unit === 'px' ? 2000 : 200" showRange />
|
||||||
v-model="widthModel"
|
|
||||||
:units="['%', 'px']"
|
|
||||||
:min="1"
|
|
||||||
:max="width.unit === 'px' ? 2000 : 200"
|
|
||||||
showRange
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="info-default">Valeur par défaut : {{ imageDefaults.width.value }}{{ imageDefaults.width.unit }}</p>
|
<p class="info-default">Valeur par défaut : {{ imageDefaults.width.value }}{{ imageDefaults.width.unit }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hauteur -->
|
<!-- Hauteur -->
|
||||||
<div
|
<div class="setting__section" data-setting="height" :class="{ 'setting-disabled': !heightEnabled }" @click="onHeightSectionClick">
|
||||||
class="setting__section"
|
|
||||||
data-setting="height"
|
|
||||||
:class="{ 'setting-disabled': !heightEnabled }"
|
|
||||||
@click="onHeightSectionClick"
|
|
||||||
>
|
|
||||||
<div class="setting__header">
|
<div class="setting__header">
|
||||||
<input type="checkbox" id="toggle-height" class="toggle-setting" :checked="heightEnabled" @change="onToggleHeight($event.target.checked)" /><label for="toggle-height" aria-label="Activer le réglage de hauteur" @click.stop></label>
|
<input type="checkbox" id="img-toggle-height" class="toggle-setting" :checked="heightEnabled" @change="onToggleHeight($event.target.checked)" /><label for="img-toggle-height" aria-label="Activer le réglage de hauteur" @click.stop></label>
|
||||||
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting__body">
|
<div class="setting__body">
|
||||||
<div class="field-checkbox" @click.stop>
|
<div class="field-checkbox" @click.stop>
|
||||||
<input
|
<input type="checkbox" id="img-height-auto" :checked="heightAuto" @change="onToggleHeightAuto(!heightAuto)" />
|
||||||
type="checkbox"
|
<label for="img-height-auto">Auto</label>
|
||||||
id="height-auto"
|
|
||||||
:checked="heightAuto"
|
|
||||||
@change="onToggleHeightAuto(!heightAuto)"
|
|
||||||
/>
|
|
||||||
<label for="height-auto">Auto</label>
|
|
||||||
</div>
|
</div>
|
||||||
<InputWithUnit
|
<InputWithUnit v-if="!heightAuto" :model-value="{ value: heightPx.value, unit: heightPx.unit }" @update:model-value="onHeightValueChange" :units="['px']" :min="1" :max="800" showRange />
|
||||||
v-if="!heightAuto"
|
|
||||||
:model-value="{ value: heightPx.value, unit: heightPx.unit }"
|
|
||||||
@update:model-value="onHeightValueChange"
|
|
||||||
:units="['px']"
|
|
||||||
:min="1"
|
|
||||||
:max="800"
|
|
||||||
showRange
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="info-default">Valeur par défaut : {{ imageDefaults.height.auto ? 'auto' : imageDefaults.height.value + imageDefaults.height.unit }}</p>
|
<p class="info-default">Valeur par défaut : {{ imageDefaults.height.auto ? 'auto' : imageDefaults.height.value + imageDefaults.height.unit }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Marges extérieures -->
|
||||||
|
<div class="setting__section" data-setting="margin" :class="{ 'setting-disabled': !marginEnabled }" @click="onMarginSectionClick">
|
||||||
|
<div class="setting__header">
|
||||||
|
<input type="checkbox" id="img-toggle-margin" class="toggle-setting" :checked="marginEnabled" @change="onToggleMargin($event.target.checked)" /><label for="img-toggle-margin" aria-label="Activer les marges" @click.stop></label>
|
||||||
|
<label class="label-with-tooltip" data-css="margin">Marges extérieures</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div v-for="side in marginSides" :key="side.key" class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" :data-css="`margin-${side.key}`">{{ side.label }}</label>
|
||||||
|
<div class="input-with-unit">
|
||||||
|
<NumberInput :modelValue="margin[side.key]" :min="0" :max="500" :step="1" @update:modelValue="onMarginChange(side.key, $event)" />
|
||||||
|
<div class="unit-toggle"><button type="button" class="active">px</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Position relative -->
|
||||||
|
<div class="setting__section" data-setting="position" :class="{ 'setting-disabled': !positionEnabled }" @click="onPositionSectionClick">
|
||||||
|
<div class="setting__header">
|
||||||
|
<input type="checkbox" id="img-toggle-position" class="toggle-setting" :checked="positionEnabled" @change="onTogglePosition($event.target.checked)" /><label for="img-toggle-position" aria-label="Activer la position relative" @click.stop></label>
|
||||||
|
<label class="label-with-tooltip" data-css="position">Position relative</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" data-css="top">Haut</label>
|
||||||
|
<InputWithUnit v-model="posTopModel" :units="['px']" :min="-500" :max="500" showRange />
|
||||||
|
</div>
|
||||||
|
<div class="field field-margin">
|
||||||
|
<label class="label-with-tooltip" data-css="left">Gauche</label>
|
||||||
|
<InputWithUnit v-model="posLeftModel" :units="['px']" :min="-500" :max="500" showRange />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</BasePopup>
|
</BasePopup>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -81,6 +87,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
||||||
import InputWithUnit from './ui/InputWithUnit.vue';
|
import InputWithUnit from './ui/InputWithUnit.vue';
|
||||||
|
import NumberInput from './ui/NumberInput.vue';
|
||||||
import BasePopup from './ui/BasePopup.vue';
|
import BasePopup from './ui/BasePopup.vue';
|
||||||
import { useStylesheetStore } from '../stores/stylesheet';
|
import { useStylesheetStore } from '../stores/stylesheet';
|
||||||
import { useImageDefaults } from '../composables/useImageDefaults';
|
import { useImageDefaults } from '../composables/useImageDefaults';
|
||||||
|
|
@ -98,23 +105,36 @@ const { debouncedUpdate } = useDebounce(400);
|
||||||
const basePopup = ref(null);
|
const basePopup = ref(null);
|
||||||
const visible = computed(() => basePopup.value?.visible ?? false);
|
const visible = computed(() => basePopup.value?.visible ?? false);
|
||||||
const currentFigure = ref(null);
|
const currentFigure = ref(null);
|
||||||
|
|
||||||
const selector = ref('');
|
const selector = ref('');
|
||||||
|
|
||||||
// --- Width state ---
|
// --- Width ---
|
||||||
const width = reactive({ value: 100, unit: '%' });
|
const width = reactive({ value: 100, unit: '%' });
|
||||||
const widthEnabled = ref(false);
|
const widthEnabled = ref(false);
|
||||||
const widthCache = ref(null);
|
const widthCache = ref(null);
|
||||||
|
|
||||||
// --- Height state ---
|
// --- Height ---
|
||||||
const heightAuto = ref(true);
|
const heightAuto = ref(true);
|
||||||
const heightPx = reactive({ value: IMAGE_DEFAULTS.height.value, unit: IMAGE_DEFAULTS.height.unit });
|
const heightPx = reactive({ value: IMAGE_DEFAULTS.height.value, unit: IMAGE_DEFAULTS.height.unit });
|
||||||
const heightEnabled = ref(false);
|
const heightEnabled = ref(false);
|
||||||
const heightCache = ref(null); // { auto, value, unit } | null
|
const heightCache = ref(null);
|
||||||
const measuredHeight = ref(null); // Actual height captured on first uncheck, per open session
|
const measuredHeight = ref(null);
|
||||||
|
|
||||||
|
// --- Margin ---
|
||||||
|
const margin = reactive({ top: 0, right: 0, bottom: 0, left: 0 });
|
||||||
|
const marginEnabled = ref(false);
|
||||||
|
const marginCache = ref(null);
|
||||||
|
const marginSides = [
|
||||||
|
{ key: 'top', label: 'Haut' }, { key: 'right', label: 'Droite' },
|
||||||
|
{ key: 'bottom', label: 'Bas' }, { key: 'left', label: 'Gauche' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Position ---
|
||||||
|
const posTop = ref(0);
|
||||||
|
const posLeft = ref(0);
|
||||||
|
const positionEnabled = ref(false);
|
||||||
|
const positionCache = ref(null);
|
||||||
|
|
||||||
const elementStates = new Map();
|
const elementStates = new Map();
|
||||||
|
|
||||||
let isUpdatingFromStore = false;
|
let isUpdatingFromStore = false;
|
||||||
|
|
||||||
// --- Models ---
|
// --- Models ---
|
||||||
|
|
@ -123,152 +143,157 @@ const widthModel = computed({
|
||||||
set: (v) => { width.value = v.value; width.unit = v.unit; },
|
set: (v) => { width.value = v.value; width.unit = v.unit; },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// --- Watchers ---
|
// --- Watchers ---
|
||||||
|
|
||||||
watch(() => [width.value, width.unit], () => {
|
watch(() => [width.value, width.unit], () => {
|
||||||
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
||||||
debouncedUpdate(() => {
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit); saveState(); });
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
|
||||||
saveState();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const onHeightValueChange = (v) => {
|
|
||||||
heightPx.value = v.value;
|
|
||||||
heightPx.unit = v.unit;
|
|
||||||
if (isUpdatingFromStore || !heightEnabled.value || heightAuto.value || !selector.value) return;
|
|
||||||
measuredHeight.value = v.value;
|
|
||||||
debouncedUpdate(() => {
|
|
||||||
stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit);
|
|
||||||
saveState();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sync greyed width field with imageDefaults when toggle OFF and no cache
|
|
||||||
watch(() => imageDefaults.width, (val) => {
|
watch(() => imageDefaults.width, (val) => {
|
||||||
if (!widthEnabled.value && widthCache.value === null) {
|
if (!widthEnabled.value && widthCache.value === null) {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true; width.value = val.value; width.unit = val.unit;
|
||||||
width.value = val.value;
|
|
||||||
width.unit = val.unit;
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
// Sync greyed height field with imageDefaults when toggle OFF and no cache
|
|
||||||
watch(() => imageDefaults.height, (val) => {
|
watch(() => imageDefaults.height, (val) => {
|
||||||
if (!heightEnabled.value && heightCache.value === null) {
|
if (!heightEnabled.value && heightCache.value === null) {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true; heightAuto.value = val.auto; heightPx.value = val.value; heightPx.unit = val.unit;
|
||||||
heightAuto.value = val.auto;
|
|
||||||
heightPx.value = val.value;
|
|
||||||
heightPx.unit = val.unit;
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
// --- Width toggle ---
|
// --- Height handlers ---
|
||||||
|
const onHeightValueChange = (v) => {
|
||||||
const onWidthSectionClick = () => {
|
heightPx.value = v.value; heightPx.unit = v.unit;
|
||||||
if (!widthEnabled.value) onToggleWidth(true);
|
if (isUpdatingFromStore || !heightEnabled.value || heightAuto.value || !selector.value) return;
|
||||||
};
|
measuredHeight.value = v.value;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit); saveState(); });
|
||||||
const onToggleWidth = (enabled) => {
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
widthEnabled.value = enabled;
|
|
||||||
if (enabled) {
|
|
||||||
if (widthCache.value) {
|
|
||||||
width.value = widthCache.value.value;
|
|
||||||
width.unit = widthCache.value.unit;
|
|
||||||
widthCache.value = null;
|
|
||||||
}
|
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
|
||||||
} else {
|
|
||||||
widthCache.value = { value: width.value, unit: width.unit };
|
|
||||||
removeProp('width');
|
|
||||||
width.value = imageDefaults.width.value;
|
|
||||||
width.unit = imageDefaults.width.unit;
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Height toggle ---
|
|
||||||
|
|
||||||
const onHeightSectionClick = () => {
|
|
||||||
if (!heightEnabled.value) onToggleHeight(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggleHeight = (enabled) => {
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
heightEnabled.value = enabled;
|
|
||||||
if (enabled) {
|
|
||||||
if (heightCache.value) {
|
|
||||||
heightAuto.value = heightCache.value.auto;
|
|
||||||
heightPx.value = heightCache.value.value;
|
|
||||||
heightPx.unit = heightCache.value.unit;
|
|
||||||
heightCache.value = null;
|
|
||||||
}
|
|
||||||
applyHeight();
|
|
||||||
if (!heightAuto.value) updateStyle(`${selector.value} img`, 'height', '100%');
|
|
||||||
} else {
|
|
||||||
heightCache.value = { auto: heightAuto.value, value: heightPx.value, unit: heightPx.unit };
|
|
||||||
removeProp('height');
|
|
||||||
removeProperty(`${selector.value} img`, 'height');
|
|
||||||
heightAuto.value = imageDefaults.height.auto;
|
|
||||||
heightPx.value = imageDefaults.height.value;
|
|
||||||
heightPx.unit = imageDefaults.height.unit;
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auto checkbox (only active when heightEnabled)
|
|
||||||
const onToggleHeightAuto = (auto) => {
|
|
||||||
if (!heightEnabled.value) return;
|
|
||||||
isUpdatingFromStore = true;
|
|
||||||
if (!auto && heightAuto.value) {
|
|
||||||
// Unchecking auto → use measured height (capture once, reuse on subsequent unchecks)
|
|
||||||
if (measuredHeight.value === null) {
|
|
||||||
measuredHeight.value = getActualImageHeight();
|
|
||||||
}
|
|
||||||
heightPx.value = measuredHeight.value;
|
|
||||||
heightPx.unit = 'px';
|
|
||||||
}
|
|
||||||
heightAuto.value = auto;
|
|
||||||
applyHeight();
|
|
||||||
if (!auto) {
|
|
||||||
updateStyle(`${selector.value} img`, 'height', '100%');
|
|
||||||
} else {
|
|
||||||
removeProperty(`${selector.value} img`, 'height');
|
|
||||||
}
|
|
||||||
saveState();
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActualImageHeight = () => {
|
const getActualImageHeight = () => {
|
||||||
if (currentFigure.value) {
|
if (currentFigure.value) {
|
||||||
const img = currentFigure.value.querySelector('img');
|
const img = currentFigure.value.querySelector('img');
|
||||||
if (img) {
|
if (img) { const h = Math.round(img.getBoundingClientRect().height); if (h > 0) return Math.min(h, 800); }
|
||||||
const h = Math.round(img.getBoundingClientRect().height);
|
|
||||||
if (h > 0) return Math.min(h, 800);
|
|
||||||
}
|
|
||||||
const figH = Math.round(currentFigure.value.getBoundingClientRect().height);
|
const figH = Math.round(currentFigure.value.getBoundingClientRect().height);
|
||||||
if (figH > 0) return Math.min(figH, 800);
|
if (figH > 0) return Math.min(figH, 800);
|
||||||
}
|
}
|
||||||
return IMAGE_DEFAULTS.height.value;
|
return IMAGE_DEFAULTS.height.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- CSS helpers ---
|
// --- Toggle Width ---
|
||||||
|
const onWidthSectionClick = () => { if (!widthEnabled.value) onToggleWidth(true); };
|
||||||
const applyHeight = () => {
|
const onToggleWidth = (enabled) => {
|
||||||
if (!selector.value) return;
|
isUpdatingFromStore = true; widthEnabled.value = enabled;
|
||||||
if (heightAuto.value) {
|
if (enabled) {
|
||||||
stylesheetStore.updateProperty(selector.value, 'height', 'auto');
|
if (widthCache.value) { width.value = widthCache.value.value; width.unit = widthCache.value.unit; widthCache.value = null; }
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
||||||
} else {
|
} else {
|
||||||
stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit);
|
widthCache.value = { value: width.value, unit: width.unit };
|
||||||
|
removeProp('width'); width.value = imageDefaults.width.value; width.unit = imageDefaults.width.unit;
|
||||||
}
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Toggle Height ---
|
||||||
|
const onHeightSectionClick = () => { if (!heightEnabled.value) onToggleHeight(true); };
|
||||||
|
const onToggleHeight = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; heightEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (heightCache.value) { heightAuto.value = heightCache.value.auto; heightPx.value = heightCache.value.value; heightPx.unit = heightCache.value.unit; heightCache.value = null; }
|
||||||
|
applyHeight();
|
||||||
|
if (!heightAuto.value) updateStyle(`${selector.value} img`, 'height', '100%');
|
||||||
|
} else {
|
||||||
|
heightCache.value = { auto: heightAuto.value, value: heightPx.value, unit: heightPx.unit };
|
||||||
|
removeProp('height'); removeProperty(`${selector.value} img`, 'height');
|
||||||
|
heightAuto.value = imageDefaults.height.auto; heightPx.value = imageDefaults.height.value; heightPx.unit = imageDefaults.height.unit;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
const onToggleHeightAuto = (auto) => {
|
||||||
|
if (!heightEnabled.value) return;
|
||||||
|
isUpdatingFromStore = true;
|
||||||
|
if (!auto && heightAuto.value) { if (measuredHeight.value === null) measuredHeight.value = getActualImageHeight(); heightPx.value = measuredHeight.value; heightPx.unit = 'px'; }
|
||||||
|
heightAuto.value = auto; applyHeight();
|
||||||
|
if (!auto) { updateStyle(`${selector.value} img`, 'height', '100%'); } else { removeProperty(`${selector.value} img`, 'height'); }
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Toggle Margin ---
|
||||||
|
const onMarginSectionClick = () => { if (!marginEnabled.value) onToggleMargin(true); };
|
||||||
|
const onToggleMargin = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; marginEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (marginCache.value) { Object.assign(margin, marginCache.value); marginCache.value = null; }
|
||||||
|
applyMargin();
|
||||||
|
} else {
|
||||||
|
marginCache.value = { ...margin };
|
||||||
|
removeMarginProps();
|
||||||
|
margin.top = 0; margin.right = 0; margin.bottom = 0; margin.left = 0;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
const onMarginChange = (side, val) => {
|
||||||
|
margin[side] = val;
|
||||||
|
if (isUpdatingFromStore || !marginEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, `margin-${side}`, val, 'px'); saveState(); });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Position models ---
|
||||||
|
const posTopModel = computed({
|
||||||
|
get: () => ({ value: posTop.value, unit: 'px' }),
|
||||||
|
set: (v) => { posTop.value = v.value; },
|
||||||
|
});
|
||||||
|
const posLeftModel = computed({
|
||||||
|
get: () => ({ value: posLeft.value, unit: 'px' }),
|
||||||
|
set: (v) => { posLeft.value = v.value; },
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(posTop, (val) => {
|
||||||
|
if (isUpdatingFromStore || !positionEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'top', val, 'px'); saveState(); });
|
||||||
|
});
|
||||||
|
watch(posLeft, (val) => {
|
||||||
|
if (isUpdatingFromStore || !positionEnabled.value || !selector.value) return;
|
||||||
|
debouncedUpdate(() => { stylesheetStore.updateProperty(selector.value, 'left', val, 'px'); saveState(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Toggle Position ---
|
||||||
|
const onPositionSectionClick = () => { if (!positionEnabled.value) onTogglePosition(true); };
|
||||||
|
const onTogglePosition = (enabled) => {
|
||||||
|
isUpdatingFromStore = true; positionEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (positionCache.value) { posTop.value = positionCache.value.top; posLeft.value = positionCache.value.left; positionCache.value = null; }
|
||||||
|
applyPosition();
|
||||||
|
} else {
|
||||||
|
positionCache.value = { top: posTop.value, left: posLeft.value };
|
||||||
|
removeProp('position'); removeProp('top'); removeProp('left');
|
||||||
|
posTop.value = 0; posLeft.value = 0;
|
||||||
|
}
|
||||||
|
saveState(); nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- CSS helpers ---
|
||||||
|
const applyHeight = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
if (heightAuto.value) { stylesheetStore.updateProperty(selector.value, 'height', 'auto'); }
|
||||||
|
else { stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit); }
|
||||||
|
};
|
||||||
|
const applyMargin = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
|
stylesheetStore.updateProperty(selector.value, `margin-${side}`, margin[side], 'px');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const applyPosition = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'position', 'relative');
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'top', posTop.value, 'px');
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'left', posLeft.value, 'px');
|
||||||
|
};
|
||||||
|
const removeMarginProps = () => {
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) removeProp(`margin-${side}`);
|
||||||
|
};
|
||||||
const removeProp = (prop) => {
|
const removeProp = (prop) => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
const block = stylesheetStore.extractBlock(selector.value);
|
const block = stylesheetStore.extractBlock(selector.value);
|
||||||
|
|
@ -276,30 +301,32 @@ const removeProp = (prop) => {
|
||||||
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
const newBlock = block.replace(new RegExp(`[ \\t]*${escaped}\\s*:[^;]*;[ \\t]*\\n?`, 'g'), '');
|
const newBlock = block.replace(new RegExp(`[ \\t]*${escaped}\\s*:[^;]*;[ \\t]*\\n?`, 'g'), '');
|
||||||
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
||||||
if (!inner.trim()) {
|
if (!inner.trim()) { stylesheetStore.replaceInCustomCss(block, ''); } else { stylesheetStore.replaceBlock(block, newBlock); }
|
||||||
stylesheetStore.replaceInCustomCss(block, '');
|
|
||||||
} else {
|
|
||||||
stylesheetStore.replaceBlock(block, newBlock);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Displayed CSS ---
|
// --- Displayed CSS ---
|
||||||
|
|
||||||
const displayedCss = computed(() => {
|
const displayedCss = computed(() => {
|
||||||
if (!selector.value) return '';
|
if (!selector.value) return '';
|
||||||
const lines = [];
|
const lines = [];
|
||||||
|
|
||||||
// Width
|
|
||||||
const widthVal = `${width.value}${width.unit}`;
|
const widthVal = `${width.value}${width.unit}`;
|
||||||
const defaultWidthVal = `${imageDefaults.width.value}${imageDefaults.width.unit}`;
|
const defaultWidthVal = `${imageDefaults.width.value}${imageDefaults.width.unit}`;
|
||||||
const widthComment = (!widthEnabled.value && widthVal === defaultWidthVal) ? ' /* hérité de .block-image */' : '';
|
const wComment = (!widthEnabled.value && widthVal === defaultWidthVal) ? ' /* hérité de .block-image */' : '';
|
||||||
lines.push(` width: ${widthVal};${widthComment}`);
|
lines.push(` width: ${widthVal};${wComment}`);
|
||||||
|
|
||||||
// Height
|
|
||||||
const heightVal = heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`;
|
const heightVal = heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`;
|
||||||
const defaultHeightVal = imageDefaults.height.auto ? 'auto' : `${imageDefaults.height.value}${imageDefaults.height.unit}`;
|
const defaultHeightVal = imageDefaults.height.auto ? 'auto' : `${imageDefaults.height.value}${imageDefaults.height.unit}`;
|
||||||
const heightComment = (!heightEnabled.value && heightVal === defaultHeightVal) ? ' /* hérité de .block-image */' : '';
|
const hComment = (!heightEnabled.value && heightVal === defaultHeightVal) ? ' /* hérité de .block-image */' : '';
|
||||||
lines.push(` height: ${heightVal};${heightComment}`);
|
lines.push(` height: ${heightVal};${hComment}`);
|
||||||
|
|
||||||
|
if (marginEnabled.value) {
|
||||||
|
for (const side of ['top', 'right', 'bottom', 'left']) lines.push(` margin-${side}: ${margin[side]}px;`);
|
||||||
|
}
|
||||||
|
if (positionEnabled.value) {
|
||||||
|
lines.push(` position: relative;`);
|
||||||
|
lines.push(` top: ${posTop.value}px;`);
|
||||||
|
lines.push(` left: ${posLeft.value}px;`);
|
||||||
|
}
|
||||||
|
|
||||||
return `${selector.value} {\n${lines.join('\n')}\n}`;
|
return `${selector.value} {\n${lines.join('\n')}\n}`;
|
||||||
});
|
});
|
||||||
|
|
@ -308,62 +335,82 @@ const editableCss = computed(() => {
|
||||||
return displayedCss.value.replace(/ \/\* hérité de \.block-image \*\//g, '');
|
return displayedCss.value.replace(/ \/\* hérité de \.block-image \*\//g, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- CSS input (edit mode) ---
|
// --- CSS input ---
|
||||||
|
|
||||||
const handleCssInput = (newCss) => {
|
const handleCssInput = (newCss) => {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
|
|
||||||
const widthMatch = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
const wm = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
||||||
if (widthMatch) {
|
if (wm) { width.value = parseFloat(wm[1]); width.unit = wm[2]; }
|
||||||
width.value = parseFloat(widthMatch[1]);
|
|
||||||
width.unit = widthMatch[2];
|
const hm = newCss.match(/height\s*:\s*([^;]+)/i);
|
||||||
|
if (hm) {
|
||||||
|
const hVal = hm[1].trim();
|
||||||
|
if (hVal === 'auto') { heightAuto.value = true; }
|
||||||
|
else { const m = hVal.match(/([\d.]+)px/i); if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; } }
|
||||||
}
|
}
|
||||||
|
|
||||||
const heightMatch = newCss.match(/height\s*:\s*([^;]+)/i);
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
if (heightMatch) {
|
const mm = newCss.match(new RegExp(`margin-${side}\\s*:\\s*([\\d.]+)px`, 'i'));
|
||||||
const hVal = heightMatch[1].trim();
|
if (mm) margin[side] = parseFloat(mm[1]);
|
||||||
if (hVal === 'auto') {
|
|
||||||
heightAuto.value = true;
|
|
||||||
} else {
|
|
||||||
const m = hVal.match(/([\d.]+)px/i);
|
|
||||||
if (m) {
|
|
||||||
heightAuto.value = false;
|
|
||||||
heightPx.value = parseFloat(m[1]);
|
|
||||||
heightPx.unit = 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const widthLine = ` width: ${width.value}${width.unit};`;
|
const posCss = newCss.match(/(?:^|\n)\s*position\s*:\s*([^;]+)/i);
|
||||||
const heightLine = ` height: ${heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`};`;
|
if (posCss) positionEnabled.value = posCss[1].trim() === 'relative';
|
||||||
const newBlock = `${selector.value} {\n${widthLine}\n${heightLine}\n}\n`;
|
const tm = newCss.match(/(?:^|\n)\s*top\s*:\s*([-\d.]+)px/i);
|
||||||
|
if (tm) posTop.value = parseFloat(tm[1]);
|
||||||
|
const bm = newCss.match(/(?:^|\n)\s*left\s*:\s*([-\d.]+)px/i);
|
||||||
|
if (bm) posLeft.value = parseFloat(bm[1]);
|
||||||
|
|
||||||
|
const lines = [` width: ${width.value}${width.unit};`, ` height: ${heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`};`];
|
||||||
|
if (marginEnabled.value) for (const s of ['top','right','bottom','left']) lines.push(` margin-${s}: ${margin[s]}px;`);
|
||||||
|
if (positionEnabled.value) { lines.push(` position: relative;`); lines.push(` top: ${posTop.value}px;`); lines.push(` left: ${posLeft.value}px;`); }
|
||||||
|
|
||||||
|
const newBlock = `${selector.value} {\n${lines.join('\n')}\n}\n`;
|
||||||
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
||||||
if (oldBlock) {
|
if (oldBlock) { stylesheetStore.replaceInCustomCss(oldBlock, newBlock); }
|
||||||
stylesheetStore.replaceInCustomCss(oldBlock, newBlock);
|
else { stylesheetStore.setCustomCss((stylesheetStore.customCss || '') + '\n' + newBlock); }
|
||||||
} else {
|
|
||||||
stylesheetStore.setCustomCss((stylesheetStore.customCss || '') + '\n' + newBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- State persistence ---
|
// --- State persistence ---
|
||||||
|
|
||||||
const saveState = () => {
|
const saveState = () => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
elementStates.set(selector.value, {
|
elementStates.set(selector.value, {
|
||||||
widthToggle: widthEnabled.value,
|
widthToggle: widthEnabled.value, widthCache: widthCache.value ? { ...widthCache.value } : null,
|
||||||
widthCache: widthCache.value ? { ...widthCache.value } : null,
|
|
||||||
widthValue: { value: width.value, unit: width.unit },
|
widthValue: { value: width.value, unit: width.unit },
|
||||||
heightToggle: heightEnabled.value,
|
heightToggle: heightEnabled.value, heightCache: heightCache.value ? { ...heightCache.value } : null,
|
||||||
heightCache: heightCache.value ? { ...heightCache.value } : null,
|
heightAuto: heightAuto.value, heightValue: { value: heightPx.value, unit: heightPx.unit },
|
||||||
heightAuto: heightAuto.value,
|
marginToggle: marginEnabled.value, marginCache: marginCache.value ? { ...marginCache.value } : null,
|
||||||
heightValue: { value: heightPx.value, unit: heightPx.unit },
|
marginValue: { ...margin },
|
||||||
|
positionToggle: positionEnabled.value, positionCache: positionCache.value ? { ...positionCache.value } : null,
|
||||||
|
posTop: posTop.value, posLeft: posLeft.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Open ---
|
const restoreFromStored = (stored, defaults) => {
|
||||||
|
widthEnabled.value = stored.widthToggle;
|
||||||
|
widthCache.value = stored.widthCache ? { ...stored.widthCache } : null;
|
||||||
|
width.value = stored.widthToggle ? stored.widthValue.value : (stored.widthCache ? stored.widthCache.value : defaults.width.value);
|
||||||
|
width.unit = stored.widthToggle ? stored.widthValue.unit : (stored.widthCache ? stored.widthCache.unit : defaults.width.unit);
|
||||||
|
|
||||||
|
heightEnabled.value = stored.heightToggle;
|
||||||
|
heightCache.value = stored.heightCache ? { ...stored.heightCache } : null;
|
||||||
|
heightAuto.value = stored.heightAuto;
|
||||||
|
heightPx.value = stored.heightToggle ? stored.heightValue.value : (stored.heightCache ? stored.heightCache.value : defaults.height.value);
|
||||||
|
heightPx.unit = stored.heightToggle ? stored.heightValue.unit : (stored.heightCache ? stored.heightCache.unit : defaults.height.unit);
|
||||||
|
|
||||||
|
marginEnabled.value = stored.marginToggle || false;
|
||||||
|
marginCache.value = stored.marginCache ? { ...stored.marginCache } : null;
|
||||||
|
Object.assign(margin, stored.marginToggle ? stored.marginValue : (stored.marginCache || { top: 0, right: 0, bottom: 0, left: 0 }));
|
||||||
|
|
||||||
|
positionEnabled.value = stored.positionToggle || false;
|
||||||
|
positionCache.value = stored.positionCache ? { ...stored.positionCache } : null;
|
||||||
|
posTop.value = stored.positionToggle ? stored.posTop : (stored.positionCache ? stored.positionCache.top : 0);
|
||||||
|
posLeft.value = stored.positionToggle ? stored.posLeft : (stored.positionCache ? stored.positionCache.left : 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Open ---
|
||||||
const handleImageClick = (figure, event) => {
|
const handleImageClick = (figure, event) => {
|
||||||
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-image--'));
|
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-image--'));
|
||||||
const sel = uniqueClass
|
const sel = uniqueClass
|
||||||
|
|
@ -378,47 +425,41 @@ const handleImageClick = (figure, event) => {
|
||||||
|
|
||||||
const stored = elementStates.get(sel);
|
const stored = elementStates.get(sel);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
widthEnabled.value = stored.widthToggle;
|
restoreFromStored(stored, imageDefaults);
|
||||||
widthCache.value = stored.widthCache ? { ...stored.widthCache } : null;
|
|
||||||
width.value = stored.widthToggle ? stored.widthValue.value : (stored.widthCache ? stored.widthCache.value : imageDefaults.width.value);
|
|
||||||
width.unit = stored.widthToggle ? stored.widthValue.unit : (stored.widthCache ? stored.widthCache.unit : imageDefaults.width.unit);
|
|
||||||
|
|
||||||
heightEnabled.value = stored.heightToggle;
|
|
||||||
heightCache.value = stored.heightCache ? { ...stored.heightCache } : null;
|
|
||||||
heightAuto.value = stored.heightAuto;
|
|
||||||
heightPx.value = stored.heightToggle ? stored.heightValue.value : (stored.heightCache ? stored.heightCache.value : imageDefaults.height.value);
|
|
||||||
heightPx.unit = stored.heightToggle ? stored.heightValue.unit : (stored.heightCache ? stored.heightCache.unit : imageDefaults.height.unit);
|
|
||||||
} else {
|
} else {
|
||||||
// Width
|
widthEnabled.value = false; widthCache.value = null;
|
||||||
widthEnabled.value = false;
|
|
||||||
widthCache.value = null;
|
|
||||||
const wCss = stylesheetStore.extractValue(sel, 'width');
|
const wCss = stylesheetStore.extractValue(sel, 'width');
|
||||||
if (wCss && wCss.value !== undefined) {
|
if (wCss && wCss.value !== undefined) {
|
||||||
width.value = wCss.value;
|
width.value = wCss.value; width.unit = wCss.unit;
|
||||||
width.unit = wCss.unit;
|
if (!(wCss.value === imageDefaults.width.value && wCss.unit === imageDefaults.width.unit)) widthEnabled.value = true;
|
||||||
const isDefaultWidth = wCss.value === imageDefaults.width.value && wCss.unit === imageDefaults.width.unit;
|
} else { width.value = imageDefaults.width.value; width.unit = imageDefaults.width.unit; }
|
||||||
if (!isDefaultWidth) widthEnabled.value = true;
|
|
||||||
} else {
|
|
||||||
width.value = imageDefaults.width.value; width.unit = imageDefaults.width.unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Height
|
heightEnabled.value = false; heightCache.value = null;
|
||||||
heightEnabled.value = false;
|
|
||||||
heightCache.value = null;
|
|
||||||
const hCss = stylesheetStore.extractValue(sel, 'height');
|
const hCss = stylesheetStore.extractValue(sel, 'height');
|
||||||
const hStr = typeof hCss === 'string' ? hCss : null;
|
const hStr = typeof hCss === 'string' ? hCss : null;
|
||||||
if (hStr === 'auto') {
|
if (hStr === 'auto') { heightAuto.value = true; }
|
||||||
heightAuto.value = true; // toggle reste OFF — c'est la valeur par défaut
|
else if (hStr) { const m = hStr.match(/([\d.]+)px/i); if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; heightEnabled.value = true; } else { heightAuto.value = imageDefaults.height.auto; heightPx.value = imageDefaults.height.value; heightPx.unit = imageDefaults.height.unit; } }
|
||||||
} else if (hStr) {
|
else if (hCss && hCss.value !== undefined) { heightAuto.value = false; heightPx.value = hCss.value; heightPx.unit = hCss.unit; heightEnabled.value = true; }
|
||||||
const m = hStr.match(/([\d.]+)px/i);
|
else { heightAuto.value = imageDefaults.height.auto; heightPx.value = imageDefaults.height.value; heightPx.unit = imageDefaults.height.unit; }
|
||||||
if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; heightEnabled.value = true; }
|
|
||||||
else { heightAuto.value = imageDefaults.height.auto; heightPx.value = imageDefaults.height.value; heightPx.unit = imageDefaults.height.unit; }
|
marginEnabled.value = false; marginCache.value = null;
|
||||||
} else if (hCss && hCss.value !== undefined) {
|
margin.top = 0; margin.right = 0; margin.bottom = 0; margin.left = 0;
|
||||||
heightAuto.value = false; heightPx.value = hCss.value; heightPx.unit = hCss.unit; heightEnabled.value = true;
|
let hasMargin = false;
|
||||||
} else {
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
heightAuto.value = imageDefaults.height.auto;
|
const mCss = stylesheetStore.extractValue(sel, `margin-${side}`);
|
||||||
heightPx.value = imageDefaults.height.value;
|
if (mCss) { const v = mCss.value !== undefined ? mCss.value : parseFloat(mCss); if (!isNaN(v) && v !== 0) { margin[side] = v; hasMargin = true; } }
|
||||||
heightPx.unit = imageDefaults.height.unit;
|
}
|
||||||
|
if (hasMargin) marginEnabled.value = true;
|
||||||
|
|
||||||
|
positionEnabled.value = false; positionCache.value = null; posTop.value = 0; posLeft.value = 0;
|
||||||
|
const posCss = stylesheetStore.extractValue(sel, 'position');
|
||||||
|
const posStr = typeof posCss === 'string' ? posCss : posCss?.value;
|
||||||
|
if (posStr === 'relative') {
|
||||||
|
positionEnabled.value = true;
|
||||||
|
const tCss = stylesheetStore.extractValue(sel, 'top');
|
||||||
|
const bCss = stylesheetStore.extractValue(sel, 'left');
|
||||||
|
posTop.value = tCss ? (tCss.value !== undefined ? tCss.value : parseFloat(tCss)) || 0 : 0;
|
||||||
|
posLeft.value = bCss ? (bCss.value !== undefined ? bCss.value : parseFloat(bCss)) || 0 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,7 +468,6 @@ const handleImageClick = (figure, event) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Close ---
|
// --- Close ---
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
saveState();
|
saveState();
|
||||||
selector.value = '';
|
selector.value = '';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue