geoproject-app/src/components/ElementPopup.vue

219 lines
4.2 KiB
Vue
Raw Normal View History

<template>
<div
v-if="visible"
id="element-popup"
:style="{ top: position.y + 'px', left: position.x + 'px' }"
>
<div class="popup-header">
<span>{{ selector }}</span>
<button class="close-btn" @click="close">×</button>
</div>
<div class="popup-body">
<div class="popup-controls">
<div class="control" v-if="fontSizeData">
<label>font-size</label>
<input
type="number"
step="0.1"
:value="fontSizeData.value"
@input="updateFontSize(parseFloat($event.target.value))"
/>
<span>{{ fontSizeData.unit }}</span>
</div>
<p v-else class="no-styles">Aucun style éditable</p>
</div>
<div class="popup-css">
<textarea
:value="elementCss"
@input="handleCssInput"
spellcheck="false"
></textarea>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
const stylesheetStore = useStylesheetStore();
const props = defineProps({
iframeRef: Object,
});
const visible = ref(false);
const position = ref({ x: 0, y: 0 });
const selector = ref('');
const getSelectorFromElement = (element) => {
return element.id ? `#${element.id}` : `.${element.className.split(' ')[0]}`;
};
const calculatePosition = (element) => {
const rect = element.getBoundingClientRect();
const iframeRect = props.iframeRef.getBoundingClientRect();
return {
x: iframeRect.left + rect.left,
y: iframeRect.top + rect.bottom + 5,
};
};
const open = (element) => {
selector.value = getSelectorFromElement(element);
position.value = calculatePosition(element);
visible.value = true;
};
const close = () => {
visible.value = false;
};
const handleIframeClick = (event) => {
const element = event.target;
if (element.tagName === 'BODY' || element.tagName === 'HTML') {
close();
return;
}
open(element);
};
const elementCss = computed(() => {
if (!selector.value) return '';
return stylesheetStore.extractBlock(selector.value);
});
const fontSizeData = computed(() => {
if (!selector.value) return null;
return stylesheetStore.extractValue(selector.value, 'font-size');
});
const updateFontSize = (value) => {
if (!fontSizeData.value) return;
stylesheetStore.updateProperty(
selector.value,
'font-size',
value,
fontSizeData.value.unit
);
};
let cssDebounceTimer = null;
const handleCssInput = (event) => {
const newCss = event.target.value;
if (cssDebounceTimer) {
clearTimeout(cssDebounceTimer);
}
cssDebounceTimer = setTimeout(() => {
const oldBlock = elementCss.value;
stylesheetStore.content = stylesheetStore.content.replace(oldBlock, newCss);
}, 500);
};
defineExpose({ handleIframeClick });
</script>
<style scoped>
#element-popup {
position: fixed;
background: white;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
min-width: 400px;
max-width: 500px;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #eee;
font-size: 0.875rem;
font-weight: bold;
background: #f5f5f5;
}
.close-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
padding: 0;
}
.popup-body {
display: flex;
gap: 1px;
background: #eee;
}
.popup-controls {
flex: 1;
padding: 0.75rem;
background: white;
}
.popup-css {
flex: 1;
background: #1e1e1e;
display: flex;
}
.popup-css textarea {
width: 100%;
height: 200px;
background: #1e1e1e;
color: #abb2bf;
border: none;
padding: 0.75rem;
font-family: 'Courier New', Courier, monospace;
font-size: 0.75rem;
line-height: 1.4;
resize: none;
outline: none;
}
.control {
margin-bottom: 0.5rem;
}
.control label {
display: block;
font-size: 0.75rem;
margin-bottom: 0.25rem;
color: #666;
}
.control input {
width: 60px;
padding: 0.25rem;
font-size: 0.875rem;
}
.control span {
font-size: 0.75rem;
color: #666;
margin-left: 0.25rem;
}
.no-styles {
font-size: 0.75rem;
color: #999;
margin: 0;
}
.no-css {
color: #666;
font-style: italic;
}
</style>