Skip to content

Commit 5202c9a

Browse files
committed
[frontend] refactor draggable logic
1 parent 1435562 commit 5202c9a

File tree

4 files changed

+165
-136
lines changed

4 files changed

+165
-136
lines changed

frontend/src/Shell.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Component } from "dreamland/core";
2+
import type { Tab } from "./Tab";
3+
4+
export const Shell: Component<
5+
{
6+
tabs: Tab[];
7+
activeTab: Tab;
8+
},
9+
{},
10+
{
11+
pushFrame: (frame: ScramjetFrame) => void;
12+
}
13+
> = function (cx) {
14+
this.pushFrame = (frame) => {
15+
cx.root.appendChild(
16+
<div
17+
class="container"
18+
class:active={use(this.activeTab).map(
19+
(t) => t && t.$.state.frame === frame
20+
)}
21+
>
22+
{frame.frame}
23+
</div>
24+
);
25+
};
26+
27+
return <div></div>;
28+
};
29+
Shell.css = `
30+
iframe {
31+
32+
}
33+
`;

frontend/src/main.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ let connection = new BareMuxConnection("/baremux/worker.js");
99
connection.setTransport("/epoxy/index.mjs", [{ wisp: "wss://anura.pro" }]);
1010
export let client = new BareClient();
1111

