mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-07 05:33:27 +00:00
[ENG-624] Explorer order by (#831)
added order by + fixed explorer padding situation
This commit is contained in:
parent
0ba5874947
commit
3880428596
|
@ -42,12 +42,20 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
||||||
#[specta(inline)]
|
#[specta(inline)]
|
||||||
enum Ordering {
|
enum Ordering {
|
||||||
Name(bool),
|
Name(bool),
|
||||||
|
SizeInBytes(bool),
|
||||||
|
DateCreated(bool),
|
||||||
|
DateModified(bool),
|
||||||
|
DateIndexed(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ordering {
|
impl Ordering {
|
||||||
fn get_direction(&self) -> Direction {
|
fn get_direction(&self) -> Direction {
|
||||||
match self {
|
match self {
|
||||||
Self::Name(v) => v,
|
Self::Name(v) => v,
|
||||||
|
Self::SizeInBytes(v) => v,
|
||||||
|
Self::DateCreated(v) => v,
|
||||||
|
Self::DateModified(v) => v,
|
||||||
|
Self::DateIndexed(v) => v,
|
||||||
}
|
}
|
||||||
.then_some(Direction::Asc)
|
.then_some(Direction::Asc)
|
||||||
.unwrap_or(Direction::Desc)
|
.unwrap_or(Direction::Desc)
|
||||||
|
@ -57,6 +65,10 @@ pub fn mount() -> AlphaRouter<Ctx> {
|
||||||
use file_path::*;
|
use file_path::*;
|
||||||
match self {
|
match self {
|
||||||
Self::Name(_) => name::order(dir),
|
Self::Name(_) => name::order(dir),
|
||||||
|
Self::SizeInBytes(_) => size_in_bytes::order(dir),
|
||||||
|
Self::DateCreated(_) => date_created::order(dir),
|
||||||
|
Self::DateModified(_) => date_modified::order(dir),
|
||||||
|
Self::DateIndexed(_) => date_indexed::order(dir),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
@ -71,6 +72,8 @@ const GridViewItem = memo(({ data, selected, index, ...props }: GridViewItemProp
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LEFT_PADDING = 14;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const explorerStore = useExplorerStore();
|
const explorerStore = useExplorerStore();
|
||||||
const { data, scrollRef, onLoadMore, hasNextPage, isFetchingNextPage } =
|
const { data, scrollRef, onLoadMore, hasNextPage, isFetchingNextPage } =
|
||||||
|
@ -107,7 +110,7 @@ export default () => {
|
||||||
|
|
||||||
function handleWindowResize() {
|
function handleWindowResize() {
|
||||||
if (scrollRef.current) {
|
if (scrollRef.current) {
|
||||||
setWidth(scrollRef.current.offsetWidth);
|
setWidth(scrollRef.current.offsetWidth - LEFT_PADDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +189,8 @@ export default () => {
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
style={{
|
style={{
|
||||||
height: `${rowVirtualizer.getTotalSize()}px`
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||||
|
marginLeft: `${LEFT_PADDING - 4}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{virtualRows.map((virtualRow) => (
|
{virtualRows.map((virtualRow) => (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnSizingState,
|
ColumnSizingState,
|
||||||
|
@ -162,12 +163,12 @@ export default () => {
|
||||||
return aName === bName
|
return aName === bName
|
||||||
? 0
|
? 0
|
||||||
: aName > bName
|
: aName > bName
|
||||||
? desc
|
? desc
|
||||||
? 1
|
? 1
|
||||||
: -1
|
: -1
|
||||||
: desc
|
: desc
|
||||||
? -1
|
? -1
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return aDate > bDate ? 1 : -1;
|
return aDate > bDate ? 1 : -1;
|
||||||
|
@ -225,13 +226,13 @@ export default () => {
|
||||||
...sizing,
|
...sizing,
|
||||||
...(scrollWidth && nameWidth
|
...(scrollWidth && nameWidth
|
||||||
? {
|
? {
|
||||||
Name:
|
Name:
|
||||||
nameWidth +
|
nameWidth +
|
||||||
scrollWidth -
|
scrollWidth -
|
||||||
paddingX * 2 -
|
paddingX * 2 -
|
||||||
scrollBarWidth -
|
scrollBarWidth -
|
||||||
tableLength
|
tableLength
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -360,8 +361,8 @@ export default () => {
|
||||||
i === 0
|
i === 0
|
||||||
? size + paddingX
|
? size + paddingX
|
||||||
: i === headerGroup.headers.length - 1
|
: i === headerGroup.headers.length - 1
|
||||||
? size - paddingX
|
? size - paddingX
|
||||||
: size
|
: size
|
||||||
}}
|
}}
|
||||||
onClick={header.column.getToggleSortingHandler()}
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
>
|
>
|
||||||
|
@ -381,16 +382,16 @@ export default () => {
|
||||||
{(i !== headerGroup.headers.length - 1 ||
|
{(i !== headerGroup.headers.length - 1 ||
|
||||||
(i === headerGroup.headers.length - 1 &&
|
(i === headerGroup.headers.length - 1 &&
|
||||||
!locked)) && (
|
!locked)) && (
|
||||||
<div
|
<div
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
setLocked(false);
|
setLocked(false);
|
||||||
header.getResizeHandler()(e);
|
header.getResizeHandler()(e);
|
||||||
}}
|
}}
|
||||||
onTouchStart={header.getResizeHandler()}
|
onTouchStart={header.getResizeHandler()}
|
||||||
className="absolute right-0 h-[70%] w-2 cursor-col-resize border-r border-app-line/50"
|
className="absolute right-0 h-[70%] w-2 cursor-col-resize border-r border-app-line/50"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { ArrowsOutSimple } from 'phosphor-react';
|
import { ArrowsOutSimple } from 'phosphor-react';
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { RadixCheckbox, Select, SelectOption, Slider, tw } from '@sd/ui';
|
import { RadixCheckbox, Select, SelectOption, Slider, tw } from '@sd/ui';
|
||||||
import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
import { ExplorerDirection, ExplorerOrderByKeys, getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
||||||
import { getExplorerConfigStore, useExplorerConfigStore } from '~/hooks/useExplorerConfigStore';
|
import { getExplorerConfigStore, useExplorerConfigStore } from '~/hooks/useExplorerConfigStore';
|
||||||
|
|
||||||
const Heading = tw.div`text-ink-dull text-xs font-semibold`;
|
const Heading = tw.div`text-ink-dull text-xs font-semibold`;
|
||||||
const Subheading = tw.div`text-ink-dull mb-1 text-xs font-medium`;
|
const Subheading = tw.div`text-ink-dull mb-1 text-xs font-medium`;
|
||||||
|
|
||||||
const sortOptions = {
|
const sortOptions: Record<ExplorerOrderByKeys, string> = {
|
||||||
|
none: 'None',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
kind: 'Kind',
|
sizeInBytes: 'Size',
|
||||||
favorite: 'Favorite',
|
dateCreated: 'Date created',
|
||||||
date_created: 'Date Created',
|
dateModified: 'Date modified',
|
||||||
date_modified: 'Date Modified',
|
dateIndexed: 'Date indexed'
|
||||||
date_last_opened: 'Date Last Opened'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [sortBy, setSortBy] = useState('name');
|
|
||||||
const [stackBy, setStackBy] = useState('kind');
|
|
||||||
|
|
||||||
const explorerStore = useExplorerStore();
|
const explorerStore = useExplorerStore();
|
||||||
const explorerConfig = useExplorerConfigStore();
|
const explorerConfig = useExplorerConfigStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 ">
|
<div className="p-4">
|
||||||
<Subheading>Item size</Subheading>
|
<Subheading>Item size</Subheading>
|
||||||
{explorerStore.layoutMode === 'media' ? (
|
{explorerStore.layoutMode === 'media' ? (
|
||||||
<Slider
|
<Slider
|
||||||
|
@ -52,7 +49,7 @@ export default () => {
|
||||||
<div className="my-2 mt-4 grid grid-cols-2 gap-2">
|
<div className="my-2 mt-4 grid grid-cols-2 gap-2">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Subheading>Sort by</Subheading>
|
<Subheading>Sort by</Subheading>
|
||||||
<Select value={sortBy} size="sm" onChange={setSortBy}>
|
<Select value={explorerStore.orderBy} size="sm" className='w-full' onChange={(value) => getExplorerStore().orderBy = value as ExplorerOrderByKeys}>
|
||||||
{Object.entries(sortOptions).map(([value, text]) => (
|
{Object.entries(sortOptions).map(([value, text]) => (
|
||||||
<SelectOption key={value} value={value}>
|
<SelectOption key={value} value={value}>
|
||||||
{text}
|
{text}
|
||||||
|
@ -61,11 +58,10 @@ export default () => {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Subheading>Stack by</Subheading>
|
<Subheading>Direction</Subheading>
|
||||||
<Select value={stackBy} size="sm" onChange={setStackBy}>
|
<Select value={explorerStore.orderByDirection} size="sm" className='w-full' onChange={(value) => getExplorerStore().orderByDirection = value as ExplorerDirection}>
|
||||||
<SelectOption value="kind">Kind</SelectOption>
|
<SelectOption value="asc">Asc</SelectOption>
|
||||||
<SelectOption value="location">Location</SelectOption>
|
<SelectOption value="desc">Desc</SelectOption>
|
||||||
<SelectOption value="node">Node</SelectOption>
|
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default memo((props: Props) => {
|
||||||
ref={props.scrollRef || scrollRef}
|
ref={props.scrollRef || scrollRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'custom-scroll explorer-scroll h-screen',
|
'custom-scroll explorer-scroll h-screen',
|
||||||
layoutMode === 'grid' && 'overflow-x-hidden pl-4',
|
layoutMode === 'grid' && 'overflow-x-hidden',
|
||||||
props.viewClassName
|
props.viewClassName
|
||||||
)}
|
)}
|
||||||
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ExplorerItem, ObjectKind, ObjectKindKey, isObject, isPath } from '@sd/client';
|
import { ExplorerItem, ObjectKind, ObjectKindKey, Ordering, isObject, isPath } from '@sd/client';
|
||||||
import { useZodSearchParams } from '~/hooks';
|
import { useExplorerStore, useZodSearchParams } from '~/hooks';
|
||||||
|
|
||||||
export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail: boolean) {
|
export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail?: boolean) {
|
||||||
const objectData = getItemObject(data);
|
const objectData = getItemObject(data);
|
||||||
const filePath = getItemFilePath(data);
|
const filePath = getItemFilePath(data);
|
||||||
|
|
||||||
|
@ -15,6 +15,14 @@ export function getExplorerItemData(data: ExplorerItem, hasNewThumbnail: boolean
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useExplorerOrder(): Ordering | undefined {
|
||||||
|
const explorerStore = useExplorerStore();
|
||||||
|
|
||||||
|
if (explorerStore.orderBy === 'none') return undefined;
|
||||||
|
|
||||||
|
return { [explorerStore.orderBy]: explorerStore.orderByDirection === 'asc' } as Ordering;
|
||||||
|
}
|
||||||
|
|
||||||
export function getItemObject(data: ExplorerItem) {
|
export function getItemObject(data: ExplorerItem) {
|
||||||
return isObject(data) ? data.item : data.item.object;
|
return isObject(data) ? data.item : data.item.object;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const Component = () => {
|
||||||
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
style={{ paddingTop: TOP_BAR_HEIGHT }}
|
||||||
>
|
>
|
||||||
<PageContext.Provider value={{ ref }}>
|
<PageContext.Provider value={{ ref }}>
|
||||||
<div className="flex h-screen w-full flex-col p-5">
|
<div className="flex h-screen w-full flex-col">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</PageContext.Provider>
|
</PageContext.Provider>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { getExplorerStore, useExplorerStore } from '~/hooks/useExplorerStore';
|
||||||
import { useExplorerTopBarOptions } from '~/hooks/useExplorerTopBarOptions';
|
import { useExplorerTopBarOptions } from '~/hooks/useExplorerTopBarOptions';
|
||||||
import Explorer from '../Explorer';
|
import Explorer from '../Explorer';
|
||||||
import DeleteDialog from '../Explorer/File/DeleteDialog';
|
import DeleteDialog from '../Explorer/File/DeleteDialog';
|
||||||
import { useExplorerSearchParams } from '../Explorer/util';
|
import { useExplorerOrder, useExplorerSearchParams } from '../Explorer/util';
|
||||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||||
|
|
||||||
const PARAMS = z.object({
|
const PARAMS = z.object({
|
||||||
|
@ -81,6 +81,7 @@ const useItems = () => {
|
||||||
{
|
{
|
||||||
library_id: library.uuid,
|
library_id: library.uuid,
|
||||||
arg: {
|
arg: {
|
||||||
|
order: useExplorerOrder(),
|
||||||
locationId,
|
locationId,
|
||||||
take,
|
take,
|
||||||
...(explorerState.layoutMode === 'media'
|
...(explorerState.layoutMode === 'media'
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default () => {
|
||||||
});
|
});
|
||||||
mounted = true;
|
mounted = true;
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full">
|
<div className="flex w-full px-5 pt-4">
|
||||||
{/* STAT CONTAINER */}
|
{/* STAT CONTAINER */}
|
||||||
<div className="-mb-1 flex h-20 overflow-hidden">
|
<div className="-mb-1 flex h-20 overflow-hidden">
|
||||||
{Object.entries(stats?.data || []).map(([key, value]) => {
|
{Object.entries(stats?.data || []).map(([key, value]) => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { useMemo, useState } from 'react';
|
||||||
import 'react-loading-skeleton/dist/skeleton.css';
|
import 'react-loading-skeleton/dist/skeleton.css';
|
||||||
import { useExplorerStore, useExplorerTopBarOptions } from '~/hooks';
|
import { useExplorerStore, useExplorerTopBarOptions } from '~/hooks';
|
||||||
import Explorer from '../Explorer';
|
import Explorer from '../Explorer';
|
||||||
import { SEARCH_PARAMS } from '../Explorer/util';
|
import { SEARCH_PARAMS, useExplorerOrder } from '../Explorer/util';
|
||||||
import { usePageLayout } from '../PageLayout';
|
import { usePageLayout } from '../PageLayout';
|
||||||
import TopBarChildren from '../TopBar/TopBarChildren';
|
import TopBarChildren from '../TopBar/TopBarChildren';
|
||||||
import CategoryButton from '../overview/CategoryButton';
|
import CategoryButton from '../overview/CategoryButton';
|
||||||
|
@ -79,6 +79,7 @@ export const Component = () => {
|
||||||
{
|
{
|
||||||
library_id: library.uuid,
|
library_id: library.uuid,
|
||||||
arg: {
|
arg: {
|
||||||
|
order: useExplorerOrder(),
|
||||||
favorite: isFavoritesCategory ? true : undefined,
|
favorite: isFavoritesCategory ? true : undefined,
|
||||||
...(explorerStore.layoutMode === 'media'
|
...(explorerStore.layoutMode === 'media'
|
||||||
? { kind: [5, 7].includes(kind) ? [kind] : isFavoritesCategory ? [5, 7] : [5, 7, kind] }
|
? { kind: [5, 7].includes(kind) ? [kind] : isFavoritesCategory ? [5, 7] : [5, 7, kind] }
|
||||||
|
@ -91,7 +92,7 @@ export const Component = () => {
|
||||||
'search.paths',
|
'search.paths',
|
||||||
{
|
{
|
||||||
...queryKey[1].arg,
|
...queryKey[1].arg,
|
||||||
cursor,
|
cursor
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
getNextPageParam: (lastPage) => lastPage.cursor ?? undefined
|
getNextPageParam: (lastPage) => lastPage.cursor ?? undefined
|
||||||
|
@ -123,8 +124,9 @@ export const Component = () => {
|
||||||
isFetchingNextPage={query.isFetchingNextPage}
|
isFetchingNextPage={query.isFetchingNextPage}
|
||||||
scrollRef={page?.ref}
|
scrollRef={page?.ref}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Statistics />
|
<Statistics />
|
||||||
<div className="no-scrollbar sticky top-0 z-50 mt-4 flex space-x-[1px] overflow-x-scroll bg-app/90 py-1.5 backdrop-blur">
|
<div className="no-scrollbar sticky top-0 z-50 mt-2 flex space-x-[1px] overflow-x-scroll bg-app/90 px-5 py-1.5 backdrop-blur">
|
||||||
{categories.data?.map((category) => {
|
{categories.data?.map((category) => {
|
||||||
const iconString = CategoryToIcon[category.name] || 'Document';
|
const iconString = CategoryToIcon[category.name] || 'Document';
|
||||||
const icon = icons[iconString as keyof typeof icons];
|
const icon = icons[iconString as keyof typeof icons];
|
||||||
|
@ -140,6 +142,7 @@ export const Component = () => {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Explorer>
|
</Explorer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { proxy, useSnapshot } from 'valtio';
|
import { proxy, useSnapshot } from 'valtio';
|
||||||
import { ExplorerItem } from '@sd/client';
|
import { ExplorerItem, Ordering } from '@sd/client';
|
||||||
import { resetStore } from '@sd/client/src/stores/util';
|
import { resetStore } from '@sd/client/src/stores/util';
|
||||||
|
|
||||||
|
type UnionKeys<T> = T extends any ? keyof T : never;
|
||||||
|
|
||||||
export type ExplorerLayoutMode = 'rows' | 'grid' | 'columns' | 'media';
|
export type ExplorerLayoutMode = 'rows' | 'grid' | 'columns' | 'media';
|
||||||
|
|
||||||
export enum ExplorerKind {
|
export enum ExplorerKind {
|
||||||
|
@ -12,6 +14,10 @@ export enum ExplorerKind {
|
||||||
|
|
||||||
export type CutCopyType = 'Cut' | 'Copy';
|
export type CutCopyType = 'Cut' | 'Copy';
|
||||||
|
|
||||||
|
export type ExplorerOrderByKeys = UnionKeys<Ordering> | 'none';
|
||||||
|
|
||||||
|
export type ExplorerDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
locationId: null as number | null,
|
locationId: null as number | null,
|
||||||
layoutMode: 'grid' as ExplorerLayoutMode,
|
layoutMode: 'grid' as ExplorerLayoutMode,
|
||||||
|
@ -35,7 +41,10 @@ const state = {
|
||||||
quickViewObject: null as ExplorerItem | null,
|
quickViewObject: null as ExplorerItem | null,
|
||||||
isRenaming: false,
|
isRenaming: false,
|
||||||
mediaColumns: 8,
|
mediaColumns: 8,
|
||||||
mediaAspectSquare: true
|
mediaAspectSquare: true,
|
||||||
|
orderBy: 'dateCreated' as ExplorerOrderByKeys,
|
||||||
|
orderByDirection: 'desc' as ExplorerDirection,
|
||||||
|
groupBy: 'none',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep the private and use `useExplorerState` or `getExplorerStore` or you will get production build issues.
|
// Keep the private and use `useExplorerState` or `getExplorerStore` or you will get production build issues.
|
||||||
|
|
|
@ -200,7 +200,7 @@ export type CRDTOperation = { node: string; timestamp: number; id: string; typ:
|
||||||
*/
|
*/
|
||||||
export type Salt = number[]
|
export type Salt = number[]
|
||||||
|
|
||||||
export type Ordering = { name: boolean }
|
export type Ordering = { name: boolean } | { sizeInBytes: boolean } | { dateCreated: boolean } | { dateModified: boolean } | { dateIndexed: boolean }
|
||||||
|
|
||||||
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
export type Node = { id: number; pub_id: number[]; name: string; platform: number; version: string | null; last_seen: string; timezone: string | null; date_created: string }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue