Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/settings/v3/AppSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { LabsFeatures, SettingsCategory } from "./maps/Categories";
import { DeepFlatten, ValuesOf } from "../../utils/ts";
import { AllSettingsMap } from "./maps/AllSettings";

// This looks useless in terms of code, and arguably it is, however it does an important job
// to enforce typechecking on the incoming map. All this definition does is allows us to use
// friendlier names for our settings by putting them into categories for dot exploration. For
// example, a hypothetical RoomListLayout setting might want to be recorded as RoomList.Layout,
// which is hard to do or ugly to represent in code (property names can't have dots, which means
// making them strings, which means our access looks like S["RoomList.Layout"] instead of a
// cleaner S.RoomList.Layout). By defining a SettingsCategory we are able to help make this
// mapping possible, and need to typecheck it for reasons explained in Categories.ts
//
// TLDR: We return the same thing because we're just typechecking our fancy map of setting values.
type MappedSettings<T extends SettingsCategory> = { [P in keyof T]: T[P] };
function remap<T extends SettingsCategory>(cat: T): MappedSettings<T> {
return cat;
}

// We define our mapped settings ahead of the global definition so it is easier to exclude settings
// which are mapped to categories. We mostly want to do this to help developers use the right setting
// even if others are technically possible: for example, if we have S.RoomList.Layout then we don't
// want someone to accidentally use S["RoomList.Layout"] as their IDE might suggest. By defining the
// mapped types (S.RoomList.*) ahead of the definition, we can omit the mapped values from the top
// level definition. Inspecting the type of S or the Omit<> below should give a better idea of what
// is going on.
const mappedSettings = {
//RoomList: remap(RoomListSettings),
Features: remap(LabsFeatures),
};

// Finally, this is our accessor for setting IDs. Yes, code can use the setting IDs as strings,
// but that can conflict with some "find usages of..." tooling available in IDEs and GitHub.
// This is pretty much just a cheap way to continue using that tooling while also being descriptive
// in code.
//
// As for why we call this just "S": `S.Breadcrumbs` is the same number of characters that are needed
// for `"Breadcrumbs"` - we are actively trying to avoid making lines of code larger by optimizing for
// IDE tooling. The S denotes "Setting ID".
export const S = {
...AllSettingsMap as Omit<typeof AllSettingsMap, ValuesOf<DeepFlatten<typeof mappedSettings>>>,
...mappedSettings,
};

// function getValue<K extends SettingID>(id: K): SettingType<K> {
// if (id === S.RoomList.Breadcrumbs) {
// return ['test'];
// } else if (id === S.ShowReadReceipts) {
// return false;
// } else if (id === S["Video.TestDevice"]) {
// return "ok";
// }
// return null;
// }
30 changes: 30 additions & 0 deletions src/settings/v3/maps/AllSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// This is just to create a type which maps setting ID to setting ID. This might seem pointless,
// but it's an intermediary type to create an interface that Settings.get() can later use.
import { RawAppSettings, SettingID } from "./types";

export type SettingMap = {
[P in SettingID]: P;
};

// This is our "All Settings Map" where we actually build the map defined by the SettingMap
// type we created above.
export const AllSettingsMap = Object.keys(RawAppSettings).reduce((p, c) => {
p[c] = c;
return p;
}, {}) as SettingMap;
43 changes: 43 additions & 0 deletions src/settings/v3/maps/Categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {SettingID} from "./types";
import { AllSettingsMap } from "./AllSettings";

export type SettingsCategory = Record<string, SettingID>;

// Note: None of the categories listed here are typed to be a SettingsCategory. This
// is because TypeScript wipes out our types, which makes the Settings.get() function
// return a union of all types instead of just the specified setting's type, making
// manual casting a requirement. Instead, we use TypeScript's implied interface support
// and hope that the remap() function in AppSettings.ts will error if someone creates
// an invalid setting map.

