├── .eslintrc.json ├── .firebaserc ├── .gitignore ├── LICENSE ├── README.md ├── firebase.json ├── package.json ├── public ├── 404.html ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── mstile-150x150.png ├── offline.html ├── preview.png ├── robots.txt └── safari-pinned-tab.svg ├── src ├── App.tsx ├── CustomDragLayer.tsx ├── components │ ├── AlertSnackbar.tsx │ ├── ErrorBoundary.tsx │ ├── ErrorBoundaryRecover.tsx │ ├── Footer.tsx │ ├── Loading.tsx │ ├── Simulator.tsx │ ├── SvgButton.tsx │ ├── TabSelect.tsx │ ├── actions │ │ ├── ActionButton.tsx │ │ ├── Actions.tsx │ │ ├── EditElementButton.tsx │ │ └── SimulateButton.tsx │ ├── diagram │ │ ├── AdSense.tsx │ │ ├── AddBanner.tsx │ │ ├── BlockCoil.tsx │ │ ├── BlockCompare.tsx │ │ ├── BlockContact.tsx │ │ ├── BlockCounter.tsx │ │ ├── BlockHelp.tsx │ │ ├── BlockMath.tsx │ │ ├── BlockMove.tsx │ │ ├── BlockTimer.tsx │ │ ├── Branch.tsx │ │ ├── BranchRung.tsx │ │ ├── Diagram.tsx │ │ ├── DiagramHelp.tsx │ │ ├── DraggableBlock.tsx │ │ ├── LadderBlock.tsx │ │ ├── PowerRail.tsx │ │ ├── Rung.tsx │ │ ├── RungHelp.tsx │ │ ├── Wire.tsx │ │ └── block-components │ │ │ ├── BlockInOut.tsx │ │ │ ├── BlockParam.tsx │ │ │ ├── BlockParamDesc.tsx │ │ │ ├── BlockParamValue.tsx │ │ │ ├── BlockType.tsx │ │ │ ├── BlockVarName.tsx │ │ │ ├── FunctionBlock.tsx │ │ │ └── MiddleLineBox.tsx │ ├── menu │ │ ├── Help.tsx │ │ ├── Menu.tsx │ │ ├── PassRecover.tsx │ │ ├── ShareButton.tsx │ │ ├── SignButton.tsx │ │ ├── SignIn.tsx │ │ ├── SignOut.tsx │ │ ├── SignUp.tsx │ │ ├── TutorialVideo.tsx │ │ ├── shareLink.ts │ │ └── useOnline.ts │ ├── properties │ │ ├── Properties.tsx │ │ ├── PropertiesCoilType.tsx │ │ ├── PropertiesCompareType.tsx │ │ ├── PropertiesContactType.tsx │ │ ├── PropertiesCounterType.tsx │ │ ├── PropertiesMathType.tsx │ │ ├── PropertiesParameters.tsx │ │ ├── PropertiesTimerType.tsx │ │ └── PropertiesTypeOption.tsx │ ├── toolbox │ │ ├── Toolbox.tsx │ │ ├── ToolboxBlock.tsx │ │ ├── ToolboxBranch.tsx │ │ ├── ToolboxIcon.tsx │ │ ├── ToolboxRung.tsx │ │ └── elements.ts │ └── variables │ │ ├── NewVarHelp.tsx │ │ ├── VariableDelete.tsx │ │ ├── VariableName.tsx │ │ ├── VariableTable.tsx │ │ ├── VariableTableFoot.tsx │ │ ├── VariableTableSubVarRow.tsx │ │ ├── VariableTableVarRow.tsx │ │ ├── VariableValue.tsx │ │ ├── VariableValueBool.tsx │ │ ├── VariableValueNumber.tsx │ │ └── VariableValueShowSubVars.tsx ├── consts │ ├── blockDimensions.ts │ ├── colors.ts │ ├── consts.ts │ ├── elementTypes.ts │ ├── itemTypes.ts │ ├── ladderObjects.ts │ ├── variableTableStyles.ts │ └── variables.ts ├── helpers │ ├── addBranch.ts │ ├── addRung.ts │ ├── addVariable.ts │ ├── assignParameter.ts │ ├── cycleScan.ts │ ├── deleteObject.ts │ ├── deleteVariable.ts │ ├── firebase.ts │ ├── simulationObjects.ts │ └── variables.ts ├── images │ └── conveyor-simulator-banner.png ├── index.css ├── index.tsx ├── interface.ts ├── react-app-env.d.ts ├── service-worker.ts ├── serviceWorkerRegistration.ts ├── store │ ├── const.ts │ ├── simulator.ts │ ├── store.ts │ └── types.ts └── svg │ ├── arrowDownRound.svg │ ├── arrowLeftRound.svg │ ├── arrowRightRound.svg │ ├── arrowUpRound.svg │ ├── binDelete.svg │ ├── confirm.svg │ ├── editPencil.svg │ ├── expandLess.svg │ ├── expandMore.svg │ ├── fileEmpty.svg │ ├── fileText.svg │ ├── help.svg │ ├── lock.svg │ ├── redo.svg │ ├── reject.svg │ ├── share.svg │ ├── simulationStart.svg │ ├── simulationStop.svg │ ├── toolbox │ ├── branch.svg │ ├── coil.svg │ ├── compare.svg │ ├── contact.svg │ ├── counter.svg │ ├── math.svg │ ├── move.svg │ ├── rung.svg │ └── timer.svg │ ├── undo.svg │ ├── varDelete.svg │ └── wifi-off.svg ├── tests ├── create-test.sh └── template.test.js ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:react/recommended" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "jsx": true 19 | }, 20 | "ecmaVersion": 2018, 21 | "sourceType": "module" 22 | }, 23 | "plugins": ["react", "@typescript-eslint"], 24 | "rules": { 25 | "no-unused-vars": [1, { "vars": "all", "args": "none" }], 26 | "react/react-in-jsx-scope": "off", 27 | "no-console": ["warn", { "allow": ["warn", "error"] }], 28 | "react/prop-types": "off" 29 | }, 30 | "settings": { 31 | "react": { 32 | "createClass": "createReactClass", // Regex for Component Factory to use, 33 | // default to "createReactClass" 34 | "pragma": "React", // Pragma to use, default to "React" 35 | "fragment": "Fragment", // Fragment to use (may be a property of ), default to "Fragment" 36 | "version": "detect", // React version. "detect" automatically picks the version you have installed. 37 | // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. 38 | // default to latest and warns if missing 39 | // It will default to "detect" in the future 40 | "flowVersion": "0.53" // Flow version 41 | }, 42 | "propWrapperFunctions": [ 43 | // The names of any function used to wrap propTypes, e.g. `forbidExtraProps`. If this isn't set, any propTypes wrapped in a function will be skipped. 44 | "forbidExtraProps", 45 | { "property": "freeze", "object": "Object" }, 46 | { "property": "myFavoriteWrapper" } 47 | ], 48 | "linkComponents": [ 49 | // Components used as alternatives to for linking, eg. 50 | "Hyperlink", 51 | { "name": "Link", "linkAttribute": "to" } 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "coding-plc-90047" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .eslintcache 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # deploy 16 | /.firebase 17 | 18 | # misc 19 | /.vscode 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | public/sw.js.map 30 | public/sw.js 31 | 32 | public/workbox-*.js 33 | public/workbox-*.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PLC Simulator Online 2 | 3 | Welcome to the PLC Simulator project! This project provides an open-source PLC (Programmable Logic Controller) simulator designed to help users practice and develop their PLC programming skills. 4 | 5 | ## About 6 | 7 | The PLC Simulator is a tool for simulating the behavior of PLCs, enabling users to write and test PLC code without needing physical hardware. The simulator supports Ladder Diagram language and provides an environment where users can learn, experiment, and validate their PLC programs. 8 | 9 | ## Installation 10 | 11 | ### Clone the Repository: 12 | 13 | ```bash 14 | git clone https://github.com/codingplc/plc-simulator.git 15 | cd plc-simulator 16 | ``` 17 | 18 | ### Install Dependencies: 19 | 20 | ```bash 21 | yarn install 22 | ``` 23 | 24 | ### Run the Simulator: 25 | 26 | ```bash 27 | yarn start 28 | ``` 29 | 30 | ## Contributing 31 | 32 | We welcome contributions from the community! Please follow these steps to contribute: 33 | 34 | 1. **Fork the Repository**: Click the 'Fork' button and clone your forked repository locally. 35 | 2. **Create a New Branch**: Run `git checkout -b branch-name` to create a new branch for your changes. 36 | 3. **Make Your Changes**: Implement your changes in the project. 37 | 4. **Commit Your Changes**: Commit your changes using `git commit -m "Description of changes"`. 38 | 5. **Push to GitHub**: Push your changes with `git push origin branch-name`. 39 | 6. **Submit a Pull Request**: Create a pull request from your branch to the main repository. 40 | 41 | ## License 42 | 43 | This project is licensed under the GNU General Public License v3.0. See the [LICENSE](https://github.com/codingplc/plc-simulator/blob/main/LICENSE) file for details. 44 | 45 | ## Reporting Issues 46 | 47 | Please use the [Issues](https://github.com/codingplc/plc-simulator/issues) section to report bugs or request new features. Provide as much detail as possible to help us address your issues effectively. 48 | 49 | ## Contact 50 | 51 | If you have any questions, feel free to reach out via [email](mailto:info@codingplc.com). 52 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "site": "ladder-simulator", 4 | "public": "build", 5 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 6 | "rewrites": [ 7 | { 8 | "source": "**", 9 | "destination": "/index.html" 10 | } 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plc-simulator", 3 | "homepage": "https://app.plcsimulator.online/", 4 | "version": "1.6.2", 5 | "private": true, 6 | "dependencies": { 7 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 8 | "@emotion/react": "^11.10.6", 9 | "@emotion/styled": "^11.10.6", 10 | "@mui/icons-material": "^5.14.16", 11 | "@mui/lab": "^5.0.0-alpha.152", 12 | "@mui/material": "^5.14.17", 13 | "@mui/styles": "^5.14.17", 14 | "@sentry/react": "^8.7.0", 15 | "firebase": "^9.19.1", 16 | "immer": "^9.0.21", 17 | "localforage": "^1.9.0", 18 | "nanoid": "^5.0.5", 19 | "react": "^18.2.0", 20 | "react-dnd": "^16.0.1", 21 | "react-dnd-html5-backend": "^16.0.1", 22 | "react-dom": "^18.2.0", 23 | "react-firebase-hooks": "3.0.5", 24 | "react-icons": "^4.11.0", 25 | "react-redux": "^8.1.3", 26 | "react-scripts": "^5.0.1", 27 | "react-select": "^5.8.0", 28 | "redux": "^4.0.5", 29 | "redux-persist": "^6.0.0", 30 | "styled-components": "^5.2.0" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20.8.10", 34 | "@types/react": "^18.2.36", 35 | "@types/react-dom": "^18.2.14", 36 | "@types/react-redux": "^7.1.29", 37 | "@types/styled-components": "^5.1.29", 38 | "@typescript-eslint/eslint-plugin": "^6.10.0", 39 | "@typescript-eslint/parser": "^6.10.0", 40 | "eslint": "^8.53.0", 41 | "eslint-config-prettier": "^9.0.0", 42 | "eslint-plugin-react": "^7.33.2", 43 | "typescript": "^5.2.2" 44 | }, 45 | "scripts": { 46 | "start": "react-scripts start", 47 | "build": "react-scripts build", 48 | "test": "react-scripts test", 49 | "eject": "react-scripts eject", 50 | "predeploy": "npm run build", 51 | "deploy": " react-scripts build && firebase deploy", 52 | "deploy-beta": "react-scripts build && firebase hosting:channel:deploy beta", 53 | "set-release-env": "ts-node set-release-env.ts" 54 | }, 55 | "eslintConfig": { 56 | "extends": "react-app" 57 | }, 58 | "browserslist": { 59 | "production": [ 60 | ">0.2%", 61 | "not dead", 62 | "not op_mini all" 63 | ], 64 | "development": [ 65 | "last 1 chrome version", 66 | "last 1 firefox version", 67 | "last 1 safari version" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PLC Simulator OFFLINE 8 | 9 | 10 | 11 |

This page does not exist.

12 | 13 | 14 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "PLC-Sim", 3 | "name": "PLC Simulator Online", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "orientation": "portrait", 24 | "theme_color": "#000000", 25 | "background_color": "#ffffff" 26 | } 27 | -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PLC Simulator OFFLINE 8 | 9 | 10 | 11 |

