-
Notifications
You must be signed in to change notification settings - Fork 4
[ENG-1332] Automatic node relationships #730
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
base: main
Are you sure you want to change the base?
Changes from all commits
a0d7f1e
b872e5b
2f53080
428b1f1
9ed219c
964865e
52782dd
5dbdffc
ccffe4a
2151ed9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,11 +19,15 @@ type ModifyNodeFormProps = { | |
| title: string; | ||
| initialFile?: TFile; // for edit mode | ||
| selectedExistingNode?: TFile; | ||
| relationshipTypeId?: string; | ||
| relationshipTargetFile?: TFile; | ||
| isCurrentFileSource?: boolean; | ||
| }) => Promise<void>; | ||
| onCancel: () => void; | ||
| initialTitle?: string; | ||
| initialNodeType?: DiscourseNode; | ||
| initialFile?: TFile; // for edit mode | ||
| currentFile?: TFile; // the file where the node is being created from | ||
| plugin: DiscourseGraphPlugin; | ||
| }; | ||
|
|
||
|
|
@@ -34,6 +38,7 @@ export const ModifyNodeForm = ({ | |
| initialTitle = "", | ||
| initialNodeType, | ||
| initialFile, | ||
| currentFile, | ||
| plugin, | ||
| }: ModifyNodeFormProps) => { | ||
| const isEditMode = !!initialFile; | ||
|
|
@@ -48,6 +53,9 @@ export const ModifyNodeForm = ({ | |
| const [isFocused, setIsFocused] = useState(false); | ||
| const [searchResults, setSearchResults] = useState<TFile[]>([]); | ||
| const [isSearching, setIsSearching] = useState(false); | ||
| const [selectedRelationshipKey, setSelectedRelationshipKey] = useState< | ||
| string | undefined | ||
| >(undefined); | ||
| const queryEngine = useRef(new QueryEngine(plugin.app)); | ||
| const titleInputRef = useRef<HTMLInputElement>(null); | ||
| const popoverRef = useRef<HTMLDivElement>(null); | ||
|
|
@@ -136,6 +144,60 @@ export const ModifyNodeForm = ({ | |
| } | ||
| }, [activeIndex, isOpen]); | ||
|
|
||
| // Determine available relationships based on current file and selected node type | ||
| const availableRelationships = useMemo(() => { | ||
| if (!currentFile || !selectedNodeType || isEditMode) { | ||
| return []; | ||
| } | ||
|
|
||
| const currentFileCache = plugin.app.metadataCache.getFileCache(currentFile); | ||
| const currentNodeTypeId = currentFileCache?.frontmatter?.nodeTypeId; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe assignment of an |
||
|
|
||
| if (!currentNodeTypeId) { | ||
| return []; | ||
| } | ||
|
|
||
| // Find all relations that connect the current node type to the selected node type | ||
| const relevantRelations = plugin.settings.discourseRelations.filter( | ||
| (relation) => | ||
| (relation.sourceId === currentNodeTypeId && | ||
| relation.destinationId === selectedNodeType.id) || | ||
| (relation.sourceId === selectedNodeType.id && | ||
| relation.destinationId === currentNodeTypeId), | ||
| ); | ||
|
|
||
| return relevantRelations | ||
| .map((relation) => { | ||
| const relationType = plugin.settings.relationTypes.find( | ||
| (rt) => rt.id === relation.relationshipTypeId, | ||
| ); | ||
| if (!relationType) return null; | ||
|
|
||
| const isCurrentFileSource = relation.sourceId === currentNodeTypeId; | ||
| return { | ||
| relationTypeId: relation.relationshipTypeId, | ||
| label: isCurrentFileSource | ||
| ? relationType.label | ||
| : relationType.complement, | ||
| isCurrentFileSource, | ||
| uniqueKey: relation.id, | ||
| }; | ||
| }) | ||
| .filter(Boolean) as Array<{ | ||
| relationTypeId: string; | ||
| label: string; | ||
| isCurrentFileSource: boolean; | ||
| uniqueKey: string; | ||
| }>; | ||
| }, [currentFile, selectedNodeType, isEditMode, plugin]); | ||
|
|
||
| useEffect(() => { | ||
| if (selectedRelationshipKey === undefined && availableRelationships[0]) { | ||
| setSelectedRelationshipKey(availableRelationships[0].uniqueKey); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps -- only run when availableRelationships changes | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We generally want to avoid this. Are there any changes we can make so that we can include all the deps? |
||
| }, [availableRelationships]); | ||
|
|
||
| const isFormValid = title.trim() && selectedNodeType; | ||
|
|
||
| const handleSelect = useCallback((file: TFile) => { | ||
|
|
@@ -194,6 +256,7 @@ export const ModifyNodeForm = ({ | |
| setQuery(""); | ||
| setTitle(""); | ||
| } | ||
| setSelectedRelationshipKey(undefined); | ||
| }; | ||
|
|
||
| const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
|
|
@@ -223,11 +286,19 @@ export const ModifyNodeForm = ({ | |
|
|
||
| try { | ||
| setIsSubmitting(true); | ||
| const selectedRel = selectedRelationshipKey | ||
| ? availableRelationships.find( | ||
| (r) => r.uniqueKey === selectedRelationshipKey, | ||
| ) | ||
| : undefined; | ||
| await onSubmit({ | ||
| nodeType: selectedNodeType, | ||
| title: trimmedTitle, | ||
| initialFile, | ||
| selectedExistingNode: selectedExistingNode || undefined, | ||
| relationshipTypeId: selectedRel?.relationTypeId, | ||
| relationshipTargetFile: currentFile || undefined, | ||
| isCurrentFileSource: selectedRel?.isCurrentFileSource, | ||
| }); | ||
| onCancel(); | ||
| } catch (error) { | ||
|
|
@@ -252,11 +323,16 @@ export const ModifyNodeForm = ({ | |
| isEditMode, | ||
| initialFile, | ||
| selectedExistingNode, | ||
| selectedRelationshipKey, | ||
| currentFile, | ||
| availableRelationships, | ||
| ]); | ||
|
|
||
| return ( | ||
| <div> | ||
| <h2>{isEditMode ? "Modify discourse node" : "Create discourse node"}</h2> | ||
| <h2> | ||
| {isEditMode ? "Modify discourse node" : "Create discourse node"} | ||
| </h2> | ||
| <div className="setting-item"> | ||
| <div className="setting-item-name">Type</div> | ||
| <div className="setting-item-control"> | ||
|
|
@@ -371,6 +447,30 @@ export const ModifyNodeForm = ({ | |
| </div> | ||
| </div> | ||
|
|
||
| {availableRelationships.length > 0 && !isEditMode && ( | ||
| <div className="setting-item"> | ||
| <div className="setting-item-name">Relationship</div> | ||
| <div className="setting-item-control"> | ||
| <select | ||
| value={selectedRelationshipKey ?? ""} | ||
| onChange={(e) => | ||
| setSelectedRelationshipKey(e.target.value || undefined) | ||
| } | ||
| disabled={isSubmitting} | ||
| className="w-full" | ||
| > | ||
| <option value="">No relation</option> | ||
| {availableRelationships.map((rel) => ( | ||
| <option key={rel.uniqueKey} value={rel.uniqueKey}> | ||
| {rel.label} "{currentFile?.basename} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move |
||
| " | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="modal-button-container mt-5 flex justify-end gap-2"> | ||
| <button | ||
| type="button" | ||
|
|
@@ -407,10 +507,14 @@ type ModifyNodeModalProps = { | |
| title: string; | ||
| initialFile?: TFile; | ||
| selectedExistingNode?: TFile; | ||
| relationshipTypeId?: string; | ||
| relationshipTargetFile?: TFile; | ||
| isCurrentFileSource?: boolean; | ||
| }) => Promise<void>; | ||
| initialTitle?: string; | ||
| initialNodeType?: DiscourseNode; | ||
| initialFile?: TFile; | ||
| currentFile?: TFile; | ||
| }; | ||
|
|
||
| class ModifyNodeModal extends Modal { | ||
|
|
@@ -420,11 +524,15 @@ class ModifyNodeModal extends Modal { | |
| title: string; | ||
| initialFile?: TFile; | ||
| selectedExistingNode?: TFile; | ||
| relationshipTypeId?: string; | ||
| relationshipTargetFile?: TFile; | ||
| isCurrentFileSource?: boolean; | ||
| }) => Promise<void>; | ||
| private root: Root | null = null; | ||
| private initialTitle?: string; | ||
| private initialNodeType?: DiscourseNode; | ||
| private initialFile?: TFile; | ||
| private currentFile?: TFile; | ||
| private plugin: DiscourseGraphPlugin; | ||
|
|
||
| constructor(app: App, props: ModifyNodeModalProps) { | ||
|
|
@@ -434,6 +542,7 @@ class ModifyNodeModal extends Modal { | |
| this.initialTitle = props.initialTitle; | ||
| this.initialNodeType = props.initialNodeType; | ||
| this.initialFile = props.initialFile; | ||
| this.currentFile = props.currentFile; | ||
| this.plugin = props.plugin; | ||
| } | ||
|
|
||
|
|
@@ -451,6 +560,7 @@ class ModifyNodeModal extends Modal { | |
| initialTitle={this.initialTitle} | ||
| initialNodeType={this.initialNodeType} | ||
| initialFile={this.initialFile} | ||
| currentFile={this.currentFile} | ||
| plugin={this.plugin} | ||
| /> | ||
| </StrictMode>, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,7 +129,10 @@ const convertImageShapeToNode = async ({ | |
| plugin, | ||
| initialNodeType: nodeType, | ||
| initialTitle: "", | ||
| onSubmit: async ({ nodeType: selectedNodeType, title }) => { | ||
| onSubmit: async ({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this changed from prettier format on save? If so, let me know because mine changes it back to the way it was before the PR. |
||
| nodeType: selectedNodeType, | ||
| title, | ||
| }) => { | ||
| try { | ||
| const createdFile = await createDiscourseNodeFile({ | ||
| plugin, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.