feat: block sync (#548)

This commit is contained in:
Aaryan Khandelwal 2023-03-28 00:36:20 +05:30 committed by GitHub
parent a5a96d9f66
commit 08ee5dc6b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 16 deletions

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { mutate } from "swr"; import { mutate } from "swr";
@ -9,7 +10,6 @@ import { mutate } from "swr";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// services // services
import pagesService from "services/pages.service"; import pagesService from "services/pages.service";
import aiService from "services/ai.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
@ -18,13 +18,16 @@ import { GptAssistantModal } from "components/core";
// ui // ui
import { CustomMenu, Loader, TextArea } from "components/ui"; import { CustomMenu, Loader, TextArea } from "components/ui";
// icons // icons
import { WaterDropIcon } from "components/icons"; import { LayerDiagonalIcon, WaterDropIcon } from "components/icons";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
import { IPageBlock, IProject } from "types"; import { IIssue, IPageBlock, IProject } from "types";
// fetch-keys // fetch-keys
import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; import { PAGE_BLOCKS_LIST } from "constants/fetch-keys";
import { ArrowTopRightOnSquareIcon, CheckIcon } from "@heroicons/react/24/outline";
import issuesService from "services/issues.service";
import { ArrowPathIcon } from "@heroicons/react/20/solid";
type Props = { type Props = {
block: IPageBlock; block: IPageBlock;
@ -42,6 +45,7 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor
export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => { export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
const [isSyncing, setIsSyncing] = useState(false);
const [gptAssistantModal, setGptAssistantModal] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false);
@ -63,6 +67,8 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
if (!formData.name || formData.name.length === 0 || formData.name === "") return; if (!formData.name || formData.name.length === 0 || formData.name === "") return;
if (block.issue && block.sync) setIsSyncing(true);
mutate<IPageBlock[]>( mutate<IPageBlock[]>(
PAGE_BLOCKS_LIST(pageId as string), PAGE_BLOCKS_LIST(pageId as string),
(prevData) => (prevData) =>
@ -80,8 +86,16 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
description: formData.description, description: formData.description,
description_html: formData.description_html, description_html: formData.description_html,
}) })
.then(() => { .then((res) => {
mutate(PAGE_BLOCKS_LIST(pageId as string)); mutate(PAGE_BLOCKS_LIST(pageId as string));
if (block.issue && block.sync)
issuesService
.patchIssue(workspaceSlug as string, projectId as string, block.issue, {
name: res.name,
description: res.description,
description_html: res.description_html,
})
.finally(() => setIsSyncing(false));
}); });
}; };
@ -95,12 +109,12 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
pageId as string, pageId as string,
block.id block.id
) )
.then((res) => { .then((res: IIssue) => {
mutate<IPageBlock[]>( mutate<IPageBlock[]>(
PAGE_BLOCKS_LIST(pageId as string), PAGE_BLOCKS_LIST(pageId as string),
(prevData) => (prevData) =>
(prevData ?? []).map((p) => { (prevData ?? []).map((p) => {
if (p.id === block.id) return { ...p, issue: res.id }; if (p.id === block.id) return { ...p, issue: res.id, issue_detail: res };
return p; return p;
}), }),
@ -181,6 +195,31 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
}); });
}; };
const handleBlockSync = () => {
if (!workspaceSlug || !projectId || !pageId) return;
mutate<IPageBlock[]>(
PAGE_BLOCKS_LIST(pageId as string),
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === block.id) return { ...p, sync: !block.sync };
return p;
}),
false
);
pagesService.patchPageBlock(
workspaceSlug as string,
projectId as string,
pageId as string,
block.id,
{
sync: !block.sync,
}
);
};
const handleCopyText = () => { const handleCopyText = () => {
const originURL = const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
@ -217,26 +256,48 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
<TextArea <TextArea
id="name" id="name"
name="name" name="name"
placeholder="Enter block title" placeholder="Block title"
value={watch("name")} value={watch("name")}
onBlur={handleSubmit(updatePageBlock)} onBlur={handleSubmit(updatePageBlock)}
onChange={(e) => setValue("name", e.target.value)} onChange={(e) => setValue("name", e.target.value)}
required={true} required={true}
className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent text-base font-medium" className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent text-base font-medium"
role="textbox" role="textbox"
disabled={block.issue ? true : false}
/> />
<div className="flex items-center"> <div className="flex flex-shrink-0 items-center gap-2">
{block.sync && (
<div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded bg-gray-100 py-1 px-1.5 text-xs">
{isSyncing ? (
<ArrowPathIcon className="h-3 w-3 animate-spin" />
) : (
<CheckIcon className="h-3 w-3" />
)}
{isSyncing ? "Syncing..." : "Synced"}
</div>
)}
{block.issue && (
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${block.issue}`}>
<a className="flex flex-shrink-0 items-center gap-1 rounded bg-gray-100 px-1.5 py-1 text-xs">
<LayerDiagonalIcon height="16" width="16" color="black" />
{projectDetails?.identifier}-{block.issue_detail?.sequence_id}
</a>
</Link>
)}
<button <button
type="button" type="button"
className="rounded px-1.5 py-1 text-xs hover:bg-gray-100" className="-mr-2 rounded px-1.5 py-1 text-xs hover:bg-gray-100"
onClick={() => setGptAssistantModal((prevData) => !prevData)} onClick={() => setGptAssistantModal((prevData) => !prevData)}
> >
AI AI
</button> </button>
<CustomMenu label={<WaterDropIcon width={14} height={15} />} noBorder noChevron> <CustomMenu label={<WaterDropIcon width={14} height={15} />} noBorder noChevron>
{block.issue ? ( {block.issue ? (
<>
<CustomMenu.MenuItem onClick={handleBlockSync}>
<>Turn sync {block.sync ? "off" : "on"}</>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy issue link</CustomMenu.MenuItem> <CustomMenu.MenuItem onClick={handleCopyText}>Copy issue link</CustomMenu.MenuItem>
</>
) : ( ) : (
<> <>
<CustomMenu.MenuItem onClick={pushBlockIntoIssues}> <CustomMenu.MenuItem onClick={pushBlockIntoIssues}>
@ -265,10 +326,8 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails }) => {
onBlur={handleSubmit(updatePageBlock)} onBlur={handleSubmit(updatePageBlock)}
onJSONChange={(jsonValue) => setValue("description", jsonValue)} onJSONChange={(jsonValue) => setValue("description", jsonValue)}
onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)}
placeholder="Description..." placeholder="Block description..."
editable={block.issue ? false : true}
customClassName="text-gray-500" customClassName="text-gray-500"
// gptOption
noBorder noBorder
/> />
)} )}

View File

@ -1,5 +1,5 @@
// types // types
import { IIssueLabels } from "./issues"; import { IIssue, IIssueLabels } from "./issues";
export interface IPage { export interface IPage {
access: number; access: number;
@ -36,11 +36,12 @@ export interface IPageBlock {
description_stripped: any; description_stripped: any;
id: string; id: string;
issue: string | null; issue: string | null;
issue_detail: string | null; issue_detail: IIssue | null;
name: string; name: string;
page: string; page: string;
project: string; project: string;
sort_order: number; sort_order: number;
sync: boolean;
updated_at: Date; updated_at: Date;
updated_by: string; updated_by: string;
workspace: string; workspace: string;