working on cutscenes
This commit is contained in:
1 parent
54cc118df7
commit
6349859e66
47 files changed
+6234
-21
No files matched your search
@@ -0,0 +1,100 @@
|
||||
.panel {
|
||||
width: 200px;
|
||||
min-width: 180px;
|
||||
background: #252525;
|
||||
border-right: 1px solid #333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: #888;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #333;
|
||||
color: #ddd;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 5px 8px;
|
||||
text-align: center;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.btn:hover:not(:disabled) { background: #3d3d3d; }
|
||||
.btn:disabled { opacity: 0.4; cursor: default; }
|
||||
|
||||
.btnPrimary {
|
||||
composes: btn;
|
||||
background: #2a4a6e;
|
||||
border-color: #3a6090;
|
||||
color: #a8d0f0;
|
||||
}
|
||||
.btnPrimary:hover { background: #2e5480; }
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 16px 12px;
|
||||
color: #555;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 7px 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-left: 3px solid transparent;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.item:hover { background: #2e2e2e; }
|
||||
|
||||
.selected {
|
||||
background: #1e3a55;
|
||||
border-left-color: #5b9bd5;
|
||||
}
|
||||
|
||||
.itemId {
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.deleteBtn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #666;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s, color 0.1s;
|
||||
}
|
||||
.item:hover .deleteBtn { opacity: 1; }
|
||||
.deleteBtn:hover { color: #e06c6c; }
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useRef } from 'react';
|
||||
import { useCutsceneStore } from '../../store/cutsceneStore';
|
||||
import { parseFile, triggerDownload } from '../../utils/fileIO';
|
||||
import styles from './LeftPanel.module.css';
|
||||
|
||||
export default function LeftPanel() {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { file, selectedCutsceneId, loadFile, addCutscene, deleteCutscene, selectCutscene, getExportData } = useCutsceneStore();
|
||||
|
||||
function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const f = e.target.files?.[0];
|
||||
if (!f) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (ev) => {
|
||||
try {
|
||||
const json = JSON.parse(ev.target!.result as string);
|
||||
loadFile(parseFile(json));
|
||||
} catch {
|
||||
alert('Invalid JSON file');
|
||||
}
|
||||
};
|
||||
reader.readAsText(f);
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
const data = getExportData();
|
||||
if (data) triggerDownload(data);
|
||||
}
|
||||
|
||||
function handleDelete(id: string) {
|
||||
if (confirm(`Delete cutscene "${id}"?`)) deleteCutscene(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.header}>Cutscenes</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button className={styles.btnPrimary} onClick={() => fileInputRef.current?.click()}>
|
||||
Load JSON
|
||||
</button>
|
||||
<input ref={fileInputRef} type="file" accept=".json" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<button className={styles.btn} onClick={handleSave} disabled={!file}>
|
||||
Save JSON
|
||||
</button>
|
||||
<button className={styles.btn} onClick={addCutscene}>
|
||||
+ New
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.list}>
|
||||
{!file || file.cutscenes.length === 0 ? (
|
||||
<div className={styles.empty}>No cutscenes. Load a JSON or create new.</div>
|
||||
) : (
|
||||
file.cutscenes.map(c => (
|
||||
<div
|
||||
key={c.id}
|
||||
className={`${styles.item} ${c.id === selectedCutsceneId ? styles.selected : ''}`}
|
||||
onClick={() => selectCutscene(c.id)}
|
||||
>
|
||||
<span className={styles.itemId}>{c.id}</span>
|
||||
<button
|
||||
className={styles.deleteBtn}
|
||||
onClick={(e) => { e.stopPropagation(); handleDelete(c.id); }}
|
||||
title="Delete"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user