├── .babelrc ├── .dockerignore ├── .eslintrc.json ├── .github ├── pull_request_template.md └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGE_LOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile-old ├── Dockerrun.aws.json ├── LICENSE.md ├── Procfile ├── README.md ├── __tests__ ├── BottomTabs.test.tsx ├── DragAndDrop.test.tsx ├── NavBar.test.tsx ├── componentBuilder.test.tsx ├── componentReducer.test.ts ├── contextReducer.test.js ├── generateCode.test.ts ├── gql.test.ts ├── helper.test.tsx ├── marketplace.test.tsx ├── playwright │ └── example.spec.ts ├── projects.test.ts ├── server.test.tsx ├── signIn.test.tsx ├── spec.ts ├── stateManagementReducer.test.js ├── tree.test.tsx └── userAuth.test.ts ├── amplify ├── .config │ └── project-config.json ├── README.md ├── backend │ ├── auth │ │ └── reactype24e8d371 │ │ │ └── cli-inputs.json │ ├── backend-config.json │ ├── storage │ │ └── ReacTypeMktImg │ │ │ └── cli-inputs.json │ ├── tags.json │ └── types │ │ └── amplify-dependent-resources-ref.d.ts ├── cli.json └── hooks │ └── README.md ├── app └── src │ ├── Dashboard │ ├── NavbarDash.tsx │ ├── Project.tsx │ ├── ProjectContainer.tsx │ ├── gqlStrings.ts │ └── styles.css │ ├── components │ ├── App.tsx │ ├── ContextAPIManager │ │ ├── AssignTab │ │ │ ├── AssignContainer.tsx │ │ │ └── components │ │ │ │ ├── ComponentDropDrown.tsx │ │ │ │ ├── ComponentTable.tsx │ │ │ │ ├── ContextDropDown.tsx │ │ │ │ └── ContextTable.tsx │ │ ├── ContextManager.tsx │ │ ├── CreateTab │ │ │ ├── CreateContainer.tsx │ │ │ └── components │ │ │ │ ├── AddContextForm.tsx │ │ │ │ ├── AddDataForm.tsx │ │ │ │ └── DataTable.tsx │ │ └── DisplayTab │ │ │ └── DisplayContainer.tsx │ ├── ContextMenu.tsx │ ├── CustomEditIcon.tsx │ ├── StateManagement │ │ ├── CreateTab │ │ │ ├── CreateContainer.tsx │ │ │ └── components │ │ │ │ ├── StatePropsPanel.tsx │ │ │ │ ├── TableParentProps.tsx │ │ │ │ ├── TablePassedInProps.tsx │ │ │ │ └── TableStateProps.tsx │ │ ├── DisplayTab │ │ │ ├── DataTable.tsx │ │ │ ├── DisplayContainer.tsx │ │ │ ├── Tree.tsx │ │ │ └── useResizeObserver.ts │ │ └── StateManagement.tsx │ ├── Tooltip.tsx │ ├── bottom │ │ ├── BottomPanel.tsx │ │ ├── BottomTabs.tsx │ │ ├── ChatRoom.tsx │ │ ├── CodePreview.tsx │ │ ├── CreationPanel.tsx │ │ ├── StylesEditor.tsx │ │ ├── UseStateModal.tsx │ │ ├── VideoMeeting.tsx │ │ └── VideoMeetingControl.tsx │ ├── form │ │ └── Selector.tsx │ ├── left │ │ ├── ComponentDrag.tsx │ │ ├── ComponentsContainer.tsx │ │ ├── ContentArea.tsx │ │ ├── CreateContainer.tsx │ │ ├── CreatePanel.tsx │ │ ├── DragDropPanel.tsx │ │ ├── HTMLItem.tsx │ │ ├── HTMLPanel.tsx │ │ ├── HeaderButton.tsx │ │ ├── MUIDragDropPanel.tsx │ │ ├── MUIItem.tsx │ │ ├── ModulePanel.tsx │ │ ├── RoomsContainer.tsx │ │ ├── Sidebar.tsx │ │ └── TabWithTooltip.tsx │ ├── login │ │ ├── FBPassWord.tsx │ │ ├── SignIn.tsx │ │ └── SignUp.tsx │ ├── main │ │ ├── AddLink.tsx │ │ ├── AddRoute.tsx │ │ ├── Arrow.tsx │ │ ├── Canvas.tsx │ │ ├── CanvasContainer.tsx │ │ ├── DeleteButton.tsx │ │ ├── DemoRender.tsx │ │ ├── DirectChildComponent.tsx │ │ ├── DirectChildHTML.tsx │ │ ├── DirectChildHTMLNestable.tsx │ │ ├── DirectChildMUI.tsx │ │ ├── DirectChildMUINestable.tsx │ │ ├── IndirectChild.tsx │ │ ├── RouteLink.tsx │ │ └── SeparatorChild.tsx │ ├── marketplace │ │ ├── MarketplaceCard.tsx │ │ ├── MarketplaceCardContainer.tsx │ │ └── Searchbar.tsx │ ├── right │ │ ├── ComponentPanel.tsx │ │ ├── ComponentPanelItem.tsx │ │ ├── ComponentPanelRoutingItem.tsx │ │ ├── DeleteProjects.tsx │ │ ├── ExportButton.tsx │ │ ├── LoginButton.tsx │ │ ├── OpenProjects.tsx │ │ ├── ProjectManager.tsx │ │ ├── SaveProjectButton.tsx │ │ ├── SimpleModal.tsx │ │ └── createModal.tsx │ └── top │ │ ├── NavBar.tsx │ │ ├── NavBarButtons.tsx │ │ ├── NewExportButton.tsx │ │ └── PublishModal.tsx │ ├── constants │ ├── ErrorMessages.ts │ ├── ItemTypes.ts │ └── Styling.ts │ ├── containers │ ├── AppContainer.tsx │ ├── CustomizationPanel.tsx │ ├── LeftContainer.tsx │ ├── MainContainer.tsx │ ├── MarketplaceContainer.tsx │ └── ProfileContainer.tsx │ ├── helperFunctions │ ├── DemoRenderHTML.ts │ ├── auth.ts │ ├── changePositionValidation.ts │ ├── cloneDeep.ts │ ├── combineStyles.ts │ ├── componentBuilder.tsx │ ├── componentNestValidation.ts │ ├── cssRefresh.tsx │ ├── esbuildService.ts │ ├── generateCode.ts │ ├── manageSeparators.ts │ ├── projectGetSaveDel.ts │ ├── randomPassword.ts │ ├── renderChildren.tsx │ ├── socket.ts │ └── zipFiles.ts │ ├── index.tsx │ ├── interfaces │ ├── Interfaces.ts │ ├── declarations.d.ts │ └── global.ts │ ├── plugins │ ├── fetch-plugin.ts │ └── unpkg-path-plugin.ts │ ├── public │ ├── ezgif.com-gif-maker.gif │ ├── favicon-original.png │ ├── icons │ │ ├── mac │ │ │ └── icon.icns │ │ ├── png │ │ │ ├── 128x128.png │ │ │ ├── 24x24.png │ │ │ ├── 256x256.png │ │ │ ├── 32x32.png │ │ │ ├── 48x48.png │ │ │ ├── 512x512.png │ │ │ ├── 64x64.png │ │ │ └── 96x96.png │ │ └── win │ │ │ ├── dark-mode.ico │ │ │ ├── favIcon.ico │ │ │ ├── icon-original.ico │ │ │ ├── image.png │ │ │ ├── light-mode.ico │ │ │ ├── logo-original.png │ │ │ ├── logo-two.png │ │ │ ├── logo.png │ │ │ ├── reactTypeV20.ico │ │ │ ├── scrollup.png │ │ │ └── white-reactype-logo-1.psd │ ├── index-prod.ejs │ ├── index.ejs │ └── styles │ │ ├── globalDefaultStyles.ts │ │ ├── material-ui-1.svg │ │ ├── style.css │ │ └── theme.ts │ ├── redux │ ├── HTMLTypes.ts │ ├── MUITypes.ts │ ├── reducers │ │ ├── rootReducer.ts │ │ └── slice │ │ │ ├── appStateSlice.ts │ │ │ ├── codePreviewSlice.ts │ │ │ ├── contextReducer.ts │ │ │ ├── roomSlice.ts │ │ │ └── styleSlice.ts │ └── store.ts │ ├── serverConfig.js │ ├── tree │ ├── TreeChart.tsx │ └── useResizeObserver.ts │ ├── tutorial │ ├── CSSEditor.tsx │ ├── Canvas.tsx │ ├── CodePreview.tsx │ ├── ComponentTree.tsx │ ├── Customization.tsx │ ├── HtmlElements.tsx │ ├── KeyboardShortcuts.tsx │ ├── Pages.tsx │ ├── ReusableComponents.tsx │ ├── RouteLinks.tsx │ ├── States.tsx │ ├── Styling.tsx │ ├── Tutorial.tsx │ └── TutorialPage.tsx │ └── utils │ ├── createApplication.util.ts │ ├── createFiles.util.ts │ ├── createGatsbyApp.util.ts │ ├── createGatsbyFiles.util.ts │ ├── createNextApp.util.ts │ ├── createNextFiles.util.ts │ ├── createNonce.ts │ ├── createTestSuite.util.ts │ ├── createTestSuiteClassic.util.ts │ ├── createTestSuiteNext.util.ts │ └── exportProject.util.ts ├── config.js ├── contributors.md ├── docker-compose-prod.yaml ├── index.html ├── mockData.ts ├── package-lock.json ├── package.json ├── playwright.config.ts ├── prettierrc.json ├── reactypeserverlogo.png ├── resources ├── ReadMe.gif ├── annotations_tutorial_images │ ├── Annotation.gif │ └── NoteButton.gif ├── canvasDemoV20.gif ├── canvas_tutorial_images │ ├── canvas1.png │ ├── drag1.gif │ ├── drag1.png │ └── undoRedo.gif ├── code_preview_images │ └── CodePreview.png ├── csseditor_tutorial_images │ ├── CSSEditorTab.png │ ├── CopyPasteCSS.gif │ └── DirectCSSEdit.gif ├── customizing_elements_images │ ├── BackgroundColor.png │ ├── CSS.png │ ├── CodeChange.png │ ├── Display.png │ ├── Flex.png │ ├── Height.png │ ├── Lighting.png │ ├── Resize.gif │ ├── Resize.png │ ├── Theme.png │ ├── Width.png │ ├── linkState.png │ ├── text.gif │ └── textState.png ├── demo.gif ├── demo19.gif ├── export_tests_images │ └── export-new.gif ├── html_elements_tutorial_images │ ├── codeSnippet.png │ ├── createNew.png │ ├── defaultElements.png │ └── newTag.png ├── icon.icns ├── icon.ico ├── icon.png ├── marketplace_images │ ├── marketplace_avatar.png │ └── marketplace_image.png ├── pages_images │ ├── AddElements.png │ ├── DeletePage.png │ ├── Pages.png │ ├── Pages1.png │ ├── PagesPanel.png │ ├── PagesSwapping.gif │ └── Toggle.png ├── reactype17.png ├── reactype19.png ├── readme.png ├── reusable_components_tutorial_images │ ├── reusablecomponents1.png │ ├── reusablecomponents2.png │ └── reusablecomponents3.png ├── route_links_tutorial_images │ ├── links-canvas.PNG │ ├── links1.PNG │ ├── links2.PNG │ ├── links3.PNG │ ├── links4.PNG │ ├── links5.PNG │ └── links6.PNG ├── state_tutorial_images │ ├── CodePreview.png │ ├── CodePreview2.png │ ├── CreateState.png │ ├── DeleteState.gif │ ├── StateTable.png │ ├── delete.gif │ ├── display.gif │ ├── fullStateManageTab.png │ ├── table1.gif │ └── table3.gif ├── tree_tutorial_images │ ├── tree1.png │ ├── tree2.png │ ├── tree3.png │ ├── tree4.png │ └── tree5.png ├── v19 collab room.png ├── v19filestructure.png ├── v20 collab room.png ├── v20 empty canvas.png ├── v21 File Structure.png ├── v21 MUI Canvas.png ├── v21 Preview.gif ├── v21 code preview.png ├── v22 MUI Canvas.png ├── v22 Preview.gif └── v22 code preview.png ├── server ├── README.md ├── assets │ └── renderDemo.css ├── controllers │ ├── cookieController.ts │ ├── marketplaceController.ts │ ├── projectController.ts │ ├── sessionController.ts │ ├── userController.ts │ └── userStylesController.ts ├── graphQL │ ├── resolvers │ │ ├── mutation.ts │ │ └── query.ts │ └── schema │ │ └── typeDefs.ts ├── interfaces.ts ├── models │ ├── Oauth-model.ts │ └── reactypeModels.ts ├── routers │ ├── auth.ts │ ├── passport-setup.ts │ └── stylesRouter.ts ├── server.ts └── tsconfig.json ├── src └── custom-aws-exports.js ├── tsconfig.json ├── tslint.json ├── vite.config.ts └── vitest.config.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "module-resolver", 5 | { 6 | "cwd": "babelrc", 7 | "alias": { 8 | "Components": "./app/src/components", 9 | "Containers": "./app/src/containers" 10 | } 11 | } 12 | ] 13 | ], 14 | // presets are a set of of plug-ins 15 | "presets": [ 16 | ["@babel/preset-env", { "targets": { "node": "current" } }], 17 | "@babel/preset-typescript", 18 | "@babel/preset-react" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "airbnb-base"], 3 | "parserOptions": { 4 | "ecmaVersion": 2024, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true 8 | } 9 | }, 10 | "plugins": ["@typescript-eslint", "import", "react", "jsx-a11y"], 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | }, 15 | "import/resolver": { 16 | "typescript": { 17 | "alwaysTryTypes": true // This will help ESLint resolve `.ts` and `.tsx` files 18 | } 19 | } 20 | }, 21 | "parser": "@typescript-eslint/parser", 22 | "env": { 23 | "browser": true, 24 | "node": true, 25 | "es6": true 26 | }, 27 | "rules": { 28 | "class-methods-use-this": "off", 29 | "linebreak-style": 0, 30 | "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], 31 | "no-unused-vars": "off", // disable the base rule, use the TypeScript version 32 | "no-console": "off", // "warn": ensure that debugging statements are not left in the final version of the code; "off": disabled 33 | "prefer-const": "warn", // enforces that variables that are never reassigned should be declared as const 34 | "no-undef": "warn", // catch references to variables that have not been declared 35 | "import/no-extraneous-dependencies": "off", // turns off the import/no-extraneous-dependencies rule 36 | "@typescript-eslint/no-explicit-any": "off", // turns off the no-explicit-any rule 37 | "import/extensions": [ 38 | "error", 39 | "ignorePackages", 40 | { 41 | "ts": "never", 42 | "tsx": "never" 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please describe the issue of the pull request and the changes 4 | 5 | 10 | 11 | ## Type of Change 12 | 13 | Please check the options that apply 14 | 15 | - [ ] Bug fix (non-breaking change which fixes an issue) 16 | - [ ] New feature (non-breaking change which adds functionality) 17 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 18 | - [ ] This change requires a documentation update 19 | 20 | # How Has the Changes Been Tested? 21 | 22 | 27 | 28 | # Checklist: 29 | 30 | - [ ] My code follows the style guidelines of this project 31 | - [ ] Changes included in this pull request covers minimal topic 32 | - [ ] I have performed a self-review of my code 33 | - [ ] I have commented my code properly, particularly in hard-to-understand areas 34 | - [ ] I have made corresponding changes to the documentation 35 | - [ ] My changes generate no new warnings 36 | - [ ] I have added tests that prove my fix is effective or that my feature works 37 | - [ ] New and existing unit tests pass locally with my changes 38 | - [ ] Any dependent changes have been merged and published in downstream modules 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [dev] 10 | pull_request: 11 | branches: [dev] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | with: 28 | node-version: 18.13.0 29 | 30 | # Runs a single command using the runners shell 31 | - name: Install dependencies 32 | run: npm install 33 | - name: Run all tests 34 | run: npm run test --bail 35 | 36 | # Runs a set of commands using the runners shell 37 | - name: Run a multi-line script 38 | run: | 39 | echo Add other actions to build, 40 | echo test, and deploy your project. 41 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Install 2 | FROM node:21.2.0-alpine AS installation 3 | 4 | # this at a least keeps the packageloc out of the build stage. 5 | COPY package*.json ./ 6 | # we do need this. 7 | 8 | RUN npm install --omit=dev --no-install-recommends --fetch-retry-maxtimeout 500000 9 | 10 | 11 | 12 | 13 | # Stage 1: Build 14 | FROM node:21.2.0-alpine AS build 15 | 16 | # python: required dependency for node alpine, shrinks image size from 2.17GB to 1.67GB 17 | # as of the v22 team, this does nothing to help the package size, and is not needed, prior to v22, the package size grew to > 3 GB,but the v22 team got it down to 1.48Gb by streamlining the dockerfile logic. 18 | # RUN apk add --no-cache --virtual .gyp \ 19 | # python3 \ 20 | # make \ 21 | # g++ 22 | 23 | ENV NODE_ENV='production' 24 | 25 | 26 | # we can just copy this for savings in build stage ram cap? 27 | COPY --from=installation ./node_modules ./node_modules 28 | 29 | 30 | #yep, we have to copy these.... ); 31 | COPY ./index.html ./index.html 32 | #COPY ./app/src/public/styles/style.css ./app/src/public/styles/style.css 33 | COPY ./app/src ./app/src 34 | COPY ./vite.config.ts ./vite.config.ts 35 | COPY ./resources ./resources 36 | COPY ./src ./src 37 | # we need the package.json, but hopefully, not the pacakge lock. 38 | COPY ./package.json ./ 39 | 40 | RUN npm run prod-build 41 | # as a note, this is much easier for CI/CD, but we could deploy with the build folder part of the zip, which would remove the need for this step.s 42 | 43 | 44 | # # Stage 2: Runtime 45 | FROM node:21.2.0-alpine AS runtime 46 | 47 | WORKDIR /app 48 | 49 | 50 | COPY --from=build /build /app/build 51 | 52 | 53 | # just copy this straight if we can? 54 | COPY ../server ./server 55 | 56 | # these things build dosent have so dont copy them through 57 | COPY package*.json ./ 58 | #COPY .env .env 59 | COPY ./config.js ./config.js 60 | 61 | 62 | # and now we need to copy a bunch of stuff. 63 | COPY --from=build ./node_modules ./node_modules 64 | 65 | ENV PORT=5656 66 | EXPOSE 5656 67 | 68 | ENV IS_DOCKER=true 69 | 70 | ENV VIDEOSDK='vidsdk' 71 | 72 | 73 | CMD [ "npm", "start" ] 74 | -------------------------------------------------------------------------------- /Dockerfile-old: -------------------------------------------------------------------------------- 1 | # Stage 1: Build 2 | FROM node:21.2.0-alpine AS build 3 | 4 | # python: required dependency for node alpine, shrinks image size from 2.17GB to 1.67GB 5 | RUN apk add --no-cache --virtual .gyp \ 6 | python3 \ 7 | make \ 8 | g++ 9 | 10 | RUN npm install -g npm@10.9.1 11 | 12 | 13 | WORKDIR /app 14 | 15 | COPY package*.json ./ 16 | # also make the below copies for the runtime stage to use. 17 | COPY ./config.js ./config.js 18 | COPY ./server ./server 19 | 20 | 21 | RUN npm install --production --no-install-recommends --fetch-retry-maxtimeout 500000 22 | 23 | # install vite virst 24 | 25 | 26 | COPY ./index.html ./index.html 27 | #COPY ./app/src/public/styles/style.css ./app/src/public/styles/style.css 28 | COPY ./app/src ./app/src 29 | COPY ./vite.config.ts ./vite.config.ts 30 | COPY ./resources ./resources 31 | COPY ./src ./src 32 | 33 | 34 | #also copy this one file lol. 35 | 36 | ENV NODE_ENV='production' 37 | # i am hoping the above will make it so that the frontend files will know that it is production. 38 | RUN npm run prod-build 39 | 40 | 41 | #COPY ./build ./build 42 | # they will need the above. 43 | 44 | # Stage 2: Runtime 45 | FROM node:21.2.0-alpine AS runtime 46 | 47 | WORKDIR /app 48 | 49 | COPY --from=build /app/package*.json ./ 50 | 51 | RUN npm install --production --no-install-recommends --fetch-retry-maxtimeout 500000 52 | 53 | # RUN npm run dev-frontend # no, dont just run the app while building 54 | 55 | # make a build file? 56 | 57 | 58 | # --only=prod 59 | # COPY --from=build /app/.env .env 60 | COPY --from=build /app/config.js ./config.js 61 | COPY --from=build /app/server ./server 62 | COPY --from=build /app/build /app/build 63 | 64 | COPY .env .env 65 | #just make the env file go into the docker image? 66 | 67 | 68 | EXPOSE 5656 69 | 70 | ENV IS_DOCKER=true 71 | 72 | 73 | ENV VIDEOSDK='vidsdk' 74 | 75 | ENV PORT=5656 76 | 77 | # no longer put the envs here 78 | 79 | CMD [ "npm", "start" ] 80 | -------------------------------------------------------------------------------- /Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSEBDockerrunVersion": "1", 3 | "Image": { 4 | "Name": "035101486432.dkr.ecr.us-east-1.amazonaws.com/reactype-2stage:latest", 5 | "Update": "true" 6 | }, 7 | "Ports": [ 8 | { 9 | "ContainerPort": "5656" 10 | } 11 | ], 12 | "Environment": [ 13 | { 14 | "Name": "API_BASE_URL", 15 | "Value": "Reactype-v19env.eba-sw2fhsbj.us-east-1.elasticbeanstalk.com" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ReacType 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npx tsx server/server.ts -------------------------------------------------------------------------------- /__tests__/DragAndDrop.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { fireEvent, render, screen } from '@testing-library/react'; 3 | 4 | import ComponentDrag from '../app/src/components/left/ComponentDrag'; 5 | import { DndProvider } from 'react-dnd'; 6 | import DragDropPanel from '../app/src/components/left/DragDropPanel'; 7 | import { HTML5Backend } from 'react-dnd-html5-backend'; 8 | import MainContainer from '../app/src/containers/MainContainer'; 9 | import { Provider } from 'react-redux'; 10 | import React from 'react'; 11 | import store from '../app/src/redux/store'; 12 | import { within } from '@testing-library/react'; 13 | 14 | function TestContext(component) { 15 | return ( 16 | 17 | {component} 18 | 19 | ); 20 | } 21 | 22 | describe('Drag and Drop Side Panel', () => { 23 | it('Renders all HTML Element choices', () => { 24 | render(TestContext()); 25 | expect(screen.getByText('Div')).toBeDefined(); 26 | expect(screen.getByText('Img')).toBeDefined(); 27 | expect(screen.getByText('Form')).toBeDefined(); 28 | expect(screen.getByText('Button')).toBeDefined(); 29 | expect(screen.getByText('Link')).toBeDefined(); 30 | expect(screen.getByText('Paragraph')).toBeDefined(); 31 | expect(screen.getByText('Header 1')).toBeDefined(); 32 | expect(screen.getByText('Header 2')).toBeDefined(); 33 | expect(screen.getByText('Span')).toBeDefined(); 34 | expect(screen.getByText('Input')).toBeDefined(); 35 | expect(screen.getByText('Label')).toBeDefined(); 36 | expect(screen.getByText('Ordered List')).toBeDefined(); 37 | expect(screen.getByText('Unordered List')).toBeDefined(); 38 | expect(screen.getByText('Menu')).toBeDefined(); 39 | expect(screen.getByText('List')).toBeDefined(); 40 | expect(screen.queryByText('separator')).toBe(null); 41 | }); 42 | 43 | it('Renders all React Router Component choices', () => { 44 | render(TestContext()); 45 | 46 | expect(screen.getAllByText('Switch')[0]).toBeDefined(); 47 | expect(screen.getAllByText('Route')[0]).toBeDefined(); 48 | expect(screen.getAllByText('LinkTo')[0]).toBeDefined(); 49 | }); 50 | 51 | it.skip('Should render Roots Components and Reusbale components', () => { 52 | render(TestContext()); 53 | 54 | expect(screen.getByText('Root Components')).toBeDefined(); 55 | expect(screen.getByText('Reusable Components')).toBeDefined(); 56 | }); 57 | 58 | it('test drag and drop', () => { 59 | render( 60 | TestContext( 61 | <> 62 | 63 | 64 | 65 | ) 66 | ); 67 | const drop = screen.getByTestId('drop'); 68 | const div = screen.getAllByText('Div')[0]; 69 | expect(drop).toBeDefined(); 70 | expect(div).toBeDefined(); 71 | fireEvent.dragStart(div); 72 | fireEvent.dragEnter(drop); 73 | fireEvent.dragOver(drop); 74 | fireEvent.drop(drop); 75 | expect(within(drop).getByText('div')).toBeDefined(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /__tests__/helper.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import randomPassword from '../app/src/helperFunctions/randomPassword'; 3 | 4 | describe('Random Password', () => { 5 | it('should generate password with 18 characters', () => { 6 | expect(randomPassword()).toHaveLength(18); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /__tests__/playwright/example.spec.ts: -------------------------------------------------------------------------------- 1 | // import { test, expect } from '@playwright/test'; 2 | 3 | // test('has title', async ({ page }) => { 4 | // await page.goto('https://app.reactype.dev/#/'); 5 | 6 | // // Expect a title "to contain" a substring. 7 | // await expect(page).toHaveTitle('ReacType'); 8 | // }); 9 | 10 | // test('get started link', async ({ page }) => { 11 | // await page.goto('https://playwright.dev/'); 12 | 13 | // // Click the get started link. 14 | // await page.getByRole('link', { name: 'Get started' }).click(); 15 | 16 | // // Expects the URL to contain intro. 17 | // await expect(page).toHaveURL(/.*intro/); 18 | // }); 19 | -------------------------------------------------------------------------------- /__tests__/projects.test.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment node 2 | import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; 3 | import Mongoose from 'mongoose'; 4 | import Request from 'supertest'; 5 | // initializes the project to be sent to server/DB 6 | import http from 'http'; 7 | import mockData from '../mockData'; 8 | import app from '../server/server.ts'; 9 | 10 | const { state, projectToSave } = mockData; 11 | 12 | // save and get projects endpoint testing 13 | describe.skip('Project endpoints tests', () => { 14 | let server; 15 | beforeAll(async () => { 16 | server = http.createServer(app); 17 | // server.listen(); 18 | await vi.waitFor(() => server.listen()); 19 | }); 20 | 21 | afterAll(async () => { 22 | await Mongoose.disconnect(); 23 | // Close the HTTP server 24 | server.close(); 25 | }); 26 | 27 | // test saveProject endpoint 28 | describe.skip('/saveProject', () => { 29 | describe('POST', () => { 30 | it('responds with a status of 200 and json object equal to project sent', async () => { 31 | const res = await Request(server) 32 | .post('/saveProject') 33 | .set('Accept', 'application/json') 34 | .send(projectToSave); 35 | 36 | expect(res.status).toBe(200); 37 | expect(res.header['content-type']).toContain('application/json'); 38 | expect(res.body.name).toBe(projectToSave.name); 39 | }, 20000); 40 | }); 41 | }); 42 | // test getProjects endpoint 43 | describe.skip('/getProjects', () => { 44 | describe('POST', () => { 45 | it('responds with status of 200 and json object equal to an array of user projects', async () => { 46 | const res = await Request(server) 47 | .post('/getProjects') 48 | .set('Accept', 'application/json') 49 | .send({ userId: projectToSave.userId }); 50 | 51 | expect(res.status).toBe(200); 52 | expect(Array.isArray(res.body)).toBeTruthy(); 53 | expect(res.body[0].name).toBe(state.name); 54 | }, 20000); 55 | }); 56 | }); 57 | // test deleteProject endpoint 58 | describe.skip('/deleteProject', () => { 59 | describe('DELETE', () => { 60 | const { name, userId } = projectToSave; 61 | it('responds with status of 200 and json object equal to deleted project', async () => { 62 | const res = await Request(server) 63 | .delete('/deleteProject') 64 | .set('Accept', 'application/json') 65 | .send({ name, userId }); 66 | 67 | expect(res.status).toBe(200); 68 | expect(res.body.name).toBe(projectToSave.name); 69 | }, 20000); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /__tests__/signIn.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import SignIn from '../app/src/components/login/SignIn'; 3 | import React from 'react'; 4 | import { render, screen, fireEvent, waitFor } from '@testing-library/react'; 5 | import { Provider } from 'react-redux'; 6 | import store from '../app/src/redux/store'; 7 | import { BrowserRouter } from 'react-router-dom'; 8 | import '@testing-library/jest-dom'; 9 | 10 | function TestSignIn() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | describe('sign in page', () => { 21 | it('should render a login input', () => { 22 | render(); 23 | expect(screen.getByTestId('username-input')).toBeDefined(); 24 | }); 25 | it('should render a password field', () => { 26 | render(); 27 | expect(screen.getByTestId('password-input')).toBeDefined(); 28 | }); 29 | it('should render 4 login buttons', () => { 30 | render(); 31 | expect(screen.getAllByRole('button')).toHaveLength(1); 32 | }); 33 | it('should invalidate empty username field', () => { 34 | render(); 35 | fireEvent.click(screen.getByRole('button')); 36 | waitFor(() => { 37 | expect(screen.getByText('No Username Input')).toBeDefined(); 38 | }); 39 | }); 40 | it('should invalidate empty password field', () => { 41 | render(); 42 | fireEvent.change(screen.getByRole('textbox'), { 43 | target: { 44 | value: 'username' 45 | } 46 | }); 47 | fireEvent.click(screen.getByRole('button')); 48 | waitFor(() => { 49 | expect(screen.getByText('No Password Input')).toBeDefined(); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /__tests__/spec.ts: -------------------------------------------------------------------------------- 1 | // import 'regenerator-runtime/runtime'; // if there is an error with moduleNameMapper, npm -S install regenerator-runtime 2 | 3 | //const { Application } = require('spectron'); 4 | // const electronPath = require('electron'); 5 | // const path = require('path'); 6 | 7 | // let app; 8 | 9 | // // beforeAll(() => { 10 | // // // create a new app to test with setTimeout to be 15000 because the app takes a few seconds to spin up 11 | // // app = new Application({ 12 | // // path: electronPath, 13 | // // chromeDriverArgs: ['--disable-extensions'], 14 | // // args: [path.join(__dirname, '../app/electron/main.js')] // this is the path from this test file to main.js inside electron folder 15 | // // }); 16 | // // return app.start(); 17 | // // }, 15000); 18 | 19 | // // getWindowsCount() will return 2 instead of 1 in dev mode (one for the actual app, one in the browser at localhost:8080 in dev mode) 20 | // xtest('Displays App window', async () => { 21 | // const windowCount = await app.client.getWindowCount(); 22 | // // expect(windowCount).toBe(1); // this returns true/passed if in production mode, change mode in script "test" to 'production' instead of 'test' 23 | // expect(windowCount).toBe(2); // 'dev' or 'test' mode results in 2 windows (one for the app and one for the browser) 24 | // }); 25 | 26 | // /* we want to test other functionalities of app.client such as text, title, etc. but even the examples from the official spectron website 27 | // or github repo did not yield the same outcomes as demonstrated. So we stopped testing Electron app here */ 28 | // // afterAll(() => { 29 | // // if (app && app.isRunning()) { 30 | // // return app.stop(); 31 | // // } 32 | // // }); 33 | -------------------------------------------------------------------------------- /__tests__/tree.test.tsx: -------------------------------------------------------------------------------- 1 | // vitest-enviroment jsdom 2 | import { describe, it, expect } from 'vitest'; 3 | import TreeChart from '../app/src/tree/TreeChart'; 4 | import React from 'react'; 5 | import { render, screen } from '@testing-library/react'; 6 | import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; 7 | import { Provider } from 'react-redux'; 8 | import store from '../app/src/redux/store'; 9 | import 'd3'; 10 | 11 | let state = JSON.parse(JSON.stringify(initialState)); 12 | state.components = [ 13 | { 14 | id: 1, 15 | name: 'index', 16 | style: {}, 17 | code: `import React, { useState } from 'react'; 18 | import A from '../components/A'; 19 | import B from '../components/B'; 20 | import Head from 'next/head'; 21 | const index = (props): JSX.Element => { 22 | const [value, setValue] = useState('INITIAL VALUE'); 23 | return ( 24 | <> 25 | 26 | index 27 | 28 | 36 | 37 | ); 38 | }; 39 | export default index; 40 | `, 41 | children: [ 42 | { 43 | childId: 1, 44 | children: [ 45 | { 46 | childId: 2, 47 | children: [], 48 | name: 'A', 49 | style: {}, 50 | type: 'Component', 51 | typeId: 2 52 | } 53 | ], 54 | name: 'div', 55 | style: {}, 56 | type: 'HTML Element', 57 | typeId: 11 58 | }, 59 | { 60 | childId: 3, 61 | children: [ 62 | { 63 | childId: 4, 64 | children: [], 65 | name: 'B', 66 | style: {}, 67 | type: 'Component', 68 | typeId: 3 69 | } 70 | ], 71 | name: 'div', 72 | style: {}, 73 | type: 'HTML Element', 74 | typeId: 11 75 | } 76 | ], 77 | isPage: true 78 | }, 79 | { 80 | id: 2, 81 | nextChildId: 1, 82 | name: 'A', 83 | style: {}, 84 | code: '', 85 | children: [], 86 | isPage: false 87 | }, 88 | { 89 | id: 3, 90 | nextChildId: 1, 91 | name: 'B', 92 | style: {}, 93 | code: '', 94 | children: [], 95 | isPage: false 96 | } 97 | ]; 98 | 99 | // renders a tree of the components in tester 100 | describe('Component Tree Render Test', () => { 101 | it('should render full component tree based on state', () => { 102 | render( 103 | 104 | 105 | 106 | ); 107 | // elements that are not separators should appear in the tree 108 | expect(screen.getByText('index')).toBeDefined(); 109 | expect(screen.getByText('A')).toBeDefined(); 110 | expect(screen.getByText('B')).toBeDefined(); 111 | // tree should not include separators 112 | expect(screen.queryByText('separator')).toBe(null); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "ReacType", 3 | "version": "3.1", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "none", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "dist", 10 | "BuildCommand": "npm run-script build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /amplify/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Amplify CLI 2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli). 3 | 4 | Helpful resources: 5 | - Amplify documentation: https://docs.amplify.aws 6 | - Amplify CLI documentation: https://docs.amplify.aws/cli 7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files 8 | - Join Amplify's community: https://amplify.aws/community/ 9 | -------------------------------------------------------------------------------- /amplify/backend/auth/reactype24e8d371/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "cognitoConfig": { 4 | "identityPoolName": "reactype24e8d371_identitypool_24e8d371", 5 | "allowUnauthenticatedIdentities": true, 6 | "resourceNameTruncated": "reacty24e8d371", 7 | "userPoolName": "reactype24e8d371_userpool_24e8d371", 8 | "autoVerifiedAttributes": [ 9 | "email" 10 | ], 11 | "mfaConfiguration": "OFF", 12 | "mfaTypes": [ 13 | "SMS Text Message" 14 | ], 15 | "smsAuthenticationMessage": "Your authentication code is {####}", 16 | "smsVerificationMessage": "Your verification code is {####}", 17 | "emailVerificationSubject": "Your verification code", 18 | "emailVerificationMessage": "Your verification code is {####}", 19 | "defaultPasswordPolicy": false, 20 | "passwordPolicyMinLength": 8, 21 | "passwordPolicyCharacters": [], 22 | "requiredAttributes": [ 23 | "email" 24 | ], 25 | "aliasAttributes": [], 26 | "userpoolClientGenerateSecret": false, 27 | "userpoolClientRefreshTokenValidity": 30, 28 | "userpoolClientWriteAttributes": [ 29 | "email" 30 | ], 31 | "userpoolClientReadAttributes": [ 32 | "email" 33 | ], 34 | "userpoolClientLambdaRole": "reacty24e8d371_userpoolclient_lambda_role", 35 | "userpoolClientSetAttributes": false, 36 | "sharedId": "24e8d371", 37 | "resourceName": "reactype24e8d371", 38 | "authSelections": "identityPoolAndUserPool", 39 | "useDefault": "default", 40 | "userPoolGroupList": [], 41 | "serviceName": "Cognito", 42 | "usernameCaseSensitive": false, 43 | "useEnabledMfas": true, 44 | "authRoleArn": { 45 | "Fn::GetAtt": [ 46 | "AuthRole", 47 | "Arn" 48 | ] 49 | }, 50 | "unauthRoleArn": { 51 | "Fn::GetAtt": [ 52 | "UnauthRole", 53 | "Arn" 54 | ] 55 | }, 56 | "breakCircularDependency": true, 57 | "dependsOn": [] 58 | } 59 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "reactype24e8d371": { 4 | "customAuth": false, 5 | "dependsOn": [], 6 | "frontendAuthConfig": { 7 | "mfaConfiguration": "OFF", 8 | "mfaTypes": [ 9 | "SMS" 10 | ], 11 | "passwordProtectionSettings": { 12 | "passwordPolicyCharacters": [], 13 | "passwordPolicyMinLength": 8 14 | }, 15 | "signupAttributes": [ 16 | "EMAIL" 17 | ], 18 | "socialProviders": [], 19 | "usernameAttributes": [], 20 | "verificationMechanisms": [ 21 | "EMAIL" 22 | ] 23 | }, 24 | "providerPlugin": "awscloudformation", 25 | "service": "Cognito" 26 | } 27 | }, 28 | "storage": { 29 | "ReacTypeMktImg": { 30 | "dependsOn": [], 31 | "providerPlugin": "awscloudformation", 32 | "service": "S3" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /amplify/backend/storage/ReacTypeMktImg/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceName": "ReacTypeMktImg", 3 | "policyUUID": "f87a3e6a", 4 | "bucketName": "reactypemktimgs", 5 | "storageAccess": "authAndGuest", 6 | "guestAccess": [ 7 | "READ" 8 | ], 9 | "authAccess": [ 10 | "CREATE_AND_UPDATE", 11 | "READ", 12 | "DELETE" 13 | ], 14 | "triggerFunction": "NONE", 15 | "groupAccess": {} 16 | } -------------------------------------------------------------------------------- /amplify/backend/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Key": "user:Stack", 4 | "Value": "{project-env}" 5 | }, 6 | { 7 | "Key": "user:Application", 8 | "Value": "{project-name}" 9 | } 10 | ] -------------------------------------------------------------------------------- /amplify/backend/types/amplify-dependent-resources-ref.d.ts: -------------------------------------------------------------------------------- 1 | export type AmplifyDependentResourcesAttributes = { 2 | "auth": { 3 | "reactype24e8d371": { 4 | "AppClientID": "string", 5 | "AppClientIDWeb": "string", 6 | "IdentityPoolId": "string", 7 | "IdentityPoolName": "string", 8 | "UserPoolArn": "string", 9 | "UserPoolId": "string", 10 | "UserPoolName": "string" 11 | } 12 | }, 13 | "storage": { 14 | "ReacTypeMktImg": { 15 | "BucketName": "string", 16 | "Region": "string" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /amplify/cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "features": { 3 | "graphqltransformer": { 4 | "addmissingownerfields": true, 5 | "improvepluralization": false, 6 | "validatetypenamereservedwords": true, 7 | "useexperimentalpipelinedtransformer": true, 8 | "enableiterativegsiupdates": true, 9 | "secondarykeyasgsi": true, 10 | "skipoverridemutationinputtypes": true, 11 | "transformerversion": 2, 12 | "suppressschemamigrationprompt": true, 13 | "securityenhancementnotification": false, 14 | "showfieldauthnotification": false, 15 | "usesubusernamefordefaultidentityclaim": true, 16 | "usefieldnameforprimarykeyconnectionfield": false, 17 | "enableautoindexquerynames": true, 18 | "respectprimarykeyattributesonconnectionfield": true, 19 | "shoulddeepmergedirectiveconfigdefaults": false, 20 | "populateownerfieldforstaticgroupauth": true 21 | }, 22 | "frontend-ios": { 23 | "enablexcodeintegration": true 24 | }, 25 | "auth": { 26 | "enablecaseinsensitivity": true, 27 | "useinclusiveterminology": true, 28 | "breakcirculardependency": true, 29 | "forcealiasattributes": false, 30 | "useenabledmfas": true 31 | }, 32 | "codegen": { 33 | "useappsyncmodelgenplugin": true, 34 | "usedocsgeneratorplugin": true, 35 | "usetypesgeneratorplugin": true, 36 | "cleangeneratedmodelsdirectory": true, 37 | "retaincasestyle": true, 38 | "addtimestampfields": true, 39 | "handlelistnullabilitytransparently": true, 40 | "emitauthprovider": true, 41 | "generateindexrules": true, 42 | "enabledartnullsafety": true, 43 | "generatemodelsforlazyloadandcustomselectionset": false 44 | }, 45 | "appsync": { 46 | "generategraphqlpermissions": true 47 | }, 48 | "latestregionsupport": { 49 | "pinpoint": 1, 50 | "translate": 1, 51 | "transcribe": 1, 52 | "rekognition": 1, 53 | "textract": 1, 54 | "comprehend": 1 55 | }, 56 | "project": { 57 | "overrides": true 58 | } 59 | }, 60 | "debug": { 61 | "shareProjectConfig": false 62 | } 63 | } -------------------------------------------------------------------------------- /amplify/hooks/README.md: -------------------------------------------------------------------------------- 1 | # Command Hooks 2 | 3 | Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc. 4 | 5 | To get started, add your script files based on the expected naming convention in this directory. 6 | 7 | Learn more about the script file naming convention, hook parameters, third party dependencies, and advanced configurations at https://docs.amplify.aws/cli/usage/command-hooks 8 | -------------------------------------------------------------------------------- /app/src/Dashboard/gqlStrings.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | // Query 3 | export const GET_PROJECTS = gql`query GetAllProjects($userId: ID) { 4 | getAllProjects(userId: $userId) { 5 | name 6 | likes 7 | id 8 | userId 9 | username 10 | published 11 | comments { 12 | username 13 | text 14 | } 15 | } 16 | }`; 17 | 18 | // Mutation 19 | export const ADD_LIKE = gql` 20 | mutation AddLike($projId: ID!, $likes: Int!) { 21 | addLike(projId: $projId, likes: $likes) 22 | { 23 | id 24 | } 25 | }`; 26 | 27 | export const MAKE_COPY = gql` 28 | mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { 29 | makeCopy(userId: $userId, projId: $projId, username: $username) 30 | { 31 | id 32 | } 33 | }`; 34 | 35 | export const DELETE_PROJECT = gql` 36 | mutation DeleteProject($projId: ID!) { 37 | deleteProject(projId: $projId) 38 | { 39 | id 40 | } 41 | }`; 42 | 43 | export const PUBLISH_PROJECT = gql` 44 | mutation Publish($projId: ID!, $published: Boolean!) { 45 | publishProject(projId: $projId, published: $published) 46 | { 47 | id 48 | published 49 | } 50 | }`; 51 | 52 | export const ADD_COMMENT = gql` 53 | mutation AddComment($projId: ID!, $comment: String!, $username: String!) { 54 | addComment(projId: $projId, comment: $comment, username: $username) 55 | { 56 | id 57 | } 58 | }`; 59 | -------------------------------------------------------------------------------- /app/src/Dashboard/styles.css: -------------------------------------------------------------------------------- 1 | .project { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: stretch; 5 | margin: 5px; 6 | border: 1px solid #f0f0f0; 7 | border-radius: 5px; 8 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); 9 | height: 500px; 10 | width: 400px; 11 | justify-content: space-between; 12 | } 13 | 14 | .dashboardContainer { 15 | height: 100%; 16 | width: 100%; 17 | } 18 | 19 | .userDashboard { 20 | display: flex; 21 | flex-direction: row; 22 | } 23 | 24 | .projectPanel { 25 | width: 100%; 26 | } 27 | 28 | .projectContainer { 29 | display: flex; 30 | flex-direction: column-reverse; 31 | flex-flow: row wrap; 32 | flex-grow: 1; 33 | overflow-y: scroll; 34 | } 35 | 36 | .projectInfo { 37 | text-align: center; 38 | } 39 | 40 | .commentContainer { 41 | text-align: center; 42 | height: 400px; 43 | overflow-y: scroll; 44 | } 45 | 46 | .commentBtn { 47 | color: #bebebed8; 48 | } 49 | 50 | .comment { 51 | font-size: 125%; 52 | } 53 | 54 | .renderedCom { 55 | text-align: center; 56 | } 57 | 58 | .commentInput { 59 | display: flex; 60 | height: 100px; 61 | width: 100%; 62 | border: 1px solid #f0f0f0; 63 | position: relative; 64 | } 65 | 66 | .commentInput > * { 67 | flex: auto; 68 | } 69 | 70 | .icons { 71 | width: 100%; 72 | display: flex; 73 | justify-content: center; 74 | align-items: center; 75 | } 76 | 77 | .header { 78 | background-color: #f88e16; 79 | color: rgba(255, 255, 255, 0.897); 80 | width: 100%; 81 | position: relative; 82 | } 83 | 84 | .commentField { 85 | border: 1px solid #f0f0f0; 86 | padding-left: 2%; 87 | } 88 | 89 | h1 { 90 | text-align: center; 91 | } 92 | -------------------------------------------------------------------------------- /app/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import '../public/styles/style.css'; 3 | 4 | import React, { useEffect } from 'react'; 5 | import { toggleLoggedIn } from '../redux/reducers/slice/appStateSlice'; 6 | import { useDispatch } from 'react-redux'; 7 | 8 | import AppContainer from '../containers/AppContainer'; 9 | /** 10 | * The `App` component is the root component of the React application. It performs an initial check 11 | * to determine if a user is logged in (not a 'guest') by inspecting local storage, and updates the 12 | * application's state accordingly using Redux. It then renders the `AppContainer`, which serves as 13 | * the main container for the application's user interface. 14 | * 15 | * The `useEffect` hook is used to perform the login check once on component mount, ensuring that 16 | * the login state is correctly set based on the presence of a specific item in local storage. 17 | * 18 | * @returns {JSX.Element} Renders the `AppContainer` wrapped within a div with a class of 'app', 19 | * serving as the root of the user interface. 20 | * 21 | * This component interacts with Redux by dispatching actions to modify the global state, particularly 22 | * the logged-in status of the user. This is central for managing conditional rendering and access 23 | * throughout the application based on user authentication status. 24 | */ 25 | export const App: React.FC = (): JSX.Element => { 26 | const dispatch = useDispatch(); 27 | useEffect(() => { 28 | if (window.localStorage.getItem('ssid') !== 'guest') { 29 | dispatch(toggleLoggedIn(true)); 30 | } 31 | }, []); 32 | 33 | return ( 34 |
35 | 36 |
37 | ); 38 | }; 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import Table from '@mui/material/Table'; 4 | import TableBody from '@mui/material/TableBody'; 5 | import TableContainer from '@mui/material/TableContainer'; 6 | import TableHead from '@mui/material/TableHead'; 7 | import TableRow from '@mui/material/TableRow'; 8 | import Paper from '@mui/material/Paper'; 9 | import { styled } from '@mui/material/styles'; 10 | import TableCell, { tableCellClasses } from '@mui/material/TableCell'; 11 | 12 | /** 13 | * Styles the TableCell component using Material-UI's styling system. Custom styles are applied to table head and body cells: 14 | * - Head cells are styled with a black background and white text. 15 | * - Body cells have a font size of 14. 16 | * 17 | * @param {object} theme - The theme object provided by Material-UI's ThemeProvider. 18 | * @returns {object} A styled TableCell component with customized appearance. 19 | */ 20 | const StyledTableCell = styled(TableCell)(({ theme }) => ({ 21 | [`&.${tableCellClasses.head}`]: { 22 | backgroundColor: theme.palette.common.black, 23 | color: theme.palette.common.white, 24 | }, 25 | [`&.${tableCellClasses.body}`]: { 26 | fontSize: 14, 27 | }, 28 | })); 29 | 30 | /** 31 | * Styles the TableRow component to enhance table row appearance. Custom styles include: 32 | * - Every odd row is styled with a background color for hover state. 33 | * - Removes the border from the last child table cells and headers to enhance appearance. 34 | * 35 | * @param {object} theme - The theme object provided by Material-UI's ThemeProvider. 36 | * @returns {JSX.Element} A styled TableRow component with alternate row coloring and modified border visibility. 37 | */ 38 | const StyledTableRow = styled(TableRow)(({ theme }) => ({ 39 | '&:nth-of-type(odd)': { 40 | backgroundColor: theme.palette.action.hover, 41 | }, 42 | // hide last border 43 | '&:last-child td, &:last-child th': { 44 | border: 0, 45 | }, 46 | })); 47 | 48 | /** 49 | * A styled data table component that displays a list of items in a single column format. 50 | * This component uses Material-UI to create a visually distinct table with styled cells and rows. 51 | * It is specifically designed to display 'contexts consumed' but can be generalized for other single-column data. 52 | * 53 | * @param {Object} props - Component props. 54 | * @param {Array} props.target - The data array containing strings to be displayed in the table. 55 | * 56 | * @returns {JSX.Element} A TableContainer component housing a Table, where each row displays an element from the `target` prop. 57 | */ 58 | export default function DataTable({ target }) { 59 | return ( 60 | 61 | 62 | 63 | 64 | Contexts Consumed 65 | 66 | 67 | 68 | {target.map((data, index) => ( 69 | 70 | 71 | {data} 72 | 73 | 74 | ))} 75 | 76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /app/src/components/ContextAPIManager/ContextManager.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useContext } from 'react'; 3 | import { makeStyles } from '@mui/styles'; 4 | import Box from '@mui/material/Box'; 5 | import Tab from '@mui/material/Tab'; 6 | import TabContext from '@mui/lab/TabContext'; 7 | import TabList from '@mui/lab/TabList'; 8 | import TabPanel from '@mui/lab/TabPanel'; 9 | 10 | import { useSelector } from 'react-redux'; 11 | import CreateContainer from './CreateTab/CreateContainer'; 12 | import AssignContainer from './AssignTab/AssignContainer'; 13 | import DisplayContainer from './DisplayTab/DisplayContainer'; 14 | import { RootState } from '../../redux/store'; 15 | 16 | const useStyles = makeStyles({ 17 | contextContainer: { 18 | height: 'fit-content', 19 | }, 20 | }); 21 | 22 | /** 23 | * Manages and displays tabs for creating, assigning, and displaying contexts in a React application. 24 | * Utilizes Material-UI's TabContext for tab management. This component allows users to switch between 25 | * different functionalities related to context manipulation within the application, such as creating or editing, 26 | * assigning, and displaying contexts. 27 | * 28 | * @returns {JSX.Element} - A component structure with tabs managing different context-related containers. 29 | * Each tab switches to a respective panel that loads a specific container for creating/editing contexts, 30 | * assigning contexts, or displaying contexts. The active tab is highlighted, and each tab panel contains 31 | * specific functionalities encapsulated in the respective container components. 32 | */ 33 | const ContextManager = (props): JSX.Element => { 34 | const style = useSelector((store: RootState) => store.styleSlice); 35 | const classes = useStyles(); 36 | const [value, setValue] = React.useState('1'); 37 | 38 | const handleChange = (event: React.SyntheticEvent, newValue: string) => { 39 | setValue(newValue); 40 | }; 41 | 42 | const color = 'white'; 43 | 44 | return ( 45 | 46 |
47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 | ); 74 | }; 75 | 76 | export default ContextManager; 77 | -------------------------------------------------------------------------------- /app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { useSelector } from 'react-redux'; 4 | import { Chart } from 'react-google-charts'; 5 | import Grid from '@mui/material/Grid'; 6 | import { RootState } from '../../../redux/store'; 7 | 8 | /** 9 | * A React component that displays a WordTree chart based on context data from a Redux store. 10 | * Utilizes the `react-google-charts` library to visualize relationships between components in the application, 11 | * fetching data from the `contextSlice.allContext` in the Redux store. The data should include objects with names and components arrays. 12 | * 13 | * @component 14 | * @example 15 | * ```jsx 16 | * 17 | * ``` 18 | * 19 | * @param {Object} props - The component does not accept any props. 20 | * 21 | * State: 22 | * - `contextData`: An array of arrays structured for the WordTree chart, with the initial element being ["Phrases"]. 23 | * 24 | * Redux State Dependencies: 25 | * - `allContext`: Retrieved from `contextSlice.allContext`, expected to be an array of objects each containing a `name` and a `components` array. 26 | * 27 | * Effects: 28 | * - On mount, transforms the Redux store data into a format suitable for the WordTree chart by calling `transformData`. 29 | * 30 | * Methods: 31 | * - `transformData`: Transforms raw context data from the Redux store to be usable by the `Chart` component, 32 | * mapping each context object to phrases connecting the application name with the context name and its components. 33 | * 34 | * @returns {JSX.Element} A React element containing a Grid layout. If data is available, displays a WordTree chart, otherwise shows a message indicating no data. 35 | */ 36 | const DisplayContainer = (): JSX.Element => { 37 | const allContext = useSelector( 38 | (store: RootState) => store.contextSlice.allContext, 39 | ); 40 | const [contextData, setContextData] = useState([]); 41 | 42 | useEffect(() => { 43 | transformData(); 44 | }, []); 45 | 46 | // formats context data for use in react google charts 47 | const transformData = () => { 48 | const formattedData = allContext 49 | .map((obj) => obj.components.map((component) => [`App ⎯⎯ ${obj.name} ⎯⎯ ${component}`])) 50 | .flat(); 51 | setContextData([['Phrases'], ...formattedData]); 52 | }; 53 | 54 | // format options for google chart 55 | const options = { 56 | wordtree: { 57 | format: 'implicit', 58 | word: 'App', 59 | }, 60 | backgroundColor: '#1E2024', 61 | }; 62 | 63 | return ( 64 | 65 | {contextData.length < 2 &&

No Contexts Consumed

} 66 | 67 | 74 | 75 |
76 | ); 77 | }; 78 | export default DisplayContainer; 79 | -------------------------------------------------------------------------------- /app/src/components/CustomEditIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EditIcon from '@mui/icons-material/Edit'; 3 | import makeStyles from '@mui/styles/makeStyles'; 4 | 5 | const useStyles = makeStyles({ 6 | editIconContainer: { 7 | position: 'absolute', 8 | right: '5%', 9 | top: '50%', 10 | transform: 'translateY(-50%)', 11 | cursor: 'pointer', 12 | color: 'white', 13 | '&:hover': { 14 | color: '#f88e16', 15 | }, 16 | }, 17 | }); 18 | 19 | const CustomEditIcon = ({ handleClickEditModule }) => { 20 | const classes = useStyles(); 21 | return ( 22 |
23 |
24 | 25 |
26 |
27 | ); 28 | }; 29 | 30 | export default CustomEditIcon; 31 | -------------------------------------------------------------------------------- /app/src/components/StateManagement/CreateTab/CreateContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import Grid from '@mui/material/Grid'; 4 | import StatePropsPanel from './components/StatePropsPanel'; 5 | 6 | /** 7 | * `CreateContainer` is a React functional component that renders a container for managing the creation or configuration 8 | * of component properties and state within an application. It is primarily used as a layout container for the `StatePropsPanel`. 9 | * 10 | * @param {Object} props - Properties passed to the component. 11 | * @param {boolean} props.isThemeLight - Indicates if the application's theme is light. This is used to adjust the visual styling of the contents. 12 | * @param {Array} props.data - The data passed to the `StatePropsPanel`, typically representing the application's components or elements. 13 | * @returns {JSX.Element} A `Grid` container from Material-UI that includes a `StatePropsPanel` component, configured for managing and displaying component properties and state based on the passed `isThemeLight` and `data` props. 14 | * 15 | * This component uses Material-UI's `Grid` to create a flexible column layout that adjusts based on its content. It serves as a wrapper 16 | * for the `StatePropsPanel` to provide consistent spacing and alignment. 17 | * 18 | * Example Usage: 19 | * ```jsx 20 | * 24 | * ``` 25 | */ 26 | const CreateContainer = ({ isThemeLight, data }) => { 27 | return ( 28 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default CreateContainer; 40 | -------------------------------------------------------------------------------- /app/src/components/StateManagement/DisplayTab/useResizeObserver.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import ResizeObserver from 'resize-observer-polyfill'; 3 | 4 | /** 5 | * A custom React hook that allows you to monitor changes in the dimensions of an HTML element. 6 | * It uses the `ResizeObserver` API to observe the specified element and updates the state 7 | * with the new dimensions whenever a change is detected. 8 | * 9 | * This hook is useful for responsive components that need to adjust their layout or functionality 10 | * based on their size, and it abstracts the setup and teardown logic associated with `ResizeObserver` 11 | * to make it easy to use within a React component. 12 | * 13 | * @param {React.RefObject} ref - A React ref attached to the element whose dimensions you want to monitor. 14 | * @returns {DOMRectReadOnly | null} The latest dimensions of the observed element, or `null` if the dimensions are not available. 15 | * 16 | * The returned object includes properties such as `width`, `height`, `top`, `right`, `bottom`, and `left`, 17 | * providing a comprehensive overview of the element's size and position relative to its containing block. 18 | * 19 | * Usage: 20 | * 1. Attach a ref to your component using `React.useRef()`. 21 | * 2. Pass this ref to the `useResizeObserver` hook. 22 | * 3. Use the returned dimensions to adjust your component's styling or behavior. 23 | * 24 | * Example: 25 | * ```jsx 26 | * const MyResponsiveComponent = () => { 27 | * const myRef = useRef(); 28 | * const dimensions = useResizeObserver(myRef); 29 | * 30 | * return
31 | * {dimensions && {`Width: ${dimensions.width}, Height: ${dimensions.height}`}} 32 | *
; 33 | * }; 34 | * ``` 35 | * 36 | * Note: 37 | * Make sure to polyfill `ResizeObserver` in environments that do not support it, as this hook 38 | * relies on that API to function properly. 39 | */ 40 | const useResizeObserver = ( 41 | ref: React.RefObject 42 | ): DOMRectReadOnly | null => { 43 | const [dimensions, setDimensions] = useState(null); 44 | useEffect(() => { 45 | // the element being observed (div with green border) 46 | const observeTarget = ref.current; 47 | const resizeObserver = new ResizeObserver((entries) => { 48 | entries.forEach((entry) => { 49 | // contentRect is an object containing the dimensions of the observed element 50 | setDimensions(entry.contentRect); 51 | }); 52 | }); 53 | resizeObserver.observe(observeTarget); 54 | return () => { 55 | resizeObserver.unobserve(observeTarget); 56 | }; 57 | }, [ref]); 58 | return dimensions; 59 | }; 60 | 61 | export default useResizeObserver; 62 | -------------------------------------------------------------------------------- /app/src/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | interface TooltipProps { 4 | label: string; 5 | isCollabTab: boolean; 6 | children: React.ReactNode; 7 | } 8 | 9 | const Tooltip: React.FC = ({ label, isCollabTab, children }) => { 10 | const [showTooltip, setShowTooltip] = useState(false); 11 | 12 | const handleMouseEnter = () => { 13 | setShowTooltip(true); 14 | }; 15 | 16 | const handleMouseLeave = () => { 17 | setShowTooltip(false); 18 | }; 19 | 20 | return ( 21 |
22 | {children} 23 | {showTooltip && ( 24 |
41 | {label} 42 |
56 |
57 | )} 58 |
59 | ); 60 | }; 61 | 62 | export default Tooltip; 63 | -------------------------------------------------------------------------------- /app/src/components/bottom/CreationPanel.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import HTMLPanel from '../left/HTMLPanel'; 4 | import { useSelector } from 'react-redux'; 5 | import { RootState } from '../../redux/store'; 6 | 7 | /** 8 | * A container component that organizes the user interface elements for creating various aspects of the application. 9 | * It includes the ComponentPanel and HTMLPanel, each responsible for different parts of the application's creation process. 10 | * The appearance of these panels is controlled by the Redux store's style state, allowing for dynamic styling. 11 | * 12 | * @param {Object} props - Component props. 13 | * @param {boolean} props.isThemeLight - Indicates if the light theme is active, used to adjust the styling of child components. 14 | * 15 | * Redux State Dependencies: 16 | * - `styleSlice`: The style settings from the Redux store which determine the CSS styles applied to the creation panel. 17 | * 18 | * @returns {JSX.Element} A React element that renders the creation panel containing the component and HTML panels, 19 | * configured according to the theme specified in the props. 20 | */ 21 | const CreationPanel = (props): JSX.Element => { 22 | const style = useSelector((store: RootState) => store.styleSlice); 23 | return ( 24 |
25 | {/* NOTE: This component has been moved to ModulePanel.tsx 26 | */} 27 | 28 |
29 | ); 30 | }; 31 | 32 | export default CreationPanel; 33 | -------------------------------------------------------------------------------- /app/src/components/bottom/UseStateModal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useState, useRef } from 'react'; 3 | import Modal from '@mui/material/Modal'; 4 | import TableStateProps from '../StateManagement/CreateTab/components/TableStateProps'; 5 | 6 | /** 7 | * A React component that provides a modal interface for linking state properties to component attributes. 8 | * It displays a list of available state properties in a table format and allows the user to select one to associate 9 | * with a specified attribute of a component. The modal can be opened by clicking a button and closed using a dedicated close button. 10 | * 11 | * @param {Object} props - Component props. 12 | * @param {Function} props.updateAttributeWithState - Function to update the component attribute with the selected state property. 13 | * @param {string} props.attributeToChange - The name of the attribute in the component to which the state is to be linked. 14 | * 15 | * State: 16 | * - `open`: Boolean to control the visibility of the modal. 17 | * - `stateKey`: Stores the key part of the state property being linked. 18 | * - `statePropsId`: Stores the ID of the state property being linked. 19 | * - `componentProviderId`: Stores the ID of the component that provides the state. Set initially to 1 and can be changed as needed. 20 | * 21 | * @returns {JSX.Element} A React element that renders a button to open the modal and the modal itself containing the TableStateProps component. 22 | */ 23 | function UseStateModal({ 24 | updateAttributeWithState, 25 | attributeToChange, 26 | }): JSX.Element { 27 | const [open, setOpen] = useState(false); 28 | const [stateKey, setStateKey] = useState(''); 29 | const [statePropsId, setStatePropsId] = useState(-1); 30 | const [componentProviderId, setComponentProviderId] = useState(1); 31 | const container = useRef(null); 32 | // table to choose state from 33 | const body = ( 34 |
35 |
36 | Choose State 37 | 47 |
48 |
49 |
50 | { 54 | updateAttributeWithState( 55 | attributeToChange, 56 | componentProviderId, 57 | statePropsId > 0 ? statePropsId : table.row.id, 58 | table.row, 59 | stateKey + table.row.key, 60 | ); 61 | setStateKey(''); 62 | setStatePropsId(-1); 63 | setOpen(false); 64 | }} 65 | isThemeLight={true} 66 | /> 67 |
68 |
69 |
70 | ); 71 | 72 | return ( 73 |
74 | 77 | 78 | {body} 79 | 80 |
81 | ); 82 | } 83 | 84 | export default UseStateModal; 85 | -------------------------------------------------------------------------------- /app/src/components/left/ComponentsContainer.tsx: -------------------------------------------------------------------------------- 1 | import Grid from '@mui/material/Grid'; 2 | import React from 'react'; 3 | import makeStyles from '@mui/styles/makeStyles'; 4 | import { useSelector } from 'react-redux'; 5 | import { RootState } from '../../redux/store'; 6 | import ComponentPanelItem from '../right/ComponentPanelItem'; 7 | 8 | /** 9 | * Displays a panel of reusable components that are not root components. 10 | * This panel lists components that can be reused across different parts of the application. 11 | * Each component item in the list is capable of being focused, which is visually indicated. 12 | * @params 13 | * @returns {JSX.Element} A container that holds a list of `ComponentPanelItem` elements, 14 | * each representing a non-root component from the application state. These components are not 15 | * designated as top-level pages or structures but are reusable UI elements. 16 | */ 17 | const ComponentsContainer = ({ handleClickEditModule }): JSX.Element => { 18 | const classes = useStyles(); 19 | const state = useSelector((store: RootState) => store.appState); 20 | 21 | const isFocus = (targetId: number) => (state.canvasFocus.componentId === targetId); 22 | return ( 23 |
24 |
25 |
26 | {/*

Reusable Components

*/} 27 | 28 | {state.components 29 | .filter((comp) => !state.rootComponents.includes(comp.id)) 30 | .map((comp) => ( 31 | 40 | ))} 41 | 42 |
43 |
44 |
45 | ); 46 | }; 47 | 48 | const useStyles = makeStyles({ 49 | panelWrapper: { 50 | display: 'flex', 51 | flexDirection: 'column', 52 | alignItems: 'center', 53 | flexGrow: 1, 54 | overflow: 'auto', 55 | }, 56 | panelWrapperList: { 57 | minHeight: 'auto', 58 | }, 59 | lightThemeFontColor: { 60 | color: '#fff', 61 | }, 62 | darkThemeFontColor: { 63 | color: '#fff', 64 | }, 65 | }); 66 | 67 | export default ComponentsContainer; 68 | -------------------------------------------------------------------------------- /app/src/components/left/ContentArea.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import Box from '@mui/material/Box'; 3 | import ModulePanel from './ModulePanel'; 4 | import React from 'react'; 5 | import RoomsContainer from './RoomsContainer'; 6 | import Settings from './Settings'; 7 | import CreateContainer from './CreateContainer'; 8 | 9 | interface ContentAreaProps { 10 | activeTab: number | null; 11 | isVisible: boolean; 12 | } 13 | 14 | /** 15 | * TabPanel is a functional component that conditionally renders its children 16 | * based on whether its index matches the currently active tab. This allows for 17 | * efficient rendering of tabbed content only when the tab is active, improving 18 | * performance and user experience by not rendering hidden tab content. 19 | * 20 | * @param {Object} props - Component props 21 | * @param {React.ReactNode} props.children - The content to be rendered when this tab is active. 22 | * @param {number | null} props.activeTab - The index of the currently active tab. 23 | * @param {number} props.index - The index of this specific tab panel. 24 | * @returns {JSX.Element} A Box component from MUI that renders children if it's the active tab. 25 | */ 26 | const TabPanel: React.FC<{ 27 | children: React.ReactNode; 28 | activeTab: number | null; 29 | index: number; 30 | }> = ({ children, activeTab, index }) => { 31 | return ( 32 | 33 | ); 34 | }; 35 | 36 | const panels = [, , ]; 37 | 38 | /** 39 | * ContentArea component that renders different panels based on the active tab. 40 | * This component acts as a dynamic container for switching between different sections 41 | * of the application like elements, components, rooms, profile page, and settings 42 | * depending on user interaction with the tab interface. 43 | * 44 | * The visibility of the entire container can be toggled, which is controlled by the `isVisible` prop. 45 | * 46 | * @param {Object} props - Component props 47 | * @param {number | null} props.activeTab - The index of the currently active tab; controls which tab content is visible. 48 | * @param {boolean} props.isVisible - Flag indicating whether the content area should be displayed. 49 | * @returns {JSX.Element} The content area component with conditional rendering based on the active tab. 50 | */ 51 | const ContentArea: React.FC = ({ activeTab, isVisible }) => { 52 | return ( 53 |
57 |
58 | {panels.map((panel, idx) => ( 59 | 60 | {panel} 61 | 62 | ))} 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default ContentArea; 69 | -------------------------------------------------------------------------------- /app/src/components/left/CreateContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Grid from '@mui/material/Grid'; 3 | import makeStyles from '@mui/styles/makeStyles'; 4 | import MUIItem from './MUIItem'; 5 | import HTMLItem from './HTMLItem'; 6 | import { useSelector, useDispatch } from 'react-redux'; 7 | import { RootState } from '../../redux/store'; 8 | import CreatePanel from './CreatePanel'; 9 | import Box from '@mui/material/Box'; 10 | import { useCallback, useEffect } from 'react'; 11 | import ComponentDrag from './ComponentDrag'; 12 | 13 | import { 14 | deleteChild, 15 | deleteElement 16 | } from '../../redux/reducers/slice/appStateSlice'; 17 | import { emitEvent } from '../../helperFunctions/socket'; 18 | 19 | /** 20 | * 21 | * A functional component that renders the CreatePanel within a simple container. 22 | * The CreateContainer currently acts as a wrapper for the CreatePanel. 23 | * /** 24 | * CreateContainer serves as the left-hand side container in the application, primarily responsible 25 | * for hosting user interaction elements such as `DragDropPanel` and `MUIDragDropPanel`. This component handles user inputs 26 | * related to component selection and arrangement via drag-and-drop interfaces. 27 | * 28 | * It also includes key bindings to enhance user interaction, such as deleting a child component 29 | * with the 'Backspace' key when focus is not on text input fields. 30 | * 31 | * Props: 32 | * @param {Object} props - The properties passed down to this component. 33 | * @param {boolean} props.isThemeLight - Indicates if the current theme is light, affecting the visual styling. 34 | * 35 | * @returns {JSX.Element} The container element that holds UI sections for component interaction. 36 | * 37 | * 38 | * @returns {JSX.Element} 39 | * @example 40 | * return ( 41 | * 42 | * ) 43 | */ 44 | 45 | const CreateContainer = (props): JSX.Element => { 46 | const contextParam = useSelector((store: RootState) => store.contextSlice); 47 | const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); 48 | 49 | const dispatch = useDispatch(); 50 | 51 | const handleDelete = () => { 52 | dispatch(deleteChild({ id: {}, contextParam: contextParam })); 53 | if (roomCode) { 54 | emitEvent('deleteChildAction', roomCode, { 55 | id: {}, 56 | contextParam: contextParam 57 | }); 58 | } 59 | }; 60 | 61 | const keyBindedFunc = useCallback((e) => { 62 | if ( 63 | e.key === 'Backspace' && 64 | e.target.tagName !== 'TEXTAREA' && 65 | e.target.tagName !== 'INPUT' 66 | ) 67 | handleDelete(); 68 | }, []); 69 | 70 | // useEffect(() => { 71 | // document.addEventListener('keydown', keyBindedFunc); 72 | // return () => { 73 | // document.removeEventListener('keydown', keyBindedFunc); 74 | // }; 75 | // }, []); 76 | 77 | return ( 78 | 89 | 90 | 91 | ); 92 | }; 93 | 94 | export default CreateContainer; 95 | -------------------------------------------------------------------------------- /app/src/components/left/HeaderButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Popover, { PopoverProps } from '@mui/material/Popover'; 4 | import Typography from '@mui/material/Typography'; 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import Button from '@mui/material/Button'; 7 | import HelpIcon from '@mui/icons-material/Help'; 8 | 9 | const HeaderButton = (props) => { 10 | const [anchorEl, setAnchorEl] = 11 | React.useState(null); 12 | const classes = useStyles(); 13 | const handleClickPopover = (event: React.MouseEvent) => { 14 | setAnchorEl(anchorEl ? null : event.currentTarget); 15 | }; 16 | 17 | const handleClose = () => { 18 | setAnchorEl(null); 19 | }; 20 | const open = Boolean(anchorEl); 21 | const id = open ? open : undefined; 22 | 23 | return ( 24 |
25 | 55 |
56 | ); 57 | }; 58 | 59 | const useStyles = makeStyles({ 60 | headerButton: { 61 | alignSelf: 'center' 62 | }, 63 | popover: { 64 | backgroundColor: '#ffdbbb', 65 | display: 'flex', 66 | color: 'black', 67 | fontSize: '0.8rem', 68 | padding: '8px', 69 | width: '300px' 70 | }, 71 | popoverIcon: { 72 | paddingRight: '10px', 73 | paddingLeft: '8px', 74 | paddingTop: '10px' 75 | } 76 | }); 77 | export default HeaderButton; 78 | -------------------------------------------------------------------------------- /app/src/components/left/TabWithTooltip.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tab } from '@mui/material'; 3 | import { AddBox, Folder, Groups2 } from '@mui/icons-material'; 4 | import Tooltip from '../Tooltip'; 5 | 6 | interface TabWithTooltipProps { 7 | label: string; 8 | value: number; 9 | activeTab: number; 10 | handleTabChange: (event: React.ChangeEvent<{}>, value: number) => void; 11 | } 12 | 13 | const TabWithTooltip: React.FC = ({ 14 | label, 15 | value, 16 | activeTab, 17 | handleTabChange 18 | }) => { 19 | let iconType; 20 | if (value === 0) { 21 | iconType = ; 22 | } else if (value === 1) { 23 | iconType = ; 24 | } else if (value === 2) { 25 | iconType = ; 26 | } 27 | 28 | const isCollabTab = label === 'Collab'; 29 | 30 | return ( 31 | 32 | handleTabChange(event, value)} 46 | /> 47 | 48 | ); 49 | }; 50 | 51 | export default TabWithTooltip; 52 | -------------------------------------------------------------------------------- /app/src/components/main/AddRoute.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { AddRoutes } from '../../interfaces/Interfaces'; 5 | import { addChild } from '../../redux/reducers/slice/appStateSlice'; 6 | import { RootState } from '../../redux/store'; 7 | import { emitEvent } from '../../helperFunctions/socket'; 8 | 9 | /** 10 | * Provides a button that when clicked, dispatches an action to add a new route as a child element in the application's state. 11 | * This component is typically used in scenarios where dynamic route addition is needed within a React application managing routes state via Redux. 12 | * 13 | * @param {Object} props - Component props. 14 | * @param {number} props.id - The ID of the parent element to which the new route should be added. 15 | * @returns {JSX.Element} A React element representing a button that adds a route. 16 | */ 17 | function AddRoute({ id }: AddRoutes): JSX.Element { 18 | const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); 19 | const dispatch = useDispatch(); 20 | const contextParam = useSelector((store: RootState) => store.contextSlice); 21 | const handleClick = (id: number): void => { 22 | dispatch( 23 | addChild({ 24 | type: 'HTML Element', 25 | typeId: -1, 26 | childId: id, // this is the id of the parent to attach it to 27 | contextParam: contextParam, 28 | }), 29 | ); 30 | if (roomCode) { 31 | emitEvent('addChildAction', roomCode, { 32 | type: 'HTML Element', 33 | typeId: -1, 34 | childId: id, 35 | contextParam: contextParam, 36 | }); 37 | 38 | console.log('emit addChildAction event is triggered in AddRoute!'); 39 | } 40 | }; 41 | 42 | return ( 43 |
44 | 47 |
48 | ); 49 | } 50 | 51 | export default AddRoute; 52 | -------------------------------------------------------------------------------- /app/src/components/main/DeleteButton.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { Clear } from '@mui/icons-material'; 5 | import { DeleteButtons } from '../../interfaces/Interfaces'; 6 | import { deleteChild } from '../../redux/reducers/slice/appStateSlice'; 7 | import { RootState } from '../../redux/store'; 8 | import { emitEvent } from '../../helperFunctions/socket'; 9 | 10 | /** 11 | * `DeleteButton` is a React component that renders a button for deleting a specific HTML element or component within the application. 12 | * It uses Redux actions to manage the state and potentially emits events over sockets for collaborative environments. 13 | * 14 | * @param {Object} props - The component props. 15 | * @param {number} props.id - The unique identifier of the component or HTML element to be deleted. 16 | * @param {string} props.name - The name of the component or HTML element, used for identification in the UI or logs. 17 | * @param {Function} [props.onClickHandler] - An optional function to handle additional or preparatory actions on button click before the deletion occurs. 18 | * 19 | * @returns {JSX.Element} A button element that triggers the deletion of the specified component or HTML element when clicked. 20 | */ 21 | function DeleteButton({ 22 | id, 23 | name, 24 | onClickHandler, 25 | }: DeleteButtons): JSX.Element { 26 | const contextParam = useSelector((store: RootState) => store.contextSlice); 27 | 28 | const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); 29 | 30 | const dispatch = useDispatch(); 31 | 32 | const deleteHTMLtype = (id: number) => { 33 | dispatch(deleteChild({ id: id, contextParam: contextParam })); 34 | if (roomCode) { 35 | emitEvent('deleteChildAction', roomCode, { 36 | id, 37 | contextParam, 38 | }); 39 | } 40 | }; 41 | 42 | return ( 43 |
44 | 57 |
58 | ); 59 | } 60 | 61 | export default DeleteButton; 62 | -------------------------------------------------------------------------------- /app/src/components/main/IndirectChild.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { combineStyles } from '../../helperFunctions/combineStyles'; 5 | import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; 6 | import { Component } from '../../interfaces/Interfaces'; 7 | import DeleteButton from './DeleteButton'; 8 | import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; 9 | import { RootState } from '../../redux/store'; 10 | 11 | /** 12 | * A component representing an indirect child element that may contain a navigation link to another component. 13 | * It can display either a placeholder or a direct link to another component based on its configuration. 14 | * 15 | * @param {Object} props - The properties passed to the component. 16 | * @param {Object} props.style - Custom styles applied to the component instance. 17 | * @param {React.ReactNode} props.children - Nested child components or elements. 18 | * @param {string} props.placeHolder - Placeholder text displayed when no link is active. 19 | * @param {number} props.linkId - Identifier for the linked component if available. 20 | * @param {number} props.childId - Unique identifier for the child component instance. 21 | * @param {string} props.name - Display name of the component, used for identification within the UI. 22 | * @returns {JSX.Element} A styled component that optionally acts as a navigation link to another component. 23 | */ 24 | function IndirectChild({ 25 | style, 26 | children, 27 | placeHolder, 28 | linkId, 29 | childId, 30 | name, 31 | }) { 32 | const state = useSelector((store: RootState) => store.appState); 33 | const dispatch = useDispatch(); 34 | let combinedStyle = combineStyles(globalDefaultStyle, style); 35 | // when a user clicks a link, the focus should change to that component 36 | function onClickHandlerRoute(event) { 37 | event.stopPropagation(); 38 | dispatch(changeFocus({ componentId: linkId, childId: null })); 39 | } 40 | let linkName: string; 41 | // if there's a link in this component, then include a link 42 | if (linkId) { 43 | linkName = state.components.find( 44 | (comp: Component) => comp.id === linkId, 45 | ).name; 46 | combinedStyle = combineStyles(combinedStyle, { color: 'blue' }); 47 | } 48 | 49 | return ( 50 |
51 | {` ( ${childId} )`} 52 | 53 | 58 | 59 | {linkId ? ( 60 |
{linkName}
61 | ) : ( 62 | placeHolder 63 | )} 64 | {children} 65 |
66 | ); 67 | } 68 | 69 | export default IndirectChild; 70 | -------------------------------------------------------------------------------- /app/src/components/marketplace/MarketplaceCardContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { Container, Grid } from '@mui/material'; 3 | 4 | import React from 'react'; 5 | import MarketplaceCard from './MarketplaceCard'; 6 | 7 | /** 8 | * `MarketplaceCardContainer` is a React component that renders a container for `MarketplaceCard` components. 9 | * It organizes project cards into a responsive grid layout. Each project is represented by a `MarketplaceCard` which 10 | * displays the project's details and interactions such as cloning and opening. 11 | * 12 | * @param {Object} props - The properties passed to the component. 13 | * @param {Array} props.displayProjects - An array of project objects to display. Each object contains details 14 | * necessary for the `MarketplaceCard` component to function properly. 15 | * 16 | * @returns {JSX.Element} - A container that organizes project cards into a grid layout for display in the UI. 17 | */ 18 | const MarketplaceCardContainer = ({ displayProjects }): JSX.Element => { 19 | return ( 20 | <> 21 | 22 | 28 | {displayProjects.map((proj, i) => ( 29 | 30 | 31 | 32 | ))} 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default MarketplaceCardContainer; 40 | -------------------------------------------------------------------------------- /app/src/components/right/createModal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import SimpleModal from './SimpleModal'; 4 | 5 | type Props = { 6 | open: boolean; 7 | message: string; 8 | primBtnLabel: any; 9 | secBtnLabel: any; 10 | primBtnAction: any; 11 | secBtnAction: any; 12 | children: any; 13 | closeModal: any; 14 | }; 15 | 16 | /** 17 | * Creates a customizable modal dialog using the `SimpleModal` component. 18 | * 19 | * @param {Object} props - The properties for configuring the modal. 20 | * @param {boolean} [props.open=true] - Determines if the modal is open. 21 | * @param {string} props.message - The message or content displayed in the modal. 22 | * @param {any} props.primBtnLabel - Label for the primary button. 23 | * @param {any} [props.secBtnLabel=null] - Label for the secondary button (optional). 24 | * @param {any} props.primBtnAction - Handler for the primary button click event. 25 | * @param {any} [props.secBtnAction=null] - Handler for the secondary button click event (optional). 26 | * @param {any} [props.children=null] - Child components or elements to be displayed within the modal. 27 | * @param {Function} props.closeModal - Function to call when closing the modal. 28 | * 29 | * @returns {JSX.Element} A `SimpleModal` component configured with the specified properties. 30 | */ 31 | const createModal = ({ 32 | open = true, 33 | message, 34 | primBtnLabel, 35 | secBtnLabel = null, 36 | primBtnAction, 37 | secBtnAction = null, 38 | children = null, 39 | closeModal, 40 | }: Props): JSX.Element => ( 41 | 50 | {children} 51 | 52 | ); 53 | 54 | export default createModal; 55 | -------------------------------------------------------------------------------- /app/src/components/top/PublishModal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import Dialog from '@mui/material/Dialog'; 4 | import DialogTitle from '@mui/material/DialogTitle'; 5 | import DialogContent from '@mui/material/DialogContent'; 6 | import DialogActions from '@mui/material/DialogActions'; 7 | import Button from '@mui/material/Button'; 8 | import TextField from '@mui/material/TextField'; 9 | 10 | /** 11 | * `PublishModal` is a React component that renders a dialog for entering a project name 12 | * and publishing the project. This modal includes a text field for the project name and 13 | * buttons to either cancel the action or save and publish the project. 14 | * 15 | * The modal is designed to provide feedback on the validity of the project name through 16 | * text field validation, displaying an error message if the project name is considered invalid. 17 | * 18 | * @param {Object} props - The properties passed to the component. 19 | * @param {boolean} props.open - Boolean to control the visibility of the modal. 20 | * @param {Function} props.onClose - Function to be called when the modal is requested to be closed. 21 | * @param {Function} props.onSave - Function to be called when the publish button is clicked. 22 | * @param {string} props.projectName - Current value of the project name input field. 23 | * @param {Function} props.onChange - Function to handle changes to the project name input field. 24 | * @param {boolean} props.invalidProjectName - Boolean indicating whether the current project name is invalid. 25 | * @param {string} props.invalidProjectNameMessage - Message to display when the project name is invalid. 26 | * @returns {JSX.Element} A modal dialog with a form for publishing a project that includes a text input for the project name and action buttons. 27 | * 28 | * The `PublishModal` is used in scenarios where a user needs to provide a project name before publishing it to ensure 29 | * that the project name meets certain criteria. It is part of the project's workflow where publishing is a key step. 30 | */ 31 | const PublishModal = ({ 32 | open, 33 | onClose, 34 | onSave, 35 | projectName, 36 | onChange, 37 | invalidProjectName, 38 | invalidProjectNameMessage 39 | }): JSX.Element => ( 40 | 46 | 47 | Publish Project 48 | 49 | 50 | 64 | 65 | 66 | 69 | 72 | 73 | 74 | ); 75 | 76 | export default PublishModal; 77 | -------------------------------------------------------------------------------- /app/src/constants/ErrorMessages.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | deleteIndexTitle: 'You cannot delete index.', 3 | deleteIndexMessage: 'You must have at least one page.', 4 | deleteComponentTitle: 'You cannot delete this component.', 5 | deleteComponentMessage: 6 | 'Reusable components inside of a page must be deleted from the page.', 7 | deleteLinkedPageTitle: 'Delete linked page error.', 8 | deleteLinkedPageMessage: 9 | 'Please delete the links to this page before deleting the page.', 10 | }; 11 | -------------------------------------------------------------------------------- /app/src/constants/ItemTypes.ts: -------------------------------------------------------------------------------- 1 | import { DragItemType } from '../interfaces/Interfaces'; 2 | 3 | const ItemTypes: DragItemType = { 4 | INSTANCE: 'instance', 5 | }; 6 | export { ItemTypes }; 7 | -------------------------------------------------------------------------------- /app/src/constants/Styling.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | neonGreen: '#189bd7', 3 | darkBlue: '#0671e3', 4 | darkGray: '#252526', 5 | tutorialGray: '#f2f0f0' 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/containers/AppContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { 4 | StyledEngineProvider, 5 | Theme, 6 | ThemeProvider 7 | } from '@mui/material/styles'; 8 | import { theme1, theme2 } from '../public/styles/theme'; 9 | import { useDispatch, useSelector } from 'react-redux'; 10 | 11 | import LeftContainer from './LeftContainer'; 12 | import MainContainer from './MainContainer'; 13 | 14 | import MarketplaceContainer from './MarketplaceContainer'; 15 | import NavBar from '../components/top/NavBar'; 16 | import { RootState } from '../redux/store'; 17 | 18 | import { setStyle } from '../redux/reducers/slice/styleSlice'; 19 | import { useHistory } from 'react-router-dom'; 20 | 21 | declare module '@mui/styles/defaultTheme' { 22 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 23 | interface DefaultTheme extends Theme {} 24 | } 25 | 26 | declare module '@mui/styles/defaultTheme' { 27 | interface DefaultTheme extends Theme {} 28 | } 29 | 30 | // setting light and dark themes (navbar and background); linked to theme.ts 31 | const lightTheme = theme1; 32 | const darkTheme = theme2; // dark mode color in theme.ts not reached 33 | 34 | /** 35 | * The `AppContainer` component serves as the primary layout container for the application. 36 | * It uses the `useHistory` hook from `react-router-dom` to determine the current URL path and conditionally 37 | * renders different child components based on whether the path is '/marketplace' or another. It integrates 38 | * a theme provider to apply a light theme to all nested components and controls the overall layout of 39 | * the application including the navigation bar and main content areas. 40 | * 41 | * The component selectively renders `MarketplaceContainer` for the marketplace route, otherwise it renders 42 | * both `LeftContainer` and `MainContainer` for general application navigation and display. This allows for 43 | * flexible navigation and layout management based on user interaction and route changes. 44 | * 45 | * @returns {JSX.Element} A React element representing the structured layout of the application, including 46 | * the navigation bar and the main or marketplace content areas, wrapped within theme 47 | * providers to ensure consistent styling. 48 | * 49 | * The usage of `ThemeProvider` from Material-UI ensures that the theme is consistently applied across all 50 | * child components. The `StyledEngineProvider` with `injectFirst` helps in overriding the default Material-UI 51 | * styles with custom styles defined in the application. 52 | */ 53 | const AppContainer: React.FC = () => { 54 | // useHistory hook to grab the url, if it is /marketplace then selectively render MarketplaceContainer 55 | const urlAdd = useHistory(); 56 | const isMarketplace = urlAdd.location.pathname === '/marketplace'; 57 | 58 | return ( 59 | 60 | 61 |
62 | 63 |
64 |
65 | {isMarketplace ? ( 66 | 67 | ) : ( 68 | <> 69 | 70 | 71 | 72 | )} 73 |
74 |
75 |
76 | ); 77 | }; 78 | export default AppContainer; 79 | -------------------------------------------------------------------------------- /app/src/containers/LeftContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { useState, useEffect } from 'react'; 3 | import ContentArea from '../components/left/ContentArea'; 4 | import Sidebar from '../components/left/Sidebar'; 5 | 6 | /** 7 | * The `App` component serves as the main container for a simple application that includes a sidebar 8 | * and a content area. The sidebar allows for tab selection and toggling the visibility of the content area. 9 | * The content area displays content based on the selected tab and its visibility state. 10 | * 11 | * State management within the component includes: 12 | * - `activeTab`: Tracks the currently active tab in the sidebar. 13 | * - `isVisible`: Controls the visibility of the content area. 14 | * 15 | * The component uses the `useState` hook to manage these states and provides methods to update them, 16 | * which are passed down to child components (`Sidebar` and `ContentArea`) as props. 17 | * 18 | * @returns {JSX.Element} The main layout of the application, including a sidebar and a content area, 19 | * structured in a flexible display container. 20 | * 21 | * This structure facilitates a user interface where interactions in the sidebar affect the content displayed, 22 | * making it dynamic based on user actions. This pattern is typical for applications requiring a navigation panel 23 | * alongside content that updates based on user interaction. 24 | */ 25 | const App = () => { 26 | const [activeTab, setActiveTab] = useState(0); 27 | const [isVisible, setIsVisible] = useState(true); 28 | 29 | const toggleVisibility = (state: boolean) => { 30 | setIsVisible(state); 31 | }; 32 | 33 | return ( 34 |
35 | 41 | 42 |
43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /app/src/helperFunctions/changePositionValidation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { State, ChildElement } from '../interfaces/Interfaces'; 3 | 4 | /** 5 | * Validates whether the new parent is a valid target destination for moving an element on the canvas. 6 | * This function checks if the target destination is a nested component within its own children array, preventing nesting inside its children. 7 | * @param {State} state - The state object containing canvas components and focus information. 8 | * @param {number} currentChildId - The ID of the child element being moved. 9 | * @param {number} toTargetParentId - The ID of the potential new parent element. 10 | * @returns {boolean} Returns true if the new parent is a valid target, otherwise false. 11 | */ 12 | const validateNewParent = ( 13 | state: State, 14 | currentChildId: number, 15 | toTargetParentId: number, 16 | ): boolean => { 17 | const focusIndex = state.canvasFocus.componentId - 1; 18 | const childrenArray = state.components[focusIndex].children; 19 | // Checks to see if a Parent is trying to nest inside one of its children 20 | const selfNestingCheck = ( 21 | array: ChildElement[], 22 | nestedChild = false, 23 | nestedParent = false, 24 | ): boolean => { 25 | for (const element of array) { 26 | if ( 27 | element.childId === toTargetParentId && 28 | nestedChild === true && 29 | element.typeId > 1000 // check if not a separator (1000) or previously dragged component (> 1000) 30 | ) 31 | return (nestedParent = true); 32 | else if ( 33 | element.childId === currentChildId && 34 | element.children.length > 0 && 35 | nestedChild === false 36 | ) 37 | nestedParent = selfNestingCheck( 38 | element.children, 39 | (nestedChild = true), 40 | nestedParent, 41 | ); 42 | else if (element.children.length > 0 && nestedChild === false) 43 | nestedParent = selfNestingCheck( 44 | element.children, 45 | nestedChild, 46 | nestedParent, 47 | ); 48 | } 49 | return nestedParent; 50 | }; 51 | const parentNestingIntoChild = selfNestingCheck(childrenArray); 52 | if (parentNestingIntoChild === true) return false; 53 | return true; 54 | }; 55 | export default validateNewParent; 56 | -------------------------------------------------------------------------------- /app/src/helperFunctions/cloneDeep.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deeply clones an object or an array, including nested objects and arrays. 3 | * @param {object | any[]} value - The object or array to be cloned. 4 | * @returns {object | any[]} Returns a deep clone of the input value. 5 | */ 6 | function cloneDeep( 7 | value: { [key: string]: any } | any[], 8 | ): { [key: string]: any } | any { 9 | if (Array.isArray(value)) { 10 | const result: any[] = []; 11 | value.forEach((el) => { 12 | if (typeof el === 'object') { 13 | result.push(cloneDeep(el)); 14 | } else { 15 | result.push(el); 16 | } 17 | }); 18 | return result; 19 | } 20 | if (typeof value === 'object' && value !== null) { 21 | const result: { [key: string]: any } = {}; 22 | Object.keys(value).forEach((key) => { 23 | if (typeof value[key] === 'object') { 24 | result[key] = cloneDeep(value[key]); 25 | } else { 26 | result[key] = value[key]; 27 | } 28 | }); 29 | return result; 30 | } 31 | return value; 32 | } 33 | export default cloneDeep; 34 | -------------------------------------------------------------------------------- /app/src/helperFunctions/combineStyles.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /** 3 | * Combines default styles with priority styles, prioritizing the properties from the priority styles. 4 | * @param {Object} defaultStyle - The default styles. 5 | * @param {Object} priorityStyle - The priority styles. 6 | * @returns {Object} Returns the combined styles object. 7 | */ 8 | export const combineStyles = ( 9 | defaultStyle: object, 10 | priorityStyle: object, 11 | ): object => { 12 | // initialize output object that's a copy of priority styles 13 | const combinedStyle = { ...priorityStyle }; 14 | // iterate through each style in default style 15 | // if property is not in the output object, add it to the output object 16 | for (const i in defaultStyle) { 17 | if (!combinedStyle.hasOwnProperty(i)) { 18 | combinedStyle[i] = defaultStyle[i]; 19 | } 20 | } 21 | return combinedStyle; 22 | }; 23 | -------------------------------------------------------------------------------- /app/src/helperFunctions/componentNestValidation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { ChildElement } from '../interfaces/Interfaces'; 3 | 4 | /** 5 | * Used in both DirectChildHTMLNestable and SeparatorChild to ensure that user-created components do not nest within themselves. 6 | * To allow such nesting would be a certain paradox and locks the application. 7 | * This check is performed after the drag functionality resolves and releases. 8 | * @param {ChildElement[]} children - The array of child elements to check. 9 | * @param {number} nestId - The ID of the component to check against nesting. 10 | * @returns {boolean} Returns true if the component is not nested within itself, otherwise returns false. 11 | */ 12 | const componentNest = (children: ChildElement[], nestId: number) => { 13 | let notNested = true; 14 | for (const element of children) { 15 | if (element.childId === nestId) return false; 16 | if (element.children.length > 0) notNested = componentNest(element.children, nestId); 17 | } 18 | return notNested; 19 | }; 20 | export default componentNest; 21 | -------------------------------------------------------------------------------- /app/src/helperFunctions/cssRefresh.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Removes the old link to CSS and creates a new stylesheet link on demo render. 3 | * This function is not currently being used for the website version. 4 | */ 5 | const cssRefresher = (): void => { 6 | const oldStylesheet = document.getElementById('stylesheet'); 7 | if (oldStylesheet !== null) oldStylesheet.remove(); 8 | const newStylesheet = document.createElement('LINK') as HTMLLinkElement; 9 | newStylesheet.rel = 'stylesheet'; 10 | newStylesheet.type = 'text/css'; 11 | newStylesheet.href = 'fake.css'; 12 | newStylesheet.id = 'stylesheet'; 13 | const renderFocusElement = document.getElementById('renderFocus'); 14 | if (renderFocusElement !== null) { 15 | renderFocusElement.appendChild(newStylesheet); 16 | } 17 | }; 18 | export default cssRefresher; 19 | -------------------------------------------------------------------------------- /app/src/helperFunctions/esbuildService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import * as esbuild from 'esbuild-wasm'; 3 | 4 | let isEsbuildInitialized = false; 5 | 6 | /** 7 | * Singleton pattern for initializing esbuild. 8 | * Ensures that esbuild is only initialized once, regardless of how many times CodePreview is mounted and unmounted. 9 | */ 10 | export const initializeEsbuild = async () => { 11 | if (!isEsbuildInitialized) { 12 | await esbuild.initialize({ 13 | worker: true, 14 | wasmURL: 'https://unpkg.com/esbuild-wasm@0.8.27/esbuild.wasm', 15 | }); 16 | isEsbuildInitialized = true; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /app/src/helperFunctions/randomPassword.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a random password. 3 | * @returns {string} - A randomly generated password string. 4 | */ 5 | const randomPassword = () => { 6 | /** 7 | * Generates a random special character. 8 | * @returns {string} - A random special character. 9 | */ 10 | function getRandomSpecialChar() { 11 | const code = Math.round(Math.random() * (38 - 37) + 37); 12 | return String.fromCharCode(code); 13 | } 14 | /** 15 | * Generates a random digit. 16 | * @returns {string} - A random digit. 17 | */ 18 | function getRandomDigit() { 19 | const code = Math.round(Math.random() * (57 - 48) + 48); 20 | return String.fromCharCode(code); 21 | } 22 | /** 23 | * Generates a random letter. 24 | * @returns {string} - A random letter. 25 | */ 26 | function getRandomLetter() { 27 | const code = Math.round(Math.random() * (90 - 65) + 65); 28 | return String.fromCharCode(code); 29 | } 30 | let password = ''; 31 | 32 | for (let i = 0; i < 6; i += 1) { 33 | password += getRandomLetter() + getRandomDigit() + getRandomSpecialChar(); 34 | } 35 | return password; 36 | }; 37 | export default randomPassword; 38 | -------------------------------------------------------------------------------- /app/src/helperFunctions/socket.ts: -------------------------------------------------------------------------------- 1 | import { io } from 'socket.io-client'; 2 | // import config from '../../../config.js'; 3 | import serverConfig from '../serverConfig'; 4 | 5 | const { API_BASE_URL } = serverConfig; 6 | let socket = null; 7 | 8 | /** 9 | * Initializes the socket connection. 10 | */ 11 | export const initializeSocket = () => { 12 | socket = io(API_BASE_URL, { 13 | transports: ['websocket'], 14 | // will force new socket connection if re-joining to prevent double emits 15 | forceNew: true, 16 | }); 17 | }; 18 | 19 | /** 20 | * Returns the socket instance. 21 | * @returns {Socket | null} The socket instance, or null if not initialized. 22 | */ 23 | export const getSocket = () => socket; 24 | 25 | /** 26 | * Disconnects the socket if it's currently connected. 27 | */ 28 | export const disconnectSocket = () => { 29 | if (socket) { 30 | socket.disconnect(); 31 | } 32 | }; 33 | 34 | /** 35 | * Emits an event through the socket. 36 | * @param {string} event - The name of the event to emit. 37 | * @param {string} roomCode - The room code to emit the event to. 38 | * @param {any} data - The data to send with the event. 39 | */ 40 | export const emitEvent = (event, roomCode, data) => { 41 | if (socket) { 42 | socket.emit(event, roomCode, data); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /app/src/helperFunctions/zipFiles.ts: -------------------------------------------------------------------------------- 1 | import { saveAs } from 'file-saver'; 2 | import JSZip from 'jszip'; 3 | import { State } from '../interfaces/Interfaces'; 4 | 5 | /** 6 | * Creates a zip file containing the project files for export. 7 | * @param {State} state - The current state of the project. 8 | */ 9 | const zipFiles = (state: State): void => { 10 | // initializes zip 11 | const zip = new JSZip(); 12 | const reacTypeApp = zip.folder('ReacTypeApp'); 13 | // creates component folder inside of zip folder 14 | const componentFolder = reacTypeApp.folder('componentfolder'); 15 | // writes a file with default index.html code 16 | reacTypeApp.file( 17 | 'index.html', 18 | ' ReacType App
' 19 | ); 20 | // writes each component as its own file in the component folder 21 | for (const i in state.components) { 22 | componentFolder.file( 23 | `${state.components[i].name}.jsx`, 24 | state.components[i].code 25 | ); 26 | } 27 | // writes our css file 28 | reacTypeApp.file('style.css', state.stylesheet); 29 | // zips the file and saves to local machine 30 | zip.generateAsync({ type: 'blob' }).then(function (content) { 31 | // see FileSaver.js 32 | saveAs(content, 'ReacTypeApp.zip'); 33 | }); 34 | }; 35 | 36 | export default zipFiles; 37 | -------------------------------------------------------------------------------- /app/src/interfaces/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | declare module '*.svg'; 5 | declare module '*.gif'; 6 | -------------------------------------------------------------------------------- /app/src/interfaces/global.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | declare global { 3 | interface Window { 4 | api: any; 5 | } 6 | } 7 | 8 | let { api } = window; 9 | -------------------------------------------------------------------------------- /app/src/plugins/fetch-plugin.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild-wasm'; 2 | import axios from 'axios'; 3 | import localForage from 'localforage'; 4 | 5 | /** 6 | * Creates a file cache instance using localForage. 7 | */ 8 | const fileCache = localForage.createInstance({ 9 | name: 'filecache', 10 | }); 11 | 12 | /** 13 | * A plugin for esbuild to fetch and load code from external sources. 14 | * @param {string} inputCode - The input code to load as the entry file. 15 | * @returns {esbuild.Plugin} - The esbuild plugin for fetching and loading code. 16 | */ 17 | export const fetchPlugin = (inputCode: string): esbuild.Plugin => { 18 | return { 19 | name: 'fetch-plugin', 20 | /** 21 | * Setup function for the fetch plugin. 22 | * @param {esbuild.PluginBuild} build - The esbuild PluginBuild object. 23 | */ 24 | setup(build: esbuild.PluginBuild) { 25 | build.onLoad({ filter: /(^index\.js$)/ }, () => { 26 | return { 27 | loader: 'jsx', 28 | contents: inputCode, 29 | }; 30 | }); 31 | build.onLoad({ filter: /.*/ }, async (args: any) => { 32 | const cachedResult = await fileCache.getItem( 33 | args.path, 34 | ); 35 | if (cachedResult) { 36 | return cachedResult; 37 | } 38 | }); 39 | build.onLoad({ filter: /.css$/ }, async (args: any) => { 40 | const { data, request } = await axios.get(args.path); 41 | const escaped = data 42 | .replace(/\n/g, '') 43 | .replace(/"/g, '\\"') 44 | .replace(/'/g, "\\'"); 45 | const contents = ` 46 | const style = document.createElement('style'); 47 | style.innerText = '${escaped}'; 48 | document.head.appendChild(style) 49 | `; 50 | const result: esbuild.OnLoadResult = { 51 | loader: 'jsx', 52 | contents, 53 | resolveDir: new URL('./', request.responseURL).pathname, 54 | }; 55 | await fileCache.setItem(args.path, result); 56 | return result; 57 | }); 58 | 59 | build.onLoad({ filter: /.*/ }, async (args: any) => { 60 | const cachedResult = await fileCache.getItem( 61 | args.path, 62 | ); 63 | if (cachedResult) { 64 | return cachedResult; 65 | } 66 | const { data, request } = await axios.get(args.path); 67 | 68 | const result: esbuild.OnLoadResult = { 69 | loader: 'jsx', 70 | contents: data, 71 | resolveDir: new URL('./', request.responseURL).pathname, 72 | }; 73 | await fileCache.setItem(args.path, result); 74 | return result; 75 | }); 76 | }, 77 | }; 78 | }; 79 | -------------------------------------------------------------------------------- /app/src/plugins/unpkg-path-plugin.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild-wasm'; 2 | /** 3 | * Plugin for esbuild to resolve paths for UNPKG. 4 | * @returns {esbuild.Plugin} - The esbuild plugin for resolving UNPKG paths. 5 | */ 6 | export const unpkgPathPlugin = (): esbuild.Plugin => { 7 | return { 8 | name: 'unpkg-path-plugin', 9 | /** 10 | * Setup function for the UNPKG path plugin. 11 | * @param {esbuild.PluginBuild} build - The esbuild PluginBuild object. 12 | */ 13 | setup(build: esbuild.PluginBuild) { 14 | // Handle root entry file of index.js 15 | build.onResolve({ filter: /(^index\.js$)/ }, () => { 16 | return { path: 'index.js', namespace: 'a' }; 17 | }); 18 | // Handle relative paths in a module 19 | build.onResolve({ filter: /^\.+\// }, (args: any) => { 20 | const seeURL = new URL( 21 | args.path, 22 | 'https://unpkg.com' + args.resolveDir + '/', 23 | ).href; 24 | return { 25 | namespace: 'a', 26 | path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/') 27 | .href, 28 | }; 29 | }); 30 | // Handle main file of a module 31 | build.onResolve({ filter: /.*/ }, async (args: any) => { 32 | return { 33 | namespace: 'a', 34 | path: `https://unpkg.com/${args.path}`, 35 | }; 36 | }); 37 | }, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /app/src/public/ezgif.com-gif-maker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/ezgif.com-gif-maker.gif -------------------------------------------------------------------------------- /app/src/public/favicon-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/favicon-original.png -------------------------------------------------------------------------------- /app/src/public/icons/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/mac/icon.icns -------------------------------------------------------------------------------- /app/src/public/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/128x128.png -------------------------------------------------------------------------------- /app/src/public/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/24x24.png -------------------------------------------------------------------------------- /app/src/public/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/256x256.png -------------------------------------------------------------------------------- /app/src/public/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/32x32.png -------------------------------------------------------------------------------- /app/src/public/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/48x48.png -------------------------------------------------------------------------------- /app/src/public/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/512x512.png -------------------------------------------------------------------------------- /app/src/public/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/64x64.png -------------------------------------------------------------------------------- /app/src/public/icons/png/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/png/96x96.png -------------------------------------------------------------------------------- /app/src/public/icons/win/dark-mode.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/dark-mode.ico -------------------------------------------------------------------------------- /app/src/public/icons/win/favIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/favIcon.ico -------------------------------------------------------------------------------- /app/src/public/icons/win/icon-original.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/icon-original.ico -------------------------------------------------------------------------------- /app/src/public/icons/win/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/image.png -------------------------------------------------------------------------------- /app/src/public/icons/win/light-mode.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/light-mode.ico -------------------------------------------------------------------------------- /app/src/public/icons/win/logo-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/logo-original.png -------------------------------------------------------------------------------- /app/src/public/icons/win/logo-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/logo-two.png -------------------------------------------------------------------------------- /app/src/public/icons/win/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/logo.png -------------------------------------------------------------------------------- /app/src/public/icons/win/reactTypeV20.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/reactTypeV20.ico -------------------------------------------------------------------------------- /app/src/public/icons/win/scrollup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/scrollup.png -------------------------------------------------------------------------------- /app/src/public/icons/win/white-reactype-logo-1.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/app/src/public/icons/win/white-reactype-logo-1.psd -------------------------------------------------------------------------------- /app/src/public/index-prod.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | ReacType 14 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/public/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | ReacType 11 | 15 | 19 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/public/styles/globalDefaultStyles.ts: -------------------------------------------------------------------------------- 1 | const globalDefaultStyle: Object = { 2 | display: 'flex', 3 | flexDirection: 'column', 4 | justifyContent: 'space-between', 5 | boxSizing: 'border-box', 6 | padding: '10px 20px 10px 20px', 7 | margin: '10px', 8 | borderRadius: '10px', 9 | border: '10px Solid grey', 10 | fontFamily: 'Roboto', 11 | color: '#f1efea', 12 | maxWidth: 'fit-content', 13 | minWidth: '250px', 14 | cursor: 'grab', 15 | backgroundColor: 'rgba(0, 0, 0, 0.2)', 16 | }; 17 | 18 | export default globalDefaultStyle; 19 | -------------------------------------------------------------------------------- /app/src/public/styles/material-ui-1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/public/styles/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, adaptV4Theme } from '@mui/material/styles'; 2 | // theme creator: https://bareynol.github.io/mui-theme-creator/ 3 | 4 | export const theme1 = createTheme({ 5 | palette: { 6 | mode: 'dark', 7 | primary: { 8 | main: '#ef6c00', 9 | light: '#f7f4dc', 10 | dark: '#83bbff', 11 | contrastText: '#f7f4dc' // white 12 | }, 13 | secondary: { 14 | main: '#00acc1' // light blue 15 | }, 16 | background: { 17 | paper: '#181818' 18 | } 19 | } 20 | }); 21 | 22 | export const theme2 = createTheme({ 23 | palette: { 24 | mode: 'light', 25 | primary: { 26 | main: '#ef6c00' 27 | }, 28 | secondary: { 29 | main: '#00acc1' 30 | }, 31 | background: { 32 | paper: '#181818' 33 | } 34 | } 35 | }); 36 | 37 | export const SigninDark = createTheme({ 38 | palette: { 39 | mode: 'dark', 40 | primary: { 41 | main: '#3c59ba' 42 | }, 43 | secondary: { 44 | main: '#17a2b8' 45 | }, 46 | background: { 47 | paper: '#2997ff' 48 | } 49 | } 50 | }); 51 | 52 | export const SigninLight = createTheme({ 53 | palette: { 54 | mode: 'light', 55 | primary: { 56 | main: '#3c59ba' 57 | }, 58 | secondary: { 59 | main: '#17a2b8' 60 | }, 61 | background: { 62 | paper: '#f88e16' 63 | } 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /app/src/redux/reducers/rootReducer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { combineReducers } from '@reduxjs/toolkit'; 3 | 4 | // Need to import each slice which will be combined in the rootReducer 5 | import codePreviewReducer from './slice/codePreviewSlice'; 6 | import contextReducer from './slice/contextReducer'; 7 | import appStateReducer from './slice/appStateSlice'; 8 | import styleReducer from './slice/styleSlice'; 9 | import roomReducer from './slice/roomSlice'; 10 | 11 | /** 12 | * Combines individual reducers into a single root reducer to be used in the Redux store configuration. 13 | * This rootReducer integrates various features of the application such as code preview, context management, 14 | * application state, styling properties, and room functionalities into the Redux state management. 15 | * 16 | * Each reducer manages a specific aspect of the application's state: 17 | * - `codePreviewSlice`: Manages state related to code previews in the application. 18 | * - `contextSlice`: Handles state for contextual information within the application. 19 | * - `appState`: Manages general application state and settings. 20 | * - `styleSlice`: Controls style-related state, likely for dynamic styling in the app. 21 | * - `roomSlice`: Manages state related to room and meeting functionalities. 22 | * 23 | * @module rootReducer 24 | * @function 25 | * @example 26 | * import rootReducer from './reducers/rootReducer'; 27 | * 28 | * const store = configureStore({ 29 | * reducer: rootReducer 30 | * }); 31 | * 32 | * @returns {Reducer} The combined reducer containing all individual slice reducers, ready to be used in the Redux store. 33 | */ 34 | const rootReducer = combineReducers({ 35 | codePreviewSlice: codePreviewReducer, 36 | contextSlice: contextReducer, 37 | appState: appStateReducer, 38 | styleSlice: styleReducer, 39 | roomSlice: roomReducer 40 | }); 41 | 42 | export default rootReducer; 43 | -------------------------------------------------------------------------------- /app/src/redux/reducers/slice/codePreviewSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | code: ``, 5 | input: `` 6 | }; 7 | 8 | /** 9 | * `codePreviewSlice` manages the state of the code preview functionality in the application. 10 | * It handles real-time updates to the code editor and input fields. This slice is essential for functionalities 11 | * that require storing and retrieving user-entered code and associated input for execution or display purposes. 12 | * 13 | * @module codePreviewSlice 14 | * @type {Slice} 15 | * 16 | * @typedef {Object} CodePreviewState 17 | * @property {string} code - The current code stored in the state, typically representing the code written in an editor. 18 | * @property {string} input - Additional input or parameters associated with the code that might affect its execution or presentation. 19 | * 20 | * Actions: 21 | * - codePreviewSave: Updates the `code` property in the state with new content. 22 | * - codePreviewInput: Updates the `input` property in the state with new content. 23 | * - codePreviewCooperative: Merges provided payload into the existing state in a cooperative manner. 24 | * 25 | * @example 26 | * dispatch(codePreviewSave("function example() { return 'Hello, world!'; }")); 27 | * dispatch(codePreviewInput("Example input")); 28 | * 29 | * @returns {Reducer} The reducer for this slice of state, handling updates to code and input properties. 30 | */ 31 | const codePreviewSlice = createSlice({ 32 | name: 'codePreview', 33 | initialState, 34 | reducers: { 35 | codePreviewSave: (state, action) => { 36 | state.code = action.payload; 37 | }, 38 | codePreviewInput: (state, action) => { 39 | state.input = action.payload; 40 | }, 41 | codePreviewCooperative: (state, action) => { 42 | return Object.assign({}, state, action.payload); 43 | } 44 | } 45 | }); 46 | 47 | export const { codePreviewSave, codePreviewInput, codePreviewCooperative } = 48 | codePreviewSlice.actions; 49 | 50 | export default codePreviewSlice.reducer; 51 | -------------------------------------------------------------------------------- /app/src/redux/reducers/slice/styleSlice.ts: -------------------------------------------------------------------------------- 1 | import { PayloadAction, createSlice } from '@reduxjs/toolkit'; 2 | 3 | type StyleState = { 4 | style: any; // Replace with your specific type 5 | isThemeLight: boolean; 6 | }; 7 | 8 | const initialState: StyleState = { 9 | style: null, 10 | isThemeLight: true 11 | }; 12 | 13 | /** 14 | * `styleSlice` manages the styling state of the application. It holds the current style settings 15 | * and theme mode (light or dark). This slice is part of the Redux state management and is used 16 | * to dynamically adjust the styles throughout the app based on user preferences or predefined conditions. 17 | * 18 | * @module styleSlice 19 | * @type {Slice} 20 | * 21 | * @typedef {Object} StyleState 22 | * @property {any} style - The current style settings. Replace `any` with more specific type definition as needed. 23 | * @property {boolean} isThemeLight - Indicates if the current theme is light. `true` for light theme, `false` for dark theme. 24 | * 25 | * State Example: 26 | * { 27 | * style: null, // This should be replaced with a specific type if available. 28 | * isThemeLight: true 29 | * } 30 | * 31 | * @function setStyle - Action to set the style of the application. It should receive the new style settings. 32 | * @param {PayloadAction} action - The action payload containing the new style settings. 33 | * 34 | * @function cooperativeStyle - A reducer that merges partial style state updates into the existing style state. 35 | * This allows for cooperative style updates where multiple properties might need to be updated simultaneously. 36 | * @param {PayloadAction>} action - The action payload containing the partial updates to be merged. 37 | * 38 | * @example 39 | * dispatch(setStyle(newStyleSettings)); 40 | * dispatch(cooperativeStyle({ isThemeLight: false })); 41 | * 42 | * @returns {Reducer} The reducer for this slice of state, handling updates to styling and theme properties. 43 | */ 44 | const styleSlice = createSlice({ 45 | name: 'style', 46 | initialState, 47 | reducers: { 48 | setStyle: (state, action: PayloadAction) => { 49 | state.style = action.payload; 50 | }, 51 | cooperativeStyle: (state, action: PayloadAction>) => { 52 | return { ...state, ...action.payload }; 53 | } 54 | } 55 | }); 56 | 57 | export const { setStyle, cooperativeStyle } = styleSlice.actions; 58 | 59 | export default styleSlice.reducer; 60 | -------------------------------------------------------------------------------- /app/src/redux/store.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { configureStore } from '@reduxjs/toolkit'; 3 | import rootReducer from './reducers/rootReducer'; 4 | 5 | /** 6 | * Configures and creates a Redux store for the application. This setup includes the root reducer, 7 | * and custom middleware to handle serializability checks. The store is configured with default middleware 8 | * from Redux Toolkit, modified to ignore certain non-serializable parts of the state that are expected 9 | * due to the use of complex objects (like components or elements in the state). 10 | * 11 | * The serializability check middleware is configured to ignore paths where non-serializable values 12 | * are known to exist. This is necessary when state includes elements like React components or other 13 | * non-serializable objects. 14 | * 15 | * @module store 16 | * @function 17 | * @example 18 | * import store from './store'; 19 | * 20 | * console.log(store.getState()); // Logs the current state of the Redux store. 21 | * 22 | * @returns {object} The Redux store configured with root reducers and customized middleware. 23 | * 24 | * @typedef {ReturnType} RootState 25 | * Defines a type that represents the state of the entire Redux store, returned by `store.getState()`. 26 | * 27 | * @typedef {typeof store} AppStore 28 | * Defines a type representing the Redux store itself for use in typing contexts where the store object is used. 29 | */ 30 | const store = configureStore({ 31 | reducer: rootReducer, // combine all reducers into one rootReducer 32 | middleware: (getDefaultMiddleware) => { 33 | let ignoredPaths: string[] = []; 34 | 35 | for (let i = 0; i < 21; i++) { // iterate through paths of the state and ignore specific properties from serializability checks 36 | ignoredPaths.push(`appState.HTMLTypes.${i}.icon`); 37 | ignoredPaths.push(`appState.HTMLTypes.${i}.icon.$$typeof`); 38 | } 39 | 40 | return getDefaultMiddleware({ // return getDefaultMiddleware but with customized serializability check 41 | serializableCheck: { 42 | ignoredPaths, // specified paths to be ignored for serializability checks 43 | }, 44 | }); 45 | }, 46 | }); 47 | 48 | export type RootState = ReturnType; 49 | 50 | export type AppStore = typeof store; 51 | 52 | export default store; 53 | -------------------------------------------------------------------------------- /app/src/serverConfig.js: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === 'production'; 2 | 3 | const serverConfig = { 4 | DEV_PORT: 5656, 5 | API_BASE_URL: isProduction 6 | ? 'http://localhost:5656' 7 | : 'http://localhost:5656', 8 | // : 'http://localhost:8080', 9 | API_BASE_URL2: isProduction 10 | ? 'http://localhost:5656' 11 | : 'http://localhost:8080' 12 | }; 13 | // module.exports = config; 14 | 15 | export default serverConfig; 16 | -------------------------------------------------------------------------------- /app/src/tree/useResizeObserver.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { RefObject, useEffect, useState } from 'react'; 3 | import ResizeObserver from 'resize-observer-polyfill'; 4 | 5 | /** 6 | * A custom React hook that observes the resize of a specified DOM element. 7 | * @param {RefObject} ref - The reference to the DOM element to observe. 8 | * @returns {DOMRectReadOnly | null} - The dimensions of the observed element, or null if not available. 9 | */ 10 | const useResizeObserver = ( 11 | ref: RefObject, 12 | ): DOMRectReadOnly | null => { 13 | const [dimensions, setDimensions] = useState(null); 14 | useEffect(() => { 15 | // the element being observed (div with green border) 16 | const observeTarget = ref.current; 17 | const resizeObserver = new ResizeObserver((entries) => { 18 | entries.forEach((entry) => { 19 | // contentRect is an object containing the dimensions of the observed element 20 | setDimensions(entry.contentRect); 21 | }); 22 | }); 23 | resizeObserver.observe(observeTarget); 24 | return () => { 25 | resizeObserver.unobserve(observeTarget); 26 | }; 27 | }, [ref]); 28 | return dimensions; 29 | }; 30 | 31 | export default useResizeObserver; 32 | -------------------------------------------------------------------------------- /app/src/tutorial/CSSEditor.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import cssEditorTab from '../../../resources/csseditor_tutorial_images/CSSEditorTab.png'; 4 | import cssEdit from '../../../resources/csseditor_tutorial_images/DirectCSSEdit.gif'; 5 | import copyPaste from '../../../resources/csseditor_tutorial_images/CopyPasteCSS.gif'; 6 | 7 | /** 8 | * CSSEditor component displays the tutorial for the CSS editor tab, allowing users to edit CSS code directly and see the changes reflected immediately. 9 | * 10 | * @param {object} props - Component props. 11 | * @param {object} props.classes - CSS classes for styling. 12 | * @param {Function} props.setPage - Function to set the current page. 13 | * @returns {JSX.Element} CSSEditor component JSX. 14 | */ 15 | const CSSEditor: React.FC<{ 16 | classes: any; 17 | setPage: Function; 18 | }> = ({ classes, setPage }): JSX.Element => ( 19 |
20 |

CSS Editor

21 |
22 |

23 | The CSS editor tab is located in the bottom panel of the application and 24 | is where the CSS file's code is located. 25 |
26 |
27 | You are able to freely edit the CSS code directly and have the changes 28 | reflected immediately in the demo render panel. You can also copy and 29 | paste your own CSS code into the editor to take full control of custom 30 | CSS classes. 31 |
32 |

33 |
34 | 35 |
36 |
37 |

Edit CSS code

38 |

39 | To edit the CSS code in the CSS editor, make the desired changes 40 | directly within the editor. 41 |
42 |

43 |
44 | 45 |
46 |
47 |

48 | Or copy and paste your own CSS code directly into the editor. 49 |

50 |
51 |
52 | 53 |
54 |
55 |
56 | ); 57 | 58 | export default CSSEditor; 59 | -------------------------------------------------------------------------------- /app/src/tutorial/Canvas.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import canvas1 from '../../../resources/canvas_tutorial_images/canvas1.png'; 4 | import drag1 from '../../../resources/canvas_tutorial_images/drag1.gif'; 5 | import undoRedo from '../../../resources/canvas_tutorial_images/undoRedo.gif'; 6 | 7 | /** 8 | * Canvas component displays tutorial for HTML elements, components, MUI Components of the prototype application. 9 | * 10 | * @param {object} props - Component props. 11 | * @param {object} props.classes - CSS classes for styling. 12 | * @param {Function} props.setPage - Function to set the current page. 13 | * @returns {JSX.Element} Canvas component JSX. 14 | */ 15 | const Canvas: React.FC<{ 16 | classes: any; 17 | setPage: Function; 18 | }> = ({ classes, setPage }): JSX.Element => ( 19 |
20 |

Canvas

21 |
22 |

The canvas is located in the left center panel of the application and is where all 23 | the HTML elements and components of the prototype application are displayed.

24 |
25 | 26 |
27 |
28 |

Drag-n-Drop

29 |

The drag and drop functionality is used to populate the canvas with HTML elements and 30 | reusable components from the left panel.

31 | To use the drag and drop functionality, select the desired setPage('HTML Elements')}> 32 | HTML element, custom setPage('HTML Elements')}>HTML element, or 33 | setPage('Reusable Components')}> reusable component then click and hold it 34 | to drag it onto the canvas.

