Skip to content

Commit

Permalink
Merge pull request #702 from tone-row/dev
Browse files Browse the repository at this point in the history
v1.54.0
  • Loading branch information
rob-gordon authored Aug 5, 2024
2 parents b266ce0 + 7d5ff9a commit 46b79c6
Show file tree
Hide file tree
Showing 34 changed files with 1,092 additions and 488 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "1.53.0",
"version": "1.54.0",
"main": "module/module.js",
"license": "MIT",
"scripts": {
Expand Down
Binary file added app/public/images/ai-convert.mp4
Binary file not shown.
180 changes: 180 additions & 0 deletions app/src/components/DownloadDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { FileImage, Image, Copy, Check } from "phosphor-react";
import { useHasProAccess, useDownloadFilename } from "../lib/hooks";
import { AUTH_IMG_SCALE, UNAUTH_IMG_SCALE } from "../lib/constants";
import { downloadCanvas, downloadSvg, getCanvas, getSvg } from "./downloads";
import { t } from "@lingui/macro";
import { showPaywall } from "../lib/usePaywallModalStore";
import { useState } from "react";
import * as Toast from "@radix-ui/react-toast";

export function DownloadDropdown({ children }: { children: React.ReactNode }) {
const hasProAccess = useHasProAccess();
const filename = useDownloadFilename();
const watermark = !hasProAccess;
const scale = hasProAccess ? AUTH_IMG_SCALE : UNAUTH_IMG_SCALE;

// store a string for a recently completed action we will use to show a checkmark in certain cases
const [message, setMessage] = useState<string | null>(null);

const handleDownload = async (format: string) => {
if (!window.__cy) return;

if (format === "svg" && hasProAccess) {
const svg = await getSvg({ cy: window.__cy });
downloadSvg({ svg, filename });
} else {
const { canvas } = await getCanvas({
cy: window.__cy,
type: format as "png" | "jpg",
watermark,
scale,
});
downloadCanvas({
canvas,
filename,
type: format as "png" | "jpg",
cleanup: () => {},
});
}
};

const handleCopy = async (format: string) => {
if (!window.__cy) return;

// if the type is svg, copy code
if (format === "svg") {
const svg = await getSvg({ cy: window.__cy });
navigator.clipboard.writeText(svg);
setMessage(t`Copied SVG code to clipboard`);
return;
}

const { canvas } = await getCanvas({
cy: window.__cy,
type: format as "png" | "jpg",
watermark,
scale,
});

canvas.toBlob(async (blob) => {
if (blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({ [blob.type]: blob }),
]);
console.log(`Copied ${format} to clipboard`);
setMessage(t`Copied ${format} to clipboard`);
} catch (err) {
console.error(`Failed to copy ${format}:`, err);
}
}
}, `image/${format}`);
};

const handleSvgAction = (action: "download" | "copy") => {
if (hasProAccess) {
action === "download" ? handleDownload("svg") : handleCopy("svg");
} else {
showPaywall({
title: t`SVG Export is a Pro Feature`,
content: t`Upgrade to Flowchart Fun Pro to unlock SVG exports and enjoy more advanced features for your diagrams.`,
toPricingCode: "SVGExport",
});
}
};

return (
<>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>{children}</DropdownMenu.Trigger>
<DropdownMenu.Content
side="bottom"
sideOffset={5}
className="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-md shadow-lg p-1 min-w-[160px]"
>
<DropdownMenuItem
onClick={() => handleDownload("png")}
icon={Image}
data-session-activity="Download Dropdown: Download PNG"
>
Download PNG
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDownload("jpg")}
icon={Image}
data-session-activity="Download Dropdown: Download JPG"
>
Download JPG
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleSvgAction("download")}
icon={FileImage}
data-session-activity="Download Dropdown: Download SVG"
>
Download SVG
</DropdownMenuItem>
<DropdownMenu.Separator className="h-px bg-neutral-200 dark:bg-neutral-700 my-1" />
<DropdownMenuItem
onClick={() => handleCopy("png")}
icon={Copy}
data-session-activity="Download Dropdown: Copy PNG"
>
Copy PNG
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleCopy("jpg")}
icon={Copy}
data-session-activity="Download Dropdown: Copy JPG"
>
Copy JPG
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleSvgAction("copy")}
icon={Copy}
data-session-activity="Download Dropdown: Copy SVG"
>
Copy SVG
</DropdownMenuItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Toast.Root
type="foreground"
duration={3000}
className="ToastRoot bg-white dark:bg-neutral-800 border-2 border-neutral-300 dark:border-neutral-700 rounded-md shadow-md dark:shadow-lg p-4 grid [grid-template-areas:_'title_action'_'description_action'] grid-cols-[auto_max-content] gap-x-4 items-center data-[state=open]:animate-slideIn data-[state=closed]:animate-hide data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[transform_200ms_ease-out] data-[swipe=end]:animate-swipeOut"
open={message !== null}
onOpenChange={(open) => {
if (!open) setMessage(null);
}}
>
<Toast.Description>
<div className="flex text-xs items-center gap-3 text-neutral-800 dark:text-neutral-200">
<Check
size={24}
className="shrink-0 text-green-500 dark:text-green-400"
/>
<p className="leading-normal">{message}</p>
</div>
</Toast.Description>
</Toast.Root>
</>
);
}

