2015-09-15 20:34:36 +08:00
|
|
|
/*
|
2016-01-07 12:06:39 +08:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2015-09-15 20:34:36 +08:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
import React from "react";
|
|
|
|
|
|
|
|
export interface IComponents {
|
|
|
|
[key: string]: React.Component;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ISkinObject {
|
|
|
|
components: IComponents;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Skinner {
|
|
|
|
public components: IComponents = null;
|
2015-09-15 20:34:36 +08:00
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
public getComponent(name: string): React.Component {
|
2020-01-28 20:44:14 +08:00
|
|
|
if (!name) throw new Error(`Invalid component name: ${name}`);
|
2015-09-15 20:34:36 +08:00
|
|
|
if (this.components === null) {
|
|
|
|
throw new Error(
|
2021-03-09 10:33:10 +08:00
|
|
|
`Attempted to get a component (${name}) before a skin has been loaded.`+
|
2017-06-01 16:26:35 +08:00
|
|
|
" This is probably because either:"+
|
2015-09-15 20:34:36 +08:00
|
|
|
" a) Your app has not called sdk.loadSkin(), or"+
|
2017-06-01 16:26:35 +08:00
|
|
|
" b) A component has called getComponent at the root level",
|
2015-09-15 20:34:36 +08:00
|
|
|
);
|
|
|
|
}
|
2019-12-13 10:31:52 +08:00
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
const doLookup = (components: IComponents): React.Component => {
|
2019-12-13 10:31:52 +08:00
|
|
|
if (!components) return null;
|
|
|
|
let comp = components[name];
|
|
|
|
// XXX: Temporarily also try 'views.' as we're currently
|
|
|
|
// leaving the 'views.' off views.
|
|
|
|
if (!comp) {
|
|
|
|
comp = components['views.' + name];
|
|
|
|
}
|
|
|
|
return comp;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check the skin first
|
2020-01-28 20:53:37 +08:00
|
|
|
const comp = doLookup(this.components);
|
2019-12-13 10:31:52 +08:00
|
|
|
|
|
|
|
// Just return nothing instead of erroring - the consumer should be smart enough to
|
|
|
|
// handle this at this point.
|
2017-06-01 16:26:35 +08:00
|
|
|
if (!comp) {
|
2019-12-13 10:31:52 +08:00
|
|
|
return null;
|
2017-06-01 16:26:35 +08:00
|
|
|
}
|
|
|
|
|
2020-11-06 00:54:25 +08:00
|
|
|
// components have to be functions or forwardRef objects with a render function.
|
|
|
|
const validType = typeof comp === 'function' || comp.render;
|
2017-06-01 16:26:35 +08:00
|
|
|
if (!validType) {
|
2019-12-21 04:58:37 +08:00
|
|
|
throw new Error(`Not a valid component: ${name} (type = ${typeof(comp)}).`);
|
2015-12-01 01:33:04 +08:00
|
|
|
}
|
2017-06-01 16:26:35 +08:00
|
|
|
return comp;
|
2015-09-15 20:34:36 +08:00
|
|
|
}
|
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
public load(skinObject: ISkinObject): void {
|
2015-09-15 20:34:36 +08:00
|
|
|
if (this.components !== null) {
|
|
|
|
throw new Error(
|
|
|
|
"Attempted to load a skin while a skin is already loaded"+
|
2017-07-01 21:28:12 +08:00
|
|
|
"If you want to change the active skin, call resetSkin first");
|
2015-09-15 20:34:36 +08:00
|
|
|
}
|
2015-12-01 01:33:04 +08:00
|
|
|
this.components = {};
|
2017-07-01 21:28:12 +08:00
|
|
|
const compKeys = Object.keys(skinObject.components);
|
|
|
|
for (let i = 0; i < compKeys.length; ++i) {
|
|
|
|
const comp = skinObject.components[compKeys[i]];
|
2015-12-01 01:33:04 +08:00
|
|
|
this.addComponent(compKeys[i], comp);
|
|
|
|
}
|
2020-01-28 20:44:14 +08:00
|
|
|
|
|
|
|
// Now that we have a skin, load our components too
|
2021-09-27 19:04:00 +08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
2020-01-28 20:44:14 +08:00
|
|
|
const idx = require("./component-index");
|
|
|
|
if (!idx || !idx.components) throw new Error("Invalid react-sdk component index");
|
|
|
|
for (const c in idx.components) {
|
|
|
|
if (!this.components[c]) this.components[c] = idx.components[c];
|
|
|
|
}
|
2015-12-01 01:33:04 +08:00
|
|
|
}
|
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
public addComponent(name: string, comp: any) {
|
2017-07-01 21:28:12 +08:00
|
|
|
let slot = name;
|
2015-12-01 01:33:04 +08:00
|
|
|
if (comp.replaces !== undefined) {
|
|
|
|
if (comp.replaces.indexOf('.') > -1) {
|
|
|
|
slot = comp.replaces;
|
|
|
|
} else {
|
|
|
|
slot = name.substr(0, name.lastIndexOf('.') + 1) + comp.replaces.split('.').pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.components[slot] = comp;
|
2015-09-15 20:34:36 +08:00
|
|
|
}
|
|
|
|
|
2021-09-27 19:04:00 +08:00
|
|
|
public reset(): void {
|
2015-09-15 20:34:36 +08:00
|
|
|
this.components = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We define one Skinner globally, because the intention is
|
|
|
|
// very much that it is a singleton. Relying on there only being one
|
|
|
|
// copy of the module can be dicey and not work as browserify's
|
|
|
|
// behaviour with multiple copies of files etc. is erratic at best.
|
|
|
|
// XXX: We can still end up with the same file twice in the resulting
|
|
|
|
// JS bundle which is nonideal.
|
2017-09-09 00:43:41 +08:00
|
|
|
// See https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
|
|
|
|
// or https://nodejs.org/api/modules.html#modules_module_caching_caveats
|
|
|
|
// ("Modules are cached based on their resolved filename")
|
2021-09-27 19:04:00 +08:00
|
|
|
if (window.mxSkinner === undefined) {
|
|
|
|
window.mxSkinner = new Skinner();
|
2015-09-15 20:34:36 +08:00
|
|
|
}
|
2021-09-27 19:04:00 +08:00
|
|
|
export default window.mxSkinner;
|
2015-09-15 20:34:36 +08:00
|
|
|
|