typescript(lunargame/client): progress on user page
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
2bc7d4e3e1
commit
db4f749cd3
|
@ -1,17 +1,17 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
"@babel/preset-env",
|
'@babel/preset-env',
|
||||||
"@babel/preset-react",
|
'@babel/preset-react',
|
||||||
"@babel/preset-typescript"
|
'@babel/preset-typescript'
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
['@babel/plugin-proposal-class-properties', { loose: true }]
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
test: {
|
test: {
|
||||||
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
|
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
51
typescript/lunargame/client/package-lock.json
generated
51
typescript/lunargame/client/package-lock.json
generated
|
@ -736,18 +736,6 @@
|
||||||
"@babel/helper-plugin-utils": "^7.0.0"
|
"@babel/helper-plugin-utils": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/plugin-transform-runtime": {
|
|
||||||
"version": "7.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.0.tgz",
|
|
||||||
"integrity": "sha512-LmPIZOAgTLl+86gR9KjLXex6P/lRz1fWEjTz6V6QZMmKie51ja3tvzdwORqhHc4RWR8TcZ5pClpRWs0mlaA2ng==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@babel/helper-module-imports": "^7.0.0",
|
|
||||||
"@babel/helper-plugin-utils": "^7.0.0",
|
|
||||||
"resolve": "^1.8.1",
|
|
||||||
"semver": "^5.5.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@babel/plugin-transform-shorthand-properties": {
|
"@babel/plugin-transform-shorthand-properties": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
|
||||||
|
@ -1638,6 +1626,39 @@
|
||||||
"object.assign": "^4.1.0"
|
"object.assign": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-plugin-transform-runtime": {
|
||||||
|
"version": "6.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
|
||||||
|
"integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"babel-runtime": "^6.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"babel-regenerator-runtime": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.5.0.tgz",
|
||||||
|
"integrity": "sha1-DkHNHJ+ARCRm8BXHSf/4upj44RA=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"babel-runtime": {
|
||||||
|
"version": "6.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||||
|
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^2.4.0",
|
||||||
|
"regenerator-runtime": "^0.11.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||||
|
@ -2489,6 +2510,12 @@
|
||||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"core-js-compat": {
|
"core-js-compat": {
|
||||||
"version": "3.1.4",
|
"version": "3.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz",
|
||||||
|
|
|
@ -12,12 +12,13 @@
|
||||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
"@babel/plugin-proposal-decorators": "^7.4.4",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
|
||||||
"@babel/preset-env": "^7.5.0",
|
"@babel/preset-env": "^7.5.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@babel/preset-typescript": "^7.3.3",
|
"@babel/preset-typescript": "^7.3.3",
|
||||||
"@types/react-router-dom": "^4.3.4",
|
"@types/react-router-dom": "^4.3.4",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
|
"babel-regenerator-runtime": "^6.5.0",
|
||||||
"css-loader": "^3.0.0",
|
"css-loader": "^3.0.0",
|
||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
"html-webpack-inline-source-plugin": "0.0.10",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
export const Login = () => {
|
|
||||||
return <h1>This is the login component</h1>
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
export const Signup = () => {
|
|
||||||
return <h1>This is the signup component</h1>
|
|
||||||
}
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { Singleton } from '@eix/utils'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
|
||||||
|
export interface DialogAction {
|
||||||
|
name: string
|
||||||
|
variant: 'standard' | 'contained' | 'outlined'
|
||||||
|
callback: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Dialog {
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
actions: DialogAction[]
|
||||||
|
onClose: (event: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
export class DialogManager {
|
||||||
|
public active = new BehaviorSubject<Dialog | null>(null)
|
||||||
|
public queue: Dialog[] = []
|
||||||
|
|
||||||
|
public add(dialog: Dialog) {
|
||||||
|
if (this.active.value !== null) {
|
||||||
|
this.queue.push(dialog)
|
||||||
|
} else {
|
||||||
|
this.activate(dialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private activate(dialog: Dialog) {
|
||||||
|
this.active.next({
|
||||||
|
...dialog,
|
||||||
|
onClose: (event: unknown) => {
|
||||||
|
dialog.onClose(event)
|
||||||
|
|
||||||
|
if (this.queue.length) {
|
||||||
|
const newDialog = this.queue.shift()
|
||||||
|
this.activate(newDialog)
|
||||||
|
} else {
|
||||||
|
this.active.next(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Dialog from '@material-ui/core/Dialog'
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
|
import { DialogManager } from '../classes/DialogManager'
|
||||||
|
import { useObservable } from 'rxjs-hooks'
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions'
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
|
import { Button } from '@material-ui/core'
|
||||||
|
|
||||||
|
const manager = new DialogManager()
|
||||||
|
|
||||||
|
export const BaseDialogRenderer = () => {
|
||||||
|
const activeDialog = useObservable(() => manager.active)
|
||||||
|
|
||||||
|
if (activeDialog !== null) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
onClose={activeDialog.onClose}
|
||||||
|
open={true}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{activeDialog.title}
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
{activeDialog.message}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
{activeDialog.actions.map((action, index) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={event => {
|
||||||
|
action.callback()
|
||||||
|
activeDialog.onClose(event)
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{action.name}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <></>
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Subscription, BehaviorSubject } from 'rxjs'
|
||||||
|
|
||||||
|
export interface FormFieldSnapshot {
|
||||||
|
passing: boolean
|
||||||
|
errorMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormValidator {
|
||||||
|
validate: (input: string) => FormFieldSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FormField {
|
||||||
|
private subscription: Subscription
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: string,
|
||||||
|
public input: BehaviorSubject<string>,
|
||||||
|
public output: BehaviorSubject<FormFieldSnapshot>,
|
||||||
|
private validators: FormValidator[]
|
||||||
|
) {
|
||||||
|
this.subscription = this.input.subscribe((text: string) => {
|
||||||
|
for (const validator of this.validators) {
|
||||||
|
const result = validator.validate(text)
|
||||||
|
|
||||||
|
if (!result.passing) {
|
||||||
|
return this.output.next(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.output.next({
|
||||||
|
passing: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public passes() {
|
||||||
|
return this.output.value.passing
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value() {
|
||||||
|
return this.input.value
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { FormField } from './FormField'
|
||||||
|
import { BaseServer } from '../../../../modules/network/classes/BaseServer'
|
||||||
|
|
||||||
|
const server = new BaseServer()
|
||||||
|
|
||||||
|
export class FormManager {
|
||||||
|
constructor(public fields: FormField[]) {}
|
||||||
|
|
||||||
|
public collect() {
|
||||||
|
const data: Record<string, string> = {}
|
||||||
|
|
||||||
|
for (const { name, value } of this.fields) {
|
||||||
|
data[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
public async submit(url: string) {
|
||||||
|
if (this.validate()) {
|
||||||
|
return server.request(url, 'POST', this.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public validate() {
|
||||||
|
for (const field of this.fields) {
|
||||||
|
if (!field.passes()) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
for (const field of this.fields) {
|
||||||
|
field.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react'
|
||||||
|
import TextField from '@material-ui/core/TextField'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { FormFieldSnapshot } from '../classes/FormField'
|
||||||
|
import { useObservable } from 'rxjs-hooks'
|
||||||
|
|
||||||
|
export interface TextFieldWithErrorsProps {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
input: BehaviorSubject<string>
|
||||||
|
output: BehaviorSubject<FormFieldSnapshot>
|
||||||
|
className: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const good = '✅'
|
||||||
|
|
||||||
|
export const TextFieldWithErrors = (props: TextFieldWithErrorsProps) => {
|
||||||
|
const outputSnapshot = useObservable(() => props.output, {
|
||||||
|
passing: true,
|
||||||
|
errorMessage: good
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
className={props.className}
|
||||||
|
label={props.name}
|
||||||
|
type={props.type}
|
||||||
|
error={!outputSnapshot.passing}
|
||||||
|
helperText={
|
||||||
|
outputSnapshot.passing ? good : outputSnapshot.errorMessage
|
||||||
|
}
|
||||||
|
onChange={event => {
|
||||||
|
props.input.next(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Dialog from '@material-ui/core/Dialog'
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions'
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
|
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
|
import { FormValidator, FormField } from '../classes/FormField'
|
||||||
|
import { Button } from '@material-ui/core'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { FormManager } from '../classes/FormManager'
|
||||||
|
import { TextFieldWithErrors } from '../components/TextFieldWithError'
|
||||||
|
import { makeStyles, Theme } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
export interface TextFieldData {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
validators: FormValidator[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModalProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
field: {
|
||||||
|
marginTop: theme.spacing(2)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
export const createFormModal = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
url: string,
|
||||||
|
fields: TextFieldData[],
|
||||||
|
onSubmit?: (data: unknown) => void
|
||||||
|
) => {
|
||||||
|
const formFields = fields.map(
|
||||||
|
field =>
|
||||||
|
new FormField(
|
||||||
|
field.name,
|
||||||
|
new BehaviorSubject(''),
|
||||||
|
new BehaviorSubject({
|
||||||
|
passing: true
|
||||||
|
}),
|
||||||
|
field.validators
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const formManager = new FormManager(formFields)
|
||||||
|
|
||||||
|
return (props: ModalProps) => {
|
||||||
|
const handleClose = (event: unknown) => {
|
||||||
|
props.onClose(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const textFields = fields.map((field, index) => {
|
||||||
|
const fieldObject = formFields[index]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextFieldWithErrors
|
||||||
|
className={classes.field}
|
||||||
|
name={field.name}
|
||||||
|
type={field.type}
|
||||||
|
input={fieldObject.input}
|
||||||
|
output={fieldObject.output}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth="sm"
|
||||||
|
open={props.open}
|
||||||
|
aria-labelledby={title}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<DialogTitle id={title}>{title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>{description}</DialogContentText>
|
||||||
|
{textFields}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={event => {
|
||||||
|
if (formManager.validate()) {
|
||||||
|
formManager.submit(url).then(data => {
|
||||||
|
handleClose(event)
|
||||||
|
|
||||||
|
if (onSubmit) onSubmit(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { FormValidator, FormFieldSnapshot } from '../classes/FormField'
|
||||||
|
|
||||||
|
export interface ValidatorCondition {
|
||||||
|
regex: RegExp
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createValidator = (
|
||||||
|
...conditions: ValidatorCondition[]
|
||||||
|
): { new (): FormValidator } =>
|
||||||
|
class implements FormValidator {
|
||||||
|
public validate(text: string): FormFieldSnapshot {
|
||||||
|
for (const condition of conditions) {
|
||||||
|
if (!condition.regex.test(text)) {
|
||||||
|
return {
|
||||||
|
passing: false,
|
||||||
|
errorMessage: condition.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passing: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { createValidator } from '../helpers/createValidator'
|
||||||
|
|
||||||
|
export const lengthValidator = (min: number, max: number) =>
|
||||||
|
createValidator({
|
||||||
|
regex: new RegExp(`^.{${min},${max}}$`),
|
||||||
|
message: `Must contain between ${min} and ${max} characters.`
|
||||||
|
})
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { decorable } from '@eix/utils'
|
||||||
|
import { areEqual } from '../helpers/areEqual'
|
||||||
|
|
||||||
|
export interface ObjectArgumentsRef<T> {
|
||||||
|
instance: T
|
||||||
|
arguments: unknown[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cacheInstances = (argCount = Infinity) => {
|
||||||
|
const objectMemory: ObjectArgumentsRef<unknown>[] = []
|
||||||
|
|
||||||
|
return <T extends { new (...args: any[]): { init: () => void } }>(
|
||||||
|
toDecorate: T
|
||||||
|
) => {
|
||||||
|
return class extends toDecorate {
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
super(...args)
|
||||||
|
|
||||||
|
const sliceParameters =
|
||||||
|
argCount === Infinity ? [0] : [0, argCount]
|
||||||
|
const argumentsToStore = args.slice(...sliceParameters)
|
||||||
|
|
||||||
|
const reference = objectMemory.find(instance =>
|
||||||
|
areEqual(argumentsToStore, instance.arguments)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (reference) return reference.instance as this
|
||||||
|
else
|
||||||
|
objectMemory.push({
|
||||||
|
instance: this,
|
||||||
|
arguments: argumentsToStore
|
||||||
|
})
|
||||||
|
|
||||||
|
if (super.init) super.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const areEqual = <T extends {}>(first: T, last: T) => {
|
||||||
|
for (const key of Object.keys(first)) {
|
||||||
|
// for ts to shut up
|
||||||
|
const typedKey = key as keyof T
|
||||||
|
|
||||||
|
if (first[typedKey] !== last[typedKey]) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
import { Account } from '../types/Account'
|
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
import { Singleton } from '@eix/utils'
|
|
||||||
import { defaultAvatar } from '../constants'
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
export class BaseServer {
|
|
||||||
public account = new BehaviorSubject<Account | null>(null)
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// mock account for now
|
|
||||||
// this.account.next({
|
|
||||||
// name: 'Mock',
|
|
||||||
// email: 'mock@somethng.io',
|
|
||||||
// avatar: defaultAvatar,
|
|
||||||
// description: 'Just a random mock account',
|
|
||||||
// uid: '1234',
|
|
||||||
// verified: true
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { App } from './common/core/components/App'
|
import { App } from './modules/core/components/App'
|
||||||
|
|
||||||
render(<App />, document.querySelector('#app'))
|
render(<App />, document.querySelector('#app'))
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { createFormModal } from '../../../common/dom/forms/helpers/createFormModal'
|
||||||
|
import {
|
||||||
|
emailValidatorList,
|
||||||
|
passwordValidatorList
|
||||||
|
} from '../validators/authValidators'
|
||||||
|
import { Account } from '../../network/types/Account'
|
||||||
|
import { updateAccount } from '../../helpers/updateAccount'
|
||||||
|
|
||||||
|
export const LoginModal = createFormModal(
|
||||||
|
'Login',
|
||||||
|
`To subscribe to this website, please enter you r email address here. We will send updates occasionally.`,
|
||||||
|
'auth/login',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'email',
|
||||||
|
validators: emailValidatorList()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'password',
|
||||||
|
validators: passwordValidatorList()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
updateAccount
|
||||||
|
)
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import Button from '@material-ui/core/Button'
|
||||||
|
import { ModalProps } from '../../../common/dom/forms/helpers/createFormModal'
|
||||||
|
|
||||||
|
export interface ModalButtonProps {
|
||||||
|
modal: (props: ModalProps) => JSX.Element
|
||||||
|
children: string
|
||||||
|
className?: string
|
||||||
|
contained?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalButton = (props: ModalButtonProps) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className={props.className}
|
||||||
|
variant={props.contained ? 'contained' : 'text'}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Button>
|
||||||
|
<props.modal open={open} onClose={() => setOpen(false)} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { createFormModal } from '../../../common/dom/forms/helpers/createFormModal'
|
||||||
|
import {
|
||||||
|
usernameValidatorList,
|
||||||
|
emailValidatorList,
|
||||||
|
passwordValidatorList
|
||||||
|
} from '../validators/authValidators'
|
||||||
|
import { updateAccount } from '../../helpers/updateAccount'
|
||||||
|
import { Account } from '../../network/types/Account'
|
||||||
|
import { DialogManager } from '../../../common/dom/dialogs/classes/DialogManager'
|
||||||
|
|
||||||
|
const dialogManager = new DialogManager()
|
||||||
|
|
||||||
|
export const SignupModal = createFormModal(
|
||||||
|
'Signup',
|
||||||
|
`To create an account you need to provide an username, email and a password.`,
|
||||||
|
'auth/create',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
validators: usernameValidatorList()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'email',
|
||||||
|
validators: emailValidatorList()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
type: 'password',
|
||||||
|
validators: passwordValidatorList()
|
||||||
|
}
|
||||||
|
],
|
||||||
|
(data: Account) => {
|
||||||
|
updateAccount(data)
|
||||||
|
dialogManager.add({
|
||||||
|
title: 'Email verification',
|
||||||
|
message:
|
||||||
|
'To unlock the full set of features offered by lunrabox, please verify your email',
|
||||||
|
actions: [],
|
||||||
|
onClose: () => {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
|
@ -2,9 +2,10 @@ import React from 'react'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import { BaseServer } from '../../network/classes/BaseServer'
|
import { BaseServer } from '../../network/classes/BaseServer'
|
||||||
import Avatar from '@material-ui/core/Avatar'
|
import Avatar from '@material-ui/core/Avatar'
|
||||||
import Button from '@material-ui/core/Button'
|
|
||||||
import { makeStyles, Theme } from '@material-ui/core/styles'
|
import { makeStyles, Theme } from '@material-ui/core/styles'
|
||||||
import { Link } from 'react-router-dom'
|
import { LoginModal } from './LoginModal'
|
||||||
|
import { ModalButton } from './ModalButton'
|
||||||
|
import { SignupModal } from './SignupModal'
|
||||||
|
|
||||||
const { account } = new BaseServer()
|
const { account } = new BaseServer()
|
||||||
|
|
||||||
|
@ -16,18 +17,18 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
|
||||||
export const TopbarAccount = (props: unknown) => {
|
export const TopbarAccount = (props: unknown) => {
|
||||||
const accountSnapshot = useObservable(() => account, null)
|
const accountSnapshot = useObservable(() => account, null)
|
||||||
|
|
||||||
const classes = useStyles(props)
|
const classes = useStyles(props)
|
||||||
|
|
||||||
const signup = (
|
const signup = (
|
||||||
<>
|
<>
|
||||||
<Button>
|
<ModalButton modal={SignupModal}>Sign up</ModalButton>
|
||||||
<Link to="/signup">Sign up</Link>
|
<ModalButton
|
||||||
</Button>
|
modal={LoginModal}
|
||||||
|
className={classes.loginButton}
|
||||||
<Button variant="contained" className={classes.loginButton}>
|
contained
|
||||||
<Link to="/login"> Login</Link>
|
>
|
||||||
</Button>
|
Log in
|
||||||
|
</ModalButton>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { createValidator } from '../../../common/dom/forms/helpers/createValidator'
|
||||||
|
|
||||||
|
export const alphaNumericValidator = createValidator({
|
||||||
|
regex: /^[a-zA-Z0-9_]*$/,
|
||||||
|
message: 'Must only contain alpha-numeric characters or underscores.'
|
||||||
|
})
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { fieldLengthValidator } from './fieldLength'
|
||||||
|
import { emailValidator } from './emailValidator'
|
||||||
|
import { requiredValidator } from './requiredValidator'
|
||||||
|
import { alphaNumericValidator } from './alphaNumeric'
|
||||||
|
|
||||||
|
export const usernameValidatorList = () => [
|
||||||
|
new requiredValidator(),
|
||||||
|
new fieldLengthValidator(),
|
||||||
|
new alphaNumericValidator()
|
||||||
|
]
|
||||||
|
export const emailValidatorList = () => [
|
||||||
|
new requiredValidator(),
|
||||||
|
new fieldLengthValidator(),
|
||||||
|
new emailValidator()
|
||||||
|
]
|
||||||
|
export const passwordValidatorList = () => [
|
||||||
|
new requiredValidator(),
|
||||||
|
new fieldLengthValidator(),
|
||||||
|
new alphaNumericValidator()
|
||||||
|
]
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { createValidator } from '../../../common/dom/forms/helpers/createValidator'
|
||||||
|
|
||||||
|
export const emailValidator = createValidator({
|
||||||
|
regex: /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/,
|
||||||
|
message: 'Must be a valid email'
|
||||||
|
})
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { lengthValidator } from '../../../common/dom/forms/validators/lengthValidator'
|
||||||
|
|
||||||
|
export const fieldLengthValidator = lengthValidator(3, 30)
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { createValidator } from '../../../common/dom/forms/helpers/createValidator'
|
||||||
|
|
||||||
|
export const requiredValidator = createValidator({
|
||||||
|
regex: /^.{1,}$/,
|
||||||
|
message: 'Field is required'
|
||||||
|
})
|
|
@ -8,6 +8,7 @@ import { theme as MuiTheme } from '../data/Theme'
|
||||||
import { ThemeProvider as Theme } from '@material-ui/styles'
|
import { ThemeProvider as Theme } from '@material-ui/styles'
|
||||||
import { AppBar } from './AppBar'
|
import { AppBar } from './AppBar'
|
||||||
import { Body } from './Body'
|
import { Body } from './Body'
|
||||||
|
import { BaseDialogRenderer } from '../../../common/dom/dialogs/components/BaseDialogRenderer'
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -16,6 +17,7 @@ export const App = () => {
|
||||||
<Router>
|
<Router>
|
||||||
<AppBar />
|
<AppBar />
|
||||||
<Body />
|
<Body />
|
||||||
|
<BaseDialogRenderer />
|
||||||
</Router>
|
</Router>
|
||||||
</Theme>
|
</Theme>
|
||||||
)
|
)
|
|
@ -2,8 +2,6 @@ import React from 'react'
|
||||||
import { SiddebarRoutes } from './SidebarRouteList'
|
import { SiddebarRoutes } from './SidebarRouteList'
|
||||||
import { makeStyles } from '@material-ui/styles'
|
import { makeStyles } from '@material-ui/styles'
|
||||||
import { Route } from 'react-router-dom'
|
import { Route } from 'react-router-dom'
|
||||||
import { Signup } from '../../account/components/Signup'
|
|
||||||
import { Login } from '../../account/components/Login'
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
root: {
|
root: {
|
||||||
|
@ -17,9 +15,6 @@ export const Body = (props: unknown) => {
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<SiddebarRoutes />
|
<SiddebarRoutes />
|
||||||
|
|
||||||
<Route component={Signup} path="/signup" />
|
|
||||||
<Route component={Login} path="/login" />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||||
import Typography from '@material-ui/core/Typography'
|
import Typography from '@material-ui/core/Typography'
|
||||||
import Divider from '@material-ui/core/Divider'
|
import Divider from '@material-ui/core/Divider'
|
||||||
import { makeStyles, Theme } from '@material-ui/core/styles'
|
import { makeStyles, Theme } from '@material-ui/core/styles'
|
||||||
|
import { BaseServer } from '../../network/classes/BaseServer'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
root: {
|
root: {
|
||||||
|
@ -10,6 +11,9 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
divider: {
|
divider: {
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
marginTop: theme.spacing(2)
|
marginTop: theme.spacing(2)
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
color: '#0000ff'
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -24,9 +28,34 @@ export const Home = (props: unknown) => {
|
||||||
|
|
||||||
<Typography variant="h6" color="textSecondary">
|
<Typography variant="h6" color="textSecondary">
|
||||||
Lunarbox is a game streaming website for games made with the eix
|
Lunarbox is a game streaming website for games made with the eix
|
||||||
game engine. The project is open source, and right now it's
|
game engine. The project is open source, and right now also
|
||||||
unusable.
|
unusable.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
The project is open source on{' '}
|
||||||
|
<a
|
||||||
|
className={classes.a}
|
||||||
|
href="https://github.com/Mateiadrielrafael/lunarbox-client"
|
||||||
|
>
|
||||||
|
github
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Todo: remove */}
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
const server = new BaseServer()
|
||||||
|
await server.request('account/uid', 'DELETE')
|
||||||
|
|
||||||
|
server.account.next(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
logout
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { BaseServer } from '../network/classes/BaseServer'
|
||||||
|
import { Account } from '../network/types/Account'
|
||||||
|
|
||||||
|
const server = new BaseServer()
|
||||||
|
|
||||||
|
export const updateAccount = (data: Account | null) => {
|
||||||
|
server.account.next(data)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Account } from '../types/Account'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { Singleton } from '@eix/utils'
|
||||||
|
import { Response } from '../types/Response'
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
export class BaseServer {
|
||||||
|
public account = new BehaviorSubject<Account | null>(null)
|
||||||
|
public path = 'http://localhost:8000'
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.refreshAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async refreshAccount(url = 'account', method = 'GET', body = {}) {
|
||||||
|
try {
|
||||||
|
const account = await this.request<Account>(url, method, body)
|
||||||
|
this.account.next(account)
|
||||||
|
return account
|
||||||
|
} catch (err) {
|
||||||
|
this.account.next(null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async request<T>(
|
||||||
|
url: string,
|
||||||
|
method = 'GET',
|
||||||
|
body = {}
|
||||||
|
): Promise<T> {
|
||||||
|
const noBody = ['GET', 'DELETE']
|
||||||
|
|
||||||
|
const response = await fetch(`${this.path}/${url}`, {
|
||||||
|
...(noBody.indexOf(method) === -1
|
||||||
|
? { body: JSON.stringify(body) }
|
||||||
|
: {}),
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Credentials': 'true'
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const parsed: Response<T> = await response.json()
|
||||||
|
const status = response.status
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
console.warn(parsed.message)
|
||||||
|
throw new Error(parsed.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.data
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { cacheInstances } from '../../../common/lang/objects/decorators/cacheInstances'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { AccountPublicData } from '../types/AccountPublicData'
|
||||||
|
import { BaseServer } from './BaseServer'
|
||||||
|
|
||||||
|
@cacheInstances()
|
||||||
|
export class PublicAccount {
|
||||||
|
public static server = new BaseServer()
|
||||||
|
public account = new BehaviorSubject<AccountPublicData | null>(null)
|
||||||
|
|
||||||
|
public constructor(public name: string) {}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async refresh() {
|
||||||
|
try {
|
||||||
|
const account = await PublicAccount.server.request<
|
||||||
|
AccountPublicData
|
||||||
|
>(`user/name/${this.name}`)
|
||||||
|
|
||||||
|
this.account.next(account)
|
||||||
|
return account
|
||||||
|
} catch {
|
||||||
|
this.account.next(null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { AccountPublicData } from './AccountPublicData'
|
||||||
|
|
||||||
|
export interface Account extends AccountPublicData {
|
||||||
|
uid: string
|
||||||
|
verified: boolean
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
export interface Account {
|
export interface AccountPublicData {
|
||||||
name: string
|
|
||||||
email: string
|
email: string
|
||||||
avatar: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
uid: string
|
avatar: string
|
||||||
verified: boolean
|
|
||||||
}
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface Response<T> {
|
||||||
|
data: T
|
||||||
|
message: string
|
||||||
|
}
|
|
@ -4,7 +4,8 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"experimentalDecorators": true
|
"experimentalDecorators": true,
|
||||||
|
"target": "esnext"
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
const { resolve } = require("path")
|
const { resolve } = require('path')
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin")
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin")
|
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin")
|
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
|
||||||
const webpackMerge = require("webpack-merge")
|
const webpackMerge = require('webpack-merge')
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === "production"
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
const projectRoot = resolve(__dirname)
|
const projectRoot = resolve(__dirname)
|
||||||
const sourceFolder = resolve(projectRoot, "src")
|
const sourceFolder = resolve(projectRoot, 'src')
|
||||||
const buildFolder = resolve(projectRoot, "dist")
|
const buildFolder = resolve(projectRoot, 'dist')
|
||||||
const htmlTemplateFile = resolve(sourceFolder, "index.html")
|
const htmlTemplateFile = resolve(sourceFolder, 'index.html')
|
||||||
|
|
||||||
const babelRule = {
|
const babelRule = {
|
||||||
test: /\.(js|tsx?)$/,
|
test: /\.(js|tsx?)$/,
|
||||||
use: "babel-loader",
|
use: 'babel-loader'
|
||||||
}
|
}
|
||||||
|
|
||||||
const sassRule = {
|
const sassRule = {
|
||||||
|
@ -23,62 +23,61 @@ const sassRule = {
|
||||||
isProduction
|
isProduction
|
||||||
? MiniCssExtractPlugin.loader
|
? MiniCssExtractPlugin.loader
|
||||||
: {
|
: {
|
||||||
loader: "style-loader",
|
loader: 'style-loader',
|
||||||
options: {
|
options: {
|
||||||
singleton: true,
|
singleton: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
{ loader: 'css-loader' },
|
||||||
{ loader: "css-loader" },
|
|
||||||
{
|
{
|
||||||
loader: "sass-loader",
|
loader: 'sass-loader',
|
||||||
options: {
|
options: {
|
||||||
includePaths: [sourceFolder],
|
includePaths: [sourceFolder]
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseConfig = {
|
|
||||||
mode: "none",
|
|
||||||
entry: [ resolve(sourceFolder, "main")],
|
|
||||||
output: {
|
|
||||||
filename: "js/[name].js",
|
|
||||||
path: buildFolder,
|
|
||||||
publicPath: "/",
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [ babelRule, sassRule],
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [".js", ".ts", ".tsx", ".scss"],
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
mode: 'none',
|
||||||
|
entry: ['babel-regenerator-runtime', resolve(sourceFolder, 'main')],
|
||||||
|
output: {
|
||||||
|
filename: 'js/[name].js',
|
||||||
|
path: buildFolder,
|
||||||
|
publicPath: '/'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [babelRule, sassRule]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts', '.tsx', '.scss']
|
||||||
|
},
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
|
|
||||||
const devConfig = {
|
const devConfig = {
|
||||||
mode: "development",
|
mode: 'development',
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: htmlTemplateFile,
|
template: htmlTemplateFile,
|
||||||
chunksSortMode: "dependency",
|
chunksSortMode: 'dependency'
|
||||||
}),
|
})
|
||||||
],
|
],
|
||||||
devtool: "inline-source-map",
|
devtool: 'inline-source-map',
|
||||||
devServer: {
|
devServer: {
|
||||||
historyApiFallback: true
|
historyApiFallback: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prodConfig = {
|
const prodConfig = {
|
||||||
mode: "production",
|
mode: 'production',
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: true,
|
minimize: true,
|
||||||
nodeEnv: "production",
|
nodeEnv: 'production'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: "css/[name].min.css",
|
filename: 'css/[name].min.css'
|
||||||
}),
|
}),
|
||||||
new OptimizeCssAssetsWebpackPlugin(),
|
new OptimizeCssAssetsWebpackPlugin(),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
|
@ -99,16 +98,16 @@ const prodConfig = {
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackInlineSourcePlugin()
|
new HtmlWebpackInlineSourcePlugin()
|
||||||
],
|
],
|
||||||
devtool: "source-map"
|
devtool: 'source-map'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFinalConfig() {
|
function getFinalConfig() {
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
console.info("Running production config")
|
console.info('Running production config')
|
||||||
return webpackMerge(baseConfig, prodConfig)
|
return webpackMerge(baseConfig, prodConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("Running development config")
|
console.info('Running development config')
|
||||||
return webpackMerge(baseConfig, devConfig)
|
return webpackMerge(baseConfig, devConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue