initial commit
This commit is contained in:
38
.babelrc.js
Normal file
38
.babelrc.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const Environment = {
|
||||||
|
development: "development",
|
||||||
|
production: "production",
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (api) => {
|
||||||
|
const presets = [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
useBuiltIns: "usage",
|
||||||
|
debug: false,
|
||||||
|
corejs: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@babel/preset-react",
|
||||||
|
];
|
||||||
|
const plugins = [
|
||||||
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
|
];
|
||||||
|
|
||||||
|
const environment = api.cache.using(() => {
|
||||||
|
const env = process.env.NODE_ENV ?? Environment.production;
|
||||||
|
return env;
|
||||||
|
});
|
||||||
|
console.log(`Current environment: '${environment}'`);
|
||||||
|
switch (environment) {
|
||||||
|
case Environment.development:
|
||||||
|
plugins.push("react-refresh/babel");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
presets,
|
||||||
|
plugins,
|
||||||
|
};
|
||||||
|
};
|
||||||
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
> 1%
|
||||||
|
last 4 versions
|
||||||
|
not ie < 9
|
||||||
|
not dead
|
||||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
2
.env.development
Normal file
2
.env.development
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
API_PATH=http://localhost:5000
|
||||||
|
PORT=5001
|
||||||
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
API_PATH=http://localhost:5000
|
||||||
|
PORT=5001
|
||||||
1
.env.nonprod
Normal file
1
.env.nonprod
Normal file
@@ -0,0 +1 @@
|
|||||||
|
API_PATH=
|
||||||
1
.env.prelive
Normal file
1
.env.prelive
Normal file
@@ -0,0 +1 @@
|
|||||||
|
API_PATH=
|
||||||
1
.env.production
Normal file
1
.env.production
Normal file
@@ -0,0 +1 @@
|
|||||||
|
API_PATH=
|
||||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
123
.eslintrc.js
Normal file
123
.eslintrc.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
plugins: [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"react",
|
||||||
|
"react-hooks",
|
||||||
|
"jsx-a11y",
|
||||||
|
"prettier",
|
||||||
|
"import",
|
||||||
|
"simple-import-sort",
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"airbnb-typescript",
|
||||||
|
"plugin:import/typescript",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaVersion: 2021,
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
|
"react/jsx-filename-extension": ["warn", { extensions: [".tsx"] }],
|
||||||
|
"react/prop-types": 0,
|
||||||
|
indent: 0,
|
||||||
|
"@typescript-eslint/indent": 0, // prettier is used instead
|
||||||
|
quotes: 0,
|
||||||
|
"@typescript-eslint/quotes": ["error", "double"],
|
||||||
|
"import/no-unresolved": "error",
|
||||||
|
"react/jsx-props-no-spreading": 0,
|
||||||
|
"react/require-default-props": 0,
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: ["variable", "function"],
|
||||||
|
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||||
|
leadingUnderscore: "allowSingleOrDouble",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "variable",
|
||||||
|
types: ["boolean"],
|
||||||
|
format: ["camelCase", "StrictPascalCase"],
|
||||||
|
prefix: ["is", "should", "has", "can", "did", "will"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
vars: "all",
|
||||||
|
args: "after-used",
|
||||||
|
argsIgnorePattern: "^_.*$",
|
||||||
|
varsIgnorePattern: "^_.*$",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
patterns: [
|
||||||
|
"@material-ui/*/*/*",
|
||||||
|
"!@material-ui/core/test-utils/*",
|
||||||
|
"!@material-ui/pickers/constants/*",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"react/prefer-stateless-function": 2,
|
||||||
|
"import/prefer-default-export": 0,
|
||||||
|
"import/no-extraneous-dependencies": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
devDependencies: ["**/*.test.js", "**/*.spec.js", "./webpack/**/*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// slow rules that aren't relevant
|
||||||
|
"@typescript-eslint/no-implied-eval": 0,
|
||||||
|
"react/jsx-no-bind": 0,
|
||||||
|
"import/order": 0,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
"import/resolver": {
|
||||||
|
typescript: {
|
||||||
|
alwaysTryTypes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"import/parsers": {
|
||||||
|
"@typescript-eslint/parser": [".ts", ".tsx"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.tsx"],
|
||||||
|
rules: {
|
||||||
|
"simple-import-sort/imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
["^react", "^@?\\w"],
|
||||||
|
["^(@|@company|@ui|components|utils|config|vendored-lib)(/.*|$)"],
|
||||||
|
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
|
||||||
|
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
|
||||||
|
["^.+\\.s?css$"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
dist
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
*.log
|
||||||
|
.swp
|
||||||
|
|
||||||
|
.env
|
||||||
6
.postcssrc.js
Normal file
6
.postcssrc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
"postcss-preset-env",
|
||||||
|
"autoprefixer",
|
||||||
|
],
|
||||||
|
};
|
||||||
36
.prettierrc.js
Normal file
36
.prettierrc.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
module.exports = {
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "all",
|
||||||
|
semi: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
endOfLine: "lf",
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.ts"],
|
||||||
|
options: {
|
||||||
|
parser: "typescript",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.tsx"],
|
||||||
|
options: {
|
||||||
|
parser: "typescript",
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.scss"],
|
||||||
|
options: {
|
||||||
|
parser: "scss",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["*.json"],
|
||||||
|
options: {
|
||||||
|
parser: "json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
21
deploy/demo/Dockerfile
Normal file
21
deploy/demo/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
### STAGE 1: Build ###
|
||||||
|
|
||||||
|
# We label our stage as ‘builder’
|
||||||
|
FROM node:16 as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
RUN yarn build:nonprod
|
||||||
|
|
||||||
|
# runner container
|
||||||
|
# - nginx, to serve static built React app
|
||||||
|
|
||||||
|
# nginx state for serving content
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY deploy/nonprod/nginx/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
17
deploy/demo/nginx/nginx.conf
Normal file
17
deploy/demo/nginx/nginx.conf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
deploy/prelive/Dockerfile
Normal file
21
deploy/prelive/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
### STAGE 1: Build ###
|
||||||
|
|
||||||
|
# We label our stage as ‘builder’
|
||||||
|
FROM node:16 as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
RUN yarn build:prelive
|
||||||
|
|
||||||
|
# runner container
|
||||||
|
# - nginx, to serve static built React app
|
||||||
|
|
||||||
|
# nginx state for serving content
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY deploy/prelive/nginx/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
17
deploy/prelive/nginx/nginx.conf
Normal file
17
deploy/prelive/nginx/nginx.conf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
deploy/production/Dockerfile
Normal file
21
deploy/production/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
### STAGE 1: Build ###
|
||||||
|
|
||||||
|
# We label our stage as ‘builder’
|
||||||
|
FROM node:16 as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
RUN yarn build:live
|
||||||
|
|
||||||
|
# runner container
|
||||||
|
# - nginx, to serve static built React app
|
||||||
|
|
||||||
|
# nginx state for serving content
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY deploy/production/nginx/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
17
deploy/production/nginx/nginx.conf
Normal file
17
deploy/production/nginx/nginx.conf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
server {
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
107
package.json
Executable file
107
package.json
Executable file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"name": "react-template-esbuild",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"main": "webpack.config.babel.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16",
|
||||||
|
"yarn": "^1.22.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prebuild": "yarn clean",
|
||||||
|
"bundle": "webpack --config webpack.config.babel.js",
|
||||||
|
"build": "cross-env NODE_ENV=production yarn bundle",
|
||||||
|
"build:nonprod": "cross-env ENV_CONFIGURATION=nonprod yarn build",
|
||||||
|
"build:prelive": "cross-env ENV_CONFIGURATION=prelive yarn build",
|
||||||
|
"build:live": "cross-env ENV_CONFIGURATION=production yarn build",
|
||||||
|
"start": "cross-env WEBPACK_IS_DEV_SERVER=true NODE_ENV=development webpack serve --config webpack.config.babel.js",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"profile": "cross-env NODE_ENV=production webpack --profile --json --config webpack.config.babel.js > ./dist/profile.json",
|
||||||
|
"perf:size": "yarn profile && webpack-bundle-analyzer ./dist/profile.json",
|
||||||
|
"perf:lint": "cross-env TIMING=1 yarn lint",
|
||||||
|
"perf:build": "cross-env MEASURE=1 yarn build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@date-io/date-fns": "1.3.13",
|
||||||
|
"@hookform/resolvers": "2.8.3",
|
||||||
|
"@types/i18n-js": "3.8.2",
|
||||||
|
"@types/js-cookie": "3.0.0",
|
||||||
|
"@types/react": "17.0.34",
|
||||||
|
"@types/react-dom": "17.0.11",
|
||||||
|
"@types/react-router-dom": "5.3.2",
|
||||||
|
"@types/sanitize-html": "2.5.0",
|
||||||
|
"classnames": "2.3.1",
|
||||||
|
"date-fns": "2.25.0",
|
||||||
|
"history": "5.1.0",
|
||||||
|
"html-react-parser": "1.4.0",
|
||||||
|
"i18n-js": "3.8.0",
|
||||||
|
"react": "17.0.2",
|
||||||
|
"react-dom": "17.0.2",
|
||||||
|
"react-error-boundary": "3.1.4",
|
||||||
|
"react-hook-form": "7.18.0",
|
||||||
|
"react-hot-loader": "4.13.0",
|
||||||
|
"react-loader-spinner": "5.1.4",
|
||||||
|
"react-router-dom": "next",
|
||||||
|
"react-skeleton-css": "1.1.0",
|
||||||
|
"sanitize-html": "2.5.3",
|
||||||
|
"swr": "1.0.1",
|
||||||
|
"use-async-effect": "2.2.3",
|
||||||
|
"yup": "0.32.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "7.16.0",
|
||||||
|
"@babel/eslint-parser": "7.16.3",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
|
"@babel/preset-env": "7.16.0",
|
||||||
|
"@babel/preset-react": "7.16.0",
|
||||||
|
"@babel/register": "7.16.0",
|
||||||
|
"@pmmmwh/react-refresh-webpack-plugin": "0.5.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "5.2.0",
|
||||||
|
"@typescript-eslint/parser": "5.2.0",
|
||||||
|
"autoprefixer": "10.4.0",
|
||||||
|
"babel-plugin-import": "1.13.3",
|
||||||
|
"copy-webpack-plugin": "9.0.1",
|
||||||
|
"core-js": "3.19.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
|
"css-loader": "6.5.1",
|
||||||
|
"dotenv-webpack": "7.0.3",
|
||||||
|
"esbuild-loader": "2.16.0",
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-airbnb-base": "14.2.1",
|
||||||
|
"eslint-config-airbnb-typescript": "14.0.1",
|
||||||
|
"eslint-config-prettier": "8.3.0",
|
||||||
|
"eslint-import-resolver-alias": "1.1.2",
|
||||||
|
"eslint-import-resolver-typescript": "2.5.0",
|
||||||
|
"eslint-plugin-import": "2.25.3",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||||
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
|
"eslint-plugin-react": "7.27.0",
|
||||||
|
"eslint-plugin-react-hooks": "4.3.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||||
|
"eslint-webpack-plugin": "3.1.0",
|
||||||
|
"fast-sass-loader": "2.0.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "6.4.0",
|
||||||
|
"html-loader": "3.0.1",
|
||||||
|
"html-webpack-plugin": "5.5.0",
|
||||||
|
"mini-css-extract-plugin": "2.4.4",
|
||||||
|
"path": "0.12.7",
|
||||||
|
"postcss": "8.3.11",
|
||||||
|
"postcss-loader": "6.2.0",
|
||||||
|
"postcss-preset-env": "6.7.0",
|
||||||
|
"prettier": "2.4.1",
|
||||||
|
"react-refresh": "0.11.0",
|
||||||
|
"rimraf": "3.0.2",
|
||||||
|
"sass": "1.43.4",
|
||||||
|
"speed-measure-webpack-plugin": "1.5.0",
|
||||||
|
"style-loader": "3.3.1",
|
||||||
|
"tsconfig-paths-webpack-plugin": "3.5.1",
|
||||||
|
"typescript": "4.4.4",
|
||||||
|
"webpack": "5.63.0",
|
||||||
|
"webpack-bundle-analyzer": "4.5.0",
|
||||||
|
"webpack-cli": "4.9.1",
|
||||||
|
"webpack-dev-server": "4.4.0",
|
||||||
|
"webpack-merge": "5.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/@types/declarations.d.ts
vendored
Normal file
4
src/@types/declarations.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.scss" {
|
||||||
|
const content: { [className: string]: string };
|
||||||
|
export = content;
|
||||||
|
}
|
||||||
14
src/api/types.ts
Normal file
14
src/api/types.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export enum ContentType {
|
||||||
|
APPLICATION_JSON = "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RequestMethod {
|
||||||
|
GET = "GET",
|
||||||
|
POST = "POST",
|
||||||
|
PUT = "PUT",
|
||||||
|
DELETE = "DELETE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RequestHeader {
|
||||||
|
CONTENT_TYPE = "Content-Type",
|
||||||
|
}
|
||||||
40
src/api/util.ts
Normal file
40
src/api/util.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { ContentType, RequestHeader, RequestMethod } from "./types";
|
||||||
|
|
||||||
|
const getAuthenticationHeaders = () => ({});
|
||||||
|
|
||||||
|
const apiPath = process.env.API_PATH;
|
||||||
|
const getApiPath = (api: string): string => `${apiPath}/lg/${api}`;
|
||||||
|
|
||||||
|
const swrFetcher = (path: string, init?: RequestInit): Promise<Response> =>
|
||||||
|
fetch(getApiPath(path), {
|
||||||
|
...init,
|
||||||
|
headers: {
|
||||||
|
[RequestHeader.CONTENT_TYPE]: ContentType.APPLICATION_JSON,
|
||||||
|
...getAuthenticationHeaders(),
|
||||||
|
},
|
||||||
|
}).then(async (res) => {
|
||||||
|
let text = await res.json();
|
||||||
|
if (typeof text === "string") {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/quotes
|
||||||
|
text = text.replace(`b\"`, "").replace(`\\n\"`, "");
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
|
||||||
|
const post = (path: string): Promise<Response> =>
|
||||||
|
fetch(getApiPath(path), {
|
||||||
|
method: RequestMethod.POST,
|
||||||
|
headers: {
|
||||||
|
[RequestHeader.CONTENT_TYPE]: ContentType.APPLICATION_JSON,
|
||||||
|
...getAuthenticationHeaders(),
|
||||||
|
},
|
||||||
|
}).then(async (res) => {
|
||||||
|
let text = await res.json();
|
||||||
|
if (typeof text === "string") {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/quotes
|
||||||
|
text = text.replace(`b\"`, "").replace(`\\n\"`, "");
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
|
||||||
|
export { post, swrFetcher };
|
||||||
BIN
src/assets/favicon.ico
Normal file
BIN
src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
0
src/assets/images/.gitkeep
Normal file
0
src/assets/images/.gitkeep
Normal file
0
src/assets/svg/.gitkeep
Normal file
0
src/assets/svg/.gitkeep
Normal file
34
src/common/ComponentErrorBoundary.tsx
Normal file
34
src/common/ComponentErrorBoundary.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { ErrorBoundary, FallbackProps } from "react-error-boundary";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
const ErrorFallback: React.FC<FallbackProps> = ({
|
||||||
|
error,
|
||||||
|
resetErrorBoundary,
|
||||||
|
}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const [initialLocation] = useState(location.pathname);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.pathname !== initialLocation || !error) {
|
||||||
|
resetErrorBoundary();
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Error occurred", error);
|
||||||
|
}
|
||||||
|
}, [error, location.pathname]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Something went wrong!</h2>
|
||||||
|
<h4>Please try again later.</h4>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ComponentErrorBoundary: React.FC = ({ children }) => (
|
||||||
|
<ErrorBoundary FallbackComponent={ErrorFallback}>{children}</ErrorBoundary>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ComponentErrorBoundary;
|
||||||
23
src/components/App.tsx
Normal file
23
src/components/App.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { SWRConfig } from "swr";
|
||||||
|
|
||||||
|
import { swrFetcher } from "@/api/util";
|
||||||
|
|
||||||
|
import AppRouter from "./AppRouter";
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<BrowserRouter>
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
fetcher: swrFetcher,
|
||||||
|
suspense: true,
|
||||||
|
dedupingInterval: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppRouter />
|
||||||
|
</SWRConfig>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
||||||
34
src/components/AppRouter.tsx
Normal file
34
src/components/AppRouter.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
|
import LayoutWrapper from "@/components/LayoutWrapper";
|
||||||
|
import routes from "@/constants/routes";
|
||||||
|
|
||||||
|
import PrivateRoute from "./PrivateRoute";
|
||||||
|
|
||||||
|
const AppRouter: React.FC = () => (
|
||||||
|
<Routes>
|
||||||
|
{routes.public.map((route) => (
|
||||||
|
<Route key={route.path} path={route.path} element={<route.Component />} />
|
||||||
|
))}
|
||||||
|
<Route path="/" element={<LayoutWrapper />}>
|
||||||
|
{routes.private.map(({ path, Component, subroutes, redirect }) => (
|
||||||
|
<Route
|
||||||
|
key={path}
|
||||||
|
path={path}
|
||||||
|
element={<PrivateRoute Component={Component} redirect={redirect} />}
|
||||||
|
>
|
||||||
|
{subroutes?.map((subroute) => (
|
||||||
|
<Route
|
||||||
|
key={subroute.path}
|
||||||
|
path={subroute.path}
|
||||||
|
element={<PrivateRoute Component={subroute.Component} />}
|
||||||
|
/>
|
||||||
|
)) || null}
|
||||||
|
</Route>
|
||||||
|
))}
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AppRouter;
|
||||||
15
src/components/LayoutWrapper.tsx
Normal file
15
src/components/LayoutWrapper.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useOutlet } from "react-router-dom";
|
||||||
|
|
||||||
|
import ComponentErrorBoundary from "@/common/ComponentErrorBoundary";
|
||||||
|
|
||||||
|
const LayoutWrapper: React.FC = () => {
|
||||||
|
const outlet = useOutlet();
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<ComponentErrorBoundary>{outlet}</ComponentErrorBoundary>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LayoutWrapper;
|
||||||
25
src/components/PrivateRoute.tsx
Normal file
25
src/components/PrivateRoute.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const PrivateRoute: React.FC<{
|
||||||
|
Component: React.FC;
|
||||||
|
redirect?: string;
|
||||||
|
}> = ({ Component, redirect }) => {
|
||||||
|
const isAuthenticated = true;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
// navigate("/login");
|
||||||
|
}
|
||||||
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
if (redirect) {
|
||||||
|
return <Navigate to={redirect} />;
|
||||||
|
}
|
||||||
|
return <Component />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrivateRoute;
|
||||||
5
src/constants/navigation.ts
Normal file
5
src/constants/navigation.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const NavigationPath = {
|
||||||
|
Home: "/",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavigationPath;
|
||||||
29
src/constants/routes.ts
Normal file
29
src/constants/routes.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Homepage from "@/pages/home/Homepage";
|
||||||
|
|
||||||
|
import NavigationPath from "./navigation";
|
||||||
|
|
||||||
|
interface NavigationRoute {
|
||||||
|
path: string;
|
||||||
|
Component: React.FC;
|
||||||
|
subroutes?: NavigationRoute[];
|
||||||
|
redirect?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavigationRoutes {
|
||||||
|
public: NavigationRoute[];
|
||||||
|
private: NavigationRoute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes: NavigationRoutes = {
|
||||||
|
public: [
|
||||||
|
{
|
||||||
|
path: NavigationPath.Home,
|
||||||
|
Component: Homepage,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
private: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default routes;
|
||||||
14
src/index.html
Normal file
14
src/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="theme-color" content="#FFF">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="version" content="1.0">
|
||||||
|
<title>REPLACE_TITLE</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="./assets/favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
src/index.tsx
Normal file
10
src/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import App from "./components/App";
|
||||||
|
|
||||||
|
import "react-skeleton-css/styles/skeleton.2.0.4.css";
|
||||||
|
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
||||||
|
import "./styles/styles.scss";
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById("root"));
|
||||||
1
src/locale/en.json
Normal file
1
src/locale/en.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
src/locale/et.json
Normal file
1
src/locale/et.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
203
src/pages/home/Homepage.tsx
Normal file
203
src/pages/home/Homepage.tsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import React, { Suspense } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
import { post } from "@/api/util";
|
||||||
|
|
||||||
|
import LoadingPage from "./LoadingPage";
|
||||||
|
import {
|
||||||
|
IAudioStatus,
|
||||||
|
IConfigs,
|
||||||
|
ISoftwareInfo,
|
||||||
|
ISystemInfo,
|
||||||
|
ITVState,
|
||||||
|
} from "./webos-types";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
const Wrapper: React.FC = ({ children }) => (
|
||||||
|
<div className="container">{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const toTitleCase = (value?: string) =>
|
||||||
|
value
|
||||||
|
?.toLowerCase()
|
||||||
|
.replaceAll("_", " ")
|
||||||
|
.replace(/[^-’\s]+/g, (match) =>
|
||||||
|
match.replace(/^./, (first) => first.toUpperCase()),
|
||||||
|
) || "";
|
||||||
|
|
||||||
|
const turnScreenOn = async () => {
|
||||||
|
await post("screen-on");
|
||||||
|
};
|
||||||
|
const turnScreenOff = async () => {
|
||||||
|
await post("screen-off");
|
||||||
|
};
|
||||||
|
|
||||||
|
const mute = async () => {
|
||||||
|
await post("mute");
|
||||||
|
};
|
||||||
|
const unmute = async () => {
|
||||||
|
await post("unmute");
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormattedJson = function <T extends object | string>(
|
||||||
|
badPythonJson?: string,
|
||||||
|
): T | null {
|
||||||
|
if (!badPythonJson) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const fixed = badPythonJson
|
||||||
|
// eslint-disable-next-line @typescript-eslint/quotes
|
||||||
|
// .replaceAll("'", '"')
|
||||||
|
// .replaceAll("\n", "")
|
||||||
|
.replaceAll(" True", " true")
|
||||||
|
.replaceAll(" False", " false");
|
||||||
|
console.log("JSON parse values", {
|
||||||
|
fixed,
|
||||||
|
badPythonJson,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { returnValue, subscribed, ...result } = JSON.parse(fixed);
|
||||||
|
return Object.keys(result).length === 1
|
||||||
|
? result[Object.keys(result)[0]]
|
||||||
|
: result;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Bad response", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAudioScenario = (scenario?: string) => {
|
||||||
|
switch (scenario) {
|
||||||
|
case "mastervolume_ext_speaker_bt":
|
||||||
|
return "Bluetooth";
|
||||||
|
case "mastervolume_tv_speaker":
|
||||||
|
return "Internal TV speaker";
|
||||||
|
case "mastervolume_headphone":
|
||||||
|
return "Wired headphones";
|
||||||
|
default:
|
||||||
|
return scenario;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAX_TABLES_NESTING = 3;
|
||||||
|
|
||||||
|
const ObjectTable: React.FC<{ values?: object | null; nesting?: number }> = ({
|
||||||
|
values,
|
||||||
|
nesting = 0,
|
||||||
|
}) => {
|
||||||
|
if (!values || nesting > MAX_TABLES_NESTING) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="small-table-wrapper">
|
||||||
|
<table className={classNames(["u-full-width", "small-table"])}>
|
||||||
|
<tbody>
|
||||||
|
{Object.entries(values).map(([key, value]) => (
|
||||||
|
<tr key={key}>
|
||||||
|
<td>{key.includes(".") ? key : toTitleCase(key)}</td>
|
||||||
|
<td>
|
||||||
|
{typeof value === "object" ? (
|
||||||
|
<ObjectTable values={value} nesting={nesting + 1} />
|
||||||
|
) : (
|
||||||
|
String(value)
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HomepageContent: React.FC = () => {
|
||||||
|
const { data: tvState } = useSWR<ITVState>("get-state");
|
||||||
|
const { data: audioStatus } = useSWR<IAudioStatus>("get-audio-status");
|
||||||
|
const { data: softwareInfo } = useSWR<ISoftwareInfo>("get-software-info");
|
||||||
|
const { data: systemInfo } = useSWR<ISystemInfo>("get-system-info");
|
||||||
|
const { data: configs } = useSWR<IConfigs>("get-configs");
|
||||||
|
const { data: apps } = useSWR<IConfigs>("get-apps");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(["container", "homepage-content"])}>
|
||||||
|
<div className="row">
|
||||||
|
<div className={classNames(["row", "buttons-row"])}>
|
||||||
|
<button className="button-primary" onClick={turnScreenOn}>
|
||||||
|
TV screen on
|
||||||
|
</button>
|
||||||
|
<button className="button-primary" onClick={turnScreenOff}>
|
||||||
|
TV screen off
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={classNames(["row", "buttons-row"])}>
|
||||||
|
{(!audioStatus || !audioStatus.mute) && (
|
||||||
|
<button className="button-primary" onClick={mute}>
|
||||||
|
Mute
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{(!audioStatus || audioStatus.mute) && (
|
||||||
|
<button className="button-primary" onClick={unmute}>
|
||||||
|
Unmute
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<table className={classNames(["u-full-width", "properties-table"])}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Key</td>
|
||||||
|
<td>Value</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>TV state</td>
|
||||||
|
<td>{tvState?.state}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Audio status</td>
|
||||||
|
<td>
|
||||||
|
{
|
||||||
|
<ObjectTable
|
||||||
|
values={{
|
||||||
|
...audioStatus,
|
||||||
|
scenario: getAudioScenario(audioStatus?.scenario),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Software info</td>
|
||||||
|
<td>{<ObjectTable values={softwareInfo} />}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>System info</td>
|
||||||
|
<td>{<ObjectTable values={systemInfo} />}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Configs</td>
|
||||||
|
<td>{<ObjectTable values={configs} />}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Apps</td>
|
||||||
|
<td>{<ObjectTable values={apps} />}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Homepage: React.FC = () => (
|
||||||
|
<Suspense fallback={<LoadingPage />}>
|
||||||
|
<HomepageContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Homepage;
|
||||||
18
src/pages/home/LoadingPage.tsx
Normal file
18
src/pages/home/LoadingPage.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Triangle } from "react-loader-spinner";
|
||||||
|
|
||||||
|
const LoadingPage: React.FC = () => (
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
<div className="eight columns">
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", justifyContent: "center", marginTop: 24 }}
|
||||||
|
>
|
||||||
|
<Triangle height="100" width="100" color="grey" ariaLabel="loading" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LoadingPage;
|
||||||
34
src/pages/home/styles.css
Normal file
34
src/pages/home/styles.css
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
.properties-table td:first-child {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-row {
|
||||||
|
gap: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homepage-content {
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||||
|
}
|
||||||
|
.container > .row {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead td {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-table tbody td:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.small-table-wrapper {
|
||||||
|
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
170
src/pages/home/webos-types.ts
Normal file
170
src/pages/home/webos-types.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
interface Configs {
|
||||||
|
"tv.model.supportMovingSpeaker": boolean;
|
||||||
|
"tv.model.tunerSleepMode": boolean;
|
||||||
|
"tv.model.supportMotionDetectSensor": boolean;
|
||||||
|
"tv.model.micomType": string;
|
||||||
|
"tv.model.bplCurrentType": string;
|
||||||
|
"tv.model.twinTV": boolean;
|
||||||
|
"tv.model.audioIndex": number;
|
||||||
|
"tv.model.supportNewPowerSave": boolean;
|
||||||
|
"tv.model.supportAutoStoreMode": boolean;
|
||||||
|
"tv.model.supportMhl": boolean;
|
||||||
|
"tv.model.motionProMode": string;
|
||||||
|
"tv.model.adaptDimDefault": string;
|
||||||
|
"tv.model.supportTvLink": boolean;
|
||||||
|
"tv.model.supportC2": boolean;
|
||||||
|
"tv.model.supportAppStore": boolean;
|
||||||
|
"tv.model.hwSettingGroup": string;
|
||||||
|
"tv.model.analogDemodTypeSub": string;
|
||||||
|
"tv.model.supportNetflix": boolean;
|
||||||
|
"tv.model.supportIsf": boolean;
|
||||||
|
"tv.model.supportZram": boolean;
|
||||||
|
"tv.model.supportWarmStandbySoc": boolean;
|
||||||
|
"tv.model.displayResolution": number;
|
||||||
|
"tv.model.supportScreenRollerMotor": boolean;
|
||||||
|
"tv.model.supportDvrReady": boolean;
|
||||||
|
"tv.model.maxInputNumberTv": number;
|
||||||
|
"tv.model.transitionOpt": string;
|
||||||
|
"tv.model.panelLedBarType": string;
|
||||||
|
"tv.model.motionRemoconType": string;
|
||||||
|
"tv.model.supportMarlinAp": boolean;
|
||||||
|
"tv.model.panelGamutType": string;
|
||||||
|
"tv.model.moduleBackLightType": string;
|
||||||
|
"tv.model.supportEsnAp": boolean;
|
||||||
|
"tv.model.supportPip": boolean;
|
||||||
|
"tv.model.analogDemodType": string;
|
||||||
|
"tv.model.sPqlConfig_ePipType_NOTMAPPED": number;
|
||||||
|
"tv.model.cameraType": string;
|
||||||
|
"tv.model.digitalDemod": string;
|
||||||
|
"tv.model.soundModeType": string;
|
||||||
|
"tv.model.eventBoardType": string;
|
||||||
|
"tv.model.displayLvdsType": string;
|
||||||
|
"tv.model.videoIndex": number;
|
||||||
|
"tv.model.zramSize": number;
|
||||||
|
"tv.model.pwm2Duty": string;
|
||||||
|
"tv.model.supportEmmcRecord": boolean;
|
||||||
|
"tv.model.supportLocalDimming": boolean;
|
||||||
|
"tv.model.audioPwVolt": string;
|
||||||
|
"tv.model.whiteBalanceNpoint": number;
|
||||||
|
"tv.model.supportDolbyVisionHDR": boolean;
|
||||||
|
"tv.model.supportLocalDimmingOsdMenu": boolean;
|
||||||
|
"tv.model.tconPmicType": string;
|
||||||
|
"tv.model.supportEpa": boolean;
|
||||||
|
"tv.model.supportWol": boolean;
|
||||||
|
"tv.model.adjustVCOM": boolean;
|
||||||
|
"tv.model.supportDimming": boolean;
|
||||||
|
"tv.model.supportWidevineAp": boolean;
|
||||||
|
"tv.model.supportMirrorMode": boolean;
|
||||||
|
"tv.model.supportGameMode": boolean;
|
||||||
|
"tv.model.supportOledTconOrbit": boolean;
|
||||||
|
"tv.model.edidType": string;
|
||||||
|
"tv.model.sPqlConfig_eDisplayModule_NOTMAPPED": number;
|
||||||
|
"tv.model.pipType": string;
|
||||||
|
"tv.model.magicSpaceSound": boolean;
|
||||||
|
"tv.model.maxInputNumberUsb": number;
|
||||||
|
"tv.model.digitalEye": string;
|
||||||
|
"tv.model.motionEyeCareIndex": string;
|
||||||
|
"tv.model.supportInternalAdcSoc": boolean;
|
||||||
|
"tv.model.toolType": string;
|
||||||
|
"tv.model.modeSelect": string;
|
||||||
|
"tv.model.reserved": boolean;
|
||||||
|
"tv.model.logoLight": boolean;
|
||||||
|
"tv.model.supportThx": boolean;
|
||||||
|
"tv.model.pwmFreqType": string;
|
||||||
|
"tv.model.supportCiEcpAp": boolean;
|
||||||
|
"tv.model.lvdsBits": string;
|
||||||
|
"tv.model.sdpProductionMode": boolean;
|
||||||
|
"tv.model.supportOledJB": boolean;
|
||||||
|
"tv.model.supportHDR": boolean;
|
||||||
|
"tv.model.supportWiSA": boolean;
|
||||||
|
"tv.model.otaId": string;
|
||||||
|
"tv.model.hdmiSwitchType": string;
|
||||||
|
"tv.model.instantBoot": string;
|
||||||
|
"tv.model.usb1": string;
|
||||||
|
"tv.model.usb2": string;
|
||||||
|
"tv.model.wifiType": string;
|
||||||
|
"tv.model.supportAtvAvDvr": boolean;
|
||||||
|
"tv.model.usb3": string;
|
||||||
|
"tv.model.satDigitalDemod": string;
|
||||||
|
"tv.model.scanningBl": boolean;
|
||||||
|
"tv.model.supportMacAp": boolean;
|
||||||
|
"tv.model.audioSpkWatt": string;
|
||||||
|
"tv.model.osdResolution": number;
|
||||||
|
"tv.model.continentIndx": number;
|
||||||
|
"tv.model.supportTemp8K": boolean;
|
||||||
|
"tv.model.maxTvPathNumber": number;
|
||||||
|
"tv.model.maxPwmType": string;
|
||||||
|
"tv.model.supportDtcpKey": boolean;
|
||||||
|
"tv.model.supportHeadPhone": boolean;
|
||||||
|
"tv.model.moduleInchType": string;
|
||||||
|
"tv.model.languageCountrySel": string;
|
||||||
|
"tv.model.support21YLcon": boolean;
|
||||||
|
"tv.model.3dOrderType": string;
|
||||||
|
"tv.model.supportHdmi2ExtEdid": boolean;
|
||||||
|
"tv.model.supportExternalSpeaker": boolean;
|
||||||
|
"tv.model.supportLuminanceUp": boolean;
|
||||||
|
"tv.model.supportDualView": boolean;
|
||||||
|
"tv.model.defaultStdBacklight": number;
|
||||||
|
"tv.model.ampChipType": string;
|
||||||
|
"tv.model.supportAudioLineOut": boolean;
|
||||||
|
"tv.model.pdpModuleId": number;
|
||||||
|
"tv.model.eModeSelect": boolean;
|
||||||
|
"tv.model.supportFanControl": boolean;
|
||||||
|
"tv.model.supportBNO": boolean;
|
||||||
|
"tv.model.localKeyType": string;
|
||||||
|
"tv.model.hardwareVersion": number;
|
||||||
|
"tv.model.supportPsuPowerBoard": boolean;
|
||||||
|
"tv.model.instantBootDefaultValue": boolean;
|
||||||
|
"tv.model.cellType": string;
|
||||||
|
"tv.model.sysType": string;
|
||||||
|
"tv.model.eyeCurveDerivation": string;
|
||||||
|
"tv.model.moduleMakerType": string;
|
||||||
|
"tv.model.digitalDemodSub": string;
|
||||||
|
"tv.model.supportCiAp": boolean;
|
||||||
|
"tv.model.supportCompRcaCommon": boolean;
|
||||||
|
"tv.model.supportOledOffRsQuickStart": boolean;
|
||||||
|
"tv.model.displayType": string;
|
||||||
|
"tv.model.3dSupportType": string;
|
||||||
|
"tv.model.maxInputNumberAutoAv": number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConfigs {
|
||||||
|
subscribed: boolean;
|
||||||
|
configs: Configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISystemInfo {
|
||||||
|
receiverType: string;
|
||||||
|
modelName: string;
|
||||||
|
programMode: string;
|
||||||
|
features: {
|
||||||
|
"3d": boolean;
|
||||||
|
dvr: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISoftwareInfo {
|
||||||
|
product_name: string;
|
||||||
|
model_name: string;
|
||||||
|
sw_type: string;
|
||||||
|
major_ver: string;
|
||||||
|
minor_ver: string;
|
||||||
|
country: string;
|
||||||
|
country_group: string;
|
||||||
|
device_id: string;
|
||||||
|
auth_flag: string;
|
||||||
|
ignore_disable: string;
|
||||||
|
eco_info: string;
|
||||||
|
config_key: string;
|
||||||
|
language_code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAudioStatus {
|
||||||
|
scenario: string;
|
||||||
|
volume: number;
|
||||||
|
mute: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITVState {
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
74
src/styles/styles.scss
Normal file
74
src/styles/styles.scss
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;1,400;1,500&display=swap');
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
background: #FBFCFD;
|
||||||
|
color: #000;
|
||||||
|
word-break: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
button > span {
|
||||||
|
letter-spacing: 0.6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hides chrome input color
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus textarea:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill:hover textarea:-webkit-autofill:focus,
|
||||||
|
select:-webkit-autofill,
|
||||||
|
select:-webkit-autofill:hover,
|
||||||
|
select:-webkit-autofill:focus {
|
||||||
|
-webkit-box-shadow: 0 0 0px 1000px #ffffff inset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #E6E8EC;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #C3C4C6;
|
||||||
|
&:hover {
|
||||||
|
background: #75787A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/theme/index.ts
Normal file
37
src/theme/index.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const theme = {
|
||||||
|
color: {
|
||||||
|
white: "#FFFFFF",
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
s: 4,
|
||||||
|
m: 8,
|
||||||
|
l: 16,
|
||||||
|
},
|
||||||
|
font: {
|
||||||
|
size: {
|
||||||
|
xxs: 12,
|
||||||
|
xs: 14,
|
||||||
|
s: 16,
|
||||||
|
m: 18,
|
||||||
|
l: 24,
|
||||||
|
xl: 36,
|
||||||
|
xxl: 48,
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
s: 1.2,
|
||||||
|
m: 1.4,
|
||||||
|
l: 1.7,
|
||||||
|
},
|
||||||
|
letterSpacing: {
|
||||||
|
s: "0.15px",
|
||||||
|
m: "0.32px",
|
||||||
|
l: "0.4px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sizes: {
|
||||||
|
maxWidthValue: 1440,
|
||||||
|
maxWidth: "@media screen and (min-width: 1440px)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
||||||
28
src/theme/scrollbar.ts
Normal file
28
src/theme/scrollbar.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const scrollbarStyles = {
|
||||||
|
"&::-webkit-scrollbar": {
|
||||||
|
width: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
background: "#E6E8EC",
|
||||||
|
},
|
||||||
|
"&::-webkit-scrollbar-thumb": {
|
||||||
|
borderRadius: 4,
|
||||||
|
background: "#C3C4C6",
|
||||||
|
"&:hover": {
|
||||||
|
background: "#75787A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const horizontalScrollBar = {
|
||||||
|
"&::-webkit-scrollbar": {
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
background: "#E6E8EC",
|
||||||
|
},
|
||||||
|
"&::-webkit-scrollbar-thumb": {
|
||||||
|
backgroundColor: "#C3C4C6",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default scrollbarStyles;
|
||||||
|
export { horizontalScrollBar };
|
||||||
11
tsconfig.eslint.json
Normal file
11
tsconfig.eslint.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"./webpack/**/*",
|
||||||
|
"./.eslintrc.js",
|
||||||
|
"./.babelrc.js",
|
||||||
|
"./.prettierrc.js",
|
||||||
|
"./.postcssrc.js",
|
||||||
|
"./webpack.config.babel.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "es6",
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"typeRoots": [
|
||||||
|
"./src/@types",
|
||||||
|
"node_modules/@types"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"importsNotUsedAsValues": "remove",
|
||||||
|
"incremental": false,
|
||||||
|
"skipLibCheck": false,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*"],
|
||||||
|
"exclude": ["**/node_modules"]
|
||||||
|
}
|
||||||
8
webpack.config.babel.js
Normal file
8
webpack.config.babel.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import merge from "webpack-merge";
|
||||||
|
|
||||||
|
import baseConfig from "./webpack/base";
|
||||||
|
import config from "./webpack/config";
|
||||||
|
import devConfig from "./webpack/dev";
|
||||||
|
import prodConfig from "./webpack/prod";
|
||||||
|
|
||||||
|
export default merge(baseConfig, config.isProd ? prodConfig : devConfig);
|
||||||
46
webpack/base.js
Executable file
46
webpack/base.js
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
import { join } from "path";
|
||||||
|
import SpeedMeasurePlugin from "speed-measure-webpack-plugin";
|
||||||
|
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||||
|
|
||||||
|
import config from "./config";
|
||||||
|
import { basePlugins } from "./options/plugins";
|
||||||
|
import rules from "./options/rules";
|
||||||
|
|
||||||
|
const { devServer } = config;
|
||||||
|
console.log("dev server", JSON.stringify(devServer));
|
||||||
|
const baseConfig = {
|
||||||
|
context: __dirname,
|
||||||
|
entry: {
|
||||||
|
main: ["../src/index.tsx"],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: join(config.webpack.rootDir, "dist"),
|
||||||
|
publicPath: devServer.isDevServer ? devServer.url : "./",
|
||||||
|
filename: devServer.isDevServer
|
||||||
|
? "[name].[fullhash].js"
|
||||||
|
: "[name].[contenthash].js",
|
||||||
|
},
|
||||||
|
module: { rules },
|
||||||
|
plugins: basePlugins,
|
||||||
|
resolve: {
|
||||||
|
extensions: [".tsx", ".ts", ".js", ".jsx"],
|
||||||
|
plugins: [new TsconfigPathsPlugin({})],
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
runtimeChunk: {
|
||||||
|
name: "runtime",
|
||||||
|
},
|
||||||
|
splitChunks: {
|
||||||
|
cacheGroups: {
|
||||||
|
commons: {
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
name: "vendor",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default process.env.MEASURE
|
||||||
|
? new SpeedMeasurePlugin().wrap(baseConfig)
|
||||||
|
: baseConfig;
|
||||||
43
webpack/config.js
Normal file
43
webpack/config.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
const port = process.env.PORT || 5005;
|
||||||
|
const devServerHost = "127.0.0.1";
|
||||||
|
const devServerUrl = `http://${devServerHost}:${port}/`;
|
||||||
|
|
||||||
|
const Environment = {
|
||||||
|
development: "development",
|
||||||
|
production: "production",
|
||||||
|
};
|
||||||
|
const environment = process.env.NODE_ENV || Environment.production;
|
||||||
|
|
||||||
|
const EnvironmentConfig = {
|
||||||
|
development: "development",
|
||||||
|
nonprod: "nonprod",
|
||||||
|
prelive: "prelive",
|
||||||
|
production: "production",
|
||||||
|
};
|
||||||
|
const environmentConfiguration = process.env.ENV_CONFIGURATION || EnvironmentConfig.development;
|
||||||
|
|
||||||
|
const isProd = environmentConfiguration === EnvironmentConfig.production;
|
||||||
|
const isPrelive = environmentConfiguration === EnvironmentConfig.prelive;
|
||||||
|
const isNonprod = environmentConfiguration === EnvironmentConfig.nonprod;
|
||||||
|
const isDev = environment === Environment.development
|
||||||
|
&& environmentConfiguration === EnvironmentConfig.development;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
environment,
|
||||||
|
environmentConfiguration,
|
||||||
|
isProd,
|
||||||
|
isNonprod,
|
||||||
|
isDev,
|
||||||
|
isPrelive,
|
||||||
|
devServer: {
|
||||||
|
isDevServer: process.env.WEBPACK_IS_DEV_SERVER === "true",
|
||||||
|
host: devServerHost,
|
||||||
|
url: devServerUrl,
|
||||||
|
},
|
||||||
|
webpack: {
|
||||||
|
rootDir: join(__dirname, "../"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export { EnvironmentConfig };
|
||||||
21
webpack/dev.js
Normal file
21
webpack/dev.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import config from "./config";
|
||||||
|
import { devPlugins } from "./options/plugins";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
target: "web",
|
||||||
|
mode: "development",
|
||||||
|
devtool: "eval-cheap-source-map",
|
||||||
|
devServer: {
|
||||||
|
port: process.env.PORT || 5005,
|
||||||
|
host: config.devServer.host,
|
||||||
|
historyApiFallback: true,
|
||||||
|
hot: true,
|
||||||
|
headers: {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
overlay: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: devPlugins,
|
||||||
|
};
|
||||||
66
webpack/options/plugins.js
Normal file
66
webpack/options/plugins.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||||
|
import CopyPlugin from "copy-webpack-plugin";
|
||||||
|
import Dotenv from "dotenv-webpack";
|
||||||
|
import ESLintPlugin from "eslint-webpack-plugin";
|
||||||
|
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
|
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||||
|
import { join } from "path";
|
||||||
|
import { ProgressPlugin } from "webpack";
|
||||||
|
|
||||||
|
import config, { EnvironmentConfig } from "../config";
|
||||||
|
|
||||||
|
const basePlugins = [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
publicPath: "/",
|
||||||
|
template: join(config.webpack.rootDir, "src/index.html"),
|
||||||
|
}),
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
typescript: {
|
||||||
|
configFile: join(config.webpack.rootDir, "tsconfig.json"),
|
||||||
|
mode: "write-references",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new ESLintPlugin({
|
||||||
|
context: join(config.webpack.rootDir, "src"),
|
||||||
|
extensions: ["ts", "tsx"],
|
||||||
|
failOnError: !config.isDev,
|
||||||
|
lintDirtyModulesOnly: true,
|
||||||
|
}),
|
||||||
|
new CopyPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: join(config.webpack.rootDir, "src/assets"),
|
||||||
|
to: "assets",
|
||||||
|
globOptions: {
|
||||||
|
dot: false,
|
||||||
|
ignore: ["**/*.tsx", "**/*.ico"],
|
||||||
|
},
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new Dotenv({
|
||||||
|
path: (() => {
|
||||||
|
let path = "./.env";
|
||||||
|
if (config.isProd) {
|
||||||
|
path = "./.env.production";
|
||||||
|
} else if (config.isPrelive) {
|
||||||
|
path = "./.env.prelive";
|
||||||
|
} else if (config.isNonprod) {
|
||||||
|
path = "./.env.nonprod";
|
||||||
|
}
|
||||||
|
console.log(`Using '${path}' for build variables.`)
|
||||||
|
return path;
|
||||||
|
})(),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const devPlugins = [
|
||||||
|
new ProgressPlugin(),
|
||||||
|
!config.isProd &&
|
||||||
|
new ReactRefreshWebpackPlugin({
|
||||||
|
overlay: false,
|
||||||
|
}),
|
||||||
|
].filter((_) => _);
|
||||||
|
|
||||||
|
export { basePlugins, devPlugins };
|
||||||
56
webpack/options/rules.js
Normal file
56
webpack/options/rules.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||||
|
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
|
const styleRules = [
|
||||||
|
{
|
||||||
|
loader: config.isProd ? MiniCssExtractPlugin.loader : "style-loader",
|
||||||
|
options: {
|
||||||
|
esModule: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ loader: "css-loader" },
|
||||||
|
{ loader: "postcss-loader" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
test: /\.(ts|tsx)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: "esbuild-loader",
|
||||||
|
options: {
|
||||||
|
loader: "tsx",
|
||||||
|
target: "es2015",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.html$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: "html-loader",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(gif|png|jpg|jpeg|webp)$/i,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
type: "asset/resource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?|eot|ttf|otf|)$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
type: "asset/inline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s([ca])ss$/,
|
||||||
|
use: [
|
||||||
|
...styleRules,
|
||||||
|
{ loader: "fast-sass-loader" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: styleRules,
|
||||||
|
},
|
||||||
|
];
|
||||||
28
webpack/prod.js
Normal file
28
webpack/prod.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { ESBuildMinifyPlugin } from "esbuild-loader";
|
||||||
|
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
target: "web",
|
||||||
|
mode: "production",
|
||||||
|
devtool: false,
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new ESBuildMinifyPlugin({
|
||||||
|
target: "es2015",
|
||||||
|
css: true,
|
||||||
|
legalComments: "eof",
|
||||||
|
sourcemap: false,
|
||||||
|
minifyWhitespace: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: "[name].[contenthash].css",
|
||||||
|
chunkFilename: "[id].[contenthash].css",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user