-
Notifications
You must be signed in to change notification settings - Fork 2
[ENG-42] Create setting to define a relationship #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mdroidian
merged 25 commits into
DiscourseGraphs:main
from
trangdoan982:trang/relationship-type-def
Mar 21, 2025
Merged
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
8d968e1
Rename trang.JPG to trang.jpg
mdroidian d4c636b
remove trang.jpg
mdroidian 95d426f
curr progress
trangdoan982 4a72b97
finished current settings
trangdoan982 c08d23d
small update
trangdoan982 34f6899
setting for hotkey
trangdoan982 f2de373
node instantiation finished
trangdoan982 833e246
address PR comments
trangdoan982 2ee0d28
add description
trangdoan982 9ae208c
address PR comments
trangdoan982 5eedbc4
fix the NodeType validation
trangdoan982 435233d
address PR review
trangdoan982 d9dc433
add Save button for new changes
trangdoan982 de5242e
types defined and basic settings up
trangdoan982 e00c251
fix the bug. now relationship is updated
trangdoan982 2c44ed9
change the style to show bidirectional relations visually
trangdoan982 4098e58
create plugin as context instead of passing in props
trangdoan982 9ff13f5
new type definitions + settings finished
trangdoan982 88466b5
rename
trangdoan982 3c8bdcd
Merge branch 'main' into trang/relationship-type-def
trangdoan982 0dbb188
check for duplicates
trangdoan982 c42d587
address PR comments
trangdoan982 bbc1871
Merge branch 'DiscourseGraphs:main' into trang/relationship-type-def
trangdoan982 585b631
current progress
trangdoan982 c4f591b
confirm before delete
trangdoan982 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| import { useState } from "react"; | ||
| import { | ||
| validateAllNodes, | ||
| validateNodeFormat, | ||
| validateNodeName, | ||
| } from "../utils/validateNodeType"; | ||
| import { usePlugin } from "./PluginContext"; | ||
| import { Notice } from "obsidian"; | ||
| import generateUid from "~/utils/generateUid"; | ||
| import { DiscourseNode } from "~/types"; | ||
|
|
||
| const NodeTypeSettings = () => { | ||
| const plugin = usePlugin(); | ||
| const [nodeTypes, setNodeTypes] = useState( | ||
| () => plugin.settings.nodeTypes ?? [], | ||
| ); | ||
| const [formatErrors, setFormatErrors] = useState<Record<number, string>>({}); | ||
| const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); | ||
|
|
||
| const handleNodeTypeChange = async ( | ||
| index: number, | ||
| field: keyof DiscourseNode, | ||
| value: string, | ||
| ): Promise<void> => { | ||
| const updatedNodeTypes = [...nodeTypes]; | ||
| if (!updatedNodeTypes[index]) { | ||
| const newId = generateUid("node"); | ||
| updatedNodeTypes[index] = { id: newId, name: "", format: "" }; | ||
| } | ||
|
|
||
| updatedNodeTypes[index][field] = value; | ||
|
|
||
| if (field === "format") { | ||
| const { isValid, error } = validateNodeFormat(value, updatedNodeTypes); | ||
| if (!isValid) { | ||
| setFormatErrors((prev) => ({ | ||
| ...prev, | ||
| [index]: error || "Invalid format", | ||
| })); | ||
| } else { | ||
| setFormatErrors((prev) => { | ||
| const newErrors = { ...prev }; | ||
| delete newErrors[index]; | ||
| return newErrors; | ||
| }); | ||
| } | ||
| } else if (field === "name") { | ||
| const nameValidation = validateNodeName(value, updatedNodeTypes); | ||
| if (!nameValidation.isValid) { | ||
| setFormatErrors((prev) => ({ | ||
| ...prev, | ||
| [index]: nameValidation.error || "Invalid name", | ||
| })); | ||
| } else { | ||
| setFormatErrors((prev) => { | ||
| const newErrors = { ...prev }; | ||
| delete newErrors[index]; | ||
| return newErrors; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| setNodeTypes(updatedNodeTypes); | ||
| setHasUnsavedChanges(true); | ||
| }; | ||
|
|
||
| const handleAddNodeType = (): void => { | ||
| const newId = generateUid("node"); | ||
| const updatedNodeTypes = [ | ||
| ...nodeTypes, | ||
| { | ||
| id: newId, | ||
| name: "", | ||
| format: "", | ||
| }, | ||
| ]; | ||
| setNodeTypes(updatedNodeTypes); | ||
| setHasUnsavedChanges(true); | ||
| }; | ||
|
|
||
| const handleDeleteNodeType = async (index: number): Promise<void> => { | ||
| const nodeId = nodeTypes[index]?.id; | ||
| const isUsed = plugin.settings.discourseRelations?.some( | ||
| (rel) => rel.sourceId === nodeId || rel.destinationId === nodeId, | ||
| ); | ||
|
|
||
| if (isUsed) { | ||
| new Notice( | ||
| "Cannot delete this node type as it is used in one or more relations.", | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| const updatedNodeTypes = nodeTypes.filter((_, i) => i !== index); | ||
| setNodeTypes(updatedNodeTypes); | ||
| plugin.settings.nodeTypes = updatedNodeTypes; | ||
| await plugin.saveSettings(); | ||
| if (formatErrors[index]) { | ||
| setFormatErrors((prev) => { | ||
| const newErrors = { ...prev }; | ||
| delete newErrors[index]; | ||
| return newErrors; | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const handleSave = async (): Promise<void> => { | ||
| const { hasErrors, errorMap } = validateAllNodes(nodeTypes); | ||
|
|
||
| if (hasErrors) { | ||
| setFormatErrors(errorMap); | ||
| new Notice("Please fix the errors before saving"); | ||
| return; | ||
| } | ||
| plugin.settings.nodeTypes = nodeTypes; | ||
| await plugin.saveSettings(); | ||
| new Notice("Node types saved"); | ||
| setHasUnsavedChanges(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="discourse-node-types"> | ||
| <h3>Node Types</h3> | ||
| {nodeTypes.map((nodeType, index) => ( | ||
| <div key={index} className="setting-item"> | ||
| <div | ||
| style={{ display: "flex", flexDirection: "column", width: "100%" }} | ||
| > | ||
| <div style={{ display: "flex", gap: "10px" }}> | ||
| <input | ||
| type="text" | ||
| placeholder="Name" | ||
| value={nodeType.name} | ||
| onChange={(e) => | ||
| handleNodeTypeChange(index, "name", e.target.value) | ||
| } | ||
| style={{ flex: 1 }} | ||
| /> | ||
| <input | ||
| type="text" | ||
| placeholder="Format (e.g., [CLM] - {content})" | ||
| value={nodeType.format} | ||
| onChange={(e) => | ||
| handleNodeTypeChange(index, "format", e.target.value) | ||
| } | ||
| style={{ flex: 2 }} | ||
| /> | ||
| <button | ||
| onClick={() => handleDeleteNodeType(index)} | ||
| className="mod-warning" | ||
| > | ||
| Delete | ||
| </button> | ||
| </div> | ||
| {formatErrors[index] && ( | ||
| <div | ||
| style={{ | ||
| color: "var(--text-error)", | ||
| fontSize: "12px", | ||
| marginTop: "4px", | ||
| }} | ||
| > | ||
| {formatErrors[index]} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ))} | ||
| <div className="setting-item"> | ||
| <div style={{ display: "flex", gap: "10px" }}> | ||
| <button onClick={handleAddNodeType}>Add Node Type</button> | ||
| <button | ||
| onClick={handleSave} | ||
| className={hasUnsavedChanges ? "mod-cta" : ""} | ||
| disabled={ | ||
| !hasUnsavedChanges || Object.keys(formatErrors).length > 0 | ||
| } | ||
| > | ||
| Save Changes | ||
| </button> | ||
| </div> | ||
| </div> | ||
| {hasUnsavedChanges && ( | ||
| <div style={{ marginTop: "8px", color: "var(--text-muted)" }}> | ||
| You have unsaved changes | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default NodeTypeSettings; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import React, { createContext, useContext, ReactNode } from "react"; | ||
| import type DiscourseGraphPlugin from "../index"; | ||
|
|
||
| export const PluginContext = createContext<DiscourseGraphPlugin | undefined>( | ||
| undefined, | ||
| ); | ||
|
|
||
| export const usePlugin = (): DiscourseGraphPlugin => { | ||
| const plugin = useContext(PluginContext); | ||
| if (!plugin) { | ||
| throw new Error("usePlugin must be used within a PluginProvider"); | ||
| } | ||
| return plugin; | ||
| }; | ||
|
|
||
| export const PluginProvider = ({ | ||
| plugin, | ||
| children, | ||
| }: { | ||
| plugin: DiscourseGraphPlugin; | ||
| children: ReactNode; | ||
| }) => { | ||
| return ( | ||
| <PluginContext.Provider value={plugin}>{children}</PluginContext.Provider> | ||
| ); | ||
| }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.