Very WIP
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
.file-browser {
|
||||
background: var(--c-bg-0);
|
||||
margin-left: calc(var(--sp-2) * -1);
|
||||
}
|
||||
|
||||
.file-list {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding: 0 0 0 var(--sp-2);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
|
||||
.file-label {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: var(--sp-1);
|
||||
border-radius: var(--sp-1);
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin-right: var(--sp-1);
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background: var(--c-bg-2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import "./FileBrowser.css";
|
||||
import { type Component, createSignal, Index, Show } from "solid-js";
|
||||
import { FileIcon, FolderIcon, FolderOpenIcon } from "#/dev/icons/index.ts";
|
||||
|
||||
export type BrowserFolder = Map<string, string | BrowserFolder>;
|
||||
|
||||
export type FileHandler = (path: string) => void;
|
||||
|
||||
function isFolder(entry: string | BrowserFolder): entry is BrowserFolder {
|
||||
return typeof (entry as BrowserFolder).size === "number";
|
||||
}
|
||||
|
||||
export const FileBrowser: Component<{
|
||||
root: BrowserFolder;
|
||||
handler: FileHandler;
|
||||
}> = ({ root, handler }) => {
|
||||
const FileEntry: Component<{
|
||||
name: string;
|
||||
path: string;
|
||||
}> = ({ name, path }) => {
|
||||
return (
|
||||
<div class="file-label" onClick={[handler, path]}>
|
||||
<FileIcon />
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FolderEntry: Component<{
|
||||
folder: BrowserFolder;
|
||||
name: string;
|
||||
open: boolean;
|
||||
}> = ({ folder, name, open }) => {
|
||||
const [isOpen, setIsOpen] = createSignal(open);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="file-label" onClick={() => setIsOpen(!isOpen())}>
|
||||
{isOpen() ? <FolderOpenIcon /> : <FolderIcon />}
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
|
||||
<Show when={isOpen()}>
|
||||
<FileList folder={folder} />
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const FileList: Component<{ folder: BrowserFolder }> = ({ folder }) => {
|
||||
return (
|
||||
<ul class="file-list">
|
||||
<Index each={[...folder.entries()]}>
|
||||
{(item, index) => {
|
||||
const [name, entry] = item();
|
||||
|
||||
return (
|
||||
<li class="file-item">
|
||||
{isFolder(entry) ? (
|
||||
<FolderEntry folder={entry} name={name} open={index < 1} />
|
||||
) : (
|
||||
<FileEntry name={name} path={entry} />
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
</Index>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="file-browser">
|
||||
<FileList folder={root} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
.input-field {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: var(--c-bg-2);
|
||||
color: var(--c-fg-1);
|
||||
border-radius: var(--sp-1);
|
||||
}
|
||||
|
||||
.input-field input {
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
font-size: medium;
|
||||
margin: 0;
|
||||
padding: var(--sp-1);
|
||||
border: none;
|
||||
background: var(--c-bg-2);
|
||||
color: inherit;
|
||||
border-radius: var(--sp-1);
|
||||
}
|
||||
|
||||
.input-field.icon {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
padding: var(--sp-1);
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&:has(:focus) {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--c-primary);
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import "./InputField.css";
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
export const InputField: Component<{
|
||||
name: string;
|
||||
value: string;
|
||||
type?: string;
|
||||
icon?: JSX.Element;
|
||||
children?: JSX.Element;
|
||||
onChange?: (value: string) => void;
|
||||
onInput?: (value: string) => void;
|
||||
}> = (props) => (
|
||||
<label class="input-field" classList={{ icon: !!props.icon }}>
|
||||
{props.children}
|
||||
{props.icon}
|
||||
<input
|
||||
type={props.type ?? "text"}
|
||||
name={props.name}
|
||||
value={props.value}
|
||||
onChange={(e) => props.onChange?.(e.target.value)}
|
||||
onInput={(e) => props.onInput?.(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
@@ -0,0 +1,69 @@
|
||||
.range-slider {
|
||||
width: 100%;
|
||||
color: var(--c-fg-1);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.range-slider [type="range"] {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border-radius: var(--sp-2);
|
||||
height: var(--sp-3);
|
||||
flex: auto;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: var(--sp-2);
|
||||
border-radius: var(--sp-3);
|
||||
color: transparent;
|
||||
background: var(--c-bg-2);
|
||||
border: none;
|
||||
}
|
||||
&::-webkit-slider-thumb {
|
||||
height: var(--sp-3);
|
||||
width: var(--sp-3);
|
||||
border-radius: var(--sp-3);
|
||||
border: none;
|
||||
background: var(--c-fg-1);
|
||||
margin-top: calc(var(--sp-1) * -1);
|
||||
--webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
&:focus::-webkit-slider-thumb {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--c-primary);
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
width: 100%;
|
||||
height: var(--sp-2);
|
||||
border-radius: var(--sp-3);
|
||||
color: transparent;
|
||||
background: var(--c-bg-2);
|
||||
border: none;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
height: var(--sp-3);
|
||||
width: var(--sp-3);
|
||||
border-radius: var(--sp-3);
|
||||
border: none;
|
||||
background: var(--c-fg-1);
|
||||
}
|
||||
&:focus::-moz-range-thumb {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--c-primary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import "./RangeSlider.css";
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
export const RangeSlider: Component<{
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
value: number;
|
||||
children?: JSX.Element;
|
||||
onChange?: (value: number) => void;
|
||||
onInput?: (value: number) => void;
|
||||
}> = (props) => (
|
||||
<label class="range-slider">
|
||||
{props.children}
|
||||
<input
|
||||
type="range"
|
||||
name={props.name}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
step={props.step}
|
||||
value={props.value}
|
||||
onChange={(e) => props.onChange?.(e.target.valueAsNumber)}
|
||||
onInput={(e) => props.onInput?.(e.target.valueAsNumber)}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const ArrowAutofitHeightIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M12 20H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h6m6 10v7m0-18v7m-3 8l3 3l3-3M15 6l3-3l3 3"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const ArrowAutofitWidthIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M4 12V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v6m-10 6H3m18 0h-7m-8-3l-3 3l3 3m12-6l3 3l-3 3"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
export const FileIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const FileDeltaIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"></path>
|
||||
<path d="M9 17h6l-3-6z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const FileMusicIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"></path>
|
||||
<path d="M10 16a1 1 0 1 0 2 0a1 1 0 1 0-2 0m2 0v-5l2 1"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const FilePencilIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"></path>
|
||||
<path d="m10 18l5-5a1.414 1.414 0 0 0-2-2l-5 5v2z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const FileUnknownIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
|
||||
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2m-5-4v.01"></path>
|
||||
<path d="M12 14a1.5 1.5 0 1 0-1.14-2.474"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
export const FloppyDiscIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M6 4h10l4 4v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"></path>
|
||||
<path d="M10 14a2 2 0 1 0 4 0a2 2 0 1 0-4 0m4-10v4H8V4"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const FolderIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const FolderOpenIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="m5 19l2.757-7.351A1 1 0 0 1 8.693 11H21a1 1 0 0 1 .986 1.164l-.996 5.211A2 2 0 0 1 19.026 19za2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4l3 3h7a2 2 0 0 1 2 2v2"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const LassoIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M4.028 13.252C3.371 12.28 3 11.174 3 10c0-3.866 4.03-7 9-7s9 3.134 9 7s-4.03 7-9 7c-1.913 0-3.686-.464-5.144-1.255"></path>
|
||||
<path d="M3 15a2 2 0 1 0 4 0a2 2 0 1 0-4 0"></path>
|
||||
<path d="M5 17c0 1.42.316 2.805 1 4"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
export const LassoPolygonIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="M4.028 13.252L3 10l2-7l7 5l8-3l1 9l-9 3l-5.144-1.255"></path>
|
||||
<path d="M3 15a2 2 0 1 0 4 0a2 2 0 1 0-4 0"></path>
|
||||
<path d="M5 17c0 1.42.316 2.805 1 4"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const LayoutGridIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M4 5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm10 0a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1zM4 15a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm10 0a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const PencilIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M4 20h4L18.5 9.5a2.828 2.828 0 1 0-4-4L4 16zm9.5-13.5l4 4"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const PolygonIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M10 5a2 2 0 1 0 4 0a2 2 0 1 0-4 0m7 3a2 2 0 1 0 4 0a2 2 0 1 0-4 0M3 11a2 2 0 1 0 4 0a2 2 0 1 0-4 0m10 8a2 2 0 1 0 4 0a2 2 0 1 0-4 0M6.5 9.5l3.5-3m4-1L17 7m1.5 3L16 17m-2.5.5l-7-5"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const RefreshIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const RefreshAlertIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4m-4-6v3m0 3h.01"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const RefreshDotIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4m-5-3a1 1 0 1 0 2 0a1 1 0 1 0-2 0"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const ScriptIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M17 20H6a3 3 0 0 1 0-6h11a3 3 0 0 0 0 6h1a3 3 0 0 0 3-3V6a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v8"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
export const StackBackwardIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path d="m14 12l6-3l-8-4l-8 4l6 3"></path>
|
||||
<path fill="currentColor" d="m10 12l-6 3l8 4l8-4l-6-3l-2 1z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
export const StackForwardIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
>
|
||||
<path fill="currentColor" d="M12 5L4 9l8 4l8-4z"></path>
|
||||
<path d="m10 12l-6 3l8 4l8-4l-6-3"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const TransformPointIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M3 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm0 14a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zM17 4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1zm0 14a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1zM11 5h2m-8 6v2m14-2v2m-8 6h2"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
export const ZoomScanIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width={2}
|
||||
d="M4 8V6a2 2 0 0 1 2-2h2M4 16v2a2 2 0 0 0 2 2h2m8-16h2a2 2 0 0 1 2 2v2m-4 12h2a2 2 0 0 0 2-2v-2M8 11a3 3 0 1 0 6 0a3 3 0 0 0-6 0m8 5l-2.5-2.5"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
export * from "./ArrowAutofitHeight.tsx";
|
||||
export * from "./ArrowAutofitWidth.tsx";
|
||||
export * from "./File.tsx";
|
||||
export * from "./FileDelta.tsx";
|
||||
export * from "./FileMusic.tsx";
|
||||
export * from "./FilePencil.tsx";
|
||||
export * from "./FileUnknown.tsx";
|
||||
export * from "./FloppyDisc.tsx";
|
||||
export * from "./Folder.tsx";
|
||||
export * from "./FolderOpen.tsx";
|
||||
export * from "./Lasso.tsx";
|
||||
export * from "./LassoPolygon.tsx";
|
||||
export * from "./LayoutGrid.tsx";
|
||||
export * from "./Pencil.tsx";
|
||||
export * from "./Polygon.tsx";
|
||||
export * from "./Refresh.tsx";
|
||||
export * from "./RefreshAlert.tsx";
|
||||
export * from "./RefreshDot.tsx";
|
||||
export * from "./Script.tsx";
|
||||
export * from "./StackBackward.tsx";
|
||||
export * from "./StackForward.tsx";
|
||||
export * from "./TransformPoint.tsx";
|
||||
export * from "./ZoomScan.tsx";
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
.dev-tools {
|
||||
--font-sans:
|
||||
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
|
||||
--rgb-blurple: 101, 114, 245;
|
||||
--c-blurple: rgb(var(--rgb-blurple));
|
||||
|
||||
--rgb-fuchsia: 238, 69, 158;
|
||||
--c-fuchsia: rgb(var(--rgb-fuchsia));
|
||||
|
||||
--rgb-green: 35, 165, 89;
|
||||
--c-green: rgb(var(--rgb-green));
|
||||
|
||||
--rgb-yellow: 240, 178, 50;
|
||||
--c-yellow: rgb(var(--rgb-yellow));
|
||||
|
||||
--rgb-orange: 255, 144, 0;
|
||||
--c-orange: rgb(var(--rgb-orange));
|
||||
|
||||
--rgb-red: 218, 55, 60;
|
||||
--c-red: rgb(var(--rgb-red));
|
||||
|
||||
--rgb-cyan: 95, 205, 228;
|
||||
--c-cyan: rgb(var(--rgb-cyan));
|
||||
|
||||
--rgb-pink: 246, 42, 245;
|
||||
--c-pink: rgb(var(--rgb-pink));
|
||||
|
||||
--rgb-gray: 117, 126, 138;
|
||||
--c-gray: rgb(var(--rgb-gray));
|
||||
|
||||
--rgb-white: 255, 255, 255;
|
||||
--c-white: rgb(var(--rgb-white));
|
||||
|
||||
--rgb-black: 30, 31, 34;
|
||||
--c-black: rgb(var(--rgb-black));
|
||||
|
||||
--c-primary: var(--c-blurple);
|
||||
--c-secondary: var(--c-fuchsia);
|
||||
--c-alert: var(--c-orange);
|
||||
--c-error: var(--c-red);
|
||||
|
||||
--c-bg-0: rgb(30, 31, 34);
|
||||
--c-bg-1: rgb(43, 45, 49);
|
||||
--c-bg-2: rgb(49, 51, 56);
|
||||
--c-bg-3: rgb(64, 66, 73);
|
||||
|
||||
--c-fg-0: rgb(255 255 225);
|
||||
--c-fg-1: rgb(219 222 225);
|
||||
--c-fg-2: rgb(181 186 193);
|
||||
--c-fg-3: rgb(113 116 120);
|
||||
|
||||
--sp-1: 4px;
|
||||
--sp-2: 8px;
|
||||
--sp-3: 16px;
|
||||
--sp-4: 24px;
|
||||
--sp-5: 32px;
|
||||
--sp-6: 48px;
|
||||
|
||||
height: 100%;
|
||||
min-width: 1px;
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
order: -1;
|
||||
font-size: medium;
|
||||
font-family: var(--font-sans);
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--c-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--c-fg-3);
|
||||
background: var(--c-bg-0);
|
||||
color: var(--c-fg-1);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--sp-2);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.tool-bar-resize {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.scene-props {
|
||||
display: flex;
|
||||
gap: var(--sp-2);
|
||||
flex-flow: row wrap;
|
||||
|
||||
& > :not(:first-child) {
|
||||
width: calc(50% - var(--sp-1));
|
||||
flex-basis: calc(50% - var(--sp-1));
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
import "./main.css";
|
||||
import { type Component, createSignal, onMount } from "solid-js";
|
||||
import { render } from "solid-js/web";
|
||||
import { InputField } from "#/dev/controls/InputField.tsx";
|
||||
import { RangeSlider } from "#/dev/controls/RangeSlider.tsx";
|
||||
import { currentScene } from "#/engine/Scene.ts";
|
||||
import { setScale, viewport } from "#/engine/Viewport.ts";
|
||||
import { type BrowserFolder, FileBrowser } from "./FileBrowser.tsx";
|
||||
import {
|
||||
ArrowAutofitHeightIcon,
|
||||
ArrowAutofitWidthIcon,
|
||||
FileIcon,
|
||||
ZoomScanIcon,
|
||||
} from "./icons/index.ts";
|
||||
|
||||
function sceneTreeBuilder(
|
||||
current: BrowserFolder,
|
||||
entries: string[],
|
||||
index: number,
|
||||
ref: string[],
|
||||
): void {
|
||||
console.log(entries);
|
||||
|
||||
const next = current.has(entries[0])
|
||||
? (current.get(entries[0]) as BrowserFolder)
|
||||
: (new Map() as BrowserFolder);
|
||||
|
||||
if (entries.length > 1) {
|
||||
current.set(entries.shift() as string, next);
|
||||
sceneTreeBuilder(next, entries, index, ref);
|
||||
} else {
|
||||
// if (entries[0] !== "index.ts") {
|
||||
// return;
|
||||
// }
|
||||
current.set(entries[0], entries[0]);
|
||||
}
|
||||
}
|
||||
async function fetchSceneIndex(): Promise<BrowserFolder> {
|
||||
const indexRef: string[] = await (await fetch("/api/scenes")).json();
|
||||
|
||||
return indexRef.reduce((root, entry, index) => {
|
||||
const entries = `scenes${entry}`.split("/");
|
||||
sceneTreeBuilder(root, entries, index, indexRef);
|
||||
|
||||
return root;
|
||||
}, new Map() as BrowserFolder);
|
||||
}
|
||||
|
||||
const root = await fetchSceneIndex();
|
||||
|
||||
function adjustGameContainer(gameContainer: HTMLElement, width: number): void {
|
||||
gameContainer.style = `position: relative; top: 0; left: ${width}px; width: ${document.body.offsetWidth - width}px;`;
|
||||
}
|
||||
|
||||
const DevTools: Component<{ gameContainer: HTMLElement }> = ({
|
||||
gameContainer,
|
||||
}) => {
|
||||
const [width, setWidth] = createSignal(320);
|
||||
let isResizing = false;
|
||||
|
||||
const startResizing = (): void => {
|
||||
isResizing = true;
|
||||
};
|
||||
|
||||
const stopResizing = (): void => {
|
||||
isResizing = false;
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (isResizing) {
|
||||
setWidth(event.clientX);
|
||||
self.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
};
|
||||
|
||||
adjustGameContainer(gameContainer, width());
|
||||
|
||||
onMount(() => {
|
||||
self.addEventListener("resize", () => {
|
||||
adjustGameContainer(gameContainer, width());
|
||||
});
|
||||
self.dispatchEvent(new Event("resize"));
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", stopResizing);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="dev-tools" style={{ width: `${width()}px` }}>
|
||||
<div class="tool-bar">
|
||||
<RangeSlider
|
||||
name="scale"
|
||||
min={1}
|
||||
max={8}
|
||||
step={1}
|
||||
value={viewport.initialScale}
|
||||
onInput={setScale}
|
||||
>
|
||||
<ZoomScanIcon />
|
||||
</RangeSlider>
|
||||
|
||||
<div class="scene-props">
|
||||
<InputField
|
||||
name="file"
|
||||
value={currentScene.data.id}
|
||||
icon={FileIcon()}
|
||||
></InputField>
|
||||
<InputField
|
||||
type="number"
|
||||
name="width"
|
||||
value={String(currentScene.data.width)}
|
||||
icon={ArrowAutofitWidthIcon()}
|
||||
onChange={(value) => {
|
||||
currentScene.data.width = parseInt(value);
|
||||
self.dispatchEvent(new Event("resize"));
|
||||
}}
|
||||
></InputField>
|
||||
<InputField
|
||||
type="number"
|
||||
name="height"
|
||||
value={String(currentScene.data.height)}
|
||||
icon={ArrowAutofitHeightIcon()}
|
||||
onChange={(value) => {
|
||||
currentScene.data.height = parseInt(value);
|
||||
self.dispatchEvent(new Event("resize"));
|
||||
}}
|
||||
></InputField>
|
||||
</div>
|
||||
|
||||
<FileBrowser root={root} handler={console.log} />
|
||||
</div>
|
||||
<div class="tool-bar-resize" onMouseDown={startResizing}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function use(container: HTMLElement): void {
|
||||
render(() => <DevTools gameContainer={container} />, document.body);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { readdirSync, writeFileSync } from "node:fs";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
|
||||
function buildBrowserIndex(name: string, dir: string): string[] {
|
||||
const dirents = readdirSync(dir, {
|
||||
withFileTypes: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return dirents
|
||||
.filter((dirent) => dirent.isFile() && dirent.name !== name)
|
||||
.map((dirent) => {
|
||||
const path = dirent.parentPath.substring(dir.length);
|
||||
return `${path}/${dirent.name}`;
|
||||
});
|
||||
}
|
||||
|
||||
function get(req: IncomingMessage, res: ServerResponse): void {
|
||||
console.log(req.url);
|
||||
const match = req.url?.match(/\/(\w+)$/);
|
||||
if (!match) {
|
||||
res.statusCode = 400;
|
||||
res.end();
|
||||
}
|
||||
|
||||
const sceneData = buildBrowserIndex("scenes", "scenes"); // FIXME
|
||||
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify(sceneData));
|
||||
}
|
||||
|
||||
function post(req: IncomingMessage, res: ServerResponse): void {
|
||||
// let body = "";
|
||||
// req.on("data", (chunk) => {
|
||||
// body += chunk.toString();
|
||||
// });
|
||||
// req.on("end", () => {
|
||||
// const data: SceneData = JSON.parse(body);
|
||||
// writeFileSync(
|
||||
// `${dev.scenesDir}/${data.name}.json`,
|
||||
// JSON.stringify(data, null, 2),
|
||||
// "utf8",
|
||||
// );
|
||||
// res.statusCode = 204;
|
||||
// res.end();
|
||||
// });
|
||||
}
|
||||
|
||||
export default function (req: IncomingMessage, res: ServerResponse): void {
|
||||
switch (req.method) {
|
||||
case "GET": {
|
||||
get(req, res);
|
||||
break;
|
||||
}
|
||||
case "POST": {
|
||||
post(req, res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user