diff --git a/docs/query-builder.md b/docs/query-builder.md index 9a9c91e..c8e873f 100644 --- a/docs/query-builder.md +++ b/docs/query-builder.md @@ -208,6 +208,10 @@ This will allow you to search across all columns, case-insensitive. https://github.com/RoamJS/query-builder/assets/3792666/6499147d-4d03-4767-b7e7-d1ea925697b5 +### Hide Columns + +Click on the Menu button, then **Hide Columns** to toggle which columns are visible in the results table. + ### View Type Each column also has a view type. Choosing a view type will change how the cell is displayed in the table. diff --git a/src/components/ResultsView.tsx b/src/components/ResultsView.tsx index 098f6d0..6f5fd2c 100644 --- a/src/components/ResultsView.tsx +++ b/src/components/ResultsView.tsx @@ -7,6 +7,7 @@ import { Popover, Menu, MenuItem, + Checkbox, Switch, Intent, Label, @@ -216,7 +217,7 @@ const SUPPORTED_LAYOUTS = [ }, ] as const; const settingsById = Object.fromEntries( - SUPPORTED_LAYOUTS.map((l) => [l.id, l.settings]) + SUPPORTED_LAYOUTS.map((l) => [l.id, l.settings]), ); type ResultsViewComponent = (props: { @@ -278,7 +279,7 @@ const ResultsView: ResultsViewComponent = ({ const extensionAPI = useExtensionAPI(); const settings = useMemo( () => parseResultSettings(parentUid, columns, extensionAPI), - [parentUid] + [parentUid], ); const [activeSort, setActiveSort] = useState(settings.activeSort); const resultViewSetActiveSort = React.useCallback( @@ -298,10 +299,26 @@ const ResultsView: ResultsViewComponent = ({ parentUid: sortsNode.uid, node, order, - }) + }), ); }, - [setActiveSort, preventSavingSettings, parentUid] + [setActiveSort, preventSavingSettings, parentUid], + ); + + const resultViewSetHiddenColumns = React.useCallback( + (cols: string[]) => { + setHiddenColumns(cols); + if (preventSavingSettings) return; + const hiddenNode = getSubTree({ + key: "hiddenColumns", + parentUid: settings.resultNodeUid, + }); + hiddenNode.children.forEach((c) => deleteBlock(c.uid)); + cols.forEach((c, order) => + createBlock({ parentUid: hiddenNode.uid, node: { text: c }, order }), + ); + }, + [setHiddenColumns, preventSavingSettings, parentUid], ); // @deprecated - use columnFilters @@ -312,6 +329,7 @@ const ResultsView: ResultsViewComponent = ({ const pageSizeTimeoutRef = useRef(0); const [views, setViews] = useState(settings.views); const [columnFilters, setColumnFilters] = useState(settings.columnFilters); + const [hiddenColumns, setHiddenColumns] = useState(settings.hiddenColumns); const [searchFilter, setSearchFilter] = useState(settings.searchFilter); const [showInterface, setShowInterface] = useState(settings.showInterface); const [showMenuIcons, setShowMenuIcons] = useState(false); @@ -359,20 +377,33 @@ const ResultsView: ResultsViewComponent = ({ const [isEditLayout, setIsEditLayout] = useState(false); const [isEditColumnSort, setIsEditColumnSort] = useState(false); const [isEditColumnFilter, setIsEditColumnFilter] = useState(false); + const [isEditHiddenColumns, setIsEditHiddenColumns] = useState(false); const [isEditSearchFilter, setIsEditSearchFilter] = useState(false); const [layout, setLayout] = useState(settings.layout); const layoutMode = useMemo( () => (Array.isArray(layout.mode) ? layout.mode[0] : layout.mode), - [layout] + [layout], + ); + const visibleColumns = useMemo( + () => columns.filter((c) => !hiddenColumns.includes(c.key)), + [columns, hiddenColumns], ); const isMenuIconDirty = useMemo( () => searchFilter || columnFilters.length || + hiddenColumns.length || random.count || (activeSort.length && layout.mode !== "table"), // indicator is on ResultHeader - [searchFilter, columnFilters, random, activeSort, layout.mode] + [ + searchFilter, + columnFilters, + hiddenColumns, + random, + activeSort, + layout.mode, + ], ); const onViewChange = (view: (typeof views)[number], i: number) => { const newViews = views.map((v, j) => (i === j ? view : v)); @@ -397,12 +428,12 @@ const ResultsView: ResultsViewComponent = ({ node, order, parentUid: viewsNode.uid, - }) + }), ); }; const debounceRef = useRef(0); const showColumnViewOptions = views.some( - (view) => VIEWS[view.mode]?.value === true + (view) => VIEWS[view.mode]?.value === true, ); return ( @@ -476,6 +507,7 @@ const ResultsView: ResultsViewComponent = ({ setIsEditColumnFilter(false); setIsEditViews(false); setIsEditColumnSort(false); + setIsEditHiddenColumns(false); }} autoFocus={false} enforceFocus={false} @@ -636,8 +668,8 @@ const ResultsView: ResultsViewComponent = ({ const descending = value === "Descending"; resultViewSetActiveSort( activeSort.map((s) => - s.key === key ? { key, descending } : s - ) + s.key === key ? { key, descending } : s, + ), ); }} /> @@ -646,7 +678,7 @@ const ResultsView: ResultsViewComponent = ({ minimal onClick={() => { resultViewSetActiveSort( - activeSort.filter((s) => s.key !== key) + activeSort.filter((s) => s.key !== key), ); }} /> @@ -697,8 +729,8 @@ const ResultsView: ResultsViewComponent = ({ onItemSelect={(newKey) => { setColumnFilters( columnFilters.map((f) => - f.uid === uid ? { ...f, key: newKey } : f - ) + f.uid === uid ? { ...f, key: newKey } : f, + ), ); updateBlock({ uid, text: newKey }); }} @@ -706,14 +738,14 @@ const ResultsView: ResultsViewComponent = ({ c.id + (c) => c.id, )} activeItem={type} onItemSelect={(newType) => { setColumnFilters( columnFilters.map((f) => - f.uid === uid ? { ...f, type: newType } : f - ) + f.uid === uid ? { ...f, type: newType } : f, + ), ); setInputSetting({ blockUid: uid, @@ -727,7 +759,7 @@ const ResultsView: ResultsViewComponent = ({ minimal onClick={() => { setColumnFilters( - columnFilters.filter((f) => f.uid !== uid) + columnFilters.filter((f) => f.uid !== uid), ); deleteBlock(uid); }} @@ -758,8 +790,8 @@ const ResultsView: ResultsViewComponent = ({ new Set( results .map((r) => r[key].toString()) - .filter((v) => !value.includes(v)) - ) + .filter((v) => !value.includes(v)), + ), )} onRemove={(newValue) => { setColumnFilters( @@ -768,11 +800,11 @@ const ResultsView: ResultsViewComponent = ({ ? { ...f, value: value.filter( - (v) => v !== newValue + (v) => v !== newValue, ), } - : f - ) + : f, + ), ); setInputSettings({ blockUid: uid, @@ -785,8 +817,8 @@ const ResultsView: ResultsViewComponent = ({ columnFilters.map((f) => f.uid === uid ? { ...f, value: [...value, newValue] } - : f - ) + : f, + ), ); setInputSettings({ blockUid: uid, @@ -806,8 +838,8 @@ const ResultsView: ResultsViewComponent = ({ columnFilters.map((f) => f.uid === uid ? { ...f, value: [newValue] } - : f - ) + : f, + ), ); setInputSettings({ blockUid: uid, @@ -858,6 +890,33 @@ const ResultsView: ResultsViewComponent = ({ /> + ) : isEditHiddenColumns ? ( +
+ setIsEditHiddenColumns(false)} + text="Hide Columns" + /> +
+ {columns.map((c) => { + const checked = !hiddenColumns.includes(c.key); + return ( + { + const isChecked = (e.target as HTMLInputElement) + .checked; + const newCols = isChecked + ? hiddenColumns.filter((h) => h !== c.key) + : [...hiddenColumns, c.key]; + resultViewSetHiddenColumns(newCols); + }} + /> + ); + })} +
+
) : isEditViews ? (
@@ -975,6 +1034,16 @@ const ResultsView: ResultsViewComponent = ({ setIsEditViews(true); }} /> + { + setIsEditHiddenColumns(true); + }} + /> { const location = getUids( containerRef.current?.closest( - ".roam-block" - ) as HTMLDivElement + ".roam-block", + ) as HTMLDivElement, ); window.roamAlphaAPI.ui.setBlockFocusAndSelection({ location: { @@ -1063,7 +1132,7 @@ const ResultsView: ResultsViewComponent = ({ onClick={() => { const getTextFromTreeToPaste = ( items: RoamBasicNode[], - indentLevel = 0 + indentLevel = 0, ): string => { const indentation = " ".repeat(indentLevel); @@ -1073,7 +1142,7 @@ const ResultsView: ResultsViewComponent = ({ item.children.length > 0 ? getTextFromTreeToPaste( item.children, - indentLevel + 1 + indentLevel + 1, ) : ""; return `${indentation}- ${item.text}\n${childrenText}`; @@ -1082,7 +1151,7 @@ const ResultsView: ResultsViewComponent = ({ }; const tree = getBasicTreeByParentUid(parentUid); navigator.clipboard.writeText( - "- {{query block}}\n" + getTextFromTreeToPaste(tree, 1) + "- {{query block}}\n" + getTextFromTreeToPaste(tree, 1), ); renderToast({ id: "query-copy", @@ -1130,7 +1199,7 @@ const ResultsView: ResultsViewComponent = ({ layoutMode === "table" ? ( ) : layoutMode === "bar" ? ( ) : layoutMode === "timeline" ? ( @@ -1167,7 +1236,7 @@ const ResultsView: ResultsViewComponent = ({ data={allProcessedResults} layout={layout} onQuery={() => onRefresh(true)} - resultKeys={columns} + resultKeys={visibleColumns} parentUid={parentUid} views={views} activeSort={activeSort} diff --git a/src/utils/parseResultSettings.ts b/src/utils/parseResultSettings.ts index f7e0a3c..285dd48 100644 --- a/src/utils/parseResultSettings.ts +++ b/src/utils/parseResultSettings.ts @@ -18,7 +18,7 @@ export type Views = { }[]; const getFilterEntries = ( - n: Pick + n: Pick, ): [string, Filters][] => n.children.map((c) => [ c.text, @@ -26,15 +26,15 @@ const getFilterEntries = ( includes: { values: new Set( getSubTree({ tree: c.children, key: "includes" }).children.map( - (t) => t.text - ) + (t) => t.text, + ), ), }, excludes: { values: new Set( getSubTree({ tree: c.children, key: "excludes" }).children.map( - (t) => t.text - ) + (t) => t.text, + ), ), }, uid: c.uid, @@ -48,18 +48,18 @@ const getSettings = (extensionAPI?: OnloadArgs["extensionAPI"]) => { (extensionAPI?.settings.get("default-filters") as Record< string, StoredFilters - >) || {} + >) || {}, ).map(([k, v]) => [ k, { includes: Object.fromEntries( - Object.entries(v.includes || {}).map(([k, v]) => [k, new Set(v)]) + Object.entries(v.includes || {}).map(([k, v]) => [k, new Set(v)]), ), excludes: Object.fromEntries( - Object.entries(v.excludes || {}).map(([k, v]) => [k, new Set(v)]) + Object.entries(v.excludes || {}).map(([k, v]) => [k, new Set(v)]), ), }, - ]) + ]), ), globalPageSize: Number(extensionAPI?.settings.get("default-page-size")) || 10, @@ -70,7 +70,7 @@ const parseResultSettings = ( // TODO - this should be the resultNode uid parentUid: string, columns: Column[], - extensionAPI?: OnloadArgs["extensionAPI"] + extensionAPI?: OnloadArgs["extensionAPI"], ) => { const { globalFiltersData, globalPageSize } = getSettings(extensionAPI); const tree = getBasicTreeByParentUid(parentUid); @@ -89,6 +89,10 @@ const parseResultSettings = ( tree: resultNode.children, key: "interface", }); + const hiddenColumnsNode = getSubTree({ + tree: resultNode.children, + key: "hiddenColumns", + }); const filterEntries = getFilterEntries(filtersNode); const savedFilterData = filterEntries.length ? Object.fromEntries(filterEntries) @@ -108,7 +112,7 @@ const parseResultSettings = ( mode: c.children[0]?.text, value: c.children[0]?.children?.[0]?.text || "", }, - ]) + ]), ); const layoutNode = getSubTree({ tree: resultNode.children, @@ -122,7 +126,7 @@ const parseResultSettings = ( c.children.length === 1 ? c.children[0].text : c.children.map((cc) => cc.text), - ]) + ]), ); if (!layout.mode) layout.mode = @@ -145,7 +149,7 @@ const parseResultSettings = ( includes: { values: new Set() }, excludes: { values: new Set() }, }, - ]) + ]), ), columnFilters: columnFiltersNode.children.map((c) => { return { @@ -155,6 +159,7 @@ const parseResultSettings = ( type: getSettingValueFromTree({ tree: c.children, key: "type" }), }; }), + hiddenColumns: hiddenColumnsNode.children.map((c) => c.text), views: columns.map(({ key: column }) => ({ column, mode: