├── .circleci
└── config.yml
├── .eslintrc
├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.png
├── index.html
├── logo.png
└── manifest.json
├── src
├── component
│ ├── color-bubble.js
│ ├── color-edition-list-item.js
│ ├── download-button.js
│ ├── github-button.js
│ ├── logo-button.js
│ ├── palette-edition-card.js
│ ├── preview
│ │ ├── app-bar.js
│ │ ├── badge.js
│ │ ├── bottom-navigation.js
│ │ ├── button.js
│ │ ├── container.js
│ │ └── index.js
│ ├── section-edition.js
│ ├── section-preview.js
│ └── view-selector.js
├── index.css
├── index.js
├── logo.svg
├── registerServiceWorker.js
├── screen
│ └── editor.js
└── service
│ └── theme.js
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | branches:
5 | ignore:
6 | - gh-pages
7 |
8 | docker:
9 | - image: circleci/node:10
10 |
11 | working_directory: ~/repo
12 |
13 | steps:
14 | - checkout
15 | - run: yarn
16 | - run: npm run lint
17 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "quotes": ["error", "single", { "allowTemplateLiterals": true }],
5 | "jsx-quotes": ["error", "prefer-double"]
6 | }
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Material UI theme editor
2 |
3 | Click [here](https://in-your-saas.github.io/material-ui-theme-editor/) to access it
4 |
5 | ## What is it made for?
6 |
7 | I'm a developer that loves to work with [MaterialUI](https://material-ui.com/) but it generates always the same looking websites.
8 | That's when a designer arrives and tell you "You've to use this color, and this color...", and it's a pain to integrate, you cannot make
9 | it fit with your MaterialUI theme, etc, just because your designer isn't really aware of what you use and what are the limits.
10 |
11 | So now, give this website to your designer, and wait for him to give you the theme file. And then, to integrate it, you just have to do
12 | the following
13 |
14 | ```javascript
15 | import React from 'react';
16 | import ReactDOM from 'react-dom';
17 | import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles';
18 | import Application from './your/entry/point';
19 | import yourRawTheme from './wherever/is/your/theme.json';
20 |
21 | const theme = createMuiTheme(yourRawTheme);
22 |
23 | ReactDOM.render(
24 |
25 |
26 |
27 | , document.getElementById('root'));
28 | ```
29 |
30 | And BOOOM! You have a nice theme, you designer is happy, and you didn't fight with him/her...
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-ui-theme-editor",
3 | "version": "0.1.0",
4 | "homepage": "https://in-your-saas.github.io/material-ui-theme-editor",
5 | "dependencies": {
6 | "@material-ui/core": "^3.4.0",
7 | "@material-ui/icons": "^3.0.1",
8 | "@material-ui/lab": "^3.0.0-alpha.23",
9 | "classnames": "^2.2.6",
10 | "file-saver": "^2.0.0-rc.4",
11 | "gh-pages": "^2.0.1",
12 | "lodash": "^4.17.11",
13 | "react": "^16.6.1",
14 | "react-color": "^2.14.1",
15 | "react-dom": "^16.6.1",
16 | "react-scripts": "2.1.1"
17 | },
18 | "scripts": {
19 | "predeploy": "npm run build",
20 | "deploy": "gh-pages -d build",
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test --env=jsdom",
24 | "eject": "react-scripts eject",
25 | "lint": "eslint src"
26 | },
27 | "browserslist": [
28 | ">0.2%",
29 | "not dead",
30 | "not ie <= 11",
31 | "not op_mini all"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/in-your-saas/material-ui-theme-editor/31eea1c3208cec9396fe11ddb575f7f510252d1f/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Material UI theme editor
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/in-your-saas/material-ui-theme-editor/31eea1c3208cec9396fe11ddb575f7f510252d1f/public/logo.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Theme Editor",
3 | "name": "Materil UI theme editor",
4 | "icons": [
5 | {
6 | "src": "favicon.png",
7 | "sizes": "256x256",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/component/color-bubble.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Avatar from '@material-ui/core/Avatar';
5 |
6 | const styles = (theme) => ({
7 | root: {
8 | borderColor: theme.palette.grey['300'],
9 | borderStyle: 'solid',
10 | borderWidth: 1,
11 | },
12 | });
13 |
14 | class ColorBubble extends React.PureComponent {
15 | getStyle() {
16 | return { backgroundColor: this.props.color };
17 | }
18 |
19 | render() {
20 | return (
21 |
26 | );
27 | }
28 | }
29 |
30 | export default withStyles(styles)(ColorBubble);
31 |
--------------------------------------------------------------------------------
/src/component/color-edition-list-item.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { SketchPicker } from 'react-color';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import ListItem from '@material-ui/core/ListItem';
5 | import ListItemText from '@material-ui/core/ListItemText';
6 | import Popover from '@material-ui/core/Popover';
7 |
8 | import ColorBubble from './color-bubble';
9 |
10 | const styles = () => ({
11 | avatar: {
12 | height: 25,
13 | width: 25,
14 | },
15 | });
16 |
17 | const anchorOrigin = {
18 | vertical: 'bottom',
19 | horizontal: 'left',
20 | };
21 |
22 | class ColorEditionListItem extends React.Component {
23 | state = {
24 | anchor: null,
25 | };
26 |
27 | handleClick = (event) => {
28 | this.setState({ anchor: event.currentTarget });
29 | };
30 |
31 | handleClose = () => {
32 | this.setState({ anchor: null });
33 | };
34 |
35 | handleChange = (color) => {
36 | const rgba = `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`;
37 | this.props.onChange(this.props.name, rgba);
38 | };
39 |
40 | render() {
41 | const {
42 | classes,
43 | rootClassName,
44 | } = this.props;
45 | return (
46 |
47 |
52 |
57 |
60 |
61 |
67 |
71 |
72 |
73 | );
74 | }
75 | }
76 |
77 | export default withStyles(styles)(ColorEditionListItem);
78 |
--------------------------------------------------------------------------------
/src/component/download-button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import CloudDownload from '@material-ui/icons/CloudDownload';
4 | import Tooltip from '@material-ui/core/Tooltip';
5 |
6 | import ThemeService from '../service/theme';
7 |
8 | export default class DownloadButton extends React.Component {
9 | state = {
10 | running: false,
11 | };
12 |
13 | handleClick = () => {
14 | this.setState({ running: true }, () => {
15 | ThemeService.download(this.props.theme)
16 | .then(() => this.setState({ running: false }));
17 | });
18 | };
19 |
20 | render() {
21 | const { rootClassName } = this.props;
22 | return (
23 |
24 |
33 |
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/component/github-button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 |
4 | const styles = () => ({
5 | image: {
6 | position: 'fixed',
7 | top: 0,
8 | right: 0,
9 | boder: 0,
10 | zIndex: 2000,
11 | },
12 | });
13 |
14 | const GithubButton = (props) => (
15 |
20 |
25 |
26 | );
27 |
28 | export default withStyles(styles)(GithubButton);
29 |
--------------------------------------------------------------------------------
/src/component/logo-button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 |
4 | const styles = (theme) => ({
5 | root: {
6 | display: 'block',
7 | textAlign: 'center',
8 | padding: theme.spacing.unit,
9 | paddingBottom: 0,
10 | },
11 | image: {
12 | maxWidth: 100,
13 | },
14 | });
15 |
16 | class LogoButton extends React.PureComponent {
17 | render() {
18 | return (
19 |
25 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default withStyles(styles)(LogoButton);
36 |
--------------------------------------------------------------------------------
/src/component/palette-edition-card.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Card from '@material-ui/core/Card';
5 | import CardContent from '@material-ui/core/CardContent';
6 | import List from '@material-ui/core/List';
7 | import Typography from '@material-ui/core/Typography';
8 |
9 | import ColorEditionListItem from './color-edition-list-item';
10 |
11 | const styles = (theme) => ({
12 | root: {
13 | paddingBottom: theme.spacing.unit,
14 | },
15 | title: {
16 | fontSize: 14,
17 | },
18 | });
19 |
20 | class PaletteEditionCard extends React.PureComponent {
21 | handleChange = (name, value) => {
22 | this.props.onChange(this.props.name, {
23 | ...this.props.palette,
24 | [name]: value,
25 | });
26 | };
27 |
28 | render() {
29 | const { classes, palette, rootClassName, label, fields } = this.props;
30 | return (
31 |
32 |
33 |
37 | {label}
38 |
39 |
40 |
41 | {fields.map((item) => (
42 |
49 | ))}
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default withStyles(styles)(PaletteEditionCard);
57 |
--------------------------------------------------------------------------------
/src/component/preview/app-bar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import AppBar from '@material-ui/core/AppBar';
4 | import Toolbar from '@material-ui/core/Toolbar';
5 | import Typography from '@material-ui/core/Typography';
6 |
7 | const styles = (theme) => ({
8 | root: {
9 | padding: theme.spacing.unit,
10 | },
11 | item: {
12 | marginBottom: theme.spacing.unit,
13 | },
14 | });
15 |
16 | class PreviewAppBar extends React.PureComponent {
17 | render() {
18 | const { classes } = this.props;
19 | return (
20 |
21 |
22 |
23 |
24 | Default App Bar
25 |
26 |
27 |
28 |
29 |
30 |
31 | Primary App Bar
32 |
33 |
34 |
35 |
36 |
37 |
38 | Secondary App Bar
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | export default withStyles(styles)(PreviewAppBar);
48 |
--------------------------------------------------------------------------------
/src/component/preview/badge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Badge from '@material-ui/core/Badge';
4 | import MailIcon from '@material-ui/icons/Mail';
5 |
6 | const styles = (theme) => ({
7 | root: {
8 | padding: theme.spacing.unit,
9 | },
10 | margin: {
11 | margin: theme.spacing.unit,
12 | },
13 | });
14 |
15 | class PreviewBadge extends React.PureComponent {
16 | render() {
17 | const { classes } = this.props;
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default withStyles(styles)(PreviewBadge);
35 |
--------------------------------------------------------------------------------
/src/component/preview/bottom-navigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import BottomNavigation from '@material-ui/core/BottomNavigation';
4 | import BottomNavigationAction from '@material-ui/core/BottomNavigationAction';
5 | import RestoreIcon from '@material-ui/icons/Restore';
6 | import FavoriteIcon from '@material-ui/icons/Favorite';
7 | import LocationOnIcon from '@material-ui/icons/LocationOn';
8 |
9 | const styles = (theme) => ({
10 | root: {
11 | padding: theme.spacing.unit,
12 | },
13 | item: {
14 | maxWidth: 500,
15 | },
16 | });
17 |
18 | class PreviewBottomNavigation extends React.PureComponent {
19 | render() {
20 | const { classes } = this.props;
21 | return (
22 |
23 |
28 | } />
29 | } />
30 | } />
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | export default withStyles(styles)(PreviewBottomNavigation);
38 |
--------------------------------------------------------------------------------
/src/component/preview/button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Button from '@material-ui/core/Button';
4 | import AddIcon from '@material-ui/icons/Add';
5 | import DeleteIcon from '@material-ui/icons/Delete';
6 | import NavigationIcon from '@material-ui/icons/Navigation';
7 |
8 | const styles = (theme) => ({
9 | root: {
10 | padding: theme.spacing.unit,
11 | },
12 | button: {
13 | margin: theme.spacing.unit,
14 | },
15 | });
16 |
17 | class PreviewButton extends React.PureComponent {
18 | render() {
19 | const { classes } = this.props;
20 | return (
21 |
22 |
23 |
24 |
27 |
30 |
33 |
36 |
37 |
38 |
39 |
42 |
45 |
48 |
51 |
52 |
53 |
54 |
57 |
60 |
63 |
66 |
67 |
68 |
71 |
74 |
77 |
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default withStyles(styles)(PreviewButton);
87 |
--------------------------------------------------------------------------------
/src/component/preview/container.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Paper from '@material-ui/core/Paper';
5 |
6 | import PreviewAppBar from './app-bar';
7 | import PreviewBadge from './badge';
8 | import PreviewBottomNavigation from './bottom-navigation';
9 | import PreviewButton from './button';
10 |
11 | const styles = (theme) => ({
12 | root: {
13 | backgroundColor: theme.palette.background.default,
14 | },
15 | });
16 |
17 | class PreviewContainer extends React.PureComponent {
18 | render() {
19 | const { classes, className } = this.props;
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
31 | export default withStyles(styles)(PreviewContainer);
32 |
--------------------------------------------------------------------------------
/src/component/preview/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MuiThemeProvider } from '@material-ui/core/styles';
3 |
4 | import PreviewContainer from './container';
5 |
6 | export default class PreviewDisplay extends React.PureComponent {
7 | render() {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/component/section-edition.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 | import { withStyles } from '@material-ui/core/styles';
4 |
5 | import LogoButton from './logo-button';
6 | import PaletteEditionCard from './palette-edition-card';
7 |
8 | const styles = (theme) => ({
9 | root: {
10 | overflowY: 'auto',
11 | },
12 | card: {
13 | margin: theme.spacing.unit,
14 | },
15 | logo: {
16 | maxWidth: 150,
17 | },
18 | });
19 |
20 | const paletteFields = {
21 | common: [
22 | {label: 'Black', name: 'black'},
23 | {label: 'White', name: 'white'},
24 | ],
25 | background: [
26 | {label: 'Paper', name: 'paper'},
27 | {label: 'Default', name: 'default'},
28 | ],
29 | primary: [
30 | {label: 'Main', name: 'main'},
31 | {label: 'Light', name: 'light'},
32 | {label: 'Dark', name: 'dark'},
33 | {label: 'Contrast text', name: 'contrastText'},
34 | ],
35 | secondary: [
36 | {label: 'Main', name: 'main'},
37 | {label: 'Light', name: 'light'},
38 | {label: 'Dark', name: 'dark'},
39 | {label: 'Contrast text', name: 'contrastText'},
40 | ],
41 | error: [
42 | {label: 'Main', name: 'main'},
43 | {label: 'Light', name: 'light'},
44 | {label: 'Dark', name: 'dark'},
45 | {label: 'Contrast text', name: 'contrastText'},
46 | ],
47 | text: [
48 | {label: 'Primary', name: 'primary'},
49 | {label: 'Secondary', name: 'secondary'},
50 | {label: 'Disabled', name: 'disabled'},
51 | {label: 'Hint', name: 'hint'},
52 | ],
53 | };
54 |
55 | class SectionEdition extends React.PureComponent {
56 | handleChangePalette = (name, changes) => {
57 | this.props.onChange({
58 | ...this.props.theme,
59 | palette: {
60 | ...this.props.theme.palette,
61 | [name]: changes,
62 | },
63 | });
64 | };
65 |
66 | render() {
67 | const { classes, rootClassName } = this.props;
68 | return (
69 |
120 | );
121 | }
122 | }
123 |
124 | export default withStyles(styles)(SectionEdition);
125 |
--------------------------------------------------------------------------------
/src/component/section-preview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 | import { withStyles } from '@material-ui/core/styles';
4 |
5 | import PreviewDisplay from './preview';
6 | import ViewSelector from './view-selector';
7 |
8 | const styles = (theme) => ({
9 | root: {
10 | display: 'flex',
11 | flex: 3,
12 | flexDirection: 'column',
13 | },
14 | selector: {
15 | alignSelf: 'center',
16 | margin: theme.spacing.unit,
17 | },
18 | container: {
19 | alignSelf: 'center',
20 | flex: 1,
21 | margin: theme.spacing.unit,
22 | overflow: 'auto',
23 | width: '100%',
24 | },
25 | desktop: {
26 | },
27 | mobile: {
28 | maxWidth: 350,
29 | maxHeight: 650,
30 | },
31 | });
32 |
33 | class SectionPreview extends React.PureComponent {
34 | render() {
35 | const { classes, rootClassName } = this.props;
36 | return (
37 |
53 | );
54 | }
55 | }
56 |
57 | export default withStyles(styles)(SectionPreview);
58 |
--------------------------------------------------------------------------------
/src/component/view-selector.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import ToggleButton from '@material-ui/lab/ToggleButton';
4 | import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
5 | import DesktopWindows from '@material-ui/icons/DesktopWindows';
6 | import Smartphone from '@material-ui/icons/Smartphone';
7 |
8 | const styles = () => ({
9 | root: {
10 | display: 'flex',
11 | flex: 3,
12 | flexDirection: 'column',
13 | },
14 | });
15 |
16 | class ViewSelector extends React.PureComponent {
17 | handleChange = (e, value) =>
18 | this.props.onChange(value);
19 |
20 | render() {
21 | return (
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 | }
37 |
38 | export default withStyles(styles)(ViewSelector);
39 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | height: 100vh;
6 | display: flex;
7 | }
8 | #root {
9 | display: flex;
10 | flex: 1;
11 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import Editor from './screen/editor';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | registerServiceWorker();
10 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/screen/editor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createMuiTheme, withStyles } from '@material-ui/core/styles';
3 | import SectionEdition from '../component/section-edition';
4 | import SectionPreview from '../component/section-preview';
5 | import DownloadButton from '../component/download-button';
6 | import GithubButton from '../component/github-button';
7 |
8 | const styles = (theme) => ({
9 | root: {
10 | backgroundColor: theme.palette.background.default,
11 | display: 'flex',
12 | flex: 1,
13 | flexDirection: 'row',
14 | },
15 | edition: {
16 | minWidth: 250,
17 | maxWidth: 350,
18 | flex: 2,
19 | },
20 | preview: {
21 | flex: 3,
22 | },
23 | button: {
24 | position: 'fixed',
25 | bottom: theme.spacing.unit * 2,
26 | right: theme.spacing.unit * 2,
27 | },
28 | });
29 |
30 | class Editor extends React.Component {
31 | state = {
32 | theme: createMuiTheme(),
33 | view: 'desktop',
34 | };
35 |
36 | handleChangeTheme = (theme) =>
37 | this.setState({ theme });
38 |
39 | handleChangeView = (view) =>
40 | this.setState({ view });
41 |
42 | render() {
43 | const { classes } = this.props;
44 | return (
45 |
46 |
47 |
52 |
58 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default withStyles(styles)(Editor);
68 |
--------------------------------------------------------------------------------
/src/service/theme.js:
--------------------------------------------------------------------------------
1 | import saveAs from 'file-saver';
2 | import get from 'lodash/get';
3 | import set from 'lodash/set';
4 |
5 | const whitelist = [
6 | 'palette.common',
7 | 'palette.background',
8 | 'palette.primary',
9 | 'palette.secondary',
10 | 'palette.error',
11 | 'palette.text',
12 | ];
13 |
14 | const convert = (theme) => {
15 | return whitelist.reduce((res, key) => {
16 | const value = get(theme, key);
17 | return set(res, key, value);
18 | }, {});
19 | };
20 |
21 | const download = (theme) => {
22 | const clean = convert(theme);
23 | const fileToSave = new Blob([JSON.stringify(clean)], {
24 | type: 'application/json',
25 | name: 'theme.json',
26 | });
27 | saveAs(fileToSave, 'theme.json');
28 | return Promise.resolve();
29 | };
30 |
31 | export default {
32 | download,
33 | };
34 |
--------------------------------------------------------------------------------