geoproject-app/src/components/ui/BasePopup.vue
2026-03-06 18:07:27 +01:00

188 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
v-if="visible"
class="settings-popup"
:style="{ top: position.y + 'px', left: position.x + 'px' }"
>
<div class="popup-header" @mousedown="startDrag">
<div class="header-left">
<slot name="header-left" />
</div>
<button class="close-btn" @click="$emit('close')">×</button>
</div>
<div class="popup-body">
<!-- Left: Controls -->
<div class="popup-controls">
<slot name="controls" />
<!-- Lock/Unlock Inheritance Button -->
<div v-if="showInheritance" class="settings-subsection">
<button class="inheritance-btn" @click="$emit('toggle-inheritance')">
<svg
v-if="inheritanceLocked"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M19 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9V10ZM5 12V20H19V12H5ZM11 14H13V18H11V14ZM17 10V9C17 6.23858 14.7614 4 12 4C9.23858 4 7 6.23858 7 9V10H17Z"
></path>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M7 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C14.7405 2 17.1131 3.5748 18.2624 5.86882L16.4731 6.76344C15.6522 5.12486 13.9575 4 12 4C9.23858 4 7 6.23858 7 9V10ZM5 12V20H19V12H5ZM10 15H14V17H10V15Z"
></path>
</svg>
<span>{{
inheritanceLocked
? "Déverrouiller l'héritage"
: "Verrouiller l'héritage"
}}</span>
</button>
</div>
</div>
<!-- Right: CSS Editor -->
<div class="popup-css">
<div class="css-header">
<span>CSS</span>
<label
class="toggle"
:class="{ 'field--view-only': showInheritance && inheritanceLocked }"
>
<span class="toggle-label">Mode édition</span>
<input
type="checkbox"
v-model="isEditable"
:disabled="showInheritance && inheritanceLocked"
/>
<span class="toggle-switch"></span>
</label>
</div>
<pre
v-if="!isEditable"
class="readonly"
><code class="hljs language-css" v-html="highlightedCss"></code></pre>
<textarea
v-else
:value="editableCss"
@input="handleCssInput"
:disabled="showInheritance && inheritanceLocked"
spellcheck="false"
></textarea>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onBeforeUnmount } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
import { usePopupPosition } from '../../composables/usePopupPosition';
import { initColoris } from '../../composables/useColoris';
import hljs from 'highlight.js/lib/core';
import css from 'highlight.js/lib/languages/css';
import 'highlight.js/styles/atom-one-dark.css';
hljs.registerLanguage('css', css);
const stylesheetStore = useStylesheetStore();
const props = defineProps({
displayCss: { type: String, default: '' },
editableCss: { type: String, default: '' },
popupWidth: { type: Number, default: 800 },
popupHeight: { type: Number, default: 600 },
showInheritance: { type: Boolean, default: true },
});
const emit = defineEmits(['close', 'css-input', 'toggle-inheritance']);
const visible = ref(false);
const position = ref({ x: 0, y: 0 });
const isEditable = ref(false);
const inheritanceLocked = ref(true);
const { calculatePosition } = usePopupPosition(props.popupWidth, props.popupHeight);
// Drag handling
let dragOffset = { x: 0, y: 0 };
const startDrag = (e) => {
// Don't drag when clicking the close button
if (e.target.closest('.close-btn')) return;
dragOffset = {
x: e.clientX - position.value.x,
y: e.clientY - position.value.y,
};
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', stopDrag);
};
const onDrag = (e) => {
position.value = {
x: e.clientX - dragOffset.x,
y: e.clientY - dragOffset.y,
};
};
const stopDrag = () => {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
};
onBeforeUnmount(() => {
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
});
const highlightedCss = computed(() => {
if (!props.displayCss) return '';
return hljs.highlight(props.displayCss, { language: 'css' }).value;
});
let cssDebounceTimer = null;
const handleCssInput = (event) => {
const newCss = event.target.value;
if (cssDebounceTimer) {
clearTimeout(cssDebounceTimer);
}
cssDebounceTimer = setTimeout(() => {
emit('css-input', newCss);
}, 500);
};
// Watch isEditable to format when exiting edit mode
watch(isEditable, async (newValue, oldValue) => {
stylesheetStore.isEditing = newValue;
if (oldValue && !newValue) {
await stylesheetStore.formatCustomCss();
}
});
const open = (event) => {
position.value = calculatePosition(event);
visible.value = true;
// Initialize Coloris after opening
setTimeout(() => initColoris(), 0);
};
const close = () => {
visible.value = false;
isEditable.value = false;
emit('close');
};
defineExpose({ visible, position, isEditable, inheritanceLocked, open, close, calculatePosition });
</script>