make create dashboard here actually create a dashboard here
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router';
|
||||
import { Link, useLocation, useSearchParams } from 'react-router';
|
||||
import { LayoutGrid, Plus, Pencil, Trash2, Search } from 'lucide-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { generateSlug } from 'helpers/slug';
|
||||
import { useUserState } from 'state/useUserState';
|
||||
import { useDashboardState, persistDashboardState } from 'state/useDashboardState';
|
||||
import type { DashboardDefinition } from '../../components/Workspace';
|
||||
@@ -25,10 +24,10 @@ import {
|
||||
NEW_DASH_DESC_KEY,
|
||||
NEW_DASH_TEMPLATE_KEY,
|
||||
} from './constants';
|
||||
import { useNewDashboardDraft } from './useNewDashboardDraft';
|
||||
|
||||
export const DashboardListApp = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
const { value: dashboards, setValue: setDashboards } = useDashboardState<DashboardDefinition[]>('workspaces', []);
|
||||
@@ -49,6 +48,9 @@ export const DashboardListApp = () => {
|
||||
const [, setDescription] = useGlobal<string>(NEW_DASH_DESC_KEY, '');
|
||||
const [, setTemplateIdx] = useGlobal<number>(NEW_DASH_TEMPLATE_KEY, 0);
|
||||
const [, setFilePath] = useUserState<string>('files/currentPath', '/');
|
||||
// Shared with the file browser's "Create Dashboard here", which needs the same five writes seeded from
|
||||
// a folder rather than from nothing.
|
||||
const openNewDashboard = useNewDashboardDraft();
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [deleting, setDeleting] = useState<DashboardDefinition | null>(null);
|
||||
@@ -106,16 +108,7 @@ export const DashboardListApp = () => {
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
clearSelected();
|
||||
setEditing(null);
|
||||
setName(generateSlug());
|
||||
setDescription('');
|
||||
setTemplateIdx(0);
|
||||
setFilePath('/');
|
||||
setCreating(true);
|
||||
if (!isDashboardsPage) navigate('/dashboards');
|
||||
}}
|
||||
onClick={() => openNewDashboard()}
|
||||
className="flex items-center justify-center gap-1 py-2 px-3 rounded-lg text-sm font-medium bg-duck-teal hover:bg-duck-teal/90 text-white transition-all cursor-pointer"
|
||||
>
|
||||
<Plus className="h-4 w-4 shrink-0" />
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { useUserState } from 'state/useUserState';
|
||||
import { generateSlug } from 'helpers/slug';
|
||||
import {
|
||||
CREATING_DASHBOARD_KEY,
|
||||
EDITING_DASHBOARD_KEY,
|
||||
NEW_DASH_NAME_KEY,
|
||||
NEW_DASH_DESC_KEY,
|
||||
NEW_DASH_TEMPLATE_KEY,
|
||||
} from './constants';
|
||||
|
||||
type NewDashboardSeed = {
|
||||
/** Pre-fills the name field. Falls back to a generated slug, which is what the plain button does. */
|
||||
name?: string;
|
||||
/** Home-relative folder the dashboard should be rooted at, leading slash, e.g. `/Pictures/Photos`. */
|
||||
path?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the create-dashboard form, optionally seeded. Returns the opener, not state.
|
||||
*
|
||||
* The seed is spread across four `useGlobal` form-draft keys plus `files/currentPath` — `NewDashboardForm`
|
||||
* derives its `cwd` from that last one rather than holding a folder field of its own — so "start creating a
|
||||
* dashboard" is five writes in a specific order, not one. It lived inline in the New Dashboard button, and
|
||||
* the file browser's "Create Dashboard here" tried to express the same intent as a URL instead
|
||||
* (`/dashboards/new?name=…&cwd=…`). That URL never had a route: `new` matched `/dashboards/:id`, found no
|
||||
* dashboard by that id, and redirected to the bare list, dropping the params — so the menu item had been
|
||||
* doing nothing since the workspaces→dashboards rename in `927267e`. One opener, two call sites, no URL
|
||||
* contract to keep in sync.
|
||||
*
|
||||
* Seeding a draft *and then* navigating is why the callers stay buttons rather than becoming links: a link
|
||||
* cannot express the "and then".
|
||||
*/
|
||||
export const useNewDashboardDraft = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [, setCreating] = useGlobal<boolean>(CREATING_DASHBOARD_KEY, false);
|
||||
const [, setEditing] = useGlobal<string | null>(EDITING_DASHBOARD_KEY, null);
|
||||
const [, setName] = useGlobal<string>(NEW_DASH_NAME_KEY, '');
|
||||
const [, setDescription] = useGlobal<string>(NEW_DASH_DESC_KEY, '');
|
||||
const [, setTemplateIdx] = useGlobal<number>(NEW_DASH_TEMPLATE_KEY, 0);
|
||||
const [, setFilePath] = useUserState<string>('files/currentPath', '/');
|
||||
|
||||
return ({ name, path }: NewDashboardSeed = {}) => {
|
||||
setEditing(null);
|
||||
setName(name?.trim() || generateSlug());
|
||||
setDescription('');
|
||||
setTemplateIdx(0);
|
||||
setFilePath(path || '/');
|
||||
setCreating(true);
|
||||
// `/dashboards` with no query — which is also how `?selected=` gets cleared, since an open form and a
|
||||
// selected dashboard are different states. Replace when already there so opening the form is not a
|
||||
// history entry you have to press Back through twice.
|
||||
navigate('/dashboards', { replace: location.pathname === '/dashboards' });
|
||||
};
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { useAgents, type AgentSummary } from '../useAgents';
|
||||
import { useUserState } from 'state/useUserState';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useFilesRefresh } from '../../../channels';
|
||||
import { useNewDashboardDraft } from '../../Dashboards/useNewDashboardDraft';
|
||||
|
||||
/**
|
||||
* Where the browser is looking, when it is the one browser that owns the address bar. `urlPath` is opt-in
|
||||
@@ -26,6 +27,7 @@ const withPath = (prev: URLSearchParams, path: string, basePath: string) => {
|
||||
export const useFileBrowserApp = (basePath: string, rootOverride?: string, urlPath = false) => {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const openNewDashboard = useNewDashboardDraft();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const homeRoot = 'home';
|
||||
const [, setGlobalPath] = useUserState<string>('files/currentPath', '/');
|
||||
@@ -428,7 +430,7 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, urlPa
|
||||
|
||||
const handleCreateDashboard = (entry: DirEntry) => {
|
||||
const folderPath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
|
||||
navigate(`/dashboards/new?name=${encodeURIComponent(entry.name)}&cwd=${encodeURIComponent(folderPath)}`);
|
||||
openNewDashboard({ name: entry.name, path: folderPath });
|
||||
};
|
||||
|
||||
const handleCopyPath = (entry: DirEntry) => {
|
||||
@@ -452,9 +454,7 @@ export const useFileBrowserApp = (basePath: string, rootOverride?: string, urlPa
|
||||
|
||||
const handleCreateDashboardHere = () => {
|
||||
const dirName = currentPath === '/' ? '' : currentPath.split('/').pop()!;
|
||||
const params = new URLSearchParams({ cwd: currentPath });
|
||||
if (dirName) params.set('name', dirName);
|
||||
navigate(`/dashboards/new?${params}`);
|
||||
openNewDashboard({ name: dirName, path: currentPath });
|
||||
};
|
||||
|
||||
const handleReadAloud = async (entry: DirEntry) => {
|
||||
|
||||
Reference in New Issue
Block a user