New site setup
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"varsIgnorePattern": "^map"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
|
||||||
|
import {BingMaps, Vector as VectorSource} from '../src/ol/source.js';
|
||||||
|
import {Style, Stroke} from '../src/ol/style.js';
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new BingMaps({
|
||||||
|
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5',
|
||||||
|
imagerySet: 'Aerial'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
format: new GeoJSON(),
|
||||||
|
url: 'https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json'
|
||||||
|
}),
|
||||||
|
opacity: 0.5,
|
||||||
|
style: new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
width: 1.25,
|
||||||
|
color: 'lightgrey'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Multiple Layers
|
||||||
|
shortdesc: Using layers to compose a map.
|
||||||
|
docs: >
|
||||||
|
Layers control how data is rendered on a map. In this example, a `Vector` layer displays GeoJSON over tiles from a `Tile` layer.
|
||||||
|
cloak:
|
||||||
|
- key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5
|
||||||
|
value: Your Bing Maps Key from http://www.bingmapsportal.com/ here
|
||||||
|
---
|
||||||
|
<div id="map"></div>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import TileLayer from '../src/ol/layer/Tile.js';
|
||||||
|
import OSM from '../src/ol/source/OSM.js';
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new OSM()
|
||||||
|
})
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: My First Map
|
||||||
|
shortdesc: Example of a map with a single layer.
|
||||||
|
docs: >
|
||||||
|
A map with an OSM source.
|
||||||
|
---
|
||||||
|
<div id="map"></div>
|
||||||
@@ -52,6 +52,9 @@
|
|||||||
"coveralls": "3.0.1",
|
"coveralls": "3.0.1",
|
||||||
"eslint": "4.19.1",
|
"eslint": "4.19.1",
|
||||||
"eslint-config-openlayers": "^9.2.0",
|
"eslint-config-openlayers": "^9.2.0",
|
||||||
|
"eslint-config-prettier": "^2.9.0",
|
||||||
|
"eslint-plugin-prettier": "^2.6.0",
|
||||||
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"expect.js": "0.3.1",
|
"expect.js": "0.3.1",
|
||||||
"front-matter": "^2.1.2",
|
"front-matter": "^2.1.2",
|
||||||
"fs-extra": "^6.0.0",
|
"fs-extra": "^6.0.0",
|
||||||
@@ -73,6 +76,7 @@
|
|||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"mustache": "^2.3.0",
|
"mustache": "^2.3.0",
|
||||||
"pixelmatch": "^4.0.2",
|
"pixelmatch": "^4.0.2",
|
||||||
|
"prettier": "^1.12.0",
|
||||||
"proj4": "2.4.4",
|
"proj4": "2.4.4",
|
||||||
"recast": "0.15.0",
|
"recast": "0.15.0",
|
||||||
"rollup": "0.60.2",
|
"rollup": "0.60.2",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "openlayers/react",
|
||||||
|
"globals": {
|
||||||
|
"graphql": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/.cache/
|
||||||
|
/public/
|
||||||
|
/build/
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const typography = require('./src/utils/typography');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
resolve: 'gatsby-plugin-typography',
|
||||||
|
options: {
|
||||||
|
pathToConfigModule: 'src/utils/typography.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'gatsby-plugin-jss',
|
||||||
|
{
|
||||||
|
resolve: 'gatsby-source-filesystem',
|
||||||
|
options: {
|
||||||
|
name: 'examples',
|
||||||
|
path: path.join(__dirname, '..', 'new-examples')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolve: 'examples',
|
||||||
|
options: {
|
||||||
|
sourceInstanceName: 'examples',
|
||||||
|
baseCss: typography.toString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'gatsby-transformer-remark',
|
||||||
|
'gatsby-plugin-react-next'
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// exports.onPreBuild = () => {
|
||||||
|
// // TODO: empty public folder
|
||||||
|
// };
|
||||||
Generated
+15635
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "@ol/site",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "gatsby develop",
|
||||||
|
"build": "gatsby build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"gatsby": "1.9.250",
|
||||||
|
"gatsby-link": "1.6.40",
|
||||||
|
"gatsby-plugin-jss": "1.5.13",
|
||||||
|
"gatsby-plugin-react-helmet": "2.0.10",
|
||||||
|
"gatsby-plugin-react-next": "^1.0.11",
|
||||||
|
"gatsby-plugin-typography": "1.7.18",
|
||||||
|
"gatsby-source-filesystem": "1.5.31",
|
||||||
|
"gatsby-transformer-remark": "^1.7.40",
|
||||||
|
"prop-types": "15.6.1",
|
||||||
|
"react-helmet": "5.2.0",
|
||||||
|
"rollup": "^0.58.1",
|
||||||
|
"rollup-plugin-commonjs": "^9.1.0",
|
||||||
|
"rollup-plugin-node-resolve": "^3.3.0",
|
||||||
|
"string-template": "^1.0.0",
|
||||||
|
"typography-plugin-code": "^0.16.11",
|
||||||
|
"typography-theme-noriega": "^0.15.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import React, {Component, Fragment} from 'react';
|
||||||
|
import {object} from 'prop-types';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
|
||||||
|
class Example extends Component {
|
||||||
|
render() {
|
||||||
|
const example = this.props.data.sitePage.context;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h1>{example.frontmatter.title}</h1>
|
||||||
|
<iframe
|
||||||
|
className={this.props.classes.embed}
|
||||||
|
src={example.embedUrl}
|
||||||
|
frameBorder="0"
|
||||||
|
/>
|
||||||
|
<h3>script</h3>
|
||||||
|
<pre>
|
||||||
|
<code>{example.js}</code>
|
||||||
|
</pre>
|
||||||
|
<h3>markup</h3>
|
||||||
|
<pre>
|
||||||
|
<code>{example.html}</code>
|
||||||
|
</pre>
|
||||||
|
{example.css && (
|
||||||
|
<Fragment>
|
||||||
|
<h3>style</h3>
|
||||||
|
<pre>
|
||||||
|
<code>{example.css}</code>
|
||||||
|
</pre>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Example.propTypes = {
|
||||||
|
data: object.isRequired,
|
||||||
|
classes: object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export const query = graphql`
|
||||||
|
query ExampleQuery($slug: String!) {
|
||||||
|
sitePage(context: {slug: {eq: $slug}}) {
|
||||||
|
context {
|
||||||
|
slug
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
embedUrl
|
||||||
|
html
|
||||||
|
js
|
||||||
|
css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
embed: {
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
height: 350,
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectSheet(styles)(Example);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charSet="utf-8">
|
||||||
|
<title>{title}</title>
|
||||||
|
<style>{baseCss}</style>
|
||||||
|
<style>{olCss}</style>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>{exampleCss}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{html}
|
||||||
|
<script type="module" src="{mainBundleUrl}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const {createFilePath} = require('gatsby-source-filesystem');
|
||||||
|
const rollup = require('rollup');
|
||||||
|
const resolve = require('rollup-plugin-node-resolve');
|
||||||
|
const common = require('rollup-plugin-commonjs');
|
||||||
|
const fs = require('fs');
|
||||||
|
const promisify = require('util').promisify;
|
||||||
|
const compileTemplate = require('string-template/compile');
|
||||||
|
|
||||||
|
const writeFile = promisify(fs.writeFile);
|
||||||
|
const readFile = promisify(fs.readFile);
|
||||||
|
|
||||||
|
let rollupCache;
|
||||||
|
const rollupPlugins = [resolve(), common()];
|
||||||
|
|
||||||
|
let olCss;
|
||||||
|
async function getOLCss() {
|
||||||
|
if (olCss) {
|
||||||
|
return olCss;
|
||||||
|
}
|
||||||
|
const cssPath = path.join(__dirname, '..', '..', '..', 'css', 'ol.css');
|
||||||
|
olCss = await readFile(cssPath, {encoding: 'utf8'});
|
||||||
|
return olCss;
|
||||||
|
}
|
||||||
|
|
||||||
|
let embedTemplate;
|
||||||
|
async function getEmbedTemplate() {
|
||||||
|
if (embedTemplate) {
|
||||||
|
return embedTemplate;
|
||||||
|
}
|
||||||
|
const embedPath = path.join(__dirname, 'embed.html');
|
||||||
|
const src = await readFile(embedPath, {encoding: 'utf8'});
|
||||||
|
embedTemplate = compileTemplate(src);
|
||||||
|
return embedTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.onCreateNode = ({node, getNode, boundActionCreators}) => {
|
||||||
|
const {createNodeField} = boundActionCreators;
|
||||||
|
if (node.internal.type === 'MarkdownRemark') {
|
||||||
|
const slug = createFilePath({node, getNode});
|
||||||
|
createNodeField({
|
||||||
|
node,
|
||||||
|
name: 'slug',
|
||||||
|
value: `/examples${slug}` // TODO: get this from options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.createPages = async (
|
||||||
|
{graphql, boundActionCreators},
|
||||||
|
{sourceInstanceName, baseCss = ''}
|
||||||
|
) => {
|
||||||
|
const {createPage} = boundActionCreators;
|
||||||
|
const {data} = await graphql(`
|
||||||
|
{
|
||||||
|
allFile(
|
||||||
|
filter: {sourceInstanceName: {eq: "${sourceInstanceName}"}, extension: {ne: ""}}
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
base
|
||||||
|
name
|
||||||
|
extension
|
||||||
|
absolutePath
|
||||||
|
childMarkdownRemark {
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const rollupInputs = [];
|
||||||
|
|
||||||
|
const examples = {};
|
||||||
|
data.allFile.edges.forEach(({node}) => {
|
||||||
|
const name = node.name;
|
||||||
|
if (!(name in examples)) {
|
||||||
|
examples[name] = {};
|
||||||
|
}
|
||||||
|
examples[name][node.extension] = node;
|
||||||
|
if (node.extension === 'js') {
|
||||||
|
rollupInputs.push(node.absolutePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundle = await rollup.rollup({
|
||||||
|
input: rollupInputs,
|
||||||
|
plugins: rollupPlugins,
|
||||||
|
experimentalCodeSplitting: true,
|
||||||
|
cache: rollupCache
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedDirName = 'example-embeds';
|
||||||
|
const embedDir = path.join(__dirname, '..', '..', 'public', embedDirName);
|
||||||
|
|
||||||
|
rollupCache = await bundle.write({
|
||||||
|
format: 'es',
|
||||||
|
sourcemap: true,
|
||||||
|
dir: embedDir
|
||||||
|
});
|
||||||
|
|
||||||
|
const writes = [];
|
||||||
|
|
||||||
|
for (const name in examples) {
|
||||||
|
const node = examples[name].md;
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(`Missing ${name}.md`);
|
||||||
|
}
|
||||||
|
const markdownNode = node.childMarkdownRemark;
|
||||||
|
if (!markdownNode) {
|
||||||
|
throw new Error(`Expected a MarkdownRemark node for ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainBundleUrl = `${name}.js`;
|
||||||
|
const bundleInfo = rollupCache[mainBundleUrl];
|
||||||
|
if (!bundleInfo) {
|
||||||
|
throw new Error(`Expected a js bundle for ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsNode = examples[name].js;
|
||||||
|
if (!jsNode) {
|
||||||
|
throw new Error(`Missing ${name}.js`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleIndex = bundleInfo.map.sources.findIndex(
|
||||||
|
filepath => path.resolve(filepath) === jsNode.absolutePath
|
||||||
|
);
|
||||||
|
if (moduleIndex < 0) {
|
||||||
|
throw new Error(`Could not find ${node.absolutePath} in module list`);
|
||||||
|
}
|
||||||
|
const source = bundleInfo.map.sourcesContent[moduleIndex];
|
||||||
|
if (!source) {
|
||||||
|
throw new Error(`Could not find source for ${jsNode.absolutePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let exampleCss = '';
|
||||||
|
const cssNode = examples[name].css;
|
||||||
|
if (cssNode) {
|
||||||
|
exampleCss = await readFile(cssNode.absolutePath, {encoding: 'utf8'});
|
||||||
|
await writeFile(path.join(embedDir, cssNode.base), exampleCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
const embedTemplate = await getEmbedTemplate();
|
||||||
|
const embed = embedTemplate({
|
||||||
|
title: markdownNode.frontmatter.title,
|
||||||
|
baseCss,
|
||||||
|
olCss: await getOLCss(),
|
||||||
|
exampleCss,
|
||||||
|
html: markdownNode.html,
|
||||||
|
mainBundleUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
const embedName = `${name}.html`;
|
||||||
|
writes.push(writeFile(path.join(embedDir, embedName), embed));
|
||||||
|
|
||||||
|
const slug = markdownNode.fields.slug;
|
||||||
|
|
||||||
|
createPage({
|
||||||
|
path: slug,
|
||||||
|
component: path.join(__dirname, 'components', 'Example.js'),
|
||||||
|
context: {
|
||||||
|
slug,
|
||||||
|
frontmatter: markdownNode.frontmatter,
|
||||||
|
embedUrl: `/${embedDirName}/${embedName}`,
|
||||||
|
html: markdownNode.html,
|
||||||
|
js: source.replace(/'\.\.\/src\/(.*?)\.js/g, "'$1"),
|
||||||
|
css: exampleCss
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(writes);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// no-op
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "examples",
|
||||||
|
"main": "index.js"
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import injectSheet from 'react-jss';
|
||||||
|
import {object, func} from 'prop-types';
|
||||||
|
import Link from 'gatsby-link';
|
||||||
|
|
||||||
|
const Layout = ({classes, children}) => (
|
||||||
|
<div className={classes.layout}>
|
||||||
|
<header className={classes.header}>
|
||||||
|
<h1>
|
||||||
|
<Link to="/">OpenLayers</Link>
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<Link to="/examples/">examples</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main className={classes.main}>{children()}</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
Layout.propTypes = {
|
||||||
|
children: func.isRequired,
|
||||||
|
classes: object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
layout: {
|
||||||
|
margin: '1em auto',
|
||||||
|
maxWidth: '960px'
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
padding: '0 2em',
|
||||||
|
marginBottom: '2em',
|
||||||
|
'& h1': {
|
||||||
|
margin: '0 auto 0 0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
padding: '0 2em'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectSheet(styles)(Layout);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import {object} from 'prop-types';
|
||||||
|
import Link from 'gatsby-link';
|
||||||
|
|
||||||
|
class Examples extends Component {
|
||||||
|
renderExample({node}) {
|
||||||
|
const context = node.context;
|
||||||
|
return (
|
||||||
|
<li key={node.id}>
|
||||||
|
<Link to={context.slug}>{context.frontmatter.title}</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ul>{this.props.data.allSitePage.edges.map(this.renderExample)}</ul>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Examples.propTypes = {
|
||||||
|
data: object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export const query = graphql`
|
||||||
|
query ExampleList {
|
||||||
|
allSitePage(filter: {pluginCreator: {name: {eq: "examples"}}}) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
context {
|
||||||
|
slug
|
||||||
|
frontmatter {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Examples;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default () => <p>This is the home page</p>;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
const Typography = require('typography');
|
||||||
|
const theme = require('typography-theme-noriega').default;
|
||||||
|
const CodePlugin = require('typography-plugin-code').default;
|
||||||
|
|
||||||
|
theme.plugins = [new CodePlugin()];
|
||||||
|
|
||||||
|
theme.overrideThemeStyles = () => ({
|
||||||
|
a: {
|
||||||
|
color: '#003c88',
|
||||||
|
textDecoration: 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = new Typography(theme);
|
||||||
Reference in New Issue
Block a user