From 6215ae7eab91e55ac1f7d9f91eec31f477a6be00 Mon Sep 17 00:00:00 2001
From: "Visal .In"
Date: Sat, 28 Sep 2024 08:36:20 -0700
Subject: [PATCH] Restyle matching Outerbase (#166)
* styling sidebar
* change query tab editor to match outerbase
* add more style
* fixing explain query
* redesign side menu
* restyle sidebar
* add sidemenu
* change all the libsql studio name to outerbase studio
* pull the version from package
* restyle the connection list match turso style
* upgrade everything to mono color
* reskin to outerbase
---
next.config.js | 4 +
package-lock.json | 17 +-
package.json | 3 +-
src/app/(public)/databases/mysql/page.tsx | 10 +-
src/app/(public)/docs/layout.tsx | 3 +-
src/app/(public)/login/page.tsx | 3 +-
src/app/(public)/page.tsx | 32 ++-
src/app/(public)/privacy/page.tsx | 3 +-
src/app/(public)/terms/page.tsx | 5 +-
src/app/(theme)/connect/connection-list.tsx | 61 ++++--
src/app/(theme)/connect/driver-dropdown.tsx | 189 ++++++++++--------
src/app/(theme)/connect/page-client.tsx | 106 +++-------
src/app/(theme)/connect/page.tsx | 5 +-
.../(theme)/connect/saved-connection-card.tsx | 56 +++---
.../connect/saved-connection-content.tsx | 12 +-
.../connect/saved-connection-storage.ts | 26 ++-
src/app/(theme)/connect/saved-connection.tsx | 3 +-
.../(theme)/playground/client/page-client.tsx | 172 +++++++---------
src/app/globals.css | 12 +-
src/app/layout.tsx | 8 +-
src/components/gui/database-gui.tsx | 17 +-
src/components/gui/main-connection.tsx | 3 +-
src/components/gui/query-explanation.tsx | 11 +-
src/components/gui/save-doc-button.tsx | 2 +-
src/components/gui/schema-sidebar-list.tsx | 7 +-
src/components/gui/schema-sidebar.tsx | 37 ++--
src/components/gui/sidebar-tab.tsx | 162 +++++++++++----
.../saved-doc-tab/create-namespace-button.tsx | 73 +++----
.../gui/sidebar/saved-doc-tab/index.tsx | 64 +++++-
src/components/gui/sortable-tab.tsx | 26 ++-
src/components/gui/sql-editor/index.tsx | 3 +
src/components/gui/tabs/query-tab.tsx | 120 +++++++----
src/components/gui/windows-tab.tsx | 55 ++---
src/components/icons/outerbase-icon.tsx | 140 ++++++++++++-
src/components/mdx/docs-navigation.tsx | 2 +-
src/components/website-layout.tsx | 8 +-
src/const.ts | 2 +
src/messages/open-tab.tsx | 4 +-
38 files changed, 899 insertions(+), 567 deletions(-)
diff --git a/next.config.js b/next.config.js
index 6ca26217..4910094f 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,10 +1,14 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const withMDX = require("@next/mdx")();
+const pkg = require("./package.json");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
+ env: {
+ NEXT_PUBLIC_STUDIO_VERSION: pkg.version,
+ },
};
module.exports = withMDX(nextConfig);
diff --git a/package-lock.json b/package-lock.json
index 8eb006f5..62041abb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@libsqlstudio/studio",
- "version": "0.7.1",
+ "version": "0.7.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@libsqlstudio/studio",
- "version": "0.7.1",
+ "version": "0.7.2",
"dependencies": {
"@aws-sdk/client-s3": "^3.540.0",
"@blocknote/core": "^0.12.1",
@@ -23,6 +23,7 @@
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^14.2.4",
+ "@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
@@ -5642,6 +5643,18 @@
"node": ">= 8"
}
},
+ "node_modules/@phosphor-icons/react": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.7.tgz",
+ "integrity": "sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ==",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8",
+ "react-dom": ">= 16.8"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
diff --git a/package.json b/package.json
index 5a39097e..dc90a121 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@libsqlstudio/studio",
- "version": "0.7.1",
+ "version": "0.7.2",
"private": false,
"scripts": {
"dev": "next dev -p 3008",
@@ -42,6 +42,7 @@
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^14.2.4",
+ "@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
diff --git a/src/app/(public)/databases/mysql/page.tsx b/src/app/(public)/databases/mysql/page.tsx
index ca900db1..f6dedfbc 100644
--- a/src/app/(public)/databases/mysql/page.tsx
+++ b/src/app/(public)/databases/mysql/page.tsx
@@ -1,16 +1,16 @@
import { MySQLIcon } from "@/components/icons/outerbase-icon";
import WebsiteLayout from "@/components/website-layout";
+import { WEBSITE_NAME } from "@/const";
import { Metadata } from "next";
-const siteDescription =
- "LibSQL Studio is a fully-featured, lightweight GUI client for managing MySQL databases";
+const siteDescription = `${WEBSITE_NAME} is a fully-featured, lightweight GUI client for managing MySQL databases`;
export const metadata: Metadata = {
- title: "MySQL - LibSQL Studio",
+ title: `MySQL - ${WEBSITE_NAME}`,
keywords: ["mysql", "studio", "browser", "editor", "gui", "online", "client"],
description: siteDescription,
openGraph: {
- siteName: "LibSQL Studio",
+ siteName: WEBSITE_NAME,
description: siteDescription,
},
};
@@ -34,7 +34,7 @@ function HeroSection() {
MySQL Support
- LibSQL Studio is a lightweight, fully-featured GUI client for MySQL
+ {WEBSITE_NAME} is a lightweight, fully-featured GUI client for MySQL
databases. It enables you to manage and view your database, or
expose your database interface externally and much more.
diff --git a/src/app/(public)/docs/layout.tsx b/src/app/(public)/docs/layout.tsx
index 173dab82..9f5f774e 100644
--- a/src/app/(public)/docs/layout.tsx
+++ b/src/app/(public)/docs/layout.tsx
@@ -1,8 +1,9 @@
import { DocLayout } from "@/components/mdx/docs";
+import { WEBSITE_NAME } from "@/const";
const TableContent = [
{
- title: "LibSQL Studio",
+ title: WEBSITE_NAME,
href: "/docs",
},
{
diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx
index 6c96f162..e2bd2da2 100644
--- a/src/app/(public)/login/page.tsx
+++ b/src/app/(public)/login/page.tsx
@@ -1,5 +1,6 @@
"use server";
import LogoLoading from "@/components/gui/logo-loading";
+import { WEBSITE_NAME } from "@/const";
import { env } from "@/env";
import Link from "next/link";
@@ -18,7 +19,7 @@ export default async function LoginPage() {
- LibSQL Studio is free and doesn't require login. However,
+ {WEBSITE_NAME} is free and doesn't require login. However,
logged-in users get extra features:
diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx
index 89d06e9e..7dffc617 100644
--- a/src/app/(public)/page.tsx
+++ b/src/app/(public)/page.tsx
@@ -5,16 +5,14 @@ import {
} from "@/components/icons/outerbase-icon";
import { buttonVariants } from "@/components/ui/button";
import WebsiteLayout from "@/components/website-layout";
+import { WEBSITE_GENERAL_DESCRIPTION, WEBSITE_NAME } from "@/const";
import { cn } from "@/lib/utils";
import type { Metadata } from "next";
import Link from "next/link";
import { PropsWithChildren } from "react";
-const siteDescription =
- "LibSQL Studio is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything";
-
export const metadata: Metadata = {
- title: "LibSQL Studio",
+ title: WEBSITE_NAME,
keywords: [
"libsql",
"rqlite",
@@ -27,10 +25,10 @@ export const metadata: Metadata = {
"online",
"client",
],
- description: siteDescription,
+ description: WEBSITE_GENERAL_DESCRIPTION,
openGraph: {
- siteName: "LibSQL Studio",
- description: siteDescription,
+ siteName: WEBSITE_NAME,
+ description: WEBSITE_GENERAL_DESCRIPTION,
},
};
@@ -84,14 +82,14 @@ function HeroSection() {
Powerful Database Client
- LibSQL Studio is a fully-featured, lightweight GUI client for
+ {WEBSITE_NAME} is a fully-featured, lightweight GUI client for
managing Turso, LibSQL, Cloudflare D1, rqlite, MySQL, and
PostgreSQL. It runs entirely in your browser, so there's no
need to download anything.
-
+
@@ -121,7 +119,7 @@ function SupportDriver() {
PostgreSQL
- Coming Soon 15th Sep 2024
+ Coming Soon 7th Oct 2024
@@ -186,8 +184,8 @@ function FeatureList() {
- LibSQL Studio allows you to save your queries and organize them into
- folders, with the ability to sync across multiple devices.
+ {WEBSITE_NAME} allows you to save your queries and organize them
+ into folders, with the ability to sync across multiple devices.
@@ -204,7 +202,7 @@ function FeatureList() {
- LibSQL Studio features a user-friendly query editor equipped with
+ {WEBSITE_NAME} features a user-friendly query editor equipped with
auto-completion and function hint tooltips. It allows you to execute
multiple queries simultaneously and view their results efficiently.
@@ -223,8 +221,8 @@ function FeatureList() {
- LibSQL Studio allows you to quickly create, modify, and remove table
- columns with just a few clicks without writing any SQL.
+ {WEBSITE_NAME} allows you to quickly create, modify, and remove
+ table columns with just a few clicks without writing any SQL.
@@ -239,7 +237,7 @@ function CommunitySection() {
Join our community for the latest updates, roadmap insights, and
- discussions on the future of LibSQL Studio.
+ discussions on the future of {WEBSITE_NAME}.
@@ -310,7 +308,7 @@ export default async function MainPage() {
- LibSQL Studio has many features and is regularly updated. Since it
+ {WEBSITE_NAME} has many features and is regularly updated. Since it
is an{" "}
Effective Date: 2024-06-23
{`
- Welcome to LibSQL Studio ("we," "our," or "us"). We are committed to protecting your privacy and ensuring
+ Welcome to ${WEBSITE_NAME} ("we," "our," or "us"). We are committed to protecting your privacy and ensuring
that your personal information is handled in a safe and responsible manner. This Privacy Policy explains how we
collect, use, and protect your information when you use our tool.
`}
diff --git a/src/app/(public)/terms/page.tsx b/src/app/(public)/terms/page.tsx
index 9229edae..22a19935 100644
--- a/src/app/(public)/terms/page.tsx
+++ b/src/app/(public)/terms/page.tsx
@@ -1,4 +1,5 @@
import WebsiteLayout from "@/components/website-layout";
+import { WEBSITE_NAME } from "@/const";
export default function TermPage() {
return (
@@ -9,7 +10,7 @@ export default function TermPage() {
Effective Date: 2024-06-23
{`
- Welcome to LibSQL Studio ("we," "our," or "us").
+ Welcome to ${WEBSITE_NAME} ("we," "our," or "us").
By accessing or using our tool, you agree to be bound by these Terms and Conditions ("Terms").
If you do not agree to these Terms, please do not use the Tool.
`}
@@ -53,7 +54,7 @@ export default function TermPage() {
6. Limitation of Liability
- {`LibSQL Studio is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the Tool.`}
+ {`${WEBSITE_NAME} is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the Tool.`}
By using the Tool, you acknowledge that you have read, understood, and
diff --git a/src/app/(theme)/connect/connection-list.tsx b/src/app/(theme)/connect/connection-list.tsx
index a0ab7959..9eb65565 100644
--- a/src/app/(theme)/connect/connection-list.tsx
+++ b/src/app/(theme)/connect/connection-list.tsx
@@ -20,31 +20,40 @@ import ConnectionItemCard from "./saved-connection-card";
import { getDatabases } from "@/lib/api/fetch-databases";
import { User } from "lucia";
import QuickConnect from "./quick-connect";
-import { LucideChevronDown } from "lucide-react";
+import { LucideChevronDown, LucideSearch } from "lucide-react";
import DriverDropdown from "./driver-dropdown";
+import { cn } from "@/lib/utils";
function ConnectionListSection({
data,
name,
+ search,
onRemove,
onEdit,
}: Readonly<{
+ search: string;
data: SavedConnectionItem[];
name?: string;
onRemove: Dispatch>;
onEdit: Dispatch>;
}>) {
const body = useMemo(() => {
- if (data.length === 0)
+ const filteredData = data.filter((d) =>
+ d.name.toLowerCase().includes(search.toLowerCase())
+ );
+
+ if (filteredData.length === 0)
return (
-
- There is no connection. Please add new connection
+
+ {search
+ ? `There is no record with '${search}'`
+ : "There is no base. Please add new base"}
);
return (
-
- {data.map((conn) => {
+
+ {filteredData.map((conn) => {
return (
);
- }, [data, onRemove, onEdit]);
+ }, [name, data, search, onRemove, onEdit]);
return (
<>
- {name && {name} }
+ {name && (
+ {name}
+ )}
{body}
>
);
@@ -73,6 +84,7 @@ function ConnectionListSection({
export default function ConnectionList({
user,
}: Readonly<{ user: User | null }>) {
+ const [search, setSearch] = useState("");
const [quickConnect, setQuickConnect] = useState(
null
);
@@ -199,24 +211,44 @@ export default function ConnectionList({
}
return (
- <>
-
+
+
+
+ Bases
+
+
+
+
+
+
+
+
setSearch(e.currentTarget.value)}
+ type="text"
+ className="bg-inherit p-2 pl-2 pr-2 outline-none text-sm h-full grow"
+ placeholder="Search base name"
+ />
+
+
+
-
+
New Connection
- Quick Connect
+ Quick Connect
{dialogComponent}
)}
- >
+
);
}
diff --git a/src/app/(theme)/connect/driver-dropdown.tsx b/src/app/(theme)/connect/driver-dropdown.tsx
index 88b3fc4c..b2d51e1b 100644
--- a/src/app/(theme)/connect/driver-dropdown.tsx
+++ b/src/app/(theme)/connect/driver-dropdown.tsx
@@ -8,6 +8,15 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useRouter } from "next/navigation";
+import {
+ CloudflareIcon,
+ MySQLIcon,
+ RqliteIcon,
+ SQLiteIcon,
+ TursoIcon,
+ ValtownIcon,
+} from "@/components/icons/outerbase-icon";
+import Link from "next/link";
interface Props {
onSelect: (driver: SupportedDriver) => void;
@@ -22,94 +31,110 @@ export default function DriverDropdown({
return (
{children}
-
- {
- onSelect("turso");
- }}
- >
-
-
-
-
Turso / LibSQL
-
SQLite for Product
-
-
-
- {
- onSelect("cloudflare-d1");
- }}
- >
-
-
+
+
+
+
+ SQLite-based Database
+
-
Cloudflare D1
-
- D1 is Cloudflare’s native serverless database
-
-
-
-
-
{
- onSelect("rqlite");
- }}
- >
-
-
-
-
rqlite
-
- Distributed database built on SQLite
-
-
-
-
-
{
- onSelect("valtown");
- }}
- >
-
-
-
-
val.town
-
Private SQLite database
-
-
-
-
-
{
- onSelect("sqlite-filehandler");
- }}
- >
-
-
-
-
Open SQLite File
-
- Open SQLite file database directly in your browser.
-
+
{
+ onSelect("turso");
+ }}
+ >
+
+
+
+
{
+ onSelect("cloudflare-d1");
+ }}
+ >
+
+
+
+
{
+ onSelect("rqlite");
+ }}
+ >
+
+
+
+
{
+ onSelect("valtown");
+ }}
+ >
+
+
+
+
+
+
{
+ onSelect("sqlite-filehandler");
+ }}
+ >
+
+
+
+
{
+ router.push("/playground/client");
+ }}
+ >
+
+
-
-
{
- router.push("/playground/client");
- }}
- >
-
-
+
+
+
+ Other
+
+
-
Blank SQLite
-
- Start a new SQLite database directly in your browser.
-
+
+
+
+
+
+
-
+
);
diff --git a/src/app/(theme)/connect/page-client.tsx b/src/app/(theme)/connect/page-client.tsx
index 5aa0c7d1..ddfc3e6a 100644
--- a/src/app/(theme)/connect/page-client.tsx
+++ b/src/app/(theme)/connect/page-client.tsx
@@ -1,91 +1,49 @@
"use client";
import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
-import { PropsWithChildren } from "react";
import ConnectionList from "./connection-list";
import Link from "next/link";
-import { LucideCopy, LucideMoon, LucideSun } from "lucide-react";
+import { LucideMoon, LucideSun } from "lucide-react";
import { User } from "lucia";
import { useTheme } from "@/context/theme-provider";
-function TabContainer({ children }: Readonly) {
- return (
-
- );
-}
-
-function TabItem({
- active,
- text,
-}: Readonly<{ active?: boolean; text: string }>) {
- const className = cn(
- "px-3 py-1",
- active
- ? "bg-background border-b border-b-background border-t border-x"
- : "border-b border-gray-300"
- );
-
- return {text}
;
-}
-
-function Header({ user }: Readonly<{ user: User | null }>) {
+export default function ConnectBody({ user }: Readonly<{ user: User | null }>) {
const { theme, toggleTheme } = useTheme();
return (
-
-
- Welcome, {user ? user.name : "Guest"}
- {user?.id && (
- {
- window.navigator.clipboard.writeText(user.id);
- }}
+
+
+
+
-
- ID:
- {user.id}
-
-
-
- )}
-
-
-
-
toggleTheme()}>
- {theme === "dark" ? (
-
+
+
+
+
+
+
+
+ toggleTheme()}>
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+
+
+ {user ? (
+
+ Sign Out
+
) : (
-
+
+ Sign In
+
)}
-
-
- {user ? (
-
- Sign Out
-
- ) : (
-
- Sign In
-
- )}
+
-
- );
-}
-
-export default function ConnectBody({ user }: Readonly<{ user: User | null }>) {
- "use client";
- return (
-
-
-
-
-
diff --git a/src/app/(theme)/connect/page.tsx b/src/app/(theme)/connect/page.tsx
index c02987af..318da86a 100644
--- a/src/app/(theme)/connect/page.tsx
+++ b/src/app/(theme)/connect/page.tsx
@@ -2,10 +2,11 @@ import { Metadata } from "next";
import { getSessionFromCookie } from "@/lib/auth";
import ConnectBody from "./page-client";
import ThemeLayout from "../theme_layout";
+import { WEBSITE_NAME } from "@/const";
export const metadata: Metadata = {
- title: "LibSQL Studio",
- description: "LibSQL Studio",
+ title: WEBSITE_NAME,
+ description: WEBSITE_NAME,
};
export default async function Home() {
diff --git a/src/app/(theme)/connect/saved-connection-card.tsx b/src/app/(theme)/connect/saved-connection-card.tsx
index 311b8b72..66ea332e 100644
--- a/src/app/(theme)/connect/saved-connection-card.tsx
+++ b/src/app/(theme)/connect/saved-connection-card.tsx
@@ -6,7 +6,7 @@ import {
ContextMenuItem,
ContextMenuSeparator,
} from "@/components/ui/context-menu";
-import { LucidePencil, LucideTrash, LucideUsers } from "lucide-react";
+import { LucidePencil, LucideTrash } from "lucide-react";
import { useState } from "react";
import {
SavedConnectionItem,
@@ -26,6 +26,8 @@ export default function ConnectionItemCard({
}>) {
const [open, setOpen] = useState(false);
+ const DatabaseIcon = DRIVER_DETAIL[conn.driver ?? "turso"].icon;
+
return (
-
-
-
-
-
-
{conn.name}
- {conn.shared ? (
-
-
- Shared by {conn.shared.sharedBy.name}
-
- ) : (
-
- {conn.description || No description }
-
- )}
+
+
+
+
+
+
+
+
{conn.name}
+
+ {DRIVER_DETAIL[conn.driver ?? "turso"].displayName}
+
+
+
+
+
+
+
+ {conn.description || "No description"}
+
+
diff --git a/src/app/(theme)/connect/saved-connection-content.tsx b/src/app/(theme)/connect/saved-connection-content.tsx
index 75c732d1..08c33b31 100644
--- a/src/app/(theme)/connect/saved-connection-content.tsx
+++ b/src/app/(theme)/connect/saved-connection-content.tsx
@@ -19,6 +19,8 @@ export default function ConnectionDialogContent({
driver: SupportedDriver;
}>
>) {
+ const DatabaseIcon = DRIVER_DETAIL[driver ?? "turso"].icon;
+
return (
-
-
-
{title}
+
diff --git a/src/app/(theme)/connect/saved-connection-storage.ts b/src/app/(theme)/connect/saved-connection-storage.ts
index 59702360..18cf813e 100644
--- a/src/app/(theme)/connect/saved-connection-storage.ts
+++ b/src/app/(theme)/connect/saved-connection-storage.ts
@@ -1,5 +1,13 @@
+import {
+ CloudflareIcon,
+ RqliteIcon,
+ SQLiteIcon,
+ TursoIcon,
+ ValtownIcon,
+} from "@/components/icons/outerbase-icon";
import { ApiUser } from "@/lib/api/api-database-response";
import parseSafeJson from "@/lib/json-safe";
+import { FunctionComponent } from "react";
export interface DriverDetailField {
name: keyof SavedConnectionItemConfigConfig;
@@ -14,8 +22,9 @@ export interface DriverDetailField {
}
export interface DriverDetail {
+ displayName: string;
name: string;
- icon: string;
+ icon: FunctionComponent<{ className: string }>;
disableRemote?: boolean;
fields: DriverDetailField[];
}
@@ -23,8 +32,9 @@ export interface DriverDetail {
export const DRIVER_DETAIL: Record
=
Object.freeze({
"sqlite-filehandler": {
+ displayName: "SQLite",
name: "sqlite-filehandler",
- icon: "/sqlite-icon.svg",
+ icon: SQLiteIcon,
disableRemote: true,
fields: [
{
@@ -38,7 +48,8 @@ export const DRIVER_DETAIL: Record =
},
turso: {
name: "turso",
- icon: "/turso.jpeg",
+ displayName: "Turso",
+ icon: TursoIcon,
fields: [
{
name: "url",
@@ -67,7 +78,8 @@ export const DRIVER_DETAIL: Record =
},
valtown: {
name: "valtown",
- icon: "/valtown.svg",
+ displayName: "Valtown",
+ icon: ValtownIcon,
prefill: "",
fields: [
{
@@ -81,7 +93,8 @@ export const DRIVER_DETAIL: Record =
},
"cloudflare-d1": {
name: "cloudflare-d1",
- icon: "/cloudflare.png",
+ displayName: "Cloudflare D1",
+ icon: CloudflareIcon,
fields: [
{
name: "username",
@@ -108,7 +121,8 @@ export const DRIVER_DETAIL: Record =
},
rqlite: {
name: "rqlite",
- icon: "/rqlite.png",
+ displayName: "rqlite",
+ icon: RqliteIcon,
fields: [
{
name: "url",
diff --git a/src/app/(theme)/connect/saved-connection.tsx b/src/app/(theme)/connect/saved-connection.tsx
index be9520d0..3c42edd3 100644
--- a/src/app/(theme)/connect/saved-connection.tsx
+++ b/src/app/(theme)/connect/saved-connection.tsx
@@ -13,13 +13,14 @@ import {
import SavedConnectionConfig from "./saved-connection-config";
import { createDatabase } from "@/lib/api/fetch-databases";
import { User } from "lucia";
+import { WEBSITE_NAME } from "@/const";
type SaveConnectionStep = "storage" | "config";
export function RqliteInstruction() {
return (
- You should include LibSQL Studio in the list of allowed origins for CORS
+ You should include {WEBSITE_NAME} in the list of allowed origins for CORS
(Cross-Origin Resource Sharing)
{`rqlited --http-allow-origin="https://libsqlstudio.com"`}
diff --git a/src/app/(theme)/playground/client/page-client.tsx b/src/app/(theme)/playground/client/page-client.tsx
index 6b4dcfbe..34bbc5a5 100644
--- a/src/app/(theme)/playground/client/page-client.tsx
+++ b/src/app/(theme)/playground/client/page-client.tsx
@@ -1,7 +1,6 @@
"use client";
import { saveAs } from "file-saver";
import MyStudio from "@/components/my-studio";
-import { Button } from "@/components/ui/button";
import SqljsDriver from "@/drivers/sqljs-driver";
import { LucideFile, LucideLoader, LucideRefreshCw } from "lucide-react";
import Script from "next/script";
@@ -10,14 +9,13 @@ import { Database, SqlJsStatic } from "sql.js";
import ScreenDropZone from "@/components/screen-dropzone";
import { toast } from "sonner";
import downloadFileFromUrl from "@/lib/download-file";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
import { useSearchParams } from "next/navigation";
import { localDb } from "@/indexdb";
import { SavedConnectionLocalStorage } from "@/app/(theme)/connect/saved-connection-storage";
+import {
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+} from "@/components/ui/dropdown-menu";
export default function PlaygroundEditorBody({
preloadDatabase,
@@ -132,102 +130,80 @@ export default function PlaygroundEditorBody({
)}
-
-
{
- if (rawDb) {
- if (handler) {
- handler
- .createWritable()
- .then((writable) => {
- writable.write(rawDb.export());
- writable.close();
- toast.success(
-
- Successfully save {fileName}
-
- );
- db?.resetChange();
- })
- .catch(console.error);
- } else {
- saveAs(
- new Blob([rawDb.export()], {
- type: "application/x-sqlite3",
- }),
- "sqlite-dump.db"
- );
- }
+
+ {
+ if (rawDb) {
+ if (handler) {
+ handler
+ .createWritable()
+ .then((writable) => {
+ writable.write(rawDb.export());
+ writable.close();
+ toast.success(
+
+ Successfully save {fileName}
+
+ );
+ db?.resetChange();
+ })
+ .catch(console.error);
+ } else {
+ saveAs(
+ new Blob([rawDb.export()], {
+ type: "application/x-sqlite3",
+ }),
+ "sqlite-dump.db"
+ );
}
- }}
- >
- Save
-
-
{
- window
- .showOpenFilePicker({
- types: [
- {
- description: "SQLite Files",
- accept: {
- "application/x-sqlite3": [
- ".db",
- ".sdb",
- ".sqlite",
- ".db3",
- ".s3db",
- ".sqlite3",
- ".sl3",
- ".db2",
- ".s2db",
- ".sqlite2",
- ".sl2",
- ],
- },
+ }
+ }}
+ >
+ Save
+
+ {
+ window
+ .showOpenFilePicker({
+ types: [
+ {
+ description: "SQLite Files",
+ accept: {
+ "application/x-sqlite3": [
+ ".db",
+ ".sdb",
+ ".sqlite",
+ ".db3",
+ ".s3db",
+ ".sqlite3",
+ ".sl3",
+ ".db2",
+ ".s2db",
+ ".sqlite2",
+ ".sl2",
+ ],
},
- ],
- })
- .then(([fileHandler]) => {
- setHandler(fileHandler);
- });
- }}
- >
- Open
-
- {handler && (
-
-
-
-
-
-
-
-
- Refresh
-
+ },
+ ],
+ })
+ .then(([fileHandler]) => {
+ setHandler(fileHandler);
+ });
+ }}
+ >
+ Open SQLite file
+
-
- LibSQL Studio loads data into memory. If the file changes, it
- is unaware of the update.
-
+ {handler && (
+
+
+ Refresh
+
+ )}
-
- To reflect the changes, use the refresh option to reload the
- data from the file.
-
-
-
- )}
-
+
);
}, [rawDb, handler, db, fileName, onReloadDatabase]);
diff --git a/src/app/globals.css b/src/app/globals.css
index 2a44e78c..9debf4fe 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -44,7 +44,7 @@ body {
--secondary: #f1f5f9;
--secondary-foreground: #444;
- --muted: 210 40% 96.1%;
+ --muted: #eee;
--muted-foreground: #888;
--accent: #f1f5f9;
@@ -64,7 +64,7 @@ body {
}
.dark {
- --background: #181818;
+ --background: #000;
--foreground: #aaa;
--color-surface: black;
@@ -79,7 +79,7 @@ body {
--primary: white;
--primary-foreground: black;
- --secondary: #1f1f1f;
+ --secondary: #171717;
--secondary-foreground: #eee;
--muted: #333;
@@ -91,7 +91,7 @@ body {
--destructive: red;
--destructive-foreground: white;
- --border: #2b2b2b;
+ --border: #313131;
--input: #333;
--ring: #444;
@@ -140,6 +140,10 @@ body {
color: #e84393;
}
+.cm-focused {
+ outline: none !important;
+}
+
.dark .cm-table-name {
color: #fd79a8;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 35afb88c..06ca143d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -3,12 +3,12 @@ import type { Metadata } from "next";
import "./globals.css";
import "./codemirror-override.css";
import { Fragment } from "react";
+import { WEBSITE_NAME } from "@/const";
-const siteDescription =
- "LibSQL Studio is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything";
+const siteDescription = `${WEBSITE_NAME} is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything`;
export const metadata: Metadata = {
- title: "LibSQL Studio",
+ title: WEBSITE_NAME,
keywords: [
"libsql",
"rqlite",
@@ -22,7 +22,7 @@ export const metadata: Metadata = {
],
description: siteDescription,
openGraph: {
- siteName: "LibSQL Studio",
+ siteName: WEBSITE_NAME,
description: siteDescription,
},
};
diff --git a/src/components/gui/database-gui.tsx b/src/components/gui/database-gui.tsx
index b5670cf6..aa79eb98 100644
--- a/src/components/gui/database-gui.tsx
+++ b/src/components/gui/database-gui.tsx
@@ -11,12 +11,6 @@ import useMessageListener from "@/components/hooks/useMessageListener";
import { MessageChannelName } from "@/const";
import { OpenTabsProps, receiveOpenTabMessage } from "@/messages/open-tab";
import QueryWindow from "@/components/gui/tabs/query-tab";
-import {
- LucideBookmark,
- LucideCode,
- LucideDatabase,
- LucideSettings,
-} from "lucide-react";
import SidebarTab, { SidebarTabItem } from "./sidebar-tab";
import SchemaView from "./schema-sidebar";
import SettingSidebar from "./sidebar/setting-sidebar";
@@ -24,6 +18,7 @@ import SettingSidebar from "./sidebar/setting-sidebar";
import { useDatabaseDriver } from "@/context/driver-provider";
import SavedDocTab from "./sidebar/saved-doc-tab";
import { useSchema } from "@/context/schema-provider";
+import { Binoculars, GearSix, Table } from "@phosphor-icons/react";
export default function DatabaseGui() {
const DEFAULT_WIDTH = 300;
@@ -44,7 +39,7 @@ export default function DatabaseGui() {
identifier: "query",
key: "query",
component: ,
- icon: LucideCode,
+ icon: Binoculars,
},
]);
@@ -93,14 +88,14 @@ export default function DatabaseGui() {
key: "database",
name: "Schema",
content: ,
- icon: LucideDatabase,
+ icon: ,
},
docDriver
? {
key: "saved",
- name: "Saved",
+ name: "Queries",
content: ,
- icon: LucideBookmark,
+ icon: ,
}
: undefined,
collaborationDriver
@@ -108,7 +103,7 @@ export default function DatabaseGui() {
key: "setting",
name: "Setting",
content: ,
- icon: LucideSettings,
+ icon: ,
}
: undefined,
].filter(Boolean) as SidebarTabItem[];
diff --git a/src/components/gui/main-connection.tsx b/src/components/gui/main-connection.tsx
index d499caa8..55fc1cfc 100644
--- a/src/components/gui/main-connection.tsx
+++ b/src/components/gui/main-connection.tsx
@@ -8,6 +8,7 @@ import { useConfig } from "@/context/config-provider";
import { AutoCompleteProvider } from "@/context/auto-complete-provider";
import InternalPubSub from "@/components/lib/internal-pubsub";
import { SchemaProvider } from "@/context/schema-provider";
+import { WEBSITE_NAME } from "@/const";
function MainConnection() {
const { databaseDriver: driver } = useDatabaseDriver();
@@ -41,7 +42,7 @@ function MainConnectionContainer() {
}, [driver]);
useEffect(() => {
- document.title = name + " - LibSQL Studio";
+ document.title = name + " - " + WEBSITE_NAME;
}, [name]);
return (
diff --git a/src/components/gui/query-explanation.tsx b/src/components/gui/query-explanation.tsx
index a6e2b1cd..8f79f974 100644
--- a/src/components/gui/query-explanation.tsx
+++ b/src/components/gui/query-explanation.tsx
@@ -49,9 +49,14 @@ function buildQueryExplanationTree(nodes: ExplanationRow[]) {
export function QueryExplanation(props: QueryExplanationProps) {
const tree = useMemo(() => {
- const isExplanationRows = z
- .array(queryExplanationRowSchema)
- .safeParse(props.data.rows);
+ const isExplanationRows = z.array(queryExplanationRowSchema).safeParse(
+ props.data.rows.map((r) => ({
+ ...r,
+ id: Number(r.id),
+ parent: Number(r.parent),
+ notused: Number(r.notused),
+ }))
+ );
if (isExplanationRows.error) {
return { _tag: "ERROR" as const, value: isExplanationRows.error };
diff --git a/src/components/gui/save-doc-button.tsx b/src/components/gui/save-doc-button.tsx
index c23fc7d0..a622fa88 100644
--- a/src/components/gui/save-doc-button.tsx
+++ b/src/components/gui/save-doc-button.tsx
@@ -82,8 +82,8 @@ export default function SaveDocButton({
onSaveQuery()}
- className="mr-2"
disabled={loading}
+ variant={"secondary"}
>
{loading ? (
diff --git a/src/components/gui/schema-sidebar-list.tsx b/src/components/gui/schema-sidebar-list.tsx
index d6813a3b..e44039b4 100644
--- a/src/components/gui/schema-sidebar-list.tsx
+++ b/src/components/gui/schema-sidebar-list.tsx
@@ -1,4 +1,4 @@
-import { LucideCog, LucideDatabase, LucideView, Table2 } from "lucide-react";
+import { LucideCog, LucideDatabase, LucideView } from "lucide-react";
import { OpenContextMenuList } from "@/messages/open-context-menu";
import { useCallback, useEffect, useMemo, useState } from "react";
import { openTab } from "@/messages/open-tab";
@@ -6,6 +6,7 @@ import { DatabaseSchemaItem } from "@/drivers/base-driver";
import { useSchema } from "@/context/schema-provider";
import { ListView, ListViewItem } from "../listview";
import { useDatabaseDriver } from "@/context/driver-provider";
+import { Table } from "@phosphor-icons/react";
interface SchemaListProps {
search: string;
@@ -15,8 +16,8 @@ function prepareListViewItem(
schema: DatabaseSchemaItem[]
): ListViewItem[] {
return schema.map((s) => {
- let icon = Table2;
- let iconClassName = "text-blue-600 dark:text-blue-300";
+ let icon = Table;
+ let iconClassName = "";
if (s.type === "trigger") {
icon = LucideCog;
diff --git a/src/components/gui/schema-sidebar.tsx b/src/components/gui/schema-sidebar.tsx
index a95d8b2b..e3a386f9 100644
--- a/src/components/gui/schema-sidebar.tsx
+++ b/src/components/gui/schema-sidebar.tsx
@@ -1,10 +1,11 @@
-import { LucidePlus, LucideSearch } from "lucide-react";
+import { LucideSearch } from "lucide-react";
import { useCallback, useState } from "react";
import SchemaList from "./schema-sidebar-list";
-import ListButtonItem from "./list-button-item";
-import { Separator } from "../ui/separator";
import { openTab } from "@/messages/open-tab";
import { useSchema } from "@/context/schema-provider";
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "../ui/button";
+import { Plus } from "@phosphor-icons/react";
export default function SchemaView() {
const [search, setSearch] = useState("");
@@ -19,8 +20,23 @@ export default function SchemaView() {
return (
-
-
+
+
+
+
@@ -37,17 +53,6 @@ export default function SchemaView() {
-
-
);
}
diff --git a/src/components/gui/sidebar-tab.tsx b/src/components/gui/sidebar-tab.tsx
index cd8283b0..563e78da 100644
--- a/src/components/gui/sidebar-tab.tsx
+++ b/src/components/gui/sidebar-tab.tsx
@@ -1,17 +1,21 @@
import { useConfig } from "@/context/config-provider";
import { useTheme } from "@/context/theme-provider";
import { cn } from "@/lib/utils";
-import {
- LucideChevronLeft,
- LucideIcon,
- LucideMoon,
- LucideSun,
-} from "lucide-react";
import { ReactElement, useState } from "react";
+import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
+import Link from "next/link";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "../ui/dropdown-menu";
+import { ArrowLeft, MoonStars, Sun } from "@phosphor-icons/react";
export interface SidebarTabItem {
key: string;
- icon: LucideIcon;
+ icon: ReactElement;
name: string;
content: ReactElement;
}
@@ -45,9 +49,9 @@ export default function SidebarTab({ tabs }: Readonly
) {
}
return (
-
-
-
+
+
+ {/*
{config.onBack && (
) {
)}
-
-
- {config.sideBarFooterComponent && (
-
{config.sideBarFooterComponent}
- )}
+
*/}
-
-
- {tabs.map(({ key, name, icon: Icon }, idx) => {
- return (
-
{
- if (!loadedIndex[idx]) {
- loadedIndex[idx] = true;
- setLoadedIndex([...loadedIndex]);
- }
-
- if (idx !== selectedIndex) {
- setSelectedIndex(idx);
- }
+
+
+
+
+
+
+
-
-
{name}
-
+
+
Outerbase Studio
+
+ v{process.env.NEXT_PUBLIC_STUDIO_VERSION}
+
+
+
+
+ {config.sideBarFooterComponent}
+
+ {!disableToggle && (
+ {
+ toggleTheme();
+ }}
+ >
+ {theme === "dark" ? (
+
+ ) : (
+
+ )}
+ Switch to {theme === "dark" ? "light mode" : "dark mode"}
+
+ )}
+ {config.onBack && (
+
+
+ Back to bases
+
+ )}
+ {config.onBack && !disableToggle && }
+
+
+ Report issues
+
+
+
+
+ About us
+
+
+
+
+
+ {tabs.map(({ key, name, icon }, idx) => {
+ return (
+
+
+ {
+ if (!loadedIndex[idx]) {
+ loadedIndex[idx] = true;
+ setLoadedIndex([...loadedIndex]);
+ }
+
+ if (idx !== selectedIndex) {
+ setSelectedIndex(idx);
+ }
+ }}
+ className={cn(
+ "cursor cursor-pointer h-12 w-12 flex flex-col gap-0.5 justify-center items-center rounded-t hover:text-primary",
+ selectedIndex === idx
+ ? "bg-secondary rounded-lg text-primary"
+ : undefined
+ )}
+ >
+ {icon}
+
+
+ {name}
+
);
})}
-
+
{tabs.map((tab, tabIndex) => {
const selected = selectedIndex === tabIndex;
diff --git a/src/components/gui/sidebar/saved-doc-tab/create-namespace-button.tsx b/src/components/gui/sidebar/saved-doc-tab/create-namespace-button.tsx
index 77bbcdfc..005af665 100644
--- a/src/components/gui/sidebar/saved-doc-tab/create-namespace-button.tsx
+++ b/src/components/gui/sidebar/saved-doc-tab/create-namespace-button.tsx
@@ -5,25 +5,24 @@ import {
DialogDescription,
DialogFooter,
DialogTitle,
- DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { useDatabaseDriver } from "@/context/driver-provider";
import { SavedDocNamespace } from "@/drivers/saved-doc/saved-doc-driver";
-import { LucidePlus } from "lucide-react";
import { useCallback, useState } from "react";
interface CreateNamespaceButtonProps {
onCreated: (v: SavedDocNamespace) => void;
+ onClose: () => void;
}
-export default function CreateNamespaceButton({
+export default function CreateNamespaceDialog({
onCreated,
+ onClose,
}: CreateNamespaceButtonProps) {
const { docDriver } = useDatabaseDriver();
const [namespace, setNamespace] = useState("");
const [error, setError] = useState("");
- const [open, setOpen] = useState(false);
const onCreateNamespace = useCallback(() => {
if (docDriver) {
@@ -32,7 +31,7 @@ export default function CreateNamespaceButton({
.createNamespace(namespace)
.then((n) => {
onCreated(n);
- setOpen(false);
+ onClose();
})
.catch((e) => {
setError((e as Error).message);
@@ -41,50 +40,36 @@ export default function CreateNamespaceButton({
setError("The namespace name must be at least 3 characters long");
}
}
- }, [docDriver, namespace, onCreated]);
+ }, [docDriver, namespace, onCreated, onClose]);
return (
- <>
-
{
- setOpen(openState);
- setNamespace("");
- setError("");
- }}
- >
-
-
-
- Create Namespace
-
-
-
- Create Namespace
+ {
+ if (!openState) onClose();
+ }}
+ >
+
+ Create Namespace
-
- A namespace is similar to a folder that groups your work together.
- It helps you organize and arrange your queries
-
+
+ A namespace is similar to a folder that groups your work together. It
+ helps you organize and arrange your queries
+
- setNamespace(e.currentTarget.value)}
- />
+ setNamespace(e.currentTarget.value)}
+ />
- {error && {error}
}
+ {error && {error}
}
-
- Create
-
-
-
- >
+
+ Create
+
+
+
);
}
diff --git a/src/components/gui/sidebar/saved-doc-tab/index.tsx b/src/components/gui/sidebar/saved-doc-tab/index.tsx
index 4b551077..bbdffe58 100644
--- a/src/components/gui/sidebar/saved-doc-tab/index.tsx
+++ b/src/components/gui/sidebar/saved-doc-tab/index.tsx
@@ -6,15 +6,23 @@ import {
SavedDocNamespace,
} from "@/drivers/saved-doc/saved-doc-driver";
import { ListView, ListViewItem } from "@/components/listview";
-import { LucideCode, LucideFolder, LucideTrash } from "lucide-react";
+import { LucideTrash } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
-import CreateNamespaceButton from "./create-namespace-button";
import RenameNamespaceDialog from "./rename-namespace-dialog";
import RemoveDocDialog from "./remove-doc-dialog";
import { TAB_PREFIX_SAVED_QUERY } from "@/const";
import RemoveNamespaceDialog from "./remove-namespace-dialog";
import { OpenContextMenuList } from "@/messages/open-context-menu";
-import { Separator } from "@/components/ui/separator";
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+import { Binoculars, Folder, Plus } from "@phosphor-icons/react";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import CreateNamespaceDialog from "./create-namespace-button";
type SavedDocListData =
| {
@@ -33,13 +41,13 @@ function mapDoc(
return {
data: { type: "namespace", data: ns.namespace },
key: ns.namespace.id,
- icon: LucideFolder,
+ icon: Folder,
name: ns.namespace.name,
children: ns.docs.map((d) => {
return {
key: d.id,
data: { type: "doc", data: d },
- icon: LucideCode,
+ icon: Binoculars,
name: d.name,
};
}) as ListViewItem
[],
@@ -52,6 +60,7 @@ export default function SavedDocTab() {
const [selected, setSelected] = useState();
const [collapsed, setCollapsed] = useState>(new Set());
+ const [namespaceCreating, setNamespaceCreating] = useState(false);
const [namespaceToRename, setNamespaceToRename] =
useState();
const [namespaceToRemove, setNamespaceToRemove] =
@@ -133,15 +142,52 @@ export default function SavedDocTab() {
);
}
+ if (namespaceCreating) {
+ dialog = (
+ setNamespaceCreating(false)}
+ />
+ );
+ }
+
return (
<>
{dialog}
-
-
-
+
+
+
Queries
+
+
+
+
+
+
+
+
+ setNamespaceCreating(true)}>
+ New Folder
+
+ {
+ openTab({
+ type: "query",
+ });
+ }}
+ >
+ New Query
+
+
+
-
void;
onClose?: () => void;
}
@@ -19,24 +21,34 @@ type WindowTabItemButtonProps = ButtonProps & {
icon: LucideIcon;
onClose?: () => void;
isDragging?: boolean;
+ index: number;
+ tabCount: number;
};
export const WindowTabItemButton = forwardRef<
HTMLButtonElement,
WindowTabItemButtonProps
>(function WindowTabItemButton(props: WindowTabItemButtonProps, ref) {
- const { icon: Icon, selected, title, onClose, isDragging, ...rest } = props;
+ const {
+ icon: Icon,
+ selected,
+ title,
+ onClose,
+ isDragging,
+ index,
+ ...rest
+ } = props;
return (
button === 1 && onClose && onClose()}
ref={ref}
@@ -65,6 +77,8 @@ export const WindowTabItemButton = forwardRef<
});
export function SortableTab({
+ index,
+ tabCount,
tab,
selected,
onSelectChange,
@@ -93,6 +107,8 @@ export function SortableTab({
selected={selected}
onClose={onClose}
style={style}
+ index={index}
+ tabCount={tabCount}
isDragging={isDragging}
{...attributes}
{...listeners}
diff --git a/src/components/gui/sql-editor/index.tsx b/src/components/gui/sql-editor/index.tsx
index bcd23406..c6d786d9 100644
--- a/src/components/gui/sql-editor/index.tsx
+++ b/src/components/gui/sql-editor/index.tsx
@@ -148,6 +148,9 @@ const SqlEditor = forwardRef(
borderLeft: "3px solid transparent",
paddingLeft: "10px",
},
+ "& .cm-focused": {
+ outline: "none !important",
+ },
}),
keyExtensions,
sqlDialect,
diff --git a/src/components/gui/tabs/query-tab.tsx b/src/components/gui/tabs/query-tab.tsx
index ef1ae93c..2a8cf3a5 100644
--- a/src/components/gui/tabs/query-tab.tsx
+++ b/src/components/gui/tabs/query-tab.tsx
@@ -1,7 +1,6 @@
import { format } from "sql-formatter";
import { useCallback, useMemo, useRef, useState } from "react";
import {
- LucideFastForward,
LucideGrid,
LucideMessageSquareWarning,
LucidePencilRuler,
@@ -13,8 +12,7 @@ import {
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable";
-import { Separator } from "@/components/ui/separator";
-import { Button } from "@/components/ui/button";
+import { Button, buttonVariants } from "@/components/ui/button";
import { KEY_BINDING } from "@/lib/key-matcher";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import QueryProgressLog from "../query-progress-log";
@@ -43,6 +41,15 @@ import {
resolveToNearestStatement,
splitSqlQuery,
} from "../sql-editor/statement-highlight";
+import { CaretDown } from "@phosphor-icons/react";
+import { cn } from "@/lib/utils";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
interface QueryWindowProps {
initialCode?: string;
@@ -90,7 +97,7 @@ export default function QueryWindow({
}
};
- const onRunClicked = (all = false) => {
+ const onRunClicked = (all = false, explained = false) => {
let finalStatements: string[] = [];
const editorState = editorRef.current?.view?.state;
@@ -103,7 +110,14 @@ export default function QueryWindow({
const segment = resolveToNearestStatement(editorState);
if (!segment) return;
- const statement = editorState.doc.sliceString(segment.from, segment.to);
+ let statement = editorState.doc.sliceString(segment.from, segment.to);
+
+ if (
+ explained &&
+ statement.toLowerCase().indexOf("explain query plan") !== 0
+ ) {
+ statement = "explain query plan " + statement;
+ }
if (statement) {
finalStatements = [statement];
@@ -203,12 +217,12 @@ export default function QueryWindow({
-
-
+
+
{namespaceName} /
-
+
{name}
setName(e.currentTarget.value)}
/>
+
+
+
+
+ {docDriver && (
+
+ )}
+
+
+ onRunClicked()}
+ className={cn(
+ buttonVariants({ size: "sm" }),
+ "rounded-r-none"
+ )}
+ >
+
+ Run
+
+
+
+
+
+
+
+
+ onRunClicked()}>
+ Run Current Statement
+
+ onRunClicked(true)}>
+ Run All Statements
+
+
+ onRunClicked(false, true)}>
+ Explain Current Statement
+
+
+
+
+
-
+
-
-
-
onRunClicked()}
- >
-
- Run Current
-
-
-
onRunClicked(true)}
- >
-
- Run All
-
+
+
+
Ln {lineNumber}
+
Col {columnNumber + 1}
+
@@ -287,19 +336,6 @@ export default function QueryWindow({
Format SQL queries for readability
-
-
-
Ln {lineNumber}
-
Col {columnNumber + 1}
-
-
- {docDriver && (
-
- )}
diff --git a/src/components/gui/windows-tab.tsx b/src/components/gui/windows-tab.tsx
index d27586ae..7061ec33 100644
--- a/src/components/gui/windows-tab.tsx
+++ b/src/components/gui/windows-tab.tsx
@@ -147,31 +147,8 @@ export default function WindowTabs({
modifiers={[restrictToHorizontalAxis]}
>
-
-
- {menu ? (
-
-
-
-
-
-
-
- {menu.map((menuItem, menuIdx) => {
- return (
-
- {menuItem.text}
-
- );
- })}
-
-
- ) : (
-
- )}
+
+
tab.key)}
strategy={horizontalListSortingStrategy}
@@ -179,6 +156,8 @@ export default function WindowTabs({
{tabs.map((tab, idx) => (
{
@@ -204,6 +183,32 @@ export default function WindowTabs({
/>
))}
+
+ {menu && (
+
+
+
+
+ New
+
+
+
+ {menu.map((menuItem, menuIdx) => {
+ return (
+
+ {menuItem.text}
+
+ );
+ })}
+
+
+
+ )}
+
+
diff --git a/src/components/icons/outerbase-icon.tsx b/src/components/icons/outerbase-icon.tsx
index ef8b7cf5..07a3d7fb 100644
--- a/src/components/icons/outerbase-icon.tsx
+++ b/src/components/icons/outerbase-icon.tsx
@@ -1,8 +1,9 @@
-export function MySQLIcon() {
+import { cn } from "@/lib/utils";
+
+export function MySQLIcon({ className }: { className?: string }) {
return (
+
+
+ );
+}
+
+export function CloudflareIcon({ className }: { className?: string }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function ValtownIcon({ className }: { className?: string }) {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export function RqliteIcon({ className }: { className?: string }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function SQLiteIcon({ className }: { className?: string }) {
return (
- LibSQLStudio
+ Outerbase Studio
{title}
diff --git a/src/components/website-layout.tsx b/src/components/website-layout.tsx
index 13a7bac6..ec5158ae 100644
--- a/src/components/website-layout.tsx
+++ b/src/components/website-layout.tsx
@@ -12,8 +12,8 @@ async function Topbar() {
-
- LibSQL Studio
+
+ Outerbase Studio
@@ -51,8 +51,8 @@ function Footer() {
-
- LibSQL Studio
+
+ Outerbase Studio
© 2024 Outerbase Inc.
diff --git a/src/const.ts b/src/const.ts
index cb287159..ff7f6bbf 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -5,3 +5,5 @@ export enum MessageChannelName {
}
export const TAB_PREFIX_SAVED_QUERY = "saved-query-";
+export const WEBSITE_NAME = "Outerbase Studio";
+export const WEBSITE_GENERAL_DESCRIPTION = `${WEBSITE_NAME} is a fully-featured, lightweight GUI client for managing SQLite-based databases like Turso, LibSQL, and rqlite. It runs entirely in your browser, so there's no need to download anything`;
diff --git a/src/messages/open-tab.tsx b/src/messages/open-tab.tsx
index c585ad9c..f3082534 100644
--- a/src/messages/open-tab.tsx
+++ b/src/messages/open-tab.tsx
@@ -2,7 +2,6 @@ import type { WindowTabItemProps } from "@/components/gui/windows-tab";
import { MessageChannelName, TAB_PREFIX_SAVED_QUERY } from "../const";
import type { Dispatch, SetStateAction } from "react";
import {
- LucideCode,
LucideTable,
LucideTableProperties,
LucideUser,
@@ -13,6 +12,7 @@ import SchemaEditorTab from "@/components/gui/tabs/schema-editor-tab";
import TableDataWindow from "@/components/gui/tabs/table-data-tab";
import UsersTab from "@/components/gui/tabs/users-tabs";
import TriggerTab from "@/components/gui/tabs/trigger-tab";
+import { Binoculars } from "@phosphor-icons/react/dist/ssr";
interface OpenTableTab {
type: "table";
@@ -82,7 +82,7 @@ function generateKeyFromTab(tab: OpenTabsProps) {
}
function generateIconFromTab(tab: OpenTabsProps) {
- if (tab.type === "query") return LucideCode;
+ if (tab.type === "query") return Binoculars;
if (tab.type === "table") return LucideTable;
if (tab.type === "schema") return LucideTableProperties;
if (tab.type === "user") return LucideUser;