diff --git a/public/dbbrowser.png b/public/dbbrowser.png new file mode 100644 index 0000000..41f8d34 Binary files /dev/null and b/public/dbbrowser.png differ diff --git a/public/heidisqlL_logo.png b/public/heidisqlL_logo.png new file mode 100644 index 0000000..9a5ca22 Binary files /dev/null and b/public/heidisqlL_logo.png differ diff --git a/public/mariadb_logo_small.png b/public/mariadb_logo_small.png new file mode 100644 index 0000000..368bdff Binary files /dev/null and b/public/mariadb_logo_small.png differ diff --git a/public/mysql_logo_small.png b/public/mysql_logo_small.png new file mode 100644 index 0000000..a501af1 Binary files /dev/null and b/public/mysql_logo_small.png differ diff --git a/public/postgresql_logo_small.png b/public/postgresql_logo_small.png new file mode 100644 index 0000000..b5b7b51 Binary files /dev/null and b/public/postgresql_logo_small.png differ diff --git a/public/sqlite_logo_small.png b/public/sqlite_logo_small.png new file mode 100644 index 0000000..dba270e Binary files /dev/null and b/public/sqlite_logo_small.png differ diff --git a/src/App.tsx b/src/App.tsx index 27ef1b1..4fe803a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,11 +23,13 @@ function App() { - - + + + {appRoutes} - - + + + diff --git a/src/components/auto-complete/auto-complete.tsx b/src/components/auto-complete/auto-complete.tsx index 8c5ead2..2967ec3 100644 --- a/src/components/auto-complete/auto-complete.tsx +++ b/src/components/auto-complete/auto-complete.tsx @@ -1,6 +1,6 @@ import { Autocomplete as HeroUiAutocomplete, AutocompleteItem, AutocompleteSection } from "@heroui/react"; -import { Key } from "react"; +import { Key, useState } from "react"; interface AutocompleteProps { @@ -16,12 +16,13 @@ interface AutocompleteProps { const headingClasses = - "flex w-full py-1.5 px-2 bg-default shadow-small rounded-small"; + "flex w-full py-1.5 px-2 bg-default shadow-md border-1 font-medium rounded-md dark:border-font/5 dark:bg-background-100"; const Autocomplete: React.FC = ({ items, label = "name", onSelectionChange, defaultSelection, placeholder, isDisabled, selectedItem, grouped = false }) => { + const onItemChange = (item: Key | null) => { onSelectionChange && onSelectionChange(item); } @@ -30,7 +31,7 @@ const Autocomplete: React.FC = ({ items, label = "name", onSe = ({ items, label = "name", onSe aria-label={placeholder} placeholder={placeholder} selectedKey={selectedItem as any} + className="h-8 w-full focus-visible:ring-0 shadow-none " + classNames={{ + clearButton: "text-icon", + selectorButton: "text-icon", + popoverContent: "rounded-md " + }} inputProps={{ classNames: { - - inputWrapper: "border-divider group-hover:border-primary", - + inputWrapper: " border-divider group-hover:border-primary group-data-[focus=true]:border-primary", }, }} @@ -55,35 +60,37 @@ const Autocomplete: React.FC = ({ items, label = "name", onSe return { - items ? Object.keys(items).map((key: any) => ( + items ? Object.keys(items).map((key: string) => ( { - items[key].map((item: any) => ( + items[key as any].map((item: any) => ( {item[label]} )) } diff --git a/src/components/cardinality-marker/cardinality-marker.tsx b/src/components/cardinality-marker/cardinality-marker.tsx index b8f50ae..0cbeda8 100644 --- a/src/components/cardinality-marker/cardinality-marker.tsx +++ b/src/components/cardinality-marker/cardinality-marker.tsx @@ -1,17 +1,22 @@ +import { cn } from "@heroui/react"; +import React from "react"; + export interface CardinalityMarkerProps { selected?: boolean, direction?: "start" | "end", - type: "one" | "many" + cardinality: "one" | "many", + type?: "symbole" | "numeric" } -const CardinalityMarker: React.FC = ({ selected = false, type, direction = "start" }) => { +const CardinalityMarker: React.FC = ({ selected = false, cardinality, direction = "start", type = "symbole" }) => { - const id = `${type}_${direction}${selected ? "_selected" : ""}`; + const id = `${cardinality}_${direction}${selected ? "_selected" : ""}`; const renderMarker = () => { - if (type == "many") { + + if (cardinality == "many") { if (direction == "start") return () else if (direction == "end") @@ -19,7 +24,7 @@ const CardinalityMarker: React.FC = ({ selected = false, } - if (type == "one") { + if (cardinality == "one") { if (direction == "start") { return () } @@ -27,34 +32,78 @@ const CardinalityMarker: React.FC = ({ selected = false, return () } } + + } - return ( - <> + + + if (type == "symbole") + return ( + <> + + + { + renderMarker() + } + + + + ) + else if (type == "numeric") { + return ( - - { - renderMarker() + + /> + + {cardinality == "one" ? "I" : "N"} + - - ) + ) + } } -export default CardinalityMarker; \ No newline at end of file +export default React.memo( CardinalityMarker); \ No newline at end of file diff --git a/src/components/checkbox/database-checkbox.tsx b/src/components/checkbox/database-checkbox.tsx index e5e2044..238c1ee 100644 --- a/src/components/checkbox/database-checkbox.tsx +++ b/src/components/checkbox/database-checkbox.tsx @@ -17,9 +17,11 @@ const DatabaseCheckbox: React.FC = ({ database }) => { className="database-checkbox" classNames={{ base: "flex min-w-[128px] min-h-[128px] max-w-[128px] max-h-[128px] hover:bg-default rounded-md relative", - wrapper: "absolute top-2 left-2 " , + wrapper: "absolute top-2 left-2 before:border-divider group-data-[hover=true]:before:bg-default" , label : "flex items-center justify-center w-full h-full p-2 " }} + + >
= ({ value, icon, logo , label }) => { +const OptionCheckbox: React.FC = (props) => { + const { value, icon, logo, label, isSelected } = props; + + const variant = isSelected ? { + variant: "flat", + color: "primary" + } : { + variant: "bordered", + color: "default" + } return ( - : undefined} + + : undefined} startContent={ icon } > - + {label} diff --git a/src/components/clipboard/clipboard.tsx b/src/components/clipboard/clipboard.tsx new file mode 100644 index 0000000..6679823 --- /dev/null +++ b/src/components/clipboard/clipboard.tsx @@ -0,0 +1,60 @@ +import React, { useCallback, useState } from "react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip"; +import { useTranslation } from "react-i18next"; +import { Button } from "@heroui/react"; +import { Copy, CopyCheck } from "lucide-react"; + + + +interface ClipboardProps { + text?: string +} + +const Clipboard: React.FC = ({ text }) => { + const { t } = useTranslation(); + + + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = useCallback(async () => { + if (text) + try { + await navigator.clipboard.writeText(text); + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 500) + + } catch (err) { + + setIsCopied(false); + } + + }, [text]) + + return ( + + + + + + + + {!isCopied ? t("clipboard.copy") : t("clipboard.copied")} + + + ) +}; + +export default React.memo(Clipboard) \ No newline at end of file diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index d92755d..7dfa8ff 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -7,26 +7,14 @@ import { useModal } from "@/providers/modal-provider/modal-provider"; import { Modals } from "@/providers/modal-provider/modal-contxet"; - - - - - - - -interface MenuProps { -} - -const Menu: React.FC = ({ }) => { +const Menu: React.FC = ({ }) => { const { setTheme } = useTheme() const { t } = useTranslation(); const { open } = useModal(); - useEffect(() => { - open(Modals.IMPORT_DATABASE) - } , []) + const menu: MenuDropdownProps[] = useMemo(() => [ { title: t("menu.file"), diff --git a/src/components/modal/loading-modal.tsx b/src/components/modal/loading-modal.tsx new file mode 100644 index 0000000..c9ab46c --- /dev/null +++ b/src/components/modal/loading-modal.tsx @@ -0,0 +1,22 @@ +import { Spinner } from "@heroui/react"; + + + + +interface LoadingProps { + text?: string; +} + + +export const Loading: React.FC = ({ text }) => { + + return ( +
+ + + Loading ... + +
+ ) +} + diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index 94684b0..6028d82 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -8,13 +8,14 @@ import { ModalFooter, Button, useDraggable, + cn, } from "@heroui/react"; import { useTranslation } from "react-i18next"; export interface ModalProps { isOpen?: boolean, - onOpenChange? : (open: boolean) => void, + onOpenChange?: (open: boolean) => void, className?: string, backdrop?: "blur" | "transparent" | "opaque", title: string, @@ -23,10 +24,11 @@ export interface ModalProps { actionHandler?: () => void, isDisabled?: boolean, header?: string, - variant?: "default" | "danger" + variant?: "default" | "danger", + closable?: boolean } -const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action", header, actionHandler, isDisabled, variant = "default" }) => { +const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action", header, actionHandler, isDisabled, variant = "default", closable = true }) => { const targetRef = React.useRef(null); @@ -39,17 +41,21 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop setIsLoading(true) actionHandler && await actionHandler(); setIsLoading(false); - onClose() } return ( {(onClose) => ( @@ -72,9 +78,15 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop { variant == "default" && <> - + { + closable && + + } + { + !closable &&
+ } @@ -83,9 +95,15 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop { variant == "danger" && <> - + { + closable && + + } + { + !closable &&
+ } diff --git a/src/components/navbar/connection-status.tsx b/src/components/navbar/connection-status.tsx index 4dfb47e..d6045bc 100644 --- a/src/components/navbar/connection-status.tsx +++ b/src/components/navbar/connection-status.tsx @@ -2,8 +2,11 @@ import { usePowerSync } from "@powersync/react"; import { Wifi, WifiOff } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip"; -import { cn, Divider, Progress } from "@heroui/react"; +import { addToast, cn, Divider, Progress } from "@heroui/react"; import { differenceInHours, differenceInMinutes } from "date-fns"; +import { useTranslation } from "react-i18next"; + + const ConnectionStatus: React.FC = () => { @@ -11,6 +14,10 @@ const ConnectionStatus: React.FC = () => { const [syncStatus, setSyncStatus] = useState(powerSync.currentStatus); const [downloadProgress, setDownloadProgress] = useState(undefined); const clearProgressHandler: any = useRef(undefined); + const { t } = useTranslation(); + + + const [lastUploadAt, setLastUploadAt] = useState(() => { const last_upload_at = localStorage.getItem("last_upload_at"); if (last_upload_at) @@ -65,20 +72,23 @@ const ConnectionStatus: React.FC = () => { const hours = differenceInHours(currentDate, lastUploadAt); if (hours > 0 && hours < 24) { - return `${hours} hour ago` + return `${hours} ${t("connection_status.hour_ago")}` } if (minutes > 0 && minutes < 60) - return `${minutes} min ago` + return `${minutes} ${t("connection_status.min_ago")}` if (hours > 24) { return `${lastUploadAt.toLocaleDateString()}` } } - return "Just now"; - - }, [lastUploadAt]); + return t("connection_status.just_now") ; + }, [lastUploadAt , t]); + const lastSyncedDate: string | undefined = useMemo(() => { + return syncStatus.lastSyncedAt ? syncStatus.lastSyncedAt.toLocaleString("en-US") : undefined; + }, [syncStatus.lastSyncedAt]); + return (
@@ -89,10 +99,9 @@ const ConnectionStatus: React.FC = () => { syncStatus.connected ? : - } { - syncStatus.connected ? "Online" : "Offline" + syncStatus.connected ? t("connection_status.online") : t("connection_status.offline") } @@ -103,7 +112,7 @@ const ConnectionStatus: React.FC = () => { downloadProgress !== undefined ? <> - Saving ... + {t("connection_status.saving")} ... { : - Saved {deltaUploadTime} + {t("connection_status.saved")} {deltaUploadTime} }
- - Last synced {syncStatus.lastSyncedAt?.toISOString()} - + { + lastSyncedDate && + + {t("connection_status.last_synced")} : {lastSyncedDate} + + }
) diff --git a/src/components/navbar/rename-database.tsx b/src/components/navbar/rename-database.tsx index 67a1116..c7d390d 100644 --- a/src/components/navbar/rename-database.tsx +++ b/src/components/navbar/rename-database.tsx @@ -42,13 +42,13 @@ const RenameDatabase: React.FC = ({ database}) => { { @@ -503,9 +530,9 @@ const FieldSetting: React.FC = ({ field }) => { } className="h-8 w-full focus-visible:ring-0 shadow-none " classNames={{ - inputWrapper: "dark:bg-default border-divider group-hover:border-primary ", - + inputWrapper: "dark:bg-default border-divider group-hover:border-primary group-data-[focus=true]:border-primary ", }} + /> } @@ -539,9 +566,10 @@ const FieldSetting: React.FC = ({ field }) => { onValueChange={setNote} onBlur={updateFieldNote} classNames={{ - inputWrapper: "bg-default border-divider ", base: "max-w-xs", input: "resize-y min-h-[60px] max-h-[180px]", + inputWrapper: "bg-default border-divider dark:bg-background-100 group-hover:border-primary group-data-[focus=true]:border-primary", + label: "text-font/90 group-data-[focus=true]:text-font/70" }} />