Looks like you are offline. Please check back when connected to the Internet.

12 | 13 | 14 | -------------------------------------------------------------------------------- /public/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingplc/plc-simulator/0111a68e3b8d7ffea4c70e230973049150246b2d/public/preview.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import { PersistGate } from 'redux-persist/integration/react'; 3 | import * as serviceWorkerRegistration from './serviceWorkerRegistration'; 4 | import './index.css'; 5 | import 'firebase/compat/analytics'; 6 | import firebase from './helpers/firebase'; 7 | import ErrorBoundary from './components/ErrorBoundary'; 8 | import Simulator from './components/Simulator'; 9 | import Loading from './components/Loading'; 10 | import { loadDiagram, persistor, store } from './store/store'; 11 | import { CssBaseline } from '@mui/material'; 12 | import { ThemeProvider, createTheme } from '@mui/material/styles'; 13 | import { DndProvider } from 'react-dnd'; 14 | import { HTML5Backend } from 'react-dnd-html5-backend'; 15 | import { CustomDragLayer } from './CustomDragLayer'; 16 | 17 | const theme = createTheme(); 18 | 19 | firebase.analytics(); 20 | 21 | loadDiagram(); 22 | 23 | export default function App() { 24 | return ( 25 | 26 | } persistor={persistor}> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | 41 | serviceWorkerRegistration.register(); 42 | -------------------------------------------------------------------------------- /src/CustomDragLayer.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from 'react'; 2 | import type { XYCoord } from 'react-dnd'; 3 | import { useDragLayer } from 'react-dnd'; 4 | import { Box } from '@mui/material'; 5 | import { BLOCK, BRANCH, TOOL_RUNG } from './consts/itemTypes'; 6 | import LadderBlock from './components/diagram/LadderBlock'; 7 | import Wire from './components/diagram/Wire'; 8 | import { POWER_RAIL_WIDTH } from './consts/blockDimensions'; 9 | import { ELEMENT } from './consts/colors'; 10 | import { Identifier } from 'dnd-core'; 11 | 12 | const layerStyles: CSSProperties = { 13 | position: 'fixed', 14 | pointerEvents: 'none', 15 | zIndex: 100, 16 | left: 0, 17 | top: 0, 18 | width: '100%', 19 | height: '100%', 20 | }; 21 | 22 | function getItemStyles(itemType: Identifier | null, initialOffset: XYCoord | null, currentOffset: XYCoord | null, clientOffset: XYCoord | null) { 23 | if (!initialOffset || !currentOffset || !clientOffset) { 24 | return { 25 | display: 'none', 26 | }; 27 | } 28 | 29 | let x = clientOffset.x; 30 | const y = clientOffset.y; 31 | if (itemType === BRANCH) x = x - 32; 32 | if (itemType === TOOL_RUNG) x = x - 120; 33 | 34 | const transform = `translate(${x}px, ${y}px)`; 35 | return { 36 | transform, 37 | WebkitTransform: transform, 38 | }; 39 | } 40 | 41 | export const CustomDragLayer = () => { 42 | const { itemType, isDragging, item, initialOffset, currentOffset, clientOffset } = useDragLayer((monitor) => ({ 43 | item: monitor.getItem(), 44 | itemType: monitor.getItemType(), 45 | initialOffset: monitor.getInitialSourceClientOffset(), 46 | clientOffset: monitor.getClientOffset(), 47 | currentOffset: monitor.getSourceClientOffset(), 48 | isDragging: monitor.isDragging(), 49 | })); 50 | 51 | function renderItem() { 52 | switch (itemType) { 53 | case BLOCK: 54 | return ( 55 | 60 | 61 | 62 | ); 63 | case BRANCH: 64 | return ( 65 | 75 | ); 76 | case TOOL_RUNG: 77 | return ( 78 | 86 | 87 | 95 | 96 | 97 | 104 | 105 | ); 106 | default: 107 | return null; 108 | } 109 | } 110 | 111 | if (!isDragging) { 112 | return null; 113 | } 114 | return ( 115 |
116 |
{renderItem()}
117 |
118 | ); 119 | }; 120 | -------------------------------------------------------------------------------- /src/components/AlertSnackbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { Alert, IconButton, Snackbar } from '@mui/material'; 4 | import { Store } from '../interface'; 5 | import { CLOSE_ALERT_SNACKBAR } from '../store/types'; 6 | import { Close } from '@mui/icons-material'; 7 | 8 | const AlertSnackbar: React.FC = () => { 9 | const { color, open, text } = useSelector((state: Store) => state.temp.alertSnackbar); 10 | const dispatch = useDispatch(); 11 | 12 | return ( 13 | dispatch({ type: CLOSE_ALERT_SNACKBAR })} 18 | > 19 | dispatch({ type: CLOSE_ALERT_SNACKBAR })} 26 | > 27 | 28 | 29 | } 30 | severity={color} 31 | color={color} 32 | variant="filled" 33 | > 34 | {text} 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default AlertSnackbar; 41 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ErrorBoundryRecover from './ErrorBoundaryRecover'; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | } 8 | 9 | class ErrorBoundary extends React.Component { 10 | constructor(props: Props) { 11 | super(props); 12 | this.state = { hasError: false }; 13 | } 14 | 15 | static getDerivedStateFromError() { 16 | // Zaktualizuj stan, aby następny render pokazał zastępcze UI. 17 | return { hasError: true }; 18 | } 19 | 20 | componentDidCatch(error: any, errorInfo: any) { 21 | // Możesz także zalogować błąd do zewnętrznego serwisu raportowania błędów 22 | console.error('LOGGER:', error, errorInfo); 23 | } 24 | 25 | render() { 26 | if (this.state.hasError) { 27 | // Możesz wyrenderować dowolny interfejs zastępczy. 28 | return ; 29 | } 30 | return this.props.children; 31 | } 32 | } 33 | 34 | export default ErrorBoundary; 35 | -------------------------------------------------------------------------------- /src/components/ErrorBoundaryRecover.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import styled from 'styled-components'; 4 | 5 | import { LOAD_EMPTY, LOAD_SAMPLE } from '../store/types'; 6 | import { BG_ERROR } from '../consts/colors'; 7 | 8 | import { ReactComponent as FileText } from '../svg/fileText.svg'; 9 | import { ReactComponent as FileEmpty } from '../svg/fileEmpty.svg'; 10 | 11 | const Container = styled.div` 12 | background: ${BG_ERROR}; 13 | height: 100%; 14 | padding-top: 2rem; 15 | `; 16 | const H1 = styled.h1` 17 | margin-bottom: 4rem; 18 | text-align: center; 19 | `; 20 | const LoadEmpty = styled(FileEmpty)` 21 | height: 4rem; 22 | `; 23 | const LoadSample = styled(FileText)` 24 | height: 4rem; 25 | `; 26 | const Wrapper = styled.div` 27 | display: flex; 28 | justify-content: space-between; 29 | margin: 1rem auto; 30 | max-width: 20rem; 31 | `; 32 | 33 | const ErrorBoundryRecover: React.FC = () => { 34 | const dispatch = useDispatch(); 35 | const handeClick = (type: string) => { 36 | dispatch({ type }); 37 | setTimeout(function () { 38 | window.location.reload(); 39 | }, 100); 40 | }; 41 | return ( 42 | 43 |

Something went wrong.

44 | 45 |

Load empty project

46 | handeClick(LOAD_EMPTY)} /> 47 |
48 | 49 |

Load sample project

50 | handeClick(LOAD_SAMPLE)} /> 51 |
52 |
53 | ); 54 | }; 55 | 56 | export default ErrorBoundryRecover; 57 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Link, Typography } from '@mui/material'; 2 | import { FiInfo, FiMail } from 'react-icons/fi'; 3 | import styled from 'styled-components'; 4 | 5 | const Container = styled.div` 6 | background-color: #abcdef; 7 | bottom: 0; 8 | display: flex; 9 | grid-area: footer; 10 | height: 1.75rem; 11 | justify-content: space-between; 12 | left: 0; 13 | position: relative; 14 | right: 0; 15 | `; 16 | 17 | type Props = { 18 | mobileUI: boolean; 19 | }; 20 | 21 | export default function Footer({ mobileUI }: Props) { 22 | return ( 23 | 24 | {!mobileUI && ( 25 | 26 | 29 | 32 | 33 | )} 34 | 35 | 36 | 37 | SUPPORT US ON PATREON 38 | 39 | 40 | 41 | 42 | 43 | {'© '} 44 | 45 | CodingPLC 46 | {' '} 47 | {new Date().getFullYear()} 48 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Container = styled.div` 5 | margin-top: 1rem; 6 | `; 7 | 8 | const Loading: React.FC = () => { 9 | return ( 10 | 11 |
12 |

Loading Diagram...

13 |
14 | ); 15 | }; 16 | 17 | export default Loading; 18 | -------------------------------------------------------------------------------- /src/components/Simulator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { Store } from '../interface'; 5 | import { DISPLAY_TAB } from '../consts/consts'; 6 | import Actions from './actions/Actions'; 7 | import Diagram from './diagram/Diagram'; 8 | import Footer from './Footer'; 9 | import TabSelect from './TabSelect'; 10 | import Toolbox from './toolbox/Toolbox'; 11 | import Menu from './menu/Menu'; 12 | import VariableTable from './variables/VariableTable'; 13 | import AlertSnackbar from './AlertSnackbar'; 14 | import { DELETE_OBJECT } from '../store/types'; 15 | 16 | const Desktop = styled.div` 17 | display: grid; 18 | grid-template: auto 1fr auto / 1fr 2fr; 19 | grid-template-areas: 20 | 'header header' 21 | 'variables diagram' 22 | 'footer footer'; 23 | height: 100%; 24 | width: 100%; 25 | `; 26 | const Header = styled.div` 27 | display: flex; 28 | flex-wrap: wrap; 29 | grid-area: header; 30 | width: 100%; 31 | `; 32 | const Mobile = styled.div<{ displayTab: string }>` 33 | display: grid; 34 | grid-template: auto auto auto 1fr auto auto / 100%; 35 | grid-template-areas: 36 | 'menu' 37 | 'toolbox' 38 | 'tab-select' 39 | '${(props) => props.displayTab}' 40 | 'actions' 41 | 'footer'; 42 | height: 100%; 43 | width: 100%; 44 | `; 45 | const Container = styled.div` 46 | font-size: 16px; 47 | height: 100%; 48 | width: 100%; 49 | `; 50 | 51 | const Simulator: React.FC = () => { 52 | const [mobileUI, setMobileUI] = useState(window.innerWidth < 640); 53 | const displayTab = useSelector((state: Store) => state.misc.displayTab); 54 | const displayDiagramTab = displayTab === DISPLAY_TAB.DIAGRAM; 55 | const displayVariablesTab = displayTab === DISPLAY_TAB.VARIABLES; 56 | const dispatch = useDispatch(); 57 | 58 | useEffect(() => { 59 | const handleResize = () => (window.innerWidth < 640 ? setMobileUI(true) : setMobileUI(false)); 60 | window.addEventListener('resize', handleResize); 61 | return () => window.removeEventListener('resize', handleResize); 62 | }); 63 | 64 | const handleOnKeyDown = (e: React.KeyboardEvent) => { 65 | if (e.key !== 'Delete') return; 66 | dispatch({ type: DELETE_OBJECT }); 67 | }; 68 | 69 | return ( 70 | 71 | {mobileUI ? ( 72 | 73 | 74 | 75 | 76 | {displayVariablesTab && } 77 | {displayDiagramTab && } 78 | 79 |