snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
305
apps/web/core/hooks/use-navigation-preferences.ts
Normal file
305
apps/web/core/hooks/use-navigation-preferences.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import type {
|
||||
TPersonalNavigationItemKey,
|
||||
TPersonalNavigationPreferences,
|
||||
TProjectNavigationPreferences,
|
||||
TProjectNavigationMode,
|
||||
TWorkspaceNavigationPreferences,
|
||||
TWorkspaceNavigationItemState,
|
||||
TAppRailPreferences,
|
||||
TAppRailDisplayMode,
|
||||
} from "@plane/types";
|
||||
import {
|
||||
DEFAULT_PERSONAL_PREFERENCES,
|
||||
DEFAULT_PROJECT_PREFERENCES,
|
||||
DEFAULT_WORKSPACE_PREFERENCES,
|
||||
DEFAULT_APP_RAIL_PREFERENCES,
|
||||
} from "@plane/types";
|
||||
import { useWorkspace } from "./store/use-workspace";
|
||||
import useLocalStorage from "./use-local-storage";
|
||||
|
||||
const APP_RAIL_PREFERENCES_KEY = "app_rail_preferences";
|
||||
|
||||
export const usePersonalNavigationPreferences = () => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const { getNavigationPreferences, updateBulkSidebarPreferences } = useWorkspace();
|
||||
|
||||
// Get preferences from the store
|
||||
const storePreferences = getNavigationPreferences(workspaceSlug?.toString() || "");
|
||||
|
||||
// Convert store format to hook format for personal items
|
||||
const preferences: TPersonalNavigationPreferences = useMemo(() => {
|
||||
if (!storePreferences) {
|
||||
return DEFAULT_PERSONAL_PREFERENCES;
|
||||
}
|
||||
|
||||
// Extract personal items from the store (stickies, your_work, drafts)
|
||||
const personalItems: Record<TPersonalNavigationItemKey, { enabled: boolean; sort_order: number }> = {
|
||||
stickies: {
|
||||
enabled: storePreferences.stickies?.is_pinned ?? DEFAULT_PERSONAL_PREFERENCES.items.stickies.enabled,
|
||||
sort_order: storePreferences.stickies?.sort_order ?? DEFAULT_PERSONAL_PREFERENCES.items.stickies.sort_order,
|
||||
},
|
||||
your_work: {
|
||||
enabled: storePreferences.your_work?.is_pinned ?? DEFAULT_PERSONAL_PREFERENCES.items.your_work.enabled,
|
||||
sort_order: storePreferences.your_work?.sort_order ?? DEFAULT_PERSONAL_PREFERENCES.items.your_work.sort_order,
|
||||
},
|
||||
drafts: {
|
||||
enabled: storePreferences.drafts?.is_pinned ?? DEFAULT_PERSONAL_PREFERENCES.items.drafts.enabled,
|
||||
sort_order: storePreferences.drafts?.sort_order ?? DEFAULT_PERSONAL_PREFERENCES.items.drafts.sort_order,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
items: personalItems,
|
||||
};
|
||||
}, [storePreferences]);
|
||||
|
||||
const togglePersonalItem = useCallback(
|
||||
async (key: TPersonalNavigationItemKey, enabled: boolean) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const currentItem = preferences.items[key] || { enabled: false, sort_order: 0 };
|
||||
|
||||
await updateBulkSidebarPreferences(workspaceSlug.toString(), [
|
||||
{
|
||||
key,
|
||||
is_pinned: enabled,
|
||||
sort_order: currentItem.sort_order,
|
||||
},
|
||||
]);
|
||||
},
|
||||
[workspaceSlug, preferences, updateBulkSidebarPreferences]
|
||||
);
|
||||
|
||||
const updatePersonalItemOrder = useCallback(
|
||||
async (items: Array<{ key: TPersonalNavigationItemKey; sortOrder: number }>) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const bulkData = items.map((item) => {
|
||||
const currentItem = preferences.items[item.key] || { enabled: true, sort_order: 0 };
|
||||
return {
|
||||
key: item.key,
|
||||
is_pinned: currentItem.enabled,
|
||||
sort_order: item.sortOrder,
|
||||
};
|
||||
});
|
||||
|
||||
await updateBulkSidebarPreferences(workspaceSlug.toString(), bulkData);
|
||||
},
|
||||
[workspaceSlug, preferences, updateBulkSidebarPreferences]
|
||||
);
|
||||
|
||||
const isPersonalItemEnabled = useCallback(
|
||||
(key: TPersonalNavigationItemKey): boolean => preferences.items[key]?.enabled ?? true,
|
||||
[preferences]
|
||||
);
|
||||
|
||||
return {
|
||||
preferences,
|
||||
togglePersonalItem,
|
||||
updatePersonalItemOrder,
|
||||
isPersonalItemEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
export const useProjectNavigationPreferences = () => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const { getProjectNavigationPreferences, updateProjectNavigationPreferences } = useWorkspace();
|
||||
|
||||
// Get preferences from the store
|
||||
const storePreferences = getProjectNavigationPreferences(workspaceSlug?.toString() || "");
|
||||
|
||||
// Computed preferences with fallback logic: API → defaults
|
||||
const preferences: TProjectNavigationPreferences = useMemo(() => {
|
||||
// 1. Try API data first
|
||||
if (
|
||||
storePreferences &&
|
||||
(storePreferences.navigation_control_preference || storePreferences.navigation_project_limit !== undefined)
|
||||
) {
|
||||
const limit = storePreferences.navigation_project_limit ?? DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount;
|
||||
|
||||
return {
|
||||
navigationMode: storePreferences.navigation_control_preference || DEFAULT_PROJECT_PREFERENCES.navigationMode,
|
||||
limitedProjectsCount: limit > 0 ? limit : DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount,
|
||||
showLimitedProjects: limit > 0, // Derived: 0 = false, >0 = true
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Fall back to defaults
|
||||
return DEFAULT_PROJECT_PREFERENCES;
|
||||
}, [storePreferences]);
|
||||
|
||||
// Update navigation mode
|
||||
const updateNavigationMode = useCallback(
|
||||
async (mode: TProjectNavigationMode) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||
navigation_control_preference: mode,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, updateProjectNavigationPreferences]
|
||||
);
|
||||
|
||||
// Update show limited projects
|
||||
const updateShowLimitedProjects = useCallback(
|
||||
async (show: boolean) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
// When toggling off, set to 0; when toggling on, use current count or default
|
||||
const newLimit = show ? preferences.limitedProjectsCount || DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount : 0;
|
||||
|
||||
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||
navigation_project_limit: newLimit,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, updateProjectNavigationPreferences, preferences.limitedProjectsCount]
|
||||
);
|
||||
|
||||
// Update limited projects count
|
||||
const updateLimitedProjectsCount = useCallback(
|
||||
async (count: number) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||
navigation_project_limit: count,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, updateProjectNavigationPreferences]
|
||||
);
|
||||
|
||||
return {
|
||||
preferences,
|
||||
updateNavigationMode,
|
||||
updateShowLimitedProjects,
|
||||
updateLimitedProjectsCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const useWorkspaceNavigationPreferences = () => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const { getNavigationPreferences, updateBulkSidebarPreferences } = useWorkspace();
|
||||
|
||||
// Get preferences from the store
|
||||
const storePreferences = getNavigationPreferences(workspaceSlug?.toString() || "");
|
||||
|
||||
// Convert store format to hook format
|
||||
const preferences: TWorkspaceNavigationPreferences = useMemo(() => {
|
||||
if (!storePreferences) {
|
||||
return DEFAULT_WORKSPACE_PREFERENCES;
|
||||
}
|
||||
|
||||
return {
|
||||
items: storePreferences,
|
||||
};
|
||||
}, [storePreferences]);
|
||||
|
||||
const toggleWorkspaceItem = useCallback(
|
||||
async (key: string, isPinned: boolean) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const currentItem = preferences.items[key] || { is_pinned: false, sort_order: 0 };
|
||||
|
||||
await updateBulkSidebarPreferences(workspaceSlug.toString(), [
|
||||
{
|
||||
key,
|
||||
is_pinned: isPinned,
|
||||
sort_order: currentItem.sort_order,
|
||||
},
|
||||
]);
|
||||
},
|
||||
[workspaceSlug, preferences, updateBulkSidebarPreferences]
|
||||
);
|
||||
|
||||
const updateWorkspaceItemOrder = useCallback(
|
||||
async (items: Array<{ key: string; sortOrder: number }>) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const bulkData = items.map((item) => {
|
||||
const currentItem = preferences.items[item.key] || { is_pinned: true, sort_order: 0 };
|
||||
return {
|
||||
key: item.key,
|
||||
is_pinned: currentItem.is_pinned,
|
||||
sort_order: item.sortOrder,
|
||||
};
|
||||
});
|
||||
|
||||
await updateBulkSidebarPreferences(workspaceSlug.toString(), bulkData);
|
||||
},
|
||||
[workspaceSlug, preferences, updateBulkSidebarPreferences]
|
||||
);
|
||||
|
||||
const getWorkspaceItemState = useCallback(
|
||||
(key: string): TWorkspaceNavigationItemState => preferences.items[key] || { is_pinned: false, sort_order: 0 },
|
||||
[preferences]
|
||||
);
|
||||
|
||||
const isWorkspaceItemPinned = useCallback(
|
||||
(key: string): boolean => {
|
||||
const state = getWorkspaceItemState(key);
|
||||
return state.is_pinned;
|
||||
},
|
||||
[getWorkspaceItemState]
|
||||
);
|
||||
|
||||
const updateWorkspaceItemSortOrder = useCallback(
|
||||
async (key: string, sortOrder: number) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const currentItem = preferences.items[key] || { is_pinned: false, sort_order: 0 };
|
||||
|
||||
await updateBulkSidebarPreferences(workspaceSlug.toString(), [
|
||||
{
|
||||
key,
|
||||
is_pinned: currentItem.is_pinned,
|
||||
sort_order: sortOrder,
|
||||
},
|
||||
]);
|
||||
},
|
||||
[workspaceSlug, preferences, updateBulkSidebarPreferences]
|
||||
);
|
||||
|
||||
return {
|
||||
preferences,
|
||||
toggleWorkspaceItem,
|
||||
updateWorkspaceItemOrder,
|
||||
updateWorkspaceItemSortOrder,
|
||||
getWorkspaceItemState,
|
||||
isWorkspaceItemPinned,
|
||||
};
|
||||
};
|
||||
|
||||
export const useAppRailPreferences = () => {
|
||||
const { storedValue, setValue } = useLocalStorage<TAppRailPreferences>(
|
||||
APP_RAIL_PREFERENCES_KEY,
|
||||
DEFAULT_APP_RAIL_PREFERENCES
|
||||
);
|
||||
|
||||
const updateDisplayMode = useCallback(
|
||||
(mode: TAppRailDisplayMode) => {
|
||||
setValue({
|
||||
displayMode: mode,
|
||||
});
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const toggleDisplayMode = useCallback(() => {
|
||||
const currentPreferences = storedValue || DEFAULT_APP_RAIL_PREFERENCES;
|
||||
const newMode = currentPreferences.displayMode === "icon_only" ? "icon_with_label" : "icon_only";
|
||||
updateDisplayMode(newMode);
|
||||
}, [storedValue, updateDisplayMode]);
|
||||
|
||||
return {
|
||||
preferences: storedValue || DEFAULT_APP_RAIL_PREFERENCES,
|
||||
updateDisplayMode,
|
||||
toggleDisplayMode,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user