diff --git a/src/components/databrowser/components/databrowser-tabs.tsx b/src/components/databrowser/components/databrowser-tabs.tsx index fb0b64b..cc4ee6a 100644 --- a/src/components/databrowser/components/databrowser-tabs.tsx +++ b/src/components/databrowser/components/databrowser-tabs.tsx @@ -301,9 +301,9 @@ function AddTabButton() { variant="secondary" size="icon-sm" onClick={handleAddTab} - className="flex-shrink-0" + className="flex-shrink-0 dark:bg-zinc-200" > - + ) } diff --git a/src/components/databrowser/components/display/delete-alert-dialog.tsx b/src/components/databrowser/components/display/delete-alert-dialog.tsx index 6fcce85..5e757b5 100644 --- a/src/components/databrowser/components/display/delete-alert-dialog.tsx +++ b/src/components/databrowser/components/display/delete-alert-dialog.tsx @@ -18,13 +18,19 @@ export function DeleteAlertDialog({ open, onOpenChange, deletionType, + count = 1, }: { children?: React.ReactNode onDeleteConfirm: MouseEventHandler open?: boolean onOpenChange?: (open: boolean) => void deletionType: "item" | "key" + count?: number }) { + const isPlural = count > 1 + const itemLabel = deletionType === "item" ? "Item" : "Key" + const itemsLabel = deletionType === "item" ? "Items" : "Keys" + return ( {children && {children}} @@ -32,10 +38,10 @@ export function DeleteAlertDialog({ - {deletionType === "item" ? "Delete Item" : "Delete Key"} + {isPlural ? `Delete ${count} ${itemsLabel}` : `Delete ${itemLabel}`} - Are you sure you want to delete this {deletionType}?
+ Are you sure you want to delete {isPlural ? `these ${count} ${deletionType}s` : `this ${deletionType}`}?
This action cannot be undone.
diff --git a/src/components/databrowser/components/sidebar-context-menu.tsx b/src/components/databrowser/components/sidebar-context-menu.tsx index efcab37..647888f 100644 --- a/src/components/databrowser/components/sidebar-context-menu.tsx +++ b/src/components/databrowser/components/sidebar-context-menu.tsx @@ -18,19 +18,23 @@ import { DeleteAlertDialog } from "./display/delete-alert-dialog" export const SidebarContextMenu = ({ children }: PropsWithChildren) => { const { mutate: deleteKey } = useDeleteKey() const [isAlertOpen, setAlertOpen] = useState(false) - const [dataKey, setDataKey] = useState("") - const { addTab, setSelectedKey, selectTab, setSearch } = useDatabrowserStore() - const { search: currentSearch } = useTab() + const [contextKeys, setContextKeys] = useState([]) + const { addTab, setSelectedKey: setSelectedKeyGlobal, selectTab, setSearch } = useDatabrowserStore() + const { search: currentSearch, selectedKeys, setSelectedKey } = useTab() return ( <> { e.stopPropagation() - deleteKey(dataKey) + // Delete all selected keys + for (const key of contextKeys) { + deleteKey(key) + } setAlertOpen(false) }} /> @@ -42,7 +46,16 @@ export const SidebarContextMenu = ({ children }: PropsWithChildren) => { const key = el.closest("[data-key]") if (key && key instanceof HTMLElement && key.dataset.key !== undefined) { - setDataKey(key.dataset.key) + const clickedKey = key.dataset.key + + // If right-clicking on a selected key, keep all selected keys + if (selectedKeys.includes(clickedKey)) { + setContextKeys(selectedKeys) + } else { + // If right-clicking on an unselected key, select only that key + setSelectedKey(clickedKey) + setContextKeys([clickedKey]) + } } else { throw new Error("Key not found") } @@ -53,12 +66,13 @@ export const SidebarContextMenu = ({ children }: PropsWithChildren) => { { - navigator.clipboard.writeText(dataKey) + navigator.clipboard.writeText(contextKeys[0]) toast({ description: "Key copied to clipboard", }) }} className="gap-2" + disabled={contextKeys.length !== 1} > Copy key @@ -66,11 +80,12 @@ export const SidebarContextMenu = ({ children }: PropsWithChildren) => { { const newTabId = addTab() - setSelectedKey(newTabId, dataKey) + setSelectedKeyGlobal(newTabId, contextKeys[0]) setSearch(newTabId, currentSearch) selectTab(newTabId) }} className="gap-2" + disabled={contextKeys.length !== 1} > Open in new tab @@ -78,7 +93,7 @@ export const SidebarContextMenu = ({ children }: PropsWithChildren) => { setAlertOpen(true)} className="gap-2"> - Delete key + {contextKeys.length > 1 ? `Delete ${contextKeys.length} keys` : "Delete key"} diff --git a/src/components/databrowser/components/sidebar/index.tsx b/src/components/databrowser/components/sidebar/index.tsx index 16909c8..d735a67 100644 --- a/src/components/databrowser/components/sidebar/index.tsx +++ b/src/components/databrowser/components/sidebar/index.tsx @@ -28,7 +28,7 @@ export function Sidebar() {
) } diff --git a/src/components/databrowser/components/sidebar/search-input.tsx b/src/components/databrowser/components/sidebar/search-input.tsx index d4472a4..b901200 100644 --- a/src/components/databrowser/components/sidebar/search-input.tsx +++ b/src/components/databrowser/components/sidebar/search-input.tsx @@ -83,11 +83,11 @@ export const SearchInput = () => {
0}> -
+
{ setState(e.currentTarget.value) diff --git a/src/components/databrowser/components/tab.tsx b/src/components/databrowser/components/tab.tsx index 672f3aa..c8929fc 100644 --- a/src/components/databrowser/components/tab.tsx +++ b/src/components/databrowser/components/tab.tsx @@ -75,7 +75,7 @@ export const Tab = ({ id, isList }: { id: TabId; isList?: boolean }) => { e.stopPropagation() removeTab(id) }} - className="p-1 text-zinc-300 transition-colors hover:text-zinc-500" + className="p-1 text-zinc-300 transition-colors hover:text-zinc-500 dark:text-zinc-400" > diff --git a/src/store.tsx b/src/store.tsx index 26e65a7..0920a98 100644 --- a/src/store.tsx +++ b/src/store.tsx @@ -45,7 +45,7 @@ export const DatabrowserProvider = ({ setItem: (_name, value) => storage.set(JSON.stringify(value)), removeItem: () => {}, }, - version: 2, + version: 3, // @ts-expect-error Reset the store for < v1 migrate: (originalState, version) => { const state = originalState as DatabrowserStore @@ -60,6 +60,23 @@ export const DatabrowserProvider = ({ } } + if (version === 2) { + // Migrate from selectedKey to selectedKeys + return { + ...state, + tabs: state.tabs.map(([id, data]) => { + const oldData = data as any + return [ + id, + { + ...data, + selectedKeys: oldData.selectedKey ? [oldData.selectedKey] : [], + }, + ] + }), + } + } + return state }, }) @@ -102,7 +119,7 @@ export type SelectedItem = { export type TabData = { id: TabId - selectedKey: string | undefined + selectedKeys: string[] selectedListItem?: SelectedItem search: SearchFilter @@ -128,8 +145,9 @@ type DatabrowserStore = { closeAllButPinned: () => void // Tab actions - getSelectedKey: (tabId: TabId) => string | undefined + getSelectedKeys: (tabId: TabId) => string[] setSelectedKey: (tabId: TabId, key: string | undefined) => void + setSelectedKeys: (tabId: TabId, keys: string[]) => void setSelectedListItem: (tabId: TabId, item?: { key: string; isNew?: boolean }) => void setSearch: (tabId: TabId, search: SearchFilter) => void setSearchKey: (tabId: TabId, key: string) => void @@ -150,7 +168,7 @@ const storeCreator: StateCreator = (set, get) => ({ const newTabData: TabData = { id, - selectedKey: undefined, + selectedKeys: [], search: { key: "", type: undefined }, pinned: false, } @@ -275,18 +293,22 @@ const storeCreator: StateCreator = (set, get) => ({ set({ selectedTab: id }) }, - getSelectedKey: (tabId) => { - return get().tabs.find(([id]) => id === tabId)?.[1]?.selectedKey + getSelectedKeys: (tabId) => { + return get().tabs.find(([id]) => id === tabId)?.[1]?.selectedKeys ?? [] }, setSelectedKey: (tabId, key) => { + get().setSelectedKeys(tabId, key ? [key] : []) + }, + + setSelectedKeys: (tabId, keys) => { set((old) => { const tabIndex = old.tabs.findIndex(([id]) => id === tabId) if (tabIndex === -1) return old const newTabs = [...old.tabs] const [, tabData] = newTabs[tabIndex] - newTabs[tabIndex] = [tabId, { ...tabData, selectedKey: key, selectedListItem: undefined }] + newTabs[tabIndex] = [tabId, { ...tabData, selectedKeys: keys, selectedListItem: undefined }] return { ...old, tabs: newTabs } }) diff --git a/src/tab-provider.tsx b/src/tab-provider.tsx index cfdd1d7..3599ce8 100644 --- a/src/tab-provider.tsx +++ b/src/tab-provider.tsx @@ -23,6 +23,7 @@ export const useTab = () => { selectedTab, tabs, setSelectedKey, + setSelectedKeys, setSelectedListItem, setSearch, setSearchKey, @@ -37,12 +38,14 @@ export const useTab = () => { return useMemo( () => ({ active: selectedTab === tabId, - selectedKey: tabData.selectedKey, + selectedKey: tabData.selectedKeys[0], // Backwards compatibility - first selected key + selectedKeys: tabData.selectedKeys, selectedListItem: tabData.selectedListItem, search: tabData.search, pinned: tabData.pinned, setSelectedKey: (key: string | undefined) => setSelectedKey(tabId, key), + setSelectedKeys: (keys: string[]) => setSelectedKeys(tabId, keys), setSelectedListItem: (item: SelectedItem | undefined) => setSelectedListItem(tabId, item), setSearch: (search: SearchFilter) => setSearch(tabId, search), setSearchKey: (key: string) => setSearchKey(tabId, key),