import { SDataArray } from 's-array'; import S, { DataSignal } from 's-js'; import { jsonable, Plain, toPlain } from '../../utils/s'; import { Contributor } from '../contributor/contributor-model'; import { assignContributors, createContributors } from '../contributors/contributors-model'; /** * Non-transient state of the settings UI component. */ export interface Settings { readonly name: DataSignal; readonly youTrackBaseUrl: DataSignal; readonly youTrackServiceId: DataSignal; readonly stateFieldId: DataSignal; readonly inactiveStateIds: DataSignal>; readonly remainingEffortFieldId: DataSignal; readonly remainingWaitFieldId: DataSignal; readonly assigneeFieldId: DataSignal; readonly typeFieldId: DataSignal; readonly splittableTypeIds: DataSignal>; readonly dependsLinkTypeId: DataSignal; readonly doesInwardDependOnOutward: DataSignal; readonly savedQuery: DataSignal; readonly overlaySavedQuery: DataSignal; readonly contributors: SDataArray; } /** * Creates a new state for the settings UI component. */ export function createSettings(): Settings { return { name: jsonable(S.value('')), youTrackBaseUrl: jsonable(S.value('')), youTrackServiceId: jsonable(S.value('')), stateFieldId: jsonable(S.value('')), inactiveStateIds: jsonable(S.value(new Set())), remainingEffortFieldId: jsonable(S.value('')), remainingWaitFieldId: jsonable(S.value('')), assigneeFieldId: jsonable(S.value('')), typeFieldId: jsonable(S.value('')), splittableTypeIds: jsonable(S.value(new Set())), dependsLinkTypeId: jsonable(S.value('')), doesInwardDependOnOutward: jsonable(S.value(true)), savedQuery: jsonable(S.value('')), overlaySavedQuery: jsonable(S.value('')), contributors: createContributors(), }; } /** * Updates the state of the settings UI component to the given values in a plain JSON object. * * The update is performed within a single S.js transaction. * * @param settings state of the settings UI component * @param plain plain JSON object */ export function assignSettings(settings: Settings, plain: Plain): void { S.freeze(() => { settings.name(plain.name); settings.youTrackBaseUrl(plain.youTrackBaseUrl); settings.youTrackServiceId(plain.youTrackServiceId); settings.stateFieldId(plain.stateFieldId); settings.inactiveStateIds(new Set(plain.inactiveStateIds)); settings.remainingEffortFieldId(plain.remainingEffortFieldId); settings.remainingWaitFieldId(plain.remainingWaitFieldId); settings.assigneeFieldId(plain.assigneeFieldId); settings.typeFieldId(plain.typeFieldId); settings.splittableTypeIds(new Set(plain.splittableTypeIds)); settings.dependsLinkTypeId(plain.dependsLinkTypeId); settings.doesInwardDependOnOutward(plain.doesInwardDependOnOutward); settings.savedQuery(plain.savedQuery); settings.overlaySavedQuery(plain.overlaySavedQuery); assignContributors(settings.contributors, plain.contributors); }); } /** * Creates a new normalized plain JSON value for the given settings. * * Normalization means that the YouTrack base URL is the result of {@link normalizedBaseUrl}(). */ export function toNormalizedPlainSettings(settings: Settings): Plain { const plainSettings = toPlain(settings); return { ...plainSettings, youTrackBaseUrl: normalizedBaseUrl(plainSettings.youTrackBaseUrl), }; } /** * Returns the normalized YouTrack base URL. * * Normalization means returning a syntactically valid URL that ends with a slash (/). * * @return the normalized URL, or the empty string if the given URL is not valid */ export function normalizedBaseUrl(baseUrl: string): string { try { const url = new URL(baseUrl); if (url.pathname.length === 0 || url.pathname.charAt(url.pathname.length - 1) !== '/') { url.pathname = url.pathname.concat('/'); } return url.toString(); } catch (exception) { if (!(exception instanceof TypeError)) { throw exception; } return ''; } }