Skip to content

Commit

Permalink
feat(project): add config loader and provider
Browse files Browse the repository at this point in the history
  • Loading branch information
royschut committed Apr 29, 2021
1 parent 411d232 commit 5a66fcd
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 9 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"react-dom": "^17.0.2",
"react-query": "^3.13.10",
"react-router-dom": "^5.2.0",
"tile-dock": "https://github.com/RCVZ/tile-dock-temp"
"tile-dock": "https://github.com/RCVZ/tile-dock-temp",
"yup": "^0.32.9"
},
"devDependencies": {
"@commitlint/cli": "^12.1.1",
Expand Down
53 changes: 53 additions & 0 deletions public/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "2",
"id": "Kfo1Se0r",
"player": "",
"theme": "light",
"siteName": "JW Showcase",
"description": "JW Showcase is an open-source, dynamically generated video website built around JW Player and JW Platform services. It enables you to easily publish your JW Player-hosted video content with no coding and minimal configuration.",
"footerText": "Powered by JW Player",
"searchPlaylist": "r3MhKJyA",
"recommendationsPlaylist": "wZuMVmMk",
"options": {
"showcaseContentOnly": "true",
"backgroundColor": "",
"highlightColor": "",
"enableContinueWatching": true,
"enableAddToHome": false,
"useRecommendationPlaylist": false,
"enableInVideoSearch": false,
"enableHeader": true,
"enableTags": false,
"rightRail": {
"enabled": true
},
"cookieNotice": {
"enabled": false,
"message": null
}
},
"content": [
{
"playlistId": "WXu7kuaW",
"featured": true,
"enableText": false
},
{
"playlistId": "lrYLc95e",
"aspectratio": "23",
"cols": {"xs": 2, "sm": 3, "md": 4, "lg": 5, "xl": 6}
},
{
"playlistId": "Q352cyuc",
"type": "grid",
"enableSeeAll": true,
"rows": 1
},
{
"playlistId": "oR7ahO0J"
}
],
"assets": {
"banner": "images/logo.png"
}
}
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/dist/index.js"></script>
<script>window.configLocation = './config.json';</script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
Expand Down
19 changes: 14 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import React from 'react';

import Slider from './containers/Slider'
import Slider from './containers/Slider';
import ConfigProvider from './providers/configProvider';

import './styles/main.scss';

function App () {
function App() {
return (
<div className="App">
<Slider />
</div>
<ConfigProvider
configLocation={window.configLocation}
onLoading={(isLoading: boolean) =>
console.info(`Loading config: ${isLoading}`)
}
onValidationError={(error: Error) => console.error(`Config ${error}`)}
>
<div className="App">
<Slider />
</div>
</ConfigProvider>
);
}

Expand Down
63 changes: 63 additions & 0 deletions src/providers/configProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, {
createContext,
FunctionComponent,
ReactNode,
useEffect,
useState,
} from 'react';

import loadConfig, { validateConfig } from '../services/config.service';
import type { Config } from '../../types/Config';

const defaultConfig: Config = {
id: '',
siteName: '',
description: '',
footerText: '',
player: '',
assets: {},
content: [],
menu: [],
options: {},
};

export const ConfigContext = createContext<Config>(defaultConfig);

export type ProviderProps = {
children: ReactNode;
configLocation: string;
onLoading: (isLoading: boolean) => void;
onValidationError: (error: Error) => void;
};

const ConfigProvider: FunctionComponent<ProviderProps> = ({
children,
configLocation,
onLoading,
onValidationError,
}) => {
const [config, setConfig] = useState<Config>(defaultConfig);

useEffect(() => {
const loadAndValidateConfig = async (configLocation: string) => {
onLoading(true);
const config = await loadConfig(configLocation);
validateConfig(config)
.then((configValidated) => {
setConfig(configValidated);
onLoading(false);
})
.catch((error: Error) => {
onValidationError(error);
onLoading(false);
});
};
loadAndValidateConfig(configLocation);
}, [configLocation, onLoading, onValidationError]);

return (
<ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>
);
};

export default ConfigProvider;
80 changes: 80 additions & 0 deletions src/services/config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Config, Content, Cols, Options, Menu } from 'types/Config';
import { string, number, boolean, array, object, SchemaOf } from 'yup';

const colsSchema: SchemaOf<Cols> = object({
xs: number().integer().positive().notRequired(),
sm: number().integer().positive().notRequired(),
md: number().integer().positive().notRequired(),
lg: number().integer().positive().notRequired(),
xl: number().integer().positive().notRequired(),
});

const contentSchema: SchemaOf<Content> = object({
playlistId: string().defined(),
featured: boolean().notRequired(),
enableText: boolean().notRequired(),
aspectRatio: number().integer().positive().notRequired(),
type: string().notRequired(),
enableSeeAll: boolean().notRequired(),
rows: number().integer().positive().notRequired(),
cols: colsSchema.notRequired(),
}).defined();

const menuSchema: SchemaOf<Menu> = object().shape({
label: string().defined(),
playlistId: string().defined(),
});

const optionsSchema: SchemaOf<Options> = object({
backgroundColor: string().notRequired(),
highlightColor: string().notRequired(),
enableContinueWatching: boolean().notRequired(),
headerBackground: string().notRequired(),
enableCasting: boolean().notRequired(),
enableSharing: boolean().notRequired(),
dynamicBlur: boolean().notRequired(),
posterFading: boolean().notRequired(),
shelveTitles: boolean().notRequired(),
});

const configSchema: SchemaOf<Config> = object({
id: string().defined(),
siteName: string().defined(),
description: string().defined(),
footerText: string().defined(),
player: string().defined(),
recommendationsPlaylist: string().notRequired(),
searchPlaylist: string().notRequired(),
analyticsToken: string().notRequired(),
assets: object({
banner: string().notRequired(),
}).defined(),
content: array().of(contentSchema),
menu: array().of(menuSchema),
options: optionsSchema.notRequired(),
json: object().notRequired(),
}).defined();

const loadConfig = async (configLocation: string) => {
if (!configLocation) {
return null;
}
try {
const response = await fetch(configLocation, {
headers: {
Accept: 'application/json',
},
method: 'GET',
});

return await response.json();
} catch (error: unknown) {
return error;
}
};

export const validateConfig = (config: unknown): Promise<Config> => {
return configSchema.validate(config, { strict: true }) as Promise<Config>;
};

export default loadConfig;
55 changes: 55 additions & 0 deletions types/Config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export type Config = {
id: string;
siteName: string;
description: string;
footerText: string;
player: string;
recommendationsPlaylist?: string;
searchPlaylist?: string;
analyticsToken?: string;
assets: { banner?: string };
content: Content[];
menu: Menu[];
options: Options;
json?: Record<string, unknown>;
};

export type Simple = {
id: string;
};

export type Content = {
playlistId: string;
featured?: boolean;
enableText?: boolean;
aspectRatio?: number;
type?: string;
enableSeeAll?: boolean;
rows?: number;
cols?: Cols;
};

export type Menu = {
label: string;
playlistId: string;
};

export type Options = {
backgroundColor?: string;
highlightColor?: string;
enableContinueWatching?: boolean;
headerBackground?: string;
enableCasting?: boolean;
enableSharing?: boolean;
dynamicBlur?: boolean;
posterFading?: boolean;
shelveTitles?: boolean;
};

export type Cols = {
xs?: number;
sm?: number;
md?: number;
lg?: number;
xl?: number;
};
3 changes: 3 additions & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface Window {
configLocation: configLocation;
}
Loading

0 comments on commit 5a66fcd

Please sign in to comment.