/* * Copyright (C) 2022 Kristóf Marussy * * This file is part of Sophie. * * Sophie is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * SPDX-License-Identifier: AGPL-3.0-only */ import { ProfileSettingsSnapshotIn } from '@sophie/shared'; import { applySnapshot, IMSTArray, IMSTMap, IReferenceType, IStateTreeNode, IType, } from 'mobx-state-tree'; import { nanoid } from 'nanoid'; import slug from 'slug'; import GlobalSettings from '../GlobalSettings'; import type Profile from '../Profile'; import type Service from '../Service'; import type { ServiceSettingsSnapshotIn } from '../ServiceSettings'; import type Config from './Config'; import type ProfileConfig from './ProfileConfig'; import type ServiceConfig from './ServiceConfig'; function generateId(name?: string | undefined): string { const nameSlug = typeof name === 'undefined' ? '' : slug(name); return `${nameSlug}_${nanoid()}`; } function addMissingProfileIds( profileConfigs: ProfileConfig[] | undefined, ): [string, ProfileSettingsSnapshotIn][] { return (profileConfigs ?? []).map((profileConfig) => { const { id, ...settings } = profileConfig; return [id === undefined ? generateId(settings.name) : id, settings]; }); } function addMissingServiceIdsAndProfiles( serviceConfigs: ServiceConfig[] | undefined, profiles: [string, ProfileSettingsSnapshotIn][], ): [string, ServiceSettingsSnapshotIn][] { return (serviceConfigs ?? []).map((serviceConfig) => { const { id, ...settings } = serviceConfig; const { name } = settings; let { profile: profileId } = settings; if (profileId === undefined) { profileId = generateId(name); profiles.push([profileId, { name }]); } return [ id === undefined ? generateId(name) : id, { ...settings, profile: profileId }, ]; }); } type TypeWithSettings = IType< { id: string; settings: C }, unknown, { settings: IStateTreeNode> } >; function applySettings>( current: IMSTArray>, currentById: IMSTMap, toApply: [string, C][], ): void { const toApplyById = new Map(toApply); const toDelete = new Set(currentById.keys()); toApplyById.forEach((settingsSnapshot, id) => { const model = currentById.get(id); if (model === undefined) { currentById.set(id, { id, settings: settingsSnapshot, }); } else { toDelete.delete(id); applySnapshot(model.settings, settingsSnapshot); } }); toDelete.forEach((id) => currentById.delete(id)); current.clear(); current.push(...toApply.map(([id]) => id)); } export default function loadConfig( target: { readonly profiles: IMSTArray>; readonly profilesById: IMSTMap; readonly services: IMSTArray>; readonly servicesById: IMSTMap; readonly settings: GlobalSettings; }, config: Config, ): void { const { profiles, profilesById, services, servicesById, settings } = target; const { profiles: profilesConfig, services: servicesConfig, ...settingsToApply } = config; const profilesToApply = addMissingProfileIds(profilesConfig); const servicesToApply = addMissingServiceIdsAndProfiles( servicesConfig, profilesToApply, ); applySettings(profiles, profilesById, profilesToApply); applySettings(services, servicesById, servicesToApply); const { selectedService } = settingsToApply; // Be more robust against when a deleted service is selected. if ( typeof selectedService !== 'string' || !servicesById.has(selectedService) ) { settingsToApply.selectedService = undefined; } applySnapshot(settings, settingsToApply); if (settings.selectedService === undefined && services.length > 0) { [settings.selectedService] = services; } }