Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 107918x 231x 7904x 25x 25x 25x 270x 5451x 5451x 5451x 5034x 2758x 1718x 997x 43x 417x 62x 5721x 4487x 1233x 1x 1x 1x | import type { FlowsheetRead } from "@/api/apiStore.gen";
export type FlowsheetAccess = {
is_owner: boolean;
read_only: boolean;
can_edit: boolean;
can_share: boolean;
can_copy: boolean;
can_export: boolean;
can_manage_template_settings: boolean;
};
export type FlowsheetWithAccess = FlowsheetRead & {
access?: FlowsheetAccess;
};
export type SharedUserAccess = {
email: string;
read_only: boolean;
};
export function getFlowsheetAccess(
project?: FlowsheetRead,
): FlowsheetAccess | undefined {
return (project as FlowsheetWithAccess | undefined)?.access;
}
export function normalizeSharedUsers(users: unknown): SharedUserAccess[] {
if (!Array.isArray(users)) {
return [];
}
return users.flatMap((user) => {
if (typeof user === "string") {
// Backward compatibility for older API responses from before share-level
// permissions existed: the frontend used to receive `string[]` emails only.
// Treat those temporary legacy entries as editable so the UI preserves the
// historical shared-user behaviour until the generated schema/client is
// regenerated everywhere to the structured `{ email, read_only }` shape.
return [{ email: user, read_only: false }];
}
if (!user || typeof user !== "object") {
return [];
}
const email = "email" in user ? user.email : undefined;
const readOnly = "read_only" in user ? user.read_only : undefined;
if (typeof email !== "string") {
return [];
}
return [{ email, read_only: Boolean(readOnly) }];
});
}
export function getCachedFlowsheetAccess(
state: Record<string, any> | undefined,
reducerPath: string,
flowsheetId: number | null,
): FlowsheetAccess | undefined {
if (!state || !reducerPath || !flowsheetId) {
return undefined;
}
const queries = state[reducerPath]?.queries;
if (!queries || typeof queries !== "object") {
return undefined;
}
const directQueryKey = `coreFlowsheetsRetrieve({"id":"${flowsheetId}"})`;
const directData = queries[directQueryKey]?.data as
| FlowsheetWithAccess
| undefined;
if (directData?.access) {
return directData.access;
}
// Some screens only have the flowsheet cached via list-style queries, so fall
// back to scanning the RTK Query cache instead of requiring a dedicated detail
// fetch before the UI can decide whether to disable mutation controls.
for (const value of Object.values(queries)) {
const data = (value as { data?: unknown } | undefined)?.data;
if (!data || typeof data !== "object") {
continue;
}
if ((data as { id?: unknown }).id !== flowsheetId) {
continue;
}
const access = (data as FlowsheetWithAccess).access;
if (access) {
return access;
}
}
return undefined;
}
const READ_ONLY_ALLOWED_MUTATION_PATH_SEGMENTS = [
"/api/flowsheet/copy",
"/download",
"/download_",
"/export",
];
export function isReadOnlyMutationBlocked({
method,
pathname,
access,
}: {
method: string;
pathname: string;
access?: FlowsheetAccess;
}) {
const normalizedMethod = method.toUpperCase();
if (
normalizedMethod === "GET" ||
normalizedMethod === "HEAD" ||
normalizedMethod === "OPTIONS"
) {
return false;
}
if (!access?.read_only) {
return false;
}
const normalizedPath = pathname.toLowerCase();
// Copy / download / export are intentionally still allowed for read-only
// shares, because they do not mutate the shared source flowsheet itself.
return !READ_ONLY_ALLOWED_MUTATION_PATH_SEGMENTS.some((segment) =>
normalizedPath.includes(segment),
);
}
|