35 | HTML elements and reusable components can be placed within each other on the canvas to nest them.
36 |

37 |
38 | 39 |
40 |
41 |

Undo and Redo

42 |

The undo functionality is implemented to revert the user's last action. Redo will reperform the user's last undid action. 43 |

44 |
45 | 46 |
47 |
48 |
49 | ); 50 | 51 | export default Canvas; 52 | -------------------------------------------------------------------------------- /app/src/tutorial/CodePreview.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import codePreview from '../../../resources/code_preview_images/CodePreview.png'; 4 | 5 | /** 6 | * CodePreview component displays tutorial for the code preview at the bottom center panel of the page. 7 | * 8 | * @param {object} props - Component props. 9 | * @param {object} props.classes - CSS classes for styling. 10 | * @param {Function} props.setPage - Function to set the current page. 11 | * @returns {JSX.Element} CodePreview component JSX. 12 | */ 13 | const CodePreview: React.FC<{ 14 | classes: any; 15 | setPage: Function; 16 | }> = ({ classes, setPage }): JSX.Element => ( 17 |
18 |

Code Preview

19 |
20 |

21 | The code preview is located at the bottom center panel of the page on 22 | the fourth tab. 23 |
24 |
25 | The code preview will generate the lines of code for functional 26 | components. As you drag and drop elements or components onto the{' '} 27 | setPage('Canvas')}> 28 | canvas 29 | 30 | , the code preview will populate and generate the corresponding lines of 31 | code in real-time. 32 |
33 |
34 | Adding a{' '} 35 | setPage('States')}> 36 | state 37 | {' '} 38 | will also generate the corresponding line of code for the state in the 39 | code preview. 40 |
41 |
42 | Code preview will also change depending on whether Classic React / 43 | Gatsby.js / Next.js is chosen. 44 |
45 |
46 | To learn more about the{' '} 47 | setPage('Canvas')}> 48 | canvas 49 | 50 | , click{' '} 51 | setPage('Canvas')}> 52 | "here" 53 | 54 |

