VibeGame / src /lib /components /layout /SplitView.svelte
dylanebert's picture
improved prompting/UX
db9635c
<script lang="ts">
import { onMount } from "svelte";
import { Splitpanes, Pane } from "svelte-splitpanes";
import { uiStore } from "../../stores/ui";
import CodeEditor from "../editor/CodeEditor.svelte";
import GameCanvas from "../game/GameCanvas.svelte";
import ConsolePanel from "../console/ConsolePanel.svelte";
import ChatPanel from "../chat/ChatPanel.svelte";
import gsap from "gsap";
$: viewMode = $uiStore.viewMode;
let userMainPaneSizes = { editor: 0.4, preview: 0.6 };
let userEditorPaneSizes = { code: 0.5, chat: 0.5 };
let userPreviewPaneSizes = { game: 0.75, console: 0.25 };
onMount(() => {
gsap.fromTo(
".editor-pane",
{ opacity: 0, x: -20 },
{ opacity: 1, x: 0, duration: 0.6, delay: 0.1, ease: "power3.out" },
);
gsap.fromTo(
".preview-pane",
{ opacity: 0, x: 20 },
{ opacity: 1, x: 0, duration: 0.6, delay: 0.2, ease: "power3.out" },
);
const resetCursor = () => {
document.body.style.cursor = "";
};
document.addEventListener("mouseup", resetCursor);
return () => {
document.removeEventListener("mouseup", resetCursor);
};
});
$: if (viewMode && viewMode !== 'about') {
handleViewModeAnimation(viewMode);
}
function handleViewModeAnimation(mode: "code" | "preview") {
const mainPanes = document
.querySelector(".main-splitpanes")
?.querySelectorAll(":scope > .splitpanes__pane");
if (!mainPanes || mainPanes.length < 2) return;
const editorPaneEl = mainPanes[0] as HTMLElement;
const previewPaneEl = mainPanes[1] as HTMLElement;
const mainSplitter = document.querySelector(
".main-splitpanes > .splitpanes__splitter",
) as HTMLElement;
if (!editorPaneEl || !previewPaneEl) return;
const editorSplitpanes = editorPaneEl.querySelectorAll(".splitpanes__pane");
const codePane = editorSplitpanes[0] as HTMLElement;
const chatPane = editorSplitpanes[1] as HTMLElement;
const previewSplitpanes = previewPaneEl.querySelectorAll(".splitpanes__pane");
const gamePane = previewSplitpanes[0] as HTMLElement;
const consolePane = previewSplitpanes[1] as HTMLElement;
const consoleSplitter = previewPaneEl.querySelector(".splitpanes__splitter") as HTMLElement;
if (mode === "preview") {
const editorWidth = editorPaneEl.offsetWidth;
const previewWidth = previewPaneEl.offsetWidth;
const totalWidth = editorWidth + previewWidth;
if (totalWidth > 0) {
userMainPaneSizes.editor = editorWidth / totalWidth;
userMainPaneSizes.preview = previewWidth / totalWidth;
}
if (codePane && chatPane) {
const codeHeight = codePane.offsetHeight;
const chatHeight = chatPane.offsetHeight;
const totalEditorHeight = codeHeight + chatHeight;
if (totalEditorHeight > 0) {
userEditorPaneSizes.code = codeHeight / totalEditorHeight;
userEditorPaneSizes.chat = chatHeight / totalEditorHeight;
}
}
if (gamePane && consolePane) {
const gameHeight = gamePane.offsetHeight;
const consoleHeight = consolePane.offsetHeight;
const totalHeight = gameHeight + consoleHeight;
if (totalHeight > 0) {
userPreviewPaneSizes.game = gameHeight / totalHeight;
userPreviewPaneSizes.console = consoleHeight / totalHeight;
}
}
gsap.set(editorPaneEl, {
flexGrow: userMainPaneSizes.editor,
flexBasis: `${userMainPaneSizes.editor * 100}%`,
});
gsap.set(previewPaneEl, {
flexGrow: userMainPaneSizes.preview,
flexBasis: `${userMainPaneSizes.preview * 100}%`,
});
if (codePane && chatPane) {
gsap.set(codePane, {
flexGrow: userEditorPaneSizes.code,
flexBasis: `${userEditorPaneSizes.code * 100}%`,
});
gsap.set(chatPane, {
flexGrow: userEditorPaneSizes.chat,
flexBasis: `${userEditorPaneSizes.chat * 100}%`,
});
}
if (gamePane && consolePane) {
gsap.set(gamePane, {
flexGrow: userPreviewPaneSizes.game,
flexBasis: `${userPreviewPaneSizes.game * 100}%`,
});
gsap.set(consolePane, {
flexGrow: userPreviewPaneSizes.console,
flexBasis: `${userPreviewPaneSizes.console * 100}%`,
});
}
}
if (mode === "preview") {
gsap.timeline({ ease: "power2.out" })
.to(editorPaneEl, {
opacity: 0,
duration: 0.15,
})
.to(
consolePane,
{
opacity: 0,
duration: 0.15,
},
"-=0.15",
)
.to(
editorPaneEl,
{
flexGrow: 0,
flexBasis: "0%",
duration: 0.2,
ease: "power2.inOut",
onComplete: () => {
editorPaneEl.style.display = "none";
if (mainSplitter) mainSplitter.style.display = "none";
},
},
"-=0.1",
)
.to(
consolePane,
{
flexGrow: 0,
flexBasis: "0%",
duration: 0.2,
ease: "power2.inOut",
onComplete: () => {
consolePane.style.display = "none";
if (consoleSplitter) consoleSplitter.style.display = "none";
},
},
"-=0.2",
)
.to(
gamePane,
{
flexGrow: 1,
flexBasis: "100%",
duration: 0.2,
ease: "power2.inOut",
},
"-=0.2",
)
.to(
previewPaneEl,
{
flexGrow: 1,
flexBasis: "100%",
duration: 0.2,
ease: "power2.inOut",
},
"-=0.2",
);
} else {
editorPaneEl.style.display = "";
if (mainSplitter) mainSplitter.style.display = "";
consolePane.style.display = "";
if (consoleSplitter) consoleSplitter.style.display = "";
gsap.timeline({ ease: "power2.out" })
.set(editorPaneEl, {
opacity: 0,
flexGrow: 0,
flexBasis: "0%",
})
.set(consolePane, {
opacity: 0,
flexGrow: 0,
flexBasis: "0%",
})
.to([editorPaneEl, previewPaneEl], {
flexGrow: (i) =>
i === 0 ? userMainPaneSizes.editor : userMainPaneSizes.preview,
flexBasis: (i) =>
i === 0
? `${userMainPaneSizes.editor * 100}%`
: `${userMainPaneSizes.preview * 100}%`,
duration: 0.2,
ease: "power2.inOut",
})
.to(
[codePane, chatPane],
{
flexGrow: (i) =>
i === 0 ? userEditorPaneSizes.code : userEditorPaneSizes.chat,
flexBasis: (i) =>
i === 0
? `${userEditorPaneSizes.code * 100}%`
: `${userEditorPaneSizes.chat * 100}%`,
duration: 0.2,
ease: "power2.inOut",
},
"-=0.2",
)
.to(
[gamePane, consolePane],
{
flexGrow: (i) =>
i === 0 ? userPreviewPaneSizes.game : userPreviewPaneSizes.console,
flexBasis: (i) =>
i === 0
? `${userPreviewPaneSizes.game * 100}%`
: `${userPreviewPaneSizes.console * 100}%`,
duration: 0.2,
ease: "power2.inOut",
},
"-=0.2",
)
.to(
consolePane,
{
opacity: 1,
duration: 0.15,
},
"-=0.15",
)
.to(
editorPaneEl,
{
opacity: 1,
duration: 0.15,
onComplete: () => {
window.dispatchEvent(new Event("resize"));
},
},
"-=0.15",
);
}
}
</script>
<div class="editor-layout">
<Splitpanes class="main-splitpanes" theme="modern-theme">
<Pane minSize={20} size={40} class="editor-pane">
<div class="editor-panel">
<Splitpanes horizontal class="editor-splitpanes" theme="modern-theme">
<Pane minSize={30} size={userEditorPaneSizes.code * 100} class="code-pane">
<CodeEditor />
</Pane>
<Pane minSize={15} size={userEditorPaneSizes.chat * 100} class="chat-pane">
<ChatPanel />
</Pane>
</Splitpanes>
</div>
</Pane>
<Pane minSize={25} size={60} class="preview-pane">
<div class="preview-panel">
<Splitpanes horizontal class="preview-splitpanes" theme="modern-theme">
<Pane minSize={30} class="game-pane">
<GameCanvas />
</Pane>
<Pane minSize={15} size={25} class="console-pane">
<ConsolePanel />
</Pane>
</Splitpanes>
</div>
</Pane>
</Splitpanes>
</div>
<style>
.editor-layout {
flex: 1;
display: flex;
min-height: 0;
background: rgba(139, 115, 85, 0.02);
}
:global(.main-splitpanes) {
width: 100%;
height: 100%;
}
:global(.editor-splitpanes) {
width: 100%;
height: 100%;
}
:global(.preview-splitpanes) {
width: 100%;
height: 100%;
}
:global(.editor-pane) {
display: flex;
overflow: hidden;
}
:global(.preview-pane) {
display: flex;
overflow: hidden;
}
:global(.code-pane) {
display: flex;
overflow: hidden;
}
:global(.game-pane) {
display: flex;
overflow: hidden;
}
:global(.console-pane) {
display: flex;
overflow: hidden;
}
:global(.chat-pane) {
display: flex;
overflow: hidden;
}
.editor-panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: rgba(11, 10, 9, 0.3);
min-width: 0;
overflow: hidden;
}
.preview-panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: rgba(11, 10, 9, 0.3);
min-width: 0;
overflow: hidden;
}
:global(.splitpanes.modern-theme .splitpanes__splitter) {
background: rgba(139, 115, 85, 0.06);
position: relative;
transition: background 0.2s;
}
:global(.splitpanes.modern-theme .splitpanes__splitter:hover) {
background: rgba(139, 115, 85, 0.12);
}
:global(.splitpanes.modern-theme .splitpanes__splitter:before) {
content: "";
position: absolute;
z-index: 1;
transition: opacity 0.2s;
background: rgba(124, 152, 133, 0.1);
opacity: 0;
}
:global(.splitpanes.modern-theme .splitpanes__splitter:hover:before) {
opacity: 1;
}
:global(.splitpanes--vertical.modern-theme > .splitpanes__splitter) {
width: 1px;
cursor: col-resize;
}
:global(.splitpanes--vertical.modern-theme > .splitpanes__splitter:before) {
left: -3px;
right: -3px;
height: 100%;
cursor: col-resize;
}
:global(.splitpanes--horizontal.modern-theme > .splitpanes__splitter) {
height: 1px;
cursor: row-resize;
}
:global(.splitpanes--horizontal.modern-theme > .splitpanes__splitter:before) {
top: -3px;
bottom: -3px;
width: 100%;
cursor: row-resize;
}
:global(body.splitpanes--dragging) {
cursor: inherit;
}
:global(body:not(.splitpanes--dragging) .splitpanes__splitter:not(:hover)) {
cursor: default;
}
</style>