export const LabsFeatures = {
Maths: AllSettingsMap.feature_latex_maths,
CommunitiesV2: AllSettingsMap.feature_communities_v2_prototypes,
NewSpinner: AllSettingsMap.feature_new_spinner,
MessagePinning: AllSettingsMap.feature_pinning,
CustomStatus: AllSettingsMap.feature_custom_status,
CustomTags: AllSettingsMap.feature_custom_tags,
StateCounters: AllSettingsMap.feature_state_counters,
ManyIntegManagers: AllSettingsMap.feature_many_integration_managers,
Mjolnir: AllSettingsMap.feature_mjolnir,
CustomThemes: AllSettingsMap.feature_custom_themes,
PreviewReactionsDMs: AllSettingsMap.feature_roomlist_preview_reactions_dms,
PreviewReactionsAll: AllSettingsMap.feature_roomlist_preview_reactions_all,
Dehydration: AllSettingsMap.feature_dehydration,
};
49 changes: 49 additions & 0 deletions src/settings/v3/maps/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {CustomSettings} from "../registry/CustomSettings";

// First let's define a variable which will help with the rest of this making sense. We just want
// an object which has all of the settings, custom and built-in, so we just map directly to the
// custom settings because they extend the built-in ones.
export const RawAppSettings = CustomSettings;

// Next we want a type to represent all the setting names. This will be a type which is a union
// of all property names on the AppSettings object. We take off 'prototype' because we don't want
// that to be considered a setting.
export type SettingID = keyof Omit<typeof RawAppSettings, keyof typeof Object>;

// This just defines a type which references the `ISetting<T>` for each setting ID. Essentially
// we're making a dynamic interface of AppSettings here, mapping the property names to ISetting<T>
// types.
export type SettingDefinition<K extends SettingID> = (typeof RawAppSettings)[K];

// Finally we can pull out the setting types by using the same dynamic interface trick from above:
// we map setting IDs (property names) to the type of the definition's `default` property. The value
// of the default can be whatever - we're just stripping the type off of it from the ISetting<T>
// contracts.
export type SettingType<K extends SettingID> = SettingDefinition<K>['default'];

/**
* The setting IDs which are "legacy". Only useful in the context of the application - if there are
* values which are not read here, they should be removed. Similarly, settings should only be added
* here if there is a need to reference them outside of SettingID.
*
* Typically used during migrations.
*/
export enum LegacySettingID {

}
30 changes: 30 additions & 0 deletions src/settings/v3/migrator/Migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Represents a setting migration.
*/
export interface Migration {
/**
* The ID for the migration. Arbitrary string.
*/
id: string;

/**
* Runs the migration. Resolves when complete.
*/
run(): Promise<void>;
}
59 changes: 59 additions & 0 deletions src/settings/v3/migrator/Migrator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Migration } from "./Migration";
import { iterableUnion } from "../../../utils/iterables";
import { NameMigration } from "./NameMigration";

const LS_RUN_HISTORY = "mx_settings_migration_history";

export class Migrator {
private static migrations: Migration[] = [
new NameMigration(<any>"test", <any>"test2"),
];

private constructor() {
// static only
}

public static async run(): Promise<void> {
console.log(`[Migrator] Starting migrations...`);
const lsFlagged = localStorage.getItem(LS_RUN_HISTORY);
const doneMigrations = new Set<string>();
if (lsFlagged) {
(<string[]>JSON.parse(lsFlagged)).forEach(r => doneMigrations.add(r));
}

for (const migration of this.migrations) {
if (doneMigrations.has(migration.id)) {
console.log(`[Migrator] Skipping migration: ${migration.id}`);
continue;
}
console.log(`[Migrator] Running migration: ${migration.id}`);
await migration.run();
doneMigrations.add(migration.id);
}

const possibleIds = new Set(this.migrations.map(m => m.id));
const union = Array.from(iterableUnion(possibleIds, doneMigrations));
localStorage.setItem(LS_RUN_HISTORY, JSON.stringify(union));

console.log(`[Migrator] Done. ` +
`${possibleIds.size} complete, ` +
`${doneMigrations.size} recorded as done, ` +
`${union.length} persisted as complete`);
}
}
32 changes: 32 additions & 0 deletions src/settings/v3/migrator/NameMigration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Migration } from "./Migration";
import { LegacySettingID, SettingID } from "../maps/types";

export class NameMigration implements Migration {
public constructor(private oldName: LegacySettingID, private newName: SettingID) {
}

public get id(): string {
return `name: ${this.oldName}->${this.newName}`;
}

public run(): Promise<void> {
// TODO
return;
}
}
Loading