55 |
56 | 57 |
58 |
59 | ); 60 | 61 | export default CodePreview; 62 | -------------------------------------------------------------------------------- /app/src/tutorial/ComponentTree.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import tree1 from '../../../resources/tree_tutorial_images/tree1.png'; 4 | import tree2 from '../../../resources/tree_tutorial_images/tree2.png'; 5 | import tree3 from '../../../resources/tree_tutorial_images/tree3.png'; 6 | import tree4 from '../../../resources/tree_tutorial_images/tree4.png'; 7 | import tree5 from '../../../resources/tree_tutorial_images/tree5.png'; 8 | 9 | /** 10 | * ComponentTree component displays the tutorial for the React component tree, providing a visual representation of the component hierarchy. 11 | * 12 | * @param {object} props - Component props. 13 | * @param {object} props.classes - CSS classes for styling. 14 | * @param {Function} props.setPage - Function to set the current page. 15 | * @returns {JSX.Element} ComponentTree component JSX. 16 | */ 17 | const ComponentTree: React.FC<{ 18 | classes: any; 19 | setPage: Function; 20 | }> = ({ classes, setPage }): JSX.Element => ( 21 |
22 |

React Component Tree

23 |
24 |

25 | The component tree provides the developer with a visual representation 26 | of the component hierarchy. The tree updates in real time as the 27 | developer adds or deletes components and HTML elements. 28 |

29 |
30 | 31 |
32 |
33 |

34 | Each tree begins with a root node. The current page that is selected 35 | represents the root node. 36 |

37 |
38 | 39 |
40 |
41 |

42 | setPage('Reusable Components')} 45 | > 46 | Reusable components 47 | {' '} 48 | are shown attached to the current page along with their subtrees of 49 | components and{' '} 50 | setPage('HTML Elements')} 53 | > 54 | HTML elements 55 | 56 | . 57 |

