import { MutableRef, useEffect, useRef, useState } from "preact/hooks"; import "./PromotedAttributes.css"; import { useNoteContext, useNoteLabel, useUniqueName } from "./react/hooks"; import { Attribute } from "../services/attribute_parser"; import FAttribute from "../entities/fattribute"; import clsx from "clsx"; import { t } from "../services/i18n"; import { DefinitionObject } from "../services/promoted_attribute_definition_parser"; import server from "../services/server"; import FNote from "../entities/fnote"; interface Cell { definitionAttr: FAttribute; definition: DefinitionObject; valueAttr: Attribute; valueName: string; } interface CellProps { note: FNote; componentId: string; cell: Cell, cells: Cell[], shouldFocus: boolean; setCells(cells: Cell[]): void; setCellToFocus(cell: Cell): void; } export default function PromotedAttributes() { const { note, componentId } = useNoteContext(); const [ cells, setCells ] = useState(); const [ viewType ] = useNoteLabel(note, "viewType"); const [ cellToFocus, setCellToFocus ] = useState(); useEffect(() => { if (!note || viewType === "table") { setCells([]); return; } const promotedDefAttrs = note.getPromotedDefinitionAttributes(); const ownedAttributes = note.getOwnedAttributes(); // attrs are not resorted if position changes after the initial load // promoted attrs are sorted primarily by order of definitions, but with multi-valued promoted attrs // the order of attributes is important as well ownedAttributes.sort((a, b) => a.position - b.position); const cells: Cell[] = []; for (const definitionAttr of promotedDefAttrs) { const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation"; const valueName = definitionAttr.name.substr(valueType.length + 1); let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[]; if (valueAttrs.length === 0) { valueAttrs.push({ attributeId: "", type: valueType, name: valueName, value: "" }); } if (definitionAttr.getDefinition().multiplicity === "single") { valueAttrs = valueAttrs.slice(0, 1); } for (const valueAttr of valueAttrs) { const definition = definitionAttr.getDefinition(); cells.push({ definitionAttr, definition, valueAttr, valueName }); } } setCells(cells); }, [ note, viewType ]); return (
{note && cells?.map(cell => )}
); } function PromotedAttributeCell(props: CellProps) { const { valueName, valueAttr, definition, definitionAttr } = props.cell; const inputId = useUniqueName(`value-${valueAttr.name}`); useEffect(() => { if (!props.shouldFocus) return; const inputEl = document.getElementById(inputId); if (inputEl) { inputEl.focus(); } }, [ props.shouldFocus ]); return (
) } function ActionCell() { return (
) } function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) { return (cell.definition.multiplicity === "multi" && { const index = cells.indexOf(cell); const newCell: Cell = { ...cell, valueAttr: { attributeId: "", type: cell.valueAttr.type, name: cell.valueName, value: "" } }; setCells([ ...cells.slice(0, index + 1), newCell, ...cells.slice(index + 1) ]); setCellToFocus(newCell); }} />{' '} { // Remove the attribute from the server if it exists. const { attributeId, type } = cell.valueAttr; const valueName = cell.valueName; if (attributeId) { await server.remove(`notes/${note.noteId}/attributes/${attributeId}`, componentId); } const index = cells.indexOf(cell); const isLastOneOfType = cells.filter(c => c.valueAttr.type === type && c.valueAttr.name === valueName).length < 2; const newOnesToInsert: Cell[] = []; if (isLastOneOfType) { newOnesToInsert.push({ ...cell, valueAttr: { attributeId: "", type: cell.valueAttr.type, name: cell.valueName, value: "" } }) } console.log("Delete at ", index, isLastOneOfType); setCells(cells.toSpliced(index, 1, ...newOnesToInsert)); }} /> ) } function PromotedActionButton({ icon, title, onClick }: { icon: string, title: string, onClick: () => void }) { return ( ) }