Skip to content

Commit

Permalink
[WEB-3088] fix: home edits (#6357)
Browse files Browse the repository at this point in the history
* fix: added delete sticky confirmation modal

* fix: prevented quick links reordering

* fix: quick links css

* fix: minor css

* fix: empty states

* Filter quick_tutorial and new_at_plane

* fix: stickies search backend change

* fix: stickies editor enhanced

* fix: sticky delete function

---------

Co-authored-by: gakshita <[email protected]>
  • Loading branch information
sangeethailango and gakshita authored Jan 9, 2025
1 parent 5d8f66a commit d96ab2e
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 173 deletions.
6 changes: 5 additions & 1 deletion apiserver/plane/app/views/workspace/preference.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ def get(self, request, slug):

create_preference_keys = []

keys = [key for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices]
keys = [
key
for key, _ in WorkspaceHomePreference.HomeWidgetKeys.choices
if key not in ["quick_tutorial", "new_at_plane"]
]

sort_order_counter = 1

Expand Down
9 changes: 7 additions & 2 deletions apiserver/plane/app/views/workspace/sticky.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ def create(self, request, slug):
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
)
def list(self, request, slug):
query = request.query_params.get("query", False)
stickies = self.get_queryset()
if query:
stickies = stickies.filter(name__icontains=query)

return self.paginate(
request=request,
queryset=(self.get_queryset()),
queryset=(stickies),
on_results=lambda stickies: StickySerializer(stickies, many=True).data,
default_per_page=20,
)

@allow_permission(allowed_roles=[], creator=True, model=Sticky, level="WORKSPACE")
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs)
Expand Down
111 changes: 111 additions & 0 deletions web/core/components/core/content-overflow-HOC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { ReactNode, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { cn } from "@plane/utils";

interface IContentOverflowWrapper {
children: ReactNode;
maxHeight?: number;
gradientColor?: string;
buttonClassName?: string;
containerClassName?: string;
fallback?: ReactNode;
}

export const ContentOverflowWrapper = observer((props: IContentOverflowWrapper) => {
const {
children,
maxHeight = 625,
buttonClassName = "text-sm font-medium text-custom-primary-100",
containerClassName,
fallback = null,
} = props;

// states
const [containerHeight, setContainerHeight] = useState(0);
const [showAll, setShowAll] = useState(false);

// refs
const contentRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!contentRef?.current) return;

const updateHeight = () => {
if (contentRef.current) {
const height = contentRef.current.getBoundingClientRect().height;
setContainerHeight(height);
}
};

// Initial height measurement
updateHeight();

// Create ResizeObserver for size changes
const resizeObserver = new ResizeObserver(updateHeight);
resizeObserver.observe(contentRef.current);

// Create MutationObserver for content changes
const mutationObserver = new MutationObserver((mutations) => {
const shouldUpdate = mutations.some(
(mutation) =>
mutation.type === "childList" ||
(mutation.type === "attributes" && (mutation.attributeName === "style" || mutation.attributeName === "class"))
);

if (shouldUpdate) {
updateHeight();
}
});

mutationObserver.observe(contentRef.current, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["style", "class"],
});

return () => {
resizeObserver.disconnect();
mutationObserver.disconnect();
};
}, [contentRef?.current]);

if (!children) return fallback;

return (
<div
className={cn(
"relative",
{
[`overflow-hidden`]: !showAll,
"overflow-visible": showAll,
},
containerClassName
)}
style={{ maxHeight: showAll ? "100%" : `${maxHeight}px` }}
>
<div ref={contentRef}>{children}</div>

{containerHeight > maxHeight && (
<div
className={cn(
"bottom-0 left-0 w-full",
`bg-gradient-to-t from-custom-background-100 to-transparent flex flex-col items-center justify-end`,
"text-center",
{
"absolute h-[100px]": !showAll,
"h-[30px]": showAll,
}
)}
>
<button
className={cn("gap-1 w-full text-custom-primary-100 text-sm font-medium", buttonClassName)}
onClick={() => setShowAll((prev) => !prev)}
>
{showAll ? "Show less" : "Show all"}
</button>
</div>
)}
</div>
);
});
2 changes: 1 addition & 1 deletion web/core/components/editor/sticky-editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface StickyEditorWrapperProps
uploadFile: (file: File) => Promise<string>;
parentClassName?: string;
handleColorChange: (data: Partial<TSticky>) => Promise<void>;
handleDelete: () => Promise<void>;
handleDelete: () => void;
}

