feat: add CSS file import with drag & drop support
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 16s
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 16s
Add CssFileImport component to StylesheetViewer allowing users to import CSS files to replace custom CSS content. Features: - Click to select file via file dialog - Drag & drop support with visual feedback - File validation (.css only, max 1MB) - Error messages for invalid files - Direct replacement of customCss content New component: - src/components/ui/CssFileImport.vue Integration: - Added at top of StylesheetViewer - Emits 'import' event with file content - Content replaces customCss in store Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b692047ff2
commit
e88c217b1e
2 changed files with 168 additions and 0 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="stylesheet-viewer">
|
<div id="stylesheet-viewer">
|
||||||
|
<!-- CSS File Import -->
|
||||||
|
<CssFileImport @import="handleCssImport" />
|
||||||
|
|
||||||
<!-- Base CSS Section (Collapsable, closed by default) -->
|
<!-- Base CSS Section (Collapsable, closed by default) -->
|
||||||
<div class="css-section">
|
<div class="css-section">
|
||||||
<div class="section-header" @click="isBaseCssExpanded = !isBaseCssExpanded">
|
<div class="section-header" @click="isBaseCssExpanded = !isBaseCssExpanded">
|
||||||
|
|
@ -53,6 +56,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { useStylesheetStore } from '../stores/stylesheet';
|
import { useStylesheetStore } from '../stores/stylesheet';
|
||||||
|
import CssFileImport from './ui/CssFileImport.vue';
|
||||||
import hljs from 'highlight.js/lib/core';
|
import hljs from 'highlight.js/lib/core';
|
||||||
import css from 'highlight.js/lib/languages/css';
|
import css from 'highlight.js/lib/languages/css';
|
||||||
import 'highlight.js/styles/atom-one-dark.css';
|
import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
|
@ -90,6 +94,11 @@ const handleFocus = () => {
|
||||||
stylesheetStore.isEditing = true;
|
stylesheetStore.isEditing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCssImport = (cssContent) => {
|
||||||
|
// Replace custom CSS with imported content
|
||||||
|
stylesheetStore.customCss = cssContent;
|
||||||
|
};
|
||||||
|
|
||||||
// Watch editing mode and format when exiting
|
// Watch editing mode and format when exiting
|
||||||
watch(isCustomCssEditable, async (newValue, oldValue) => {
|
watch(isCustomCssEditable, async (newValue, oldValue) => {
|
||||||
stylesheetStore.isEditing = newValue;
|
stylesheetStore.isEditing = newValue;
|
||||||
|
|
|
||||||
159
src/components/ui/CssFileImport.vue
Normal file
159
src/components/ui/CssFileImport.vue
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="css-import"
|
||||||
|
:class="{ 'is-dragging': isDragging }"
|
||||||
|
@dragover.prevent="handleDragOver"
|
||||||
|
@dragleave.prevent="handleDragLeave"
|
||||||
|
@drop.prevent="handleDrop"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref="fileInput"
|
||||||
|
type="file"
|
||||||
|
accept=".css"
|
||||||
|
@change="handleFileSelect"
|
||||||
|
style="display: none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="import-button" @click="openFileDialog" type="button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Importer un fichier CSS</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span class="import-hint">ou glisser-déposer ici</span>
|
||||||
|
|
||||||
|
<div v-if="error" class="import-error">{{ error }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['import']);
|
||||||
|
|
||||||
|
const fileInput = ref(null);
|
||||||
|
const isDragging = ref(false);
|
||||||
|
const error = ref('');
|
||||||
|
|
||||||
|
const openFileDialog = () => {
|
||||||
|
fileInput.value?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
isDragging.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e) => {
|
||||||
|
isDragging.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e) => {
|
||||||
|
isDragging.value = false;
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
processFile(files[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileSelect = (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
processFile(files[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processFile = (file) => {
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (!file.name.endsWith('.css')) {
|
||||||
|
error.value = 'Seuls les fichiers .css sont acceptés';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate file size (max 1MB)
|
||||||
|
if (file.size > 1024 * 1024) {
|
||||||
|
error.value = 'Le fichier est trop volumineux (max 1MB)';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file content
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const content = e.target.result;
|
||||||
|
emit('import', content);
|
||||||
|
|
||||||
|
// Reset input
|
||||||
|
if (fileInput.value) {
|
||||||
|
fileInput.value.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = () => {
|
||||||
|
error.value = 'Erreur lors de la lecture du fichier';
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.css-import {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: #21252b;
|
||||||
|
border: 2px dashed transparent;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.css-import.is-dragging {
|
||||||
|
border-color: #61afef;
|
||||||
|
background: #2c313c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: #2c313c;
|
||||||
|
color: #abb2bf;
|
||||||
|
border: 1px solid #3e4451;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-button:hover {
|
||||||
|
background: #3e4451;
|
||||||
|
border-color: #61afef;
|
||||||
|
color: #61afef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-button svg {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-hint {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #5c6370;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-error {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #e06c75;
|
||||||
|
background: rgba(224, 108, 117, 0.1);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue