formate all files in learneth

pull/4466/head
drafish 10 months ago committed by yann300
parent 3586b42e20
commit 77a01a2ff9
  1. 4
      apps/learneth/src/components/BackButton/index.scss
  2. 16
      apps/learneth/src/components/LoadingScreen/index.tsx
  3. 117
      apps/learneth/src/components/RepoImporter/index.tsx
  4. 18
      apps/learneth/src/components/SlideIn/index.tsx
  5. 20
      apps/learneth/src/main.tsx
  6. 102
      apps/learneth/src/pages/Home/index.tsx
  7. 5
      apps/learneth/src/pages/StepDetail/index.scss
  8. 8
      apps/learneth/src/pages/StepList/index.scss
  9. 8
      apps/learneth/src/redux/hooks.ts
  10. 8
      apps/learneth/src/redux/models/loading.ts
  11. 113
      apps/learneth/src/redux/models/workshop.ts
  12. 100
      apps/learneth/src/redux/store.ts
  13. 26
      apps/learneth/src/remix-client.ts
  14. 52
      apps/learneth/webpack.config.js

@ -1,5 +1,4 @@
a { a {
.arrow { .arrow {
display: inline-block; display: inline-block;
opacity: 0; opacity: 0;
@ -12,9 +11,6 @@ a {
transform: translateX(-0.875em); // size of icon transform: translateX(-0.875em); // size of icon
transition: transform 0.3s; transition: transform 0.3s;
} }
} }
.workshoptitle{ .workshoptitle{

@ -1,16 +1,16 @@
import React from 'react'; import React from 'react'
import BounceLoader from 'react-spinners/BounceLoader'; import BounceLoader from 'react-spinners/BounceLoader'
import './index.css'; import './index.css'
import { useAppSelector } from '../../redux/hooks'; import {useAppSelector} from '../../redux/hooks'
const LoadingScreen: React.FC = () => { const LoadingScreen: React.FC = () => {
const loading = useAppSelector((state) => state.loading.screen); const loading = useAppSelector((state) => state.loading.screen)
return loading ? ( return loading ? (
<div className="spinnersOverlay"> <div className="spinnersOverlay">
<BounceLoader color="#a7b0ae" size={100} className="spinnersLoading" /> <BounceLoader color="#a7b0ae" size={100} className="spinnersLoading" />
</div> </div>
) : null; ) : null
}; }
export default LoadingScreen; export default LoadingScreen

@ -1,50 +1,39 @@
import React, { useState, useEffect } from 'react'; import React, {useState, useEffect} from 'react'
import { import {Button, Dropdown, Form, Tooltip, OverlayTrigger} from 'react-bootstrap'
Button, import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
Dropdown, import {faQuestionCircle, faInfoCircle, faChevronRight, faChevronDown} from '@fortawesome/free-solid-svg-icons'
Form, import {useAppDispatch} from '../../redux/hooks'
Tooltip, import './index.css'
OverlayTrigger,
} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faQuestionCircle,
faInfoCircle,
faChevronRight,
faChevronDown,
} from '@fortawesome/free-solid-svg-icons';
import { useAppDispatch } from '../../redux/hooks';
import './index.css';
function RepoImporter({list, selectedRepo}: any): JSX.Element { function RepoImporter({list, selectedRepo}: any): JSX.Element {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false)
const [name, setName] = useState(''); const [name, setName] = useState('')
const [branch, setBranch] = useState(''); const [branch, setBranch] = useState('')
const dispatch = useAppDispatch(); const dispatch = useAppDispatch()
useEffect(() => { useEffect(() => {
setName(selectedRepo.name); setName(selectedRepo.name)
setBranch(selectedRepo.branch); setBranch(selectedRepo.branch)
}, [selectedRepo]); }, [selectedRepo])
const panelChange = () => { const panelChange = () => {
setOpen(!open); setOpen(!open)
}; }
const selectRepo = (repo: {name: string; branch: string}) => { const selectRepo = (repo: {name: string; branch: string}) => {
dispatch({ type: 'workshop/loadRepo', payload: repo }); dispatch({type: 'workshop/loadRepo', payload: repo})
}; }
const importRepo = (event: {preventDefault: () => void}) => { const importRepo = (event: {preventDefault: () => void}) => {
event.preventDefault(); event.preventDefault()
dispatch({ type: 'workshop/loadRepo', payload: { name, branch } }); dispatch({type: 'workshop/loadRepo', payload: {name, branch}})
}; }
const resetAll = () => { const resetAll = () => {
dispatch({ type: 'workshop/resetAll' }); dispatch({type: 'workshop/resetAll'})
setName(''); setName('')
setBranch(''); setBranch('')
}; }
return ( return (
<> <>
@ -52,24 +41,13 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
<div className="container-fluid mb-3 small mt-3"> <div className="container-fluid mb-3 small mt-3">
Tutorials from: Tutorials from:
<h4 className="mb-1">{selectedRepo.name}</h4> <h4 className="mb-1">{selectedRepo.name}</h4>
<span className=""> <span className="">Date modified: {new Date(selectedRepo.datemodified).toLocaleString()}</span>
Date modified:{' '}
{new Date(selectedRepo.datemodified).toLocaleString()}
</span>
</div> </div>
)} )}
<div <div onClick={panelChange} style={{cursor: 'pointer'}} className="container-fluid d-flex mb-3 small">
onClick={panelChange}
style={{ cursor: 'pointer' }}
className="container-fluid d-flex mb-3 small"
>
<div className="d-flex pr-2 pl-2"> <div className="d-flex pr-2 pl-2">
<FontAwesomeIcon <FontAwesomeIcon className="arrow-icon pt-1" size="xs" icon={open ? faChevronDown : faChevronRight} />
className="arrow-icon pt-1"
size="xs"
icon={open ? faChevronDown : faChevronRight}
/>
</div> </div>
<div className="d-flex">Import another tutorial repo</div> <div className="d-flex">Import another tutorial repo</div>
</div> </div>
@ -77,10 +55,7 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
{open && ( {open && (
<div className="container-fluid"> <div className="container-fluid">
<Dropdown className="w-100"> <Dropdown className="w-100">
<Dropdown.Toggle <Dropdown.Toggle className="btn btn-secondary w-100" id="dropdownBasic1">
className="btn btn-secondary w-100"
id="dropdownBasic1"
>
Select a repo Select a repo
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="w-100"> <Dropdown.Menu className="w-100">
@ -88,7 +63,7 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
<Dropdown.Item <Dropdown.Item
key={`${item.name}/${item.branch}`} key={`${item.name}/${item.branch}`}
onClick={() => { onClick={() => {
selectRepo(item); selectRepo(item)
}} }}
> >
{item.name}-{item.branch} {item.name}-{item.branch}
@ -96,11 +71,7 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
))} ))}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
<div <div onClick={resetAll} className="small mb-3" style={{cursor: 'pointer'}}>
onClick={resetAll}
className="small mb-3"
style={{ cursor: 'pointer' }}
>
reset list reset list
</div> </div>
</div> </div>
@ -113,19 +84,14 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
<Form.Label className="mr-2" htmlFor="name"> <Form.Label className="mr-2" htmlFor="name">
REPO REPO
</Form.Label> </Form.Label>
<OverlayTrigger <OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-right">ie username/repository</Tooltip>}>
placement="right"
overlay={
<Tooltip id="tooltip-right">ie username/repository</Tooltip>
}
>
<FontAwesomeIcon icon={faQuestionCircle} /> <FontAwesomeIcon icon={faQuestionCircle} />
</OverlayTrigger> </OverlayTrigger>
<Form.Control <Form.Control
id="name" id="name"
required required
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value)
}} }}
value={name} value={name}
/> />
@ -134,24 +100,15 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
id="branch" id="branch"
required required
onChange={(e) => { onChange={(e) => {
setBranch(e.target.value); setBranch(e.target.value)
}} }}
value={branch} value={branch}
/> />
</Form.Group> </Form.Group>
<Button <Button className="btn btn-success start w-100" type="submit" disabled={!name || !branch}>
className="btn btn-success start w-100"
type="submit"
disabled={!name || !branch}
>
Import {name} Import {name}
</Button> </Button>
<a <a href="https://github.com/bunsenstraat/remix-learneth-plugin/blob/master/README.md" className="d-none" target="_blank" rel="noreferrer">
href="https://github.com/bunsenstraat/remix-learneth-plugin/blob/master/README.md"
className="d-none"
target="_blank"
rel="noreferrer"
>
<FontAwesomeIcon icon={faInfoCircle} /> how to setup your repo <FontAwesomeIcon icon={faInfoCircle} /> how to setup your repo
</a> </a>
</Form> </Form>
@ -159,7 +116,7 @@ function RepoImporter({ list, selectedRepo }: any): JSX.Element {
<hr /> <hr />
</div> </div>
</> </>
); )
} }
export default RepoImporter; export default RepoImporter

@ -1,18 +1,18 @@
import React, { type ReactNode, useEffect, useState } from 'react'; import React, {type ReactNode, useEffect, useState} from 'react'
import { CSSTransition } from 'react-transition-group'; import {CSSTransition} from 'react-transition-group'
import './index.css'; import './index.css'
const SlideIn: React.FC<{children: ReactNode}> = ({children}) => { const SlideIn: React.FC<{children: ReactNode}> = ({children}) => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false)
useEffect(() => { useEffect(() => {
setShow(true); setShow(true)
}, []); }, [])
return ( return (
<CSSTransition in={show} timeout={400} classNames="slide" unmountOnExit> <CSSTransition in={show} timeout={400} classNames="slide" unmountOnExit>
{children} {children}
</CSSTransition> </CSSTransition>
); )
}; }
export default SlideIn; export default SlideIn

@ -1,15 +1,13 @@
import React from 'react'; import React from 'react'
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'; import {Provider} from 'react-redux'
import './index.css'; import './index.css'
import App from './App'; import App from './App'
import { store } from './redux/store'; import {store} from './redux/store'
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
document.getElementById('root') as HTMLElement,
);
root.render( root.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider>, </Provider>
); )

@ -1,48 +1,38 @@
import React, { useEffect } from 'react'; import React, {useEffect} from 'react'
import { Link } from 'react-router-dom'; import {Link} from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import { import {faChevronRight, faChevronDown, faPlayCircle} from '@fortawesome/free-solid-svg-icons'
faChevronRight, import Markdown from 'react-markdown'
faChevronDown, import rehypeRaw from 'rehype-raw'
faPlayCircle, import remarkGfm from 'remark-gfm'
} from '@fortawesome/free-solid-svg-icons'; import {useAppDispatch, useAppSelector} from '../../redux/hooks'
import Markdown from 'react-markdown'; import RepoImporter from '../../components/RepoImporter'
import rehypeRaw from 'rehype-raw'; import './index.css'
import remarkGfm from 'remark-gfm';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import RepoImporter from '../../components/RepoImporter';
import './index.css';
function HomePage(): JSX.Element { function HomePage(): JSX.Element {
const [openKeys, setOpenKeys] = React.useState<string[]>([]); const [openKeys, setOpenKeys] = React.useState<string[]>([])
const isOpen = (key: string) => openKeys.includes(key); const isOpen = (key: string) => openKeys.includes(key)
const handleClick = (key: string) => { const handleClick = (key: string) => {
setOpenKeys( setOpenKeys(isOpen(key) ? openKeys.filter((item) => item !== key) : [...openKeys, key])
isOpen(key) }
? openKeys.filter((item) => item !== key)
: [...openKeys, key],
);
};
const dispatch = useAppDispatch(); const dispatch = useAppDispatch()
const { list, detail, selectedId } = useAppSelector( const {list, detail, selectedId} = useAppSelector((state) => state.workshop)
(state) => state.workshop,
);
const selectedRepo = detail[selectedId]; const selectedRepo = detail[selectedId]
const levelMap: any = { const levelMap: any = {
1: 'Beginner', 1: 'Beginner',
2: 'Intermediate', 2: 'Intermediate',
3: 'Advanced', 3: 'Advanced',
}; }
useEffect(() => { useEffect(() => {
dispatch({ dispatch({
type: 'workshop/init', type: 'workshop/init',
}); })
}, []); }, [])
return ( return (
<div className="App"> <div className="App">
@ -59,63 +49,37 @@ function HomePage(): JSX.Element {
href="#" href="#"
className="arrow-icon" className="arrow-icon"
onClick={() => { onClick={() => {
handleClick(item.id); handleClick(item.id)
}} }}
> >
<FontAwesomeIcon <FontAwesomeIcon size="xs" icon={isOpen(item.id) ? faChevronDown : faChevronRight} />
size="xs"
icon={isOpen(item.id) ? faChevronDown : faChevronRight}
/>
</a> </a>
<a <a
href="#" href="#"
className="workshop-link" className="workshop-link"
onClick={() => { onClick={() => {
handleClick(item.id); handleClick(item.id)
}} }}
> >
{selectedRepo.entities[item.id].name} {selectedRepo.entities[item.id].name}
</a> </a>
<Link <Link to={`/list?id=${item.id}`} className="text-decoration-none float-right">
to={`/list?id=${item.id}`}
className="text-decoration-none float-right"
>
<FontAwesomeIcon icon={faPlayCircle} size="lg" /> <FontAwesomeIcon icon={faPlayCircle} size="lg" />
</Link> </Link>
</div> </div>
<div <div className={`container-fluid bg-light pt-3 mt-2 ${isOpen(item.id) ? '' : 'description-collapsed'}`}>
className={`container-fluid bg-light pt-3 mt-2 ${ {levelMap[level] && <p className="tag pt-2 pr-1 font-weight-bold small text-uppercase">{levelMap[level]}</p>}
isOpen(item.id) ? '' : 'description-collapsed'
}`}
>
{levelMap[level] && (
<p className="tag pt-2 pr-1 font-weight-bold small text-uppercase">
{levelMap[level]}
</p>
)}
{selectedRepo.entities[item.id].metadata.data.tags?.map( {selectedRepo.entities[item.id].metadata.data.tags?.map((tag: string) => (
(tag: string) => ( <p key={tag} className="tag pr-1 font-weight-bold small text-uppercase">
<p
key={tag}
className="tag pr-1 font-weight-bold small text-uppercase"
>
{tag} {tag}
</p> </p>
), ))}
)}
{selectedRepo.entities[item.id].steps && ( {selectedRepo.entities[item.id].steps && <div className="d-none">{selectedRepo.entities[item.id].steps.length} step(s)</div>}
<div className="d-none">
{selectedRepo.entities[item.id].steps.length} step(s)
</div>
)}
<div className="workshop-list_description pb-3 pt-3"> <div className="workshop-list_description pb-3 pt-3">
<Markdown <Markdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
>
{selectedRepo.entities[item.id].description?.content} {selectedRepo.entities[item.id].description?.content}
</Markdown> </Markdown>
</div> </div>
@ -130,7 +94,7 @@ function HomePage(): JSX.Element {
</div> </div>
)} )}
</div> </div>
); )
} }
export default HomePage; export default HomePage