58 |
59 | 60 |
61 |
62 |

63 | setPage('HTML Elements')} 66 | > 67 | HTML elements 68 | {' '} 69 | are shown by their tag name. 70 |

71 |
72 | 73 |
74 |
75 |

76 | You can also view the tree for each{' '} 77 | setPage('Reusable Components')} 80 | > 81 | reusable component 82 | 83 | . 84 |

85 |
86 | 87 |
88 |
89 |
90 | ); 91 | 92 | export default ComponentTree; 93 | -------------------------------------------------------------------------------- /app/src/tutorial/KeyboardShortcuts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * KeyboardShortcuts component displays tutorial for the keyboard shortcuts for Mac and Windows. 5 | * 6 | * @param {object} props - Component props. 7 | * @param {object} props.classes - CSS classes for styling. 8 | * @param {Function} props.setPage - Function to set the current page. 9 | * @returns {JSX.Element} KeyboardShortcuts component JSX. 10 | */ 11 | const KeyboardShortcuts: React.FC<{ 12 | classes: any; 13 | setPage: Function; 14 | }> = ({ classes, setPage }): JSX.Element => ( 15 |
16 |

Keyboard Shortcuts

17 |
18 |

Mac

19 |
    20 |
  • Export Project: Command + e
  • 21 |
  • Undo: Command + z
  • 22 |
  • Redo: Command + Shift + z
  • 23 |
  • Save Project As: Command + s
  • 24 |
  • Save Project: Command + shift + s
  • 25 |
  • Delete HTML Tag on Canvas: Backspace
  • 26 |
  • Delete Project: Command + Backspace
  • 27 |
  • Open Project: Command + o
  • 28 |
29 |

Windows

30 |
    31 |
  • Export Project: Control + e
  • 32 |
  • Undo: Control + z
  • 33 |
  • Redo: Control + Shift + z
  • 34 |
  • Save Project As: Control + s
  • 35 |
  • Save Project: Control + shift + s
  • 36 |
  • Delete HTML Tag on Canvas: Backspace
  • 37 |
  • Delete Project: Control + Backspace
  • 38 |
  • Open Project: Control + o
  • 39 |
40 |
41 |
42 | ); 43 | 44 | export default KeyboardShortcuts; 45 | -------------------------------------------------------------------------------- /app/src/tutorial/Pages.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import pages from '../../../resources/pages_images/Pages.png'; 3 | import toggle from '../../../resources/pages_images/Toggle.png'; 4 | import deletePage from '../../../resources/pages_images/DeletePage.png'; 5 | import pagesPanel from '../../../resources/pages_images/PagesPanel.png'; 6 | import pageSwapping from '../../../resources/pages_images/PagesSwapping.gif'; 7 | 8 | /** 9 | * Pages component displays tutorial for the functionality related to managing pages. 10 | * 11 | * @param {object} props - Component props. 12 | * @param {object} props.classes - CSS classes for styling. 13 | * @param {Function} props.setPage - Function to set the current page. 14 | * @returns {JSX.Element} Pages component JSX. 15 | */ 16 | const Pages: React.FC<{ 17 | classes: any; 18 | setPage: Function; 19 | }> = ({ classes, setPage }): JSX.Element => ( 20 |
21 |

Pages

22 |
23 |

24 | Start off by giving your page a name. Make sure to check the page box 25 | next to the Name input. Then click the add button and it will show in 26 | the Pages section below. 27 |

28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |

36 | Switch between pages by selecting the Page and customize it by dragging 37 | the elements you want into the{' '} 38 | setPage('Canvas')}> 39 | canvas 40 | {' '} 41 | of the page you're on. (Note the red dot next to the page name signals 42 | which page you are currently on). 43 |

