base: setup react redux app
This commit is contained in:
commit
68d781002a
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env",
|
||||||
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-flow"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": [
|
||||||
|
"airbnb",
|
||||||
|
"plugin:flowtype/recommended"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"plugins": ["flowtype"],
|
||||||
|
"settings": {
|
||||||
|
"flowtype": {
|
||||||
|
"onlyFilesWithFlowAnnotation": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"jsx-quotes": ["error", "prefer-single"],
|
||||||
|
"import/prefer-default-export": [
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"react/jsx-filename-extension": [
|
||||||
|
1,
|
||||||
|
{ "extensions": [".js"] }
|
||||||
|
],
|
||||||
|
"jsx-a11y/anchor-is-valid": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
"Link"
|
||||||
|
],
|
||||||
|
"specialLink": [
|
||||||
|
"to",
|
||||||
|
"hrefLeft",
|
||||||
|
"hrefRight"
|
||||||
|
],
|
||||||
|
"aspects": [
|
||||||
|
"noHref",
|
||||||
|
"invalidHref",
|
||||||
|
"preferButton"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jsx-a11y/no-autofocus": [ 0, {
|
||||||
|
"ignoreNonDOM": true
|
||||||
|
}],
|
||||||
|
"max-len": ["error", {
|
||||||
|
"ignoreUrls": true,
|
||||||
|
"ignoreComments": true,
|
||||||
|
"ignoreStrings": true,
|
||||||
|
"ignorePattern": "<p[^>]*>.*?</p>"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
[ignore]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
|
||||||
|
[options]
|
||||||
|
|
||||||
|
[strict]
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
|
webpack-stats/*
|
||||||
|
.DS_Store
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Zcash Foundation
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Zcash Node Setup
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Download the attached files, verify checksum of the archive and extract
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 -c zcash-macos-v2.0.1a.tar.bz2.hash
|
||||||
|
tar -xvf zcash-macos-v2.0.1a.tar.bz2
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
When launching Zcash on MacOS for the first time, certain initialization steps should be completed.
|
||||||
|
Please run the commands below once for the first time
|
||||||
|
|
||||||
|
```
|
||||||
|
cd zcash-macos-v2.0.1a/usr/local/bin
|
||||||
|
./zcash-fetch-params
|
||||||
|
./zcash-init
|
||||||
|
./zcashd
|
||||||
|
```
|
||||||
|
|
||||||
|
You can just run Zcash by launching the daemon afterwards:
|
||||||
|
|
||||||
|
```
|
||||||
|
./zcashd
|
||||||
|
```
|
||||||
|
|
||||||
|
You can refer to NODE_COMMANDS for basic usage
|
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
# for detailed information use the guides below
|
||||||
|
https://github.com/zcash/zcash/wiki/1.0-User-Guide
|
||||||
|
https://github.com/zcash/zcash/blob/master/doc/payment-api.md
|
||||||
|
https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_calls_list
|
||||||
|
|
||||||
|
# list info for a command
|
||||||
|
$ ./zcash-cli help <command>
|
||||||
|
|
||||||
|
|
||||||
|
# list all accounts (therefore all t-addr's)
|
||||||
|
$ ./zcash-cli listreceivedbyaddress 0 true
|
||||||
|
|
||||||
|
# list of t-addr's for default account (again all t-addr's)
|
||||||
|
$ ./zcash-cli getaddressesbyaccount ""
|
||||||
|
|
||||||
|
# list all unspent transaction outputs (t-addr UTXO's)
|
||||||
|
$ ./zcash-cli listunspent
|
||||||
|
|
||||||
|
# create new t-addr's
|
||||||
|
$ ./zcash-cli getnewaddress
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# list all z-addr's
|
||||||
|
$ ./zcash-cli z_listaddresses
|
||||||
|
|
||||||
|
# create new z-addr's
|
||||||
|
$ ./zcash-cli z_getnewaddress
|
||||||
|
|
||||||
|
# get total balance
|
||||||
|
$ ./zcash-cli z_gettotalbalance
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# send funds
|
||||||
|
$ export TADDR='t1TGVDzsEK2qbG1N8FJQFSAzV1bWWHMGGCS'
|
||||||
|
$ export ZADDR='zcNeXiyD3JkhKTrU38xM9C6HQGy9aP5qqVFH25qFzQGnmdwYZ2Dr53Jy7iRp64D4CzkMZdmKagN6mmtu3jVKHuZ8xZp8fw3'
|
||||||
|
$ export FRIEND='zcfZJW3qLHpSc7q7W1SXRGdVjgM6Q6kRwdkz1DHW5sP2EqcMHf5RCp3Frpf2qnb81j9K6upzRN4HoVxfboVwLTRaZ7bKn8b'
|
||||||
|
|
||||||
|
# send from t-addr to z-addr (with memo and fee)
|
||||||
|
$ ./zcash-cli z_sendmany "$TADDR" "[{\"address\": \"$ZADDR\", \"amount\": 0.1, \"memo\": \"0123456789\"}]" 1 0.002
|
||||||
|
|
||||||
|
# send from t-addr to t-addr (with fee)
|
||||||
|
$ ./zcash-cli z_sendmany "$TADDR1" "[{\"address\": \"$TADDR2\", \"amount\": 0.09}]" 1 0.002
|
||||||
|
|
||||||
|
# send from z-addr to z-addr (with memo and fee)
|
||||||
|
$ ./zcash-cli z_sendmany "$ZADDR" "[{\"address\": \"$FRIEND\", \"amount\": 0.05, \"memo\": \"9876543210\"}]" 1 0.002
|
||||||
|
|
||||||
|
# get send result
|
||||||
|
$ ./zcash-cli z_getoperationresult [\"$OPID\"]
|
||||||
|
|
||||||
|
|
||||||
|
# list amounts received by z-addr
|
||||||
|
$ ./zcash-cli z_listreceivedbyaddress "$ZADDR"
|
||||||
|
|
||||||
|
# list balance both for t-addr and z-addr
|
||||||
|
$ ./zcash-cli z_getbalance "$TADDR"
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Zcash Reference Wallet
|
||||||
|
|
||||||
|
Reference Wallet for the Zcash Network
|
||||||
|
|
||||||
|
## Stack Information
|
||||||
|
|
||||||
|
- [React](https://facebook.github.io/react/): UI view layer
|
||||||
|
- [Redux](http://redux.js.org/): predictable state container
|
||||||
|
- [Webpack](http://webpack.github.io/): module bundler
|
||||||
|
- [Webpack-Dev-Server](https://webpack.github.io/docs/webpack-dev-server.html): development server
|
||||||
|
- [Babel](http://babeljs.io/): ES6/JSX transpilling
|
||||||
|
- [ESLint](http://eslint.org/): code rules and linting
|
||||||
|
- [React Router](https://github.com/reactjs/react-router): routing solution for react
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run the application on port 8080
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Webpack Bundle Analysis
|
||||||
|
|
||||||
|
To visualize the bundle size of your application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn analyze
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
© Zcash Foundation 2018
|
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import uuidv4 from 'uuid/v4';
|
||||||
|
import { ADD_TODO } from '../constants/actions';
|
||||||
|
import { getTimestamp } from '../utils/timestamp';
|
||||||
|
|
||||||
|
export const addTodo = (text: string) => ({
|
||||||
|
type: ADD_TODO,
|
||||||
|
payload: {
|
||||||
|
text,
|
||||||
|
id: uuidv4(),
|
||||||
|
editing: false,
|
||||||
|
createdAt: getTimestamp(),
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { CANCEL_UPDATE_TODO } from '../constants/actions';
|
||||||
|
|
||||||
|
export const cancelUpdateTodo = (id: string) => ({
|
||||||
|
type: CANCEL_UPDATE_TODO,
|
||||||
|
payload: { id },
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { DELETE_TODO } from '../constants/actions';
|
||||||
|
|
||||||
|
export const deleteTodo = (id: string) => ({
|
||||||
|
type: DELETE_TODO,
|
||||||
|
payload: { id },
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { TOGGLE_EDIT_TODO } from '../constants/actions';
|
||||||
|
|
||||||
|
export const toggleEdit = (id: string) => ({
|
||||||
|
type: TOGGLE_EDIT_TODO,
|
||||||
|
payload: { id },
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { UPDATE_TODO } from '../constants/actions';
|
||||||
|
|
||||||
|
export const updateTodo = (id: string, text: string) => ({
|
||||||
|
type: UPDATE_TODO,
|
||||||
|
payload: {
|
||||||
|
text,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import configureStore from './store/configure';
|
||||||
|
import Router from './router';
|
||||||
|
|
||||||
|
const store = configureStore({});
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router />
|
||||||
|
</Provider>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="-793 545.5 96 100"><style>.st0{fill:#ffffff}</style><path class="st0" d="M-711.1 556.6h-63.8c-3.5 0-6.3 2.9-6.3 6.6v75.5c0 3.6 2.8 6.6 6.3 6.6h63.8c3.5 0 6.3-2.9 6.3-6.6v-75.5c0-3.6-2.8-6.6-6.3-6.6zm.8 79s.2 1.5-.5 2.3c-.7.8-2.1.7-2.1.7h-60.7s-.9 0-1.5-.8c-.6-.6-.6-2-.6-2v-70.4s0-1 .6-1.6c.7-.6 1.6-.7 1.6-.7h60.7s1.2 0 1.7.5c.7.7.7 1.5.7 1.5l.1 70.5zM-737.3 547.8c0 1.4-1.3 2.5-2.8 2.5h-5.6c-1.5 0-2.8-1.1-2.8-2.5s1.3-2.5 2.8-2.5h5.6c1.6 0 2.8 1.2 2.8 2.5z"/><path class="st0" d="M-730.9 553.3c0-1.5-.1-3.9-1.3-3.9h-21.5c-1.1 0-1.4 2.4-1.4 3.9h24.2z"/><path class="st0" d="M-725.7 558.3c0-2.1-.2-5.6-1.9-5.6h-30.8c-1.6 0-2 3.5-1.9 5.5l34.6.1zM-755.8 577.6h35.4v3.2h-35.4zM-767 588.8v7.9h7.9v-7.9h-7.9zm1.2 6.7V590h5.5v5.5h-5.5zM-755.8 592.6h35.4v3.2h-35.4zM-755.8 606.9h35.4v3.2h-35.4zM-755.8 620.9h35.4v3.1h-35.4zM-767 573.9v7.9h7.9v-7.9h-7.9zm1.2 6.7V575h5.5v5.5h-5.5zM-767 603.2v7.9h7.9v-7.9h-7.9zm1.2 6.7v-5.5h5.5v5.5h-5.5zM-767 617.2v7.9h7.9v-7.9h-7.9zm1.2 6.7v-5.5h5.5v5.5h-5.5z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,81 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import type { TodoType } from '../../types/todo';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
updateTodo: Function,
|
||||||
|
todo: TodoType,
|
||||||
|
cancelUpdateTodo: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class TodoEditInput extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
value: props.todo.text || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>, id: string) => {
|
||||||
|
const { value } = this.state;
|
||||||
|
const { updateTodo } = this.props;
|
||||||
|
const trimValue = value.trim();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (trimValue !== '') {
|
||||||
|
updateTodo(id, trimValue);
|
||||||
|
this.setState({ value: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancel = (id: string) => {
|
||||||
|
const { cancelUpdateTodo } = this.props;
|
||||||
|
cancelUpdateTodo(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const { target: { value } } = event;
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { value } = this.state;
|
||||||
|
const { todo } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='todo-item__view todo-item__view--edit'>
|
||||||
|
<form
|
||||||
|
className='todo-item__input'
|
||||||
|
onSubmit={e => this.handleSubmit(e, todo.id)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
className='todo-item__input-field'
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='todo-item__input-button'
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='todo-item__input-cancel'
|
||||||
|
onClick={() => this.handleCancel(todo.id)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
addTodo: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class TodoInput extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = this.state;
|
||||||
|
const { addTodo } = this.props;
|
||||||
|
const trimValue = value.trim();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (trimValue !== '') {
|
||||||
|
addTodo(trimValue);
|
||||||
|
this.setState({ value: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const { target: { value } } = event;
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { value } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className='todo__input'
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
className='todo__input-field'
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='todo__input-button'
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import type { TodoType } from '../../types/todo';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
todo: TodoType,
|
||||||
|
deleteTodo: Function,
|
||||||
|
toggleEdit: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class TodoListItem extends PureComponent<Props> {
|
||||||
|
handleDelete = (id: string) => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const { deleteTodo } = this.props;
|
||||||
|
deleteTodo(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditToggle = (id: string) => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const { toggleEdit } = this.props;
|
||||||
|
toggleEdit(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
todo,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='todo-item__view todo-item__view--view'>
|
||||||
|
<span className='todo-item__text'>
|
||||||
|
{todo.text}
|
||||||
|
</span>
|
||||||
|
<div className='todo-item__buttons'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => this.handleEditToggle(todo.id)}
|
||||||
|
className='todo-item__button'
|
||||||
|
>
|
||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 528.899 528.899'><path className='todo-item__svg' d='M328.883 89.125l107.59 107.589-272.34 272.34L56.604 361.465l272.279-272.34zm189.23-25.948l-47.981-47.981c-18.543-18.543-48.653-18.543-67.259 0l-45.961 45.961 107.59 107.59 53.611-53.611c14.382-14.383 14.382-37.577 0-51.959zM.3 512.69c-1.958 8.812 5.998 16.708 14.811 14.565l119.891-29.069L27.473 390.597.3 512.69z' /></svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => this.handleDelete(todo.id)}
|
||||||
|
className='todo-item__button'
|
||||||
|
>
|
||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 512 512'><path className='todo-item__svg' fill='#1D1D1B' d='M459.232 60.687h-71.955c-1.121-17.642-15.631-31.657-33.553-31.657H161.669c-17.921 0-32.441 14.015-33.553 31.657H64.579c-18.647 0-33.767 15.12-33.767 33.768v8.442c0 18.648 15.12 33.768 33.767 33.768h21.04v342.113c0 13.784 11.179 24.963 24.963 24.963h308.996c13.784 0 24.964-11.179 24.964-24.963V136.665h14.691c18.663 0 33.768-15.12 33.768-33.768v-8.442c-.001-18.648-15.105-33.768-33.769-33.768zM196.674 443.725c0 12.58-10.197 22.803-22.802 22.803-12.598 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802 12.605 0 22.802 10.206 22.802 22.802v284.9zm91.213 0c0 12.58-10.205 22.803-22.803 22.803s-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802s22.803 10.206 22.803 22.802v284.9zm91.212 0c0 12.58-10.205 22.803-22.803 22.803-12.613 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.189-22.802 22.803-22.802 12.598 0 22.803 10.206 22.803 22.802v284.9z' /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import TodoEditInput from './todo-edit-input';
|
||||||
|
import TodoListItem from './todo-list-item';
|
||||||
|
import type { TodoType } from '../../types/todo';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
todos: Array<TodoType>,
|
||||||
|
deleteTodo: Function,
|
||||||
|
toggleEdit: Function,
|
||||||
|
updateTodo: Function,
|
||||||
|
cancelUpdateTodo: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class TodoList extends PureComponent<Props> {
|
||||||
|
renderTodoView = (todo: TodoType) => {
|
||||||
|
const { deleteTodo, toggleEdit } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TodoListItem
|
||||||
|
todo={todo}
|
||||||
|
deleteTodo={deleteTodo}
|
||||||
|
toggleEdit={toggleEdit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditView = (todo: TodoType) => {
|
||||||
|
const { updateTodo, cancelUpdateTodo } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TodoEditInput
|
||||||
|
todo={todo}
|
||||||
|
updateTodo={updateTodo}
|
||||||
|
cancelUpdateTodo={cancelUpdateTodo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderList = () => {
|
||||||
|
const { todos } = this.props;
|
||||||
|
const sortTodosByTime = todos.sort((a, b) => b.createdAt - a.createdAt);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className='todo__list'>
|
||||||
|
{sortTodosByTime.map(todo => (
|
||||||
|
<li
|
||||||
|
key={todo.id}
|
||||||
|
className='todo__list-item todo-item'
|
||||||
|
>
|
||||||
|
{todo.editing ? (
|
||||||
|
this.renderEditView(todo)
|
||||||
|
) : (
|
||||||
|
this.renderTodoView(todo)
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEmptyState = () => (
|
||||||
|
<p className='todo__list todo__list--empty'>No todos right now</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos } = this.props;
|
||||||
|
const hasTodos = todos.length;
|
||||||
|
|
||||||
|
return hasTodos ? this.renderList() : this.renderEmptyState();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const ADD_TODO = 'ADD_TODO';
|
||||||
|
export const DELETE_TODO = 'DELETE_TODO';
|
||||||
|
export const UPDATE_TODO = 'UPDATE_TODO';
|
||||||
|
export const TOGGLE_EDIT_TODO = 'TOGGLE_EDIT_TODO';
|
||||||
|
export const CANCEL_UPDATE_TODO = 'CANCEL_UPDATE_TODO';
|
|
@ -0,0 +1,28 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import TodoView from '../views/todo';
|
||||||
|
import { addTodo } from '../actions/add-todo';
|
||||||
|
import { deleteTodo } from '../actions/delete-todo';
|
||||||
|
import { toggleEdit } from '../actions/toggle-edit-todo';
|
||||||
|
import { updateTodo } from '../actions/update-todo';
|
||||||
|
import { cancelUpdateTodo } from '../actions/cancel-update-todo';
|
||||||
|
import type { AppState } from '../types/app-state';
|
||||||
|
import type { Dispatch } from '../types/redux';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState) => ({
|
||||||
|
todos: state.todos,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
|
addTodo: text => dispatch(addTodo(text)),
|
||||||
|
deleteTodo: id => dispatch(deleteTodo(id)),
|
||||||
|
toggleEdit: id => dispatch(toggleEdit(id)),
|
||||||
|
updateTodo: (id, text) => dispatch(updateTodo(id, text)),
|
||||||
|
cancelUpdateTodo: id => dispatch(cancelUpdateTodo(id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(TodoView);
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './app';
|
||||||
|
|
||||||
|
const el = document.getElementById('root');
|
||||||
|
ReactDOM.render(<App />, el);
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import todoReducer from './todo';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos: todoReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {
|
||||||
|
ADD_TODO,
|
||||||
|
DELETE_TODO,
|
||||||
|
UPDATE_TODO,
|
||||||
|
CANCEL_UPDATE_TODO,
|
||||||
|
TOGGLE_EDIT_TODO,
|
||||||
|
} from '../constants/actions';
|
||||||
|
import type { TodoType } from '../types/todo';
|
||||||
|
import type { Action } from '../types/redux';
|
||||||
|
|
||||||
|
const initialState = [];
|
||||||
|
|
||||||
|
export default (state: Array<TodoType> = initialState, action: Action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_TODO:
|
||||||
|
return [
|
||||||
|
...state,
|
||||||
|
action.payload,
|
||||||
|
];
|
||||||
|
case DELETE_TODO:
|
||||||
|
// $FlowFixMe
|
||||||
|
return state.filter((todo: Object) => todo.id !== action.payload.id);
|
||||||
|
case TOGGLE_EDIT_TODO: {
|
||||||
|
const { id } = action.payload;
|
||||||
|
const todos = [...state];
|
||||||
|
const index = todos.map(todo => todo.id).indexOf(id);
|
||||||
|
todos[index].editing = true;
|
||||||
|
|
||||||
|
return todos;
|
||||||
|
}
|
||||||
|
case UPDATE_TODO: {
|
||||||
|
const { id, text } = action.payload;
|
||||||
|
const todos = [...state];
|
||||||
|
const index = todos.map(todo => todo.id).indexOf(id);
|
||||||
|
todos[index].text = text;
|
||||||
|
todos[index].editing = false;
|
||||||
|
|
||||||
|
return todos;
|
||||||
|
}
|
||||||
|
case CANCEL_UPDATE_TODO: {
|
||||||
|
const { id } = action.payload;
|
||||||
|
const todos = [...state];
|
||||||
|
const index = todos.map(todo => todo.id).indexOf(id);
|
||||||
|
todos[index].editing = false;
|
||||||
|
|
||||||
|
return todos;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { Route, Switch, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import TodoContainer from '../containers/todo';
|
||||||
|
import AboutView from '../views/about';
|
||||||
|
import NotFoundView from '../views/not-found';
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<Fragment>
|
||||||
|
<div className='header-menu'>
|
||||||
|
<Link to='/'>Todo</Link>
|
||||||
|
<Link to='/about'>About</Link>
|
||||||
|
</div>
|
||||||
|
<Switch>
|
||||||
|
<Route path='/' exact component={TodoContainer} />
|
||||||
|
<Route path='/about' component={AboutView} />
|
||||||
|
<Route component={NotFoundView} />
|
||||||
|
</Switch>
|
||||||
|
</Fragment>
|
||||||
|
);
|
|
@ -0,0 +1,24 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import rootReducer from '../reducers';
|
||||||
|
|
||||||
|
export default function configureStore(initialState: Object) {
|
||||||
|
const middleware = applyMiddleware(thunk);
|
||||||
|
let enhancer;
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production'
|
||||||
|
|| process.env.NODE_ENV !== 'staging'
|
||||||
|
) {
|
||||||
|
enhancer = compose(
|
||||||
|
middleware,
|
||||||
|
window.devToolsExtension ? window.devToolsExtension() : f => f,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
enhancer = compose(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createStore(rootReducer, initialState, enhancer);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import type { TodoType } from './todo';
|
||||||
|
|
||||||
|
export type AppState = {
|
||||||
|
todos: Array<TodoType>,
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
type State = {| |};
|
||||||
|
|
||||||
|
export type Action = { type: $Subtype<string>, payload: Object };
|
||||||
|
export type GetState = () => State;
|
||||||
|
export type Dispatch = (action: Action) => any;
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export type TodoType = {
|
||||||
|
id: string,
|
||||||
|
text: string,
|
||||||
|
editing: boolean,
|
||||||
|
createdAt: number
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
export const getTimestamp = () => new Date().getTime();
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<div className='about'>
|
||||||
|
<h1 className='about__header'>React Redux Starter</h1>
|
||||||
|
<p>This is a react-redux starter kit for Big Human Engineers.</p>
|
||||||
|
<p>It uses Redux to manage application state and Thunk to support asynchronous actions. Flow is used for static type checking which replaces the prop-types package.</p>
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<div>Not Found | 404</div>
|
||||||
|
);
|
|
@ -0,0 +1,44 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import TodoInput from '../components/todo/todo-input';
|
||||||
|
import TodoList from '../components/todo/todo-list';
|
||||||
|
import type { TodoType } from '../types/todo';
|
||||||
|
import checklist from '../assets/images/checklist.svg';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
addTodo: Function,
|
||||||
|
deleteTodo: Function,
|
||||||
|
toggleEdit: Function,
|
||||||
|
todos: Array<TodoType>,
|
||||||
|
updateTodo: Function,
|
||||||
|
cancelUpdateTodo: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const {
|
||||||
|
addTodo,
|
||||||
|
todos,
|
||||||
|
deleteTodo,
|
||||||
|
toggleEdit,
|
||||||
|
updateTodo,
|
||||||
|
cancelUpdateTodo,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='todo'>
|
||||||
|
<div className='todo__heading'>
|
||||||
|
<img src={checklist} alt='Testing File Loader' className='todo__image' />
|
||||||
|
<h1 className='todo__header'>Todo List App</h1>
|
||||||
|
</div>
|
||||||
|
<TodoInput addTodo={addTodo} />
|
||||||
|
<TodoList
|
||||||
|
todos={todos}
|
||||||
|
deleteTodo={deleteTodo}
|
||||||
|
toggleEdit={toggleEdit}
|
||||||
|
updateTodo={updateTodo}
|
||||||
|
cancelUpdateTodo={cancelUpdateTodo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
const path = require('path');
|
||||||
|
const mainWebpack = require('./webpack-main.config');
|
||||||
|
|
||||||
|
const outputPath = path.resolve(__dirname, '../', 'dist');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
output: {
|
||||||
|
path: outputPath,
|
||||||
|
filename: 'bundle.js',
|
||||||
|
chunkFilename: '[name].chunk.js',
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
historyApiFallback: true,
|
||||||
|
host: process.env.HOST || '0.0.0.0',
|
||||||
|
},
|
||||||
|
...mainWebpack,
|
||||||
|
};
|
|
@ -0,0 +1,72 @@
|
||||||
|
const HtmlWebPackPlugin = require('html-webpack-plugin'); // eslint-disable-line
|
||||||
|
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); // eslint-disable-line
|
||||||
|
const autoprefixer = require('autoprefixer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
index: './app/index.js',
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new UglifyJSPlugin({ sourceMap: true }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devtool: 'cheap-module-source-map',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [{
|
||||||
|
loader: 'style-loader',
|
||||||
|
}, {
|
||||||
|
loader: 'css-loader',
|
||||||
|
}, {
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
ident: 'postcss',
|
||||||
|
options: {
|
||||||
|
plugins: () => [autoprefixer({
|
||||||
|
browsers: ['> 1%', 'not ie 11'],
|
||||||
|
})],
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
loader: 'sass-loader',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg)$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]',
|
||||||
|
outputPath: 'assets/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
use: [{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]',
|
||||||
|
outputPath: 'assets/fonts/',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebPackPlugin({
|
||||||
|
template: './public/index.html',
|
||||||
|
filename: './index.html',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
const path = require('path');
|
||||||
|
const mainWebpack = require('./webpack-main.config');
|
||||||
|
|
||||||
|
const outputPath = path.resolve(__dirname, '../', 'dist');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
output: {
|
||||||
|
path: outputPath,
|
||||||
|
filename: 'bundle.js',
|
||||||
|
chunkFilename: '[name].chunk.js',
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
...mainWebpack,
|
||||||
|
};
|
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"name": "zcash-reference-wallet",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Zcash Reference Wallet",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "yarn dev",
|
||||||
|
"dev": "webpack-dev-server --config config/webpack-dev.config.js --mode development --open --hot",
|
||||||
|
"build": "rm -rf dist && webpack --config config/webpack-prod.config.js --mode production --env.NODE_ENV=production",
|
||||||
|
"lint:precommit": "eslint ./app/",
|
||||||
|
"flow:precommit": "glow",
|
||||||
|
"flow:coverage": "flow-coverage-report -t html -i 'app/**/*.js' -x 'dist/*.js' --threshold 70",
|
||||||
|
"flow:report": "yarn flow:coverage && cd ./flow-coverage && open index.html"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "André Neves",
|
||||||
|
"email": "andrerfneves@protonmail.com",
|
||||||
|
"url": "https://andrenev.es"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.0.0",
|
||||||
|
"@babel/core": "^7.0.0",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||||
|
"@babel/preset-env": "^7.0.0",
|
||||||
|
"@babel/preset-flow": "^7.0.0",
|
||||||
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"css-loader": "^1.0.1",
|
||||||
|
"eslint": "^5.8.0",
|
||||||
|
"eslint-config-airbnb": "^17.1.0",
|
||||||
|
"eslint-plugin-flowtype": "^3.2.0",
|
||||||
|
"eslint-plugin-import": "^2.14.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
|
"eslint-plugin-react": "^7.7.0",
|
||||||
|
"file-loader": "^2.0.0",
|
||||||
|
"flow-bin": "^0.85.0",
|
||||||
|
"glow": "^1.2.2",
|
||||||
|
"html-webpack-plugin": "^3.1.0",
|
||||||
|
"node-sass": "^4.8.3",
|
||||||
|
"postcss-loader": "^3.0.0",
|
||||||
|
"pre-commit": "^1.2.2",
|
||||||
|
"redux-logger": "^3.0.6",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
|
"style-loader": "^0.23.1",
|
||||||
|
"webpack": "^4.4.1",
|
||||||
|
"webpack-bundle-analyzer": "^3.0.3",
|
||||||
|
"webpack-cli": "^3.1.2",
|
||||||
|
"webpack-dev-server": "^3.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"autoprefixer": "^9.3.1",
|
||||||
|
"react": "^16.6.0",
|
||||||
|
"react-dom": "^16.6.0",
|
||||||
|
"react-redux": "^5.0.7",
|
||||||
|
"react-router-dom": "^4.2.2",
|
||||||
|
"redux": "^4.0.1",
|
||||||
|
"redux-thunk": "^2.2.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
},
|
||||||
|
"pre-commit": [
|
||||||
|
"lint:precommit",
|
||||||
|
"flow:precommit"
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<!-- Android Manifest -->
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<!-- End Android Manifest -->
|
||||||
|
|
||||||
|
<title>React Redux Boilerplate</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- React App Inject -->
|
||||||
|
<div id="root"></div>
|
||||||
|
<!-- End React App Inject -->
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"short_name": "React",
|
||||||
|
"name": "React Redux Starter",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
Loading…
Reference in New Issue