Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 116 additions & 13 deletions apps/obsidian/src/components/RelationshipTypeSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,112 @@
import { useState } from "react";
import { useState, useRef, useEffect } from "react";
import { DiscourseRelationType } from "~/types";
import { Notice } from "obsidian";
import { usePlugin } from "./PluginContext";
import generateUid from "~/utils/generateUid";
import { ConfirmationModal } from "./ConfirmationModal";
import {
TLDRAW_COLOR_NAMES,
TLDRAW_COLOR_LABELS,
DEFAULT_TLDRAW_COLOR,
COLOR_PALETTE,
type TldrawColorName,
} from "~/utils/tldrawColors";
import { getContrastColor } from "~/utils/colorUtils";

type ColorPickerProps = {
value: string;
onChange: (color: TldrawColorName) => void;
};

const ColorPicker = ({ value, onChange }: ColorPickerProps) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};

if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}
}, [isOpen]);

const currentColor = value as TldrawColorName;
const bgColor = COLOR_PALETTE[currentColor] ?? COLOR_PALETTE.black;
const textColor = getContrastColor(bgColor ?? DEFAULT_TLDRAW_COLOR);

return (
<div ref={dropdownRef} className="relative" style={{ minWidth: "140px" }}>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className="flex w-full items-center justify-between rounded border px-3 py-2 text-left"
style={{
backgroundColor: bgColor,
color: textColor,
}}
>
<span className="flex items-center gap-2">
<span
className="inline-block rounded-full"
style={{
width: "12px",
height: "12px",
backgroundColor: bgColor,
border: `2px solid ${textColor}`,
}}
/>
{TLDRAW_COLOR_LABELS[currentColor]}
</span>
<span style={{ fontSize: "10px" }}>{isOpen ? "▲" : "▼"}</span>
</button>

{isOpen && (
<div
className="absolute z-50 mt-1 w-full"
style={{
maxHeight: "300px",
overflowY: "auto",
}}
>
{TLDRAW_COLOR_NAMES.map((colorName) => {
const bgColor = COLOR_PALETTE[colorName] ?? COLOR_PALETTE.black;
return (
<button
key={colorName}
type="button"
onClick={() => {
onChange(colorName);
setIsOpen(false);
}}
className="flex w-full flex-row justify-start gap-2 rounded-none px-3 py-2"
>
<span
className="inline-block rounded-full"
style={{
width: "12px",
height: "12px",
backgroundColor: bgColor,
}}
/>
{TLDRAW_COLOR_LABELS[colorName]}
</button>
);
})}
</div>
)}
</div>
);
};

const RelationshipTypeSettings = () => {
const plugin = usePlugin();
Expand All @@ -24,11 +127,14 @@ const RelationshipTypeSettings = () => {
id: newId,
label: "",
complement: "",
color: "#000000",
color: DEFAULT_TLDRAW_COLOR,
};
}