44 |
45 | 46 |
47 | 48 |
49 |

50 | Delete the selected page by simply clicking the delete button below the 51 | style attribute dropdowns. 52 |

53 |
54 | 55 |
56 |
57 |
58 | ); 59 | 60 | export default Pages; 61 | -------------------------------------------------------------------------------- /app/src/tutorial/ReusableComponents.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React from 'react'; 3 | import reusableComponents1 from '../../../resources/reusable_components_tutorial_images/reusablecomponents1.png'; 4 | import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusablecomponents2.png'; 5 | import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusablecomponents3.png'; 6 | 7 | /** 8 | * ReusableComponents component displays tutorial for the functionality related to reusable components. 9 | * 10 | * @param {object} props - Component props. 11 | * @param {object} props.classes - CSS classes for styling. 12 | * @param {Function} props.setPage - Function to set the current page. 13 | * @returns {JSX.Element} ReusableComponents component JSX. 14 | */ 15 | const ReusableComponents: React.FC<{ 16 | classes: any; 17 | setPage: Function; 18 | }> = ({ classes, setPage }): JSX.Element => ( 19 |
20 |

Reusable Components

21 |
22 |

23 | NOTE: As of version 13.0, each reusable component can only have one 24 | parent. Otherwise, you will be prompted to generate a new component to 25 | nest. 26 |

27 |

To add a Reusable Component, use the New Component input form 28 | in the Creation Panel to name a Component. Leave the Root/Page checkbox 29 | unchecked. Then click Create to add a new Reusable Component. 30 |

31 |
32 | 33 |
34 |
35 |

36 | The Components you create will populate on the left panel under the 37 | section 'Reusable Components'. 38 |

39 |
40 | 41 |
42 |
43 |

44 | After creating the desired Component, you can now drag-n-drop to the 45 | Canvas. If you'd like to know about about the drag-n-drop functionality, 46 | please locate the{' '} 47 | setPage('Canvas')}> 48 | Canvas Tutorial 49 | {' '} 50 | for more information on how it works. 51 |

52 |
53 | 54 |
55 |

56 | You can place a reusable component inside{' '} 57 | setPage('Pages')}> 58 | Pages 59 | {' '} 60 | and populate the component itself with other{' '} 61 | setPage('HTML_Elements')} 64 | > 65 | HTML Elements 66 | 67 | . 68 |

