fix toolbar responsiveness and disable side panel

This commit is contained in:
miloschwartz
2026-07-01 15:14:29 -04:00
parent 0871a211ec
commit db0a7cc1ce
9 changed files with 167 additions and 122 deletions

View File

@@ -271,6 +271,7 @@ export default async function migration() {
`ALTER TABLE 'sites' DROP COLUMN 'remoteSubnets';`
).run();
<<<<<<< Updated upstream
// get all of the siteResources and set the aliasAddress to 100.96.128.x starting at .8
const siteResourcesForAlias = db
.prepare(
@@ -278,6 +279,15 @@ export default async function migration() {
)
.all() as {
siteResourceId: number;
=======
// Associate clients with site resources based on their previous site access
// Get all client-site associations from the renamed clientSitesAssociationsCache table
const clientSiteAssociations = db
.prepare(`SELECT clientId, siteId FROM 'clientSitesAssociationsCache'`)
.all() as {
clientId: number;
siteId: number;
>>>>>>> Stashed changes
}[];
const updateAliasAddress = db.prepare(
@@ -335,6 +345,7 @@ export default async function migration() {
`INSERT INTO 'clientSiteResources' ('clientId', 'siteResourceId') VALUES (?, ?)`
);
<<<<<<< Updated upstream
// create a clientSiteResourcesAssociationsCache entry for each existing association as well
const insertClientSiteResourceCache = db.prepare(
`INSERT INTO 'clientSiteResourcesAssociationsCache' ('clientId', 'siteResourceId') VALUES (?, ?)`
@@ -345,6 +356,13 @@ export default async function migration() {
const siteResources = getSiteResources.all(
association.siteId
) as {
=======
// For each client-site association, find all site resources for that site
for (const association of clientSiteAssociations) {
const siteResources = db
.prepare(`SELECT siteResourceId FROM 'siteResources' WHERE siteId = ?`)
.all(association.siteId) as {
>>>>>>> Stashed changes
siteResourceId: number;
}[];
@@ -352,15 +370,25 @@ export default async function migration() {
for (const siteResource of siteResources) {
insertClientSiteResource.run(
association.clientId,
<<<<<<< Updated upstream
siteResource.siteResourceId
);
insertClientSiteResourceCache.run(
association.clientId,
=======
>>>>>>> Stashed changes
siteResource.siteResourceId
);
}
}
// Get all clients for niceId population (used later)
const clients = db
.prepare(`SELECT clientId FROM 'clients'`)
.all() as {
clientId: number;
}[];
// Associate existing site resources with their org's admin role
const siteResourcesWithOrg = db
.prepare(`SELECT siteResourceId, orgId FROM 'siteResources'`)

View File

@@ -26,7 +26,7 @@ type LauncherGroupListProps = {
};
resourcesByGroupKey: Record<string, LauncherGroupResources>;
onClearFilters?: () => void;
onResourceSelect: (resource: LauncherResource) => void;
onResourceSelect?: (resource: LauncherResource) => void;
};
function hasActiveLauncherFilters(config: LauncherViewConfig): boolean {

View File

@@ -40,7 +40,7 @@ type LauncherGroupSectionProps = {
pageSize: number;
};
defaultOpen?: boolean;
onResourceSelect: (resource: LauncherResource) => void;
onResourceSelect?: (resource: LauncherResource) => void;
};
export function LauncherGroupSection({

View File

@@ -10,7 +10,7 @@ import { getLauncherResourceSelectProps } from "./useLauncherResourceAction";
type LauncherResourceCardProps = {
resource: LauncherResource;
showLabels: boolean;
onSelect: () => void;
onSelect?: () => void;
};
export function LauncherResourceCard({
@@ -19,18 +19,20 @@ export function LauncherResourceCard({
onSelect
}: LauncherResourceCardProps) {
const hasIcon = Boolean(resource.iconUrl);
const clickProps = getLauncherResourceSelectProps(onSelect);
const clickProps = onSelect
? getLauncherResourceSelectProps(onSelect)
: null;
return (
<div
className={cn(
"flex min-w-0 flex-col gap-2.5 overflow-hidden rounded-xl border border-border bg-background p-4",
clickProps.className
clickProps?.className
)}
onClick={clickProps.onClick}
onKeyDown={clickProps.onKeyDown}
role={clickProps.role}
tabIndex={clickProps.tabIndex}
onClick={clickProps?.onClick}
onKeyDown={clickProps?.onKeyDown}
role={clickProps?.role}
tabIndex={clickProps?.tabIndex}
>
<div
className={cn(

View File

@@ -6,7 +6,7 @@ import { LauncherResourceCard } from "./LauncherResourceCard";
type LauncherResourceGridProps = {
resources: LauncherResource[];
showLabels: boolean;
onResourceSelect: (resource: LauncherResource) => void;
onResourceSelect?: (resource: LauncherResource) => void;
};
export function LauncherResourceGrid({
@@ -21,7 +21,11 @@ export function LauncherResourceGrid({
key={resource.launcherResourceKey}
resource={resource}
showLabels={showLabels}
onSelect={() => onResourceSelect(resource)}
onSelect={
onResourceSelect
? () => onResourceSelect(resource)
: undefined
}
/>
))}
</div>

View File

@@ -6,7 +6,7 @@ import { LauncherResourceRow } from "./LauncherResourceRow";
type LauncherResourceListProps = {
resources: LauncherResource[];
showLabels: boolean;
onResourceSelect: (resource: LauncherResource) => void;
onResourceSelect?: (resource: LauncherResource) => void;
};
export function LauncherResourceList({
@@ -23,7 +23,11 @@ export function LauncherResourceList({
resource={resource}
showLabels={showLabels}
isLast={index === resources.length - 1}
onSelect={() => onResourceSelect(resource)}
onSelect={
onResourceSelect
? () => onResourceSelect(resource)
: undefined
}
/>
))}
</div>

View File

@@ -11,7 +11,7 @@ type LauncherResourceRowProps = {
resource: LauncherResource;
showLabels: boolean;
isLast?: boolean;
onSelect: () => void;
onSelect?: () => void;
};
export function LauncherResourceRow({
@@ -21,19 +21,21 @@ export function LauncherResourceRow({
onSelect
}: LauncherResourceRowProps) {
const hasTags = showLabels && resource.labels.length > 0;
const clickProps = getLauncherResourceSelectProps(onSelect);
const clickProps = onSelect
? getLauncherResourceSelectProps(onSelect)
: null;
return (
<div
className={cn(
"flex items-center gap-2.5 p-4 max-md:min-w-max max-md:whitespace-nowrap",
isLast ? undefined : "border-b border-border",
clickProps.className
clickProps?.className
)}
onClick={clickProps.onClick}
onKeyDown={clickProps.onKeyDown}
role={clickProps.role}
tabIndex={clickProps.tabIndex}
onClick={clickProps?.onClick}
onKeyDown={clickProps?.onKeyDown}
role={clickProps?.role}
tabIndex={clickProps?.tabIndex}
>
<LauncherResourceIcon
iconUrl={resource.iconUrl}

View File

@@ -37,7 +37,7 @@ export function LauncherViewTabs({
];
return (
<div className="flex items-center gap-2 overflow-x-auto max-w-full shrink min-w-0">
<div className="flex w-max items-center gap-2">
{viewOptions.map((option) => {
const isSelected = activeViewId === option.value;
return (

View File

@@ -32,7 +32,6 @@ import { useToast } from "@app/hooks/useToast";
import { useEnvContext } from "@app/hooks/useEnvContext";
import type {
LauncherGroup,
LauncherResource,
LauncherViewConfig,
LauncherViewRecord
} from "@server/routers/launcher/types";
@@ -51,12 +50,13 @@ import {
import { useDebouncedCallback } from "use-debounce";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedLabel } from "@app/components/labels-selector";
import { useMediaQuery } from "@app/hooks/useMediaQuery";
import { cn } from "@app/lib/cn";
import { LauncherFilterPopover } from "./LauncherFilterPopover";
import { LauncherGroupList } from "./LauncherGroupList";
import { LauncherRefreshButton } from "./LauncherRefreshButton";
import { LauncherSettingsMenu } from "./LauncherSettingsMenu";
import { LauncherSortButton } from "./LauncherSortButton";
import { LauncherResourcePanel } from "./LauncherResourcePanel";
import { LauncherSaveViewMenu, LauncherViewTabs } from "./LauncherViewTabs";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
@@ -98,11 +98,11 @@ export default function ResourceLauncher({
const [searchInputResetKey, setSearchInputResetKey] = useState(0);
const [saveDialogOpen, setSaveDialogOpen] = useState(false);
const [selectedResource, setSelectedResource] =
useState<LauncherResource | null>(null);
const [newViewName, setNewViewName] = useState("");
const [saveOrgWide, setSaveOrgWide] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
const configRef = useRef(config);
configRef.current = config;
const searchInputRef = useRef(config.query);
@@ -392,6 +392,90 @@ export default function ResourceLauncher({
});
};
const savedViewTabs = views.map((view) => ({
viewId: view.viewId,
name: view.name
}));
const renderToolbarSearch = (searchClassName: string) => (
<div className={cn("relative shrink-0", searchClassName)}>
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
key={`${activeViewId}-${searchInputResetKey}`}
defaultValue={config.query}
onChange={(event) => {
const value = event.currentTarget.value;
searchInputRef.current = value;
debouncedNavigateSearch(activeViewIdRef.current, value);
}}
placeholder={t("resourceLauncherSearchPlaceholder")}
className="pl-8"
type="search"
/>
</div>
);
const renderToolbarActions = () => (
<>
<LauncherSaveViewMenu
isDefaultView={isDefaultView}
isAdmin={isAdmin}
isOrgWideView={isOrgWideView}
hasUnsavedChanges={hasUnsavedChanges}
onSaveToCurrent={handleSaveToCurrent}
onSaveAsNew={handleSaveAsNew}
onSaveForEveryone={handleSaveForEveryone}
onMakePersonal={handleMakePersonal}
onResetView={handleResetView}
/>
<LauncherFilterPopover
orgId={orgId}
selectedSites={selectedSites}
selectedLabels={selectedLabels}
onSitesChange={(sites) =>
applyConfigPatch({
siteIds: sites.map((site) => site.siteId)
})
}
onLabelsChange={(labels) =>
applyConfigPatch({
labelIds: labels.map((label) => label.labelId)
})
}
/>
<LauncherSortButton
order={config.order}
onToggle={() =>
applyConfigPatch({
order: config.order === "asc" ? "desc" : "asc"
})
}
/>
<LauncherSettingsMenu
config={config}
isDefaultView={isDefaultView}
onConfigChange={applyConfigPatch}
onDeleteView={() => {
if (!isDefaultView) {
deleteViewMutation.mutate(activeViewId);
}
}}
/>
<LauncherRefreshButton
onRefresh={refreshData}
isRefreshing={isRefreshing || isNavigating}
/>
</>
);
const renderToolbarViews = () => (
<LauncherViewTabs
activeViewId={activeViewId}
savedViews={savedViewTabs}
onSelectView={selectView}
/>
);
return (
<div className="flex flex-col" aria-busy={isNavigating}>
<SettingsSectionTitle
@@ -399,93 +483,27 @@ export default function ResourceLauncher({
description={t("resourceLauncherDescription")}
/>
<div className="flex flex-col gap-3 mb-6">
<div className="flex flex-row items-center gap-3 justify-between max-md:flex-col max-md:items-stretch">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 min-w-0 flex-1 max-md:order-2">
<div className="relative w-full sm:max-w-sm shrink-0">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
key={`${activeViewId}-${searchInputResetKey}`}
defaultValue={config.query}
onChange={(event) => {
const value = event.currentTarget.value;
searchInputRef.current = value;
debouncedNavigateSearch(
activeViewIdRef.current,
value
);
}}
placeholder={t(
"resourceLauncherSearchPlaceholder"
)}
className="pl-8"
type="search"
/>
</div>
<LauncherViewTabs
activeViewId={activeViewId}
savedViews={views.map((view) => ({
viewId: view.viewId,
name: view.name
}))}
onSelectView={selectView}
/>
{isDesktop ? (
<div className="mb-6 flex w-full min-w-0 items-center gap-3">
{renderToolbarSearch("w-64")}
<div className="min-w-0 flex-1 overflow-x-auto">
{renderToolbarViews()}
</div>
<div className="flex items-center gap-2 shrink-0 justify-end max-md:order-1 max-md:justify-start">
<LauncherSaveViewMenu
isDefaultView={isDefaultView}
isAdmin={isAdmin}
isOrgWideView={isOrgWideView}
hasUnsavedChanges={hasUnsavedChanges}
onSaveToCurrent={handleSaveToCurrent}
onSaveAsNew={handleSaveAsNew}
onSaveForEveryone={handleSaveForEveryone}
onMakePersonal={handleMakePersonal}
onResetView={handleResetView}
/>
<LauncherFilterPopover
orgId={orgId}
selectedSites={selectedSites}
selectedLabels={selectedLabels}
onSitesChange={(sites) =>
applyConfigPatch({
siteIds: sites.map((site) => site.siteId)
})
}
onLabelsChange={(labels) =>
applyConfigPatch({
labelIds: labels.map(
(label) => label.labelId
)
})
}
/>
<LauncherSortButton
order={config.order}
onToggle={() =>
applyConfigPatch({
order:
config.order === "asc" ? "desc" : "asc"
})
}
/>
<LauncherSettingsMenu
config={config}
isDefaultView={isDefaultView}
onConfigChange={applyConfigPatch}
onDeleteView={() => {
if (!isDefaultView) {
deleteViewMutation.mutate(activeViewId);
}
}}
/>
<LauncherRefreshButton
onRefresh={refreshData}
isRefreshing={isRefreshing || isNavigating}
/>
<div className="flex shrink-0 items-center gap-2">
{renderToolbarActions()}
</div>
</div>
</div>
) : (
<div className="mb-6 flex flex-col gap-3">
<div className="flex items-center gap-2 overflow-x-auto">
{renderToolbarActions()}
</div>
{renderToolbarSearch("w-full")}
<div className="overflow-x-auto">
{renderToolbarViews()}
</div>
</div>
)}
<LauncherGroupList
orgId={orgId}
@@ -495,19 +513,6 @@ export default function ResourceLauncher({
groupsPagination={groupsPagination}
resourcesByGroupKey={resourcesByGroupKey}
onClearFilters={handleClearFilters}
onResourceSelect={setSelectedResource}
/>
<LauncherResourcePanel
open={selectedResource != null}
onOpenChange={(open) => {
if (!open) {
setSelectedResource(null);
}
}}
resource={selectedResource}
orgId={orgId}
isAdmin={isAdmin}
/>
<Credenza open={saveDialogOpen} onOpenChange={setSaveDialogOpen}>