updatedRelationTypes[index][field] = value;
if (field === "color") {
updatedRelationTypes[index].color = value as TldrawColorName;
} else {
updatedRelationTypes[index][field] = value;
}
setRelationTypes(updatedRelationTypes);
setHasUnsavedChanges(true);
};
Expand All @@ -42,7 +148,7 @@ const RelationshipTypeSettings = () => {
id: newId,
label: "",
complement: "",
color: "#000000",
color: DEFAULT_TLDRAW_COLOR,
},
];
setRelationTypes(updatedRelationTypes);
Expand All @@ -53,7 +159,7 @@ const RelationshipTypeSettings = () => {
const relationType = relationTypes[index] || {
label: "Unnamed",
complement: "",
color: "#000000",
color: DEFAULT_TLDRAW_COLOR,
};
const modal = new ConfirmationModal(plugin.app, {
title: "Delete Relation Type",
Expand Down Expand Up @@ -84,7 +190,7 @@ const RelationshipTypeSettings = () => {

const handleSave = async (): Promise<void> => {
for (const relType of relationTypes) {
if (!relType.id || !relType.label || !relType.complement || !relType.color) {
if (!relType.id || !relType.label || !relType.complement) {
new Notice("All fields are required for relation types.");
return;
}
Expand Down Expand Up @@ -132,14 +238,11 @@ const RelationshipTypeSettings = () => {
}
className="flex-1"
/>
<input
type="color"
<ColorPicker
value={relationType.color}
onChange={(e) =>
handleRelationTypeChange(index, "color", e.target.value)
onChange={(color) =>
handleRelationTypeChange(index, "color", color)
}
className="w-12 h-8 rounded border"
title="Relation color"
/>
<button
onClick={() => confirmDeleteRelationType(index)}
Expand Down
2 changes: 2 additions & 0 deletions apps/obsidian/src/components/canvas/DiscourseRelationTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getRelationTypeById } from "~/utils/typeUtils";
import { DiscourseRelationShape } from "./shapes/DiscourseRelationShape";
import { getNodeTypeById } from "~/utils/typeUtils";
import { showToast } from "./utils/toastUtils";
import { DEFAULT_TLDRAW_COLOR } from "~/utils/tldrawColors";

type RelationToolContext = {
plugin: DiscourseGraphPlugin;
Expand Down Expand Up @@ -250,6 +251,7 @@ class Pointing extends StateNode {
props: {
relationTypeId: relationToolContext.relationTypeId,
text: relationType?.label ?? "",
color: relationType?.color ?? DEFAULT_TLDRAW_COLOR,
scale: this.editor.user.getIsDynamicResizeMode()
? 1 / this.editor.getZoomLevel()
: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { getFrontmatterForFile } from "~/components/canvas/shapes/discourseNodeShapeUtils";
import { getRelationTypeById } from "~/utils/typeUtils";
import { showToast } from "~/components/canvas/utils/toastUtils";
import { DEFAULT_TLDRAW_COLOR } from "~/utils/tldrawColors";

type GroupedRelation = {
key: string;
Expand Down Expand Up @@ -377,7 +378,7 @@ export const RelationsPanel = ({
dash: "draw",
size: "m",
fill: "none",
color: "black",
color: relationType?.color ?? DEFAULT_TLDRAW_COLOR,
labelColor: "black",
bend: 0,
// Will be updated by bindings
Expand Down
21 changes: 15 additions & 6 deletions apps/obsidian/src/components/canvas/utils/relationUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import {
TLDefaultSizeStyle,
PI2,
TLArcInfo,
TLDefaultColorThemeColor,
} from "tldraw";
import type {
RelationBindings,
Expand All @@ -77,6 +78,7 @@ import {
DiscourseRelationShape,
DiscourseRelationUtil,
} from "~/components/canvas/shapes/DiscourseRelationShape";
import { DEFAULT_TLDRAW_COLOR } from "~/utils/tldrawColors";

let defaultPixels: { white: string; black: string } | null = null;
let globalRenderIndex = 0;
Expand Down Expand Up @@ -1872,11 +1874,18 @@ export const ArrowSvg = track(function ArrowSvg({
// color: string;
}) {
const editor = useEditor();
// const theme = useDefaultColorTheme();
const theme = useDefaultColorTheme();
const info = getArrowInfo(editor, shape);
const bounds = Box.ZeroFix(editor.getShapeGeometry(shape).bounds);
const bindings = getArrowBindings(editor, shape);

// Ensure color is a valid tldraw color name, fallback to black if not
const colorName =
shape.props.color && theme[shape.props.color as keyof typeof theme]
? (shape.props.color as keyof typeof theme)
: DEFAULT_TLDRAW_COLOR;
const colorHex = (theme[colorName] as TLDefaultColorThemeColor).solid;

const changeIndex = React.useMemo<number>(() => {
return editor.environment.isSafari ? (globalRenderIndex += 1) : 0;
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -2006,7 +2015,7 @@ export const ArrowSvg = track(function ArrowSvg({
</defs>
<g
fill="none"
stroke={shape.props.color}
stroke={colorHex}
strokeWidth={strokeWidth}
strokeLinejoin="round"
strokeLinecap="round"
Expand All @@ -2030,18 +2039,18 @@ export const ArrowSvg = track(function ArrowSvg({
</g>
{as && maskStartArrowhead && shape.props.fill !== "none" && (
<ShapeFill
// theme={theme}
theme={theme}
d={as}
color={shape.props.color}
color={colorHex}
fill={shape.props.fill}
scale={shape.props.scale}
/>
)}
{ae && maskEndArrowhead && shape.props.fill !== "none" && (
<ShapeFill
// theme={theme}
theme={theme}
d={ae}
color={shape.props.color}
color={colorHex}
fill={shape.props.fill}
scale={shape.props.scale}
/>
Expand Down
6 changes: 3 additions & 3 deletions apps/obsidian/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ export const DEFAULT_RELATION_TYPES: Record<string, DiscourseRelationType> = {
id: generateUid("relation"),
label: "supports",
complement: "is supported by",
color: "#099268",
color: "green",
},
opposes: {
id: generateUid("relation"),
label: "opposes",
complement: "is opposed by",
color: "#e03131",
color: "red",
},
informs: {
id: generateUid("relation"),
label: "informs",
complement: "is informed by",
color: "#adb5bd",
color: "grey",
},
};

Expand Down
3 changes: 2 additions & 1 deletion apps/obsidian/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TFile } from "obsidian";
import { TldrawColorName } from "./utils/tldrawColors";

export type DiscourseNode = {
id: string;
Expand All @@ -16,7 +17,7 @@ export type DiscourseRelationType = {
id: string;
label: string;
complement: string;
color: string;
color: TldrawColorName;
};

export type DiscourseRelation = {
Expand Down
60 changes: 60 additions & 0 deletions apps/obsidian/src/utils/tldrawColors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Tldraw color names that can be used for relation types.
* These match the defaultColorNames from tldraw's TLColorStyle.
*/
export const TLDRAW_COLOR_NAMES = [
"black",
"grey",
"light-violet",
"violet",
"blue",
"light-blue",
"yellow",
"orange",
"green",
"light-green",
"light-red",
"red",
"white",
] as const;

export type TldrawColorName = (typeof TLDRAW_COLOR_NAMES)[number];

/**
* Human-readable labels for tldraw color names
*/
export const TLDRAW_COLOR_LABELS: Record<TldrawColorName, string> = {
black: "Black",
grey: "Grey",
"light-violet": "Light Violet",
violet: "Violet",
blue: "Blue",
"light-blue": "Light Blue",
yellow: "Yellow",
orange: "Orange",
green: "Green",
"light-green": "Light Green",
"light-red": "Light Red",
red: "Red",
white: "White",
};

export const DEFAULT_TLDRAW_COLOR: TldrawColorName = "black";

// from @tldraw/editor/editor.css
/* eslint-disable @typescript-eslint/naming-convention */
export const COLOR_PALETTE: Record<string, string> = {
black: "#1d1d1d",
blue: "#4263eb",
green: "#099268",
grey: "#adb5bd",
"light-blue": "#4dabf7",
"light-green": "#40c057",
"light-red": "#ff8787",
"light-violet": "#e599f7",
orange: "#f76707",
red: "#e03131",
violet: "#ae3ec9",
white: "#ffffff",
yellow: "#ffc078",
};