function DropdownMenuItem({
children,
icon: Icon,
...props
}: DropdownMenu.DropdownMenuItemProps & {
icon: typeof Image;
}) {
return (
<DropdownMenu.Item
className="flex items-center px-2 py-2 text-sm text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded cursor-pointer"
{...props}
>
<Icon className="mr-2" size={18} />
{children}
</DropdownMenu.Item>
);
}
18 changes: 11 additions & 7 deletions app/src/components/FlowchartHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CloneButton } from "./CloneButton";
import styles from "./FlowchartHeader.module.css";
import { RenameButton } from "./RenameButton";
import ShareDialog from "./ShareDialog";
import { Cloud, FloppyDisk, Export, File } from "phosphor-react";
import { Cloud, FloppyDisk, File, Share, DownloadSimple } from "phosphor-react";
import classNames from "classnames";
import { useLocation, useNavigate } from "react-router-dom";
import * as Dialog from "@radix-ui/react-dialog";
Expand All @@ -21,6 +21,7 @@ import {
import { useMutation } from "react-query";
import { makeChart } from "../lib/queries";
import { saveAs } from "file-saver";
import { DownloadDropdown } from "./DownloadDropdown";

export function FlowchartHeader() {
const title = useDocDetails("title", "flowchart.fun");
Expand All @@ -43,7 +44,7 @@ export function FlowchartHeader() {
<FlowchartTitle title={title}>{pageTitle}</FlowchartTitle>
</RenameButton>
)}
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 p-1 border-2 border-neutral-300 dark:border-neutral-700 rounded-xl">
{isReadOnly && (
<span className="text-xs text-neutral-400 dark:text-neutral-600 font-extrabold uppercase tracking-tight">
<Trans>Read-only</Trans>
Expand All @@ -55,9 +56,8 @@ export function FlowchartHeader() {
{isSandbox ? <SaveButton /> : null}
<ShareDialog>
<Button2
color="blue"
onClick={() => setShareModal(true)}
leftIcon={<Export weight="bold" className="w-5 h-5" />}
leftIcon={<Share weight="bold" className="w-5 h-5" />}
aria-label="Export"
data-session-activity="Share Chart"
>
Expand All @@ -66,6 +66,13 @@ export function FlowchartHeader() {
</ShareDialog>
</>
) : null}
<DownloadDropdown>
<Button2
leftIcon={<DownloadSimple weight="bold" className="w-5 h-5" />}
>
<Trans>Download</Trans>
</Button2>
</DownloadDropdown>
</div>
</header>
);
Expand All @@ -90,7 +97,6 @@ function LogInToSaveButton() {
return (
<Button2
leftIcon={<FloppyDisk weight="bold" className="w-5 h-5" />}
color="default"
data-session-activity="Save Chart: Log in"
onClick={() => {
navigate("/l");
Expand Down Expand Up @@ -133,7 +139,6 @@ function CanSaveButton() {
<Dialog.Trigger asChild>
<Button2
leftIcon={<FloppyDisk weight="bold" className="w-5 h-5" />}
color="zinc"
data-session-activity="Save Chart"
onClick={() => {
setOpen(true);
Expand Down Expand Up @@ -234,7 +239,6 @@ function SaveForm() {
/>
</label>
<Button2
color="blue"
leftIcon={<FloppyDisk className="w-5 h-5" />}
isLoading={createChartMutation.isLoading}
>
Expand Down
5 changes: 3 additions & 2 deletions app/src/components/LoadTemplateDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ export function LoadTemplateDialog() {
<EditorActionTextButton
icon={PiShapesDuotone}
data-session-activity="Load Template: Open Dialog"
iconClassName="fill-zinc-500"
>
<Trans>Load Template</Trans>
<Trans>Examples</Trans>
</EditorActionTextButton>
</Dialog.Trigger>
<Dialog.Portal>
Expand All @@ -113,7 +114,7 @@ export function LoadTemplateDialog() {
<PiShapesDuotone className="mr-2 translate-y-1" />

<span className="mr-4">
<Trans>Templates</Trans>
<Trans>Examples</Trans>
</span>
</Dialog.Title>
<RequestTemplate />
Expand Down
5 changes: 3 additions & 2 deletions app/src/components/PaywallModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function PaywallModal() {
const movieUrl = usePaywallModalStore((s) => s.movieUrl);
const imgUrl = usePaywallModalStore((s) => s.imgUrl);
const toPricingCode = usePaywallModalStore((s) => s.toPricingCode);
const buttonText = usePaywallModalStore((s) => s.buttonText);
return (
<Dialog.Root
modal
Expand All @@ -26,7 +27,7 @@ export function PaywallModal() {
<Overlay />
<Content
maxWidthClass="max-w-[520px]"
className="content-start text-center"
className="content-start text-center overflow-auto"
noPadding
>
{movieUrl ? (
Expand Down Expand Up @@ -66,7 +67,7 @@ export function PaywallModal() {
navigate("/pricing");
}}
>
<Trans>Learn More</Trans>
<Trans>{buttonText}</Trans>
</Button2>
</Dialog.Close>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/RequestTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function RequestTemplate() {
className="text-[14px] font-normal opacity-50 hover:opacity-1000"
data-session-activity="Request Template"
>
<Trans>Missing a template? Suggest one here!</Trans>
<Trans>Would you like to suggest a new example?</Trans>
</Link>
);
}
Loading

0 comments on commit 46b79c6

Please sign in to comment.