OK
This commit is contained in:
224
src/lib/Palette.svelte
Normal file
224
src/lib/Palette.svelte
Normal file
@@ -0,0 +1,224 @@
|
||||
<script lang="ts">
|
||||
import { Square, Type, Image, Music, Video, Globe } from 'lucide-svelte';
|
||||
import { state } from './state.svelte';
|
||||
|
||||
let imageInput: HTMLInputElement;
|
||||
let soundInput: HTMLInputElement;
|
||||
let videoInput: HTMLInputElement;
|
||||
|
||||
function addTile() {
|
||||
const id = crypto.randomUUID();
|
||||
state.addItem({
|
||||
id,
|
||||
html: '',
|
||||
css: '',
|
||||
x: -state.viewport.x / state.viewport.zoom + 400,
|
||||
y: -state.viewport.y / state.viewport.zoom + 300,
|
||||
width: 200,
|
||||
height: 200,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
}
|
||||
|
||||
function addText() {
|
||||
const id = crypto.randomUUID();
|
||||
state.addItem({
|
||||
id,
|
||||
html: '<p>Text</p>',
|
||||
css: `p {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-family: 'Departure Mono', monospace;
|
||||
}`,
|
||||
x: -state.viewport.x / state.viewport.zoom + 400,
|
||||
y: -state.viewport.y / state.viewport.zoom + 300,
|
||||
width: 200,
|
||||
height: 50,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
}
|
||||
|
||||
function addEmbed() {
|
||||
const url = prompt('Enter URL to embed:');
|
||||
if (!url) return;
|
||||
const id = crypto.randomUUID();
|
||||
state.addItem({
|
||||
id,
|
||||
html: `<iframe src="${url}" frameborder="0" allowfullscreen></iframe>`,
|
||||
css: `iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}`,
|
||||
x: -state.viewport.x / state.viewport.zoom + 400,
|
||||
y: -state.viewport.y / state.viewport.zoom + 300,
|
||||
width: 640,
|
||||
height: 480,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
}
|
||||
|
||||
function handleImageChange(e: Event) {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
addImageItem(file);
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}
|
||||
|
||||
function handleSoundChange(e: Event) {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
addSoundItem(file);
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}
|
||||
|
||||
function handleVideoChange(e: Event) {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
addVideoItem(file);
|
||||
(e.target as HTMLInputElement).value = '';
|
||||
}
|
||||
|
||||
function addImageItem(file: File) {
|
||||
const id = crypto.randomUUID();
|
||||
const assetId = crypto.randomUUID();
|
||||
const url = URL.createObjectURL(file);
|
||||
const x = -state.viewport.x / state.viewport.zoom + 400;
|
||||
const y = -state.viewport.y / state.viewport.zoom + 300;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
state.addAsset(assetId, { blob: file, url, filename: file.name });
|
||||
state.addItem({
|
||||
id,
|
||||
assetId,
|
||||
html: `<img src="${url}" alt="" />`,
|
||||
css: `img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}`,
|
||||
x,
|
||||
y,
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
function addSoundItem(file: File) {
|
||||
const id = crypto.randomUUID();
|
||||
const assetId = crypto.randomUUID();
|
||||
const url = URL.createObjectURL(file);
|
||||
const x = -state.viewport.x / state.viewport.zoom + 400;
|
||||
const y = -state.viewport.y / state.viewport.zoom + 300;
|
||||
|
||||
state.addAsset(assetId, { blob: file, url, filename: file.name });
|
||||
state.addItem({
|
||||
id,
|
||||
assetId,
|
||||
html: `<audio src="${url}" controls></audio>`,
|
||||
css: `audio {
|
||||
width: 100%;
|
||||
}`,
|
||||
x,
|
||||
y,
|
||||
width: 300,
|
||||
height: 54,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
}
|
||||
|
||||
function addVideoItem(file: File) {
|
||||
const id = crypto.randomUUID();
|
||||
const assetId = crypto.randomUUID();
|
||||
const url = URL.createObjectURL(file);
|
||||
const x = -state.viewport.x / state.viewport.zoom + 400;
|
||||
const y = -state.viewport.y / state.viewport.zoom + 300;
|
||||
|
||||
const video = document.createElement('video');
|
||||
video.onloadedmetadata = () => {
|
||||
state.addAsset(assetId, { blob: file, url, filename: file.name });
|
||||
state.addItem({
|
||||
id,
|
||||
assetId,
|
||||
html: `<video src="${url}" controls></video>`,
|
||||
css: `video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}`,
|
||||
x,
|
||||
y,
|
||||
width: video.videoWidth || 640,
|
||||
height: video.videoHeight || 360,
|
||||
rotation: 0,
|
||||
zIndex: state.maxZIndex + 1
|
||||
});
|
||||
state.select(id);
|
||||
};
|
||||
video.src = url;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="palette">
|
||||
<button onclick={addTile} title="Tile"><Square size={14} /></button>
|
||||
<button onclick={addText} title="Text"><Type size={14} /></button>
|
||||
<button onclick={() => imageInput.click()} title="Image"><Image size={14} /></button>
|
||||
<button onclick={() => soundInput.click()} title="Sound"><Music size={14} /></button>
|
||||
<button onclick={() => videoInput.click()} title="Video"><Video size={14} /></button>
|
||||
<button onclick={addEmbed} title="Embed"><Globe size={14} /></button>
|
||||
|
||||
<input
|
||||
bind:this={imageInput}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onchange={handleImageChange}
|
||||
style="display:none"
|
||||
/>
|
||||
<input
|
||||
bind:this={soundInput}
|
||||
type="file"
|
||||
accept="audio/*"
|
||||
onchange={handleSoundChange}
|
||||
style="display:none"
|
||||
/>
|
||||
<input
|
||||
bind:this={videoInput}
|
||||
type="file"
|
||||
accept="video/*"
|
||||
onchange={handleVideoChange}
|
||||
style="display:none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.palette {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 4px 8px;
|
||||
background: var(--surface, #282c34);
|
||||
color: var(--text-dim, #666);
|
||||
border: 1px solid var(--border, #333);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--accent, #4a9eff);
|
||||
color: var(--text, #fff);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user