@ -13,11 +13,6 @@ header, footer {
align-items: center; align-items: center;
} }
.menuspacer{
// padding-top: 48px;
}
.errorloadingspacer{ .errorloadingspacer{
padding-top: 44px; padding-top: 44px;

@ -11,7 +11,6 @@ header {
.menuspacer{ .menuspacer{
margin-top: 52px; margin-top: 52px;
} }
.steplink { .steplink {
@ -32,13 +31,6 @@ section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.start { .start {
padding: 5px 25px; padding: 5px 25px;
animation: jittery 2s 0.5s infinite; animation: jittery 2s 0.5s infinite;

@ -1,5 +1,5 @@
import { useDispatch, type TypedUseSelectorHook, useSelector } from 'react-redux'; import {useDispatch, type TypedUseSelectorHook, useSelector} from 'react-redux'
import { type AppDispatch, type RootState } from './store'; import {type AppDispatch, type RootState} from './store'
export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

@ -1,14 +1,14 @@
import { type ModelType } from '../store'; import {type ModelType} from '../store'
const Model: ModelType = { const Model: ModelType = {
namespace: 'loading', namespace: 'loading',
state: {screen: true}, state: {screen: true},
reducers: { reducers: {
save(state, {payload}) { save(state, {payload}) {
return { ...state, ...payload }; return {...state, ...payload}
}, },
}, },
effects: {}, effects: {},
}; }
export default Model; export default Model

@ -1,13 +1,13 @@
import axios from 'axios'; import axios from 'axios'
import { toast } from 'react-toastify'; import {toast} from 'react-toastify'
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy'
import pick from 'lodash/pick'; import pick from 'lodash/pick'
import { type ModelType } from '../store'; import {type ModelType} from '../store'
import remixClient from '../../remix-client'; import remixClient from '../../remix-client'
import { router } from '../../App'; import {router} from '../../App'
// const apiUrl = 'http://localhost:3001'; // const apiUrl = 'http://localhost:3001';
const apiUrl = 'https://static.220.14.12.49.clients.your-server.de:3000'; const apiUrl = 'https://static.220.14.12.49.clients.your-server.de:3000'
const Model: ModelType = { const Model: ModelType = {
namespace: 'workshop', namespace: 'workshop',
@ -18,19 +18,19 @@ const Model: ModelType = {
}, },
reducers: { reducers: {
save(state, {payload}) { save(state, {payload}) {
return { ...state, ...payload }; return {...state, ...payload}
}, },
}, },
effects: { effects: {
*init(_, {put}) { *init(_, {put}) {
const cache = localStorage.getItem('workshop.state'); const cache = localStorage.getItem('workshop.state')
if (cache) { if (cache) {
const workshopState = JSON.parse(cache); const workshopState = JSON.parse(cache)
yield put({ yield put({
type: 'workshop/save', type: 'workshop/save',
payload: workshopState, payload: workshopState,
}); })
} else { } else {
yield put({ yield put({
type: 'workshop/loadRepo', type: 'workshop/loadRepo',
@ -38,28 +38,26 @@ const Model: ModelType = {
name: 'ethereum/remix-workshops', name: 'ethereum/remix-workshops',
branch: 'master', branch: 'master',
}, },
}); })
} }
}, },
*loadRepo({payload}, {put, select}) { *loadRepo({payload}, {put, select}) {
toast.info(`loading ${payload.name}/${payload.branch}`); toast.info(`loading ${payload.name}/${payload.branch}`)
yield put({ yield put({
type: 'loading/save', type: 'loading/save',
payload: { payload: {
screen: true, screen: true,
}, },
}); })
const { list, detail } = yield select((state) => state.workshop); const {list, detail} = yield select((state) => state.workshop)
const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${ const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${payload.branch}?${Math.random()}`
payload.branch console.log('loading ', url)
}?${Math.random()}`; const {data} = yield axios.get(url)
console.log('loading ', url); console.log(data)
const { data } = yield axios.get(url); const repoId = `${payload.name}-${payload.branch}`
console.log(data);
const repoId = `${payload.name}-${payload.branch}`;
for (let i = 0; i < data.ids.length; i++) { for (let i = 0; i < data.ids.length; i++) {
const { const {
@ -67,51 +65,40 @@ const Model: ModelType = {
metadata: { metadata: {
data: {steps: metadataSteps}, data: {steps: metadataSteps},
}, },
} = data.entities[data.ids[i]]; } = data.entities[data.ids[i]]
let newSteps = []; let newSteps = []
if (metadataSteps) { if (metadataSteps) {
newSteps = metadataSteps.map((step: any) => { newSteps = metadataSteps.map((step: any) => {
return { return {
...steps.find((item: any) => item.name === step.path), ...steps.find((item: any) => item.name === step.path),
name: step.name, name: step.name,
}; }
}); })
} else { } else {
newSteps = steps.map((step: any) => ({ newSteps = steps.map((step: any) => ({
...step, ...step,
name: step.name.replace('_', ' '), name: step.name.replace('_', ' '),
})); }))
} }
const stepKeysWithFile = [ const stepKeysWithFile = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy']
'markdown',
'solidity',
'test',
'answer',
'js',
'vy',
];
for (let j = 0; j < newSteps.length; j++) { for (let j = 0; j < newSteps.length; j++) {
const step = newSteps[j]; const step = newSteps[j]
for (let k = 0; k < stepKeysWithFile.length; k++) { for (let k = 0; k < stepKeysWithFile.length; k++) {
const key = stepKeysWithFile[k]; const key = stepKeysWithFile[k]
if (step[key]) { if (step[key]) {
try { try {
step[key].content = (yield remixClient.call( step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
'contentImport',
'resolve',
step[key].file,
)).content;
} catch (error) { } catch (error) {
console.error(error); console.error(error)
} }
} }
} }
} }
data.entities[data.ids[i]].steps = newSteps; data.entities[data.ids[i]].steps = newSteps
} }
const workshopState = { const workshopState = {
@ -120,39 +107,37 @@ const Model: ModelType = {
[repoId]: { [repoId]: {
...data, ...data,
group: groupBy( group: groupBy(
data.ids.map((id: string) => data.ids.map((id: string) => pick(data.entities[id], ['level', 'id'])),
pick(data.entities[id], ['level', 'id']), (item: any) => item.level
),
(item: any) => item.level,
), ),
...payload, ...payload,
}, },
}, },
list: detail[repoId] ? list : [...list, payload], list: detail[repoId] ? list : [...list, payload],
selectedId: repoId, selectedId: repoId,
}; }
yield put({ yield put({
type: 'workshop/save', type: 'workshop/save',
payload: workshopState, payload: workshopState,
}); })
localStorage.setItem('workshop.state', JSON.stringify(workshopState)); localStorage.setItem('workshop.state', JSON.stringify(workshopState))
toast.dismiss(); toast.dismiss()
yield put({ yield put({
type: 'loading/save', type: 'loading/save',
payload: { payload: {
screen: false, screen: false,
}, },
}); })
if (payload.id) { if (payload.id) {
const { detail, selectedId } = workshopState; const {detail, selectedId} = workshopState
const { ids, entities } = detail[selectedId]; const {ids, entities} = detail[selectedId]
for (let i = 0; i < ids.length; i++) { for (let i = 0; i < ids.length; i++) {
const entity = entities[ids[i]]; const entity = entities[ids[i]]
if (entity.metadata.data.id === payload.id || i + 1 === payload.id) { if (entity.metadata.data.id === payload.id || i + 1 === payload.id) {
yield router.navigate(`/list?id=${ids[i]}`); yield router.navigate(`/list?id=${ids[i]}`)
break; break
} }
} }
} }
@ -165,15 +150,15 @@ const Model: ModelType = {
detail: {}, detail: {},
selectedId: '', selectedId: '',
}, },
}); })
localStorage.removeItem('workshop.state'); localStorage.removeItem('workshop.state')
yield put({ yield put({
type: 'workshop/init', type: 'workshop/init',
}); })
}, },
}, },
}; }
export default Model; export default Model

@ -1,117 +1,97 @@
import { import {configureStore, createSlice, type PayloadAction, type Reducer} from '@reduxjs/toolkit'
configureStore, import createSagaMiddleware from 'redux-saga'
createSlice, import {call, put, takeEvery, delay, select, all, fork, type ForkEffect} from 'redux-saga/effects'
type PayloadAction,
type Reducer,
} from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import {
call,
put,
takeEvery,
delay,
select,
all,
fork,
type ForkEffect,
} from 'redux-saga/effects';
// @ts-expect-error // @ts-expect-error
const context = require.context('./models', false, /\.ts$/); const context = require.context('./models', false, /\.ts$/)
const models = context.keys().map((key: any) => context(key).default); const models = context.keys().map((key: any) => context(key).default)
export type StateType = Record<string, any>; export type StateType = Record<string, any>
export interface ModelType { export interface ModelType {
namespace: string; namespace: string
state: StateType; state: StateType
reducers: Record< reducers: Record<string, (state: StateType, action: PayloadAction<any>) => StateType>
string,
(state: StateType, action: PayloadAction<any>) => StateType
>;
effects: Record< effects: Record<
string, string,
( (
action: PayloadAction<any>, action: PayloadAction<any>,
effects: { effects: {
call: typeof call; call: typeof call
put: typeof put; put: typeof put
delay: typeof delay; delay: typeof delay
select: typeof select; select: typeof select
}, }
) => Generator<any, void, any> ) => Generator<any, void, any>
>; >
} }
function createReducer(model: ModelType): Reducer { function createReducer(model: ModelType): Reducer {
const reducers = model.reducers; const reducers = model.reducers
Object.keys(model.effects).forEach((key) => { Object.keys(model.effects).forEach((key) => {
reducers[key] = (state: StateType, action: PayloadAction<any>) => state; reducers[key] = (state: StateType, action: PayloadAction<any>) => state
}); })
const slice = createSlice({ const slice = createSlice({
name: model.namespace, name: model.namespace,
initialState: model.state, initialState: model.state,
reducers, reducers,
}); })
return slice.reducer; return slice.reducer
} }
const rootReducer = models.reduce((prev: any, model: ModelType) => { const rootReducer = models.reduce((prev: any, model: ModelType) => {
return { ...prev, [model.namespace]: createReducer(model) }; return {...prev, [model.namespace]: createReducer(model)}
}, {}); }, {})
function watchEffects(model: ModelType): ForkEffect { function watchEffects(model: ModelType): ForkEffect {
return fork(function* () { return fork(function* () {
for (const key in model.effects) { for (const key in model.effects) {
const effect = model.effects[key]; const effect = model.effects[key]
yield takeEvery( yield takeEvery(`${model.namespace}/${key}`, function* (action: PayloadAction) {
`${model.namespace}/${key}`,
function* (action: PayloadAction) {
yield put({ yield put({
type: 'loading/save', type: 'loading/save',
payload: { payload: {
[`${model.namespace}/${key}`]: true, [`${model.namespace}/${key}`]: true,
}, },
}); })
yield effect(action, { yield effect(action, {
call, call,
put, put,
delay, delay,
select, select,
}); })
yield put({ yield put({
type: 'loading/save', type: 'loading/save',
payload: { payload: {
[`${model.namespace}/${key}`]: false, [`${model.namespace}/${key}`]: false,
}, },
}); })
}, })
);
} }
}); })
} }
function* rootSaga(): Generator { function* rootSaga(): Generator {
yield all(models.map((model: ModelType) => watchEffects(model))); yield all(models.map((model: ModelType) => watchEffects(model)))
} }
const configureAppStore = (initialState = {}) => { const configureAppStore = (initialState = {}) => {
const reduxSagaMonitorOptions = {}; const reduxSagaMonitorOptions = {}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions); const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions)
const middleware = [sagaMiddleware]; const middleware = [sagaMiddleware]
const store = configureStore({ const store = configureStore({
reducer: rootReducer, reducer: rootReducer,
middleware: (gDM) => gDM().concat([...middleware]), middleware: (gDM) => gDM().concat([...middleware]),
preloadedState: initialState, preloadedState: initialState,
devTools: process.env.NODE_ENV !== 'production', devTools: process.env.NODE_ENV !== 'production',
}); })
sagaMiddleware.run(rootSaga); sagaMiddleware.run(rootSaga)
return store; return store
}; }
export const store = configureAppStore(); export const store = configureAppStore()
export type AppDispatch = typeof store.dispatch; export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>; export type RootState = ReturnType<typeof store.getState>

