Skip to content
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
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
Loading