export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperProps>((props, ref) => {
Expand Down
21 changes: 0 additions & 21 deletions web/core/components/home/widgets/empty-states/issues.tsx

This file was deleted.

27 changes: 27 additions & 0 deletions web/core/components/home/widgets/empty-states/links.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Link2, Plus } from "lucide-react";
import { Button } from "@plane/ui";

type TProps = {
handleCreate: () => void;
};
export const LinksEmptyState = (props: TProps) => {
const { handleCreate } = props;
return (
<div className="min-h-[200px] flex w-full justify-center py-6 border-[1.5px] border-custom-border-100 rounded">
<div className="m-auto">
<div
className={`mb-2 rounded-full mx-auto last:rounded-full w-[50px] h-[50px] flex items-center justify-center bg-custom-background-80/40 transition-transform duration-300`}
>
<Link2 size={30} className="text-custom-text-400 -rotate-45" />
</div>
<div className="text-custom-text-100 font-medium text-base text-center mb-1">No quick links yet</div>
<div className="text-custom-text-300 text-sm text-center mb-2">
Add any links you need for quick access to your work.{" "}
</div>
<Button variant="accent-primary" size="sm" onClick={handleCreate} className="mx-auto">
<Plus className="size-4 my-auto" /> <span>Add quick link</span>
</Button>
</div>
</div>
);
};
15 changes: 15 additions & 0 deletions web/core/components/home/widgets/empty-states/recents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { History } from "lucide-react";

export const RecentsEmptyState = () => (
<div className="h-[200px] flex w-full justify-center py-6 border-[1.5px] border-custom-border-100 rounded">
<div className="m-auto">
<div
className={`mb-2 rounded-full mx-auto last:rounded-full w-[50px] h-[50px] flex items-center justify-center bg-custom-background-80/40 transition-transform duration-300`}
>
<History size={30} className="text-custom-text-400 -rotate-45" />
</div>
<div className="text-custom-text-100 font-medium text-base text-center mb-1">No recent items yet</div>
<div className="text-custom-text-300 text-sm text-center mb-2">You don’t have any recent items yet. </div>
</div>
</div>
);
64 changes: 17 additions & 47 deletions web/core/components/home/widgets/links/links.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FC, useEffect, useState } from "react";
import { FC } from "react";
import { observer } from "mobx-react";
// computed
import { ContentOverflowWrapper } from "@/components/core/content-overflow-HOC";
import { useHome } from "@/hooks/store/use-home";
import { LinksEmptyState } from "../empty-states/links";
import { EWidgetKeys, WidgetLoader } from "../loaders";
import { AddLink } from "./action";
import { ProjectLinkDetail } from "./link-detail";
import { TLinkOperations } from "./use-links";

