geoproject-app/src/components/ElementPopup.vue

211 lines
4.1 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">
<pre><code class="hljs language-css" v-html="highlightedCss"></code></pre>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import hljs from 'highlight.js/lib/core';
import css from 'highlight.js/lib/languages/css';
import 'highlight.js/styles/github.css';
hljs.registerLanguage('css', css);
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 highlightedCss = computed(() => {
if (!elementCss.value) return '<span class="no-css">Aucun style défini</span>';
return hljs.highlight(elementCss.value, { language: 'css' }).value;
});
const updateFontSize = (value) => {
if (!fontSizeData.value) return;
stylesheetStore.updateProperty(
selector.value,
'font-size',
value,
fontSizeData.value.unit
);
};
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;
padding: 0.75rem;
background: #1e1e1e;
max-height: 200px;
overflow-y: auto;
}
.popup-css pre {
margin: 0;
font-size: 0.75rem;
line-height: 1.4;
}
.popup-css code {
background: transparent;
color: #fff;
}
.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>