69 |
70 |
71 | ); 72 | 73 | export default ReusableComponents; 74 | -------------------------------------------------------------------------------- /app/src/tutorial/Styling.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import lighting from '../../../resources/customizing_elements_images/Lighting.png'; 3 | import resize from '../../../resources/customizing_elements_images/Resize.gif'; 4 | import codeChange from '../../../resources/customizing_elements_images/CodeChange.png'; 5 | 6 | /** 7 | * Styling component provides information about styling features in ReacType. 8 | * 9 | * @param {object} props - Component props. 10 | * @param {object} props.classes - CSS classes for styling. 11 | * @param {Function} props.setPage - Function to set the current page. 12 | * @returns {JSX.Element} Styling component JSX. 13 | */ 14 | const Styling: React.FC<{ 15 | classes: any; 16 | setPage: Function; 17 | }> = ({ classes, setPage }): JSX.Element => ( 18 |
19 |

Styling Features

20 |
21 |

Dark Mode

22 |
23 | 24 |
25 |

26 | Spice up the app and ease the strian on your eyes by switching to DARK MODE. DARK 27 | MODE will change the background and text colors of the app. 28 |

29 |
30 |

Resize Code Preview and Component Tree

31 |
32 | 33 |
34 |

35 | Hover over the line above the setPage('Code Preview')} >code preview and/or setPage('Component Tree')} >component tree to 36 | resize the section. Simply click and drag up or down to resize. 37 |

38 |
39 |

Customize Code Preview

40 |
41 | 42 |
43 |

44 | You can manually change your code in the setPage('Code Preview')} >code preview before exporting and see the changes reflected in your exported 45 | file! 46 |

47 |
48 |
49 | ); 50 | export default Styling; 51 | -------------------------------------------------------------------------------- /app/src/utils/createFiles.util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /** 3 | * Creates TypeScript component files for each component provided in the components array. These files 4 | * are placed in a "components" directory within the specified path. The function can differentiate 5 | * between exporting files for a full application or just locally within a specified directory, based 6 | * on the `exportAppBool` flag. It handles file creation asynchronously and resolves once all files 7 | * are successfully written, or rejects upon any error. 8 | * 9 | * @param {any[]} components - An array of objects representing components, each with a `name` and `code` property. 10 | * @param {string} path - The base directory path where component files are to be created. 11 | * @param {string} appName - The name of the application, used to structure the directory if exporting as part of an application setup. 12 | * @param {boolean} exportAppBool - A boolean flag that indicates whether the components are being exported as part of a full application setup. 13 | * @returns {Promise>} - A promise that resolves with an array of paths where files have been created, or rejects with an error message. 14 | */ 15 | 16 | const createFiles = ( 17 | components: any, 18 | path: string, 19 | appName: string, 20 | exportAppBool: boolean, 21 | ): Promise> => { 22 | let dir = path; 23 | if (exportAppBool === false) { 24 | if (!dir.match(/components|\*$/)) { 25 | if (window.api.existsSync(`${dir}/src`)) { 26 | dir = `${dir}/src`; 27 | } 28 | dir = `${dir}/components`; 29 | if (!window.api.existsSync(dir)) { 30 | window.api.mkdirSync(dir); 31 | } 32 | } 33 | } else if (exportAppBool) { 34 | if (!dir.match(/${appName}|\*$/)) { 35 | dir = `${dir}/${appName}/src/components`; 36 | } 37 | } 38 | const promises: Array = []; 39 | components.forEach((component: any) => { 40 | const newPromise = new Promise((resolve, reject) => { 41 | window.api.writeFileSync( 42 | `${dir}/${component.name}.tsx`, 43 | 44 | // this formatCodefunction has asynchronous issue 45 | // window.api.formatCode(component.code), 46 | component.code, 47 | (err: any) => { 48 | if (err) return reject(err.message); 49 | return resolve(path); 50 | }, 51 | ); 52 | }); 53 | promises.push(newPromise); 54 | }); 55 | return Promise.all(promises); 56 | }; 57 | export default createFiles; 58 | -------------------------------------------------------------------------------- /app/src/utils/createGatsbyFiles.util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import { Component } from '../interfaces/Interfaces'; 3 | 4 | /** 5 | * Determines whether a given component is a root component. 6 | * Root components are identified by their IDs being included in the provided rootArray. 7 | * @param {Component} component The component to check. 8 | * @param {number[]} rootArray An array of IDs that identify root components. 9 | * @returns {boolean} Returns true if the component is a root component, false otherwise. 10 | */ 11 | const isRoot = (component: Component, rootArray: number[]): boolean => (!!rootArray.includes(component.id)); 12 | 13 | /** 14 | * Asynchronously creates TypeScript component files for a Gatsby.js application, organizing them into 'pages' and 'components' directories. 15 | * This function categorizes components as root or non-root based on their IDs. Root components are stored in the 'pages' directory, 16 | * with the first root component specifically stored as 'index.tsx'. All other root components are named according to their component name. 17 | * Non-root components are stored in the 'components' directory. 18 | * @param {Component[]} components An array of component objects that contain the necessary data to generate files. 19 | * @param {string} path The base directory path for the Gatsby application. 20 | * @param {string} appName The name of the Gatsby application. 21 | * @param {number[]} rootComponents An array of component IDs that should be treated as root components. 22 | * @returns {Promise} A promise that resolves when all component files have been successfully written to disk. 23 | * The promise returns an array of the paths where the files were created. 24 | */ 25 | const createGatsbyFiles = ( 26 | components: Component[], 27 | path: string, 28 | appName: string, 29 | rootComponents: number[], 30 | ) => { 31 | let dir = path; 32 | dir = `${dir}/${appName}`; 33 | const promises: Array = []; 34 | components.forEach((component: Component) => { 35 | let code: string; 36 | let fileName: string; 37 | if (isRoot(component, rootComponents)) { 38 | if (component.id === 1) { 39 | // first root component must be index.tsx 40 | fileName = `${dir}/src/pages/index.tsx`; 41 | } else { 42 | fileName = `${dir}/src/pages/${component.name}.tsx`; 43 | } 44 | } else { 45 | fileName = `${dir}/src/components/${component.name}.tsx`; 46 | } 47 | const newPromise = new Promise((resolve, reject) => { 48 | window.api.writeFileSync(fileName, component.code, (err: any) => { 49 | if (err) return reject(err.message); 50 | return resolve(path); 51 | }); 52 | }); 53 | promises.push(newPromise); 54 | }); 55 | return Promise.all(promises); 56 | }; 57 | export default createGatsbyFiles; 58 | -------------------------------------------------------------------------------- /app/src/utils/createNextFiles.util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | // Create all component files for a next.js application 3 | // "Root" level components are stored in a pages directory 4 | // all other components will be in a components directory 5 | import { Component } from '../interfaces/Interfaces'; 6 | 7 | /** 8 | * Determines whether a given component is a root component. 9 | * Root components are identified by their IDs being included in the provided rootArray. 10 | * @param {Component} component The component to check. 11 | * @param {number[]} rootArray An array of IDs that identify root components. 12 | * @returns {boolean} Returns true if the component is a root component, false otherwise. 13 | */ 14 | const isRoot = (component: Component, rootArray: number[]): boolean => (!!rootArray.includes(component.id)); 15 | 16 | /** 17 | * Asynchronously creates TypeScript component files for a Next.js application, organizing them into 'pages' and 'components' directories. 18 | * Components identified as root components (based on their IDs in the 'rootComponents' array) are placed in the 'pages' directory, 19 | * with the first root component specifically stored as 'index.tsx'. All other root components are named according to their component name. 20 | * Non-root components are stored in the 'components' directory. 21 | * @param {Component[]} components An array of component objects that include necessary data for generating files. 22 | * @param {string} path The base directory path for the Next.js application. 23 | * @param {string} appName The name of the Next.js application. 24 | * @param {number[]} rootComponents An array of component IDs designated as root components. 25 | * @returns {Promise} A promise that resolves when all component files have been successfully written to disk. 26 | * The promise returns an array of the paths where the files were created, which can be used for further processing or verification. 27 | */ 28 | const createNextFiles = ( 29 | components: Component[], 30 | path: string, 31 | appName: string, 32 | rootComponents: number[], 33 | ): Promise => { 34 | let dir = path; 35 | dir = `${dir}/${appName}`; 36 | 37 | const promises: Array = []; 38 | components.forEach((component: Component) => { 39 | let code: string; 40 | let fileName: string; 41 | if (isRoot(component, rootComponents)) { 42 | if (component.id === 1) { 43 | // first root component must be index.tsx 44 | fileName = `${dir}/pages/index.tsx`; 45 | } else { 46 | fileName = `${dir}/pages/${component.name}.tsx`; 47 | } 48 | } else { 49 | fileName = `${dir}/components/${component.name}.tsx`; 50 | } 51 | const newPromise = new Promise((resolve, reject) => { 52 | window.api.writeFileSync(fileName, component.code, (err: any) => { 53 | if (err) return reject(err.message); 54 | return resolve(path); 55 | }); 56 | }); 57 | 58 | promises.push(newPromise); 59 | }); 60 | return Promise.all(promises); 61 | }; 62 | 63 | export default createNextFiles; 64 | -------------------------------------------------------------------------------- /app/src/utils/createNonce.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const { v4: uuidv4 } = require('uuid'); 3 | // const Buffer = require('buffer'); 4 | 5 | // const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); 6 | // module.exports = buf; 7 | 8 | // Generate an arbitrary number 9 | // this arbitrary number will be used in CspHtmlWebpackPlugin and HtmlWebpackPlugin configuration in webpack 10 | module.exports = function () { 11 | return new Buffer.from(uuidv4()).toString('base64'); 12 | }; 13 | -------------------------------------------------------------------------------- /app/src/utils/exportProject.util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import createApplicationUtil from './createApplication.util'; 3 | import createNextApp from './createNextApp.util'; 4 | import createFiles from './createFiles.util'; 5 | import createGatsbyApp from './createGatsbyApp.util'; 6 | 7 | /** 8 | * Handles the exporting of projects in various configurations based on user selections. 9 | * This function supports the creation of Classic React applications, Next.js applications, 10 | * Gatsby.js apps, and a basic export of component files without full application scaffolding. 11 | * 12 | * @param {string} path - The directory path where the project should be exported. 13 | * @param {string} appName - The name of the application to be created. 14 | * @param {number} genOption - Indicates the type of generation: 15 | * 0 for only component files, 1 for complete application setup. 16 | * @param {string} projectType - Specifies project type: 'Classic React', 'Next.js', or 'Gatsby.js'. 17 | * @param {any[]} components - An array of components to be included in the project. 18 | * @param {number[]} rootComponents - An array of indices identifying root components. 19 | * @param {boolean} [tests] - Optional. Whether to include test setups in the generated application. 20 | * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. 21 | */ 22 | const exportProject = ( 23 | path: string, 24 | appName: string, 25 | genOption: number, 26 | projectType: string, 27 | components: any, 28 | rootComponents: number[], 29 | tests?: boolean, 30 | ): void => { 31 | // Create fully functional classic react application 32 | if (genOption === 1 && projectType === 'Classic React') { 33 | createApplicationUtil({ 34 | path, 35 | appName, 36 | components, 37 | testchecked: tests, 38 | }).catch((err) => console.log(err)); 39 | } else if (genOption === 0) { 40 | // export all component files, but don't create all application files 41 | createFiles(components, path, appName, false); 42 | } else if (genOption === 1 && projectType === 'Next.js') { 43 | // Create fully functional Next.js and Gatsby.js application files 44 | createNextApp({ 45 | path, 46 | appName, 47 | components, 48 | rootComponents, 49 | testchecked: tests, 50 | }).catch((err) => console.log(err)); 51 | } else if (genOption === 1 && projectType === 'Gatsby.js') { 52 | createGatsbyApp({ 53 | path, 54 | appName, 55 | components, 56 | rootComponents, 57 | testchecked: tests, 58 | }).catch((err) => console.log(err)); 59 | } 60 | }; 61 | 62 | export default exportProject; 63 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === 'production'; 2 | 3 | const config = { 4 | DEV_PORT: 5656, 5 | API_BASE_URL: isProduction 6 | ? 'http://localhost:5656' 7 | : 'http://localhost:5656', 8 | // Note, here is where you need to change if you are hosting on a differnt place. 9 | // : 'http://localhost:8080', 10 | API_BASE_URL2: isProduction 11 | ? 'http://localhost:5656' 12 | : 'http://localhost:8080' 13 | }; 14 | module.exports = config; 15 | -------------------------------------------------------------------------------- /docker-compose-prod.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" # define the version of the Docker Compose file 2 | 3 | services: # a 'dictionary' that defines the services that will run in containers 4 | prod: # a 'dictionary as the first 'key' in services; the 1st service, the dev env 5 | image: reactype/rt-prod # a 'key' in dev, specifying the image to use 6 | container_name: rt-prod # the container's name 7 | ports: 8 | - "5656:5656" # an 'array', maps port 8080 from the host to container 9 | # environment: # set ENV variables to MongoSQL initialization 10 | volumes: # a 'key' containing 'arrays' 11 | - .:/app/src # mounts the current directory to /usr/src/app in the container; allows webpack-dev-server running in the container to watch for code changes in our file system outside the container; we have live reloading and HMR 12 | - node_modules:/app/src/node_modules # mounts a named volume, 'node_modules', to preserve our node modules 13 | command: npm start # run 'npm run dev:hot' inside the container to start the server 14 | # depends_on: # ensures that the dev service/container starts only after postgres-db service/container is up 15 | # - postgres-db 16 | 17 | # postgres-db: # a 'dictionary'; the 2nd service, the PostgreSQL db 18 | # image: spencerayleen:mm-postgres 19 | # container_name: mm-database 20 | # environment: # set ENV variables to PostgreSQL initialization 21 | # - POSTGRES_PASSWORD=admin # set user pw 22 | # - POSTGRES_USER=mmadmin # set user (name) 23 | # - POSTGRES_DB=mmdb # set db name 24 | # volumes: 25 | # - dev-db-volume:/var/lib/postgresql/data # mount a named volume, 'dev-db-volume', to the /var/lib/postgresql/data directory in the container where postgres stores the actual data files that make up your database; this volume will persist the data between container starts and stops 26 | 27 | volumes: # a 'dictionary', defines named volumes to persist data 28 | node_modules: # empty node_modules key, will be shared between containers 29 | # dev-db-volume: # empty dev-db-volume key -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ReacType 7 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mockData.ts: -------------------------------------------------------------------------------- 1 | import HTMLTypes from './app/src/redux/HTMLTypes'; 2 | 3 | const mockObj = { 4 | //these user credentials were created via the signup page 5 | //once connecting to new mongo cluster, you must create new login credentials to test 6 | user: { 7 | username: 'test', 8 | email: 'test@gmail.com', 9 | password: 'password1!', 10 | userId: '664247b09df40d692fb7fdef' 11 | }, 12 | 13 | state: { 14 | name: 'test', 15 | isLoggedIn: false, 16 | components: [ 17 | { 18 | id: 1, 19 | name: 'index', 20 | style: {}, 21 | code: '
Drag in a component or HTML element into the canvas!
', 22 | children: [] 23 | } 24 | ], 25 | projectType: 'Next.js', 26 | rootComponents: [1], 27 | canvasFocus: { componentId: 1, childId: null }, 28 | nextComponentId: 2, 29 | nextChildId: 1, 30 | nextTopSeparatorId: 1000, 31 | HTMLTypes: HTMLTypes, // left as is for now 32 | tailwind: false, 33 | stylesheet: '', 34 | codePreview: false 35 | }, 36 | 37 | projectToSave: { 38 | name: 'super test project', 39 | forked: false, 40 | likes: 0, 41 | published: false, 42 | project: { 43 | name: 'test', 44 | isLoggedIn: false, 45 | components: [ 46 | { 47 | id: 1, 48 | name: 'index', 49 | style: {}, 50 | code: '
Drag in a component or HTML element into the canvas!
', 51 | children: [] 52 | } 53 | ], 54 | projectType: 'Next.js', 55 | rootComponents: [1], 56 | canvasFocus: { componentId: 1, childId: null }, 57 | nextComponentId: 2, 58 | nextChildId: 1, 59 | nextTopSeparatorId: 1000, 60 | HTMLTypes: HTMLTypes, // left as is for now 61 | tailwind: false, 62 | stylesheet: '', 63 | codePreview: false 64 | }, 65 | userId: { 66 | $oid: '664247b09df40d692fb7fdef' 67 | }, 68 | username: 'test', 69 | createdAt: Date.now(), 70 | isLoggedIn: false, 71 | comments: [] 72 | }, 73 | //The following is for graphQL 74 | GET_PROJECTS: `query GetAllProjects($userId: ID) { 75 | getAllProjects(userId: $userId) { 76 | name 77 | likes 78 | id 79 | userId 80 | username 81 | published 82 | } 83 | }`, 84 | 85 | ADD_LIKE: `mutation AddLike($projId: ID!, $likes: Int!) { 86 | addLike(projId: $projId, likes: $likes) 87 | { 88 | id 89 | likes 90 | } 91 | }`, 92 | 93 | PUBLISH_PROJECT: `mutation Publish($projId: ID!, $published: Boolean!) { 94 | publishProject(projId: $projId, published: $published) 95 | { 96 | id 97 | published 98 | } 99 | }`, 100 | 101 | MAKE_COPY: `mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { 102 | makeCopy(userId: $userId, projId: $projId, username: $username) 103 | { 104 | id 105 | userId 106 | username 107 | } 108 | }`, 109 | 110 | DELETE_PROJECT: `mutation DeleteProject($projId: ID!) { 111 | deleteProject(projId: $projId) 112 | { 113 | id 114 | } 115 | }` 116 | }; 117 | 118 | export default mockObj; 119 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | // import { defineConfig, devices } from '@playwright/test'; 2 | 3 | // /** 4 | // * Read environment variables from file. 5 | // * https://github.com/motdotla/dotenv 6 | // */ 7 | // // require('dotenv').config(); 8 | 9 | // /** 10 | // * See https://playwright.dev/docs/test-configuration. 11 | // */ 12 | // export default defineConfig({ 13 | // testDir: './__tests__/playwright', 14 | // /* Run tests in files in parallel */ 15 | // fullyParallel: true, 16 | // /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | // forbidOnly: !!import.meta.env.CI, 18 | // /* Retry on CI only */ 19 | // retries: import.meta.env.CI ? 2 : 0, 20 | // /* Opt out of parallel tests on CI. */ 21 | // workers: import.meta.env.CI ? 1 : undefined, 22 | // /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | // reporter: 'html', 24 | // /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | // use: { 26 | // /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // // baseURL: 'http://127.0.0.1:3000', 28 | 29 | // /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | // trace: 'on-first-retry' 31 | // }, 32 | 33 | // /* Configure projects for major browsers */ 34 | // projects: [ 35 | // { 36 | // name: 'chromium', 37 | // use: { ...devices['Desktop Chrome'] } 38 | // }, 39 | 40 | // { 41 | // name: 'firefox', 42 | // use: { ...devices['Desktop Firefox'] } 43 | // }, 44 | 45 | // { 46 | // name: 'webkit', 47 | // use: { ...devices['Desktop Safari'] } 48 | // } 49 | 50 | // /* Test against mobile viewports. */ 51 | // // { 52 | // // name: 'Mobile Chrome', 53 | // // use: { ...devices['Pixel 5'] }, 54 | // // }, 55 | // // { 56 | // // name: 'Mobile Safari', 57 | // // use: { ...devices['iPhone 12'] }, 58 | // // }, 59 | 60 | // /* Test against branded browsers. */ 61 | // // { 62 | // // name: 'Microsoft Edge', 63 | // // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 64 | // // }, 65 | // // { 66 | // // name: 'Google Chrome', 67 | // // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, 68 | // // }, 69 | // ] 70 | 71 | // /* Run your local dev server before starting the tests */ 72 | // // webServer: { 73 | // // command: 'npm run start', 74 | // // url: 'http://127.0.0.1:3000', 75 | // // reuseExistingServer: !import.meta.env.CI, 76 | // // }, 77 | // }); 78 | -------------------------------------------------------------------------------- /prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /reactypeserverlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/reactypeserverlogo.png -------------------------------------------------------------------------------- /resources/ReadMe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/ReadMe.gif -------------------------------------------------------------------------------- /resources/annotations_tutorial_images/Annotation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/annotations_tutorial_images/Annotation.gif -------------------------------------------------------------------------------- /resources/annotations_tutorial_images/NoteButton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/annotations_tutorial_images/NoteButton.gif -------------------------------------------------------------------------------- /resources/canvasDemoV20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/canvasDemoV20.gif -------------------------------------------------------------------------------- /resources/canvas_tutorial_images/canvas1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/canvas_tutorial_images/canvas1.png -------------------------------------------------------------------------------- /resources/canvas_tutorial_images/drag1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/canvas_tutorial_images/drag1.gif -------------------------------------------------------------------------------- /resources/canvas_tutorial_images/drag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/canvas_tutorial_images/drag1.png -------------------------------------------------------------------------------- /resources/canvas_tutorial_images/undoRedo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/canvas_tutorial_images/undoRedo.gif -------------------------------------------------------------------------------- /resources/code_preview_images/CodePreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/code_preview_images/CodePreview.png -------------------------------------------------------------------------------- /resources/csseditor_tutorial_images/CSSEditorTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/csseditor_tutorial_images/CSSEditorTab.png -------------------------------------------------------------------------------- /resources/csseditor_tutorial_images/CopyPasteCSS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/csseditor_tutorial_images/CopyPasteCSS.gif -------------------------------------------------------------------------------- /resources/csseditor_tutorial_images/DirectCSSEdit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/csseditor_tutorial_images/DirectCSSEdit.gif -------------------------------------------------------------------------------- /resources/customizing_elements_images/BackgroundColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/BackgroundColor.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/CSS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/CSS.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/CodeChange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/CodeChange.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Display.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Flex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Flex.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Height.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Lighting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Lighting.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Resize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Resize.gif -------------------------------------------------------------------------------- /resources/customizing_elements_images/Resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Resize.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Theme.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/Width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/Width.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/linkState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/linkState.png -------------------------------------------------------------------------------- /resources/customizing_elements_images/text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/text.gif -------------------------------------------------------------------------------- /resources/customizing_elements_images/textState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/customizing_elements_images/textState.png -------------------------------------------------------------------------------- /resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/demo.gif -------------------------------------------------------------------------------- /resources/demo19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/demo19.gif -------------------------------------------------------------------------------- /resources/export_tests_images/export-new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/export_tests_images/export-new.gif -------------------------------------------------------------------------------- /resources/html_elements_tutorial_images/codeSnippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/html_elements_tutorial_images/codeSnippet.png -------------------------------------------------------------------------------- /resources/html_elements_tutorial_images/createNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/html_elements_tutorial_images/createNew.png -------------------------------------------------------------------------------- /resources/html_elements_tutorial_images/defaultElements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/html_elements_tutorial_images/defaultElements.png -------------------------------------------------------------------------------- /resources/html_elements_tutorial_images/newTag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/html_elements_tutorial_images/newTag.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/icon.png -------------------------------------------------------------------------------- /resources/marketplace_images/marketplace_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/marketplace_images/marketplace_avatar.png -------------------------------------------------------------------------------- /resources/marketplace_images/marketplace_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/marketplace_images/marketplace_image.png -------------------------------------------------------------------------------- /resources/pages_images/AddElements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/AddElements.png -------------------------------------------------------------------------------- /resources/pages_images/DeletePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/DeletePage.png -------------------------------------------------------------------------------- /resources/pages_images/Pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/Pages.png -------------------------------------------------------------------------------- /resources/pages_images/Pages1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/Pages1.png -------------------------------------------------------------------------------- /resources/pages_images/PagesPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/PagesPanel.png -------------------------------------------------------------------------------- /resources/pages_images/PagesSwapping.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/PagesSwapping.gif -------------------------------------------------------------------------------- /resources/pages_images/Toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/pages_images/Toggle.png -------------------------------------------------------------------------------- /resources/reactype17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/reactype17.png -------------------------------------------------------------------------------- /resources/reactype19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/reactype19.png -------------------------------------------------------------------------------- /resources/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/readme.png -------------------------------------------------------------------------------- /resources/reusable_components_tutorial_images/reusablecomponents1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/reusable_components_tutorial_images/reusablecomponents1.png -------------------------------------------------------------------------------- /resources/reusable_components_tutorial_images/reusablecomponents2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/reusable_components_tutorial_images/reusablecomponents2.png -------------------------------------------------------------------------------- /resources/reusable_components_tutorial_images/reusablecomponents3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/reusable_components_tutorial_images/reusablecomponents3.png -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links-canvas.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links-canvas.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links1.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links2.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links3.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links4.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links5.PNG -------------------------------------------------------------------------------- /resources/route_links_tutorial_images/links6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/route_links_tutorial_images/links6.PNG -------------------------------------------------------------------------------- /resources/state_tutorial_images/CodePreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/CodePreview.png -------------------------------------------------------------------------------- /resources/state_tutorial_images/CodePreview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/CodePreview2.png -------------------------------------------------------------------------------- /resources/state_tutorial_images/CreateState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/CreateState.png -------------------------------------------------------------------------------- /resources/state_tutorial_images/DeleteState.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/DeleteState.gif -------------------------------------------------------------------------------- /resources/state_tutorial_images/StateTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/StateTable.png -------------------------------------------------------------------------------- /resources/state_tutorial_images/delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/delete.gif -------------------------------------------------------------------------------- /resources/state_tutorial_images/display.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/display.gif -------------------------------------------------------------------------------- /resources/state_tutorial_images/fullStateManageTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/fullStateManageTab.png -------------------------------------------------------------------------------- /resources/state_tutorial_images/table1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/table1.gif -------------------------------------------------------------------------------- /resources/state_tutorial_images/table3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/state_tutorial_images/table3.gif -------------------------------------------------------------------------------- /resources/tree_tutorial_images/tree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/tree_tutorial_images/tree1.png -------------------------------------------------------------------------------- /resources/tree_tutorial_images/tree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/tree_tutorial_images/tree2.png -------------------------------------------------------------------------------- /resources/tree_tutorial_images/tree3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/tree_tutorial_images/tree3.png -------------------------------------------------------------------------------- /resources/tree_tutorial_images/tree4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/tree_tutorial_images/tree4.png -------------------------------------------------------------------------------- /resources/tree_tutorial_images/tree5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/tree_tutorial_images/tree5.png -------------------------------------------------------------------------------- /resources/v19 collab room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v19 collab room.png -------------------------------------------------------------------------------- /resources/v19filestructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v19filestructure.png -------------------------------------------------------------------------------- /resources/v20 collab room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v20 collab room.png -------------------------------------------------------------------------------- /resources/v20 empty canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v20 empty canvas.png -------------------------------------------------------------------------------- /resources/v21 File Structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v21 File Structure.png -------------------------------------------------------------------------------- /resources/v21 MUI Canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v21 MUI Canvas.png -------------------------------------------------------------------------------- /resources/v21 Preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v21 Preview.gif -------------------------------------------------------------------------------- /resources/v21 code preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v21 code preview.png -------------------------------------------------------------------------------- /resources/v22 MUI Canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v22 MUI Canvas.png -------------------------------------------------------------------------------- /resources/v22 Preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v22 Preview.gif -------------------------------------------------------------------------------- /resources/v22 code preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-source-labs/ReacType/778f660d11575f0d5d701b7ecf2c6ac7fe428856/resources/v22 code preview.png -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

ReacType Server

4 |

5 | 6 | **ReacType Server** is the backend complement to the visual React prototyping tool **ReacType**. It is built in **Node.js** with the **Express** framework linked to **MongoDB** to handle user authentication (personal accounts on our own database as well as through Github Oauth), sessions, and user project management. The server itself is officially deployed through Heroku, but you can host your own local environment to communicate with the database with this repo. 7 | 8 | **For future development teams**: If you wish to update the server and re-deploy through heroku, you will need to get the credentials from one of the last team members: 9 | 10 | - [Alex Yu](https://www.linkedin.com/in/alexjihunyu/) [@buddhajjigae](https://github.com/buddhajjigae) 11 | - [Daryl Foster](https://www.linkedin.com/in/darylfosterma/) [@MadinventorZero](https://github.com/MadinventorZero) 12 | - [Jonathan Calvo Ramirez](https://www.linkedin.com/in/jonathan-calvo/) [@jonocr](https://github.com/jonocr) 13 | - [Kevin Park](https://www.linkedin.com/in/xkevinpark/) [@xkevinpark](https://github.com/xkevinpark) 14 | - [William Yoon](https://www.linkedin.com/in/williamdyoon/) [@williamdyoon](https://github.com/williamdyoon) 15 | 16 | Redeployment should also be done with only the server subtree and not the entire repo. See this article about deploying just a subdirectory. 17 | 18 | If `npm` is your package manager, you just need to run the script `npm run dev` and it will start the server on `http://localhost:${DEV_PORT}` for your development environment. 19 | DEV_PORT can be defined in the config.js file on the root directory. 20 | You will also need to define your server address(MONGO_DB_DEV), github OAuth ID (GITHUB_CLIENT) & Secret (GITHUB_SECRET) in a dotenv file in the root directory as well. 21 | 22 | Endpoint testing is currently integrated with Jest and Supertest as well and can be run by `npm run test` or `npm run test:watch` for watch mode. 23 | -------------------------------------------------------------------------------- /server/controllers/cookieController.ts: -------------------------------------------------------------------------------- 1 | import { CookieController } from '../interfaces'; 2 | 3 | const cookieController: CookieController = { 4 | /** 5 | * Middleware function to set the 'ssid' and 'username' cookies. 6 | * 7 | * @callback SetSSIDCookieMiddleware 8 | * @param {object} req - The request object. 9 | * @param {object} res - The response object. 10 | * @param {Function} next - The next middleware function in the stack. 11 | * @returns {void} 12 | */ 13 | setSSIDCookie: (req, res, next): void => { 14 | // set cookie with key 'ssid' and value to user's id 15 | res.cookie('ssid', res.locals.id, { 16 | httpOnly: true, 17 | sameSite: 'none', 18 | secure: true 19 | //maxAge: 60 * 60 * 1000 * 24 //uncomment to set expiration of cookies, but make sure there is something in place to expire local storage info too 20 | }); 21 | 22 | res.cookie('username', res.locals.username, { 23 | httpOnly: true, 24 | sameSite: 'none', 25 | secure: true 26 | }); 27 | return next(); 28 | }, 29 | /** 30 | * Middleware function to delete cookies. 31 | * 32 | * @callback DeleteCookiesMiddleware 33 | * @param {object} req - The request object. 34 | * @param {object} res - The response object. 35 | * @param {Function} next - The next middleware function in the stack. 36 | * @returns {void} 37 | */ 38 | deleteCookies: (req, res, next): void => { 39 | res.clearCookie('ssid'); 40 | res.clearCookie('username'); 41 | res.clearCookie('connect.sid'); 42 | return next(); 43 | } 44 | }; 45 | 46 | export default cookieController; 47 | -------------------------------------------------------------------------------- /server/controllers/userStylesController.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { UserStylesController } from '../interfaces'; 4 | 5 | const userStylesController: UserStylesController = { 6 | /** 7 | * Middleware function to save CSS data to a file. 8 | * 9 | * @callback SaveCssFileMiddleware 10 | * @param {object} req - The request object. 11 | * @param {object} res - The response object. 12 | * @param {Function} next - The next middleware function in the stack. 13 | * @returns {void} This function does not return anything directly but calls the next middleware function. 14 | */ 15 | saveCssFile: (req, res, next): void => { 16 | const newText = req.body.data; 17 | fs.writeFile( 18 | path.join(__dirname, '../assets/renderDemo.css'), 19 | newText, 20 | 'utf-8', 21 | (err) => { 22 | if (err) throw err; 23 | next(); 24 | } 25 | ); 26 | } 27 | }; 28 | 29 | export default userStylesController; 30 | -------------------------------------------------------------------------------- /server/graphQL/schema/typeDefs.ts: -------------------------------------------------------------------------------- 1 | // const { gql } = require('apollo-server-express'); 2 | const { gql } = require('@apollo/client'); 3 | 4 | // Link to defining a schema in Apollo: 5 | // https://www.apollographql.com/docs/apollo-server/schema/schema/ 6 | // The schema specifies which queries and mutations are available for clients 7 | // to execute against your data graph. 8 | 9 | // NOTE: Project type does not return the detail of the project's components, 10 | // but info needed for the dashboard 11 | 12 | // line 15, returns type Project from line 29 13 | const Project = gql` 14 | type Mutation { 15 | addLike(projId: ID!, likes: Int!): Project 16 | makeCopy(projId: ID!, userId: ID!, username: String!): Project 17 | deleteProject(projId: ID!): Project 18 | publishProject(projId: ID!, published: Boolean!): Project 19 | addComment(projId: ID!, comment: String!, username: String!): Project 20 | } 21 | 22 | type Comment { 23 | id: ID! 24 | username: String! 25 | text: String! 26 | projectId: ID! 27 | } 28 | 29 | type Project { 30 | name: String! 31 | likes: Int 32 | published: Boolean! 33 | id: ID! 34 | userId: ID! 35 | username: String! 36 | createdAt: String 37 | comments: [Comment!] 38 | } 39 | 40 | type Query { 41 | getProject(projId: ID!): Project 42 | getAllProjects(userId: ID): [Project] 43 | } 44 | `; 45 | 46 | export default Project; 47 | -------------------------------------------------------------------------------- /server/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler, Request, Response, NextFunction } from 'express'; 2 | import { Document, NativeError } from 'mongoose'; 3 | 4 | export interface CookieController { 5 | setSSIDCookie: RequestHandler; 6 | deleteCookies: RequestHandler; 7 | } 8 | 9 | export interface ProjectController { 10 | saveProject: RequestHandler; 11 | getProjects: RequestHandler; 12 | deleteProject: RequestHandler; 13 | } 14 | 15 | export interface MarketplaceController { 16 | publishProject: RequestHandler; 17 | getPublishedProjects: RequestHandler; 18 | unpublishProject: RequestHandler; 19 | cloneProject: RequestHandler; 20 | } 21 | 22 | export interface RequestId extends Request { 23 | user: { 24 | id: string; 25 | }; 26 | } 27 | 28 | export interface SessionCookie extends Document { 29 | cookieId: string; 30 | } 31 | 32 | export interface SessionController { 33 | isLoggedIn: (req: RequestId, res: Response, next: NextFunction) => void; 34 | startSession: (req: RequestId, res: Response, next: NextFunction) => void; 35 | endSession: (req: RequestId, res: Response, next: NextFunction) => void; 36 | // gitHubResponse: (req: RequestId, res: Response, next: NextFunction) => void; 37 | // gitHubSendToken: (req: RequestId, res: Response, next: NextFunction) => void; 38 | // githubSession: (req: RequestId, res: Response, next: NextFunction) => void; 39 | } 40 | 41 | export interface newUserError extends NativeError { 42 | keyValue: { 43 | email: string; 44 | username: string; 45 | }; 46 | } 47 | 48 | export interface UserController { 49 | getUser: RequestHandler; 50 | createUser: RequestHandler; 51 | verifyUser: RequestHandler; 52 | updatePassword: RequestHandler; 53 | } 54 | 55 | export interface UserStylesController { 56 | saveCssFile: RequestHandler; 57 | } 58 | export interface UserDocument extends Document { 59 | password: string; 60 | } 61 | -------------------------------------------------------------------------------- /server/models/Oauth-model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from 'mongoose'; 2 | 3 | interface UserDocument extends Document { 4 | username?: string; 5 | githubId?: string; 6 | googleId?: string; 7 | } 8 | 9 | const userSchema = new mongoose.Schema({ 10 | username: { type: String }, 11 | githubId: { type: String }, // removed unique constraint because you can have null values. 12 | googleId: { type: String } // unique:true 13 | }); 14 | 15 | const User = mongoose.model('OauthUsers', userSchema); 16 | 17 | export default User; 18 | -------------------------------------------------------------------------------- /server/routers/auth.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const passport = require('passport'); 3 | import config from '../../config'; 4 | import { Request } from 'express'; 5 | import sessionController from '../controllers/sessionController'; 6 | 7 | // trying to add interface 8 | interface UserReq extends Request { 9 | user: { 10 | id: string; 11 | username: string; 12 | googleId: string; 13 | }; 14 | } 15 | 16 | const { API_BASE_URL2 } = config; 17 | const router = express.Router(); 18 | 19 | /** 20 | * Route handler for initiating GitHub OAuth authentication. Redirects the user to GitHub OAuth login page. 21 | */ 22 | router.get( 23 | '/github', 24 | passport.authenticate('github', { 25 | scope: ['profile'] 26 | }) 27 | ); 28 | 29 | /** 30 | * Route handler for handling GitHub OAuth callback. After successful authentication, 31 | * starts a session, sets session cookies, and redirects the user back to the specified base URL. 32 | * 33 | * @param {UserReq} req - The request object from Express extended with user information. 34 | * @param {express.Response} res - The response object from Express. 35 | */ 36 | router.get( 37 | '/github/callback', 38 | passport.authenticate('github'), 39 | sessionController.startSession, 40 | (req: UserReq, res) => { 41 | res.cookie('ssid', req.user.id, { 42 | httpOnly: true, 43 | sameSite: 'none', 44 | secure: true 45 | }); 46 | 47 | res.cookie('username', req.user.username, { 48 | httpOnly: true, 49 | sameSite: 'none', 50 | secure: true 51 | }); 52 | return res.redirect(API_BASE_URL2); 53 | } 54 | ); 55 | 56 | /** 57 | * Route handler for initiating Google OAuth authentication. Redirects the user to Google OAuth login page. 58 | */ 59 | router.get( 60 | '/google', 61 | passport.authenticate('google', { 62 | scope: ['profile'] 63 | }) 64 | ); 65 | 66 | /** 67 | * Route handler for handling Google OAuth callback. After successful authentication, 68 | * starts a session, sets session cookies, and redirects the user back to the specified base URL. 69 | * 70 | * @param {UserReq} req - The request object from Express extended with user information. 71 | * @param {express.Response} res - The response object from Express. 72 | */ 73 | router.get( 74 | '/google/callback', 75 | passport.authenticate('google'), 76 | sessionController.startSession, 77 | (req: UserReq, res) => { 78 | res.cookie('ssid', req.user.id, { 79 | httpOnly: true, 80 | sameSite: 'none', 81 | secure: true 82 | }); 83 | 84 | res.cookie('username', req.user.username, { 85 | httpOnly: true, 86 | sameSite: 'none', 87 | secure: true 88 | }); 89 | return res.redirect(API_BASE_URL2); 90 | } 91 | ); 92 | 93 | export default router; 94 | -------------------------------------------------------------------------------- /server/routers/stylesRouter.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response} from 'express'; 2 | import userStylesController from '../controllers/userStylesController'; 3 | const router = express.Router(); 4 | 5 | /** 6 | * Route handler for saving a new CSS file. Invokes the userStylesController to save the CSS file, 7 | * and sends a JSON response with status 200. 8 | * 9 | * @param {express.Request} req - The request object from Express. 10 | * @param {express.Response} res - The response object from Express. 11 | */ 12 | router.post('/save', userStylesController.saveCssFile, (req: Request, res: Response) => { 13 | res.status(200).json({}); 14 | }); 15 | export default router; 16 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases 3 | "extends": "ts-node/node16/tsconfig.json", 4 | 5 | "compilerOptions": { 6 | "noImplicitAny": false, 7 | "noImplicitThis": false, 8 | "strictNullChecks": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strictFunctionTypes": false, 11 | "esModuleInterop": true, 12 | "noEmitOnError": false, 13 | "moduleResolution": "Node16" // explicitly set moduleResolution to Node16 14 | }, 15 | // Most ts-node options can be specified here using their programmatic names. 16 | "ts-node": { 17 | // It is faster to skip typechecking. 18 | // Remove if you want ts-node to do typechecking. 19 | "transpileOnly": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/custom-aws-exports.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const awsmobile = { 4 | aws_project_region: import.meta.env.REACT_APP_AWS_PROJECT_REGION, 5 | aws_cognito_identity_pool_id: import.meta.env 6 | .REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID, 7 | aws_cognito_region: import.meta.env.REACT_APP_AWS_COGNITO_REGION, 8 | aws_user_pools_id: import.meta.env.REACT_APP_AWS_USER_POOLS_ID, 9 | aws_user_pools_web_client_id: import.meta.env 10 | .REACT_APP_AWS_USER_POOLS_WEB_CLIENT_ID, 11 | oauth: {}, 12 | aws_cognito_username_attributes: import.meta.env 13 | .REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES 14 | ? import.meta.env.REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES.split(',') 15 | : [], 16 | aws_cognito_social_providers: import.meta.env 17 | .REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS 18 | ? import.meta.env.REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS.split(',') 19 | : [], 20 | aws_cognito_signup_attributes: import.meta.env 21 | .REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES 22 | ? import.meta.env.REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES.split(',') 23 | : [], 24 | aws_cognito_mfa_configuration: import.meta.env 25 | .REACT_APP_AWS_COGNITO_MFA_CONFIGURATION, 26 | aws_cognito_mfa_types: import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES 27 | ? import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES.split(',') 28 | : [], 29 | aws_cognito_password_protection_settings: { 30 | passwordPolicyMinLength: import.meta.env 31 | .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYMINLENGTH, 32 | passwordPolicyCharacters: import.meta.env 33 | .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS 34 | ? import.meta.env.REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS.split( 35 | ',' 36 | ) 37 | : [] 38 | }, 39 | aws_cognito_verification_mechanisms: import.meta.env 40 | .REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS 41 | ? import.meta.env.REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS.split(',') 42 | : [], 43 | aws_user_files_s3_bucket: import.meta.env.REACT_APP_AWS_USER_FILES_S3_BUCKET, 44 | aws_user_files_s3_bucket_region: import.meta.env 45 | .REACT_APP_AWS_USER_FILES_S3_BUCKET_REGION 46 | }; 47 | 48 | export default awsmobile; 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "types": ["vite/client", "vite-plugin-svgr/client", "vitest/jsdom"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "outDir": "./app/dist/", 19 | "sourceMap": true, 20 | "noImplicitAny": false, 21 | "noImplicitThis": false, 22 | "strictNullChecks": false, 23 | "allowSyntheticDefaultImports": true, 24 | "strictFunctionTypes": false, 25 | "typeRoots": [ 26 | "node_modules/@types", 27 | "node_modules/@types/node", 28 | "@aws-amplify" 29 | ], 30 | "noEmitOnError": false 31 | }, 32 | // only compile ts files in this directory 33 | "include": [ 34 | "app/src/**/*", 35 | "app/src/interfaces/declarations.d.ts", 36 | "server/**/*" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "tslint.autoFixOnSave": true, 4 | "linterOptions": { 5 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], 9 | "jsx-boolean-value": false, 10 | "jsx-no-lambda": false, 11 | "jsx-no-multiline-js": false, 12 | "object-literal-sort-keys": false, 13 | "member-ordering": false, 14 | "no-console": false, 15 | "ordered-imports": false, 16 | "comment-format": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import svgr from 'vite-plugin-svgr'; 4 | import { visualizer } from 'rollup-plugin-visualizer'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | // This changes the out put dir from dist to build 9 | // comment this out if that isn't relevant for your project 10 | build: { 11 | outDir: 'build' 12 | }, 13 | assetsInclude: ['**/*.png'], 14 | server: { port: 8080 }, 15 | plugins: [ 16 | react(), 17 | svgr({ 18 | include: '**/*.svg', 19 | svgrOptions: { 20 | icon: true 21 | // ...svgr options (https://react-svgr.com/docs/options/) 22 | } 23 | }) 24 | ], 25 | optimizeDeps: { 26 | include: [ 27 | '@mui/material', 28 | '@mui/icons-material' // Assuming you use icons too 29 | // Any other specific parts of MUI or related dependencies 30 | ] 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from 'vitest/config'; 2 | import viteConfig from './vite.config.ts'; 3 | 4 | export default mergeConfig(viteConfig, defineConfig({ 5 | test: { 6 | include: ['./__tests__/**/*.test.[tj]s?(x)'], 7 | exclude: [ 8 | '**/node_modules/**', 9 | '**/dist/**', 10 | ], 11 | environment: 'jsdom', 12 | globals: true, 13 | environmentMatchGlobs: [ 14 | ['__tests__/**', 'jsdom'], 15 | ], 16 | }, 17 | })); 18 | --------------------------------------------------------------------------------