wip
This commit is contained in:
parent
5a58ed4ebc
commit
3ccb69c94c
|
|
@ -1 +0,0 @@
|
||||||
nodeLinker: node-modules
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,270 +1,538 @@
|
||||||
import {
|
import {
|
||||||
BaseBoxShapeUtil,
|
BaseBoxShapeUtil,
|
||||||
Editor,
|
Editor,
|
||||||
Geometry2d,
|
Geometry2d,
|
||||||
HTMLContainer,
|
HTMLContainer,
|
||||||
Rectangle2d,
|
Rectangle2d,
|
||||||
TLBaseShape,
|
TLBaseShape,
|
||||||
TLOnResizeHandler,
|
TLOnResizeHandler,
|
||||||
TLShape,
|
TLShape,
|
||||||
TLShapeId,
|
TLShapeId,
|
||||||
resizeBox,
|
resizeBox,
|
||||||
} from 'tldraw'
|
} from "tldraw";
|
||||||
import { getUserId } from './storeUtils'
|
import { getUserId } from "./storeUtils";
|
||||||
import { getEdge } from './propagators/tlgraph'
|
import { getEdge } from "./propagators/tlgraph";
|
||||||
|
|
||||||
export type ValueType = "SCALAR" | "BOOLEAN" | "STRING" | "RANK" | "NONE"
|
export type ValueType = "SCALAR" | "BOOLEAN" | "STRING" | "RANK" | "NONE";
|
||||||
|
|
||||||
export type ISocialShape = TLBaseShape<
|
export type ISocialShape = TLBaseShape<
|
||||||
"social",
|
"social",
|
||||||
{
|
{
|
||||||
w: number
|
w: number;
|
||||||
h: number
|
h: number;
|
||||||
text: string
|
text: string;
|
||||||
selector: string
|
selector: string;
|
||||||
valueType: ValueType
|
valueType: ValueType;
|
||||||
values: Record<string, any>
|
values: Record<string, any>;
|
||||||
value: any
|
value: any;
|
||||||
syntaxError: boolean
|
syntaxError: boolean;
|
||||||
}
|
}
|
||||||
>
|
>;
|
||||||
|
|
||||||
export class SocialShapeUtil extends BaseBoxShapeUtil<ISocialShape> {
|
export class SocialShapeUtil extends BaseBoxShapeUtil<ISocialShape> {
|
||||||
static override type = 'social' as const
|
static override type = "social" as const;
|
||||||
override canBind = () => true
|
private valueTypeRegex = (valueType: ValueType) =>
|
||||||
override canEdit = () => false
|
new RegExp(`${valueType}\\s*\\((.*?)\\)|${valueType}`);
|
||||||
override getDefaultProps(): ISocialShape['props'] {
|
override canBind = () => true;
|
||||||
return { w: 160 * 2, h: 90 * 2, text: '', selector: '', valueType: "NONE", values: {}, value: null, syntaxError: false }
|
override canEdit = () => false;
|
||||||
}
|
override getDefaultProps(): ISocialShape["props"] {
|
||||||
override onResize: TLOnResizeHandler<ISocialShape> = (shape, info) => {
|
return {
|
||||||
return resizeBox(shape, info)
|
w: 160 * 2,
|
||||||
}
|
h: 90 * 2,
|
||||||
override getGeometry(shape: ISocialShape): Geometry2d {
|
text: "",
|
||||||
return new Rectangle2d({
|
selector: "",
|
||||||
width: shape.props.w,
|
valueType: "NONE",
|
||||||
height: shape.props.h,
|
values: {},
|
||||||
isFilled: true,
|
value: null,
|
||||||
})
|
syntaxError: false,
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
override onResize: TLOnResizeHandler<ISocialShape> = (shape, info) => {
|
||||||
|
return resizeBox(shape, info);
|
||||||
|
};
|
||||||
|
override getGeometry(shape: ISocialShape): Geometry2d {
|
||||||
|
return new Rectangle2d({
|
||||||
|
width: shape.props.w,
|
||||||
|
height: shape.props.h,
|
||||||
|
isFilled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
indicator(shape: ISocialShape) {
|
indicator(shape: ISocialShape) {
|
||||||
return (
|
return <rect width={shape.props.w} height={shape.props.h} rx={4} />;
|
||||||
<rect
|
}
|
||||||
width={shape.props.w}
|
|
||||||
height={shape.props.h}
|
|
||||||
rx={4}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override component(shape: ISocialShape) {
|
override component(shape: ISocialShape) {
|
||||||
const currentUser = getUserId(this.editor)
|
const currentUser = getUserId(this.editor);
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
BOOLEAN: false,
|
BOOLEAN: false,
|
||||||
SCALAR: 0,
|
SCALAR: 0,
|
||||||
DEFAULT: null
|
DEFAULT: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleOnChange = (newValue: boolean | number) => {
|
const handleOnChange = (newValue: boolean | number) => {
|
||||||
this.updateProps(shape, { values: { ...shape.props.values, [currentUser]: newValue } })
|
console.log("NEW VALUE", newValue);
|
||||||
this.updateValue(shape.id)
|
this.updateProps(shape, {
|
||||||
}
|
values: { ...shape.props.values, [currentUser]: newValue },
|
||||||
|
});
|
||||||
|
this.updateValue(shape.id);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTextChange = (text: string) => {
|
const handleTextChange = (text: string) => {
|
||||||
let valueType: ValueType = "NONE"
|
let valueType: ValueType = "NONE";
|
||||||
const selector = text.match(/@([a-zA-Z]+)/)?.[1] || ''
|
const selector = text.match(/@([a-zA-Z]+)/)?.[1] || "";
|
||||||
|
|
||||||
if (text.includes('SCALAR')) {
|
if (text.includes("SCALAR")) {
|
||||||
valueType = 'SCALAR'
|
valueType = "SCALAR";
|
||||||
} else if (text.includes('BOOLEAN')) {
|
} else if (text.includes("BOOLEAN")) {
|
||||||
valueType = 'BOOLEAN'
|
valueType = "BOOLEAN";
|
||||||
} else if (text.includes('STRING')) {
|
} else if (text.includes("STRING")) {
|
||||||
valueType = 'STRING'
|
valueType = "STRING";
|
||||||
} else if (text.includes('RANK')) {
|
} else if (text.includes("RANK")) {
|
||||||
valueType = 'RANK'
|
valueType = "RANK";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueType !== shape.props.valueType) {
|
if (valueType !== shape.props.valueType) {
|
||||||
this.updateProps(shape, { text, valueType, selector, values: {} })
|
this.updateProps(shape, { text, valueType, selector, values: {} });
|
||||||
} else {
|
} else {
|
||||||
this.updateProps(shape, { text, selector })
|
this.updateProps(shape, { text, selector });
|
||||||
}
|
}
|
||||||
this.updateValue(shape.id)
|
this.updateValue(shape.id);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
const args = this.getArgs(shape, shape.props.valueType);
|
||||||
<HTMLContainer style={{ padding: 4, borderRadius: 4, border: '1px solid #ccc', outline: shape.props.syntaxError ? '2px solid orange' : 'none' }} onPointerDown={(e) => e.stopPropagation()}>
|
const inputMap = getInputMap(this.editor, shape);
|
||||||
<textarea style={{ width: '100%', height: '60%', border: '1px solid lightgrey', resize: 'none', pointerEvents: 'all' }} value={shape.props.text} onChange={(e) => handleTextChange(e.target.value)} />
|
const usedInputs: any[] = [];
|
||||||
<ValueInterface
|
for (const arg of args) {
|
||||||
type={shape.props.valueType ?? null}
|
if (arg !== false && arg !== true && inputMap[arg]) {
|
||||||
value={shape.props.values[currentUser] ?? defaultValues[shape.props.valueType as keyof typeof defaultValues]}
|
if (Array.isArray(inputMap[arg].value)) {
|
||||||
values={shape.props.values}
|
usedInputs.push(...inputMap[arg].value);
|
||||||
onChange={handleOnChange} />
|
} else {
|
||||||
</HTMLContainer>
|
usedInputs.push(inputMap[arg].value);
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// console.log("USED INPUTS", usedInputs)
|
||||||
|
|
||||||
private updateValue(shapeId: TLShapeId) {
|
return (
|
||||||
const shape = this.editor.getShape(shapeId) as ISocialShape
|
<HTMLContainer
|
||||||
const valueType = shape.props.valueType
|
style={{
|
||||||
const vals = Array.from(Object.values(shape.props.values))
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
outline: shape.props.syntaxError ? "2px solid orange" : "none",
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "4em",
|
||||||
|
height: "auto",
|
||||||
|
border: "1px solid lightgrey",
|
||||||
|
resize: "none",
|
||||||
|
pointerEvents: "all",
|
||||||
|
}}
|
||||||
|
value={shape.props.text}
|
||||||
|
onChange={(e) => handleTextChange(e.target.value)}
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<ValueInterface
|
||||||
|
type={shape.props.valueType ?? null}
|
||||||
|
value={
|
||||||
|
shape.props.values[currentUser] ??
|
||||||
|
defaultValues[shape.props.valueType as keyof typeof defaultValues]
|
||||||
|
}
|
||||||
|
values={shape.props.values}
|
||||||
|
inputs={usedInputs}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
editor={this.editor}
|
||||||
|
/>
|
||||||
|
</HTMLContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const functionBody = `return ${shape.props.text.replace(valueType, 'VALUES')};`
|
private getArgs(shape: ISocialShape, valueType: ValueType) {
|
||||||
|
const match = shape.props.text.match(this.valueTypeRegex(valueType));
|
||||||
|
let args: (string | number | boolean)[] = [];
|
||||||
|
if (match?.[1]) {
|
||||||
|
args = match[1].split(",").map((arg) => {
|
||||||
|
const trimmed = arg.trim();
|
||||||
|
if (trimmed === "true") return true;
|
||||||
|
if (trimmed === "false") return false;
|
||||||
|
if (!Number.isNaN(Number(trimmed))) return Number(trimmed);
|
||||||
|
return trimmed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
const sum = (vals: number[] | boolean[]) => {
|
private updateValue(shapeId: TLShapeId) {
|
||||||
if (valueType === 'SCALAR') {
|
const shape = this.editor.getShape(shapeId) as ISocialShape;
|
||||||
return (vals as number[]).reduce((acc, val) => acc + val, 0)
|
const valueType = shape.props.valueType;
|
||||||
}
|
const vals = Array.from(Object.values(shape.props.values));
|
||||||
if (valueType === 'BOOLEAN') {
|
|
||||||
//@ts-ignore
|
|
||||||
return vals.filter(Boolean).length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const average = (vals: number[] | boolean[]) => {
|
|
||||||
if (valueType === 'SCALAR') {
|
|
||||||
return (vals as number[]).reduce((acc, val) => acc + val, 0) / vals.length
|
|
||||||
}
|
|
||||||
if (valueType === 'BOOLEAN') {
|
|
||||||
//@ts-ignore
|
|
||||||
return vals.filter(Boolean).length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputMap = getInputMap(this.editor, shape)
|
const functionBody = `return ${shape.props.text.replace(
|
||||||
|
this.valueTypeRegex(valueType),
|
||||||
|
"VALUES"
|
||||||
|
)};`;
|
||||||
|
|
||||||
try {
|
const sum = (vals: number[] | boolean[]) => {
|
||||||
const paramNames = ['sum', 'average', 'VALUES', ...Object.keys(inputMap)]
|
if (valueType === "SCALAR") {
|
||||||
const paramValues = [sum, average, vals, ...Object.values(inputMap).map(s => s.value)]
|
return (vals as number[]).reduce((acc, val) => acc + val, 0);
|
||||||
const func = new Function(...paramNames, functionBody)
|
}
|
||||||
const result = func(...paramValues)
|
if (valueType === "BOOLEAN") {
|
||||||
|
//@ts-ignore
|
||||||
|
return vals.filter(Boolean).length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const average = (vals: number[] | boolean[]) => {
|
||||||
|
if (valueType === "SCALAR") {
|
||||||
|
return (
|
||||||
|
(vals as number[]).reduce((acc, val) => acc + val, 0) / vals.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (valueType === "BOOLEAN") {
|
||||||
|
//@ts-ignore
|
||||||
|
return vals.filter(Boolean).length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (typeof result === 'function') {
|
const countVotes = (votes: Array<{ up: string[]; down: string[] }>) => {
|
||||||
this.updateProps({ ...shape, props: { ...shape.props, value: null } }, { syntaxError: true })
|
const voteCount = votes.reduce((acc, vote) => {
|
||||||
return
|
for (const item of vote.up) {
|
||||||
}
|
acc[item] = (acc[item] || 0) + 1;
|
||||||
console.log("VALUE", result)
|
}
|
||||||
this.updateProps(shape, { value: result, syntaxError: false })
|
for (const item of vote.down) {
|
||||||
} catch (e) {
|
acc[item] = (acc[item] || 0) - 1;
|
||||||
console.log("ERROR", e)
|
}
|
||||||
this.updateProps(shape, { syntaxError: true })
|
return acc;
|
||||||
}
|
}, {} as Record<string, number>);
|
||||||
}
|
|
||||||
|
|
||||||
|
return Object.entries(voteCount).sort((a, b) => b[1] - a[1]);
|
||||||
|
};
|
||||||
|
|
||||||
private updateProps(shape: ISocialShape, props: Partial<ISocialShape['props']>) {
|
const inputMap = getInputMap(this.editor, shape);
|
||||||
this.editor.updateShape<ISocialShape>({
|
|
||||||
id: shape.id,
|
try {
|
||||||
type: 'social',
|
const paramNames = [
|
||||||
props: {
|
"sum",
|
||||||
...shape.props,
|
"average",
|
||||||
...props
|
"countVotes",
|
||||||
},
|
"VALUES",
|
||||||
})
|
...Object.keys(inputMap),
|
||||||
}
|
];
|
||||||
|
const paramValues = [
|
||||||
|
sum,
|
||||||
|
average,
|
||||||
|
countVotes,
|
||||||
|
vals,
|
||||||
|
...Object.values(inputMap).map((s) => s.value),
|
||||||
|
];
|
||||||
|
const func = new Function(...paramNames, functionBody);
|
||||||
|
const result = func(...paramValues);
|
||||||
|
|
||||||
|
if (typeof result === "function") {
|
||||||
|
this.updateProps(
|
||||||
|
{ ...shape, props: { ...shape.props, value: null } },
|
||||||
|
{ syntaxError: true }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateProps(shape, { value: result, syntaxError: false });
|
||||||
|
} catch (e) {
|
||||||
|
console.log("ERROR", e);
|
||||||
|
this.updateProps(shape, { syntaxError: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateProps(
|
||||||
|
shape: ISocialShape,
|
||||||
|
props: Partial<ISocialShape["props"]>
|
||||||
|
) {
|
||||||
|
this.editor.updateShape<ISocialShape>({
|
||||||
|
id: shape.id,
|
||||||
|
type: "social",
|
||||||
|
props: {
|
||||||
|
...shape.props,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ValueInterface({ type, value, values, onChange }: { type: ValueType; value: boolean | number | string; values: Record<string, any>; onChange: (value: any) => void }) {
|
function ValueInterface({
|
||||||
switch (type) {
|
type,
|
||||||
case 'BOOLEAN':
|
value,
|
||||||
return <>
|
values,
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '4px' }}>
|
onChange,
|
||||||
<input style={{ pointerEvents: 'all', width: '20px', height: '20px', margin: 0 }} type="checkbox" checked={value as boolean} onChange={(e) => onChange(e.target.checked)} />
|
inputs,
|
||||||
<div style={{ width: '1px', height: '20px', backgroundColor: 'grey' }} />
|
editor,
|
||||||
{Object.values(values).map((bool, i) => (
|
}: {
|
||||||
<div key={`boolean-${i}`} style={{ backgroundColor: bool ? 'blue' : 'white', width: '20px', height: '20px', border: '1px solid lightgrey', borderRadius: 2 }} />
|
type: ValueType;
|
||||||
))}
|
value: boolean | number | string;
|
||||||
</div>
|
values: Record<string, any>;
|
||||||
</>
|
onChange: (value: any) => void;
|
||||||
case 'STRING':
|
inputs: any[];
|
||||||
return (
|
editor: Editor;
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: '4px' }}>
|
}) {
|
||||||
<textarea
|
switch (type) {
|
||||||
style={{
|
case "BOOLEAN":
|
||||||
pointerEvents: 'all',
|
return (
|
||||||
width: '100%',
|
<>
|
||||||
minHeight: '60px',
|
<div
|
||||||
resize: 'vertical',
|
style={{
|
||||||
padding: '4px',
|
display: "flex",
|
||||||
boxSizing: 'border-box',
|
flexDirection: "row",
|
||||||
}}
|
alignItems: "center",
|
||||||
value={value as string}
|
gap: "4px",
|
||||||
onChange={(e) => onChange(e.target.value)}
|
}}
|
||||||
/>
|
>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<input
|
||||||
<div style={{ display: 'flex', gap: '2px' }}>
|
style={{
|
||||||
{Object.values(values).filter(value => value !== '').map((_, i) => (
|
pointerEvents: "all",
|
||||||
<div
|
width: "20px",
|
||||||
key={`string-${i}`}
|
height: "20px",
|
||||||
style={{
|
margin: 0,
|
||||||
width: '8px',
|
}}
|
||||||
height: '8px',
|
type="checkbox"
|
||||||
backgroundColor: 'blue',
|
checked={value as boolean}
|
||||||
borderRadius: '50%',
|
onChange={(e) => onChange(e.target.checked)}
|
||||||
}}
|
/>
|
||||||
/>
|
<div
|
||||||
))}
|
style={{ width: "1px", height: "20px", backgroundColor: "grey" }}
|
||||||
</div>
|
/>
|
||||||
</div>
|
{Object.values(values).map((bool, i) => (
|
||||||
</div>
|
<div
|
||||||
);
|
key={`boolean-${i}`}
|
||||||
case 'SCALAR':
|
style={{
|
||||||
return (
|
backgroundColor: bool ? "blue" : "white",
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '4px' }}>
|
width: "20px",
|
||||||
<input
|
height: "20px",
|
||||||
type="range"
|
border: "1px solid lightgrey",
|
||||||
min="0"
|
borderRadius: 2,
|
||||||
max="1"
|
}}
|
||||||
step="0.01"
|
/>
|
||||||
value={value as number ?? 0}
|
))}
|
||||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
</div>
|
||||||
style={{ width: '100px', pointerEvents: 'all' }}
|
</>
|
||||||
/>
|
);
|
||||||
<span style={{ fontFamily: 'monospace' }}>{(value as number ?? 0).toFixed(2)}</span>
|
case "STRING":
|
||||||
<div style={{ width: '1px', height: '20px', backgroundColor: 'grey' }} />
|
return (
|
||||||
{Object.values(values).map((val, i) => (
|
<div
|
||||||
<div
|
style={{
|
||||||
key={`scalar-${i}`}
|
display: "flex",
|
||||||
style={{
|
flexDirection: "column",
|
||||||
backgroundColor: `rgba(0, 0, 255, ${val ?? 0})`,
|
width: "100%",
|
||||||
width: '20px',
|
gap: "4px",
|
||||||
height: '20px',
|
}}
|
||||||
border: '1px solid lightgrey',
|
>
|
||||||
borderRadius: 2
|
<textarea
|
||||||
}}
|
style={{
|
||||||
/>
|
pointerEvents: "all",
|
||||||
))}
|
width: "100%",
|
||||||
</div>
|
minHeight: "60px",
|
||||||
);
|
resize: "vertical",
|
||||||
default:
|
padding: "4px",
|
||||||
return <div style={{ marginTop: 10, textAlign: 'center' }}>No Interface...</div>
|
boxSizing: "border-box",
|
||||||
}
|
}}
|
||||||
|
value={value as string}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: "2px" }}>
|
||||||
|
{Object.values(values)
|
||||||
|
.filter((value) => value !== "")
|
||||||
|
.map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={`string-${i}`}
|
||||||
|
style={{
|
||||||
|
width: "8px",
|
||||||
|
height: "8px",
|
||||||
|
backgroundColor: "blue",
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "RANK": {
|
||||||
|
const currentUser = getUserId(editor);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
gap: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{inputs.map((input, index) => (
|
||||||
|
<div
|
||||||
|
key={`rank-${index}`}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
border: "1px solid lightgrey",
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: "4px 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{input}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const newUp = [
|
||||||
|
...new Set([...(values[currentUser]?.up || []), input]),
|
||||||
|
];
|
||||||
|
//@ts-ignore
|
||||||
|
const newDown = (values[currentUser]?.down || []).filter(
|
||||||
|
(v: string) => v !== input
|
||||||
|
);
|
||||||
|
onChange({ up: newUp, down: newDown });
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
padding: "4px 8px",
|
||||||
|
pointerEvents: "all",
|
||||||
|
backgroundColor: values[currentUser]?.up?.includes(input)
|
||||||
|
? "#4CAF50"
|
||||||
|
: "inherit",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
▲
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
//@ts-ignore
|
||||||
|
const newUp = (values[currentUser]?.up || []).filter(
|
||||||
|
(v: string) => v !== input
|
||||||
|
);
|
||||||
|
//@ts-ignore
|
||||||
|
const newDown = [
|
||||||
|
...new Set([...(values[currentUser]?.down || []), input]),
|
||||||
|
];
|
||||||
|
onChange({ up: newUp, down: newDown });
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
padding: "4px 8px",
|
||||||
|
pointerEvents: "all",
|
||||||
|
backgroundColor: values[currentUser]?.down?.includes(input)
|
||||||
|
? "#FF3B30"
|
||||||
|
: "inherit",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
▼
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div style={{ display: "flex", gap: "2px" }}>
|
||||||
|
{Object.values(values)
|
||||||
|
.filter((value) => value !== "")
|
||||||
|
.map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={`rank-dot-${i}`}
|
||||||
|
style={{
|
||||||
|
width: "8px",
|
||||||
|
height: "8px",
|
||||||
|
backgroundColor: "blue",
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "SCALAR":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.01"
|
||||||
|
value={(value as number) ?? 0}
|
||||||
|
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||||
|
style={{ width: "100px", pointerEvents: "all" }}
|
||||||
|
/>
|
||||||
|
<span style={{ fontFamily: "monospace" }}>
|
||||||
|
{((value as number) ?? 0).toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
style={{ width: "1px", height: "20px", backgroundColor: "grey" }}
|
||||||
|
/>
|
||||||
|
{Object.values(values).map((val, i) => (
|
||||||
|
<div
|
||||||
|
key={`scalar-${i}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: `rgba(0, 0, 255, ${val ?? 0})`,
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
border: "1px solid lightgrey",
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 10, textAlign: "center" }}>
|
||||||
|
No Interface...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInputMap(editor: Editor, shape: TLShape) {
|
function getInputMap(editor: Editor, shape: TLShape) {
|
||||||
const arrowBindings = editor.getBindingsInvolvingShape(
|
const arrowBindings = editor.getBindingsInvolvingShape(shape.id, "arrow");
|
||||||
shape.id,
|
const arrows = arrowBindings.map((binding) =>
|
||||||
"arrow",
|
editor.getShape(binding.fromId)
|
||||||
)
|
);
|
||||||
const arrows = arrowBindings
|
|
||||||
.map((binding) => editor.getShape(binding.fromId))
|
|
||||||
|
|
||||||
return arrows.reduce((acc, arrow) => {
|
return arrows.reduce((acc, arrow) => {
|
||||||
const edge = getEdge(arrow, editor);
|
const edge = getEdge(arrow, editor);
|
||||||
if (edge && edge.to === shape.id) {
|
if (edge && edge.to === shape.id) {
|
||||||
const sourceShape = editor.getShape(edge.from);
|
const sourceShape = editor.getShape(edge.from);
|
||||||
if (sourceShape && edge.text) {
|
if (sourceShape && edge.text) {
|
||||||
acc[edge.text] = { value: sourceShape.props.value || sourceShape.props.text || null, shapeId: sourceShape.id }
|
//@ts-ignore
|
||||||
}
|
acc[edge.text] = {
|
||||||
}
|
//@ts-ignore
|
||||||
return acc;
|
value: sourceShape.props.value || sourceShape.props.text || null,
|
||||||
}, {} as Record<string, { value: any, shapeId: TLShapeId }>);
|
shapeId: sourceShape.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { value: any; shapeId: TLShapeId }>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenToShape(editor: Editor, shapeId: TLShapeId, callback: (prev: TLShape, next: TLShape) => void) {
|
|
||||||
return editor.sideEffects.registerAfterChangeHandler<'shape'>('shape', (prev, next) => {
|
|
||||||
if (next.id === shapeId) {
|
|
||||||
callback(prev, next)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from "file:///Users/orion/Repositories/Orion/ggraph/node_modules/vite/dist/node/index.js";
|
||||||
|
import react from "file:///Users/orion/Repositories/Orion/ggraph/node_modules/@vitejs/plugin-react/dist/index.mjs";
|
||||||
|
import wasm from "file:///Users/orion/Repositories/Orion/ggraph/node_modules/vite-plugin-wasm/exports/import.mjs";
|
||||||
|
import topLevelAwait from "file:///Users/orion/Repositories/Orion/ggraph/node_modules/vite-plugin-top-level-await/exports/import.mjs";
|
||||||
|
var vite_config_default = defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
wasm(),
|
||||||
|
topLevelAwait()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
vite_config_default as default
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvVXNlcnMvb3Jpb24vUmVwb3NpdG9yaWVzL09yaW9uL2dncmFwaFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL29yaW9uL1JlcG9zaXRvcmllcy9Pcmlvbi9nZ3JhcGgvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL1VzZXJzL29yaW9uL1JlcG9zaXRvcmllcy9Pcmlvbi9nZ3JhcGgvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0J1xuaW1wb3J0IHdhc20gZnJvbSBcInZpdGUtcGx1Z2luLXdhc21cIjtcbmltcG9ydCB0b3BMZXZlbEF3YWl0IGZyb20gXCJ2aXRlLXBsdWdpbi10b3AtbGV2ZWwtYXdhaXRcIjtcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW1xuICAgIHJlYWN0KCksXG4gICAgd2FzbSgpLFxuICAgIHRvcExldmVsQXdhaXQoKVxuICBdLFxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBb1MsU0FBUyxvQkFBb0I7QUFDalUsT0FBTyxXQUFXO0FBQ2xCLE9BQU8sVUFBVTtBQUNqQixPQUFPLG1CQUFtQjtBQUUxQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxNQUFNO0FBQUEsSUFDTixLQUFLO0FBQUEsSUFDTCxjQUFjO0FBQUEsRUFDaEI7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
|
||||||
Loading…
Reference in New Issue