@ -1,17 +1,17 @@
import { PluginClient } from '@remixproject/plugin'; import {PluginClient} from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'; import {createClient} from '@remixproject/plugin-webview'
import { store } from './redux/store'; import {store} from './redux/store'
import { router } from './App'; import {router} from './App'
class RemixClient extends PluginClient { class RemixClient extends PluginClient {
constructor() { constructor() {
super(); super()
createClient(this); createClient(this)
} }
startTutorial(name: any, branch: any, id: any): void { startTutorial(name: any, branch: any, id: any): void {
console.log('start tutorial', name, branch, id); console.log('start tutorial', name, branch, id)
void router.navigate('/home'); void router.navigate('/home')
store.dispatch({ store.dispatch({
type: 'workshop/loadRepo', type: 'workshop/loadRepo',
payload: { payload: {
@ -19,20 +19,20 @@ class RemixClient extends PluginClient {
branch, branch,
id, id,
}, },
}); })
} }
addRepository(name: any, branch: any) { addRepository(name: any, branch: any) {
console.log('add repo', name, branch); console.log('add repo', name, branch)
void router.navigate('/home'); void router.navigate('/home')
store.dispatch({ store.dispatch({
type: 'workshop/loadRepo', type: 'workshop/loadRepo',
payload: { payload: {
name, name,
branch, branch,
}, },
}); })
} }
} }
export default new RemixClient(); export default new RemixClient()

@ -1,7 +1,7 @@
const {composePlugins, withNx} = require('@nrwl/webpack') const {composePlugins, withNx} = require('@nrwl/webpack')
const webpack = require('webpack') const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin") const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// Nx plugins for webpack. // Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => { module.exports = composePlugins(withNx(), (config) => {
@ -10,26 +10,25 @@ module.exports = composePlugins(withNx(), (config) => {
// add fallback for node modules // add fallback for node modules
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"), crypto: require.resolve('crypto-browserify'),
"stream": require.resolve("stream-browserify"), stream: require.resolve('stream-browserify'),
"path": require.resolve("path-browserify"), path: require.resolve('path-browserify'),
"http": require.resolve("stream-http"), http: require.resolve('stream-http'),
"https": require.resolve("https-browserify"), https: require.resolve('https-browserify'),
"constants": require.resolve("constants-browserify"), constants: require.resolve('constants-browserify'),
"os": false, //require.resolve("os-browserify/browser"), os: false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"), timers: false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"), zlib: require.resolve('browserify-zlib'),
"fs": false, fs: false,
"module": false, module: false,
"tls": false, tls: false,
"net": false, net: false,
"readline": false, readline: false,
"child_process": false, child_process: false,
"buffer": require.resolve("buffer/"), buffer: require.resolve('buffer/'),
"vm": require.resolve('vm-browserify'), vm: require.resolve('vm-browserify'),
} }
// add externals // add externals
config.externals = { config.externals = {
...config.externals, ...config.externals,
@ -58,13 +57,12 @@ module.exports = composePlugins(withNx(), (config) => {
// souce-map loader // souce-map loader
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre',
}) })
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({
@ -80,13 +78,13 @@ module.exports = composePlugins(withNx(), (config) => {
extractComments: false, extractComments: false,
}), }),
new CssMinimizerPlugin(), new CssMinimizerPlugin(),
]; ]
config.watchOptions = { config.watchOptions = {
ignored: /node_modules/ ignored: /node_modules/,
} }
config.experiments.syncWebAssembly = true config.experiments.syncWebAssembly = true
return config; return config
}); })

Loading…
Cancel
Save