├── src ├── icon.png ├── hooks │ ├── useTypedSelector.ts │ ├── useActions.ts │ └── useTimer.ts ├── data │ └── options.ts ├── state │ ├── slices │ │ ├── modalSlice.ts │ │ └── playersSlice.ts │ └── index.ts ├── components │ ├── Settings │ │ └── Settings.tsx │ ├── Timer │ │ └── Timer.tsx │ ├── PlayerCard │ │ └── Card.tsx │ ├── Modal │ │ └── Modal.tsx │ └── Event │ │ └── Event.jsx ├── index.tsx ├── App.tsx ├── index.html ├── index.css └── utils │ └── reload.ts ├── test ├── index.ts └── test-context.ts ├── .gitignore ├── README.md ├── jsconfig.json ├── package.json ├── globals.d.ts ├── socket.ini └── tsconfig.json /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wujekbizon/life-counter-demo/HEAD/src/icon.png -------------------------------------------------------------------------------- /src/hooks/useTypedSelector.ts: -------------------------------------------------------------------------------- 1 | import { useSelector, TypedUseSelectorHook } from 'react-redux'; 2 | import { RootState } from '../state'; 3 | 4 | export const useTypedSelector: TypedUseSelectorHook = useSelector; 5 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import './test-context'; 2 | 3 | import { test } from 'socket:test'; 4 | import os from 'socket:os'; 5 | 6 | test('test', async (t: any) => { 7 | const label1 = document.querySelector('h1')?.textContent; 8 | t.equal(label1, `Hello, ${os.platform()}`, 'label on start is correct'); 9 | }); 10 | -------------------------------------------------------------------------------- /src/data/options.ts: -------------------------------------------------------------------------------- 1 | export const options = [ 2 | { value: 20, text: 20 }, 3 | { value: 40, text: 40 }, 4 | ]; 5 | 6 | export const playerOptions = [ 7 | { value: 2, text: '2 Players' }, 8 | { value: 3, text: '3 Players' }, 9 | { value: 4, text: '4 Players' }, 10 | { value: 5, text: '5 Players' }, 11 | { value: 6, text: '6 Players' }, 12 | ]; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | *.dat 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | .pnpm-debug.log* 11 | .sscrc 12 | .*.env 13 | 14 | # Ignore all files in the node_modules folder 15 | node_modules/ 16 | 17 | # Default output directory 18 | build/ 19 | 20 | # Provisioning profile 21 | *.mobileprovision 22 | 23 | -------------------------------------------------------------------------------- /src/hooks/useActions.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch } from 'react-redux'; 2 | import { bindActionCreators } from '@reduxjs/toolkit'; 3 | import { useMemo } from 'react'; 4 | import { actionCreators } from '../state/index'; 5 | 6 | export const useActions = () => { 7 | const dispatch = useDispatch(); 8 | 9 | return useMemo(() => { 10 | return bindActionCreators(actionCreators, dispatch); 11 | }, [dispatch]); 12 | }; 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Life-counter App 2 | 3 | #### Socket Runtime Beta 4 | 5 | #### Write once. Run anywhere. Connect everyone. Build mobile and destkop apps for any OS using HTML, CSS, and JavaScript. 6 | 7 | #### Connect users with modern P2P that can make the cloud entirely optional. 8 | 9 | **This application was bootstrapped with npx create-socket-app react-ts** 10 | 11 | 1. To run build on desktop : `npm start` 12 | 13 | 2. To run build on mobile : `npm run android` 14 | -------------------------------------------------------------------------------- /test/test-context.ts: -------------------------------------------------------------------------------- 1 | import { GLOBAL_TEST_RUNNER } from 'socket:test'; 2 | import console from 'socket:console'; 3 | import process from 'socket:process'; 4 | import 'socket:runtime'; 5 | 6 | globalThis.addEventListener('error', onerror); 7 | globalThis.addEventListener('unhandledrejection', onerror); 8 | 9 | function onerror(err: any) { 10 | console.error(err.stack ?? err.reason ?? err.message ?? err); 11 | process.exit(1); 12 | } 13 | 14 | GLOBAL_TEST_RUNNER.onFinish(() => process.exit(0)); 15 | -------------------------------------------------------------------------------- /src/state/slices/modalSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | isMenuOpen: false, 5 | }; 6 | 7 | const modalSlice = createSlice({ 8 | name: 'modal', 9 | initialState, 10 | reducers: { 11 | openSideMenu(state) { 12 | state.isMenuOpen = true; 13 | }, 14 | closeSideMenu(state) { 15 | state.isMenuOpen = false; 16 | }, 17 | }, 18 | }); 19 | 20 | export const { openSideMenu, closeSideMenu } = modalSlice.actions; 21 | 22 | export const modalReducer = modalSlice.reducer; 23 | -------------------------------------------------------------------------------- /src/components/Settings/Settings.tsx: -------------------------------------------------------------------------------- 1 | import { TiThMenuOutline } from 'react-icons/ti'; 2 | import { useActions } from '../../hooks/useActions'; 3 | import os from 'socket:os'; 4 | 5 | const Settings = () => { 6 | const { openSideMenu } = useActions(); 7 | 8 | return ( 9 | 15 | ); 16 | }; 17 | export default Settings; 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import process from 'socket:process' 4 | import { Provider } from 'react-redux' 5 | import { store } from './state/index' 6 | 7 | if (process.env.DEBUG) { 8 | console.log('started in debug mode') 9 | } 10 | 11 | // components 12 | import App from './App' 13 | 14 | const root = createRoot(document.getElementById('root') as HTMLElement) 15 | 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/*", 4 | "src/**/*", 5 | "tests/*", 6 | "tests/**/*" 7 | ], 8 | "compilerOptions": { 9 | "moduleResolution": "node", 10 | "types": [ 11 | "@socketsupply/socket" 12 | ], 13 | "target": "es2022", 14 | "module": "es2022", 15 | "lib": [ 16 | "es2022", 17 | "es6", 18 | "dom" 19 | ], 20 | "removeComments": false, 21 | "checkJs": true, 22 | "allowJs": true, 23 | "noEmit": true, 24 | "alwaysStrict": true, 25 | "strictNullChecks": false, 26 | "declaration": true, 27 | "declarationMap": true, 28 | "baseUrl": "." 29 | } 30 | } -------------------------------------------------------------------------------- /src/hooks/useTimer.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | const SECOND = 1_000; 4 | const MINUTE = SECOND * 60; 5 | const HOUR = MINUTE * 60; 6 | 7 | export const useTimer = (time: any, interval = SECOND) => { 8 | const [timespan, setTimespan] = useState( 9 | new Date(time).valueOf() - Date.now().valueOf() 10 | ); 11 | 12 | // let intervalId: NodeJS.Timer; 13 | 14 | useEffect(() => { 15 | const intervalId = setInterval(() => { 16 | setTimespan(new Date(time).valueOf() - Date.now().valueOf()); 17 | }, interval); 18 | 19 | return () => { 20 | clearInterval(intervalId); 21 | }; 22 | }, [time, interval]); 23 | return { 24 | hours: Math.floor((timespan / HOUR) % 24), 25 | minutes: Math.floor((timespan / MINUTE) % 60), 26 | seconds: Math.floor((timespan / SECOND) % 60), 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import Settings from './components/Settings/Settings' 2 | import Card from './components/PlayerCard/Card' 3 | import Modal from './components/Modal/Modal' 4 | import { useTypedSelector } from './hooks/useTypedSelector' 5 | import Event from './components/Event/Event' 6 | 7 | import React from 'react' 8 | 9 | const App = () => { 10 | const isMenuOpen = useTypedSelector((state) => state.modal.isMenuOpen) 11 | const players = useTypedSelector((state) => state.player.players) 12 | 13 | return ( 14 |
15 |
16 | 17 |
18 | 19 |
20 | {players.map((player) => ( 21 | 22 | ))} 23 |
24 | {isMenuOpen && } 25 |
26 | ) 27 | } 28 | export default App 29 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 16 | 20 | 21 | Life Counter App 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "life-counter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ssc build -r -o --test=./test/index.js --headless", 8 | "init-project": "ssc init", 9 | "start": "ssc build -r -o", 10 | "build": "ssc build -o", 11 | "android": "ssc build -r -o --platform=android", 12 | "emulator": "ssc build --platform=android-emulator -r ." 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "esbuild": "^0.17.17" 19 | }, 20 | "dependencies": { 21 | "@reduxjs/toolkit": "^1.9.5", 22 | "@socketsupply/socket": "^0.2.13", 23 | "@types/node": "^18.15.12", 24 | "@types/react": "^18.0.37", 25 | "@types/react-dom": "^18.0.11", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "react-icons": "^4.8.0", 29 | "react-redux": "^8.0.5", 30 | "typescript": "^5.0.4" 31 | }, 32 | "type": "module" 33 | } 34 | -------------------------------------------------------------------------------- /src/state/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { modalReducer, openSideMenu, closeSideMenu } from './slices/modalSlice'; 3 | import { 4 | playersReducer, 5 | addLife, 6 | subtractLife, 7 | changeName, 8 | subtractLifeByAmount, 9 | addLifeByAmount, 10 | setGameOver, 11 | resetGame, 12 | setStartingLife, 13 | setNumberOfPlayers, 14 | setDefaultPlayers, 15 | } from './slices/playersSlice'; 16 | 17 | export const store = configureStore({ 18 | reducer: { 19 | modal: modalReducer, 20 | player: playersReducer, 21 | }, 22 | }); 23 | 24 | export type RootState = ReturnType; 25 | export type AppDispatch = typeof store.dispatch; 26 | 27 | export const actionCreators = { 28 | openSideMenu, 29 | closeSideMenu, 30 | addLife, 31 | subtractLife, 32 | changeName, 33 | subtractLifeByAmount, 34 | addLifeByAmount, 35 | setGameOver, 36 | resetGame, 37 | setStartingLife, 38 | setNumberOfPlayers, 39 | setDefaultPlayers, 40 | }; 41 | -------------------------------------------------------------------------------- /globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'socket:os' 2 | declare module 'socket:test' 3 | declare module 'socket:console' 4 | declare module 'socket:process' 5 | 6 | declare interface Config { 7 | build_env: string 8 | build_flags: string 9 | build_headless: boolean 10 | build_input: string 11 | build_name: string 12 | build_output: string 13 | build_redirect: Location | (string & Location) 14 | build_script: string 15 | debug_flags: string 16 | ios_distribution_method: string 17 | ios_simulator_device: string 18 | linux_categories: string 19 | linux_cmd: string 20 | linux_icon: string 21 | mac_appstore_icon: string 22 | meta_bundle_identifier: string 23 | meta_copyright: string 24 | meta_description: string 25 | meta_file_limit: number 26 | meta_lang: string 27 | meta_maintainer: string 28 | meta_title: string 29 | meta_version: string 30 | native_files: string 31 | native_headers: string 32 | win_cmd: string 33 | win_logo: string 34 | win_pfx: string 35 | win_publisher: string 36 | window_height: string 37 | window_width: string 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Timer/Timer.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useTimer } from '../../hooks/useTimer'; 3 | import { TfiTimer } from 'react-icons/tfi'; 4 | 5 | let date: Date = new Date(Date.now() + 60 * 20 * 1000); 6 | 7 | const Timer = () => { 8 | const [presets, setPresets] = useState(20); 9 | const { minutes, seconds } = useTimer(date); 10 | 11 | const onStartTimer = () => { 12 | date = new Date(Date.now() + 60 * presets * 1000); 13 | }; 14 | 15 | return ( 16 |
17 |
18 |

19 | {minutes}:{seconds} 20 |

21 |
22 | 23 |
24 |

Presets

25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 |
40 | 43 |
44 | ); 45 | }; 46 | export default Timer; 47 | -------------------------------------------------------------------------------- /src/components/PlayerCard/Card.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { GiHeartMinus, GiHeartPlus } from 'react-icons/gi'; 3 | import { BsShieldFillMinus, BsShieldFillPlus } from 'react-icons/bs'; 4 | import { useActions } from '../../hooks/useActions'; 5 | import { useTypedSelector } from '../../hooks/useTypedSelector'; 6 | 7 | interface Player { 8 | playerId: string; 9 | playerLife: number; 10 | playerName: string; 11 | } 12 | 13 | const Card = ({ playerLife, playerName, playerId }: Player) => { 14 | const { 15 | addLife, 16 | subtractLife, 17 | subtractLifeByAmount, 18 | addLifeByAmount, 19 | setGameOver, 20 | } = useActions(); 21 | const isGameOver = useTypedSelector((state) => state.player.isGameOver); 22 | 23 | useEffect(() => { 24 | if (playerLife <= 0) { 25 | setGameOver({ id: playerId }); 26 | } 27 | }, [playerLife]); 28 | 29 | return ( 30 |
31 |
32 |

{playerName}

33 |
34 |
35 |

{playerLife}

36 |
37 |
38 |
39 | 46 | 53 |
54 |
55 | 62 | 69 |
70 |
71 |
72 | ); 73 | }; 74 | export default Card; 75 | -------------------------------------------------------------------------------- /src/components/Modal/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { FaWindowClose } from 'react-icons/fa'; 2 | import { options, playerOptions } from '../../data/options'; 3 | import { useActions } from '../../hooks/useActions'; 4 | import { useTypedSelector } from '../../hooks/useTypedSelector'; 5 | import { nanoid } from '@reduxjs/toolkit'; 6 | import Timer from '../Timer/Timer'; 7 | 8 | const Modal = () => { 9 | const { 10 | closeSideMenu, 11 | changeName, 12 | resetGame, 13 | setStartingLife, 14 | setNumberOfPlayers, 15 | setDefaultPlayers, 16 | } = useActions(); 17 | const players = useTypedSelector((state) => state.player.players); 18 | 19 | const player = { 20 | playerId: nanoid(), 21 | playerLife: 20, 22 | playerName: 'DefaultPlayer', 23 | }; 24 | 25 | const onResetHandler = () => { 26 | resetGame(); 27 | closeSideMenu(); 28 | }; 29 | 30 | const onAddPlayersHandler: React.ChangeEventHandler = ( 31 | e 32 | ) => { 33 | const numPlayers = parseInt(e.target.value); 34 | 35 | if (numPlayers === 2) { 36 | setDefaultPlayers(); 37 | return; 38 | } 39 | 40 | if (numPlayers >= 3) { 41 | setDefaultPlayers(); 42 | setNumberOfPlayers({ 43 | multiplier: numPlayers, 44 | player, 45 | }); 46 | } 47 | }; 48 | 49 | return ( 50 | 105 | ); 106 | }; 107 | export default Modal; 108 | -------------------------------------------------------------------------------- /src/state/slices/playersSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, nanoid } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | players: [ 5 | { 6 | playerId: nanoid(), 7 | playerLife: 20, 8 | playerName: 'DefaultPlayer', 9 | }, 10 | { 11 | playerId: nanoid(), 12 | playerLife: 20, 13 | playerName: 'DefaultPlayer', 14 | }, 15 | ], 16 | isGameOver: false, 17 | }; 18 | 19 | const playersSlice = createSlice({ 20 | name: 'player', 21 | initialState, 22 | reducers: { 23 | setGameOver(state, { payload }) { 24 | const foundPlayer = state.players.find( 25 | (player) => player.playerId === payload.id 26 | ); 27 | 28 | if (!foundPlayer) { 29 | return; 30 | } 31 | foundPlayer.playerLife = 0; 32 | state.isGameOver = true; 33 | }, 34 | subtractLife(state, { payload }) { 35 | const foundPlayer = state.players.find( 36 | (player) => player.playerId === payload.id 37 | ); 38 | 39 | if (!foundPlayer) { 40 | return; 41 | } 42 | foundPlayer.playerLife = 43 | foundPlayer.playerLife <= 0 ? 0 : foundPlayer.playerLife - 1; 44 | }, 45 | subtractLifeByAmount(state, { payload }) { 46 | const foundPlayer = state.players.find( 47 | (player) => player.playerId === payload.id 48 | ); 49 | 50 | if (!foundPlayer) { 51 | return; 52 | } 53 | 54 | foundPlayer.playerLife = 55 | foundPlayer.playerLife <= 0 56 | ? 0 57 | : foundPlayer.playerLife - payload.amount; 58 | }, 59 | addLife(state, { payload }) { 60 | const foundPlayer = state.players.find( 61 | (player) => player.playerId === payload.id 62 | ); 63 | 64 | if (!foundPlayer) { 65 | return; 66 | } 67 | foundPlayer.playerLife = foundPlayer.playerLife + 1; 68 | }, 69 | addLifeByAmount(state, { payload }) { 70 | const foundPlayer = state.players.find( 71 | (player) => player.playerId === payload.id 72 | ); 73 | 74 | if (!foundPlayer) { 75 | return; 76 | } 77 | 78 | foundPlayer.playerLife = foundPlayer.playerLife + payload.amount; 79 | }, 80 | 81 | changeName(state, { payload }) { 82 | const foundPlayer = state.players.find( 83 | (player) => player.playerId === payload.id 84 | ); 85 | 86 | if (!foundPlayer) { 87 | return; 88 | } 89 | foundPlayer.playerName = payload.name; 90 | }, 91 | resetGame(state) { 92 | state.isGameOver = false; 93 | const resetPlayers = state.players.map((player) => ({ 94 | ...player, 95 | playerLife: 20, 96 | })); 97 | 98 | state.players = resetPlayers; 99 | }, 100 | 101 | setStartingLife(state, { payload }) { 102 | state.isGameOver = false; 103 | 104 | const newStartPlayers = state.players.map((player) => ({ 105 | ...player, 106 | playerLife: payload.life, 107 | })); 108 | 109 | state.players = newStartPlayers; 110 | }, 111 | 112 | setNumberOfPlayers(state, { payload }) { 113 | const { multiplier, player } = payload; 114 | 115 | for (let i = 2; i < multiplier; i++) { 116 | state.players.push({ ...player, playerId: nanoid() }); 117 | } 118 | }, 119 | 120 | setDefaultPlayers(state) { 121 | state.players = initialState.players; 122 | }, 123 | }, 124 | }); 125 | 126 | export const { 127 | subtractLife, 128 | addLife, 129 | changeName, 130 | subtractLifeByAmount, 131 | addLifeByAmount, 132 | setGameOver, 133 | resetGame, 134 | setStartingLife, 135 | setNumberOfPlayers, 136 | setDefaultPlayers, 137 | } = playersSlice.actions; 138 | export const playersReducer = playersSlice.reducer; 139 | -------------------------------------------------------------------------------- /socket.ini: -------------------------------------------------------------------------------- 1 | 2 | ; ___ __ ___ __ ____ 3 | ; /__ / / / /_/ /_ / 4 | ; __/ /__/ /__ / \ /__ / 5 | ; 6 | ; Socket ⚡︎ Runtime · A modern runtime for Web Apps · v0.2.8 (587c1f03) 7 | ; 8 | 9 | ; The value of the "script" property in a build section will be interpreted as a shell command when 10 | ; you run "ssc build". This is the most important command in this file. It will 11 | ; do all the heavy lifting and should handle 99.9% of your use cases for moving 12 | ; files into place or tweaking platform-specific build artifacts. If you don't 13 | ; specify it, ssc will just copy everything in your project to the build target. 14 | 15 | [build] 16 | 17 | ; ssc will copy everything in this directory to the build output directory. 18 | ; This is useful when you want to avoid bundling or want to use tools like 19 | ; vite, webpack, rollup, etc. to build your project and then copy output to 20 | ; the Socket bundle resources directory. 21 | ; copy = "src" 22 | 23 | ; An list of environment variables, separated by commas. 24 | env = USER, TMPDIR, PWD 25 | 26 | ; Advanced Compiler Settings (ie C++ compiler -02, -03, etc). 27 | flags = -O3 28 | 29 | ; If false, the window will never be displayed. 30 | headless = false 31 | 32 | ; The name of the program and executable to be output. Can't contain spaces or special characters. Required field. 33 | name = "life-counter" 34 | 35 | ; The binary output path. It's recommended to add this path to .gitignore. 36 | output = "build" 37 | 38 | ; The build script 39 | script = "node build.js" 40 | 41 | [debug] 42 | ; Advanced Compiler Settings for debug purposes (ie C++ compiler -g, etc). 43 | flags = "-g" 44 | 45 | 46 | [meta] 47 | 48 | ; A unique ID that identifies the bundle (used by all app stores). 49 | bundle_identifier = "com.beepboop" 50 | 51 | ; A string that gets used in the about dialog and package meta info. 52 | copyright = "(c) Beep Boop Corp. 1985" 53 | 54 | ; A short description of the app. 55 | description = "A UI for the beep boop network" 56 | 57 | ; Set the limit of files that can be opened by your process. 58 | file_limit = 1024 59 | 60 | ; Localization 61 | lang = "en-us" 62 | 63 | ; A String used in the about dialog and meta info. 64 | maintainer = "Beep Boop Corp." 65 | 66 | ; The title of the app used in metadata files. This is NOT a window title. Can contain spaces and special characters. Defaults to name in a [build] section. 67 | title = "Beep Boop" 68 | 69 | ; A string that indicates the version of the application. It should be a semver triple like 1.2.3. Defaults to 1.0.0. 70 | version = 1.0.0 71 | 72 | 73 | [android] 74 | ; TODO description needed 75 | main_activity = "" 76 | 77 | 78 | [ios] 79 | 80 | ; signing guide: https://sockets.sh/guides/#ios-1 81 | codesign_identity = "" 82 | 83 | ; Describes how Xcode should export the archive. Available options: app-store, package, ad-hoc, enterprise, development, and developer-id. 84 | distribution_method = "ad-hoc" 85 | 86 | ; A path to the provisioning profile used for signing iOS app. 87 | provisioning_profile = "" 88 | 89 | ; which device to target when building for the simulator 90 | simulator_device = "iPhone 14" 91 | 92 | 93 | [linux] 94 | ; Helps to make your app searchable in Linux desktop environments. 95 | categories = "Developer Tools" 96 | 97 | ; The command to execute to spawn the "back-end" process. 98 | cmd = "beepboop" 99 | 100 | ; The icon to use for identifying your app in Linux desktop environments. 101 | icon = "src/icon.png" 102 | 103 | 104 | [mac] 105 | 106 | ; Mac App Store icon 107 | appstore_icon = "src/icons/icon.png" 108 | 109 | ; A category in the App Store 110 | category = "" 111 | 112 | ; The command to execute to spawn the "back-end" process. 113 | cmd = "" 114 | 115 | ; The icon to use for identifying your app on MacOS. 116 | icon = "" 117 | 118 | ; TODO description & value (signing guide: https://sockets.sh/guides/#macos-1) 119 | sign = "" 120 | 121 | ; TODO description & value 122 | codesign_identity = "" 123 | 124 | ; TODO description & value 125 | sign_paths = "" 126 | 127 | 128 | [native] 129 | 130 | ; Files that should be added to the compile step. 131 | files = native-module1.cc native-module2.cc 132 | 133 | ; Extra Headers 134 | headers = native-module1.hh 135 | 136 | 137 | [win] 138 | 139 | ; The command to execute to spawn the “back-end” process. 140 | cmd = "beepboop.exe" 141 | 142 | ; The icon to use for identifying your app on Windows. 143 | icon = "" 144 | 145 | ; The icon to use for identifying your app on Windows. 146 | logo = "src/icons/icon.png" 147 | 148 | ; A relative path to the pfx file used for signing. 149 | pfx = "certs/cert.pfx" 150 | 151 | ; The signing information needed by the appx api. 152 | publisher = "CN=Beep Boop Corp., O=Beep Boop Corp., L=San Francisco, S=California, C=US" 153 | 154 | [window] 155 | 156 | ; The initial height of the first window. 157 | height = 50% 158 | 159 | ; The initial width of the first window. 160 | width = 50% 161 | -------------------------------------------------------------------------------- /src/components/Event/Event.jsx: -------------------------------------------------------------------------------- 1 | import { Peer } from 'socket:peer' 2 | import process from 'socket:process' 3 | import Buffer from 'socket:buffer' 4 | import fs from 'socket:fs' 5 | import React, { useRef, useEffect } from 'react' 6 | 7 | const Event = () => { 8 | const canvasRef = useRef() 9 | 10 | const onLoadDOM = async () => { 11 | const clusterId = '45ggh32095dcmcdjur75347nasnxhsyg45834589609fdfm28939432492321321321' // truncated, make your own clusterId 12 | // const clusterId = Peer.createClusterId() 13 | 14 | const publicKeyHex = 'cxzcsdflewomvfvcfd04035i43mdf34d0034cdsdcdcsd03404304043030432131321' // truncated, make your own hex encoded key 15 | const privateKeyHex = 'sdasd89234823894j3djjdjj32092390u110093483382547834534955892321373sds' // truncated, make your own hex encoded key 16 | 17 | const publicKey = Buffer.from(publicKeyHex, 'hex').buffer 18 | const privateKey = Buffer.from(privateKeyHex, 'hex').buffer 19 | 20 | let peerId = '111e0b848d77d7b3d778187d006056ee406221ca567ce2844ca403dc834ff8f1' 21 | 22 | if (process.platform === 'android') { 23 | peerId = '222d38de5b1e4b557a446401317eb02c6d1a0287959529972245159e79861567' 24 | } 25 | 26 | const peer = new Peer({ peerId, publicKey, privateKey, clusterId }) 27 | console.log(window.peer) 28 | window.peer = peer 29 | console.log('created peer', peer) 30 | 31 | if (!canvasRef.current) { 32 | return 33 | } 34 | const canvas = canvasRef.current 35 | const context = canvas.getContext('2d') 36 | 37 | const setSize = () => { 38 | canvas.width = 600 39 | canvas.height = 600 40 | } 41 | 42 | setSize() 43 | window.addEventListener('resize', setSize) 44 | 45 | let isDrawing = false 46 | let x = 0 47 | let y = 0 48 | 49 | function drawLine(context, color, x1, y1, x2, y2) { 50 | context.beginPath() 51 | context.strokeStyle = color 52 | context.lineWidth = 1 53 | context.moveTo(x1, y1) 54 | context.lineTo(x2, y2) 55 | context.stroke() 56 | context.closePath() 57 | } 58 | 59 | const network = await peer.join() 60 | console.log(network) 61 | 62 | const getOffset = (e) => { 63 | if (e.offsetX) return { offsetX: e.offsetX, offsetY: e.offsetY } 64 | if (!e.targetTouches[0]) return { offsetX: 0, offsetY: 0 } 65 | 66 | const rect = e.target.getBoundingClientRect() 67 | 68 | return { 69 | offsetX: e.changedTouches[0]?.pageX - rect.left, 70 | offsetY: e.changedTouches[0]?.pageY - rect.top 71 | } 72 | } 73 | 74 | const penDown = (e) => { 75 | isDrawing = true 76 | const o = getOffset(e) 77 | x = o.offsetX 78 | y = o.offsetY 79 | } 80 | 81 | const penUp = (e) => { 82 | if (!isDrawing) return 83 | const o = getOffset(e) 84 | if (o.offsetX <= 0) return 85 | if (o.offsetY <= 0) return 86 | 87 | drawLine(context, 'black', x, y, o.offsetX, o.offsetY) 88 | x = o.offsetX 89 | y = o.offsetY 90 | isDrawing = false 91 | } 92 | 93 | const penMove = (e) => { 94 | if (!isDrawing) return 95 | const o = getOffset(e) 96 | drawLine(context, 'black', x, y, o.offsetX, o.offsetY) 97 | const value = { x1: x, y1: y, x2: o.offsetX, y2: o.offsetY } 98 | const data = new Buffer.from(JSON.stringify(value)) 99 | 100 | if (o.offsetX > 0) x = o.offsetX 101 | if (o.offsetY > 0) y = o.offsetY 102 | 103 | for (const remotePeer of network.peers) { 104 | console.log(remotePeer) 105 | // 106 | // only send this to peers in my cluster because they are the 107 | // only peers who will know who to accept this kind of message. 108 | // 109 | if (remotePeer.clusterId !== clusterId) continue 110 | network.send(data, remotePeer.port, remotePeer.address) 111 | } 112 | } 113 | 114 | canvas.addEventListener('touchstart', penDown) 115 | canvas.addEventListener('mousedown', penDown) 116 | 117 | canvas.addEventListener('touchend', penUp) 118 | canvas.addEventListener('mouseup', penUp) 119 | 120 | canvas.addEventListener('touchmove', penMove) 121 | canvas.addEventListener('mousemove', penMove) 122 | 123 | network.onConnect = (...args) => { 124 | console.log(network.peerId, network.address, network.port, 'CONNECT', ...args) 125 | } 126 | 127 | network.onData = (packet, port, address, data) => { 128 | if (packet.type) return 129 | 130 | try { 131 | const { x1, y1, x2, y2 } = JSON.parse(data) 132 | drawLine(context, 'black', x1, y1, x2, y2) 133 | } catch (err) { 134 | console.error(err) 135 | } 136 | } 137 | } 138 | 139 | useEffect(() => { 140 | onLoadDOM() 141 | }, []) 142 | 143 | return ( 144 | <> 145 | 146 | 147 | ) 148 | } 149 | export default Event 150 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* Minimal Reset */ 2 | html { 3 | box-sizing: border-box; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | font-family: 'Nunito', serif; 26 | } 27 | 28 | ol, 29 | ul { 30 | list-style: none; 31 | } 32 | 33 | img { 34 | max-width: 100%; 35 | height: auto; 36 | } 37 | 38 | body { 39 | background-color: grey; 40 | } 41 | 42 | .app { 43 | padding: 64px; 44 | display: flex; 45 | flex-direction: column; 46 | align-items: center; 47 | justify-content: center; 48 | position: relative; 49 | } 50 | 51 | /* Card component CSS */ 52 | 53 | .cards_contanier { 54 | display: flex; 55 | align-items: center; 56 | justify-content: center; 57 | flex-wrap: wrap; 58 | } 59 | 60 | .card { 61 | padding: 15px; 62 | width: 350px; 63 | height: 350px; 64 | background-color: rgb(63, 66, 68); 65 | display: flex; 66 | flex-direction: column; 67 | align-items: center; 68 | justify-content: space-evenly; 69 | border-radius: 10px; 70 | border: 3px solid black; 71 | margin: 5px; 72 | } 73 | 74 | .direction { 75 | transform: rotate(180deg); 76 | } 77 | 78 | .player_name { 79 | width: 100%; 80 | display: flex; 81 | align-items: center; 82 | justify-content: center; 83 | } 84 | 85 | .player_name h2 { 86 | color: white; 87 | font-size: 28px; 88 | } 89 | 90 | .player_life h1 { 91 | font-size: 75px; 92 | font-weight: 700; 93 | color: red; 94 | } 95 | 96 | .player_buttons { 97 | width: 100%; 98 | display: flex; 99 | align-items: center; 100 | justify-content: space-evenly; 101 | } 102 | 103 | .player_buttons button { 104 | display: flex; 105 | align-items: center; 106 | justify-content: center; 107 | border-radius: 7px; 108 | width: 37px; 109 | height: 37px; 110 | cursor: pointer; 111 | border: none; 112 | } 113 | 114 | .lost { 115 | background-color: rgba(231, 33, 19, 0.5); 116 | } 117 | 118 | .lost .btn_success:hover { 119 | background-color: rgba(133, 178, 116, 1); 120 | } 121 | 122 | .lost .btn_danger:hover { 123 | background-color: rgba(249, 113, 103, 1); 124 | } 125 | 126 | .winner { 127 | background-color: rgba(133, 178, 116, 0.5); 128 | } 129 | 130 | .btn_success { 131 | background-color: rgba(133, 178, 116, 1); 132 | margin-left: 10px; 133 | } 134 | 135 | .btn_success:hover { 136 | background-color: rgb(84, 126, 69); 137 | } 138 | 139 | .btn_danger { 140 | background-color: rgba(249, 113, 103, 1); 141 | margin-left: 10px; 142 | } 143 | 144 | .btn_danger:hover { 145 | background-color: rgb(212, 48, 36); 146 | } 147 | 148 | .icon { 149 | font-size: 30px; 150 | } 151 | 152 | .sub_btns { 153 | display: flex; 154 | } 155 | 156 | .add_btns { 157 | display: flex; 158 | } 159 | 160 | /* Settings component CSS */ 161 | 162 | .settings_container { 163 | width: 90vw; 164 | display: flex; 165 | align-items: center; 166 | justify-content: space-between; 167 | } 168 | 169 | .settings { 170 | width: 50px; 171 | height: 50px; 172 | padding: 2px 4px; 173 | background-color: white; 174 | border: 1px solid black; 175 | border-radius: 5px; 176 | display: flex; 177 | align-items: center; 178 | justify-content: center; 179 | margin: 20px; 180 | cursor: pointer; 181 | } 182 | 183 | .settings:hover { 184 | background-color: black; 185 | } 186 | 187 | .menu_icon { 188 | font-size: 35px; 189 | } 190 | 191 | .settings:hover .menu_icon { 192 | color: white; 193 | } 194 | 195 | /* Modal Css */ 196 | 197 | .modal { 198 | width: 100%; 199 | height: 100%; 200 | background-color: rgba(0, 0, 0, 0.9); 201 | position: absolute; 202 | top: 0; 203 | left: 0; 204 | right: 0; 205 | bottom: 0; 206 | overflow: hidden; 207 | display: flex; 208 | flex-direction: column; 209 | align-items: center; 210 | justify-content: center; 211 | } 212 | 213 | .modal_left { 214 | display: flex; 215 | flex: 1; 216 | flex-direction: column; 217 | align-items: center; 218 | justify-content: center; 219 | } 220 | 221 | .modal_right { 222 | display: flex; 223 | flex: 1; 224 | flex-direction: column; 225 | align-items: center; 226 | justify-content: center; 227 | } 228 | 229 | .modal label { 230 | color: white; 231 | margin: 0 15px; 232 | } 233 | 234 | .modal_settings { 235 | display: flex; 236 | align-items: center; 237 | } 238 | 239 | .btn_menu-close { 240 | background-color: transparent; 241 | border: none; 242 | position: absolute; 243 | top: 10px; 244 | right: 10px; 245 | } 246 | 247 | .close_icon { 248 | color: white; 249 | font-size: 30px; 250 | cursor: pointer; 251 | } 252 | 253 | .close_icon:hover { 254 | color: grey; 255 | } 256 | 257 | .btn_reset { 258 | padding: 6px 15px; 259 | background-color: rgb(243, 106, 106); 260 | color: white; 261 | cursor: pointer; 262 | } 263 | 264 | .btn_reset:hover { 265 | background-color: red; 266 | } 267 | 268 | .modal_inputs { 269 | width: 100%; 270 | display: flex; 271 | flex-direction: column; 272 | align-items: center; 273 | justify-content: center; 274 | margin-bottom: 20px; 275 | } 276 | 277 | .modal_inputs input { 278 | height: 30px; 279 | outline: none; 280 | font-size: 25px; 281 | width: 70%; 282 | } 283 | 284 | @media (min-width: 786px) { 285 | .modal { 286 | display: flex; 287 | flex-direction: row; 288 | } 289 | } 290 | 291 | /* Timer Css */ 292 | 293 | .timer { 294 | display: flex; 295 | align-items: center; 296 | flex-direction: column; 297 | justify-content: center; 298 | gap: 10px; 299 | } 300 | 301 | .timer_clock h4 { 302 | color: white; 303 | font-size: 35px; 304 | } 305 | 306 | .timer_presets { 307 | display: flex; 308 | flex-direction: column; 309 | align-items: center; 310 | justify-content: space-around; 311 | } 312 | 313 | .timer_presets h4 { 314 | color: white; 315 | font-size: 20px; 316 | } 317 | 318 | .set_timer { 319 | display: flex; 320 | align-items: center; 321 | justify-content: center; 322 | } 323 | 324 | .set_timer button { 325 | border: none; 326 | background-color: transparent; 327 | color: yellow; 328 | font-size: 26px; 329 | cursor: pointer; 330 | } 331 | 332 | .set_timer button:hover { 333 | color: lawngreen; 334 | } 335 | 336 | .set_timer .icon { 337 | color: grey; 338 | font-size: 23px; 339 | } 340 | 341 | .show_timer { 342 | position: absolute; 343 | top: 10px; 344 | left: 10px; 345 | } 346 | 347 | .btn_timer { 348 | padding: 4px 15px; 349 | background-color: yellowgreen; 350 | color: black; 351 | cursor: pointer; 352 | } 353 | 354 | .btn_timer:hover { 355 | background-color: chartreuse; 356 | } 357 | 358 | .canvas { 359 | background-color: white; 360 | } 361 | -------------------------------------------------------------------------------- /src/utils/reload.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Socket Reload Module 4 | v2: 5 | supports keyboard mode by default 6 | 7 | v2.1: 8 | fix linux and mac support (find app path) 9 | call application.backend.close() 10 | 11 | Todo: 12 | Only works on desktop, there needs to be a way to tell mobile apps to update, and they should also pull assets from an http server 13 | 14 | usage: 15 | 16 | import enableSocketReload from './reload.js' 17 | 18 | // method 1 - manual refresh using ctrl+r, cmd+r, F5 19 | window.addEventListener("load", async () => { 20 | enableSocketReload({startDir: process.cwd()}) 21 | // optionally implement a custom callback, you can use this to call custom cleanup code. defaults to window.location.reload() 22 | updateCallback: () => { window.location.reload() } 23 | }) 24 | 25 | // method 2 - live reload 26 | window.addEventListener("load", async () => { 27 | enableSocketReload({startDir: process.cwd(), 28 | liveReload: true, 29 | scanInterval: 200, // how often to check for changes 30 | debounce: 1000, // how long to wait before calling updateCallback 31 | debounceCallback: () => { // This gets called when debounce is set (changes detected but updateCallback not called) 32 | console.log(`updates inbound...`); 33 | }, 34 | // optionally implement a custom callback, called after debounce has elapsed 35 | updateCallback: () => { 36 | window.location.reload() 37 | } 38 | }) 39 | }) 40 | 41 | */ 42 | 43 | import fs from 'socket:fs/promises' 44 | import process from 'socket:process' 45 | import Buffer from 'socket:buffer' 46 | import Path from 'socket:path' 47 | import application from 'socket:application' 48 | import os from 'socket:os' 49 | 50 | let enabled = false 51 | let _interval = null 52 | let _lastUpdate = 0 53 | let scanInterval = 500 54 | let _opts = {} 55 | 56 | let _copyPath = undefined 57 | let _debounce_handle = null; 58 | 59 | const recursePath = async(path, file_fn, data) => { 60 | for (const entry of (await fs.readdir(path, {withFileTypes: true}))) { 61 | let entry_path = Path.join(path, entry.name) 62 | if (entry.isDirectory()) { 63 | await recursePath(entry_path, file_fn, data) 64 | } else { 65 | await file_fn(entry_path, data) 66 | } 67 | } 68 | } 69 | 70 | async function dirname(filename) { 71 | return Path.dirname(filename) 72 | } 73 | 74 | async function exists(name) { 75 | try { 76 | return fs.stat(name) 77 | } catch (_) { 78 | return undefined 79 | } 80 | } 81 | 82 | async function mkdir(folder) { 83 | // folder = folder.replaceAll('\\\\', '\\') 84 | if (await exists(folder)) { 85 | console.log(`${folder} exists`) 86 | return 87 | } else { 88 | // console.log(`mkdir ${Path.dirname(folder)}`) 89 | await mkdir(await Path.dirname(folder)) 90 | // console.log(`mkdir ${folder}`) 91 | await fs.mkdir(folder) 92 | } 93 | } 94 | 95 | const enableSocketReload = async (opts = {}) => { 96 | // let testpath = 'D:\\code\\socket\\apps\\files-commander\\build\\win\\files-commander-dev-v1.0.0\\assets\\images\\blog.png' 97 | 98 | // await mkdir(Path.dirname(testpath)) 99 | // console.log(`dirname: ${await Path.dirname(testpath)}`) 100 | // return; 101 | 102 | if (opts.enable === undefined) { 103 | opts.enable = true 104 | } 105 | 106 | if (opts.enable) { 107 | if (!opts.startDir) { 108 | throw "startDir must be defined to monitor for file changes." 109 | } 110 | } 111 | 112 | if (opts.liveReload === undefined) { 113 | opts.liveReload = false 114 | } 115 | 116 | console.log(`liveReload: ${opts.liveReload}`) 117 | 118 | _opts = opts; 119 | 120 | if (enabled === opts.enable) 121 | return 122 | 123 | if (opts.enable) { 124 | console.log(`platform: ${os.platform()}`) 125 | let osParent = '' // define os specific parent path 126 | os.platform() === 'darwin' && (osParent = '../') 127 | os.platform() === 'linux' && (osParent = '../') 128 | osParent = `${osParent}../../../..` 129 | let parentPath = Path.join(_opts.startDir, `${osParent}../../../..`); 130 | console.log(`ini path: ${`${parentPath}/socket.ini`}`) 131 | let _parentPath = Path.resolve(Path.join(_opts.startDir, `${osParent}`)) 132 | if (os.platform() === 'win32' && (_parentPath.startsWith('/')) || (_parentPath.startsWith('\\'))) 133 | _parentPath = _parentPath.substring(1) 134 | // _copyPath = Path.join(_parentPath, 'src') 135 | _copyPath = Path.join(_parentPath, window.__args.config.build_copy.replaceAll('"', '').trim()) 136 | 137 | console.log(`enableSocketReload: ${opts.enable}, _path: ${_copyPath} => ${_opts.startDir}`) 138 | 139 | if (opts.debounce === undefined) { 140 | opts.debounce = -1 141 | } 142 | 143 | if (opts.scanInterval === undefined) { 144 | opts.scanInterval = scanInterval; 145 | } 146 | 147 | if (!opts.updateCallback) { 148 | opts.updateCallback = () => { window.location.reload() } 149 | } 150 | 151 | window.addEventListener("keydown", async(event) => { 152 | if(((event.ctrlKey || event.metaKey) && event.key === 'r') || event.key === 'F5') { 153 | event.preventDefault() 154 | await reload() 155 | } 156 | }) 157 | 158 | if (opts.liveReload) { 159 | _interval = setInterval(checkRefresh, opts.scanInterval) 160 | } 161 | 162 | // checkRefresh() 163 | enabled = true 164 | } else { 165 | clearInterval(_interval) 166 | enabled = false; 167 | } 168 | } 169 | 170 | const reload = async () => { 171 | await sscBuildOutput(_opts.startDir) 172 | _opts.updateCallback() 173 | application.backend.close() 174 | } 175 | 176 | async function checkPath(filename) { 177 | 178 | try { 179 | console.log(`check path: ${filename}`) 180 | await fs.stat(filename) 181 | } catch (_) { 182 | } 183 | } 184 | 185 | const sscBuildOutput = async (dest) => { 186 | let recurseData = { 187 | dest: dest, 188 | base: _copyPath, 189 | changed: false, 190 | } 191 | 192 | checkPath(dest) 193 | checkPath(_copyPath) 194 | 195 | await recursePath(_copyPath, async (file, data) => { 196 | let dest_path = file.replace(data.base, data.dest) 197 | checkPath(dest_path) 198 | let update = false 199 | let exists = false 200 | try { 201 | exists = await fs.access(dest_path) // todo(mribbons) fs.access should not throw exception 202 | } catch { 203 | // console.log(`dest doesn't exist: ${dest_path}`); 204 | } 205 | checkPath(file) 206 | let stat1 = await fs.stat(file); 207 | if (exists) { 208 | let stat2 = await fs.stat(dest_path); 209 | 210 | if (stat1.mtimeMs > stat2.mtimeMs || stat1.size !== stat2.size) { 211 | // console.log(`update ${file}: ${stat1.mtimeMs} > ${stat2.mtimeMs}`) 212 | update = true; 213 | } 214 | // else { 215 | // console.log(`ok ${file}: ${stat1.mtimeMs} <= ${stat2.mtimeMs}`) 216 | // } 217 | } else { 218 | // console.log(`not in dest ${file} => ${dest_path}`) 219 | update = true 220 | } 221 | 222 | if (update) { 223 | // todo(@mribbons): fs.mkdir(dirname(dest_path)) - dirname / drive letter issue on win32 224 | // console.log(`copy file: ${file} -> ${dest_path}`) 225 | // todo(@mribbons) - copyFile and utimes are noops. Without utimes we can't check times are even, only newer, which isn't great 226 | // await fs.copyFile(file, dest_path) 227 | // await fs.utimes(dest_path, parseInt(stat1.mtimeMs), parseInt(stat1.mtimeMs)) 228 | // ensure parent exists 229 | await mkdir(Path.dirname(dest_path)) 230 | await fs.writeFile(dest_path, await fs.readFile(file)); 231 | data.changed = true 232 | } 233 | }, recurseData) 234 | 235 | // console.log(`recurse data changed: ${recurseData.changed}`) 236 | return recurseData.changed 237 | } 238 | 239 | const checkRefresh = async () => { 240 | try { 241 | // if (await sscBuildOutput(_opts.startDir)) { 242 | // if (_opts.debounce > -1) { 243 | // clearTimeout(_debounce_handle) 244 | // // if debounce wait to update, other updates will reset update timeout 245 | // setTimeout(() => {_opts.updateCallback()}, _opts.debounce) 246 | 247 | // // Let consumer know that an update is coming 248 | // if (_opts.debounceCallback) { 249 | // _opts.debounceCallback(); 250 | // } 251 | // } else { 252 | // await _opts.updateCallback() 253 | // application.backend.close() 254 | // } 255 | // } 256 | } catch (e) { 257 | console.log(e) 258 | } 259 | } 260 | 261 | export default enableSocketReload -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["dom","dom.iterable","esnext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | "jsx": "react-jsx", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "dist", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------