Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 32873d3

Browse files
committed
Early implementation of module API surface + functions for ILAG module
1 parent fbb8581 commit 32873d3

File tree

7 files changed

+196
-28
lines changed

7 files changed

+196
-28
lines changed

src/components/views/rooms/RoomPreviewBar.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ import AccessibleButton from "../elements/AccessibleButton";
3434
import RoomAvatar from "../avatars/RoomAvatar";
3535
import SettingsStore from "../../../settings/SettingsStore";
3636
import { UIFeature } from "../../../settings/UIFeature";
37+
import { ModuleRunner } from "../../../modules/ModuleRunner";
38+
import {
39+
RoomPreviewOpts,
40+
RoomViewLifecycle,
41+
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
3742

3843
const MemberEventHtmlReasonField = "io.element.html_reason";
3944

@@ -313,13 +318,23 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
313318
break;
314319
}
315320
case MessageCase.NotLoggedIn: {
316-
title = _t("Join the conversation with an account");
317-
if (SettingsStore.getValue(UIFeature.Registration)) {
318-
primaryActionLabel = _t("Sign Up");
319-
primaryActionHandler = this.onRegisterClick;
321+
const opts: RoomPreviewOpts = { canJoin: false };
322+
ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, this.props.room.roomId);
323+
if (opts.canJoin) {
324+
title = _t("Join the room to participate");
325+
primaryActionLabel = _t("Join");
326+
primaryActionHandler = () => {
327+
ModuleRunner.instance.invoke(RoomViewLifecycle.JoinFromRoomPreview, this.props.room.roomId);
328+
};
329+
} else {
330+
title = _t("Join the conversation with an account");
331+
if (SettingsStore.getValue(UIFeature.Registration)) {
332+
primaryActionLabel = _t("Sign Up");
333+
primaryActionHandler = this.onRegisterClick;
334+
}
335+
secondaryActionLabel = _t("Sign In");
336+
secondaryActionHandler = this.onLoginClick;
320337
}
321-
secondaryActionLabel = _t("Sign In");
322-
secondaryActionHandler = this.onLoginClick;
323338
if (this.props.previewLoading) {
324339
footer = (
325340
<div>

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,8 @@
18241824
"Joining …": "Joining …",
18251825
"Loading …": "Loading …",
18261826
"Rejecting invite …": "Rejecting invite …",
1827+
"Join the room to participate": "Join the room to participate",
1828+
"Join": "Join",
18271829
"Join the conversation with an account": "Join the conversation with an account",
18281830
"Sign Up": "Sign Up",
18291831
"Loading preview": "Loading preview",

src/languageHandler.tsx

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import SdkConfig from "./SdkConfig";
3131

3232
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
3333
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
34+
import { ModuleRunner } from "./modules/ModuleRunner";
3435

3536
const i18nFolder = 'i18n/';
3637

@@ -606,15 +607,40 @@ export class CustomTranslationOptions {
606607
}
607608
}
608609

610+
function doRegisterTranslations(customTranslations: ICustomTranslations) {
611+
// We convert the operator-friendly version into something counterpart can
612+
// consume.
613+
const langs: {
614+
// same structure, just flipped key order
615+
[lang: string]: {
616+
[str: string]: string;
617+
};
618+
} = {};
619+
for (const [str, translations] of Object.entries(customTranslations)) {
620+
for (const [lang, newStr] of Object.entries(translations)) {
621+
if (!langs[lang]) langs[lang] = {};
622+
langs[lang][str] = newStr;
623+
}
624+
}
625+
626+
// Finally, tell counterpart about our translations
627+
for (const [lang, translations] of Object.entries(langs)) {
628+
counterpart.registerTranslations(lang, translations);
629+
}
630+
}
631+
609632
/**
610-
* If a custom translations file is configured, it will be parsed and registered.
611-
* If no customization is made, or the file can't be parsed, no action will be
612-
* taken.
633+
* Any custom modules with translations to load are parsed first, followed by an
634+
* optionally defined translations file in the config. If no customization is made,
635+
* or the file can't be parsed, no action will be taken.
613636
*
614637
* This function should be called *after* registering other translations data to
615638
* ensure it overrides strings properly.
616639
*/
617640
export async function registerCustomTranslations() {
641+
const moduleTranslations = ModuleRunner.instance.allTranslations;
642+
doRegisterTranslations(moduleTranslations);
643+
618644
const lookupUrl = SdkConfig.get().custom_translations_url;
619645
if (!lookupUrl) return; // easy - nothing to do
620646

@@ -636,25 +662,8 @@ export async function registerCustomTranslations() {
636662
// If the (potentially cached) json is invalid, don't use it.
637663
if (!json) return;
638664

639-
// We convert the operator-friendly version into something counterpart can
640-
// consume.
641-
const langs: {
642-
// same structure, just flipped key order
643-
[lang: string]: {
644-
[str: string]: string;
645-
};
646-
} = {};
647-
for (const [str, translations] of Object.entries(json)) {
648-
for (const [lang, newStr] of Object.entries(translations)) {
649-
if (!langs[lang]) langs[lang] = {};
650-
langs[lang][str] = newStr;
651-
}
652-
}
653-
654-
// Finally, tell counterpart about our translations
655-
for (const [lang, translations] of Object.entries(langs)) {
656-
counterpart.registerTranslations(lang, translations);
657-
}
665+
// Finally, register it.
666+
doRegisterTranslations(json);
658667
} catch (e) {
659668
// We consume all exceptions because it's considered non-fatal for custom
660669
// translations to break. Most failures will be during initial development

src/modules/AppModule.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ModuleFactory } from "./ModuleFactory";
18+
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
19+
import { ProxiedModuleApi } from "./ProxiedModuleApi";
20+
21+
export class AppModule {
22+
public readonly module: RuntimeModule;
23+
public readonly api = new ProxiedModuleApi();
24+
25+
public constructor(factory: ModuleFactory) {
26+
this.module = factory(this.api);
27+
}
28+
}

src/modules/ModuleFactory.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
18+
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
19+
20+
export type ModuleFactory = (api: ModuleApi) => RuntimeModule;

src/modules/ModuleRunner.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
18+
import { AppModule } from "./AppModule";
19+
import { ModuleFactory } from "./ModuleFactory";
20+
import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
21+
22+
export class ModuleRunner {
23+
public static readonly instance = new ModuleRunner();
24+
25+
private modules: AppModule[] = [];
26+
27+
private constructor() {
28+
// we only want one instance
29+
}
30+
31+
public get allTranslations(): TranslationStringsObject {
32+
const merged: TranslationStringsObject = {};
33+
34+
for (const module of this.modules) {
35+
const i18n = module.api.translations;
36+
if (!i18n) continue;
37+
38+
for (const [lang, strings] of Object.entries(i18n)) {
39+
if (!merged[lang]) merged[lang] = {};
40+
for (const [str, val] of Object.entries(strings)) {
41+
merged[lang][str] = val;
42+
}
43+
}
44+
}
45+
46+
return merged;
47+
}
48+
49+
public registerModule(factory: ModuleFactory) {
50+
this.modules.push(new AppModule(factory));
51+
}
52+
53+
public invoke(lifecycleEvent: AnyLifecycle, ...args: any[]): void {
54+
for (const module of this.modules) {
55+
module.module.emit(lifecycleEvent, ...args);
56+
}
57+
}
58+
}

src/modules/ProxiedModuleApi.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
18+
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
19+
import { Optional } from "matrix-events-sdk";
20+
import { _t } from "../languageHandler";
21+
22+
export class ProxiedModuleApi implements ModuleApi {
23+
private cachedTranslations: Optional<TranslationStringsObject>;
24+
25+
public get translations(): Optional<TranslationStringsObject> {
26+
return this.cachedTranslations;
27+
}
28+
29+
public registerTranslations(translations: TranslationStringsObject): void {
30+
this.cachedTranslations = translations;
31+
}
32+
33+
public translateString(s: string, variables?: Record<string, unknown>): string {
34+
return _t(s, variables);
35+
}
36+
}

0 commit comments

Comments
 (0)