diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..18c91471 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..80cab4eb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +The Outerbase Studio team takes security seriously. If you discover a security issue, please report it immediately. Your security reports are highly valued! + +## Reporting a Vulnerability + +Please DO NOT create a public issue. Instead, report the issue privately by emailing invisal@outerbase.com. + +## Supported Version + +We exclusively support the latest release and the main branch. diff --git a/package-lock.json b/package-lock.json index 153a6dd7..83989cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "@tiptap/core": "^2.3.0", "@tiptap/react": "^2.3.0", "@types/mdx": "^2.0.13", + "@types/react-grid-layout": "^1.3.5", "@uiw/codemirror-extensions-langs": "^4.21.24", "@uiw/codemirror-themes": "^4.21.21", "@uiw/react-codemirror": "^4.21.21", @@ -63,6 +64,7 @@ "dexie": "^4.0.8", "dotenv": "^16.4.5", "drizzle-orm": "^0.30.1", + "echarts": "^5.6.0", "eslint-plugin-jest": "^27.6.3", "file-saver": "^2.0.5", "html-to-image": "^1.11.11", @@ -72,12 +74,15 @@ "lucia": "^3.2.0", "lucide-react": "^0.474.0", "next": "15.1.4", + "next-themes": "^0.4.4", "oslo": "^1.1.3", "react": "19.0.0", "react-dom": "19.0.0", + "react-grid-layout": "^1.5.0", "react-resizable-panels": "^2.1.7", "sonner": "^1.4.41", "sql-formatter": "^15.3.2", + "swr": "^2.3.0", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "use-immer": "^0.11.0", @@ -6700,6 +6705,14 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-grid-layout": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", + "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -9108,6 +9121,20 @@ "node": ">= 0.4" } }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, "node_modules/electron-to-chromium": { "version": "1.5.88", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz", @@ -10469,6 +10496,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -13097,7 +13129,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -13667,7 +13698,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -14830,6 +14860,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz", + "integrity": "sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -14950,7 +14990,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15610,7 +15649,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -15622,7 +15660,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/property-information": { @@ -15964,6 +16001,44 @@ "react": "^19.0.0" } }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-grid-layout": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.0.tgz", + "integrity": "sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w==", + "dependencies": { + "clsx": "^2.0.0", + "fast-equals": "^4.0.3", + "prop-types": "^15.8.1", + "react-draggable": "^4.4.5", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16019,6 +16094,18 @@ } } }, + "node_modules/react-resizable": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", + "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + }, + "peerDependencies": { + "react": ">= 16.3" + } + }, "node_modules/react-resizable-panels": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", @@ -16285,6 +16372,11 @@ "dev": true, "license": "MIT" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -17261,6 +17353,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz", + "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -18862,6 +18967,19 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, "node_modules/zustand": { "version": "4.5.6", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", diff --git a/package.json b/package.json index 19635557..3fb9fa50 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "format:fix": "prettier --write .", "lint": "next lint", "lint:fix": "next lint --fix .", - "dialect": "node build-dialect.js" + "dialect": "node build-dialect.js", + "remove-branch": "npx git-removed-branches --prune -f" }, "overrides": { "@libsql/client": "^0.5.3" @@ -68,6 +69,7 @@ "@tiptap/core": "^2.3.0", "@tiptap/react": "^2.3.0", "@types/mdx": "^2.0.13", + "@types/react-grid-layout": "^1.3.5", "@uiw/codemirror-extensions-langs": "^4.21.24", "@uiw/codemirror-themes": "^4.21.21", "@uiw/react-codemirror": "^4.21.21", @@ -82,6 +84,7 @@ "dexie": "^4.0.8", "dotenv": "^16.4.5", "drizzle-orm": "^0.30.1", + "echarts": "^5.6.0", "eslint-plugin-jest": "^27.6.3", "file-saver": "^2.0.5", "html-to-image": "^1.11.11", @@ -91,12 +94,15 @@ "lucia": "^3.2.0", "lucide-react": "^0.474.0", "next": "15.1.4", + "next-themes": "^0.4.4", "oslo": "^1.1.3", "react": "19.0.0", "react-dom": "19.0.0", + "react-grid-layout": "^1.5.0", "react-resizable-panels": "^2.1.7", "sonner": "^1.4.41", "sql-formatter": "^15.3.2", + "swr": "^2.3.0", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "use-immer": "^0.11.0", diff --git a/public/assets/login-planet.png b/public/assets/login-planet.png new file mode 100644 index 00000000..d6b1e9db Binary files /dev/null and b/public/assets/login-planet.png differ diff --git a/public/assets/login-portal.png b/public/assets/login-portal.png new file mode 100644 index 00000000..ea1c0562 Binary files /dev/null and b/public/assets/login-portal.png differ diff --git a/public/assets/login-stars.png b/public/assets/login-stars.png new file mode 100644 index 00000000..00143153 Binary files /dev/null and b/public/assets/login-stars.png differ diff --git a/src/app/(outerbase)/navigation.tsx b/src/app/(outerbase)/navigation.tsx new file mode 100644 index 00000000..f9d2cc71 --- /dev/null +++ b/src/app/(outerbase)/navigation.tsx @@ -0,0 +1,93 @@ +import { getDatabaseIcon } from "@/components/resource-card/utils"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { useParams, useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; +import { useWorkspaces } from "./workspace-provider"; + +function WorkspaceSelector() { + const router = useRouter(); + const { workspaces } = useWorkspaces(); + const { workspaceId } = useParams<{ workspaceId: string }>(); + const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(workspaceId); + + const selectedWorkspace = workspaces.find( + (w) => w.id === selectedWorkspaceId || w.short_name === selectedWorkspaceId + ); + + const bases = useMemo(() => { + const currentBases = [...(selectedWorkspace?.bases ?? [])]; + currentBases.sort((a, b) => a.name.localeCompare(b.name)); + return currentBases; + }, [selectedWorkspace]); + + return ( +
+
+ + {workspaces.map((workspace) => ( +
router.push(`/w/${workspace.short_name}`)} + key={workspace.id} + onMouseEnter={() => setSelectedWorkspaceId(workspace.short_name)} + className={cn( + buttonVariants({ variant: "ghost", size: "sm" }), + "cursor-pointer justify-start py-0.5" + )} + > + {workspace.name} +
+ ))} +
+
+ + {bases.map((base) => { + const IconComponent = getDatabaseIcon(base.sources[0]?.type); + + return ( +
+ router.push(`/w/${selectedWorkspaceId}/${base.short_name}`, {}) + } + key={base.id} + className={cn( + buttonVariants({ + variant: "ghost", + size: "sm", + }), + "cursor-pointer justify-start p-2" + )} + > + + {base.name} +
+ ); + })} +
+
+ ); +} + +export function NavigationBar() { + return ( +
+ + + + + + + + +
+ ); +} diff --git a/src/app/(outerbase)/session-provider.tsx b/src/app/(outerbase)/session-provider.tsx new file mode 100644 index 00000000..8cbf1fd1 --- /dev/null +++ b/src/app/(outerbase)/session-provider.tsx @@ -0,0 +1,56 @@ +"use client"; +import { useRouter } from "next/navigation"; +import { createContext, PropsWithChildren, useContext } from "react"; +import useSWR from "swr"; +import { getOuterbaseSession } from "../../outerbase-cloud/api"; +import { + OuterbaseAPISession, + OuterbaseAPIUser, +} from "../../outerbase-cloud/api-type"; + +interface OuterebaseSessionContextProps { + session: OuterbaseAPISession; + user: OuterbaseAPIUser; +} + +const OuterbaseSessionContext = createContext<{ + session: OuterbaseAPISession; + user: OuterbaseAPIUser; +}>({} as OuterebaseSessionContextProps); + +export function useSession() { + return useContext(OuterbaseSessionContext); +} + +export function OuterbaseSessionProvider({ children }: PropsWithChildren) { + const router = useRouter(); + + const { data, isLoading } = useSWR( + "session", + () => { + return getOuterbaseSession(); + }, + { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } + ); + + if (isLoading) { + return
Session Loading...
; + } + + if (!data?.session || !data?.user) { + router.push("/signin"); + return
Redirecting...
; + } + + return ( + + {children} + + ); +} diff --git a/src/app/(theme)/w/[workspaceId]/[baseId]/page-client.tsx b/src/app/(outerbase)/w/[workspaceId]/[baseId]/page-client.tsx similarity index 52% rename from src/app/(theme)/w/[workspaceId]/[baseId]/page-client.tsx rename to src/app/(outerbase)/w/[workspaceId]/[baseId]/page-client.tsx index 2b2ffeab..f1d4d8b8 100644 --- a/src/app/(theme)/w/[workspaceId]/[baseId]/page-client.tsx +++ b/src/app/(outerbase)/w/[workspaceId]/[baseId]/page-client.tsx @@ -2,11 +2,18 @@ import OpacityLoading from "@/components/gui/loading-opacity"; import { Studio } from "@/components/gui/studio"; +import { StudioExtensionManager } from "@/core/extension-manager"; +import { + createMySQLExtensions, + createPostgreSQLExtensions, + createSQLiteExtensions, +} from "@/core/standard-extension"; import { getOuterbaseBase } from "@/outerbase-cloud/api"; import { OuterbaseAPISource } from "@/outerbase-cloud/api-type"; import { OuterbaseMySQLDriver } from "@/outerbase-cloud/database/mysql"; import { OuterbasePostgresDriver } from "@/outerbase-cloud/database/postgresql"; import { OuterbaseSqliteDriver } from "@/outerbase-cloud/database/sqlite"; +import OuterbaseQueryDriver from "@/outerbase-cloud/query-driver"; import { useEffect, useMemo, useState } from "react"; export default function OuterbaseSourcePageClient({ @@ -28,8 +35,13 @@ export default function OuterbaseSourcePageClient({ }); }, [workspaceId, baseId]); - const outerbaseDriver = useMemo(() => { - if (!workspaceId || !source) return null; + const savedDocDriver = useMemo(() => { + if (!workspaceId || !source?.id || !baseId) return null; + return new OuterbaseQueryDriver(workspaceId, baseId, source.id); + }, [workspaceId, baseId, source?.id]); + + const [outerbaseDriver, extensions] = useMemo(() => { + if (!workspaceId || !source) return [null, null]; const dialect = source.type; const outerbaseConfig = { @@ -40,19 +52,34 @@ export default function OuterbaseSourcePageClient({ }; if (dialect === "postgres") { - return new OuterbasePostgresDriver(outerbaseConfig); + return [ + new OuterbasePostgresDriver(outerbaseConfig), + new StudioExtensionManager(createPostgreSQLExtensions()), + ]; } else if (dialect === "mysql") { - return new OuterbaseMySQLDriver(outerbaseConfig); + return [ + new OuterbaseMySQLDriver(outerbaseConfig), + new StudioExtensionManager(createMySQLExtensions()), + ]; } - return new OuterbaseSqliteDriver(outerbaseConfig); + return [ + new OuterbaseSqliteDriver(outerbaseConfig), + new StudioExtensionManager(createSQLiteExtensions()), + ]; }, [workspaceId, source]); - if (!outerbaseDriver) { + if (!outerbaseDriver || !savedDocDriver) { return ; } return ( - + ); } diff --git a/src/app/(theme)/w/[workspaceId]/[baseId]/page.tsx b/src/app/(outerbase)/w/[workspaceId]/[baseId]/page.tsx similarity index 59% rename from src/app/(theme)/w/[workspaceId]/[baseId]/page.tsx rename to src/app/(outerbase)/w/[workspaceId]/[baseId]/page.tsx index d72da9cb..b2908d20 100644 --- a/src/app/(theme)/w/[workspaceId]/[baseId]/page.tsx +++ b/src/app/(outerbase)/w/[workspaceId]/[baseId]/page.tsx @@ -1,6 +1,5 @@ import ClientOnly from "@/components/client-only"; import OuterbaseSourcePageClient from "./page-client"; -import ThemeLayout from "@/app/(theme)/theme_layout"; interface OuterbaseSourcePageProps { params: Promise<{ @@ -15,13 +14,11 @@ export default async function OuterbaseSourcePage( const params = await props.params; return ( - - - - - + + + ); } diff --git a/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page-client.tsx b/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page-client.tsx new file mode 100644 index 00000000..78b65fde --- /dev/null +++ b/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page-client.tsx @@ -0,0 +1,15 @@ +"use client"; + +export default function BoardPageClient({ + workspaceId, + boardId, +}: { + workspaceId: string; + boardId: string; +}) { + return ( +
+ {workspaceId} {boardId} +
+ ); +} diff --git a/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page.tsx b/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page.tsx new file mode 100644 index 00000000..253e83f1 --- /dev/null +++ b/src/app/(outerbase)/w/[workspaceId]/board/[boardId]/page.tsx @@ -0,0 +1,19 @@ +import ClientOnly from "@/components/client-only"; +import BoardPageClient from "./page-client"; + +interface BoardPageProps { + params: Promise<{ workspaceId: string; boardId: string }>; +} + +export default async function BoardPage(props: BoardPageProps) { + const params = await props.params; + + return ( + + + + ); +} diff --git a/src/app/(outerbase)/w/[workspaceId]/layout.tsx b/src/app/(outerbase)/w/[workspaceId]/layout.tsx new file mode 100644 index 00000000..f9fe8102 --- /dev/null +++ b/src/app/(outerbase)/w/[workspaceId]/layout.tsx @@ -0,0 +1,17 @@ +import { OuterbaseSessionProvider } from "@/app/(outerbase)/session-provider"; +import ThemeLayout from "../../../(theme)/theme_layout"; +import { WorkspaceProvider } from "../../workspace-provider"; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/src/app/(outerbase)/w/[workspaceId]/page-client.tsx b/src/app/(outerbase)/w/[workspaceId]/page-client.tsx new file mode 100644 index 00000000..bf96afac --- /dev/null +++ b/src/app/(outerbase)/w/[workspaceId]/page-client.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { ButtonGroup, ButtonGroupItem } from "@/components/button-group"; +import { Toolbar } from "@/components/gui/toolbar"; +import ResourceCard from "@/components/resource-card"; +import { + getDatabaseFriendlyName, + getDatabaseIcon, + getDatabaseVisual, +} from "@/components/resource-card/utils"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { + CalendarDots, + SortAscending, + SortDescending, +} from "@phosphor-icons/react"; +import { useParams } from "next/navigation"; +import { NavigationBar } from "../../navigation"; +import { useWorkspaces } from "../../workspace-provider"; + +export default function WorkspaceListPageClient() { + const { workspaces } = useWorkspaces(); + const { workspaceId } = useParams<{ workspaceId: string }>(); + + // const boards = data?.boards ?? []; + const bases = + workspaces.find( + (workspace) => + workspace.short_name === workspaceId || workspace.id === workspaceId + )?.bases ?? []; + + return ( +
+ {/*

Board

+
+ {boards.map((board) => ( + + {board.name} + + ))} +
+ +

Base

*/} + + +
+
+ + + All + Bases + Boards + + + + + + + + + + + + + + + + + + + +
+ +
+ {bases.map((base) => ( + + Remove + + ))} +
+
+
+ ); +} diff --git a/src/app/(outerbase)/w/[workspaceId]/page.tsx b/src/app/(outerbase)/w/[workspaceId]/page.tsx new file mode 100644 index 00000000..006063e3 --- /dev/null +++ b/src/app/(outerbase)/w/[workspaceId]/page.tsx @@ -0,0 +1,11 @@ +"use client"; +import ClientOnly from "@/components/client-only"; +import WorkspaceListPageClient from "./page-client"; + +export default function WorkspaceListPage() { + return ( + + + + ); +} diff --git a/src/app/(outerbase)/workspace-provider.tsx b/src/app/(outerbase)/workspace-provider.tsx new file mode 100644 index 00000000..c9d24426 --- /dev/null +++ b/src/app/(outerbase)/workspace-provider.tsx @@ -0,0 +1,36 @@ +"use client"; +import { getOuterbaseWorkspace } from "@/outerbase-cloud/api"; +import { OuterbaseAPIWorkspace } from "@/outerbase-cloud/api-type"; +import { createContext, PropsWithChildren, useContext } from "react"; +import useSWR from "swr"; + +const WorkspaceContext = createContext<{ + workspaces: OuterbaseAPIWorkspace[]; + loading: boolean; +}>({ workspaces: [], loading: true }); + +export function useWorkspaces() { + return useContext(WorkspaceContext); +} + +export function WorkspaceProvider({ children }: PropsWithChildren) { + const { data, isLoading } = useSWR( + "workspaces", + () => { + return getOuterbaseWorkspace(); + }, + { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + } + ); + + return ( + + {children} + + ); +} diff --git a/src/app/(public)/docs/page.mdx b/src/app/(public)/docs/page.mdx index 6e95dfd7..87d86cb9 100644 --- a/src/app/(public)/docs/page.mdx +++ b/src/app/(public)/docs/page.mdx @@ -6,8 +6,8 @@ export const metadata = { - LibSQL Studio is an extremely powerful and lightweight SQLite GUI that runs in your browser. It comes packed with a ton of features, including: + - A powerful data editor capable of handling thousands of rows and columns without overwhelming your RAM. - A SQL query editor with syntax highlighting, tooltips, and auto-completion to boost your productivity. - Advanced tools for editing your table schema and indexes. diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx index 74c50a31..0fca86e2 100644 --- a/src/app/(public)/layout.tsx +++ b/src/app/(public)/layout.tsx @@ -1,12 +1,5 @@ -import { Analytics } from "@vercel/analytics/react"; -import { Inter } from "next/font/google"; -import { Toaster } from "@/components/ui/sonner"; import { Fragment } from "react"; -import Script from "next/script"; -import { cn } from "@/lib/utils"; -import PageTracker from "@/components/page-tracker"; - -const inter = Inter({ subsets: ["latin"] }); +import ThemeLayout from "../(theme)/theme_layout"; export default async function RootLayout({ children, @@ -14,12 +7,8 @@ export default async function RootLayout({ children: React.ReactNode; }) { return ( - + {children} - - - -