Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(front): shareable links in playground #43

Merged
merged 17 commits into from
Oct 26, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: Add integration with backend
daavidrgz committed Oct 12, 2024
commit 8d6e6fdfb3267af71d3e6c93c747d3cba869e369
11 changes: 11 additions & 0 deletions crates/web/frontend/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
const host = process.env.API_HOST || "localhost";
daavidrgz marked this conversation as resolved.
Show resolved Hide resolved
const port = process.env.API_PORT || 3001;

const nextConfig = {
async rewrites() {
return [
{
source: "/api/:path*",
destination: `http://${host}:${port}/:path*`,
},
];
},
webpack: (config) => {
config.experiments = {
asyncWebAssembly: true,
33 changes: 32 additions & 1 deletion crates/web/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/web/frontend/package.json
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@types/js-beautify": "^1.14.3",
"@types/uuid": "^10.0.0",
"@uiw/codemirror-themes": "^4.23.5",
"@uiw/react-codemirror": "^4.23.5",
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
@@ -52,9 +53,11 @@
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
"vaul": "^1.0.0",
"vscode-languageserver-protocol": "^3.17.5",
"webworker-promise": "^0.5.1"
"webworker-promise": "^0.5.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@biomejs/biome": "1.9.3",
20 changes: 18 additions & 2 deletions crates/web/frontend/src/app/page-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Completion } from "@/model/completion";
import type { Data } from "@/model/data";
import type FileType from "@/model/file-type";
import { Data } from "@/model/data";
import FileType from "@/model/file-type";
import { getShare } from "@/service/share-service";
import type { CompletionContext, CompletionSource } from "@codemirror/autocomplete";
import { toast } from "sonner";
import type PromiseWorker from "webworker-promise";
@@ -51,3 +52,18 @@ export const getQueryCompletionSource = (
};
};
};

