| 1 | import { |
| 2 | isNullableProjectSettingsOverride, |
| 3 | PROJECT_SCOPED_SERVER_SETTING_KEYS, |
| 4 | type EnvironmentId, |
| 5 | type ProjectId, |
| 6 | type ProjectScopedServerSettingKey, |
| 7 | type ServerSettings, |
| 8 | type ServerSettingsPatch, |
| 9 | } from "@t3tools/contracts"; |
| 10 | import { |
| 11 | clearProjectSettingsOverrides, |
| 12 | resolveProjectSettings, |
| 13 | } from "@t3tools/shared/projectSettings"; |
| 14 | |
| 15 | import type { SettingsTarget } from "./settings-environment-filter"; |
| 16 | |
| 17 | export interface ScopedMobileSettingsTarget { |
| 18 | readonly environment: SettingsTarget; |
| 19 | readonly projectId: ProjectId | null; |
| 20 | readonly settings: ServerSettings; |
| 21 | readonly sources: ReturnType<typeof resolveProjectSettings>["sources"]; |
| 22 | } |
| 23 | |
| 24 | export function resolveMobileSettingsTargets( |
| 25 | environments: readonly SettingsTarget[], |
| 26 | members: readonly { readonly environmentId: EnvironmentId; readonly id: ProjectId }[] | null, |
| 27 | ): readonly ScopedMobileSettingsTarget[] { |
| 28 | if (members === null) { |
| 29 | return environments.map((environment) => ({ |
| 30 | environment, |
| 31 | projectId: null, |
| 32 | ...resolveProjectSettings(environment.serverConfig.settings, null), |
| 33 | })); |
| 34 | } |
| 35 | const byId = new Map(environments.map((environment) => [environment.environmentId, environment])); |
| 36 | return members.flatMap((member) => { |
| 37 | const environment = byId.get(member.environmentId); |
| 38 | if (!environment) return []; |
| 39 | return [ |
| 40 | { |
| 41 | environment, |
| 42 | projectId: member.id, |
| 43 | ...resolveProjectSettings(environment.serverConfig.settings, member.id), |
| 44 | }, |
| 45 | ]; |
| 46 | }); |
| 47 | } |
| 48 | |
| 49 | export function planMobileScopedSettingsPatch( |
| 50 | targets: readonly ScopedMobileSettingsTarget[], |
| 51 | projectSelected: boolean, |
| 52 | patch: ServerSettingsPatch, |
| 53 | ) { |
| 54 | if (!projectSelected) { |
| 55 | return targets.map((target) => ({ environmentId: target.environment.environmentId, patch })); |
| 56 | } |
| 57 | const keys = Object.keys(patch); |
| 58 | if ( |
| 59 | keys.some( |
| 60 | (key) => !PROJECT_SCOPED_SERVER_SETTING_KEYS.includes(key as ProjectScopedServerSettingKey), |
| 61 | ) |
| 62 | ) |
| 63 | return []; |
| 64 | const writes = new Map<EnvironmentId, Record<string, unknown>>(); |
| 65 | for (const target of targets) { |
| 66 | if ( |
| 67 | target.projectId === null || |
| 68 | target.environment.serverConfig.environment.capabilities.projectSettingsOverrides !== true |
| 69 | ) |
| 70 | continue; |
| 71 | const current = |
| 72 | target.environment.serverConfig.settings.projectSettingsOverrides[target.projectId] ?? {}; |
| 73 | const next: Record<string, unknown> = { ...current }; |
| 74 | for (const [key, value] of Object.entries(patch)) { |
| 75 | // A picker's "Inherit" sends null; for keys whose override cannot |
| 76 | // store null that means remove the override. |
| 77 | if ( |
| 78 | value === null && |
| 79 | !isNullableProjectSettingsOverride(key as ProjectScopedServerSettingKey) |
| 80 | ) { |
| 81 | delete next[key]; |
| 82 | } else { |
| 83 | next[key] = value; |
| 84 | } |
| 85 | } |
| 86 | const overrides = writes.get(target.environment.environmentId) ?? {}; |
| 87 | overrides[target.projectId] = next; |
| 88 | writes.set(target.environment.environmentId, overrides); |
| 89 | } |
| 90 | return [...writes].map(([environmentId, projectSettingsOverrides]) => ({ |
| 91 | environmentId, |
| 92 | patch: { projectSettingsOverrides } as ServerSettingsPatch, |
| 93 | })); |
| 94 | } |
| 95 | |
| 96 | /** A mixed selection has no single value to display. */ |
| 97 | export function uniformMobileSetting<K extends keyof ServerSettings>( |
| 98 | targets: readonly Pick<ScopedMobileSettingsTarget, "settings">[], |
| 99 | key: K, |
| 100 | ): ServerSettings[K] | null { |
| 101 | const reference = targets[0]; |
| 102 | if (!reference) return null; |
| 103 | const value = reference.settings[key]; |
| 104 | return targets.every((target) => target.settings[key] === value) ? value : null; |
| 105 | } |
| 106 | |
| 107 | export function planMobileScopedSettingsClear( |
| 108 | targets: readonly ScopedMobileSettingsTarget[], |
| 109 | keys: readonly ProjectScopedServerSettingKey[], |
| 110 | ) { |
| 111 | const writes = new Map<EnvironmentId, Record<string, unknown>>(); |
| 112 | for (const target of targets) { |
| 113 | if ( |
| 114 | target.projectId === null || |
| 115 | target.environment.serverConfig.environment.capabilities.projectSettingsOverrides !== true |
| 116 | ) |
| 117 | continue; |
| 118 | const overrides = writes.get(target.environment.environmentId) ?? {}; |
| 119 | overrides[target.projectId] = clearProjectSettingsOverrides( |
| 120 | target.environment.serverConfig.settings, |
| 121 | target.projectId, |
| 122 | keys, |
| 123 | ); |
| 124 | writes.set(target.environment.environmentId, overrides); |
| 125 | } |
| 126 | return [...writes].map(([environmentId, projectSettingsOverrides]) => ({ |
| 127 | environmentId, |
| 128 | patch: { projectSettingsOverrides } as ServerSettingsPatch, |
| 129 | })); |
| 130 | } |