├── src ├── index.scss ├── softwares │ ├── Explorer │ │ ├── components │ │ │ ├── Directory │ │ │ │ ├── Directory.scss │ │ │ │ └── Directory.jsx │ │ │ ├── Icon │ │ │ │ └── Icon.jsx │ │ │ ├── Sidebar │ │ │ │ ├── Sidebar.scss │ │ │ │ └── Sidebar.jsx │ │ │ └── Ribbon │ │ │ │ ├── Ribbon.scss │ │ │ │ └── Ribbon.jsx │ │ ├── Explorer.scss │ │ └── Explorer.jsx │ ├── Calender │ │ ├── Calender.scss │ │ └── Calender.jsx │ ├── Settings │ │ ├── Settings.scss │ │ └── Settings.jsx │ ├── Calculator │ │ ├── Calculator.scss │ │ └── Calculator.jsx │ └── Notepad │ │ ├── Notepad.scss │ │ └── Notepad.jsx ├── redux │ ├── boot │ │ ├── boot.selectors.js │ │ ├── boot.types.js │ │ ├── boot.actions.js │ │ ├── boot.utils.js │ │ └── index.js │ ├── store.js │ ├── memory │ │ ├── memory.types.js │ │ ├── memory.selectors.js │ │ ├── memory.action.js │ │ ├── memory.utils.js │ │ └── index.js │ ├── auth │ │ ├── auth.types.js │ │ ├── auth.user.js │ │ ├── auth.selectors.js │ │ ├── auth.actions.js │ │ ├── auth.utils.js │ │ └── index.js │ ├── reducer.js │ └── account │ │ ├── account.types.js │ │ ├── account.selectors.js │ │ ├── account.fs.js │ │ ├── account.actions.js │ │ ├── account.utils.js │ │ └── index.js ├── components │ ├── Clock │ │ ├── Clock.scss │ │ └── Clock.jsx │ ├── Button │ │ ├── Button.scss │ │ └── Button.jsx │ ├── Loading │ │ ├── Loading.jsx │ │ └── Loading.scss │ ├── LockCover │ │ ├── LockCover.scss │ │ └── LockCover.jsx │ ├── BootLogo │ │ ├── BootLogo.jsx │ │ └── BootLogo.scss │ ├── TitleBar │ │ ├── TitleBar.scss │ │ └── TitleBar.jsx │ ├── MenuBar │ │ ├── MenuBar.jsx │ │ └── MenuBar.scss │ ├── StartMenu │ │ ├── Promotions.jsx │ │ ├── StartMenu.scss │ │ └── StartMenu.jsx │ └── TaskBar │ │ ├── TaskBar.jsx │ │ └── TaskBar.scss ├── pages │ ├── SwitchUser │ │ ├── SwitchUser.scss │ │ ├── SwitchUser.jsx │ │ ├── LoginView.scss │ │ └── LoginView.jsx │ ├── Desktop │ │ ├── Desktop.scss │ │ └── Desktop.jsx │ ├── BootScreen │ │ ├── BootScreen.scss │ │ └── BootScreen.jsx │ ├── Program │ │ ├── Program.scss │ │ └── Program.jsx │ └── NewAccount │ │ ├── NewAccount.scss │ │ ├── ChildView.scss │ │ ├── ChildView.jsx │ │ └── NewAccount.jsx ├── setupTests.js ├── App.test.js ├── App.scss ├── scss │ ├── _variables.scss │ └── _mixins.scss ├── reportWebVitals.js ├── config │ ├── wallpapers.js │ ├── strings.js │ └── apps.js ├── disk │ ├── disk.read.js │ ├── disk.delete.js │ ├── disk.write.js │ ├── disk.connect.js │ ├── disk.dispatch.js │ └── index.js ├── index.js ├── app.util.js ├── App.js └── logo.svg ├── .env ├── .vscode └── settings.json ├── .babelrc ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── fonts │ ├── OpenSans-Bold.ttf │ ├── OpenSans-Italic.ttf │ ├── OpenSans-Light.ttf │ ├── OpenSans-Regular.ttf │ └── OpenSans-SemiBold.ttf ├── images │ ├── 9fa80fd805562a6bc817f01f48b8b93e.jpg │ └── d2luMTBfb2ZmaWNpYWxfd2FsbHBhcGVy.jpg ├── icons │ ├── MiFolder.svg │ └── MiDocument.svg ├── manifest.json ├── index.css └── index.html ├── .prettierrc ├── .gitignore ├── LICENSE ├── .eslintrc.json ├── package.json └── README.md /src/index.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n" 3 | } -------------------------------------------------------------------------------- /src/softwares/Explorer/components/Directory/Directory.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/fonts/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /public/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/fonts/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /public/images/9fa80fd805562a6bc817f01f48b8b93e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/images/9fa80fd805562a6bc817f01f48b8b93e.jpg -------------------------------------------------------------------------------- /public/images/d2luMTBfb2ZmaWNpYWxfd2FsbHBhcGVy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush078/mindows/HEAD/public/images/d2luMTBfb2ZmaWNpYWxfd2FsbHBhcGVy.jpg -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "editor.formatOnSave": true 7 | } 8 | -------------------------------------------------------------------------------- /src/redux/boot/boot.selectors.js: -------------------------------------------------------------------------------- 1 | const bootState = (state) => state.boot; 2 | 3 | export const selectBootBackgrounds = (store) => bootState(store).backgrounds; 4 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import reducer from './reducer'; 3 | 4 | const store = createStore(reducer); 5 | export default store; 6 | -------------------------------------------------------------------------------- /src/components/Clock/Clock.scss: -------------------------------------------------------------------------------- 1 | .Clock { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | color: white; 6 | font-size: 14px; 7 | margin-top: -6px; 8 | } -------------------------------------------------------------------------------- /src/redux/memory/memory.types.js: -------------------------------------------------------------------------------- 1 | const MemoryActionTypes = { 2 | START_NEW_PROGRAM: 'START_NEW_PROGRAM', 3 | TERMINATE_PROGRAM: 'TERMINATE_PROGRAM', 4 | }; 5 | 6 | export default MemoryActionTypes; 7 | -------------------------------------------------------------------------------- /src/pages/SwitchUser/SwitchUser.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .new-account-opt { 4 | @include mixins.fonttype('semibold'); 5 | text-decoration: none; 6 | color: white; 7 | margin-top: 16px; 8 | } -------------------------------------------------------------------------------- /src/pages/Desktop/Desktop.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Desktop { 4 | @include mixins.fullscreen; 5 | @include mixins.wallpaperbg; 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | transition: opacity .5s ease; 10 | } -------------------------------------------------------------------------------- /src/redux/auth/auth.types.js: -------------------------------------------------------------------------------- 1 | const AuthActionTypes = { 2 | CHECK_USER_SESSION: 'CHECK_USER_SESSION', 3 | CREATE_NEW_ACCOUNT: 'CREATE_NEW_ACCOUNT', 4 | SIGN_IN: 'SIGN_IN', 5 | LOGOUT_USER: 'LOGOUT_USER', 6 | }; 7 | 8 | export default AuthActionTypes; 9 | -------------------------------------------------------------------------------- /src/redux/memory/memory.selectors.js: -------------------------------------------------------------------------------- 1 | const memoryState = (store) => store.memory; 2 | 3 | export const selectAppsInstances = (store) => memoryState(store).appsInstances; 4 | 5 | export const selectProgramsData = (store) => memoryState(store).programsData; 6 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/redux/boot/boot.types.js: -------------------------------------------------------------------------------- 1 | const BootActionTypes = { 2 | GET_BACKGROUNDS: 'GET_BACKGROUNDS', 3 | SET_LOCK_SCREEN_BACKGROUND: 'SET_LOwCK_SCREEN_BACKGROUND', 4 | SET_LOGIN_SCREEN_BACKGROUND: 'SET_LOGIN_SCREEN_BACKGROUND', 5 | }; 6 | 7 | export default BootActionTypes; 8 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/softwares/Explorer/components/Icon/Icon.jsx: -------------------------------------------------------------------------------- 1 | const Icon = ({ isDir }) => 2 | isDir ? ( 3 | Folder 4 | ) : ( 5 | Folder 6 | ); 7 | 8 | export default Icon; 9 | -------------------------------------------------------------------------------- /src/components/Button/Button.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Button { 4 | @include mixins.fonttype; 5 | width: 200px; 6 | font-size: 16px; 7 | color: white; 8 | padding-top: 8px; 9 | padding-bottom: 8px; 10 | outline: none; 11 | border: 0px; 12 | } 13 | 14 | 15 | .Button:disabled { 16 | opacity: .5; 17 | } -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @use './scss/mixins'; 2 | 3 | * { 4 | @include mixins.reset; 5 | box-sizing: border-box; 6 | } 7 | 8 | :root { 9 | font-size: .9rem; 10 | } 11 | 12 | html, body, #root, .App { 13 | @include mixins.reset; 14 | @include mixins.fullscreen; 15 | 16 | overflow: hidden !important; 17 | } 18 | 19 | @include mixins.transitionFadeIn; -------------------------------------------------------------------------------- /src/components/Button/Button.jsx: -------------------------------------------------------------------------------- 1 | import './Button.scss'; 2 | 3 | const Button = ({ styles, classNames, text, ...props }) => ( 4 | 12 | ); 13 | 14 | export default Button; 15 | -------------------------------------------------------------------------------- /src/redux/auth/auth.user.js: -------------------------------------------------------------------------------- 1 | export const User = (name, username, password) => ({ 2 | name, 3 | username, 4 | password, 5 | }); 6 | 7 | export const PublicUser = (user) => ({ 8 | name: user.name, 9 | username: user.username, 10 | }); 11 | 12 | export const getName = (user) => user.name; 13 | export const getUsername = (user) => user.username; 14 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $defaultFont: 'Segoe UI', 'Open Sans'; 2 | $defaultFontSize: 16px; 3 | 4 | $taskBarBg: #091418; 5 | $taskBarFg: white; 6 | $taskBarHoverBg: #122830f3; 7 | 8 | $titleBarHeight: 40px; 9 | $titleBarBg: white; 10 | 11 | $itemHoverBg: #d5ecfc; 12 | $itemFocusBg: #cce8fe; 13 | $itemBorderColor: #8ecbfa; 14 | $menuBarBorder: #f1f1f1; 15 | -------------------------------------------------------------------------------- /src/redux/memory/memory.action.js: -------------------------------------------------------------------------------- 1 | import MemoryActionTypes from './memory.types'; 2 | 3 | export const startNewProgram = (app, metadata) => ({ 4 | type: MemoryActionTypes.START_NEW_PROGRAM, 5 | payload: { app, metadata }, 6 | }); 7 | 8 | export const terminateProgram = (pId) => ({ 9 | type: MemoryActionTypes.TERMINATE_PROGRAM, 10 | payload: pId, 11 | }); 12 | -------------------------------------------------------------------------------- /src/redux/auth/auth.selectors.js: -------------------------------------------------------------------------------- 1 | const authState = (state) => state.auth; 2 | 3 | export const selectUsers = (store) => authState(store).users; 4 | 5 | export const selectActiveUser = (store) => authState(store).activeUser; 6 | 7 | export const selectAuthError = (store) => authState(store).error; 8 | 9 | export const selectAuthSuccess = (store) => authState(store).success; 10 | -------------------------------------------------------------------------------- /public/icons/MiFolder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/BootScreen/BootScreen.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .BootScreen { 4 | @include mixins.fullscreen; 5 | position: absolute; 6 | background: rgb(1, 1, 16); 7 | top: 0; 8 | left: 0; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | 13 | > * { 14 | margin: auto; 15 | } 16 | 17 | .brandlogo { 18 | color: lightblue; 19 | font-size: 160px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/redux/boot/boot.actions.js: -------------------------------------------------------------------------------- 1 | import BootActionTypes from './boot.types'; 2 | 3 | export const getBootBackgrounds = () => ({ 4 | type: BootActionTypes.GET_BACKGROUNDS, 5 | }); 6 | 7 | export const setLockScreenBackground = () => ({ 8 | type: BootActionTypes.SET_LOCK_SCREEN_BACKGROUND, 9 | }); 10 | 11 | export const setLoginScreenBackground = () => ({ 12 | type: BootActionTypes.SET_LOGIN_SCREEN_BACKGROUND, 13 | }); 14 | -------------------------------------------------------------------------------- /src/redux/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import AuthReducer from './auth'; 4 | import BootReducer from './boot'; 5 | import AccountReducer from './account'; 6 | import MemoryReducer from './memory'; 7 | 8 | const reducer = combineReducers({ 9 | auth: AuthReducer, 10 | boot: BootReducer, 11 | account: AccountReducer, 12 | memory: MemoryReducer, 13 | }); 14 | 15 | export default reducer; 16 | -------------------------------------------------------------------------------- /src/pages/Program/Program.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Program { 4 | position: absolute; 5 | top: 10%; 6 | left: 30%; 7 | background: white; 8 | flex-direction: column; 9 | box-shadow: 0px 0px 5px 2px #2d2d2d; 10 | transition: top 0.3s ease-out, right 0.3s ease-out, bottom 0.3s ease-out, left 0.3s ease-out; 11 | 12 | &-component { 13 | @include mixins.fullscreen; 14 | position: relative; 15 | } 16 | } -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/config/wallpapers.js: -------------------------------------------------------------------------------- 1 | const Wallpapers = { 2 | list: [ 3 | { 4 | name: '9fa80fd805562a6bc817f01f48b8b93e.jpg', 5 | description: 'Wharariki Beach Cave, Archway Islands, South Island of New Zealand', 6 | }, 7 | { 8 | name: 'd2luMTBfb2ZmaWNpYWxfd2FsbHBhcGVy.jpg', 9 | description: 'Windows 10 Official wallpaper', 10 | }, 11 | ], 12 | defaultWallpaper: 1, 13 | }; 14 | 15 | export default Wallpapers; 16 | -------------------------------------------------------------------------------- /src/pages/BootScreen/BootScreen.jsx: -------------------------------------------------------------------------------- 1 | import { GrWindows } from 'react-icons/gr'; 2 | 3 | import BootLogo from '../../components/BootLogo/BootLogo'; 4 | import './BootScreen.scss'; 5 | 6 | const BootScreen = () => ( 7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 | ); 16 | 17 | export default BootScreen; 18 | -------------------------------------------------------------------------------- /src/redux/account/account.types.js: -------------------------------------------------------------------------------- 1 | const AccountActionTypes = { 2 | LOAD_ACCOUNT: 'LOAD_ACCOUNT', 3 | GET_ACCOUNT_SETTINGS: 'GET_ACCOUNT_SETTINGS', 4 | GET_FILESYSTEM: 'GET_FILESYSTEM', 5 | SAVE_ACCOUNT: 'SAVE_ACCOUNT', 6 | 7 | CREATE_NEW_DIR_ITEM: 'CREATE_NEW_DIR_ITEM', 8 | RENAME_DIR_ITEM: 'RENAME_DIR_ITEM', 9 | DELETE_DIR_ITEM: 'DELETE_DIR_ITEM', 10 | COPY_DIR_ITEM: 'COPY_DIR_ITEM', 11 | MOVE_DIR_ITEM: 'MOVE_DIR_ITEM', 12 | }; 13 | 14 | export default AccountActionTypes; 15 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.jsx: -------------------------------------------------------------------------------- 1 | import BootLogo from '../BootLogo/BootLogo'; 2 | import './Loading.scss'; 3 | 4 | const Loading = (props) => { 5 | const { message = 'Loading...', background = '#1f1e49' } = props; 6 | const style = { background }; 7 | 8 | return ( 9 |
10 | 11 |
12 |