export const importShare = async (shareId: string): Promise<{ input: Data; query: Data }> => {
const toastId = toast.loading("Importing share...");
try {
const share = await getShare(shareId);
toast.success("Share succesfully imported", { id: toastId });
return Promise.resolve({
input: new Data(share.json, FileType.JSON),
query: new Data(share.query, FileType.GQ),
});
} catch (error) {
toast.error(`Error importing share: ${error.message}`, { id: toastId });
return Promise.reject(error);
}
};
15 changes: 13 additions & 2 deletions crates/web/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -15,10 +15,11 @@ import { useSettings } from "@/providers/settings-provider";
import { useWorker } from "@/providers/worker-provider";
import type { CompletionSource } from "@codemirror/autocomplete";
import { Link2, Link2Off } from "lucide-react";
import { useCallback, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { applyGq, getQueryCompletionSource } from "./page-utils";
import { applyGq, getQueryCompletionSource, importShare } from "./page-utils";
import styles from "./page.module.css";
import { useParams, usePathname, useSearchParams } from "next/navigation";

const Home = () => {
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
@@ -45,6 +46,7 @@ const Home = () => {
} = useSettings();
const debounce = useDebounce(debounceTime);
const { gqWorker, lspWorker } = useWorker();
const shareId = useSearchParams().get("id");

const updateOutputData = useCallback(
async (inputContent: string, inputType: FileType, queryContent: string, silent = true) => {
@@ -125,6 +127,15 @@ const Home = () => {
[autoApply, debounce, updateOutputData, debounceTime],
);

useEffect(() => {
if (!shareId) return;
importShare(shareId).then((data) => {
updateInputEditorCallback.current(data.input);
updateQueryEditorCallback.current(data.query);
// updateOutputData(data.input.content, data.input.type, data.output.content, true);
});
}, [shareId]);

return (
<main className="flex flex-col items-center pt-4 px-12 h-screen">
<Header className="w-full mb-8" onClickExample={handleClickExample} />
Original file line number Diff line number Diff line change
@@ -275,7 +275,7 @@ const ImportPopup = ({
>
Cancel
</Button>
<Button className="py-1 px-8" variant="success" type="submit">
<Button className="py-1 px-8" variant="success" type="submit" disabled={!(file || url)}>
Import
</Button>
</div>
120 changes: 71 additions & 49 deletions crates/web/frontend/src/components/share-popup/share-popup.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import { Clipboard, ClipboardCopy, ClipboardIcon, InfoIcon, Share, X } from "lucide-react";
import { Clipboard, Clock, Share } from "lucide-react";
import { useState } from "react";
import ActionButton from "../action-button/action-button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Button } from "../ui/button";
import { Label } from "../ui/label";
import { Slider } from "../ui/slider";
import { SliderWithMarks } from "../ui/slider-with-marks";
import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
import { Tooltip, TooltipProvider } from "@radix-ui/react-tooltip";
import { TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { Input } from "../ui/input";
import { copyToClipboard } from "@/lib/utils";
import { cn, copyToClipboard } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { Separator } from "../ui/separator";
import { Loader } from "../ui/sonner";

const SharePopup = () => {
const [expirationTime, setExpirationTime] = useState(3600);
const [shareLink, setShareLink] = useState(
"https://gq.hermo.dev/3c8ec511-754f-4e19-ade1-62974d745106",
);
const [expirationTime, setExpirationTime] = useState<"1 hour" | "1 day" | "1 week">("1 day");
const [shareLink, setShareLink] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [selectedExpirationTime, setSelectedExpirationTime] = useState<
"1 hour" | "1 day" | "1 week"
>();

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setShareLink("https://gq.hermo.dev/test");
setSelectedExpirationTime(expirationTime);
}, 2000);
};

const handleExpirationTimeChange = (value: number[]) => {
setExpirationTime(value[0]);
const handleChangeExpirationTime = (value: string) => {
setShareLink(undefined);
setExpirationTime(value as "1 hour" | "1 day" | "1 week");
};

return (
@@ -41,15 +40,13 @@ const SharePopup = () => {
<Share className="w-4 h-4" />
</ActionButton>
</PopoverTrigger>
<PopoverContent

className="w-[26rem] max-w-[80vw] max-h-[80vh] gap-0 relative right-2"
>
<PopoverContent className="w-[24rem] max-w-[80vw] max-h-[80vh] gap-0 relative right-2">
<div className="flex items-center mb-1.5">
<TooltipProvider>
<h4 className="text-lg font-semibold leading-none tracking-tight">
Share your playground
</h4>
<h4 className="text-lg font-semibold leading-none tracking-tight">
Share your playground
</h4>
{/* TODO: Fix this */}
{/* <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="w-3 h-3 ml-2 mt-1" />
@@ -64,55 +61,80 @@ const SharePopup = () => {
</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TooltipProvider> */}
</div>
<span className="text-sm">Create a shareable link to your current playground state</span>
<form onSubmit={handleSubmit} autoComplete="off" className="overflow-x-auto mt-8">
{/* <span className="text-sm">Create a shareable link to your current playground state</span> */}
<form onSubmit={handleSubmit} autoComplete="off" className="overflow-x-auto mt-4">
<div>
<Label htmlFor="expiration-time" variant="default">
Expiration time
</Label>
<RadioGroup
className="mt-4 text-xs flex gap-4"
defaultValue="1-day"
value={expirationTime}
onValueChange={handleChangeExpirationTime}
disabled={isLoading}
className="mt-4 text-xs flex gap-4 peer"
id="expiration-time"
>
<div className="flex items-center">
<RadioGroupItem value="1-hour" id="1-hour" />
<RadioGroupItem value="1 hour" id="1-hour" />
<Label className="pl-2" htmlFor="1-hour">
1 hour
</Label>
</div>
<div className="flex items-center">
<RadioGroupItem value="1-day" id="1-day" />
<RadioGroupItem value="1 day" id="1-day" />
<Label className="pl-2" htmlFor="1-day">
1 day
</Label>
</div>
<div className="flex items-center">
<RadioGroupItem value="1-week" id="1-week" />
<RadioGroupItem value="1 week" id="1-week" />
<Label className="pl-2" htmlFor="1-week">
1 week
</Label>
</div>
</RadioGroup>
</div>

<div className="mt-8 w-full flex">
<Input readOnly className="mb-0 rounded-r-none border-r-0" value={shareLink} />
<ActionButton
className="px-4 py-2 h-10 rounded-l-none"
description="Copy to clipboard"
variant="outline"
onClick={() => copyToClipboard(shareLink)}
>
<Clipboard className="w-3.5 h-3.5" />
</ActionButton>
</div>
<Button className="mt-8 w-full py-1 px-8" variant="outline" type="submit">
Generate link
<Button
className={cn("mt-8 w-full py-1 px-8", isLoading && "!opacity-100")}
variant="outline"
type="submit"
disabled={
isLoading || (shareLink !== undefined && expirationTime === selectedExpirationTime)
}
>
{isLoading ? (
<div className="flex items-center gap-2">
<Loader />
<span>Generating</span>
</div>
) : (
<span>Generate link</span>
)}
</Button>
</form>
{shareLink && (
<div className="animate-in fade-in">
<Separator />
<div className="w-full flex">
<Input readOnly className="mb-0 rounded-r-none border-r-0" value={shareLink} />
<ActionButton
className="px-4 py-2 h-10 rounded-l-none"
description="Copy to clipboard"
variant="outline"
onClick={() => copyToClipboard(shareLink)}
>
<Clipboard className="w-3.5 h-3.5" />
</ActionButton>
</div>
<div className="flex items-center gap-2 mt-4">
<Clock className="w-2.5 h-2.5 mt-[2px]" />
<span className="text-xs">Your link will expire in {selectedExpirationTime}</span>
</div>
</div>
)}
</PopoverContent>
</Popover>
);
8 changes: 3 additions & 5 deletions crates/web/frontend/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { type MouseEvent, forwardRef, useEffect, useRef, useState } from "react"
import styles from "./button.module.css";

const buttonVariants = cva(
"relative overflow-hidden inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
"relative overflow-hidden inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 transition-opacity",
{
variants: {
variant: {
@@ -90,16 +90,14 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
return (
<div ref={containerRef}>
<Comp
className={cn(
buttonVariants({ variant, size, className }),
disabled && "cursor-default opacity-50",
)}
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
onClick={handleClick}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
onMouseMove={handleMouseMove}
{...props}
disabled={disabled}
>
{!disabled && (
<motion.div
Loading