12-
const scramjet = new ScramjetController({
12+
export const scramjet = new ScramjetController({
1313
files: {
1414
wasm: "/scram/scramjet.wasm.wasm",
1515
worker: "/scram/scramjet.worker.js",
@@ -33,7 +33,6 @@ const scramjet = new ScramjetController({
3333

3434
scramjet.init();
3535
navigator.serviceWorker.register("./sw.js");
36-
let frame = scramjet.createFrame();
3736

3837
let browser = createBrowser();
3938
(self as any).browser = browser;

frontend/src/style.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ html,
88
overflow: hidden;
99
}
1010

11+
:root {
12+
font-family: "Noto Sans";
13+
}
14+
1115
* {
1216
box-sizing: border-box;
1317
}

frontend/src/tabs.tsx

Lines changed: 127 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import iconClose from "@ktibow/iconset-material-symbols/close";
22
import iconAdd from "@ktibow/iconset-material-symbols/add";
3-
import type { Component, ComponentInstance } from "dreamland/core";
3+
import {
4+
createState,
5+
type Component,
6+
type ComponentInstance,
7+
} from "dreamland/core";
48
import { Icon } from "./ui/Icon";
59

610
type TabCallbacks = {
@@ -13,86 +17,28 @@ type TabCallbacks = {
1317
"on:setactive": (id: Symbol) => void;
1418
};
1519

16-
export const Tab: Component<
17-
{
18-
active: boolean;
19-
icon: string;
20-
title: string;
21-
22-
funcs: TabCallbacks;
23-
},
24-
{
25-
dragroot: HTMLElement;
26-
dragoffset: number;
27-
},
28-
{
29-
id: Symbol;
30-
31-
dragpos: number;
32-
33-
width: number;
34-
pos: number;
35-
}
36-
> = function (cx) {
37-
this.dragpos = -1;
38-
this.dragoffset = -1;
39-
this.width = 0;
40-
this.pos = 0;
41-
42-
this.id = Symbol();
43-
44-
const calcDragPos = (e: MouseEvent) => {
45-
const maxPos = this.funcs.getMaxDragPos() - cx.root.offsetWidth;
46-
47-
const pos = e.clientX - this.dragoffset - this.funcs.getAbsoluteStart();
48-
49-
this.dragpos = Math.min(Math.max(this.funcs.getLayoutStart(), pos), maxPos);
50-
this.funcs.relayout();
51-
};
52-
53-
cx.mount = () => {
54-
const root = cx.root;
55-
56-
root.addEventListener("mousedown", (e: MouseEvent) => {
57-
this.active = true;
58-
this.funcs["on:setactive"](this.id);
59-
60-
const rect = root.getBoundingClientRect();
61-
root.style.zIndex = "100";
62-
this.dragroot.style.width = rect.width + "px";
63-
this.dragroot.style.position = "absolute";
64-
this.dragoffset = e.clientX - rect.left;
65-
66-
if (this.dragoffset < 0) throw new Error("dragoffset must be positive");
67-
68-
calcDragPos(e);
69-
});
70-
root.addEventListener("transitionend", () => {
71-
root.style.transition = "";
72-
root.style.zIndex = "0";
73-
this.funcs["on:transitionend"]();
74-
});
75-
76-
window.addEventListener("mousemove", (e: MouseEvent) => {
77-
if (this.dragoffset == -1) return;
78-
calcDragPos(e);
79-
});
80-
81-
window.addEventListener("mouseup", (_e) => {
82-
if (this.dragoffset == -1) return;
83-
84-
this.dragroot.style.width = "";
85-
this.dragroot.style.position = "unset";
86-
87-
this.dragoffset = -1;
88-
this.dragpos = -1;
89-
this.funcs.relayout();
90-
});
91-
};
92-
20+
export const Tab: Component<{
21+
active: boolean;
22+
id: number;
23+
icon: string;
24+
title: string;
25+
mousedown: (e: MouseEvent) => void;
26+
transitionend: () => void;
27+
}> = function (cx) {
9328
return (
94-
<div style="z-index: 0;">
95-
<div this={use(this.dragroot).bind()} style="position: unset;">
29+
<div
30+
style="z-index: 0;"
31+
class="tab"
32+
data-id={this.id}
33+
on:mousedown={(e) => this.mousedown(e)}
34+
on:transitionend={() => {
35+
console.log("tr end");
36+
cx.root.style.transition = "";
37+
cx.root.style.zIndex = "0";
38+
this.transitionend();
39+
}}
40+
>
41+
<div class="dragroot" style="position: unset;">
9642
<div class={use(this.active).map((x) => `main ${x ? "active" : ""}`)}>
9743
<img src={use(this.icon)} />
9844
<span>{use(this.title)}</span>
@@ -192,6 +138,17 @@ Tab.css = `
192138
}
193139
`;
194140

141+
type TabData = Stateful<{
142+
id: number;
143+
title: string;
144+
active: boolean;
145+
146+
dragoffset: number;
147+
dragpos: number;
148+
149+
width: number;
150+
pos: number;
151+
}>;
195152
export const Tabs: Component<
196153
{},
197154
{
@@ -200,10 +157,25 @@ export const Tabs: Component<
200157
rightEl: HTMLElement;
201158
afterEl: HTMLElement;
202159

203-
tabs: ComponentInstance<typeof Tab>[];
160+
tabs: TabData[];
161+
currentlydragging: number;
204162
},
205163
{}
206164
> = function (cx) {
165+
this.currentlydragging = -1;
166+
let id = 0;
167+
function mktab(title) {
168+
return createState({
169+
id: id++,
170+
title,
171+
dragoffset: -1,
172+
dragpos: -1,
173+
width: 0,
174+
pos: 0,
175+
});
176+
}
177+
this.tabs = [mktab("T 1"), mktab("T 2")];
178+
207179
const TAB_PADDING = 6;
208180
const TAB_MAX_SIZE = 231;
209181
const TAB_TRANSITION = "250ms ease";
@@ -249,10 +221,7 @@ export const Tabs: Component<
249221
};
250222

251223
const reorderTabs = () => {
252-
this.tabs.sort((aComponent, bComponent) => {
253-
let a = aComponent.$.state;
254-
let b = bComponent.$.state;
255-
224+
this.tabs.sort((a, b) => {
256225
const aCenter = a.pos + a.width / 2;
257226

258227
const bLeft = b.pos;
@@ -264,15 +233,19 @@ export const Tabs: Component<
264233
});
265234
};
266235

236+
const getTabFromIndex = (index: number) => {
237+
return cx.root.querySelector(`.tab[data-id='${index}']`) as HTMLElement;
238+
};
239+
267240
const layoutTabs = (transition: boolean) => {
268241
const width = getTabWidth();
269242

270243
reorderTabs();
271244

272245
let dragpos = -1;
273246
let currpos = getLayoutStart();
274-
for (const component of this.tabs) {
275-
let tab = component.$.state;
247+
for (const tab of this.tabs) {
248+
let component = getTabFromIndex(tab.id);
276249
component.style.width = width + "px";
277250

278251
const tabPos = tab.dragpos != -1 ? tab.dragpos : currpos;
@@ -291,62 +264,82 @@ export const Tabs: Component<
291264
const afterpos = Math.max(dragpos, currpos);
292265
this.afterEl.style.transform = `translateX(${afterpos}px)`;
293266
};
294-
const tabfuncs: TabCallbacks = {
295-
relayout() {
296-
layoutTabs(true);
297-
},
298-
getMaxDragPos() {
299-
return getLayoutStart() + getRootWidth();
300-
},
301-
getAbsoluteStart() {
302-
return getAbsoluteStart();
303-
},
304-
getLayoutStart() {
305-
return getLayoutStart();
306-
},
307-
308-
"on:transitionend": () => {
309-
transitioningTabs--;
310-
if (transitioningTabs == 0) {
311-
this.tabs = this.tabs;
312-
}
313-
},
314-
"on:setactive": (id: Symbol) => {
315-
for (const tab of this.tabs) {
316-
if (tab.$.state.id != id) tab.$.state.active = false;
317-
}
318-
// TODO on:active
319-
},
320-
};
321-
322-
this.tabs = [
323-
(
324-
<Tab
325-
active={true}
326-
icon="/vite.svg"
327-
title="ViteViteViteViteViteVite Vite Vite Vite"
328-
funcs={tabfuncs}
329-
/>
330-
) as ComponentInstance<typeof Tab>,
331-
(
332-
<Tab
333-
active={false}
334-
icon="/vite.svg"
335-
title="ViteViteViteViteViteVite Vite Vite Vite"
336-
funcs={tabfuncs}
337-
/>
338-
) as ComponentInstance<typeof Tab>,
339-
];
340267

341268
cx.mount = () => {
342269
requestAnimationFrame(() => layoutTabs(false));
343270
window.addEventListener("resize", () => layoutTabs(false));
344271
};
345272

273+
const getMaxDragPos = () => {
274+
return getLayoutStart() + getRootWidth();
275+
};
276+
277+
const calcDragPos = (e: MouseEvent, tab: TabData) => {
278+
const root = getTabFromIndex(tab.id);
279+
const maxPos = getMaxDragPos() - root.offsetWidth;
280+
281+
const pos = e.clientX - tab.dragoffset - getAbsoluteStart();
282+
283+
tab.dragpos = Math.min(Math.max(getLayoutStart(), pos), maxPos);
284+
layoutTabs(true);
285+
};
286+
287+
window.addEventListener("mousemove", (e: MouseEvent) => {
288+
if (this.currentlydragging == -1) return;
289+
calcDragPos(e, this.tabs.find((tab) => tab.id === this.currentlydragging)!);
290+
});
291+
292+
window.addEventListener("mouseup", (e) => {
293+
if (this.currentlydragging == -1) return;
294+
const tab = this.tabs.find((tab) => tab.id === this.currentlydragging);
295+
const root = getTabFromIndex(tab.id);
296+
const dragroot = root.querySelector(".dragroot") as HTMLElement;
297+
298+
dragroot.style.width = "";
299+
dragroot.style.position = "unset";
300+
tab.dragoffset = -1;
301+
tab.dragpos = -1;
302+
layoutTabs(true);
303+
this.currentlydragging = -1;
304+
});
305+
306+
const mosueDown = (e: MouseEvent, tab: TabData) => {
307+
tab.active = true;
308+
this.currentlydragging = tab.id;
309+
310+
const root = getTabFromIndex(tab.id);
311+
const rect = root.getBoundingClientRect();
312+
root.style.zIndex = "100";
313+
const dragroot = root.querySelector(".dragroot") as HTMLElement;
314+
dragroot.style.width = rect.width + "px";
315+
dragroot.style.position = "absolute";
316+
tab.dragoffset = e.clientX - rect.left;
317+
318+
if (tab.dragoffset < 0) throw new Error("dragoffset must be positive");
319+
320+
calcDragPos(e, tab);
321+
};
322+
323+
const transitionend = () => {
324+
transitioningTabs--;
325+
if (transitioningTabs == 0) {
326+
this.tabs = this.tabs;
327+
}
328+
};
329+
346330
return (
347331
<div this={use(this.container).bind()}>
348332
<div class="extra left" this={use(this.leftEl).bind()}></div>
349-
{use(this.tabs)}
333+
{use(this.tabs).mapEach((tab) => (
334+
<Tab
335+
id={tab.id}
336+
title={tab.title}
337+
icon="/vite.svg"
338+
active={use(tab.id).map((id) => id === this.currentlydragging)}
339+
mousedown={(e) => mosueDown(e, tab)}
340+
transitionend={transitionend}
341+
/>
342+
))}
350343
<div class="extra after" this={use(this.afterEl).bind()}></div>
351344
<div class="extra right" this={use(this.rightEl).bind()}></div>
352345
</div>

0 commit comments

Comments
 (0)