{message}

13 |
14 |
15 | ); 16 | }; 17 | 18 | export default Loading; 19 | -------------------------------------------------------------------------------- /src/softwares/Explorer/components/Sidebar/Sidebar.scss: -------------------------------------------------------------------------------- 1 | .Explorer-sidebar { 2 | position: sticky; 3 | top: 0; 4 | width: 144px; 5 | height: 100%; 6 | font-size: 13px; 7 | border-right: 1px solid lightgray; 8 | cursor: default; 9 | 10 | &-item { 11 | display: flex; 12 | padding: 4px 8px; 13 | 14 | &-subitems { 15 | > * { 16 | padding-left: 16px; 17 | } 18 | } 19 | 20 | & span:first-of-type { 21 | font-size: 1.25em; 22 | margin-right: 8px; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/disk/disk.read.js: -------------------------------------------------------------------------------- 1 | const searchInIDB = (key) => (db, STORENAME) => 2 | new Promise((resolve, reject) => { 3 | if (!key) { 4 | reject(new Error('No key provided to search the store')); 5 | } 6 | 7 | const transaction = db.transaction(STORENAME, 'readonly'); 8 | const store = transaction.objectStore(STORENAME); 9 | const query = store.get(key); 10 | 11 | query.onsuccess = () => resolve(query.result); 12 | query.onerror = () => reject(query.error); 13 | }); 14 | 15 | export default searchInIDB; 16 | -------------------------------------------------------------------------------- /src/redux/account/account.selectors.js: -------------------------------------------------------------------------------- 1 | const accountState = (state) => state.account; 2 | 3 | export const selectAccountSettings = (store) => accountState(store).settings; 4 | 5 | export const selectTaskBarApps = (store) => accountState(store).taskbarApps; 6 | 7 | export const selectDirectoryItems = (id) => (store) => { 8 | const fs = accountState(store).filesystem; 9 | return fs[id] ? fs[id].children.map((child) => fs[child].node) : []; 10 | }; 11 | 12 | export const selectDefaultApps = (store) => accountState(store).defaultApps; 13 | -------------------------------------------------------------------------------- /public/icons/MiDocument.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/disk/disk.delete.js: -------------------------------------------------------------------------------- 1 | const deleteFromIDB = (key) => (db, STORENAME) => 2 | new Promise((resolve, reject) => { 3 | if (!key) { 4 | reject(new Error('No key provided to delete from the store')); 5 | } 6 | 7 | const transaction = db.transaction(STORENAME, 'readwrite'); 8 | const store = transaction.objectStore(STORENAME); 9 | const query = store.delete(key); 10 | 11 | query.onsuccess = () => resolve(query.result); 12 | query.onerror = () => reject(query.error); 13 | }); 14 | 15 | export default deleteFromIDB; 16 | -------------------------------------------------------------------------------- /src/redux/auth/auth.actions.js: -------------------------------------------------------------------------------- 1 | import AuthActionTypes from './auth.types'; 2 | 3 | export const checkUserSession = () => ({ 4 | type: AuthActionTypes.CHECK_USER_SESSION, 5 | }); 6 | 7 | export const createNewAccount = (userDetails) => ({ 8 | type: AuthActionTypes.CREATE_NEW_ACCOUNT, 9 | payload: userDetails, 10 | }); 11 | 12 | export const signInUser = (userIndex, password) => ({ 13 | type: AuthActionTypes.SIGN_IN, 14 | payload: { userIndex, password }, 15 | }); 16 | 17 | export const logOut = () => ({ 18 | type: AuthActionTypes.LOGOUT_USER, 19 | }); 20 | -------------------------------------------------------------------------------- /src/disk/disk.write.js: -------------------------------------------------------------------------------- 1 | const writeInIDB = (document) => (db, STORENAME) => 2 | new Promise((resolve, reject) => { 3 | if (!document.id) { 4 | reject(new Error('No unique key provided to store the document')); 5 | } 6 | 7 | const transaction = db.transaction(STORENAME, 'readwrite'); 8 | const store = transaction.objectStore(STORENAME); 9 | const query = store.put(document, document.id); 10 | 11 | query.onsuccess = () => resolve(query.result); 12 | query.onerror = () => reject(query.error); 13 | }); 14 | 15 | export default writeInIDB; 16 | -------------------------------------------------------------------------------- /src/softwares/Calender/Calender.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Calender { 4 | @include mixins.fullscreen; 5 | display: flex; 6 | 7 | &-leftbar { 8 | height: 100%; 9 | flex: 1; 10 | border-right: 2px solid lightgray; 11 | display: flex; 12 | flex-direction: column; 13 | padding: 1rem; 14 | font-size: 2.5em; 15 | 16 | svg { 17 | margin-bottom: 1em; 18 | } 19 | } 20 | 21 | &-rightbar { 22 | height: 100%; 23 | flex: 2; 24 | padding: 2rem 1rem; 25 | font-size: 1em; 26 | display: flex; 27 | align-items: center; 28 | } 29 | } -------------------------------------------------------------------------------- /src/softwares/Settings/Settings.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Settings { 4 | @include mixins.fullscreen; 5 | display: flex; 6 | 7 | &-leftbar { 8 | height: 100%; 9 | flex: 1; 10 | border-right: 2px solid lightgray; 11 | display: flex; 12 | flex-direction: column; 13 | padding: 1rem; 14 | font-size: 2.5em; 15 | 16 | svg { 17 | margin-bottom: 1em; 18 | } 19 | } 20 | 21 | &-rightbar { 22 | height: 100%; 23 | flex: 2; 24 | padding: 2rem 1rem; 25 | font-size: 1em; 26 | display: flex; 27 | align-items: center; 28 | } 29 | } -------------------------------------------------------------------------------- /src/softwares/Calender/Calender.jsx: -------------------------------------------------------------------------------- 1 | import { VscDebug } from 'react-icons/vsc'; 2 | import './Calender.scss'; 3 | 4 | const Calender = () => ( 5 |
6 |
7 | 8 |

Oops! Mindows must be broken!!

9 |
10 |
11 |
12 |

There seems to be no Calender installed on the system.

13 |

But wait... you can always add it and send a pull request.

14 |
15 |
16 |
17 | ); 18 | 19 | export default Calender; 20 | -------------------------------------------------------------------------------- /src/softwares/Settings/Settings.jsx: -------------------------------------------------------------------------------- 1 | import { VscDebug } from 'react-icons/vsc'; 2 | import './Settings.scss'; 3 | 4 | const Settings = () => ( 5 |
6 |
7 | 8 |

Oops! Mindows must be broken!!

9 |
10 |
11 |
12 |

There seems to be no Settings installed on the system.

13 |

But wait... you can always add it and send a pull request.

14 |
15 |
16 |
17 | ); 18 | 19 | export default Settings; 20 | -------------------------------------------------------------------------------- /src/softwares/Calculator/Calculator.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Calculator { 4 | @include mixins.fullscreen; 5 | display: flex; 6 | 7 | &-leftbar { 8 | height: 100%; 9 | flex: 1; 10 | border-right: 2px solid lightgray; 11 | display: flex; 12 | flex-direction: column; 13 | padding: 1rem; 14 | font-size: 2.5em; 15 | 16 | svg { 17 | margin-bottom: 1em; 18 | } 19 | } 20 | 21 | &-rightbar { 22 | height: 100%; 23 | flex: 2; 24 | padding: 2rem 1rem; 25 | font-size: 1em; 26 | display: flex; 27 | align-items: center; 28 | } 29 | } -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Loading { 4 | @include mixins.fullscreen; 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | font-size: 1em; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | flex-direction: column; 13 | transition-property: opacity; 14 | transition-timing-function: ease; 15 | } 16 | 17 | .heading { 18 | text-align: center; 19 | padding-top: 48px; 20 | 21 | p { 22 | color: white; 23 | @include mixins.fonttype('light'); 24 | } 25 | 26 | &-subtitle { 27 | font-size: 24px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/softwares/Notepad/Notepad.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .Notepad { 4 | @include mixins.fullscreen; 5 | position: relative; 6 | display: flex; 7 | flex-direction: column; 8 | 9 | &-content { 10 | @include mixins.fullscreen; 11 | @include mixins.fonttype; 12 | 13 | &-textarea { 14 | @include mixins.fullscreen; 15 | padding: 8px; 16 | resize: none !important; 17 | outline: none !important; 18 | border: 0 !important; 19 | overflow-y: scroll; 20 | } 21 | 22 | &-textarea:focus { 23 | outline: none !important; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/softwares/Calculator/Calculator.jsx: -------------------------------------------------------------------------------- 1 | import { VscDebug } from 'react-icons/vsc'; 2 | import './Calculator.scss'; 3 | 4 | const Calculator = () => ( 5 |
6 |
7 | 8 |

Oops! Mindows must be broken!!

9 |
10 |
11 |
12 |

There seems to be no Calculator installed on the system.

13 |

But wait... you can always add it and send a pull request.

14 |
15 |
16 |
17 | ); 18 | 19 | export default Calculator; 20 | -------------------------------------------------------------------------------- /src/components/LockCover/LockCover.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .LockCover { 4 | @include mixins.fullscreen; 5 | @include mixins.wallpaperbg; 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | opacity: 0; 10 | transition: top .5s ease-out, opacity .25s ease; 11 | } 12 | 13 | .LockCoverHidden { 14 | top: -100%; 15 | } 16 | 17 | div.currentTime { 18 | @include mixins.fonttype('light'); 19 | 20 | position: absolute; 21 | left: 64px; 22 | bottom: 96px; 23 | color: white; 24 | 25 | p.title { 26 | font-size: 120px; 27 | margin-bottom: -16px; 28 | } 29 | 30 | p.subtitle { 31 | font-size: 64px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/redux/boot/boot.utils.js: -------------------------------------------------------------------------------- 1 | import Wallpapers from '../../config/wallpapers'; 2 | 3 | const storageKeys = { 4 | LOCK_SCREEN_BACKGROUND: 'LOCK_SCREEN_BACKGROUND', 5 | LOGIN_SCREEN_BACKGROUND: 'LOGIN_SCREEN_BACKGROUND', 6 | }; 7 | 8 | export const getLockScreenBackground = () => { 9 | const name = localStorage.getItem(storageKeys.LOCK_SCREEN_BACKGROUND); 10 | return name in Wallpapers.list ? name : Wallpapers.list[0].name; 11 | }; 12 | 13 | export const getLoginScreenBackground = () => { 14 | const name = localStorage.getItem(storageKeys.LOGIN_SCREEN_BACKGROUND); 15 | return name in Wallpapers.list ? name : Wallpapers.list[Wallpapers.defaultWallpaper].name; 16 | }; 17 | -------------------------------------------------------------------------------- /src/config/strings.js: -------------------------------------------------------------------------------- 1 | const Strings = { 2 | INCORRECT_PASSWORD: 'The password is incorrect. Try again.', 3 | LOGIN_LINK_SIGNUP_VIEW: 'Or, even better, sign-in', 4 | SIGNUP_LINK_LOGIN_VIEW: 'Sign-up options', 5 | SIGNUP_NAME_VIEW_TITLE: "Who's going to use this PC?", 6 | SIGNUP_NAME_VIEW_SUBTITLE: 'What name do you want to use?', 7 | SIGNUP_PASSWORD_VIEW_TITLE: 'Create a super memorable password', 8 | SIGNUP_PASSWORD_VIEW_SUBTITLE: "Make sure to pick something you'll absolutely remember", 9 | SUCCESSFUL_LOGIN_WELCOME_TEXT: 'Welcome', 10 | TASKBAR_SEARCH_PLACEHOLDER: 'Type here to search', 11 | PC_FILESYSTEM_ROOT_NAME: 'MY PC', 12 | }; 13 | 14 | export default Strings; 15 | -------------------------------------------------------------------------------- /src/redux/boot/index.js: -------------------------------------------------------------------------------- 1 | import BootActionTypes from './boot.types'; 2 | import { getLockScreenBackground, getLoginScreenBackground } from './boot.utils'; 3 | 4 | const initialState = { 5 | backgrounds: { lock: null, login: null }, 6 | }; 7 | 8 | const BootReducer = (state = initialState, action) => { 9 | switch (action.type) { 10 | case BootActionTypes.GET_BACKGROUNDS: { 11 | const lockBackground = getLockScreenBackground(); 12 | const loginBackground = getLoginScreenBackground(); 13 | return { ...state, backgrounds: { lock: lockBackground, login: loginBackground } }; 14 | } 15 | 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default BootReducer; 22 | -------------------------------------------------------------------------------- /src/redux/auth/auth.utils.js: -------------------------------------------------------------------------------- 1 | import { PublicUser } from './auth.user'; 2 | 3 | const storageKeys = { 4 | PERSISTENT_USERS: 'PERSISTENT_USERS', 5 | }; 6 | 7 | export const checkForStoredUser = () => { 8 | const users = localStorage.getItem(storageKeys.PERSISTENT_USERS); 9 | return users ? JSON.parse(users) : []; 10 | }; 11 | 12 | export const saveNewUser = (user) => { 13 | const users = checkForStoredUser().concat(user); 14 | localStorage.setItem(storageKeys.PERSISTENT_USERS, JSON.stringify(users)); 15 | return users; 16 | }; 17 | 18 | export const safeguardUser = (users) => (users ? users.map((user) => PublicUser(user)) : users); 19 | 20 | export const signInUser = (userIndex, password) => { 21 | const users = checkForStoredUser(); 22 | return users[userIndex].password === password; 23 | }; 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | 6 | import './index.scss'; 7 | import App from './App'; 8 | import store from './redux/store'; 9 | // import reportWebVitals from './reportWebVitals'; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | , 19 | document.getElementById('root') 20 | ); 21 | 22 | // If you want to start measuring performance in your app, pass a function 23 | // to log results (for example: reportWebVitals(console.log)) 24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 25 | // reportWebVitals(); 26 | -------------------------------------------------------------------------------- /src/disk/disk.connect.js: -------------------------------------------------------------------------------- 1 | const checkBrowserSupport = () => 2 | window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; 3 | 4 | export const indexedDB = checkBrowserSupport(); 5 | 6 | export const connectIDB = (DB_NAME, DB_VERSION, STORENAME) => { 7 | const onupgradeneeded = (db) => { 8 | console.log('IndexedDb OnUpgradeNeeded'); 9 | if (!db.objectStoreNames.contains(STORENAME)) { 10 | db.createObjectStore(STORENAME); 11 | } 12 | }; 13 | 14 | return new Promise((resolve, reject) => { 15 | const request = indexedDB.open(DB_NAME, DB_VERSION); 16 | request.onupgradeneeded = () => onupgradeneeded(request.result); 17 | request.onsuccess = () => resolve(request.result); 18 | request.onerror = () => reject(request.error); 19 | request.onblocked = () => reject(new Error('IndexedDb Blocked')); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/pages/NewAccount/NewAccount.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .NewAccount { 4 | @include mixins.fullscreen; 5 | position: absolute; 6 | background: #1f1e49; 7 | top: 0; 8 | left: 0; 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | 13 | .view-header { 14 | width: 100%; 15 | background: #13122b; 16 | padding: 0 16px; 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | color: white; 21 | 22 | > * { 23 | padding: 8px 0; 24 | } 25 | 26 | > svg { 27 | font-size: 32px; 28 | } 29 | 30 | &-title { 31 | @include mixins.fonttype('semibold'); 32 | text-align: center; 33 | width: 320px; 34 | color: white; 35 | border-bottom: 2px solid white; 36 | } 37 | } 38 | 39 | .view-content { 40 | @include mixins.fullscreen; 41 | height: calc(100% - 40px); 42 | position: relative; 43 | } 44 | -------------------------------------------------------------------------------- /src/components/BootLogo/BootLogo.jsx: -------------------------------------------------------------------------------- 1 | import './BootLogo.scss'; 2 | 3 | /** 4 | * The following code is taken from codepen created by Fernando de Almeida Faria. 5 | * https://codepen.io/feebaa/pen/PPrLQP 6 | */ 7 | 8 | const BootLogo = (props) => { 9 | const { size = 'medium' } = { ...props }; 10 | const componentSizes = { 11 | loader: size === 'medium' ? '48px' : '32px', 12 | circle: size === 'medium' ? '32px' : '24px', 13 | }; 14 | 15 | const loaderStyle = { height: componentSizes.loader, width: componentSizes.loader }; 16 | const circleStyle = { height: componentSizes.circle, width: componentSizes.circle }; 17 | 18 | return ( 19 |
20 | {[...Array(5)].map((_, i) => ( 21 |
22 | ))} 23 |
24 | ); 25 | }; 26 | 27 | export default BootLogo; 28 | -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | src: url('/fonts/OpenSans-Regular.ttf') format('ttf'); 4 | } 5 | 6 | @font-face { 7 | font-family: 'Open Sans Light'; 8 | src: url('/fonts/OpenSans-Light.ttf') format('ttf'); 9 | } 10 | 11 | @font-face { 12 | font-family: 'Open Sans Semibold'; 13 | src: url('/fonts/OpenSans-SemiBold.ttf') format('ttf'); 14 | } 15 | 16 | @font-face { 17 | font-family: 'Open Sans Bold'; 18 | src: url('/fonts/OpenSans-Bold.ttf') format('ttf'); 19 | } 20 | 21 | @font-face { 22 | font-family: 'Open Sans Italic'; 23 | src: url('/fonts/OpenSans-Italic.ttf') format('ttf'); 24 | } 25 | 26 | html, body { 27 | background: rgb(1, 1, 16); 28 | font-family: 'Segoe UI', 'Open Sans'; 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } -------------------------------------------------------------------------------- /src/components/Clock/Clock.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { subscribeClock, unsubscribeClock, formatDateTime } from '../../app.util'; 3 | import './Clock.scss'; 4 | 5 | const dateOptions = { weekday: 'short', month: 'long', day: 'numeric' }; 6 | const timeOptions = { hour12: true, hour: 'numeric', minute: '2-digit', hour12suffix: true }; 7 | 8 | const Clock = () => { 9 | const [currentDateTime, updateCurrentDateTime] = useState(new Date()); 10 | const dateTimeFormat = formatDateTime(currentDateTime, dateOptions, timeOptions); 11 | 12 | // set time interval for current time clock 13 | useEffect(() => { 14 | const clockRef = subscribeClock(updateCurrentDateTime); 15 | return () => unsubscribeClock(clockRef); 16 | }, []); 17 | 18 | return ( 19 |
20 |
{dateTimeFormat.time}
21 |
{dateTimeFormat.date}
22 |
23 | ); 24 | }; 25 | 26 | export default Clock; 27 | -------------------------------------------------------------------------------- /src/app.util.js: -------------------------------------------------------------------------------- 1 | export const addKeyListener = (handler, on = 'keydown') => { 2 | document.addEventListener(on, handler); 3 | }; 4 | 5 | export const removeKeyListener = (handler, on = 'keydown') => { 6 | document.removeEventListener(on, handler); 7 | }; 8 | 9 | export const subscribeClock = (onUpdate) => { 10 | const currentDateTime = new Date(); 11 | let clockRef = setTimeout(() => { 12 | onUpdate(new Date()); 13 | clockRef = setInterval(() => onUpdate(new Date()), 1000 * 60); 14 | }, (60 - currentDateTime.getSeconds()) * 1000); 15 | return clockRef; 16 | }; 17 | 18 | export const unsubscribeClock = (clockRef) => { 19 | clearInterval(clockRef); 20 | }; 21 | 22 | export const formatDateTime = (dateTime, dateOptions, timeOptions) => { 23 | const time = dateTime.toLocaleTimeString([], timeOptions); 24 | const date = dateTime.toLocaleDateString([], dateOptions); 25 | return { 26 | date, 27 | time: timeOptions.hour12suffix ? time : time.replace('AM', '').replace('PM', ''), 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/disk/disk.dispatch.js: -------------------------------------------------------------------------------- 1 | import { indexedDB, connectIDB } from './disk.connect'; 2 | 3 | const dispatchIDB = async ({ DB_NAME, DB_VERSION }, STORENAME, action) => 4 | new Promise((resolve, reject) => { 5 | let db = null; 6 | try { 7 | if (!indexedDB) { 8 | reject(new Error('IndexedDb not supported in the browser')); 9 | } else if (!DB_NAME || !DB_VERSION || !STORENAME || !action) { 10 | reject(new Error('Required values not provided. Cannot perform the action.')); 11 | } else { 12 | connectIDB(DB_NAME, DB_VERSION, STORENAME) 13 | .then((connectRes) => { 14 | db = connectRes; 15 | action(db, STORENAME) 16 | .then((data) => resolve(data)) 17 | .catch((error) => reject(error)); 18 | }) 19 | .catch((error) => reject(error)); 20 | } 21 | } catch (err) { 22 | reject(err); 23 | } finally { 24 | if (db) db.close(); 25 | } 26 | }); 27 | 28 | export default dispatchIDB; 29 | -------------------------------------------------------------------------------- /src/components/TitleBar/TitleBar.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | @import '../../scss/variables'; 3 | 4 | .TitleBar { 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | width: 100%; 9 | height: $titleBarHeight; 10 | background: $titleBarBg; 11 | 12 | &-details { 13 | height: 100%; 14 | display: flex; 15 | align-items: center; 16 | padding: 0 8px; 17 | 18 | &-logo { 19 | font-size: 20px; 20 | } 21 | 22 | &-title { 23 | @include mixins.fonttype; 24 | margin-left: 8px; 25 | font-size: 13px; 26 | } 27 | } 28 | 29 | &-buttons { 30 | height: 100%; 31 | display: flex; 32 | align-items: center; 33 | justify-content: flex-end; 34 | 35 | > * { 36 | height: 100%; 37 | width: $titleBarHeight; 38 | font-size: 1.0em; 39 | padding: .75em; 40 | } 41 | 42 | & > *:hover { 43 | background: beige; 44 | } 45 | 46 | &-close:hover { 47 | background: crimson; 48 | color: white; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/disk/index.js: -------------------------------------------------------------------------------- 1 | import dispatchIDB from './disk.dispatch'; 2 | import writeInIDB from './disk.write'; 3 | import searchInIDB from './disk.read'; 4 | import deleteFromIDB from './disk.delete'; 5 | 6 | export const updateProgramData = (DISK) => (STORENAME, data, callback) => { 7 | dispatchIDB(DISK, STORENAME, writeInIDB(data)) 8 | .then((res) => callback && callback({ data: res })) 9 | .catch((err) => callback && callback({ error: err })); 10 | }; 11 | 12 | export const getProgramData = (DISK) => (STORENAME, key, callback) => { 13 | dispatchIDB(DISK, STORENAME, searchInIDB(key)) 14 | .then((res) => callback && callback({ data: res })) 15 | .catch((err) => callback && callback({ error: err })); 16 | }; 17 | 18 | export const deleteProgramData = (DISK) => (STORENAME, key, callback) => { 19 | dispatchIDB(DISK, STORENAME, deleteFromIDB(key)) 20 | .then((res) => callback && callback({ data: res })) 21 | .catch((err) => callback && callback({ error: err })); 22 | }; 23 | 24 | export const initDisk = (DB_NAME, DB_VERSION) => ({ 25 | DB_NAME, 26 | DB_VERSION, 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Piyush Madhusudan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/redux/memory/memory.utils.js: -------------------------------------------------------------------------------- 1 | export const loadProgram = (program, metadata = {}) => { 2 | const title = metadata.title || program.config?.initTitle || program.name; 3 | const uniqueId = new Date().getTime().toString() + Math.floor(Math.random() * 100).toString(); 4 | 5 | return { pId: uniqueId, ...metadata, title, ...program }; 6 | }; 7 | 8 | export const updateProgramsData = (data, newProgram) => { 9 | const newData = { ...data }; 10 | newData[newProgram.pId] = newProgram; 11 | return newData; 12 | }; 13 | 14 | export const updateAppsInstances = (data, newProgram) => { 15 | const newInstances = { ...data }; 16 | const key = newProgram.id; 17 | if (!(key in data)) newInstances[key] = []; 18 | newInstances[key] = [...newInstances[key], newProgram.pId]; 19 | return newInstances; 20 | }; 21 | 22 | export const removeProgram = (data, pId) => { 23 | const newData = { ...data }; 24 | delete newData[pId]; 25 | return newData; 26 | }; 27 | 28 | export const removeAppInstance = (data, id, pId) => { 29 | const newData = {}; 30 | newData[id] = data[id].filter((instanceId) => instanceId !== pId); 31 | return Object.assign(data, newData); 32 | }; 33 | -------------------------------------------------------------------------------- /src/redux/memory/index.js: -------------------------------------------------------------------------------- 1 | import MemoryActionTypes from './memory.types'; 2 | import { 3 | loadProgram, 4 | removeAppInstance, 5 | removeProgram, 6 | updateAppsInstances, 7 | updateProgramsData, 8 | } from './memory.utils'; 9 | 10 | const initialState = { 11 | appsInstances: {}, 12 | programsData: {}, 13 | }; 14 | 15 | const MemoryReducer = (state = initialState, action) => { 16 | switch (action.type) { 17 | case MemoryActionTypes.START_NEW_PROGRAM: { 18 | const { app, metadata } = action.payload; 19 | const newProgram = loadProgram(app, metadata); 20 | return { 21 | ...state, 22 | programsData: updateProgramsData(state.programsData, newProgram), 23 | appsInstances: updateAppsInstances(state.appsInstances, newProgram), 24 | }; 25 | } 26 | 27 | case MemoryActionTypes.TERMINATE_PROGRAM: { 28 | const pId = action.payload; 29 | const { id } = state.programsData[pId]; 30 | return { 31 | ...state, 32 | programsData: removeProgram(state.programsData, pId), 33 | appsInstances: removeAppInstance(state.appsInstances, id, pId), 34 | }; 35 | } 36 | 37 | default: 38 | return state; 39 | } 40 | }; 41 | 42 | export default MemoryReducer; 43 | -------------------------------------------------------------------------------- /src/components/TitleBar/TitleBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | VscChromeClose, 4 | VscChromeMinimize, 5 | VscChromeMaximize, 6 | VscChromeRestore, 7 | } from 'react-icons/vsc'; 8 | import './TitleBar.scss'; 9 | 10 | const TitleBar = React.memo( 11 | ({ app, programId, title, isMaximized, onMinimize, onMaximize, onRestore, onTerminate }) => ( 12 |
13 |
14 |
{app.icon()}
15 |
{title}
16 |
17 |
18 |
19 | 20 |
21 |
22 | {isMaximized ? : } 23 |
24 |
25 | 26 |
27 |
28 |
29 | ), 30 | (prevProps, nextProps) => prevProps.isMaximized === nextProps.isMaximized 31 | ); 32 | 33 | export default TitleBar; 34 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @import './variables'; 2 | 3 | @mixin reset { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | @mixin fullscreen { 9 | height: 100%; 10 | width: 100%; 11 | } 12 | 13 | @mixin wallpaperbg { 14 | background-size: cover; 15 | background-repeat: no-repeat; 16 | background-position: center center; 17 | background-attachment: fixed; 18 | } 19 | 20 | @mixin fonttype($type: 'regular') { 21 | @if $type == 'light' { 22 | font-family: 'Segoe UI Light', 'Open Sans Light'; 23 | } @else if $type == 'bold' { 24 | font-family: 'Segoe UI Bold', 'Open Sans Bold'; 25 | } @else if $type == 'semibold' { 26 | font-family: 'Segoe UI Semibold', 'Open Sans Semibold'; 27 | } @else { 28 | font-family: $defaultFont; 29 | } 30 | } 31 | 32 | @mixin overlay { 33 | content: ""; 34 | position: absolute; 35 | background: rgba(0,0,0,.6); 36 | top: 0; 37 | left: 0; 38 | bottom: 0; 39 | right: 0; 40 | } 41 | 42 | @mixin transitionFadeIn { 43 | .fade { 44 | &-enter { 45 | opacity: 0; 46 | } 47 | 48 | &-enter-active { 49 | opacity: 1; 50 | transition: opacity 200ms; 51 | } 52 | 53 | &-exit { 54 | opacity: 1; 55 | } 56 | 57 | &-exit-active { 58 | opacity: 0; 59 | transition: opacity 200ms; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/redux/account/account.fs.js: -------------------------------------------------------------------------------- 1 | const generateRandomId = (len) => { 2 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'; 3 | return [...Array(len)] 4 | .map(() => characters[Math.floor(Math.random() * characters.length)]) 5 | .join(''); 6 | }; 7 | 8 | const Directory = (name, modifiedTime, createdTime) => ({ 9 | id: generateRandomId(5), 10 | name, 11 | modifiedTime: modifiedTime || new Date().getTime(), 12 | createdTime: createdTime || new Date().getTime(), 13 | isDir: true, 14 | }); 15 | 16 | const File = (name, modifiedTime, createdTime) => ({ 17 | id: generateRandomId(5), 18 | name: name.indexOf('.') !== -1 ? name.substr(0, name.lastIndexOf('.')) : name, 19 | extension: name.indexOf('.') !== -1 ? name.substring(name.lastIndexOf('.') + 1) : '', 20 | modifiedTime: modifiedTime || new Date().getTime(), 21 | createdTime: createdTime || new Date().getTime(), 22 | isDir: false, 23 | }); 24 | 25 | const Node = (name, isDir, parent, children = [], mTime = null, id = null) => { 26 | const time = mTime || new Date().getTime(); 27 | const node = isDir ? Directory(name, time, time) : File(name, time, time); 28 | node.id = id || node.id; 29 | return { 30 | node, 31 | parent, 32 | children: [...children], 33 | }; 34 | }; 35 | 36 | export default Node; 37 | -------------------------------------------------------------------------------- /src/redux/account/account.actions.js: -------------------------------------------------------------------------------- 1 | import AccountActionTypes from './account.types'; 2 | 3 | export const loadAccount = (activeUser) => ({ 4 | type: AccountActionTypes.LOAD_ACCOUNT, 5 | payload: activeUser, 6 | }); 7 | 8 | export const saveAccount = (activeUser) => ({ 9 | type: AccountActionTypes.SAVE_ACCOUNT, 10 | payload: activeUser, 11 | }); 12 | 13 | export const getAccountSettings = () => ({ 14 | type: AccountActionTypes.GET_ACCOUNT_SETTINGS, 15 | }); 16 | 17 | export const getAccountFileSystem = () => ({ 18 | type: AccountActionTypes.GET_FILESYSTEM, 19 | }); 20 | 21 | export const createNewDirectoryItem = (item) => ({ 22 | type: AccountActionTypes.CREATE_NEW_DIR_ITEM, 23 | payload: item, 24 | }); 25 | 26 | export const renameDirectoryItems = (ids, newName) => ({ 27 | type: AccountActionTypes.RENAME_DIR_ITEM, 28 | payload: { items: ids, newName }, 29 | }); 30 | 31 | export const unlinkDirectoryItems = (path, ids) => ({ 32 | type: AccountActionTypes.DELETE_DIR_ITEM, 33 | payload: { ids, path }, 34 | }); 35 | 36 | export const copyDirectoryItems = (toPath, itemIds) => ({ 37 | type: AccountActionTypes.COPY_DIR_ITEM, 38 | payload: { toPath, ids: itemIds }, 39 | }); 40 | 41 | export const moveDirectoryItems = (toPath, itemIds) => ({ 42 | type: AccountActionTypes.MOVE_DIR_ITEM, 43 | payload: { toPath, ids: itemIds }, 44 | }); 45 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "airbnb", 10 | "prettier" 11 | ], 12 | "parser": "@babel/eslint-parser", 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "requireConfigFile": false, 18 | "ecmaVersion": 8, 19 | "sourceType": "module" 20 | }, 21 | "plugins": [ 22 | "react", 23 | "prettier" 24 | ], 25 | "rules": { 26 | "no-console": "warn", 27 | "react/react-in-jsx-scope": "off", 28 | "react/prop-types": "off", 29 | "no-unused-vars": "warn", 30 | "no-nested-ternary": "off", 31 | "import/prefer-default-export": "warn", 32 | "react/no-array-index-key": "warn", 33 | "react/jsx-props-no-spreading": [1, { "exceptions": ["button"] }], 34 | "jsx-a11y/no-static-element-interactions": "off", 35 | "jsx-a11y/interactive-supports-focus": "off", 36 | "jsx-a11y/click-events-have-key-events": "off", 37 | "jsx-a11y/no-noninteractive-element-interactions": "off", 38 | "prettier/prettier": "error", 39 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/MenuBar/MenuBar.jsx: -------------------------------------------------------------------------------- 1 | import { VscCheck } from 'react-icons/vsc'; 2 | import './MenuBar.scss'; 3 | 4 | const MenuBar = ({ items }) => { 5 | const MenubarSubmenu = ({ submenu }) => 6 | submenu.map((subitem, j) => ( 7 |
12 | 13 | {subitem.toggleValue && } 14 | 15 | {subitem.title} 16 |
17 | )); 18 | 19 | const MenubarItem = ({ item }) => ( 20 |
26 | {item.title} 27 | {item.submenu && ( 28 |
e.currentTarget.parentNode.blur()} 31 | > 32 | 33 |
34 | )} 35 |
36 | ); 37 | 38 | return ( 39 |
40 | {items.map((item, i) => ( 41 | 42 | ))} 43 |
44 | ); 45 | }; 46 | 47 | export default MenuBar; 48 | -------------------------------------------------------------------------------- /src/redux/auth/index.js: -------------------------------------------------------------------------------- 1 | import AuthActionTypes from './auth.types'; 2 | import { checkForStoredUser, saveNewUser, safeguardUser, signInUser } from './auth.utils'; 3 | import Strings from '../../config/strings'; 4 | 5 | const initialState = { 6 | users: null, 7 | activeUser: null, 8 | success: false, 9 | error: null, 10 | }; 11 | 12 | const AuthReducer = (state = initialState, action) => { 13 | switch (action.type) { 14 | case AuthActionTypes.CHECK_USER_SESSION: { 15 | const users = state.users || checkForStoredUser(); 16 | return { ...state, users: safeguardUser(users) }; 17 | } 18 | 19 | case AuthActionTypes.CREATE_NEW_ACCOUNT: { 20 | const newUsers = saveNewUser(action.payload); 21 | return { ...state, users: safeguardUser(newUsers) }; 22 | } 23 | 24 | case AuthActionTypes.SIGN_IN: { 25 | const { userIndex, password } = action.payload; 26 | const signInSuccess = signInUser(userIndex, password); 27 | if (signInSuccess) { 28 | return { ...state, activeUser: state.users[userIndex], success: true }; 29 | } 30 | return { ...state, error: { text: Strings.INCORRECT_PASSWORD, __id: new Date().getTime() } }; 31 | } 32 | 33 | case AuthActionTypes.LOGOUT_USER: { 34 | return { ...state, success: null, error: null, activeUser: null }; 35 | } 36 | 37 | default: 38 | return { ...state, users: safeguardUser(state.users) }; 39 | } 40 | }; 41 | 42 | export default AuthReducer; 43 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | Mindows 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/config/apps.js: -------------------------------------------------------------------------------- 1 | import { FcCalculator, FcCalendar, FcDocument, FcFolder } from 'react-icons/fc'; 2 | import { VscSettingsGear } from 'react-icons/vsc'; 3 | import Calculator from '../softwares/Calculator/Calculator'; 4 | import Calender from '../softwares/Calender/Calender'; 5 | import Explorer from '../softwares/Explorer/Explorer'; 6 | import Notepad from '../softwares/Notepad/Notepad'; 7 | import Settings from '../softwares/Settings/Settings'; 8 | 9 | const InstalledApps = { 10 | calculator: { 11 | id: 'calculator', 12 | name: 'Calculator', 13 | icon: FcCalculator, 14 | component: Calculator, 15 | config: { 16 | initWindowWidth: '320px', 17 | initWindowHeight: '480px', 18 | }, 19 | }, 20 | calendar: { 21 | id: 'calendar', 22 | name: 'Calendar', 23 | icon: FcCalendar, 24 | component: Calender, 25 | }, 26 | fsexplorer: { 27 | id: 'fsexplorer', 28 | name: 'File Explorer', 29 | icon: FcFolder, 30 | component: Explorer, 31 | perms: { 32 | OPEN_DOCUMENT: true, 33 | }, 34 | }, 35 | notepad: { 36 | id: 'notepad', 37 | name: 'Notepad', 38 | icon: FcDocument, 39 | component: Notepad, 40 | config: { 41 | initTitle: 'Untitled - Notepad', 42 | initWindowWidth: '480px', 43 | initWindowHeight: '480px', 44 | }, 45 | }, 46 | settings: { 47 | id: 'settings', 48 | name: 'Settings', 49 | icon: VscSettingsGear, 50 | component: Settings, 51 | }, 52 | }; 53 | 54 | export default InstalledApps; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mindows", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@babel/eslint-parser": "^7.15.0", 7 | "@testing-library/jest-dom": "^5.14.1", 8 | "@testing-library/react": "^11.2.7", 9 | "@testing-library/user-event": "^12.8.3", 10 | "eslint": "6.8.0", 11 | "eslint-config-airbnb": "^18.2.1", 12 | "eslint-config-prettier": "^8.3.0", 13 | "eslint-plugin-import": "2.22.1", 14 | "eslint-plugin-jsx-a11y": "6.4.1", 15 | "eslint-plugin-prettier": "^3.4.0", 16 | "eslint-plugin-react": "7.21.5", 17 | "eslint-plugin-react-hooks": "4", 18 | "prettier": "^2.3.2", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "react-draggable": "^4.4.3", 22 | "react-icons": "^4.2.0", 23 | "react-redux": "^7.2.4", 24 | "react-router-dom": "^5.2.0", 25 | "react-scripts": "4.0.3", 26 | "redux": "^4.1.0", 27 | "sass": "^1.35.2", 28 | "web-vitals": "^1.1.2" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/softwares/Explorer/components/Ribbon/Ribbon.scss: -------------------------------------------------------------------------------- 1 | .disabled { 2 | content: ""; 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | bottom: 0; 7 | right: 0; 8 | background: rgba(233,236,238,.3); 9 | } 10 | 11 | .Explorer-ribbon { 12 | display: flex; 13 | width: 100%; 14 | align-content: center; 15 | background: #e9ecee; 16 | padding: 4px 0; 17 | border-top: 1px solid lightgray; 18 | border-bottom: 1px solid lightgray; 19 | cursor: default; 20 | position: relative; 21 | 22 | &-category { 23 | display: flex; 24 | flex-direction: column; 25 | border-right: 1px solid lightgray; 26 | position: relative; 27 | 28 | &-buttons { 29 | display: flex; 30 | padding: 0 4px; 31 | } 32 | 33 | &-label { 34 | text-align: center; 35 | color: rgba(0,0,0,0.8); 36 | } 37 | 38 | &-item { 39 | width: 56px; 40 | padding: 0 16px; 41 | height: 7em; 42 | display: flex; 43 | flex-direction: column; 44 | align-items: center; 45 | text-align: center; 46 | position: relative; 47 | 48 | &-icon { 49 | font-size: 2em; 50 | 51 | & > * { 52 | height: 1.5em; 53 | } 54 | } 55 | } 56 | 57 | &-item[disabled=""]::after { 58 | @extend .disabled; 59 | } 60 | 61 | &-item:not([disabled=""]):hover { 62 | background: lightblue; 63 | } 64 | } 65 | 66 | &-category[disabled=""]::after { 67 | @extend .disabled; 68 | } 69 | } 70 | 71 | .Explorer-ribbon[disabled=""]::after { 72 | @extend .disabled; 73 | } -------------------------------------------------------------------------------- /src/pages/NewAccount/ChildView.scss: -------------------------------------------------------------------------------- 1 | @use '../../scss/mixins'; 2 | 3 | .ChildView { 4 | @include mixins.fullscreen; 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | display: flex; 9 | } 10 | 11 | .AccountNameView, .AccountPasswordView { 12 | @extend .ChildView; 13 | justify-content: space-between; 14 | align-items: center; 15 | flex-direction: column; 16 | transition: opacity 0.25s ease-out; 17 | } 18 | 19 | .AccountSettingUpView { 20 | @extend .ChildView; 21 | justify-content: center; 22 | align-items: center; 23 | flex-direction: column; 24 | } 25 | 26 | .heading { 27 | 28 | text-align: center; 29 | padding-top: 48px; 30 | 31 | p { 32 | color: white; 33 | @include mixins.fonttype('light'); 34 | } 35 | 36 | &-title { 37 | font-size: 64px; 38 | } 39 | 40 | &-subtitle { 41 | font-size: 24px; 42 | } 43 | } 44 | 45 | .view-form { 46 | 47 | display: flex; 48 | flex-direction: column; 49 | align-items: center; 50 | margin-top: -24px; 51 | 52 | input { 53 | font-size: 18px; 54 | padding: 8px 8px; 55 | outline: none; 56 | border: 2px solid white; 57 | width: 480px; 58 | } 59 | } 60 | 61 | .view-form-pfp { 62 | border-radius: 50%; 63 | border: 2px solid white; 64 | font-size: 120px; 65 | height: 160px; 66 | width: 160px; 67 | display: flex; 68 | text-align: center; 69 | color: white; 70 | margin-bottom: 32px; 71 | 72 | > * { 73 | margin: auto; 74 | } 75 | } 76 | 77 | .view-footer { 78 | 79 | @include mixins.fonttype; 80 | width: 100%; 81 | padding: 0 32px 32px 32px; 82 | display: flex; 83 | flex-direction: row; 84 | justify-content: space-between; 85 | align-items: center; 86 | 87 | a { 88 | text-decoration: none; 89 | color: white; 90 | } 91 | } -------------------------------------------------------------------------------- /src/softwares/Notepad/Notepad.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import './Notepad.scss'; 3 | 4 | const STORENAME = 'documents'; 5 | const formMenubarItems = (onSave, onExit, wordWrap, onWordWrap) => [ 6 | { 7 | title: 'File', 8 | submenu: [ 9 | { title: 'Save', action: onSave }, 10 | { title: 'Exit', action: onExit }, 11 | ], 12 | }, 13 | { 14 | title: 'View', 15 | submenu: [{ title: 'Word Wrap', toggleValue: wordWrap, action: onWordWrap }], 16 | }, 17 | ]; 18 | 19 | const Notepad = (props) => { 20 | const { docId, onTerminate, updateProgramData, getProgramData, createMenubar } = props; 21 | const textRef = useRef(null); 22 | const [text, updateText] = useState(''); 23 | const onExit = onTerminate; 24 | const [wordWrap, onToggleWordWrap] = useState(false); 25 | const style = { whiteSpace: wordWrap ? 'nowrap' : 'pre-wrap' }; 26 | 27 | const onSave = () => { 28 | const data = { 29 | id: docId, 30 | content: textRef.current.value, 31 | }; 32 | updateProgramData(STORENAME, data); 33 | }; 34 | 35 | // get text from storage 36 | useEffect(() => { 37 | if (docId) { 38 | getProgramData(STORENAME, docId, (res) => updateText(res.data?.content || '')); 39 | } 40 | }, []); 41 | 42 | const menu = createMenubar( 43 | formMenubarItems(onSave, onExit, wordWrap, () => onToggleWordWrap(!wordWrap)) 44 | ); 45 | 46 | return ( 47 |
48 | {menu} 49 |
50 |