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: optimize the feature of saving & merge button #520

Merged
merged 2 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 36 additions & 0 deletions app/src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}

export { Badge, badgeVariants }
2 changes: 1 addition & 1 deletion app/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 text-muted-foreground",
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
Expand Down
12 changes: 5 additions & 7 deletions app/src/components/uploadChartModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import { Checkbox } from "@/components/ui/checkbox";
interface IUploadChartModal {
gwRef: React.MutableRefObject<IGWHandler | null>;
storeRef: React.MutableRefObject<VizSpecStore | null>;
open: boolean;
dark: string;
setOpen: (open: boolean) => void;
}

const UploadChartModal: React.FC<IUploadChartModal> = observer((props) => {
Expand All @@ -29,14 +27,14 @@ const UploadChartModal: React.FC<IUploadChartModal> = observer((props) => {
const [instanceType, setInstanceType] = useState("");

useEffect(() => {
if (props.open) {
if (commonStore.uploadChartModalOpen) {
const instanceType = (props.storeRef.current?.exportCode().length || 0) > 1 ? "dashboard" : "chart";
setChartName(`${instanceType}-${new Date().getTime().toString(16).padStart(16, "0")}`);
setDatasetName(`dataset-${new Date().getTime().toString(16).padStart(16, "0")}`);
setIsPublic(true);
setInstanceType(instanceType);
}
}, [props.open])
}, [commonStore.uploadChartModalOpen])

const uploadSuccess = (instanceType: string, instanceId: string, datasetId: string) => {
const managerUrl = instanceType === "chart" ? `https://kanaries.net/analytics/c/${instanceId}` : `https://kanaries.net/analytics/d/${instanceId}`
Expand Down Expand Up @@ -119,18 +117,18 @@ const UploadChartModal: React.FC<IUploadChartModal> = observer((props) => {
);
uploadSuccess(instanceType, resp?.data.chartId, resp?.data.datasetId);
}
props.setOpen(false);
commonStore.setUploadChartModalOpen(false);
} finally {
setUploading(false);
}
};

return (
<Dialog
open={props.open}
open={commonStore.uploadChartModalOpen}
modal={false}
onOpenChange={(show) => {
props.setOpen(show);
commonStore.setUploadChartModalOpen(show)
}}
>
<DialogContent>
Expand Down
173 changes: 117 additions & 56 deletions app/src/components/uploadSpecModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore'
import { chartToWorkflow } from "@kanaries/graphic-walker/utils/workflow";

import communicationStore from "../../store/communication";
import commonStore from "../../store/common";
Expand All @@ -8,15 +10,19 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { badgeVariants } from "@/components/ui/badge";

interface IUploadSpecModal {
setGwIsChanged: React.Dispatch<React.SetStateAction<boolean>>;
storeRef: React.MutableRefObject<VizSpecStore | null>;
}

const UploadSpecModal: React.FC<IUploadSpecModal> = observer((props) => {
const [uploading, setUploading] = useState(false);
const [specName, setSpecName] = useState("");
const [isSetToken, setIsSetToken] = useState(false);
const [token, setToken] = useState("");
const [contentType, setContentType] = useState<"onboarding" | "upload">("onboarding");

const uploadSuccess = (path: string) => {
commonStore.setNotification(
Expand Down Expand Up @@ -48,6 +54,7 @@ const UploadSpecModal: React.FC<IUploadSpecModal> = observer((props) => {
);
commonStore.setUploadSpecModalOpen(false);
uploadSuccess(resp?.data["specFilePath"]);
props.setGwIsChanged(false);
} finally {
setUploading(false);
}
Expand All @@ -58,6 +65,113 @@ const UploadSpecModal: React.FC<IUploadSpecModal> = observer((props) => {
setToken("");
}

const saveSpecToLocal = () => {
const visSpec = props.storeRef.current?.exportCode();
const configObj = {
config: visSpec,
chart_map: {},
version: commonStore.version,
workflow_list: visSpec?.map(spec => chartToWorkflow(spec).workflow),
}
const blob = new Blob([JSON.stringify(configObj)], {type: "text/plain;charset=utf-8"});
const url = URL.createObjectURL(blob);
const tempLink = document.createElement("a");
tempLink.href = url;
tempLink.download = `pygwalker_spec_${new Date().getTime()}.json`
tempLink.click();
URL.revokeObjectURL(url);
commonStore.setUploadSpecModalOpen(false);
props.setGwIsChanged(false);
};

useEffect(() => {
setContentType("onboarding");
}, [commonStore.uploadSpecModalOpen]);

const OnboardingContent = (
<DialogContent>
<DialogHeader>
<DialogTitle>Save Spec</DialogTitle>
</DialogHeader>
<div className="grid h-full grid-rows-2 gap-6 lg:grid-cols-2 lg:grid-rows-1">
<button
className={"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"}
onClick={() => {setContentType("upload")}}
>
<div className="flex items-center justify-center h-[160px] w-full">
<span className="font-semibold">upload as cloud file</span>
</div>
</button>
<button
className={"flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"}
onClick={saveSpecToLocal}
>
<div className="flex items-center justify-center h-[160px] w-full">
<span className="font-semibold">save as local file</span>
</div>
</button>
</div>
</DialogContent>
)

const UpdateSpecContent = (
<DialogContent>
<DialogHeader>
<DialogTitle>Upload Sepc</DialogTitle>
<DialogDescription>
<p className="py-1">Because you currently don't pass in the spec parameter or the passed in spec parameter is not a writable spec file.</p>
<p className="py-1">Currently the spec configuration is already in your ui cache, you may need to upload kanaries cloud to save it.</p>
<p className="py-1">If you don't have kanaries_token, you need to get it: <a className={badgeVariants({ variant: "outline" })} href="https://kanaries.net/analytics/settings?tab=apikey" target="_blank">Kanaries</a></p>
</DialogDescription>
</DialogHeader>
<div>
<div className="text-sm max-h-64 overflow-auto p-1">
<Input
value={specName}
onChange={(e) => {
setSpecName(e.target.value);
}}
type="text"
placeholder="please input spec file name"
id="chart-name-input"
className="mb-1"
/>
</div>
<div className="flex items-center justify-end mt-2">
<Checkbox
id="link-checkbox"
checked={isSetToken}
onCheckedChange={(checked) => {
onClickSetToken(checked as boolean);
}}
/>
<Label className="ml-2">Set a new kanaries_token?</Label>
</div>
{
isSetToken && (
<div className="text-sm max-h-64 overflow-auto p-1 mt-2">
<Input
value={token}
onChange={(e) => {
setToken(e.target.value);
}}
type="text"
autoComplete="off"
placeholder="please input new kanaries token"
id="token-input"
/>
</div>
)
}
<div className="mt-4 flex justify-end">
<Button variant="outline" className="mr-2 px-6" disabled={uploading} onClick={onClick}>
{uploading ? "uploading.." : "upload"}
</Button>
</div>
</div>
</DialogContent>
)

return (
<Dialog
open={commonStore.uploadSpecModalOpen}
Expand All @@ -66,61 +180,8 @@ const UploadSpecModal: React.FC<IUploadSpecModal> = observer((props) => {
commonStore.setUploadSpecModalOpen(show);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Upload Sepc</DialogTitle>
<DialogDescription>
<p className="py-1">Because you currently don't pass in the spec parameter or the passed in spec parameter is not a writable spec file.</p>
<p className="py-1">Currently the spec configuration is already in your ui cache, you may need to upload kanaries cloud to save it.</p>
<p className="py-1">If you don't have kanaries_token, you need to get it first, refer to: <a className="font-semibold px-1" href="https://github.com/Kanaries/pygwalker/wiki/How-to-get-api-key-of-kanaries%3F" target="_blank">How to get api key of kanaries?</a></p>
</DialogDescription>
</DialogHeader>
<div>
<div className="text-sm max-h-64 overflow-auto p-1">
<Input
value={specName}
onChange={(e) => {
setSpecName(e.target.value);
}}
type="text"
placeholder="please input spec file name"
id="chart-name-input"
className="mb-1"
/>
</div>
<div className="flex items-center justify-end mt-2">
<Checkbox
id="link-checkbox"
checked={isSetToken}
onCheckedChange={(checked) => {
onClickSetToken(checked as boolean);
}}
/>
<Label className="ml-2">Set a new kanaries_token?</Label>
</div>
{
isSetToken && (
<div className="text-sm max-h-64 overflow-auto p-1 mt-2">
<Input
value={token}
onChange={(e) => {
setToken(e.target.value);
}}
type="text"
autoComplete="off"
placeholder="please input new kanaries token"
id="token-input"
/>
</div>
)
}
<div className="mt-4 flex justify-end">
<Button variant="outline" className="mr-2 px-6" disabled={uploading} onClick={onClick}>
{uploading ? "uploading.." : "upload"}
</Button>
</div>
</div>
</DialogContent>
{contentType === "upload" && UpdateSpecContent }
{contentType === "onboarding" && OnboardingContent }
</Dialog>
);
});
Expand Down
10 changes: 2 additions & 8 deletions app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import InitModal from './components/initModal';
import { getSaveTool, hidePreview } from './tools/saveTool';
import { getExportTool } from './tools/exportTool';
import { getExportDataframeTool } from './tools/exportDataframe';
import { getUploadChartTool } from './tools/uploadChartTool';
import { formatExportedChartDatas } from "./utils/save";
import Notification from "./notify"
import initDslParser from "@kanaries/gw-dsl-parser";
Expand Down Expand Up @@ -138,7 +137,6 @@ const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
const gwRef = React.useRef<IGWHandler|null>(null);
const { userConfig } = props;
const [exportOpen, setExportOpen] = useState(false);
const [uploadChartModalOpen, setUploadChartModalOpen] = useState(false);
const [mode, setMode] = useState<string>("walker");
const [visSpec, setVisSpec] = useState(props.visSpec);
const [hideModeOption, _] = useState(true);
Expand Down Expand Up @@ -193,10 +191,6 @@ const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
const exportDataFrameTool = getExportDataframeTool(props, storeRef);
tools.push(exportDataFrameTool);
}
if (checkUploadPrivacy() && commonStore.showCloudTool) {
const uploadTool = getUploadChartTool(setUploadChartModalOpen);
tools.push(uploadTool);
}

const toolbarConfig = {
exclude: ["export_code"],
Expand Down Expand Up @@ -232,8 +226,8 @@ const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
return (
<React.StrictMode>
<Notification />
<UploadSpecModal />
<UploadChartModal gwRef={gwRef} storeRef={storeRef} open={uploadChartModalOpen} setOpen={setUploadChartModalOpen} dark={useContext(darkModeContext)} />
<UploadSpecModal storeRef={storeRef} setGwIsChanged={setIsChanged} />
<UploadChartModal gwRef={gwRef} storeRef={storeRef} dark={useContext(darkModeContext)} />
<CodeExportModal open={exportOpen} setOpen={setExportOpen} globalStore={storeRef} sourceCode={props["sourceInvokeCode"] || ""} />
{
!hideModeOption &&
Expand Down
7 changes: 7 additions & 0 deletions app/src/store/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CommonStore {
version: string = "";
notification: INotification | null = null;
uploadSpecModalOpen: boolean = false;
uploadChartModalOpen: boolean = false;

setInitModalOpen(value: boolean) {
this.initModalOpen = value;
Expand Down Expand Up @@ -55,6 +56,10 @@ class CommonStore {
this.uploadSpecModalOpen = value;
}

setUploadChartModalOpen(value: boolean) {
this.uploadChartModalOpen = value;
}

constructor() {
makeObservable(this, {
initModalOpen: observable,
Expand All @@ -63,12 +68,14 @@ class CommonStore {
version: observable,
notification: observable,
uploadSpecModalOpen: observable,
uploadChartModalOpen: observable,
setInitModalOpen: action,
setInitModalInfo: action,
setShowCloudTool: action,
setVersion: action,
setNotification: action,
setUploadSpecModalOpen: action,
setUploadChartModalOpen: action,
});
}
}
Expand Down
Loading
Loading