Expand All @@ -17,61 +18,30 @@ export type TProjectLinkList = {
export const ProjectLinkList: FC<TProjectLinkList> = observer((props) => {
// props
const { linkOperations, workspaceSlug } = props;
// states
const [columnCount, setColumnCount] = useState(4);
const [showAll, setShowAll] = useState(false);
// hooks
const {
quickLinks: { getLinksByWorkspaceId, toggleLinkModal },
} = useHome();

const links = getLinksByWorkspaceId(workspaceSlug);

useEffect(() => {
const updateColumnCount = () => {
if (window.matchMedia("(min-width: 1024px)").matches) {
setColumnCount(4); // lg screens
} else if (window.matchMedia("(min-width: 768px)").matches) {
setColumnCount(3); // md screens
} else if (window.matchMedia("(min-width: 640px)").matches) {
setColumnCount(2); // sm screens
} else {
setColumnCount(1); // mobile
}
};

// Initial check
updateColumnCount();

// Add event listener for window resize
window.addEventListener("resize", updateColumnCount);

// Cleanup
return () => window.removeEventListener("resize", updateColumnCount);
}, []);

if (links === undefined) return <WidgetLoader widgetKey={EWidgetKeys.QUICK_LINKS} />;

if (links.length === 0) return <LinksEmptyState handleCreate={() => toggleLinkModal(true)} />;
return (
<div>
<div className="flex gap-2 mb-2 flex-wrap justify-center ">
{links &&
links.length > 0 &&
(showAll ? links : links.slice(0, 2 * columnCount - 1)).map((linkId) => (
<ProjectLinkDetail key={linkId} linkId={linkId} linkOperations={linkOperations} />
))}

{/* Add new link */}
<AddLink onClick={() => toggleLinkModal(true)} />
<ContentOverflowWrapper
maxHeight={150}
containerClassName="pb-2 box-border"
fallback={<></>}
buttonClassName="bg-custom-background-90/20"
>
<div>
<div className="flex gap-2 mb-2 flex-wrap">
{links &&
links.length > 0 &&
links.map((linkId) => <ProjectLinkDetail key={linkId} linkId={linkId} linkOperations={linkOperations} />)}
</div>
</div>
{links.length > 2 * columnCount - 1 && (
<button
className="flex items-center justify-center gap-1 rounded-md px-2 py-1 text-sm font-medium text-custom-primary-100 mx-auto"
onClick={() => setShowAll((state) => !state)}
>
{showAll ? "Show less" : "Show more"}
</button>
)}
</div>
</ContentOverflowWrapper>
);
});
20 changes: 17 additions & 3 deletions web/core/components/home/widgets/links/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { observer } from "mobx-react";
import useSWR from "swr";
import { Plus } from "lucide-react";
import { THomeWidgetProps } from "@plane/types";
import { useHome } from "@/hooks/store/use-home";
import { LinkCreateUpdateModal } from "./create-update-link-modal";
Expand Down Expand Up @@ -31,9 +32,22 @@ export const DashboardQuickLinks = observer((props: THomeWidgetProps) => {
preloadedData={linkData}
setLinkData={setLinkData}
/>
<div className="flex mx-auto flex-wrap pb-4 w-full justify-center">
{/* rendering links */}
<ProjectLinkList workspaceSlug={workspaceSlug} linkOperations={linkOperations} />
<div className="mb-2">
<div className="flex items-center justify-between mb-4">
<div className="text-base font-semibold text-custom-text-350">Quick links</div>
<button
onClick={() => {
toggleLinkModal(true);
}}
className="flex gap-1 text-sm font-medium text-custom-primary-100 my-auto"
>
<Plus className="size-4 my-auto" /> <span>Add quick link</span>
</button>
</div>
<div className="flex flex-wrap w-full">
{/* rendering links */}
<ProjectLinkList workspaceSlug={workspaceSlug} linkOperations={linkOperations} />
</div>
</div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/home/widgets/loaders/quick-links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import range from "lodash/range";
import { Loader } from "@plane/ui";

export const QuickLinksWidgetLoader = () => (
<Loader className="bg-custom-background-100 rounded-xl gap-2 flex flex-wrap justify-center">
<Loader className="bg-custom-background-100 rounded-xl gap-2 flex flex-wrap">
{range(4).map((index) => (
<Loader.Item key={index} height="56px" width="230px" />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Loader } from "@plane/ui";

export const RecentActivityWidgetLoader = () => (
<Loader className="bg-custom-background-100 rounded-xl px-2 space-y-6">
{range(7).map((index) => (
{range(5).map((index) => (
<div key={index} className="flex items-start gap-3.5">
<div className="flex-shrink-0">
<Loader.Item height="32px" width="32px" />
Expand Down
12 changes: 6 additions & 6 deletions web/core/components/home/widgets/recents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { LayersIcon } from "@plane/ui";
import { useProject } from "@/hooks/store";
import { WorkspaceService } from "@/plane-web/services";
import { EmptyWorkspace } from "../empty-states";
import { IssuesEmptyState } from "../empty-states/issues";
import { RecentsEmptyState } from "../empty-states/recents";
import { EWidgetKeys, WidgetLoader } from "../loaders";
import { FiltersDropdown } from "./filters";
import { RecentIssue } from "./issue";
Expand Down Expand Up @@ -68,24 +68,24 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
if (!isLoading && recents?.length === 0)
return (
<div ref={ref} className=" max-h-[500px] overflow-y-scroll">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center justify-between mb-4">
<div className="text-base font-semibold text-custom-text-350">Recents</div>
<FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />
</div>
<div className="min-h-[400px] flex flex-col items-center justify-center">
<IssuesEmptyState />
<div className="flex flex-col items-center justify-center">
<RecentsEmptyState />
</div>
</div>
);

return (
<div ref={ref} className=" max-h-[500px] min-h-[400px] overflow-y-scroll">
<div ref={ref} className=" max-h-[500px] min-h-[250px] overflow-y-scroll">
<div className="flex items-center justify-between mb-2">
<div className="text-base font-semibold text-custom-text-350">Recents</div>

<FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />
</div>
<div className="min-h-[400px] flex flex-col">
<div className="min-h-[250px] flex flex-col">
{isLoading && <WidgetLoader widgetKey={WIDGET_KEY} />}
{!isLoading &&
recents?.length > 0 &&
Expand Down
Loading

0 comments on commit d96ab2e

Please sign in to comment.