[WIKI-320] refactor: page header actions (#6946)
* refactor: page header actions * chore: update toolbar component * chore: update archived and lock badge colors * chore: added observer to favorite control
This commit is contained in:
parent
8166a757a7
commit
eac1115566
29 changed files with 557 additions and 290 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { RefObject, useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ChevronRight, CornerDownRight, LucideIcon, RefreshCcw, Sparkles, TriangleAlert } from "lucide-react";
|
||||
// plane editor
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
|
|
@ -18,7 +18,7 @@ import { AskPiMenu } from "./ask-pi-menu";
|
|||
const aiService = new AIService();
|
||||
|
||||
type Props = {
|
||||
editorRef: RefObject<EditorRefApi>;
|
||||
editorRef: EditorRefApi | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
workspaceId: string;
|
||||
|
|
@ -73,7 +73,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
|
|||
};
|
||||
// handle task click
|
||||
const handleClick = async (key: AI_EDITOR_TASKS) => {
|
||||
const selection = editorRef.current?.getSelectedText();
|
||||
const selection = editorRef?.getSelectedText();
|
||||
if (!selection || activeTask === key) return;
|
||||
setActiveTask(key);
|
||||
if (key === AI_EDITOR_TASKS.ASK_ANYTHING) return;
|
||||
|
|
@ -86,7 +86,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
|
|||
};
|
||||
// handle re-generate response
|
||||
const handleRegenerate = async () => {
|
||||
const selection = editorRef.current?.getSelectedText();
|
||||
const selection = editorRef?.getSelectedText();
|
||||
if (!selection || !activeTask) return;
|
||||
setIsRegenerating(true);
|
||||
await handleGenerateResponse({
|
||||
|
|
@ -104,7 +104,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
|
|||
// handle re-generate response
|
||||
const handleToneChange = async (key: string) => {
|
||||
const selectedTone = TONES_LIST.find((t) => t.key === key);
|
||||
const selection = editorRef.current?.getSelectedText();
|
||||
const selection = editorRef?.getSelectedText();
|
||||
if (!selectedTone || !selection || !activeTask) return;
|
||||
setResponse(undefined);
|
||||
setIsRegenerating(false);
|
||||
|
|
@ -123,7 +123,7 @@ export const EditorAIMenu: React.FC<Props> = (props) => {
|
|||
// handle replace selected text with the response
|
||||
const handleInsertText = (insertOnNextLine: boolean) => {
|
||||
if (!response) return;
|
||||
editorRef.current?.insertText(response, insertOnNextLine);
|
||||
editorRef?.insertText(response, insertOnNextLine);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
|
|
|||
10
web/ce/components/pages/header/collaborators-list.tsx
Normal file
10
web/ce/components/pages/header/collaborators-list.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"use client";
|
||||
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
export type TPageCollaboratorsListProps = {
|
||||
page: TPageInstance;
|
||||
};
|
||||
|
||||
export const PageCollaboratorsList = ({}: TPageCollaboratorsListProps) => null;
|
||||
116
web/ce/components/pages/header/lock-control.tsx
Normal file
116
web/ce/components/pages/header/lock-control.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// hooks
|
||||
import { usePageOperations } from "@/hooks/use-page-operations";
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
// Define our lock display states, renaming "icon-only" to "neutral"
|
||||
type LockDisplayState = "neutral" | "locked" | "unlocked";
|
||||
|
||||
type Props = {
|
||||
page: TPageInstance;
|
||||
};
|
||||
|
||||
export const PageLockControl = observer(({ page }: Props) => {
|
||||
// Initial state: if locked, then "locked", otherwise default to "neutral"
|
||||
const [displayState, setDisplayState] = useState<LockDisplayState>(page.is_locked ? "locked" : "neutral");
|
||||
// derived values
|
||||
const { canCurrentUserLockPage, is_locked } = page;
|
||||
// Ref for the transition timer
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
// Ref to store the previous value of isLocked for detecting transitions
|
||||
const prevLockedRef = useRef(is_locked);
|
||||
// page operations
|
||||
const {
|
||||
pageOperations: { toggleLock },
|
||||
} = usePageOperations({
|
||||
page,
|
||||
});
|
||||
|
||||
// Cleanup any running timer on unmount
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Update display state when isLocked changes
|
||||
useEffect(() => {
|
||||
// Clear any previous timer to avoid overlapping transitions
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
|
||||
// Transition logic:
|
||||
// If locked, ensure the display state is "locked"
|
||||
// If unlocked after being locked, show "unlocked" briefly then revert to "neutral"
|
||||
if (is_locked) {
|
||||
setDisplayState("locked");
|
||||
} else if (prevLockedRef.current === true) {
|
||||
setDisplayState("unlocked");
|
||||
timerRef.current = setTimeout(() => {
|
||||
setDisplayState("neutral");
|
||||
timerRef.current = null;
|
||||
}, 600);
|
||||
} else {
|
||||
setDisplayState("neutral");
|
||||
}
|
||||
|
||||
// Update the previous locked state
|
||||
prevLockedRef.current = is_locked;
|
||||
}, [is_locked]);
|
||||
|
||||
if (!canCurrentUserLockPage) return null;
|
||||
|
||||
// Render different UI based on the current display state
|
||||
return (
|
||||
<>
|
||||
{displayState === "neutral" && (
|
||||
<Tooltip tooltipContent="Lock" position="bottom">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
|
||||
aria-label="Lock"
|
||||
>
|
||||
<LockKeyhole className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{displayState === "locked" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleLock}
|
||||
className="h-6 flex items-center gap-1 px-2 rounded text-custom-primary-100 bg-custom-primary-100/20 hover:bg-custom-primary-100/30 transition-colors"
|
||||
aria-label="Locked"
|
||||
>
|
||||
<LockKeyhole className="flex-shrink-0 size-3.5 animate-lock-icon" />
|
||||
<span className="text-xs font-medium whitespace-nowrap overflow-hidden transition-all duration-500 ease-out animate-text-slide-in">
|
||||
Locked
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{displayState === "unlocked" && (
|
||||
<div
|
||||
className="h-6 flex items-center gap-1 px-2 rounded text-custom-text-200 animate-fade-out"
|
||||
aria-label="Unlocked"
|
||||
>
|
||||
<LockKeyholeOpen className="flex-shrink-0 size-3.5 animate-unlock-icon" />
|
||||
<span className="text-xs font-medium whitespace-nowrap overflow-hidden transition-all duration-500 ease-out animate-text-slide-in animate-text-fade-out">
|
||||
Unlocked
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
10
web/ce/components/pages/header/move-control.tsx
Normal file
10
web/ce/components/pages/header/move-control.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"use client";
|
||||
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
export type TPageMoveControlProps = {
|
||||
page: TPageInstance;
|
||||
};
|
||||
|
||||
export const PageMoveControl = ({}: TPageMoveControlProps) => null;
|
||||
Loading…
Add table
Add a link
Reference in a new issue