diff --git a/.babelrc b/.babelrc index f4175971b..753e120b5 100644 --- a/.babelrc +++ b/.babelrc @@ -1,20 +1,20 @@ -{ - "plugins": [ - [ - "module-resolver", - { - "cwd": "babelrc", - "alias": { - "Components": "./app/src/components", - "Containers": "./app/src/containers" - } - } - ] - ], - // presets are a set of of plug-ins - "presets": [ - ["@babel/preset-env", { "targets": { "node": "current" } }], - "@babel/preset-typescript", - "@babel/preset-react" - ] -} +{ + "plugins": [ + [ + "module-resolver", + { + "cwd": "babelrc", + "alias": { + "Components": "./app/src/components", + "Containers": "./app/src/containers" + } + } + ] + ], + // presets are a set of of plug-ins + "presets": [ + ["@babel/preset-env", { "targets": { "node": "current" } }], + "@babel/preset-typescript", + "@babel/preset-react" + ] +} diff --git a/.dockerignore b/.dockerignore index 8361b6aa5..b6f6fb15d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,2 @@ -node_modules +node_modules app/electron \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 9b85387af..c6f33686c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,22 +1,22 @@ -{ - "extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "airbnb-base"], - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": ["import", "react", "jest", "jsx-a11y", "babel"], - "parser": "@babel/eslint-parser", - "env": { - "browser": true, - "node": true, - "es6": true, - "jest": true - }, - "rules": { - "class-methods-use-this": "off", - "linebreak-style": 0 - } -} +{ + "extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "airbnb-base"], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["import", "react", "jest", "jsx-a11y", "babel"], + "parser": "@babel/eslint-parser", + "env": { + "browser": true, + "node": true, + "es6": true, + "jest": true + }, + "rules": { + "class-methods-use-this": "off", + "linebreak-style": 0 + } +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c8972cd16..53b17ef25 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,38 +1,38 @@ -# Description - -Please describe the issue of the pull request and the changes - - - -## Type of Change - -Please check the options that apply - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update - -# How Has the Changes Been Tested? - - - -# Checklist: - -- [ ] My code follows the style guidelines of this project -- [ ] Changes included in this pull request covers minimal topic -- [ ] I have performed a self-review of my code -- [ ] I have commented my code properly, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes -- [ ] Any dependent changes have been merged and published in downstream modules +# Description + +Please describe the issue of the pull request and the changes + + + +## Type of Change + +Please check the options that apply + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# How Has the Changes Been Tested? + + + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] Changes included in this pull request covers minimal topic +- [ ] I have performed a self-review of my code +- [ ] I have commented my code properly, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ebb780741..06a720c72 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,40 +1,40 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the master branch - push: - branches: [dev] - pull_request: - branches: [dev] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - with: - node-version: 18.13.0 - - # Runs a single command using the runners shell - - name: Install dependencies - run: npm install - - name: Run all tests - run: npm run test --bail - - # Runs a set of commands using the runners shell - - name: Run a multi-line script - run: | - echo Add other actions to build, - echo test, and deploy your project. +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [dev] + pull_request: + branches: [dev] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + with: + node-version: 18.13.0 + + # Runs a single command using the runners shell + - name: Install dependencies + run: npm install + - name: Run all tests + run: npm run test --bail + + # Runs a set of commands using the runners shell + - name: Run a multi-line script + run: | + echo Add other actions to build, + echo test, and deploy your project. diff --git a/.gitignore b/.gitignore index 45c80f88a..d0e8929bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,531 +1,531 @@ -# Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn -yarn.lock -.prettierrc.json -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ -dist/ -build/ -release-builds/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -#!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!# - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -#VSCode -.vscode/ -reactype.code-workspace - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# DMG File -reactype.dmg -installers/ - -# for server key and ssl certificate generation -server/domains.ext -server/localhost.crt -server/localhost.csr -server/localhost.key -server/RootCA.crt -server/rootCA.key -server/rootCA.pem -server/RootCA.srl - - - -# End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn - -/test-results/ -/playwright-report/ -/playwright/.cache/ - -#amplify-do-not-edit-begin -amplify/\#current-cloud-backend -amplify/.config/local-* -amplify/logs -amplify/mock-data -amplify/mock-api-resources -amplify/backend/amplify-meta.json -amplify/backend/.temp -build/ -dist/ -node_modules/ -aws-exports.js -awsconfiguration.json -amplifyconfiguration.json -amplifyconfiguration.dart -amplify-build-config.json -amplify-gradle-config.json -amplifytools.xcconfig -.secret-* -**.sample -#amplify-do-not-edit-end - -/amplify/team-provider-info.json - -#TypeScript coverage report +# Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn +yarn.lock +.prettierrc.json +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ +dist/ +build/ +release-builds/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +#!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!# + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +#VSCode +.vscode/ +reactype.code-workspace + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# DMG File +reactype.dmg +installers/ + +# for server key and ssl certificate generation +server/domains.ext +server/localhost.crt +server/localhost.csr +server/localhost.key +server/RootCA.crt +server/rootCA.key +server/rootCA.pem +server/RootCA.srl + + + +# End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +#amplify-do-not-edit-begin +amplify/\#current-cloud-backend +amplify/.config/local-* +amplify/logs +amplify/mock-data +amplify/mock-api-resources +amplify/backend/amplify-meta.json +amplify/backend/.temp +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplifyconfiguration.dart +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig +.secret-* +**.sample +#amplify-do-not-edit-end + +/amplify/team-provider-info.json + +#TypeScript coverage report coverage-ts/ \ No newline at end of file diff --git a/.npmignore b/.npmignore index b42c82ad7..962999558 100644 --- a/.npmignore +++ b/.npmignore @@ -1,485 +1,485 @@ -# Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride -build/ - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ -dist/ -release-builds/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -#!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!# - -### VisualStudio ### -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# DMG File -reactype.dmg -installers/ -assets/ -.git/ - +# Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride +build/ + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ +dist/ +release-builds/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +#!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!# + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# DMG File +reactype.dmg +installers/ +assets/ +.git/ + # End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 276a47a73..d06320fb8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ -{ - "printWidth": 80, - "singleQuote": true, - "trailingComma": "none" -} +{ + "printWidth": 80, + "singleQuote": true, + "trailingComma": "none" +} diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 1fb36e28c..2af2092c0 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,389 +1,389 @@ -

- -

ReacType Change Log

-

- -## Version 20.0.0 Changes - -### Changes: - -- **Developer Improvement:** - - Migrated from Webpack to Vite, improving HMR times drastically - - Deployed app using Heroku instead of AWS decreasing time to deployment -- **User Features:** - - **Collaboration Room:** - - Implemented live video, audio, and text functionality using socket.IO - - Added authentication and error handling to joining existing rooms - - **UI updates to enhance user experience:** - - In addition to drag to add, users are now able to click to add - - Updated left panel to include user information and settings - - Added scroll and zoom buttons to canvas. Scroll now automatically scrolls to bottom once enough elements are added - - Updated UI design to reflect a more modern look -- **Bugs Fixed:** - - Canvas - All appropriate elements can now be nested - Nested Elements in the code preview now accurately reflect nested elements. They can also be dragged. - - Bottom Panel - Now opens by click instead of hover - - Users can now delete elements without first clicking it and then the X. This applies to the nested components as well. -- **Landing Page:** - - Revamped entire landing page for a more modern look - -### Recommendations for Future Enhancements: - -- Bug fix for market place preview display -- Fix bottom panel to only close upon clicking the icon, and not anywhere else -- Populate settings tab in the left panel with additional functionality -- Allow users to modify code dynamically in the code preview and reflect visual componenets in real time -- Add zoom in and zoom out / scroll functionality to code preview and component tree -- Convert from 95% to 100% typescript -- Add more functionality to the nav bar -- List all active rooms to join -- Clean up unnecessary code / comments and deprecated libraries -- a tags which are nested do not display accurate code in code preview -- Eliminate all Webpack associated files/folders/dependencies/etc... now that we run on Vite -- Remove the many deprecated dependencies -- Add additional features to the live chat (Links, reactions, raise hand feature etc) -- Allow live chat to be a popup and draggable outside of the app -- Implement MUI/ShadcnUI in addition to standard html elements on left panel so that users are able to start off with pre styled elements -- Make the app mobile responsive. Right now it does not work/look good on mobile -- We had to deploy via Heroku due to time limitations and Vite. We would recommend going back to AWS with dockerized containers. -- Light/Dark mode in the left settings tab -- Update links in the footer of the landing page - -## Version 19.0.0 Changes - -### Changes: - -- **Developer Improvement:** - - Typescript conversion continued and now sits at ~95% -- **User Features:** - - **Collaboration Room:** - - **Bug Fixes:** - - Debug “Leave Room” functionality removing username from the users list - - Debug “Join Room” functionality so the current canvas does not reset upon new user joining collaboration - - Debug Code Preview button that sent error if toggled more than once and does not force toggled view to other users in the room - - Collaboration room is now ready for release (v1) - - Implemented live cursor tracking with on/off function where multiple users can choose to see the other users mouse cursors in real-time in the same canvas. - - Added keydown functionality to “Join Room” by triggering button click on keyboard “Enter” - - Reconfigured web socket initiation to force new connection when joining room - - Refactored the way changes were being passed to other users in the collaboration room - - Significantly reduces the amount of data being passed among users by passing only the payload for each individual action, triggering singular updates for other users in the collaboration environment - - Added Event Emitters for each action that updates canvas - - Created a websocket service layer to maintain a single socket instance throughout the app - - **User List:** - - Displays the username and mouse cursor of all connected users in a particular room with a specific color scheme - - UI updated to enhance user experience: - - Rendered MUI Icons in HTML Element Panel - - Redesigned drag-n-drop to be more intuitive and professionalize application design. - - Updated styling to overall style and theme to maintain consistency across the application - - Removed Tailwind and CSS save buttons in Customization panel for cleaner UI and drying up repetitive functionality - -### Recommendations for Future Enhancements: - -- Fix Undo & Redo functionality. Undo & Redo buttons on the customization page not functioning as expected. -- Update Electron for desktop application use. Resolve electron app functionality to coincide with web app functionality. -- Add Change Log/View Edit History feature in app -- v.17 recommendations regarding the Marketplace are still undeveloped. -- Expand testing coverage. Continue fixing old tests which rely on outdated dependencies, and implementing new tests. -- Continue Typescript conversion. Consider toggling noImplicitAny to find all 'any' cases that can be addressed. -- Continue cleanup of outdated and unused code and files. -- Future teams could look into data structures for scaling on the server side of the app to improve data transmitting and multiple server functionality. -- Continue modularizing code. Many large, unwieldy files that should be broken up into more modular components still exist. -- Fix the reset of context manager and state manager when a user leaves the room. -- Collaboration room: - - Allow for given HTML components to be nested into custom created components - - Collaboration Room feature can be further scaled with AWS servers and clients for better experiences. The feature currently is limited to access with only 1 AWS cloud server. - - True real-time rendering so users can see components as they're being dragged onto the canvas, rather than only when they're placed. - - List of active rooms so users can simply pick one to join. Will likely be paired with a password feature for security, so only users with the proper credentials can join a particular room. - - Chat Feature in Collaboration Room - - Currently, the live tracking cursor is rendered based on the users username/nickname. If multiple users create the same username/nickname, the most recent username/nickname creator will override the former. Possible solution to this issue could be to store cursor with the socket id rather than username/nickname. " - -**Version 18.0.0 Changes** - -Changes:
- -- Developer Improvements: - - Typescript conversion continued and now sits at ~90% - - Dev Bug Fixes: - - Deleted ts-coverage files and added folder to git.ignore so TS conversion status is properly reflected on the GitHub repository. - - Cleaned up outdated code and removed multiple unused and duplicate files, particularly those related to the now-obsolete Dark Mode functionality and some other lingering code from the v.17 migration. - - Modularity: - - Migrated large portions of RoomContainer functionality into smaller components to improve the reusability of code. - - Created more interface types for reusability to multiple parts of the applications. -- User Features: - - Collaboration Room: - - Implemented room functionality where multiple users can see and interact with the same canvas state in real time. - - Dynamically handles the host logic of the collab room, where the oldest connected client is the one serving the room's state. - - Fixed backend web socket connections with the clients, allowing full duplex connections between multiple clients and servers. - - Note: The collaboration room is in beta - - User List: - - List that displays all connected users in a particular room. - - Dynamically updates when users join or leave a room. - - Automatically updates the new host in the room to the next oldest user. - - Join/Nickname Button: - - Allows users to specify which room to join, and what name to display upon joining the room. - - The button only shows when the user is not connected to the room, requiring both fields to be filled out. - -Recommendations for Future Enhancements:
- -- Chat functionality so users in the same room can discuss their projects. -- List of active rooms so users can simply pick one to join. Will likely be paired with a password feature for security, so only users with the proper credentials can join a particular room. -- True real-time rendering so users can see components as they're being dragged onto the canvas, rather than only when they're placed. -- Optimize performance of room state updates -- v.17 recommendations regarding the Marketplace are still undeveloped. -- Solve residual bugs. Undo & Redo buttons on the customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Persistent Redux error that causes page to rerender more often than necessary. -- Resolve electron app functionality to coincide with web app functionality. -- For the state manager option in the data table there is a MuiData-menu that is not visible when clicking it and after the filter option is clicked it creates a white space in the bottom of the page. -- Expand testing coverage. Continue fixing old tests which rely on outdated dependencies, and implementing new tests. -- Continue modularizing code. Many large, unwieldy files that should be broken up into more modular components still exist. -- Continue Typescript conversion. Consider toggling noImplicitAny to find all 'any' cases that can be addressed. -- Continue cleanup of outdated and unused code and files. -- Collaboration feature still needs to be improved for scalability with AWS servers and clients for better experiences. The feature currently is limited to access with only 1 AWS cloud server. -- Future teams could look into data structures for scaling on the server side of the app to improve data transmitting and multiple server functionality. - -**Version 17.0.0 Changes** - -Changes:
- -- Developer Improvements: - - Testing Coverage: - - Version 17 added testing for the added marketplace-related components - - Testing coverage sits at ~60% - - Typescript continued and now sits at ~80% - - Dev Bug Fixes: - - Additional logic added for edge cases in inputs for state manager (passing in non-Arrays/non-Objects as Array type and Object type). - - Fixed issue with the bottom panel not dragging or sticking to the mouse when the mouse is over the demo render iframe - - Cleaned up hundreds of lines of outdated code and archived multiple unused and duplicate files - - OAuth now linked to standalone Gmail and GitHub accounts -- User Features: - - UI updated with a modern style for a better developer experience - - Added many user feedback alerts for a better experience including alerts for when projects are published, cloned, deleted, HTML custom tags are created, context created, or custom components created. - - Built a specific buttons menu that individually displays the HTML elements, reusable components created, and join room option. - - Redesigned the state manager panel option to be readable and functional. - - Drop down menu now closes only when the user clicks outside of the menu - - Marketplace: - - Implemented a dedicated area for developers to share their projects - - Routing handled by React Router - - Projects can also be cloned to the user's account to be used and edited with the addition of a button - - Added search functionality to search by username and project name - - Included a separate section in the Saved Projects and Delete Projects modal in the Manage Project menu for cloned projects from the Marketplace - - Publish/Unpublish Button: - - Publish feature on the web app allows users to publish their saved project files into the Marketplace from the main app page - - Dynamically switches between publish/unpublish depending on whether the loaded project is in the Marketplace - -Recommendations for Future Enhancements:
- -- Add a comment section and description section for each published project -- Consider maybe a way for users to pull individual components from one project into another -- Use localforage or other methods to store unsaved projects either on logout or accidental closure of the browser, so that when the user opens the browser again it is still there. -- Continue expanding testing coverage. Improve testing by adding additional unit tests, expanding end-to-end testing, and introducing integration testing. -- Continue quality Typescript conversion. Continue to fix type errors within component files. -- Modularize appStateSlice file. Further modularization is needed for readability and maintainability. -- Solve residual bugs. Undo & Redo buttons on customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Resolve electron app functionality to coincide with web app functionality. -- Take a look at the join room functionality using web sockets in order to allow users to collaborate on the same project at the same time. -- For the state manager option in the data table there is a MuiData-menu that is not visible when clicking it and after the filter option is clicked it creates a white space in the bottom of the page. -- Continue code cleanup. Continue cleanup of outdated and unused code and files - -**Version 16.0.0 Changes** - -Changes:
- -- Developer Improvements: - - Testing Coverage: - - Version 16 introduces end-to-end testing with Playwright and adds additional unit testing with React Testing Library. - - Testing coverage has now doubled since version 15, and now sits at just over 50% coverage. - - Transitioned away from Enzyme to maintain consistency with RTL and Jest. - - Typescript Conversion: - - Upped typescript coverage from 30% to 80%. - - Fixed multiple type errors in component files. - - Added CI pipeline for testing: - - Transitioned away from Travis CI to Github Actions for improved CI pipeline. Github Actions will now run all tests upon each pull request to dev. - - Updated OAuth and Sign In Features: - - Sign in feature now connected to the latest database version. - - Fixed bug that allowed only one OAuth user to sign in at a time. - - Github OAuth is now connected to Adam Vanek. - - Dev Bug Fixes: - - Debugged ‘worker error’ on code preview & style editor by refactoring Ace-Build components. - - Additional logic added for edge cases in inputs for context manager, state manager, and signup features. - - Cleaned up hundreds of lines of outdated code and deleted multiple unused and duplicate files - - Dependency Updates: - - All previously outdated dependencies are now updated. Time it takes for the app to bundle in dev is now cut in half. -- User Features: - - Export Button: - - Export feature on the web app now allows users to download the current project as a zip file with modularized component folder, html, and css file included. - - Export feature is now available to all users including guests. - - CSS Live Rendering: - - CSS Editor changes now rendered visually in the demo page on save. - - UI Changes: - - Fixed multiple contrast issues with white text displaying on white background in State Manger Display tab tables, state management tables, table menu dropdowns, Context Manager tables, and Context Manager display. - - Adjusted context manager interface for improved UX when creating context and saving key/value pairs. - - Fixed border styling within modals and error messages that were cutting off inputs on focus. - - Added save button to customization tab. - - Bug Fixes: - - Manage project features for registered users now successfully saves, opens, and deletes projects. - - State Manager now successfully deletes state from parent components. - - Context Manager display chart renders correctly. - - CSS Editor contents now persist after rerender. - -Recommendations for Future Enhancements:
- -- Refactor away from MUI. MUI is very opinionated and while creating components with it is easy it leaves a lot to be desired. Dark Mode also needs to be improved as color contrast is very low. -- Continue expanding testing coverage. Improve testing by adding additional unit tests, expanding end-to-end testing, and introducing integration testing. -- Continue quality Typescript conversion. Continue to fix type errors within component files. -- Modularize appStateSlice file. Further modularization is needed for readability and maintainability. -- Solve residual bugs. Undo & Redo buttons on customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Resolve electron app functionality to coincide with web app functionality. -- Continue code cleanup. Continue cleanup of outdated and unused code and files - -**Version 15.0.0 Changes** - -Changes:
- -- Developer Improvements: - - Redux Toolkit: - - Migrated state from a combination of useReducer/useContext and Redux to only using Redux Toolkit. This is the recommended modern approach to handling large state management in this sort of application. Enhances the developer experience by enabling the use of the Redux Devtools to debug, and see state/actions in real-time. - - Dependency Updates - - New developers can easily npm install without having to use an older version of node or using --legacy-peer-deps - - Updated to modern versions to take advantage of newer features -- User Features: - - Websockets: - - Users can now join rooms to collaborate in realtime - - Tailwind CSS: - - In the customization panel users can now choose between inline CSS and Tailwind. These changes are reflected in the live code preview. - - OAuth: - - Users may now log in using OAuth which enhances security, and makes sign in a breeze. - - Deployed Website: - - Containerized and deployed a working version of the application. Instead of having to download an application users may now interact live. - -Recommendations for Future Enhancements:
- -- Continue working on State Management. There are some changes that can be made to make the application cleaner. Right now the appStateSlice is a large file which houses a lot of the reducer functions. We believe there is a way to further modularize this to make it simpler to read, and iterate upon in the future. -- Convert to using Vite. While developing we ran into issues with webpack taking a long time to reflect changes. Vite is lightweight and enhances the developer experience. -- Expand Testing Coverage. Making a large move of state management made a lot of the testing innefective since it was based upon old ways. -- Refactor away from MUI. MUI is very opinionated and while creating components with it is easy it leaves a lot to be desired. -- Residual Bugs. While migrating state there are a few lingering bugs within the application. This process should be easier now with Redux Devtools availability, but we did not have time to go through every action and conduct thorough testing. - -**Version 14.0.0 Changes** - -Changes:
- -- Added functionality & improvements: - - Event listeners: - - Added ability to assign event listeners to elements in the bottom customization tab - - Can name the function on the event or use the default name provided - - Updates reflected in the code preview render - - Live code preview: Bottom tool tabs code preview box updates immediately and automatically to reflect the latest changes in state - - Converted the annotation button into a delete button on the canvas elements and reusable components - - Code preview render: The formatting for generated code has been corrected for improved readability -- Major UI changes: - - Left panel: - - Only display when mouse hovered over - - When extended, floats in front of the canvas without affecting the main window formatting - - Bottom panel - - Retractable feature added - - Has internal scroll ability in the tabs - - Resized functionality is stable - - Added indicator tabs to each signifying to the user their presence - - Canvas container (upper left): - - Changed the formatting to a center column with readable size and label orientation - - Standardized the size of components and rate of growth when nesting - - Tutorial: - - Users can now reference tutorials in split-screen mode without the canvas being auto-cleared when going back and forth from the tutorial -- Bug fixes: - - Reusable component: The drag-and-drop feature for reusable components is now functioning smoothly and without bugs - -Recommendations for Future Enhancements:
- -- Add function content in the current event listeners' function skeleton. -- The code output formatting in generateCode.ts is currently difficult to read, and could be improved for better readability. -- Currently, the project uses two sets of state management tools: useReducer/useContext and Redux. useReducer/useContext is used for handling the customization state, and Redux for managing the code preview, context manager, and dark mode reducer state. However, there seems to be some confusion around how to integrate these two tools effectively. For instance, both tools are used for managing the code preview state, and changing the useReducer/useContext state would replace the corresponding redux state. Need to clean up the logic and find a solution to solve this issue. -- Some of the files structure is not accurate (e.g., customizationPanel.tsx is in the containers folder instead of the bottom folder), need to rearrange the file hierarchy. -- Update packages and resolve package dependency issues. - -**Version 13.0.0 Changes** - -New Functionality:
- -- Manage state locally: Users can now manage state dynamically within nested components using React Hooks within the state manager tab. -- Add/delete props: For a selected component, users can see a list of available props from the parent, add props, and delete props in case they are not - required later on. -- State/props flow: If state or props are deleted upstream, it will automatically update the state for its children components. -- Visualize state/props flow: Within the display sub-tab of the state manager tab, users can visualize an interactive tree diagram depicting the state initialized in the current component and passed down props from the parent component. - -Enhancements:
- -- Live code preview: Live rendering of code based on any changes in the state and dragging and nesting of components. -- Next.js & Gatsby compatibility: New state manager tab is now compatible with next.js and Gatsby. -- Tutorial: Tutorial is functional and has the latest guides to navigate through the newly added state management tab. - -Deployment Updates:
- -- Electron app is now available for Windows users. -- Web based version of the app is available on Heroku. - -Bug Fixes:
- -- User dashboard: The dashboard works now and shows private and shared projects with the ability for users to drop comments. -- Login/logout: Users can now signup/login/logout now on both development and production environments. -- Manage Projects: Github authenticated users are now able to create and save projects. -- Customization: Use State works as expected now within HTML elements. - -What’s next:
- -- Adding on click functionality within components. Goal: Make a fully functional tic-tac-toe app. -- Incorporating material ui into the components so that exported app has visually appealing components. -- Enabling auto save functionality when dragging and dropping components, and amending component state. -- Allowing users to click and access projects within the dashboard for review. -- Adding more integration and E2E testing. -- Fixing bugs in the heroku (web based) deployment: login/logout, GitHub oauth etc. -- Enabling google oauth in all environments. -- Packaging electron app for Linux users. -- Conversion from monolithic to micro services for better scaling in the future. - -**Version 12.0.0 Changes** - -- Context Visualizer: You can now visually see what component is consuming which context. As you click on the interactive tree, the component assigned to the context will be revealed. -- React 18: Updated to React 18 -- Export Feature: Created an exportable context file, integrated with original codebase. Ready to go code: Added boilerplate codes to components based on which contexts they are consuming. - -**A note to future contributors** - -Attempted to implement Facebook and Google OAuth via passport but as of Electron’s current version, neither of them not compatible with electron. - -**Version 11.0.0 Changes:** - -- Added Next.js functionality - - Next.js projects will generate the right code needed for exporting a Next.js application - - Link & Image elements have been added - - Link components are able to couple with a page to enable SSR - - Next Link components have a drop down menu to quickly and easily link pages - - Current canvas can be saved as a page to be coupled with a Next.js Link element - - Files are exported with the appropriate Next.js imports and structure -- Added Redux and began migrating some state over for ease of development (debugging & readability) -- Fixed bug causing electron to crash when closing the window rather than going to file > exit -- Fixed bug causing app to crash when project was changed to either Next.js or Gatsby.js -- Fixed GitHub OAuth - - added Passport.js & Passport-Github libraries for strategies which takes care of all the credential exchanges and session information - - linked electron front end to talk to backend to exchange credentials -- Fixed code preview not displaying properly -- Fixed demo render preview so that changes in the canvas appears instantly - - any links in the demo render preview can now be clicked on and it will take you to its related page -- Properties of each component now persist in the customization tab -- Fixed dark mode not syncing properly across pages - -**Version 11.0.0 Stretch Features:** - -- Move more state away from the react hook & context API and into the Redux store - - This will be very time consuming but will make implementing new features much easier - - Highly suggest you read Redux documentation on best practices before diving into this - - This will improve performance by reducing the amount of unneccessary re-render. The context API causes certain pieces of state to be needlessly coupled - - Debugging is much easier by the use of Redux dev tools which allow time travel debugging - - Code will be easier to read and thus data flow will be easier to visualize - - Don't move **everything** onto Redux. ie: Material UI uses the context API to handle theme changes -- Enable remote work similar to vscode's live share - - Tried to implement peer to peer communication via webRTC with redux swarmlog but was not successful - - Look into using websockets - - Think about security. What features needs to be implemented for secure sharing? - - Transfer actions through websockets via Redux middleware (Thunk)? -- Save project (state) onto local storage for guests -- Redesign UI to be more flexible - - Read material ui docs for best practices. - - creation panel should be redesigned. Its react component structure is too fragmented. -- Add missing Next.js features - - Image components need sizing & loading options to capitalize on Next.js' Image optimization +

+ +

ReacType Change Log

+

+ +## Version 20.0.0 Changes + +### Changes: + +- **Developer Improvement:** + - Migrated from Webpack to Vite, improving HMR times drastically + - Deployed app using Heroku instead of AWS decreasing time to deployment +- **User Features:** + - **Collaboration Room:** + - Implemented live video, audio, and text functionality using socket.IO + - Added authentication and error handling to joining existing rooms + - **UI updates to enhance user experience:** + - In addition to drag to add, users are now able to click to add + - Updated left panel to include user information and settings + - Added scroll and zoom buttons to canvas. Scroll now automatically scrolls to bottom once enough elements are added + - Updated UI design to reflect a more modern look +- **Bugs Fixed:** + - Canvas - All appropriate elements can now be nested - Nested Elements in the code preview now accurately reflect nested elements. They can also be dragged. + - Bottom Panel - Now opens by click instead of hover + - Users can now delete elements without first clicking it and then the X. This applies to the nested components as well. +- **Landing Page:** + - Revamped entire landing page for a more modern look + +### Recommendations for Future Enhancements: + +- Bug fix for market place preview display +- Fix bottom panel to only close upon clicking the icon, and not anywhere else +- Populate settings tab in the left panel with additional functionality +- Allow users to modify code dynamically in the code preview and reflect visual componenets in real time +- Add zoom in and zoom out / scroll functionality to code preview and component tree +- Convert from 95% to 100% typescript +- Add more functionality to the nav bar +- List all active rooms to join +- Clean up unnecessary code / comments and deprecated libraries +- a tags which are nested do not display accurate code in code preview +- Eliminate all Webpack associated files/folders/dependencies/etc... now that we run on Vite +- Remove the many deprecated dependencies +- Add additional features to the live chat (Links, reactions, raise hand feature etc) +- Allow live chat to be a popup and draggable outside of the app +- Implement MUI/ShadcnUI in addition to standard html elements on left panel so that users are able to start off with pre styled elements +- Make the app mobile responsive. Right now it does not work/look good on mobile +- We had to deploy via Heroku due to time limitations and Vite. We would recommend going back to AWS with dockerized containers. +- Light/Dark mode in the left settings tab +- Update links in the footer of the landing page + +## Version 19.0.0 Changes + +### Changes: + +- **Developer Improvement:** + - Typescript conversion continued and now sits at ~95% +- **User Features:** + - **Collaboration Room:** + - **Bug Fixes:** + - Debug “Leave Room” functionality removing username from the users list + - Debug “Join Room” functionality so the current canvas does not reset upon new user joining collaboration + - Debug Code Preview button that sent error if toggled more than once and does not force toggled view to other users in the room + - Collaboration room is now ready for release (v1) + - Implemented live cursor tracking with on/off function where multiple users can choose to see the other users mouse cursors in real-time in the same canvas. + - Added keydown functionality to “Join Room” by triggering button click on keyboard “Enter” + - Reconfigured web socket initiation to force new connection when joining room + - Refactored the way changes were being passed to other users in the collaboration room + - Significantly reduces the amount of data being passed among users by passing only the payload for each individual action, triggering singular updates for other users in the collaboration environment + - Added Event Emitters for each action that updates canvas + - Created a websocket service layer to maintain a single socket instance throughout the app + - **User List:** + - Displays the username and mouse cursor of all connected users in a particular room with a specific color scheme + - UI updated to enhance user experience: + - Rendered MUI Icons in HTML Element Panel + - Redesigned drag-n-drop to be more intuitive and professionalize application design. + - Updated styling to overall style and theme to maintain consistency across the application + - Removed Tailwind and CSS save buttons in Customization panel for cleaner UI and drying up repetitive functionality + +### Recommendations for Future Enhancements: + +- Fix Undo & Redo functionality. Undo & Redo buttons on the customization page not functioning as expected. +- Update Electron for desktop application use. Resolve electron app functionality to coincide with web app functionality. +- Add Change Log/View Edit History feature in app +- v.17 recommendations regarding the Marketplace are still undeveloped. +- Expand testing coverage. Continue fixing old tests which rely on outdated dependencies, and implementing new tests. +- Continue Typescript conversion. Consider toggling noImplicitAny to find all 'any' cases that can be addressed. +- Continue cleanup of outdated and unused code and files. +- Future teams could look into data structures for scaling on the server side of the app to improve data transmitting and multiple server functionality. +- Continue modularizing code. Many large, unwieldy files that should be broken up into more modular components still exist. +- Fix the reset of context manager and state manager when a user leaves the room. +- Collaboration room: + - Allow for given HTML components to be nested into custom created components + - Collaboration Room feature can be further scaled with AWS servers and clients for better experiences. The feature currently is limited to access with only 1 AWS cloud server. + - True real-time rendering so users can see components as they're being dragged onto the canvas, rather than only when they're placed. + - List of active rooms so users can simply pick one to join. Will likely be paired with a password feature for security, so only users with the proper credentials can join a particular room. + - Chat Feature in Collaboration Room + - Currently, the live tracking cursor is rendered based on the users username/nickname. If multiple users create the same username/nickname, the most recent username/nickname creator will override the former. Possible solution to this issue could be to store cursor with the socket id rather than username/nickname. " + +**Version 18.0.0 Changes** + +Changes:
+ +- Developer Improvements: + - Typescript conversion continued and now sits at ~90% + - Dev Bug Fixes: + - Deleted ts-coverage files and added folder to git.ignore so TS conversion status is properly reflected on the GitHub repository. + - Cleaned up outdated code and removed multiple unused and duplicate files, particularly those related to the now-obsolete Dark Mode functionality and some other lingering code from the v.17 migration. + - Modularity: + - Migrated large portions of RoomContainer functionality into smaller components to improve the reusability of code. + - Created more interface types for reusability to multiple parts of the applications. +- User Features: + - Collaboration Room: + - Implemented room functionality where multiple users can see and interact with the same canvas state in real time. + - Dynamically handles the host logic of the collab room, where the oldest connected client is the one serving the room's state. + - Fixed backend web socket connections with the clients, allowing full duplex connections between multiple clients and servers. + - Note: The collaboration room is in beta + - User List: + - List that displays all connected users in a particular room. + - Dynamically updates when users join or leave a room. + - Automatically updates the new host in the room to the next oldest user. + - Join/Nickname Button: + - Allows users to specify which room to join, and what name to display upon joining the room. + - The button only shows when the user is not connected to the room, requiring both fields to be filled out. + +Recommendations for Future Enhancements:
+ +- Chat functionality so users in the same room can discuss their projects. +- List of active rooms so users can simply pick one to join. Will likely be paired with a password feature for security, so only users with the proper credentials can join a particular room. +- True real-time rendering so users can see components as they're being dragged onto the canvas, rather than only when they're placed. +- Optimize performance of room state updates +- v.17 recommendations regarding the Marketplace are still undeveloped. +- Solve residual bugs. Undo & Redo buttons on the customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Persistent Redux error that causes page to rerender more often than necessary. +- Resolve electron app functionality to coincide with web app functionality. +- For the state manager option in the data table there is a MuiData-menu that is not visible when clicking it and after the filter option is clicked it creates a white space in the bottom of the page. +- Expand testing coverage. Continue fixing old tests which rely on outdated dependencies, and implementing new tests. +- Continue modularizing code. Many large, unwieldy files that should be broken up into more modular components still exist. +- Continue Typescript conversion. Consider toggling noImplicitAny to find all 'any' cases that can be addressed. +- Continue cleanup of outdated and unused code and files. +- Collaboration feature still needs to be improved for scalability with AWS servers and clients for better experiences. The feature currently is limited to access with only 1 AWS cloud server. +- Future teams could look into data structures for scaling on the server side of the app to improve data transmitting and multiple server functionality. + +**Version 17.0.0 Changes** + +Changes:
+ +- Developer Improvements: + - Testing Coverage: + - Version 17 added testing for the added marketplace-related components + - Testing coverage sits at ~60% + - Typescript continued and now sits at ~80% + - Dev Bug Fixes: + - Additional logic added for edge cases in inputs for state manager (passing in non-Arrays/non-Objects as Array type and Object type). + - Fixed issue with the bottom panel not dragging or sticking to the mouse when the mouse is over the demo render iframe + - Cleaned up hundreds of lines of outdated code and archived multiple unused and duplicate files + - OAuth now linked to standalone Gmail and GitHub accounts +- User Features: + - UI updated with a modern style for a better developer experience + - Added many user feedback alerts for a better experience including alerts for when projects are published, cloned, deleted, HTML custom tags are created, context created, or custom components created. + - Built a specific buttons menu that individually displays the HTML elements, reusable components created, and join room option. + - Redesigned the state manager panel option to be readable and functional. + - Drop down menu now closes only when the user clicks outside of the menu + - Marketplace: + - Implemented a dedicated area for developers to share their projects + - Routing handled by React Router + - Projects can also be cloned to the user's account to be used and edited with the addition of a button + - Added search functionality to search by username and project name + - Included a separate section in the Saved Projects and Delete Projects modal in the Manage Project menu for cloned projects from the Marketplace + - Publish/Unpublish Button: + - Publish feature on the web app allows users to publish their saved project files into the Marketplace from the main app page + - Dynamically switches between publish/unpublish depending on whether the loaded project is in the Marketplace + +Recommendations for Future Enhancements:
+ +- Add a comment section and description section for each published project +- Consider maybe a way for users to pull individual components from one project into another +- Use localforage or other methods to store unsaved projects either on logout or accidental closure of the browser, so that when the user opens the browser again it is still there. +- Continue expanding testing coverage. Improve testing by adding additional unit tests, expanding end-to-end testing, and introducing integration testing. +- Continue quality Typescript conversion. Continue to fix type errors within component files. +- Modularize appStateSlice file. Further modularization is needed for readability and maintainability. +- Solve residual bugs. Undo & Redo buttons on customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Resolve electron app functionality to coincide with web app functionality. +- Take a look at the join room functionality using web sockets in order to allow users to collaborate on the same project at the same time. +- For the state manager option in the data table there is a MuiData-menu that is not visible when clicking it and after the filter option is clicked it creates a white space in the bottom of the page. +- Continue code cleanup. Continue cleanup of outdated and unused code and files + +**Version 16.0.0 Changes** + +Changes:
+ +- Developer Improvements: + - Testing Coverage: + - Version 16 introduces end-to-end testing with Playwright and adds additional unit testing with React Testing Library. + - Testing coverage has now doubled since version 15, and now sits at just over 50% coverage. + - Transitioned away from Enzyme to maintain consistency with RTL and Jest. + - Typescript Conversion: + - Upped typescript coverage from 30% to 80%. + - Fixed multiple type errors in component files. + - Added CI pipeline for testing: + - Transitioned away from Travis CI to Github Actions for improved CI pipeline. Github Actions will now run all tests upon each pull request to dev. + - Updated OAuth and Sign In Features: + - Sign in feature now connected to the latest database version. + - Fixed bug that allowed only one OAuth user to sign in at a time. + - Github OAuth is now connected to Adam Vanek. + - Dev Bug Fixes: + - Debugged ‘worker error’ on code preview & style editor by refactoring Ace-Build components. + - Additional logic added for edge cases in inputs for context manager, state manager, and signup features. + - Cleaned up hundreds of lines of outdated code and deleted multiple unused and duplicate files + - Dependency Updates: + - All previously outdated dependencies are now updated. Time it takes for the app to bundle in dev is now cut in half. +- User Features: + - Export Button: + - Export feature on the web app now allows users to download the current project as a zip file with modularized component folder, html, and css file included. + - Export feature is now available to all users including guests. + - CSS Live Rendering: + - CSS Editor changes now rendered visually in the demo page on save. + - UI Changes: + - Fixed multiple contrast issues with white text displaying on white background in State Manger Display tab tables, state management tables, table menu dropdowns, Context Manager tables, and Context Manager display. + - Adjusted context manager interface for improved UX when creating context and saving key/value pairs. + - Fixed border styling within modals and error messages that were cutting off inputs on focus. + - Added save button to customization tab. + - Bug Fixes: + - Manage project features for registered users now successfully saves, opens, and deletes projects. + - State Manager now successfully deletes state from parent components. + - Context Manager display chart renders correctly. + - CSS Editor contents now persist after rerender. + +Recommendations for Future Enhancements:
+ +- Refactor away from MUI. MUI is very opinionated and while creating components with it is easy it leaves a lot to be desired. Dark Mode also needs to be improved as color contrast is very low. +- Continue expanding testing coverage. Improve testing by adding additional unit tests, expanding end-to-end testing, and introducing integration testing. +- Continue quality Typescript conversion. Continue to fix type errors within component files. +- Modularize appStateSlice file. Further modularization is needed for readability and maintainability. +- Solve residual bugs. Undo & Redo buttons on customization page not functioning as expected. Backend bugs persist as seen in the console when running the dev environment. Resolve electron app functionality to coincide with web app functionality. +- Continue code cleanup. Continue cleanup of outdated and unused code and files + +**Version 15.0.0 Changes** + +Changes:
+ +- Developer Improvements: + - Redux Toolkit: + - Migrated state from a combination of useReducer/useContext and Redux to only using Redux Toolkit. This is the recommended modern approach to handling large state management in this sort of application. Enhances the developer experience by enabling the use of the Redux Devtools to debug, and see state/actions in real-time. + - Dependency Updates + - New developers can easily npm install without having to use an older version of node or using --legacy-peer-deps + - Updated to modern versions to take advantage of newer features +- User Features: + - Websockets: + - Users can now join rooms to collaborate in realtime + - Tailwind CSS: + - In the customization panel users can now choose between inline CSS and Tailwind. These changes are reflected in the live code preview. + - OAuth: + - Users may now log in using OAuth which enhances security, and makes sign in a breeze. + - Deployed Website: + - Containerized and deployed a working version of the application. Instead of having to download an application users may now interact live. + +Recommendations for Future Enhancements:
+ +- Continue working on State Management. There are some changes that can be made to make the application cleaner. Right now the appStateSlice is a large file which houses a lot of the reducer functions. We believe there is a way to further modularize this to make it simpler to read, and iterate upon in the future. +- Convert to using Vite. While developing we ran into issues with webpack taking a long time to reflect changes. Vite is lightweight and enhances the developer experience. +- Expand Testing Coverage. Making a large move of state management made a lot of the testing innefective since it was based upon old ways. +- Refactor away from MUI. MUI is very opinionated and while creating components with it is easy it leaves a lot to be desired. +- Residual Bugs. While migrating state there are a few lingering bugs within the application. This process should be easier now with Redux Devtools availability, but we did not have time to go through every action and conduct thorough testing. + +**Version 14.0.0 Changes** + +Changes:
+ +- Added functionality & improvements: + - Event listeners: + - Added ability to assign event listeners to elements in the bottom customization tab + - Can name the function on the event or use the default name provided + - Updates reflected in the code preview render + - Live code preview: Bottom tool tabs code preview box updates immediately and automatically to reflect the latest changes in state + - Converted the annotation button into a delete button on the canvas elements and reusable components + - Code preview render: The formatting for generated code has been corrected for improved readability +- Major UI changes: + - Left panel: + - Only display when mouse hovered over + - When extended, floats in front of the canvas without affecting the main window formatting + - Bottom panel + - Retractable feature added + - Has internal scroll ability in the tabs + - Resized functionality is stable + - Added indicator tabs to each signifying to the user their presence + - Canvas container (upper left): + - Changed the formatting to a center column with readable size and label orientation + - Standardized the size of components and rate of growth when nesting + - Tutorial: + - Users can now reference tutorials in split-screen mode without the canvas being auto-cleared when going back and forth from the tutorial +- Bug fixes: + - Reusable component: The drag-and-drop feature for reusable components is now functioning smoothly and without bugs + +Recommendations for Future Enhancements:
+ +- Add function content in the current event listeners' function skeleton. +- The code output formatting in generateCode.ts is currently difficult to read, and could be improved for better readability. +- Currently, the project uses two sets of state management tools: useReducer/useContext and Redux. useReducer/useContext is used for handling the customization state, and Redux for managing the code preview, context manager, and dark mode reducer state. However, there seems to be some confusion around how to integrate these two tools effectively. For instance, both tools are used for managing the code preview state, and changing the useReducer/useContext state would replace the corresponding redux state. Need to clean up the logic and find a solution to solve this issue. +- Some of the files structure is not accurate (e.g., customizationPanel.tsx is in the containers folder instead of the bottom folder), need to rearrange the file hierarchy. +- Update packages and resolve package dependency issues. + +**Version 13.0.0 Changes** + +New Functionality:
+ +- Manage state locally: Users can now manage state dynamically within nested components using React Hooks within the state manager tab. +- Add/delete props: For a selected component, users can see a list of available props from the parent, add props, and delete props in case they are not - required later on. +- State/props flow: If state or props are deleted upstream, it will automatically update the state for its children components. +- Visualize state/props flow: Within the display sub-tab of the state manager tab, users can visualize an interactive tree diagram depicting the state initialized in the current component and passed down props from the parent component. + +Enhancements:
+ +- Live code preview: Live rendering of code based on any changes in the state and dragging and nesting of components. +- Next.js & Gatsby compatibility: New state manager tab is now compatible with next.js and Gatsby. +- Tutorial: Tutorial is functional and has the latest guides to navigate through the newly added state management tab. + +Deployment Updates:
+ +- Electron app is now available for Windows users. +- Web based version of the app is available on Heroku. + +Bug Fixes:
+ +- User dashboard: The dashboard works now and shows private and shared projects with the ability for users to drop comments. +- Login/logout: Users can now signup/login/logout now on both development and production environments. +- Manage Projects: Github authenticated users are now able to create and save projects. +- Customization: Use State works as expected now within HTML elements. + +What’s next:
+ +- Adding on click functionality within components. Goal: Make a fully functional tic-tac-toe app. +- Incorporating material ui into the components so that exported app has visually appealing components. +- Enabling auto save functionality when dragging and dropping components, and amending component state. +- Allowing users to click and access projects within the dashboard for review. +- Adding more integration and E2E testing. +- Fixing bugs in the heroku (web based) deployment: login/logout, GitHub oauth etc. +- Enabling google oauth in all environments. +- Packaging electron app for Linux users. +- Conversion from monolithic to micro services for better scaling in the future. + +**Version 12.0.0 Changes** + +- Context Visualizer: You can now visually see what component is consuming which context. As you click on the interactive tree, the component assigned to the context will be revealed. +- React 18: Updated to React 18 +- Export Feature: Created an exportable context file, integrated with original codebase. Ready to go code: Added boilerplate codes to components based on which contexts they are consuming. + +**A note to future contributors** + +Attempted to implement Facebook and Google OAuth via passport but as of Electron’s current version, neither of them not compatible with electron. + +**Version 11.0.0 Changes:** + +- Added Next.js functionality + - Next.js projects will generate the right code needed for exporting a Next.js application + - Link & Image elements have been added + - Link components are able to couple with a page to enable SSR + - Next Link components have a drop down menu to quickly and easily link pages + - Current canvas can be saved as a page to be coupled with a Next.js Link element + - Files are exported with the appropriate Next.js imports and structure +- Added Redux and began migrating some state over for ease of development (debugging & readability) +- Fixed bug causing electron to crash when closing the window rather than going to file > exit +- Fixed bug causing app to crash when project was changed to either Next.js or Gatsby.js +- Fixed GitHub OAuth + - added Passport.js & Passport-Github libraries for strategies which takes care of all the credential exchanges and session information + - linked electron front end to talk to backend to exchange credentials +- Fixed code preview not displaying properly +- Fixed demo render preview so that changes in the canvas appears instantly + - any links in the demo render preview can now be clicked on and it will take you to its related page +- Properties of each component now persist in the customization tab +- Fixed dark mode not syncing properly across pages + +**Version 11.0.0 Stretch Features:** + +- Move more state away from the react hook & context API and into the Redux store + - This will be very time consuming but will make implementing new features much easier + - Highly suggest you read Redux documentation on best practices before diving into this + - This will improve performance by reducing the amount of unneccessary re-render. The context API causes certain pieces of state to be needlessly coupled + - Debugging is much easier by the use of Redux dev tools which allow time travel debugging + - Code will be easier to read and thus data flow will be easier to visualize + - Don't move **everything** onto Redux. ie: Material UI uses the context API to handle theme changes +- Enable remote work similar to vscode's live share + - Tried to implement peer to peer communication via webRTC with redux swarmlog but was not successful + - Look into using websockets + - Think about security. What features needs to be implemented for secure sharing? + - Transfer actions through websockets via Redux middleware (Thunk)? +- Save project (state) onto local storage for guests +- Redesign UI to be more flexible + - Read material ui docs for best practices. + - creation panel should be redesigned. Its react component structure is too fragmented. +- Add missing Next.js features + - Image components need sizing & loading options to capitalize on Next.js' Image optimization diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b4627fdaf..c9430d234 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,125 +1,125 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Dockerfile b/Dockerfile index 5eebc20b0..27226349b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,36 @@ -# Stage 1: Build -FROM node:21.2.0-alpine as build - -# python: required dependency for node alpine, shrinks image size from 2.17GB to 1.67GB -RUN apk add --no-cache --virtual .gyp \ - python3 \ - make \ - g++ - -WORKDIR /app - -COPY package*.json ./ - -RUN npm install --no-install-recommends --fetch-retry-maxtimeout 500000 - -COPY . . - -# Stage 2: Runtime -FROM node:21.2.0-alpine as runtime - -WORKDIR /app - -COPY --from=build /app/package*.json ./ - -RUN npm install --no-install-recommends --only=production --fetch-retry-maxtimeout 500000 - -# COPY --from=build /app/.env .env -COPY --from=build /app/config.js ./config.js -COPY --from=build /app/server ./server -COPY --from=build /app/build /app - -EXPOSE 5656 - -ENV IS_DOCKER true - +# Stage 1: Build +FROM node:21.2.0-alpine as build + +# python: required dependency for node alpine, shrinks image size from 2.17GB to 1.67GB +RUN apk add --no-cache --virtual .gyp \ + python3 \ + make \ + g++ + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install --no-install-recommends --fetch-retry-maxtimeout 500000 + +COPY . . + +# Stage 2: Runtime +FROM node:21.2.0-alpine as runtime + +WORKDIR /app + +COPY --from=build /app/package*.json ./ + +RUN npm install --no-install-recommends --only=production --fetch-retry-maxtimeout 500000 + +# COPY --from=build /app/.env .env +COPY --from=build /app/config.js ./config.js +COPY --from=build /app/server ./server +COPY --from=build /app/build /app + +EXPOSE 5656 + +ENV IS_DOCKER true + CMD [ "npm", "start" ] \ No newline at end of file diff --git a/Dockerrun.aws.json b/Dockerrun.aws.json index a4a538840..3930f4966 100644 --- a/Dockerrun.aws.json +++ b/Dockerrun.aws.json @@ -1,18 +1,18 @@ -{ - "AWSEBDockerrunVersion": "1", - "Image": { - "Name": "035101486432.dkr.ecr.us-east-1.amazonaws.com/reactype-2stage:latest", - "Update": "true" - }, - "Ports": [ - { - "ContainerPort": "5656" - } - ], - "Environment": [ - { - "Name": "API_BASE_URL", - "Value": "Reactype-v19env.eba-sw2fhsbj.us-east-1.elasticbeanstalk.com" - } - ] -} +{ + "AWSEBDockerrunVersion": "1", + "Image": { + "Name": "035101486432.dkr.ecr.us-east-1.amazonaws.com/reactype-2stage:latest", + "Update": "true" + }, + "Ports": [ + { + "ContainerPort": "5656" + } + ], + "Environment": [ + { + "Name": "API_BASE_URL", + "Value": "Reactype-v19env.eba-sw2fhsbj.us-east-1.elasticbeanstalk.com" + } + ] +} diff --git a/LICENSE.md b/LICENSE.md index 41ccbcd37..177a6267d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,9 @@ -MIT License - -Copyright (c) 2022 ReacType - -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: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -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. +MIT License + +Copyright (c) 2022 ReacType + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index 5ce612d06..b9012244c 100644 --- a/README.md +++ b/README.md @@ -1,172 +1,172 @@ - - - - - - - -
- -[![StarShield][stars]][stars-url] -[![ContributorShield][contributors]][contributors-url] -[![ForksShield][forks]][forks-url] -![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) -![Version: 20.0.0](https://img.shields.io/badge/version-20.0.0-orange) - -
- -

- -

- -![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) -![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) -![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) -![Express.js](https://img.shields.io/badge/express.js-%23404d59.svg?style=for-the-badge&logo=express&logoColor=%2361DAFB) -![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) -![Redux](https://img.shields.io/badge/redux-%23593d88.svg?style=for-the-badge&logo=redux&logoColor=white) -![Socket.io](https://img.shields.io/badge/Socket.io-black?style=for-the-badge&logo=socket.io&badgeColor=010101) -![Jest](https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white) -![Babel](https://img.shields.io/badge/Babel-F9DC3e?style=for-the-badge&logo=babel&logoColor=black) -![Git](https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white) -![MUI](https://img.shields.io/badge/MUI-%230081CB.svg?style=for-the-badge&logo=mui&logoColor=white) -![Electron.js](https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white) -![MongoDB](https://img.shields.io/badge/MongoDB-%234ea94b.svg?style=for-the-badge&logo=mongodb&logoColor=white) -![AWS](https://img.shields.io/badge/AWS-%23FF9900.svg?style=for-the-badge&logo=amazon-aws&logoColor=white) -![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) -![Vite](https://img.shields.io/badge/Vite-orange?logo=vite&style=for-the-badge) - -

ReacType

- -**ReacType** is a React prototyping tool that allows users _visualize_ their application architecture dynamically, employing an interactive drop and drag display with real-time component code preview and a collaboration room that features live video and chat functionality. Generated code can be exported as a **React** app for developers employing React component architecture alongside the comprehensive type-checking of **TypeScript**. In other words, **you can draw prototypes and export React / TypeScript code!** - -

- -

- -Visit [reactype.dev](https://reactype.dev) to learn more about the product. - -Follow [@ReacType](https://twitter.com/reactype) on Twitter for important announcements. - -## Changes with Version 20.0.0 - -- **Collaboration Rooms**: Official launch of v2 collaboration rooms - Now featuring a secure live collaborative chat room with video and cursor tracking functionality! -- **UI Updates**: The UI now features a new logo, zoom and scroll functionality to the canvas, and numerous updates to styling to reflect a more modern and user friendly experience. -- **DX Updates**: Migrated from WebPack to Vite, drastically reducing HMR time. Now deployed via Heroku instead of AWS. -- **Typescript Conversion**: Typescript coverage is at 95%. -- **Cleanup**: Removed unused code, fixed bugs, and made major performance improvements. -- **And more:** See the [change log](https://github.com/open-source-labs/ReacType/blob/master/CHANGE_LOG.md) for more details on what was changed from the previous versions, as well as plans for upcoming features! - -

- -

- -## Preview - -Get a glimpse of how ReacType works! - -

- -

- - - -## File Structure of ReacType Version 20.0.0 - -Here is the main file structure: - -

- -

- -Given to us courtesy of our friends over at React Relay - -## Run ReacType using CLI - -- **Fork** and **Clone** Repository. -- Open the project directory. -- Install dependencies. - -```bash -npm install -``` - -- To run the production build - -```bash -npm run prod -``` - -- To run tests - -```bash -npm run test -``` - -- To run the development build - -```bash -npm run dev -``` - -- Note that DEV_PORT, NODE_ENV flag (=production or development) and VIDEOSDK token are needed in the .env file. -- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can select "Continue as guest" on the login page of the app, which will not use any features that rely on the server (authentication and saving project data.) - -- To run the development build of electron app - -```bash -npm run dev -npm run electron-dev -``` - -## Run Exported App - -- Open exported project directory -- Install dependencies - -```bash -npm install -``` - -- Build the app - -```bash -npm run build -``` - -- Start an instance - -```bash -npm run start -``` - -- Open browser and navigate to localhost at specified port - -## Stack - -Typescript, React.js, Redux Toolkit, Javascript, ESM, Node.js (Express), HTML, CSS, MUI, GraphQL, Next.js, Gatsby.js, Electron, NoSQL, Webpack, TDD (Jest, React Testing Library, Playwright), OAuth 2.0, Websocket, SocketIO, Continuous Integration (Github Actions), Docker, AWS (ECR, Elastic Beanstalk), Ace Editor, Google Charts, React DnD, Vite - -## Contributions - -Here is the up to date [list](https://github.com/open-source-labs/ReacType/blob/master/contributors.md) of all co-developers of this product. - -## License - -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/team-reactype/ReacType/blob/development/LICENSE.md) file for details. - -[stars]: https://img.shields.io/github/stars/open-source-labs/ReacType -[stars-url]: https://github.com/open-source-labs/ReacType/stargazers -[forks]: https://img.shields.io/github/forks/open-source-labs/ReacType -[forks-url]: https://github.com/open-source-labs/ReacType/network/members -[contributors]: https://img.shields.io/github/contributors/open-source-labs/ReacType -[contributors-url]: https://github.com/open-source-labs/ReacType/graphs/contributors + + + + + + + +
+ +[![StarShield][stars]][stars-url] +[![ContributorShield][contributors]][contributors-url] +[![ForksShield][forks]][forks-url] +![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) +![Version: 20.0.0](https://img.shields.io/badge/version-20.0.0-orange) + +
+ +

+ +

+ +![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) +![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) +![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) +![Express.js](https://img.shields.io/badge/express.js-%23404d59.svg?style=for-the-badge&logo=express&logoColor=%2361DAFB) +![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) +![Redux](https://img.shields.io/badge/redux-%23593d88.svg?style=for-the-badge&logo=redux&logoColor=white) +![Socket.io](https://img.shields.io/badge/Socket.io-black?style=for-the-badge&logo=socket.io&badgeColor=010101) +![Jest](https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white) +![Babel](https://img.shields.io/badge/Babel-F9DC3e?style=for-the-badge&logo=babel&logoColor=black) +![Git](https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white) +![MUI](https://img.shields.io/badge/MUI-%230081CB.svg?style=for-the-badge&logo=mui&logoColor=white) +![Electron.js](https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white) +![MongoDB](https://img.shields.io/badge/MongoDB-%234ea94b.svg?style=for-the-badge&logo=mongodb&logoColor=white) +![AWS](https://img.shields.io/badge/AWS-%23FF9900.svg?style=for-the-badge&logo=amazon-aws&logoColor=white) +![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) +![Vite](https://img.shields.io/badge/Vite-orange?logo=vite&style=for-the-badge) + +

ReacType

+ +**ReacType** is a React prototyping tool that allows users _visualize_ their application architecture dynamically, employing an interactive drop and drag display with real-time component code preview and a collaboration room that features live video and chat functionality. Generated code can be exported as a **React** app for developers employing React component architecture alongside the comprehensive type-checking of **TypeScript**. In other words, **you can draw prototypes and export React / TypeScript code!** + +

+ +

+ +Visit [reactype.dev](https://reactype.dev) to learn more about the product. + +Follow [@ReacType](https://twitter.com/reactype) on Twitter for important announcements. + +## Changes with Version 20.0.0 + +- **Collaboration Rooms**: Official launch of v2 collaboration rooms - Now featuring a secure live collaborative chat room with video and cursor tracking functionality! +- **UI Updates**: The UI now features a new logo, zoom and scroll functionality to the canvas, and numerous updates to styling to reflect a more modern and user friendly experience. +- **DX Updates**: Migrated from WebPack to Vite, drastically reducing HMR time. Now deployed via Heroku instead of AWS. +- **Typescript Conversion**: Typescript coverage is at 95%. +- **Cleanup**: Removed unused code, fixed bugs, and made major performance improvements. +- **And more:** See the [change log](https://github.com/open-source-labs/ReacType/blob/master/CHANGE_LOG.md) for more details on what was changed from the previous versions, as well as plans for upcoming features! + +

+ +

+ +## Preview + +Get a glimpse of how ReacType works! + +

+ +

+ + + +## File Structure of ReacType Version 20.0.0 + +Here is the main file structure: + +

+ +

+ +Given to us courtesy of our friends over at React Relay + +## Run ReacType using CLI + +- **Fork** and **Clone** Repository. +- Open the project directory. +- Install dependencies. + +```bash +npm install +``` + +- To run the production build + +```bash +npm run prod +``` + +- To run tests + +```bash +npm run test +``` + +- To run the development build + +```bash +npm run dev +``` + +- Note that DEV_PORT, NODE_ENV flag (=production or development) and VIDEOSDK token are needed in the .env file. +- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can select "Continue as guest" on the login page of the app, which will not use any features that rely on the server (authentication and saving project data.) + +- To run the development build of electron app + +```bash +npm run dev +npm run electron-dev +``` + +## Run Exported App + +- Open exported project directory +- Install dependencies + +```bash +npm install +``` + +- Build the app + +```bash +npm run build +``` + +- Start an instance + +```bash +npm run start +``` + +- Open browser and navigate to localhost at specified port + +## Stack + +Typescript, React.js, Redux Toolkit, Javascript, ESM, Node.js (Express), HTML, CSS, MUI, GraphQL, Next.js, Gatsby.js, Electron, NoSQL, Webpack, TDD (Jest, React Testing Library, Playwright), OAuth 2.0, Websocket, SocketIO, Continuous Integration (Github Actions), Docker, AWS (ECR, Elastic Beanstalk), Ace Editor, Google Charts, React DnD, Vite + +## Contributions + +Here is the up to date [list](https://github.com/open-source-labs/ReacType/blob/master/contributors.md) of all co-developers of this product. + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/team-reactype/ReacType/blob/development/LICENSE.md) file for details. + +[stars]: https://img.shields.io/github/stars/open-source-labs/ReacType +[stars-url]: https://github.com/open-source-labs/ReacType/stargazers +[forks]: https://img.shields.io/github/forks/open-source-labs/ReacType +[forks-url]: https://github.com/open-source-labs/ReacType/network/members +[contributors]: https://img.shields.io/github/contributors/open-source-labs/ReacType +[contributors-url]: https://github.com/open-source-labs/ReacType/graphs/contributors diff --git a/__tests__/BottomTabs.test.tsx b/__tests__/BottomTabs.test.tsx index e7d55068e..31145f71d 100644 --- a/__tests__/BottomTabs.test.tsx +++ b/__tests__/BottomTabs.test.tsx @@ -1,233 +1,233 @@ -import '@testing-library/jest-dom'; - -import { - fireEvent, - render, - screen, - waitFor, - within -} from '@testing-library/react'; - -import BottomTabs from '../app/src/components/bottom/BottomTabs'; -import { BrowserRouter } from 'react-router-dom'; -import ComponentPanel from '../app/src/components/right/ComponentPanel'; -import ContextManager from '../app/src/components/ContextAPIManager/ContextManager'; -import CustomizationPanel from '../app/src/containers/CustomizationPanel'; -import { DndProvider } from 'react-dnd'; -import DragDropPanel from '../app/src/components/left/DragDropPanel'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import HTMLPanel from '../app/src/components/left/HTMLPanel'; -import MainContainer from '../app/src/containers/MainContainer'; -import { Provider } from 'react-redux'; -import React from 'react'; -import StateManager from '../app/src/components/StateManagement/StateManagement'; -import store from '../app/src/redux/store'; - -describe('Bottom Panel Render Test', () => { - test('should render all six tabs', () => { - render( - - - - ); - expect(screen.getAllByRole('tab')).toHaveLength(6); - // expect(screen.getByText('Code Preview')).toBeInTheDocument(); - expect(screen.getByText('Component Tree')).toBeInTheDocument(); - expect(screen.getByText('Creation Panel')).toBeInTheDocument(); - expect(screen.getByText('Customization')).toBeInTheDocument(); - expect(screen.getByText('CSS Editor')).toBeInTheDocument(); - expect(screen.getByText('Context Manager')).toBeInTheDocument(); - expect(screen.getByText('State Manager')).toBeInTheDocument(); - }); -}); - -describe('Creation Panel', () => { - test('should invalidate empty field in New Component name', async () => { - render( - - - - ); - - fireEvent.click(screen.getByText('Create')); - - await waitFor(() => { - expect( - screen.getByText('Component name cannot be blank.') - ).toBeInTheDocument(); - }); - }); - - test('should invalidate New Component name containing symbols', async () => { - render( - - - - ); - - fireEvent.change(screen.getByLabelText('Name:'), { - target: { - value: '!@#' - } - }); - - fireEvent.click(screen.getByText('Create')); - - await waitFor(() => { - expect( - screen.getByText('Component name must start with a letter.') - ).toBeInTheDocument(); - }); - }); - - test('should invalidate empty field in HTML Tag tag', async () => { - render( - - - - ); - - fireEvent.click(screen.getByText('Add Element')); - - await waitFor(() => { - expect(screen.getAllByText('* Input cannot be blank. *')).toHaveLength(2); - }); - }); - - test('should invalidate HTML Element name containing symbols', async () => { - render( - - - - ); - - fireEvent.change(screen.getByLabelText('Element Name:'), { - target: { - value: '!@#' - } - }); - - fireEvent.change(screen.getByLabelText('Tag:'), { - target: { - value: '!@#' - } - }); - - fireEvent.click(screen.getByText('Add Element')); - - await waitFor(() => { - expect( - screen.getAllByText('* Input must start with a letter. *') - ).toHaveLength(2); - }); - }); -}); - -describe('Context Manager', () => { - test('should render Create/Edit, Assign, and Display tabs', () => { - render( - - - - ); - expect(screen.getAllByRole('tab')).toHaveLength(3); - }); - test('Create/Edit Tab should contain all buttons, inputs field, and a data table', () => { - render( - - - - ); - expect(screen.getAllByRole('textbox')).toHaveLength(3); - expect(screen.getAllByRole('button')).toHaveLength(4); - expect(screen.getByText('Context Name')).toBeInTheDocument(); - expect(screen.getByRole('table')).toBeInTheDocument(); - }); - test('Assign Tab should contain all buttons and input fields', () => { - render( - - - - ); - - fireEvent.click(screen.getByText('Assign')); - expect(screen.getByText('Contexts Consumed')).toBeInTheDocument(); - const dropdown = screen.getByLabelText('Select Component'); - expect(dropdown).toBeInTheDocument(); - expect(screen.getAllByRole('button')).toHaveLength(1); - expect(screen.getAllByRole('combobox')).toHaveLength(2); - expect(screen.getAllByRole('table')).toHaveLength(2); - }); -}); - -describe('State Manager', () => { - test('Should render all containers', () => { - render( - - - - ); - expect(screen.getAllByRole('heading')).toHaveLength(4); - expect(screen.getAllByRole('textbox')).toHaveLength(2); - expect(screen.getAllByRole('grid')).toHaveLength(3); - expect(screen.getAllByRole('columnheader')).toHaveLength(9); - }); - - test('Display tab should render correct elements', () => { - render( - - - - ); - fireEvent.click(screen.getByText('Display')); - expect(screen.getByRole('table')).toBeInTheDocument(); - expect( - screen.getByText('State Initialized in Current Component:') - ).toBeInTheDocument(); - }); -}); - -describe('Customization Panel', () => { - test('Should render customization container with no elements in Canvas', () => { - render( - - - - - - ); - expect(screen.getByText('Parent Component:')).toBeInTheDocument(); - expect(screen.getByText('App')).toBeInTheDocument(); - expect( - screen.getByText( - 'Drag and drop an html element (or focus one) to see what happens!' - ) - ).toBeInTheDocument(); - }); - test('Should render all buttons and inputs when Canvas has element', () => { - render( - - - - - - - - - - ); - const drop = screen.getByTestId('drop'); - const div = screen.getAllByText('Div')[0]; - expect(drop).toBeInTheDocument(); - expect(div).toBeInTheDocument(); - fireEvent.dragStart(div); - fireEvent.dragEnter(drop); - fireEvent.dragOver(drop); - fireEvent.drop(drop); - //check if customization panel elements are rendering correctly - const panel = screen.getByTestId('customization'); - expect(within(panel).getAllByRole('textbox')).toHaveLength(4); - // check dropdowns - expect(within(panel).getAllByRole('button')).toHaveLength(12); - }); -}); +import '@testing-library/jest-dom'; + +import { + fireEvent, + render, + screen, + waitFor, + within +} from '@testing-library/react'; + +import BottomTabs from '../app/src/components/bottom/BottomTabs'; +import { BrowserRouter } from 'react-router-dom'; +import ComponentPanel from '../app/src/components/right/ComponentPanel'; +import ContextManager from '../app/src/components/ContextAPIManager/ContextManager'; +import CustomizationPanel from '../app/src/containers/CustomizationPanel'; +import { DndProvider } from 'react-dnd'; +import DragDropPanel from '../app/src/components/left/DragDropPanel'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import HTMLPanel from '../app/src/components/left/HTMLPanel'; +import MainContainer from '../app/src/containers/MainContainer'; +import { Provider } from 'react-redux'; +import React from 'react'; +import StateManager from '../app/src/components/StateManagement/StateManagement'; +import store from '../app/src/redux/store'; + +describe('Bottom Panel Render Test', () => { + test('should render all six tabs', () => { + render( + + + + ); + expect(screen.getAllByRole('tab')).toHaveLength(6); + // expect(screen.getByText('Code Preview')).toBeInTheDocument(); + expect(screen.getByText('Component Tree')).toBeInTheDocument(); + expect(screen.getByText('Creation Panel')).toBeInTheDocument(); + expect(screen.getByText('Customization')).toBeInTheDocument(); + expect(screen.getByText('CSS Editor')).toBeInTheDocument(); + expect(screen.getByText('Context Manager')).toBeInTheDocument(); + expect(screen.getByText('State Manager')).toBeInTheDocument(); + }); +}); + +describe('Creation Panel', () => { + test('should invalidate empty field in New Component name', async () => { + render( + + + + ); + + fireEvent.click(screen.getByText('Create')); + + await waitFor(() => { + expect( + screen.getByText('Component name cannot be blank.') + ).toBeInTheDocument(); + }); + }); + + test('should invalidate New Component name containing symbols', async () => { + render( + + + + ); + + fireEvent.change(screen.getByLabelText('Name:'), { + target: { + value: '!@#' + } + }); + + fireEvent.click(screen.getByText('Create')); + + await waitFor(() => { + expect( + screen.getByText('Component name must start with a letter.') + ).toBeInTheDocument(); + }); + }); + + test('should invalidate empty field in HTML Tag tag', async () => { + render( + + + + ); + + fireEvent.click(screen.getByText('Add Element')); + + await waitFor(() => { + expect(screen.getAllByText('* Input cannot be blank. *')).toHaveLength(2); + }); + }); + + test('should invalidate HTML Element name containing symbols', async () => { + render( + + + + ); + + fireEvent.change(screen.getByLabelText('Element Name:'), { + target: { + value: '!@#' + } + }); + + fireEvent.change(screen.getByLabelText('Tag:'), { + target: { + value: '!@#' + } + }); + + fireEvent.click(screen.getByText('Add Element')); + + await waitFor(() => { + expect( + screen.getAllByText('* Input must start with a letter. *') + ).toHaveLength(2); + }); + }); +}); + +describe('Context Manager', () => { + test('should render Create/Edit, Assign, and Display tabs', () => { + render( + + + + ); + expect(screen.getAllByRole('tab')).toHaveLength(3); + }); + test('Create/Edit Tab should contain all buttons, inputs field, and a data table', () => { + render( + + + + ); + expect(screen.getAllByRole('textbox')).toHaveLength(3); + expect(screen.getAllByRole('button')).toHaveLength(4); + expect(screen.getByText('Context Name')).toBeInTheDocument(); + expect(screen.getByRole('table')).toBeInTheDocument(); + }); + test('Assign Tab should contain all buttons and input fields', () => { + render( + + + + ); + + fireEvent.click(screen.getByText('Assign')); + expect(screen.getByText('Contexts Consumed')).toBeInTheDocument(); + const dropdown = screen.getByLabelText('Select Component'); + expect(dropdown).toBeInTheDocument(); + expect(screen.getAllByRole('button')).toHaveLength(1); + expect(screen.getAllByRole('combobox')).toHaveLength(2); + expect(screen.getAllByRole('table')).toHaveLength(2); + }); +}); + +describe('State Manager', () => { + test('Should render all containers', () => { + render( + + + + ); + expect(screen.getAllByRole('heading')).toHaveLength(4); + expect(screen.getAllByRole('textbox')).toHaveLength(2); + expect(screen.getAllByRole('grid')).toHaveLength(3); + expect(screen.getAllByRole('columnheader')).toHaveLength(9); + }); + + test('Display tab should render correct elements', () => { + render( + + + + ); + fireEvent.click(screen.getByText('Display')); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect( + screen.getByText('State Initialized in Current Component:') + ).toBeInTheDocument(); + }); +}); + +describe('Customization Panel', () => { + test('Should render customization container with no elements in Canvas', () => { + render( + + + + + + ); + expect(screen.getByText('Parent Component:')).toBeInTheDocument(); + expect(screen.getByText('App')).toBeInTheDocument(); + expect( + screen.getByText( + 'Drag and drop an html element (or focus one) to see what happens!' + ) + ).toBeInTheDocument(); + }); + test('Should render all buttons and inputs when Canvas has element', () => { + render( + + + + + + + + + + ); + const drop = screen.getByTestId('drop'); + const div = screen.getAllByText('Div')[0]; + expect(drop).toBeInTheDocument(); + expect(div).toBeInTheDocument(); + fireEvent.dragStart(div); + fireEvent.dragEnter(drop); + fireEvent.dragOver(drop); + fireEvent.drop(drop); + //check if customization panel elements are rendering correctly + const panel = screen.getByTestId('customization'); + expect(within(panel).getAllByRole('textbox')).toHaveLength(4); + // check dropdowns + expect(within(panel).getAllByRole('button')).toHaveLength(12); + }); +}); diff --git a/__tests__/DragAndDrop.test.tsx b/__tests__/DragAndDrop.test.tsx index 8241523ae..3b82c001a 100644 --- a/__tests__/DragAndDrop.test.tsx +++ b/__tests__/DragAndDrop.test.tsx @@ -1,78 +1,78 @@ -import '@testing-library/jest-dom'; - -import { fireEvent, render, screen } from '@testing-library/react'; - -import ComponentDrag from '../app/src/components/left/ComponentDrag'; -import { DndProvider } from 'react-dnd'; -import DragDropPanel from '../app/src/components/left/DragDropPanel'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import MainContainer from '../app/src/containers/MainContainer'; -import { Provider } from 'react-redux'; -import React from 'react'; -import store from '../app/src/redux/store'; -import { within } from '@testing-library/react'; - -function TestContext(component) { - return ( - - {component} - - ); -} - -describe('Drag and Drop Side Panel', () => { - test('Renders all HTML Element choices', () => { - render(TestContext()); - expect(screen.getByText('Div')).toBeInTheDocument(); - expect(screen.getByText('Img')).toBeInTheDocument(); - expect(screen.getByText('Form')).toBeInTheDocument(); - expect(screen.getByText('Button')).toBeInTheDocument(); - expect(screen.getByText('Link')).toBeInTheDocument(); - expect(screen.getByText('Paragraph')).toBeInTheDocument(); - expect(screen.getByText('Header 1')).toBeInTheDocument(); - expect(screen.getByText('Header 2')).toBeInTheDocument(); - expect(screen.getByText('Span')).toBeInTheDocument(); - expect(screen.getByText('Input')).toBeInTheDocument(); - expect(screen.getByText('Label')).toBeInTheDocument(); - expect(screen.getByText('Ordered List')).toBeInTheDocument(); - expect(screen.getByText('Unordered List')).toBeInTheDocument(); - expect(screen.getByText('Menu')).toBeInTheDocument(); - expect(screen.getByText('List')).toBeInTheDocument(); - expect(screen.queryByText('separator')).toBe(null); - }); - - test('Renders all React Router Component choices', () => { - render(TestContext()); - - expect(screen.getByText('Switch')).toBeInTheDocument(); - expect(screen.getByText('Route')).toBeInTheDocument(); - expect(screen.getByText('LinkTo')).toBeInTheDocument(); - }); - - test.skip('Should render Roots Components and Reusbale components', () => { - render(TestContext()); - - expect(screen.getByText('Root Components')).toBeInTheDocument(); - expect(screen.getByText('Reusable Components')).toBeInTheDocument(); - }); - - test('test drag and drop', () => { - render( - TestContext( - <> - - - - ) - ); - const drop = screen.getByTestId('drop'); - const div = screen.getByText('Div'); - expect(drop).toBeInTheDocument(); - expect(div).toBeInTheDocument(); - fireEvent.dragStart(div); - fireEvent.dragEnter(drop); - fireEvent.dragOver(drop); - fireEvent.drop(drop); - expect(within(drop).getByText('div')).toBeInTheDocument(); - }); -}); +import '@testing-library/jest-dom'; + +import { fireEvent, render, screen } from '@testing-library/react'; + +import ComponentDrag from '../app/src/components/left/ComponentDrag'; +import { DndProvider } from 'react-dnd'; +import DragDropPanel from '../app/src/components/left/DragDropPanel'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import MainContainer from '../app/src/containers/MainContainer'; +import { Provider } from 'react-redux'; +import React from 'react'; +import store from '../app/src/redux/store'; +import { within } from '@testing-library/react'; + +function TestContext(component) { + return ( + + {component} + + ); +} + +describe('Drag and Drop Side Panel', () => { + test('Renders all HTML Element choices', () => { + render(TestContext()); + expect(screen.getByText('Div')).toBeInTheDocument(); + expect(screen.getByText('Img')).toBeInTheDocument(); + expect(screen.getByText('Form')).toBeInTheDocument(); + expect(screen.getByText('Button')).toBeInTheDocument(); + expect(screen.getByText('Link')).toBeInTheDocument(); + expect(screen.getByText('Paragraph')).toBeInTheDocument(); + expect(screen.getByText('Header 1')).toBeInTheDocument(); + expect(screen.getByText('Header 2')).toBeInTheDocument(); + expect(screen.getByText('Span')).toBeInTheDocument(); + expect(screen.getByText('Input')).toBeInTheDocument(); + expect(screen.getByText('Label')).toBeInTheDocument(); + expect(screen.getByText('Ordered List')).toBeInTheDocument(); + expect(screen.getByText('Unordered List')).toBeInTheDocument(); + expect(screen.getByText('Menu')).toBeInTheDocument(); + expect(screen.getByText('List')).toBeInTheDocument(); + expect(screen.queryByText('separator')).toBe(null); + }); + + test('Renders all React Router Component choices', () => { + render(TestContext()); + + expect(screen.getByText('Switch')).toBeInTheDocument(); + expect(screen.getByText('Route')).toBeInTheDocument(); + expect(screen.getByText('LinkTo')).toBeInTheDocument(); + }); + + test.skip('Should render Roots Components and Reusbale components', () => { + render(TestContext()); + + expect(screen.getByText('Root Components')).toBeInTheDocument(); + expect(screen.getByText('Reusable Components')).toBeInTheDocument(); + }); + + test('test drag and drop', () => { + render( + TestContext( + <> + + + + ) + ); + const drop = screen.getByTestId('drop'); + const div = screen.getByText('Div'); + expect(drop).toBeInTheDocument(); + expect(div).toBeInTheDocument(); + fireEvent.dragStart(div); + fireEvent.dragEnter(drop); + fireEvent.dragOver(drop); + fireEvent.drop(drop); + expect(within(drop).getByText('div')).toBeInTheDocument(); + }); +}); diff --git a/__tests__/NavBar.test.tsx b/__tests__/NavBar.test.tsx index 7429aa42a..7fc02d706 100644 --- a/__tests__/NavBar.test.tsx +++ b/__tests__/NavBar.test.tsx @@ -1,294 +1,294 @@ -import React from 'react'; -import { render, fireEvent, waitFor } from '@testing-library/react'; -import '@testing-library/jest-dom/extend-expect'; -import { MemoryRouter } from 'react-router-dom'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; -import NavBar from '../app/src/components/top/NavBar'; -import * as projectFunctions from '../app/src/helperFunctions/projectGetSaveDel'; -import { Provider } from 'react-redux'; -import { act } from 'react-dom/test-utils'; -import { configureStore } from '@reduxjs/toolkit'; -import rootReducer from '../app/src/redux/reducers/rootReducer'; -import { initialState as appStateInitialState } from '../app/src/redux/reducers/slice/appStateSlice'; - -// Mock the non-serializable HTMLTypes -const mockHTMLTypes = [ - { - id: 11, - tag: 'div', - name: 'Div', - style: {}, - placeHolderShort: 'div', - placeHolderLong: '', - framework: 'reactClassic', - nestable: true, - }, - { - id: 1000, - tag: 'separator', - name: 'separator', - style: { border: 'none' }, - placeHolderShort: '', - placeHolderLong: '', - framework: '', - nestable: true, - }, - { - id: 1, - tag: 'img', - name: 'Img', - style: {}, - placeHolderShort: 'image', - placeHolderLong: '', - framework: 'reactClassic', - nestable: false, - }, -]; - -// Mocking the theme -const theme = createTheme({ - spacing: (value) => `${value}px`, -}); - -// Mocking the logo -jest.mock('../../public/icons/win/logo.png', () => 'dummy-image-url'); - -// Grabbing publish and unpublish functions -jest.mock('../app/src/helperFunctions/projectGetSaveDel', () => ({ - publishProject: jest.fn(), - unpublishProject: jest.fn(), -})); - -//mock the file saver library -jest.mock('file-saver', () => ({ - ...jest.requireActual('file-saver'), - saveAs: jest.fn(), -})); - -// const originalError = console.error; -// beforeAll(() => { -// console.error = jest.fn(); -// }); - -afterAll(() => { - jest.resetAllMocks(); -}); - -// Mocking the render -const renderNavBar = (store) => { - return render( - - - - - - - - ); -}; - -describe('NavBar Component', () => { - it('handles publish correctly with saved project', async () => { - const publishProjectMock = jest.spyOn(projectFunctions, 'publishProject'); - publishProjectMock.mockResolvedValueOnce({ - _id: 'mockedId', - name: 'Mocked Project', - published: true, - }); - - const store = configureStore({ - reducer: rootReducer, - preloadedState: { - appState: { - ...appStateInitialState, - isLoggedIn: true, - name: 'Mock Project Name', - HTMLTypes: mockHTMLTypes, - }, - }, - }); - - console.log('Before rendering NavBar'); - - const { getByText } = renderNavBar(store); - - console.log('After rendering NavBar'); - - await act(async () => { - const publishButton = getByText('Publish'); - fireEvent.click(publishButton); - }); - }); - - it('handles publish correctly with new project', async () => { - const publishProjectMock = jest.spyOn(projectFunctions, 'publishProject'); - publishProjectMock.mockResolvedValueOnce({ - _id: 'mockedId', - name: 'My Project', - published: true, - }); - - const store = configureStore({ - reducer: rootReducer, - preloadedState: { - appState: { - ...appStateInitialState, - isLoggedIn: true, - name: '', - HTMLTypes: mockHTMLTypes, - }, - }, - }); - - console.log('Before rendering NavBar'); - - const { getByText, queryByText, getByTestId, queryByTestId } = renderNavBar(store); - - console.log('After rendering NavBar'); - - await act(async () => { - // Check if the "Publish" button is present - const publishButton = queryByText('Publish'); - - if (publishButton) { - fireEvent.click(publishButton); - } else { - // If "Publish" button is not found, look for the "Unpublish" button - const unpublishButton = getByText('Unpublish'); - fireEvent.click(unpublishButton); - } - - // Check if the modal for a new project is displayed - const projectNameInput = queryByTestId('project-name-input'); - - if (projectNameInput) { - // entering a project name in the modal - fireEvent.change(projectNameInput, { target: { value: 'My Project' } }); - } - }); - }); - - it('handles unpublish correctly', async () => { - const unpublishProjectMock = jest.spyOn(projectFunctions, 'unpublishProject'); - unpublishProjectMock.mockResolvedValueOnce({ - _id: 'mockedId', - name: 'Mocked Project', - published: false, - }); - - const store = configureStore({ - reducer: rootReducer, - preloadedState: { - appState: { - ...appStateInitialState, - isLoggedIn: true, - name: 'Mock Project Name', - HTMLTypes: mockHTMLTypes, - }, - }, - }); - - console.log('Before rendering NavBar'); - - const { queryByText } = renderNavBar(store); - - console.log('After rendering NavBar'); - - // Find the "Publish" or "Unpublish" button based on the project's publish state - const publishButton = queryByText('Publish'); - const unpublishButton = queryByText('Unpublish'); - - if (publishButton) { - fireEvent.click(publishButton); - } else if (unpublishButton) { - fireEvent.click(unpublishButton); - } - }); - - it('handles export correctly', async () => { - const store = configureStore({ - reducer: rootReducer, - preloadedState: { - appState: { - ...appStateInitialState, - isLoggedIn: true, - name: 'Mock Project Name', - HTMLTypes: mockHTMLTypes, - }, - }, - }); - - console.log('Before rendering NavBar'); - - const { getByText } = renderNavBar(store); - - console.log('After rendering NavBar'); - - // Find and click the export button - const exportButton = getByText('< > Export'); - fireEvent.click(exportButton); - - // Check if the modal for export options is displayed - await waitFor(() => { - const exportModal = getByText('Click to download in zip file:'); - expect(exportModal).toBeInTheDocument(); - }); - - // Simulate clicking the export components - const exportComponentsOption = getByText('Export components'); - fireEvent.click(exportComponentsOption); - - }); - - it('handles dropdown menu correctly', async () => { - const store = configureStore({ - reducer: rootReducer, - preloadedState: { - appState: { - ...appStateInitialState, - }, - }, - }); - - console.log('Before rendering NavBar'); - - const { getByTestId, getByText } = render( - - - - - - - - ); - - console.log('After rendering NavBar'); - - await act(async () => { - - const dropdownMenu = getByTestId('navDropDown'); - expect(dropdownMenu).toHaveClass('hideNavDropDown'); - - - const moreVertButton = getByTestId('more-vert-button'); - fireEvent.click(moreVertButton); - - - expect(dropdownMenu).toHaveClass('hideNavDropDown'); - - - const clearCanvasMenuItem = getByText('Clear Canvas'); - fireEvent.click(clearCanvasMenuItem); - expect(dropdownMenu).toHaveClass('hideNavDropDown'); - - - const marketplaceMenuItem = getByText('Marketplace'); - fireEvent.click(marketplaceMenuItem); - expect(dropdownMenu).toHaveClass('hideNavDropDown'); - - fireEvent.click(moreVertButton); - - expect(dropdownMenu).toHaveClass('hideNavDropDown'); - }); - }); -}) +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { MemoryRouter } from 'react-router-dom'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import NavBar from '../app/src/components/top/NavBar'; +import * as projectFunctions from '../app/src/helperFunctions/projectGetSaveDel'; +import { Provider } from 'react-redux'; +import { act } from 'react-dom/test-utils'; +import { configureStore } from '@reduxjs/toolkit'; +import rootReducer from '../app/src/redux/reducers/rootReducer'; +import { initialState as appStateInitialState } from '../app/src/redux/reducers/slice/appStateSlice'; + +// Mock the non-serializable HTMLTypes +const mockHTMLTypes = [ + { + id: 11, + tag: 'div', + name: 'Div', + style: {}, + placeHolderShort: 'div', + placeHolderLong: '', + framework: 'reactClassic', + nestable: true, + }, + { + id: 1000, + tag: 'separator', + name: 'separator', + style: { border: 'none' }, + placeHolderShort: '', + placeHolderLong: '', + framework: '', + nestable: true, + }, + { + id: 1, + tag: 'img', + name: 'Img', + style: {}, + placeHolderShort: 'image', + placeHolderLong: '', + framework: 'reactClassic', + nestable: false, + }, +]; + +// Mocking the theme +const theme = createTheme({ + spacing: (value) => `${value}px`, +}); + +// Mocking the logo +jest.mock('../../public/icons/win/logo.png', () => 'dummy-image-url'); + +// Grabbing publish and unpublish functions +jest.mock('../app/src/helperFunctions/projectGetSaveDel', () => ({ + publishProject: jest.fn(), + unpublishProject: jest.fn(), +})); + +//mock the file saver library +jest.mock('file-saver', () => ({ + ...jest.requireActual('file-saver'), + saveAs: jest.fn(), +})); + +// const originalError = console.error; +// beforeAll(() => { +// console.error = jest.fn(); +// }); + +afterAll(() => { + jest.resetAllMocks(); +}); + +// Mocking the render +const renderNavBar = (store) => { + return render( + + + + + + + + ); +}; + +describe('NavBar Component', () => { + it('handles publish correctly with saved project', async () => { + const publishProjectMock = jest.spyOn(projectFunctions, 'publishProject'); + publishProjectMock.mockResolvedValueOnce({ + _id: 'mockedId', + name: 'Mocked Project', + published: true, + }); + + const store = configureStore({ + reducer: rootReducer, + preloadedState: { + appState: { + ...appStateInitialState, + isLoggedIn: true, + name: 'Mock Project Name', + HTMLTypes: mockHTMLTypes, + }, + }, + }); + + console.log('Before rendering NavBar'); + + const { getByText } = renderNavBar(store); + + console.log('After rendering NavBar'); + + await act(async () => { + const publishButton = getByText('Publish'); + fireEvent.click(publishButton); + }); + }); + + it('handles publish correctly with new project', async () => { + const publishProjectMock = jest.spyOn(projectFunctions, 'publishProject'); + publishProjectMock.mockResolvedValueOnce({ + _id: 'mockedId', + name: 'My Project', + published: true, + }); + + const store = configureStore({ + reducer: rootReducer, + preloadedState: { + appState: { + ...appStateInitialState, + isLoggedIn: true, + name: '', + HTMLTypes: mockHTMLTypes, + }, + }, + }); + + console.log('Before rendering NavBar'); + + const { getByText, queryByText, getByTestId, queryByTestId } = renderNavBar(store); + + console.log('After rendering NavBar'); + + await act(async () => { + // Check if the "Publish" button is present + const publishButton = queryByText('Publish'); + + if (publishButton) { + fireEvent.click(publishButton); + } else { + // If "Publish" button is not found, look for the "Unpublish" button + const unpublishButton = getByText('Unpublish'); + fireEvent.click(unpublishButton); + } + + // Check if the modal for a new project is displayed + const projectNameInput = queryByTestId('project-name-input'); + + if (projectNameInput) { + // entering a project name in the modal + fireEvent.change(projectNameInput, { target: { value: 'My Project' } }); + } + }); + }); + + it('handles unpublish correctly', async () => { + const unpublishProjectMock = jest.spyOn(projectFunctions, 'unpublishProject'); + unpublishProjectMock.mockResolvedValueOnce({ + _id: 'mockedId', + name: 'Mocked Project', + published: false, + }); + + const store = configureStore({ + reducer: rootReducer, + preloadedState: { + appState: { + ...appStateInitialState, + isLoggedIn: true, + name: 'Mock Project Name', + HTMLTypes: mockHTMLTypes, + }, + }, + }); + + console.log('Before rendering NavBar'); + + const { queryByText } = renderNavBar(store); + + console.log('After rendering NavBar'); + + // Find the "Publish" or "Unpublish" button based on the project's publish state + const publishButton = queryByText('Publish'); + const unpublishButton = queryByText('Unpublish'); + + if (publishButton) { + fireEvent.click(publishButton); + } else if (unpublishButton) { + fireEvent.click(unpublishButton); + } + }); + + it('handles export correctly', async () => { + const store = configureStore({ + reducer: rootReducer, + preloadedState: { + appState: { + ...appStateInitialState, + isLoggedIn: true, + name: 'Mock Project Name', + HTMLTypes: mockHTMLTypes, + }, + }, + }); + + console.log('Before rendering NavBar'); + + const { getByText } = renderNavBar(store); + + console.log('After rendering NavBar'); + + // Find and click the export button + const exportButton = getByText('< > Export'); + fireEvent.click(exportButton); + + // Check if the modal for export options is displayed + await waitFor(() => { + const exportModal = getByText('Click to download in zip file:'); + expect(exportModal).toBeInTheDocument(); + }); + + // Simulate clicking the export components + const exportComponentsOption = getByText('Export components'); + fireEvent.click(exportComponentsOption); + + }); + + it('handles dropdown menu correctly', async () => { + const store = configureStore({ + reducer: rootReducer, + preloadedState: { + appState: { + ...appStateInitialState, + }, + }, + }); + + console.log('Before rendering NavBar'); + + const { getByTestId, getByText } = render( + + + + + + + + ); + + console.log('After rendering NavBar'); + + await act(async () => { + + const dropdownMenu = getByTestId('navDropDown'); + expect(dropdownMenu).toHaveClass('hideNavDropDown'); + + + const moreVertButton = getByTestId('more-vert-button'); + fireEvent.click(moreVertButton); + + + expect(dropdownMenu).toHaveClass('hideNavDropDown'); + + + const clearCanvasMenuItem = getByText('Clear Canvas'); + fireEvent.click(clearCanvasMenuItem); + expect(dropdownMenu).toHaveClass('hideNavDropDown'); + + + const marketplaceMenuItem = getByText('Marketplace'); + fireEvent.click(marketplaceMenuItem); + expect(dropdownMenu).toHaveClass('hideNavDropDown'); + + fireEvent.click(moreVertButton); + + expect(dropdownMenu).toHaveClass('hideNavDropDown'); + }); + }); +}) diff --git a/__tests__/componentReducer.test.ts b/__tests__/componentReducer.test.ts index 72f736449..142a45047 100644 --- a/__tests__/componentReducer.test.ts +++ b/__tests__/componentReducer.test.ts @@ -1,298 +1,298 @@ -import reducer from '../app/src/redux/reducers/slice/appStateSlice'; -import { State, Action } from '../app/src/interfaces/Interfaces'; -import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; - -describe('componentReducer Test', () => { - let state: State = initialState; - - // TEST 'ADD COMPONENT' - describe('addComponent reducer', () => { - it('should add new reuseable component to state', () => { - const action: Action = { - type: 'appState/addComponent', - payload: { - componentName: 'TestRegular', - root: false, - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, action); - // expect state.components array to have length 2 - const length = state.components.length; - expect(length).toEqual(2); - // expect new component name to match name of last elem in state.components array - expect(state.components[length - 1].name).toEqual( - action.payload.componentName - ); - }); - }); - - // TEST 'ADD COMPONENT' with new root - describe('addComponent', () => { - it('should add new reuseable component to state as the new root', () => { - const action: Action = { - type: 'appState/addComponent', - payload: { - componentName: 'TestRootChange', - id: 3, - root: true, - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, action); - - // expect state.components array to have length 3 - const length = state.components.length; - expect(length).toEqual(3); - // expect new root to match id of component id of TestRootChange (rootComponents is an array of component ID numbers) - expect(state.rootComponents[state.rootComponents.length - 1]).toEqual( - action.payload.id - ); - }); - }); - - // TEST 'ADD CHILD' - describe('addChild', () => { - it('should add child component and separator to top-level component', () => { - const action: Action = { - type: 'appState/addChild', - payload: { - type: 'Component', - typeId: 2, - childId: null, - contextParam: { - allContext: [] - } - } - }; - // switch focus to very first root component - // state.canvasFocus = { componentId: 1, childId: null }; - state = reducer(state, action); - const newParent = state.components[0]; - // expect new parent's children array to have length 2 (component + separator) - expect(newParent.children.length).toEqual(2); - // expect first element in children array to be separator - expect(newParent.children[0].name).toEqual('separator'); - // expect new child to have type 'Component' - expect(newParent.children[1].type).toEqual('Component'); - const addedChild = state.components.find( - (comp) => comp.id === newParent.children[1].typeId - ); - // expect new child typeId to correspond to component with name 'TestRegular' - expect(addedChild.name).toEqual('TestRegular'); - }); - }); - - // TEST 'CHANGE POSITION' - describe('changePosition', () => { - it('should move position of an instance', () => { - const actionHtml: Action = { - type: 'appState/addChild', - payload: { - type: 'HTML Element', - typeId: 9, - childId: null, - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, actionHtml); - const actionChangePos: Action = { - type: 'appState/changePosition', - payload: { - currentChildId: 1, - newParentChildId: null, - contextParam: { - allContext: [] - } - } - }; - - state = reducer(state, actionChangePos); - const changeParent = state.components.find( - (comp) => comp.id === state.canvasFocus.componentId - ); - const changeParentChildLength = changeParent.children.length; - // expect last child of parent to be moved Component element - expect(changeParent.children[changeParentChildLength - 1].type).toEqual( - 'Component' - ); - // expect last child of parent to have current child ID of payload - expect( - changeParent.children[changeParentChildLength - 1].childId - ).toEqual(1); - }); - }); - - // TEST 'UPDATE CSS' - describe('updateCss', () => { - it('should add style to focused component', () => { - const action: Action = { - type: 'appState/updateCss', - payload: { - style: { - backgroundColor: 'gray' - }, - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, action); - // expect the style property on targeted comp to equal style property in payload - expect(state.components[0].children[1].style).toEqual( - action.payload.style - ); - }); - }); - - // TEST 'DELETE CHILD' - describe('deleteChild', () => { - it('should delete child of focused top-level component', () => { - // canvas still focused on childId: 2, which is an HTML element - const action: Action = { - type: 'appState/deleteChild', - payload: { - id: 2, - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, action); - // expect only one remaining child - const delParent = state.components.find( - (comp) => comp.id === state.canvasFocus.componentId - ); - // expect remaining child to have type 'Component' and to be preceded by separator - expect(delParent.children.length).toEqual(2); - expect(delParent.children[delParent.children.length - 1].type).toEqual( - 'Component' - ); - expect(delParent.children[delParent.children.length - 2].name).toEqual( - 'separator' - ); - }); - }); - - // TEST 'CHANGE FOCUS' - describe('changeFocus', () => { - it('should change focus to specified component', () => { - const action: Action = { - type: 'appState/changeFocus', - payload: { - componentId: 2, - childId: null - } - }; - state = reducer(state, action); - expect(state.canvasFocus.componentId).toEqual(2); - expect(state.canvasFocus.childId).toEqual(null); - }); - }); - - // TEST 'UPDATE PROJECT NAME' - describe('updateProjectName', () => { - it('should update project with specified name', () => { - const action: Action = { - type: 'appState/updateProjectName', - payload: 'TESTNAME' - }; - state = reducer(state, action); - // expect state name to equal payload - expect(state.name).toEqual(action.payload); - }); - }); - - // TEST 'CHANGE PROJECT TYPE' - describe('changeProjectType', () => { - it('should change project type to specified type', () => { - const action: Action = { - type: 'appState/changeProjectType', - payload: { - projectType: 'Classic React', - contextParam: { - allContext: [] - } - } - }; - state = reducer(state, action); - expect(state.projectType).toEqual(action.payload.projectType); - }); - }); - - // TEST 'UNDO' - xdescribe('undo', () => { - it('should remove the last element from the past array and push it to the future array', () => { - const focusIndex = state.canvasFocus.componentId - 1; - state.components[focusIndex].past = []; - - // snapShotFunc taken from src/components/main/canvas.tsx to test undo functionality - // snapShotFunc takes a snapshot of state to be used in UNDO/REDO functionality - const snapShotFuncCopy = () => { - const deepCopiedState = JSON.parse(JSON.stringify(state)); - // pushes the last user action on the canvas into the past array of Component - state.components[focusIndex].past.push( - deepCopiedState.components[focusIndex].children - ); - }; - - const actionHTML2: Action = { - type: 'appState/addChild', - payload: { - type: 'HTML Element', - typeId: 4, - childId: null - } - }; - state = reducer(state, actionHTML2); - // invoking snapShotFunc is necessary to push actions into the past array, referenced in the UNDO functionality to define children - snapShotFuncCopy(); - - const actionUndo: Action = { - type: 'appState/undo', - payload: {} - }; - state = reducer(state, actionUndo); - - expect(state.components[focusIndex].past.length).toEqual(0); - expect(state.components[focusIndex].future.length).toEqual(1); - }); - }); - - // TEST 'REDO' - xdescribe('redo', () => { - it('should remove the last element from the future array and push it to the past array', () => { - const focusIndex = state.canvasFocus.componentId - 1; - const actionRedo: Action = { - type: 'appState/redo', - payload: {} - }; - state = reducer(state, actionRedo); - expect(state.components[focusIndex].future.length).toEqual(0); - expect(state.components[focusIndex].past.length).toEqual(1); - }); - }); - - // TEST 'RESET STATE' - describe('resetState', () => { - it('should reset project to initial state', () => { - const action: Action = { - type: 'appState/resetState', - payload: '' - }; - state = reducer(state, action); - // expect default project to have empty string as name - expect(state.name).toEqual('TESTNAME'); - // expect default project to only have one component in components array - expect(state.components.length).toEqual(1); - // expect lone component to have no children :( - expect(state.components[0].children.length).toEqual(0); - }); - }); -}); +import reducer from '../app/src/redux/reducers/slice/appStateSlice'; +import { State, Action } from '../app/src/interfaces/Interfaces'; +import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; + +describe('componentReducer Test', () => { + let state: State = initialState; + + // TEST 'ADD COMPONENT' + describe('addComponent reducer', () => { + it('should add new reuseable component to state', () => { + const action: Action = { + type: 'appState/addComponent', + payload: { + componentName: 'TestRegular', + root: false, + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, action); + // expect state.components array to have length 2 + const length = state.components.length; + expect(length).toEqual(2); + // expect new component name to match name of last elem in state.components array + expect(state.components[length - 1].name).toEqual( + action.payload.componentName + ); + }); + }); + + // TEST 'ADD COMPONENT' with new root + describe('addComponent', () => { + it('should add new reuseable component to state as the new root', () => { + const action: Action = { + type: 'appState/addComponent', + payload: { + componentName: 'TestRootChange', + id: 3, + root: true, + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, action); + + // expect state.components array to have length 3 + const length = state.components.length; + expect(length).toEqual(3); + // expect new root to match id of component id of TestRootChange (rootComponents is an array of component ID numbers) + expect(state.rootComponents[state.rootComponents.length - 1]).toEqual( + action.payload.id + ); + }); + }); + + // TEST 'ADD CHILD' + describe('addChild', () => { + it('should add child component and separator to top-level component', () => { + const action: Action = { + type: 'appState/addChild', + payload: { + type: 'Component', + typeId: 2, + childId: null, + contextParam: { + allContext: [] + } + } + }; + // switch focus to very first root component + // state.canvasFocus = { componentId: 1, childId: null }; + state = reducer(state, action); + const newParent = state.components[0]; + // expect new parent's children array to have length 2 (component + separator) + expect(newParent.children.length).toEqual(2); + // expect first element in children array to be separator + expect(newParent.children[0].name).toEqual('separator'); + // expect new child to have type 'Component' + expect(newParent.children[1].type).toEqual('Component'); + const addedChild = state.components.find( + (comp) => comp.id === newParent.children[1].typeId + ); + // expect new child typeId to correspond to component with name 'TestRegular' + expect(addedChild.name).toEqual('TestRegular'); + }); + }); + + // TEST 'CHANGE POSITION' + describe('changePosition', () => { + it('should move position of an instance', () => { + const actionHtml: Action = { + type: 'appState/addChild', + payload: { + type: 'HTML Element', + typeId: 9, + childId: null, + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, actionHtml); + const actionChangePos: Action = { + type: 'appState/changePosition', + payload: { + currentChildId: 1, + newParentChildId: null, + contextParam: { + allContext: [] + } + } + }; + + state = reducer(state, actionChangePos); + const changeParent = state.components.find( + (comp) => comp.id === state.canvasFocus.componentId + ); + const changeParentChildLength = changeParent.children.length; + // expect last child of parent to be moved Component element + expect(changeParent.children[changeParentChildLength - 1].type).toEqual( + 'Component' + ); + // expect last child of parent to have current child ID of payload + expect( + changeParent.children[changeParentChildLength - 1].childId + ).toEqual(1); + }); + }); + + // TEST 'UPDATE CSS' + describe('updateCss', () => { + it('should add style to focused component', () => { + const action: Action = { + type: 'appState/updateCss', + payload: { + style: { + backgroundColor: 'gray' + }, + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, action); + // expect the style property on targeted comp to equal style property in payload + expect(state.components[0].children[1].style).toEqual( + action.payload.style + ); + }); + }); + + // TEST 'DELETE CHILD' + describe('deleteChild', () => { + it('should delete child of focused top-level component', () => { + // canvas still focused on childId: 2, which is an HTML element + const action: Action = { + type: 'appState/deleteChild', + payload: { + id: 2, + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, action); + // expect only one remaining child + const delParent = state.components.find( + (comp) => comp.id === state.canvasFocus.componentId + ); + // expect remaining child to have type 'Component' and to be preceded by separator + expect(delParent.children.length).toEqual(2); + expect(delParent.children[delParent.children.length - 1].type).toEqual( + 'Component' + ); + expect(delParent.children[delParent.children.length - 2].name).toEqual( + 'separator' + ); + }); + }); + + // TEST 'CHANGE FOCUS' + describe('changeFocus', () => { + it('should change focus to specified component', () => { + const action: Action = { + type: 'appState/changeFocus', + payload: { + componentId: 2, + childId: null + } + }; + state = reducer(state, action); + expect(state.canvasFocus.componentId).toEqual(2); + expect(state.canvasFocus.childId).toEqual(null); + }); + }); + + // TEST 'UPDATE PROJECT NAME' + describe('updateProjectName', () => { + it('should update project with specified name', () => { + const action: Action = { + type: 'appState/updateProjectName', + payload: 'TESTNAME' + }; + state = reducer(state, action); + // expect state name to equal payload + expect(state.name).toEqual(action.payload); + }); + }); + + // TEST 'CHANGE PROJECT TYPE' + describe('changeProjectType', () => { + it('should change project type to specified type', () => { + const action: Action = { + type: 'appState/changeProjectType', + payload: { + projectType: 'Classic React', + contextParam: { + allContext: [] + } + } + }; + state = reducer(state, action); + expect(state.projectType).toEqual(action.payload.projectType); + }); + }); + + // TEST 'UNDO' + xdescribe('undo', () => { + it('should remove the last element from the past array and push it to the future array', () => { + const focusIndex = state.canvasFocus.componentId - 1; + state.components[focusIndex].past = []; + + // snapShotFunc taken from src/components/main/canvas.tsx to test undo functionality + // snapShotFunc takes a snapshot of state to be used in UNDO/REDO functionality + const snapShotFuncCopy = () => { + const deepCopiedState = JSON.parse(JSON.stringify(state)); + // pushes the last user action on the canvas into the past array of Component + state.components[focusIndex].past.push( + deepCopiedState.components[focusIndex].children + ); + }; + + const actionHTML2: Action = { + type: 'appState/addChild', + payload: { + type: 'HTML Element', + typeId: 4, + childId: null + } + }; + state = reducer(state, actionHTML2); + // invoking snapShotFunc is necessary to push actions into the past array, referenced in the UNDO functionality to define children + snapShotFuncCopy(); + + const actionUndo: Action = { + type: 'appState/undo', + payload: {} + }; + state = reducer(state, actionUndo); + + expect(state.components[focusIndex].past.length).toEqual(0); + expect(state.components[focusIndex].future.length).toEqual(1); + }); + }); + + // TEST 'REDO' + xdescribe('redo', () => { + it('should remove the last element from the future array and push it to the past array', () => { + const focusIndex = state.canvasFocus.componentId - 1; + const actionRedo: Action = { + type: 'appState/redo', + payload: {} + }; + state = reducer(state, actionRedo); + expect(state.components[focusIndex].future.length).toEqual(0); + expect(state.components[focusIndex].past.length).toEqual(1); + }); + }); + + // TEST 'RESET STATE' + describe('resetState', () => { + it('should reset project to initial state', () => { + const action: Action = { + type: 'appState/resetState', + payload: '' + }; + state = reducer(state, action); + // expect default project to have empty string as name + expect(state.name).toEqual('TESTNAME'); + // expect default project to only have one component in components array + expect(state.components.length).toEqual(1); + // expect lone component to have no children :( + expect(state.components[0].children.length).toEqual(0); + }); + }); +}); diff --git a/__tests__/contextReducer.test.js b/__tests__/contextReducer.test.js index 72b98944f..31b9db262 100644 --- a/__tests__/contextReducer.test.js +++ b/__tests__/contextReducer.test.js @@ -1,195 +1,195 @@ -import subject from '../app/src/redux/reducers/slice/contextReducer.ts'; - -describe('contextReducer test', () => { - let state; - - beforeEach(() => { - state = { - allContext: [], - }; - }); - - describe('default state', () => { - it('should return a default state when given an undefined input', () => { - expect(subject(undefined, { type: undefined })).toEqual(state); - }); - }); - - describe('unrecognized action types', () => { - it('should return the original state without any duplication', () => { - expect(subject(state, { type: 'REMOVE_STATE' })).toBe(state); - }); - }); - - describe('addContext', () => { - const action = { - type: 'context/addContext', - payload: { - name: 'Theme Context', - }, - }; - - it('adds a context', () => { - const { allContext } = subject(state, action); - expect(allContext[0]).toEqual({ - name: 'Theme Context', - values: [], - components: [], - }); - }); - - it('returns a state object not strictly equal to the original', () => { - const newState = subject(state, action); - expect(newState).not.toBe(state); - }); - - it('should immutably update the nested state object', () => { - const { allContext } = subject(state, action); - expect(allContext).not.toBe(state.allContext); - }); - }); - - // OLD ADD CONTEX TEST - - // describe('ADD_CONTEXT', () => { - // const action = { - // type: 'ADD_CONTEXT', - // payload: { - // name: 'Theme Context' - // } - // }; - - // it('adds a context', () => { - // const { allContext } = subject(state, action); - // expect(allContext[0]).toEqual({ - // name: 'Theme Context', - // values: [], - // components: [] - // }); - // }); - - // it('returns a state object not strictly equal to the original', () => { - // const newState = subject(state, action); - // expect(newState).not.toBe(state); - // }); - - // it('should immutably update the nested state object', () => { - // const { allContext } = subject(state, action); - // expect(allContext).not.toBe(state.allContext); - // }); - // }); - - describe('addContextValues', () => { - beforeEach(() => { - state = { - allContext: [ - { - name: 'Theme Context', - values: [], - components: [], - }, - ], - }; - }); - - const action = { - type: 'context/addContextValues', - payload: { - name: 'Theme Context', - inputKey: 'Theme Color', - inputValue: 'Dark', - }, - }; - - it('adds a key-value pair to values array of the specified context', () => { - const { allContext } = subject(state, action); - expect(allContext[0].values.length).toEqual(1); - expect(allContext[0].values[0].key).toEqual('Theme Color'); - expect(allContext[0].values[0].value).toEqual('Dark'); - }); - - it('includes an allContext not strictly equal to the original', () => { - const { allContext } = subject(state, action); - - expect(allContext).not.toBe(state.allContext); - }); - }); - - describe('deleteContext', () => { - let action; - beforeEach(() => { - state = { - allContext: [ - { - name: 'Theme Context', - values: [], - components: [], - }, - { - name: 'To be deleted', - values: [], - components: [], - }, - ], - }; - - action = { - type: 'context/deleteContext', - payload: { - name: 'Theme Context', - }, - }; - }); - - it('removes specified context from the state', () => { - const { allContext } = subject(state, action); - - expect(allContext.length).toEqual(1); - }); - - it('includes an allContext not strictly equal to the original', () => { - const { allContext } = subject(state, action); - - expect(allContext).not.toBe(state.allContext); - }); - }); - - describe('addComponentToContext', () => { - beforeEach(() => { - state = { - allContext: [ - { - name: 'Theme Context', - values: [], - components: [], - }, - ], - }; - }); - - const action = { - type: 'context/addComponentToContext', - payload: { - context: { - name: 'Theme Context', - }, - component: { - name: 'Main Component', - }, - }, - }; - - it('adds a new component to the specified context', () => { - const { allContext } = subject(state, action); - - expect(allContext[0].components.length).toEqual(1); - expect(allContext[0].components[0]).toEqual('Main Component'); - }); - - it('includes an allContext not strictly equal to the original', () => { - const { allContext } = subject(state, action); - - expect(allContext).not.toBe(state.allContext); - }); - }); -}); +import subject from '../app/src/redux/reducers/slice/contextReducer.ts'; + +describe('contextReducer test', () => { + let state; + + beforeEach(() => { + state = { + allContext: [], + }; + }); + + describe('default state', () => { + it('should return a default state when given an undefined input', () => { + expect(subject(undefined, { type: undefined })).toEqual(state); + }); + }); + + describe('unrecognized action types', () => { + it('should return the original state without any duplication', () => { + expect(subject(state, { type: 'REMOVE_STATE' })).toBe(state); + }); + }); + + describe('addContext', () => { + const action = { + type: 'context/addContext', + payload: { + name: 'Theme Context', + }, + }; + + it('adds a context', () => { + const { allContext } = subject(state, action); + expect(allContext[0]).toEqual({ + name: 'Theme Context', + values: [], + components: [], + }); + }); + + it('returns a state object not strictly equal to the original', () => { + const newState = subject(state, action); + expect(newState).not.toBe(state); + }); + + it('should immutably update the nested state object', () => { + const { allContext } = subject(state, action); + expect(allContext).not.toBe(state.allContext); + }); + }); + + // OLD ADD CONTEX TEST + + // describe('ADD_CONTEXT', () => { + // const action = { + // type: 'ADD_CONTEXT', + // payload: { + // name: 'Theme Context' + // } + // }; + + // it('adds a context', () => { + // const { allContext } = subject(state, action); + // expect(allContext[0]).toEqual({ + // name: 'Theme Context', + // values: [], + // components: [] + // }); + // }); + + // it('returns a state object not strictly equal to the original', () => { + // const newState = subject(state, action); + // expect(newState).not.toBe(state); + // }); + + // it('should immutably update the nested state object', () => { + // const { allContext } = subject(state, action); + // expect(allContext).not.toBe(state.allContext); + // }); + // }); + + describe('addContextValues', () => { + beforeEach(() => { + state = { + allContext: [ + { + name: 'Theme Context', + values: [], + components: [], + }, + ], + }; + }); + + const action = { + type: 'context/addContextValues', + payload: { + name: 'Theme Context', + inputKey: 'Theme Color', + inputValue: 'Dark', + }, + }; + + it('adds a key-value pair to values array of the specified context', () => { + const { allContext } = subject(state, action); + expect(allContext[0].values.length).toEqual(1); + expect(allContext[0].values[0].key).toEqual('Theme Color'); + expect(allContext[0].values[0].value).toEqual('Dark'); + }); + + it('includes an allContext not strictly equal to the original', () => { + const { allContext } = subject(state, action); + + expect(allContext).not.toBe(state.allContext); + }); + }); + + describe('deleteContext', () => { + let action; + beforeEach(() => { + state = { + allContext: [ + { + name: 'Theme Context', + values: [], + components: [], + }, + { + name: 'To be deleted', + values: [], + components: [], + }, + ], + }; + + action = { + type: 'context/deleteContext', + payload: { + name: 'Theme Context', + }, + }; + }); + + it('removes specified context from the state', () => { + const { allContext } = subject(state, action); + + expect(allContext.length).toEqual(1); + }); + + it('includes an allContext not strictly equal to the original', () => { + const { allContext } = subject(state, action); + + expect(allContext).not.toBe(state.allContext); + }); + }); + + describe('addComponentToContext', () => { + beforeEach(() => { + state = { + allContext: [ + { + name: 'Theme Context', + values: [], + components: [], + }, + ], + }; + }); + + const action = { + type: 'context/addComponentToContext', + payload: { + context: { + name: 'Theme Context', + }, + component: { + name: 'Main Component', + }, + }, + }; + + it('adds a new component to the specified context', () => { + const { allContext } = subject(state, action); + + expect(allContext[0].components.length).toEqual(1); + expect(allContext[0].components[0]).toEqual('Main Component'); + }); + + it('includes an allContext not strictly equal to the original', () => { + const { allContext } = subject(state, action); + + expect(allContext).not.toBe(state.allContext); + }); + }); +}); diff --git a/__tests__/gql.projects.test.ts b/__tests__/gql.projects.test.ts index 212ea7c7f..e77fa2a22 100644 --- a/__tests__/gql.projects.test.ts +++ b/__tests__/gql.projects.test.ts @@ -1,143 +1,143 @@ -// /** -// * @jest-environment node -// */ - -// const { Mongoose } = require('mongoose'); -// const request = require('supertest'); -// const http = require('http'); -// const app = require('../server/server'); -// const mock = require('../mockData'); - -// tests user signup and login routes -xdescribe('GraphQL tests', () => { - let server; - // Mutation test variables - const projectId = '62fd62c6d37748133a6fdc81'; // Must use a valid projectId from the database. NOTE: This should be revised for each Production Project Team since the database store different projectId - const testNum = 100; - const makeCopyUserIdTest = '604333d10004ad51c899e250'; - const makeCopyUsernameTest = 'test1'; - let makeCopyProjId = ''; - beforeAll((done) => { - server = http.createServer(app); - server.listen(done); - }); - afterAll((done) => { - Mongoose.disconnect(); - server.close(done); - }); - // GraphQL Query - - xdescribe('Testing GraphQL query', () => { - it('getAllProjects should return more than 1 project by default', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.GET_PROJECTS - }) - .expect(200) - .then((res) => - expect(res.body.data.getAllProjects.length).toBeGreaterThanOrEqual(1) - )); - it('getAllProjects should return projects that matches the provided userId', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.GET_PROJECTS, - variables: { - userId: '604d21b2b61a1c95f2dc9105' - } - }) - .expect(200) - .then((res) => - expect(res.body.data.getAllProjects[0].userId).toBe( - '604d21b2b61a1c95f2dc9105' - ) - )); - }); - // GraphQL Mutation - - xdescribe('Testing GraphQL mutation', () => { - // Add likes - it('addLike should update the "likes" field of the project document', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.ADD_LIKE, - variables: { - projId: projectId, - likes: testNum - } - }) - .expect(200) - .then((res) => expect(res.body.data.addLike.likes).toBe(testNum))); - // Publish project - it('Should set the "published" on the project document to TRUE', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.PUBLISH_PROJECT, - variables: { - projId: projectId, - published: true - } - }) - .expect(200) - .then((res) => - expect(res.body.data.publishProject.published).toBe(true) - )); - it('Should set the "published" on the project document to FALSE', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.PUBLISH_PROJECT, - variables: { - projId: projectId, - published: false - } - }) - .expect(200) - .then((res) => - expect(res.body.data.publishProject.published).toBe(false) - )); - // Make copy - it('Should make a copy of an existing project and change the userId and userName', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.MAKE_COPY, - variables: { - projId: projectId, - userId: makeCopyUserIdTest, - username: makeCopyUsernameTest - } - }) - .expect(200) - .then((res) => { - expect(res.body.data.makeCopy.userId).toBe(makeCopyUserIdTest); - expect(res.body.data.makeCopy.username).toBe(makeCopyUsernameTest); - makeCopyProjId = res.body.data.makeCopy.id; - })); - - // Delete copy - it('Should make a copy of an existing project and change the userId and userName', () => - request(server) - .post('/graphql') - .set('Content-Type', 'application/json') - .send({ - query: mock.DELETE_PROJECT, - variables: { - projId: makeCopyProjId - } - }) - .expect(200) - .then((res) => { - expect(res.body.data.deleteProject.id).toBe(makeCopyProjId); - })); - }); -}); +// /** +// * @jest-environment node +// */ + +// const { Mongoose } = require('mongoose'); +// const request = require('supertest'); +// const http = require('http'); +// const app = require('../server/server'); +// const mock = require('../mockData'); + +// tests user signup and login routes +xdescribe('GraphQL tests', () => { + let server; + // Mutation test variables + const projectId = '62fd62c6d37748133a6fdc81'; // Must use a valid projectId from the database. NOTE: This should be revised for each Production Project Team since the database store different projectId + const testNum = 100; + const makeCopyUserIdTest = '604333d10004ad51c899e250'; + const makeCopyUsernameTest = 'test1'; + let makeCopyProjId = ''; + beforeAll((done) => { + server = http.createServer(app); + server.listen(done); + }); + afterAll((done) => { + Mongoose.disconnect(); + server.close(done); + }); + // GraphQL Query + + xdescribe('Testing GraphQL query', () => { + it('getAllProjects should return more than 1 project by default', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.GET_PROJECTS + }) + .expect(200) + .then((res) => + expect(res.body.data.getAllProjects.length).toBeGreaterThanOrEqual(1) + )); + it('getAllProjects should return projects that matches the provided userId', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.GET_PROJECTS, + variables: { + userId: '604d21b2b61a1c95f2dc9105' + } + }) + .expect(200) + .then((res) => + expect(res.body.data.getAllProjects[0].userId).toBe( + '604d21b2b61a1c95f2dc9105' + ) + )); + }); + // GraphQL Mutation + + xdescribe('Testing GraphQL mutation', () => { + // Add likes + it('addLike should update the "likes" field of the project document', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.ADD_LIKE, + variables: { + projId: projectId, + likes: testNum + } + }) + .expect(200) + .then((res) => expect(res.body.data.addLike.likes).toBe(testNum))); + // Publish project + it('Should set the "published" on the project document to TRUE', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.PUBLISH_PROJECT, + variables: { + projId: projectId, + published: true + } + }) + .expect(200) + .then((res) => + expect(res.body.data.publishProject.published).toBe(true) + )); + it('Should set the "published" on the project document to FALSE', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.PUBLISH_PROJECT, + variables: { + projId: projectId, + published: false + } + }) + .expect(200) + .then((res) => + expect(res.body.data.publishProject.published).toBe(false) + )); + // Make copy + it('Should make a copy of an existing project and change the userId and userName', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.MAKE_COPY, + variables: { + projId: projectId, + userId: makeCopyUserIdTest, + username: makeCopyUsernameTest + } + }) + .expect(200) + .then((res) => { + expect(res.body.data.makeCopy.userId).toBe(makeCopyUserIdTest); + expect(res.body.data.makeCopy.username).toBe(makeCopyUsernameTest); + makeCopyProjId = res.body.data.makeCopy.id; + })); + + // Delete copy + it('Should make a copy of an existing project and change the userId and userName', () => + request(server) + .post('/graphql') + .set('Content-Type', 'application/json') + .send({ + query: mock.DELETE_PROJECT, + variables: { + projId: makeCopyProjId + } + }) + .expect(200) + .then((res) => { + expect(res.body.data.deleteProject.id).toBe(makeCopyProjId); + })); + }); +}); diff --git a/__tests__/helper.test.tsx b/__tests__/helper.test.tsx index 14ce14b3b..c5f62655e 100644 --- a/__tests__/helper.test.tsx +++ b/__tests__/helper.test.tsx @@ -1,7 +1,7 @@ -import randomPassword from '../app/src/helperFunctions/randomPassword'; - -describe('Random Password', () => { - test('should generate password with 18 characters', () => { - expect(randomPassword()).toHaveLength(18); - }); -}); +import randomPassword from '../app/src/helperFunctions/randomPassword'; + +describe('Random Password', () => { + test('should generate password with 18 characters', () => { + expect(randomPassword()).toHaveLength(18); + }); +}); diff --git a/__tests__/marketplace.test.tsx b/__tests__/marketplace.test.tsx index d08680931..f1c8a38c6 100644 --- a/__tests__/marketplace.test.tsx +++ b/__tests__/marketplace.test.tsx @@ -1,133 +1,133 @@ -import '@testing-library/jest-dom/extend-expect'; - -import { fireEvent, render, screen } from '@testing-library/react'; - -import MarketplaceCard from '../app/src/components/marketplace/MarketplaceCard'; -import MarketplaceCardContainer from '../app/src/components/marketplace/MarketplaceCardContainer'; -import { Provider } from 'react-redux'; -import React from 'react'; -import SearchBar from '../app/src/components/marketplace/Searchbar'; -import axios from 'axios'; -import store from '../app/src/redux/store'; - -// Mocking the axios module to avoid actual network calls -jest.mock('axios'); -jest.mock( - 'resources/marketplace_images/marketplace_image.png', - () => 'mock-image-url' -); - -describe('MarketplaceCard Render Test', () => { - const mockProject = { - _id: 123, - name: 'Sample Project', - username: 'user123', - forked: 'false', - comments: [], - createdAt: new Date(), - likes: 0, - project: { - id: 'sample-project-id' - }, - published: true, - userId: 123456 - }; - - it('displays project name and username', () => { - render( - - - - ); - - expect(screen.getByText('Sample Project')).toBeInTheDocument(); - expect(screen.getByText('user123')).toBeInTheDocument(); - }); -}); - -describe('MarketplaceContainer', () => { - const mockProjects = [ - { - _id: 1, - name: 'Project 1', - username: 'user1' - }, - { - _id: 2, - name: 'Project 2', - username: 'user2' - } - ]; - - beforeEach(() => { - // Set up mock axios call for every test - axios.get = jest.fn().mockResolvedValue({ data: mockProjects }); - }); - - it('renders multiple MarketplaceCards', () => { - render( - - - - ); - - expect(screen.getByText('Project 1')).toBeInTheDocument(); - expect(screen.getByText('user1')).toBeInTheDocument(); - expect(screen.getByText('Project 2')).toBeInTheDocument(); - expect(screen.getByText('user2')).toBeInTheDocument(); - }); -}); - -const mockProjects = [ - { - name: 'Sample Project', - username: 'user123' - }, - { - name: 'Test Project', - username: 'user_test' - }, - { - name: 'Hello Project', - username: 'hello_user' - } -]; - -describe('SearchBar Component', () => { - it('updates the text field value on change', () => { - const updateDisplayProjects = jest.fn(); - - render( - - ); - - const textField = screen.getByLabelText('Search') as HTMLInputElement; - fireEvent.change(textField, { target: { value: 'Sample' } }); - - expect(textField.value).toBe('Sample'); - }); - - it('filters projects by username', () => { - const updateDisplayProjects = jest.fn(); - - render( - - ); - - const textField = screen.getByLabelText('Search'); - fireEvent.change(textField, { target: { value: 'test' } }); - - // Using setImmediate to wait for useEffect to execute. - setTimeout(() => { - expect(updateDisplayProjects).toHaveBeenCalledWith([ - { name: 'Test Project', username: 'user_test' } - ]); - }); - }); -}); +import '@testing-library/jest-dom/extend-expect'; + +import { fireEvent, render, screen } from '@testing-library/react'; + +import MarketplaceCard from '../app/src/components/marketplace/MarketplaceCard'; +import MarketplaceCardContainer from '../app/src/components/marketplace/MarketplaceCardContainer'; +import { Provider } from 'react-redux'; +import React from 'react'; +import SearchBar from '../app/src/components/marketplace/Searchbar'; +import axios from 'axios'; +import store from '../app/src/redux/store'; + +// Mocking the axios module to avoid actual network calls +jest.mock('axios'); +jest.mock( + 'resources/marketplace_images/marketplace_image.png', + () => 'mock-image-url' +); + +describe('MarketplaceCard Render Test', () => { + const mockProject = { + _id: 123, + name: 'Sample Project', + username: 'user123', + forked: 'false', + comments: [], + createdAt: new Date(), + likes: 0, + project: { + id: 'sample-project-id' + }, + published: true, + userId: 123456 + }; + + it('displays project name and username', () => { + render( + + + + ); + + expect(screen.getByText('Sample Project')).toBeInTheDocument(); + expect(screen.getByText('user123')).toBeInTheDocument(); + }); +}); + +describe('MarketplaceContainer', () => { + const mockProjects = [ + { + _id: 1, + name: 'Project 1', + username: 'user1' + }, + { + _id: 2, + name: 'Project 2', + username: 'user2' + } + ]; + + beforeEach(() => { + // Set up mock axios call for every test + axios.get = jest.fn().mockResolvedValue({ data: mockProjects }); + }); + + it('renders multiple MarketplaceCards', () => { + render( + + + + ); + + expect(screen.getByText('Project 1')).toBeInTheDocument(); + expect(screen.getByText('user1')).toBeInTheDocument(); + expect(screen.getByText('Project 2')).toBeInTheDocument(); + expect(screen.getByText('user2')).toBeInTheDocument(); + }); +}); + +const mockProjects = [ + { + name: 'Sample Project', + username: 'user123' + }, + { + name: 'Test Project', + username: 'user_test' + }, + { + name: 'Hello Project', + username: 'hello_user' + } +]; + +describe('SearchBar Component', () => { + it('updates the text field value on change', () => { + const updateDisplayProjects = jest.fn(); + + render( + + ); + + const textField = screen.getByLabelText('Search') as HTMLInputElement; + fireEvent.change(textField, { target: { value: 'Sample' } }); + + expect(textField.value).toBe('Sample'); + }); + + it('filters projects by username', () => { + const updateDisplayProjects = jest.fn(); + + render( + + ); + + const textField = screen.getByLabelText('Search'); + fireEvent.change(textField, { target: { value: 'test' } }); + + // Using setImmediate to wait for useEffect to execute. + setTimeout(() => { + expect(updateDisplayProjects).toHaveBeenCalledWith([ + { name: 'Test Project', username: 'user_test' } + ]); + }); + }); +}); diff --git a/__tests__/playwright/example.spec.ts b/__tests__/playwright/example.spec.ts index bf3d92ab5..67ffac6f9 100644 --- a/__tests__/playwright/example.spec.ts +++ b/__tests__/playwright/example.spec.ts @@ -1,18 +1,18 @@ -// import { test, expect } from '@playwright/test'; - -// test('has title', async ({ page }) => { -// await page.goto('https://app.reactype.dev/#/'); - -// // Expect a title "to contain" a substring. -// await expect(page).toHaveTitle('ReacType'); -// }); - -// test('get started link', async ({ page }) => { -// await page.goto('https://playwright.dev/'); - -// // Click the get started link. -// await page.getByRole('link', { name: 'Get started' }).click(); - -// // Expects the URL to contain intro. -// await expect(page).toHaveURL(/.*intro/); -// }); +// import { test, expect } from '@playwright/test'; + +// test('has title', async ({ page }) => { +// await page.goto('https://app.reactype.dev/#/'); + +// // Expect a title "to contain" a substring. +// await expect(page).toHaveTitle('ReacType'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects the URL to contain intro. +// await expect(page).toHaveURL(/.*intro/); +// }); diff --git a/__tests__/projects.test.ts b/__tests__/projects.test.ts index f9ec600e1..4b65abfb2 100644 --- a/__tests__/projects.test.ts +++ b/__tests__/projects.test.ts @@ -1,71 +1,71 @@ -/** - * @jest-environment node - */ - -const { Mongoose } = require('mongoose'); -const request = require('supertest'); -// initializes the project to be sent to server/DB -import mockData from '../mockData'; -import app from '../server/server'; -const http = require('http'); -const { state, projectToSave } = mockData; - -// save and get projects endpoint testing -describe('Project endpoints tests', () => { - let server; - beforeAll((done) => { - server = http.createServer(app); - server.listen(done); - }); - afterAll((done) => { - Mongoose.disconnect().then(() => { - // Close the HTTP server - server.close(done); - }); - }); - // test saveProject endpoint - describe('/saveProject', () => { - describe('/POST', () => { - it('responds with a status of 200 and json object equal to project sent', () => { - return request(server) - .post('/saveProject') - .set('Accept', 'application/json') - .send(projectToSave) - .expect(200) - .expect('Content-Type', /application\/json/) - .then((res) => expect(res.body.name).toBe(projectToSave.name)); - }); - }); - }); - // test getProjects endpoint - describe('/getProjects', () => { - describe('POST', () => { - it('responds with status of 200 and json object equal to an array of user projects', () => { - return request(server) - .post('/getProjects') - .set('Accept', 'application/json') - .send({ userId: projectToSave.userId }) - .expect(200) - .expect('Content-Type', /json/) - .then((res) => { - expect(Array.isArray(res.body)).toBeTruthy; - expect(res.body[0].name).toBe(state.name); - }); - }); - }); - }); - // test deleteProject endpoint - describe('/deleteProject', () => { - describe('DELETE', () => { - const { name, userId } = projectToSave; - it('responds with status of 200 and json object equal to deleted project', () => { - return request(server) - .delete('/deleteProject') - .set('Accept', 'application/json') - .send({ name, userId }) - .expect(200) - .then((res) => expect(res.body.name).toBe(projectToSave.name)); - }); - }); - }); -}); +/** + * @jest-environment node + */ + +const { Mongoose } = require('mongoose'); +const request = require('supertest'); +// initializes the project to be sent to server/DB +import mockData from '../mockData'; +import app from '../server/server'; +const http = require('http'); +const { state, projectToSave } = mockData; + +// save and get projects endpoint testing +describe('Project endpoints tests', () => { + let server; + beforeAll((done) => { + server = http.createServer(app); + server.listen(done); + }); + afterAll((done) => { + Mongoose.disconnect().then(() => { + // Close the HTTP server + server.close(done); + }); + }); + // test saveProject endpoint + describe('/saveProject', () => { + describe('/POST', () => { + it('responds with a status of 200 and json object equal to project sent', () => { + return request(server) + .post('/saveProject') + .set('Accept', 'application/json') + .send(projectToSave) + .expect(200) + .expect('Content-Type', /application\/json/) + .then((res) => expect(res.body.name).toBe(projectToSave.name)); + }); + }); + }); + // test getProjects endpoint + describe('/getProjects', () => { + describe('POST', () => { + it('responds with status of 200 and json object equal to an array of user projects', () => { + return request(server) + .post('/getProjects') + .set('Accept', 'application/json') + .send({ userId: projectToSave.userId }) + .expect(200) + .expect('Content-Type', /json/) + .then((res) => { + expect(Array.isArray(res.body)).toBeTruthy; + expect(res.body[0].name).toBe(state.name); + }); + }); + }); + }); + // test deleteProject endpoint + describe('/deleteProject', () => { + describe('DELETE', () => { + const { name, userId } = projectToSave; + it('responds with status of 200 and json object equal to deleted project', () => { + return request(server) + .delete('/deleteProject') + .set('Accept', 'application/json') + .send({ name, userId }) + .expect(200) + .then((res) => expect(res.body.name).toBe(projectToSave.name)); + }); + }); + }); +}); diff --git a/__tests__/server.test.tsx b/__tests__/server.test.tsx index d1c339383..62743c3d3 100644 --- a/__tests__/server.test.tsx +++ b/__tests__/server.test.tsx @@ -1,445 +1,445 @@ -/** - * @jest-environment node - */ - -import marketplaceController from '../server/controllers/marketplaceController'; -import sessionController from '../server/controllers/sessionController'; -import app from '../server/server'; -import mockData from '../mockData'; -import { profileEnd } from 'console'; -import { Projects, Users, Sessions } from '../server/models/reactypeModels'; -const request = require('supertest'); -const mongoose = require('mongoose'); -const mockNext = jest.fn(); // Mock nextFunction -const MONGO_DB = import.meta.env.MONGO_DB_TEST; -const { state, projectToSave, user } = mockData; -const PORT = 8080; - -beforeAll(async () => { - await mongoose.connect(MONGO_DB, { - useNewUrlParser: true, - useUnifiedTopology: true - }); -}); - -afterAll(async () => { - const result = await Projects.deleteMany({}); //clear the projects collection after tests are done - const result2 = await Users.deleteMany({ - _id: { $ne: '64f551e5b28d5292975e08c8' } - }); //clear the users collection after tests are done except for the mockdata user account - const result3 = await Sessions.deleteMany({ - cookieId: { $ne: '64f551e5b28d5292975e08c8' } - }); - await mongoose.connection.close(); -}); - -describe('Server endpoint tests', () => { - it('should pass this test request', async () => { - const response = await request(app).get('/test'); - expect(response.status).toBe(200); - expect(response.text).toBe('test request is working'); - }); - - // // test saveProject endpoint - // describe('/login', () => { - // describe('/POST', () => { - // it('responds with a status of 200 and json object equal to project sent', async () => { - // return request(app) - // .post('/login') - // .set('Cookie', [`ssid=${user.userId}`]) - // .set('Accept', 'application/json') - // .send(projectToSave) - // .expect(200) - // .expect('Content-Type', /application\/json/) - // .then((res) => expect(res.body.name).toBe(projectToSave.name)); - // }); - // // }); - // }); - // }); - - // test saveProject endpoint - describe('/saveProject', () => { - describe('POST', () => { - it('responds with a status of 200 and json object equal to project sent', async () => { - return request(app) - .post('/saveProject') - .set('Cookie', [`ssid=${user.userId}`]) - .set('Accept', 'application/json') - .send(projectToSave) - .expect(200) - .expect('Content-Type', /application\/json/) - .then((res) => expect(res.body.name).toBe(projectToSave.name)); - }); - // }); - }); - }); - // test getProjects endpoint - describe('/getProjects', () => { - describe('POST', () => { - it('responds with status of 200 and json object equal to an array of user projects', () => { - return request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }) - .expect(200) - .expect('Content-Type', /json/) - .then((res) => { - expect(Array.isArray(res.body)).toBeTruthy; - expect(res.body[0].name).toBe(state.name); - }); - }); - }); - }); - // test deleteProject endpoint - describe('/deleteProject', () => { - describe('DELETE', () => { - it('responds with status of 200 and json object equal to deleted project', async () => { - const response: Response = await request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }); - const _id: String = response.body[0]._id; - const userId: String = user.userId; - return request(app) - .delete('/deleteProject') - .set('Cookie', [`ssid=${user.userId}`]) - .set('Content-Type', 'application/json') - .send({ _id, userId }) - .expect(200) - .then((res) => expect(res.body._id).toBe(_id)); - }); - }); - }); - - //test publishProject endpoint - describe('/publishProject', () => { - describe('POST', () => { - it('responds with status of 200 and json object equal to published project', async () => { - const projObj = await request(app) - .post('/saveProject') - .set('Cookie', [`ssid=${user.userId}`]) - .set('Accept', 'application/json') - .send(projectToSave); - const _id: String = projObj.body._id; - const project: String = projObj.body.project; - const comments: String = projObj.body.comments; - const username: String = projObj.body.username; - const name: String = projObj.body.name; - const userId: String = user.userId; - return request(app) - .post('/publishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name - .expect(200) - .then((res) => { - expect(res.body._id).toBe(_id); - expect(res.body.published).toBe(true); - }); - }); - it('responds with status of 500 and error if userId and cookie ssid do not match', async () => { - const projObj: Response = await request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }); - const _id: String = projObj.body[0]._id; - const project: String = projObj.body[0].project; - const comments: String = projObj.body[0].comments; - const username: String = projObj.body[0].username; - const name: String = projObj.body[0].name; - const userId: String = 'ERROR'; - return request(app) - .post('/publishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name - .expect(500) - .then((res) => { - expect(res.body.err).not.toBeNull(); - }); - }); - it('responds with status of 500 and error if _id was not a valid mongo ObjectId', async () => { - const projObj: Response = await request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }); - const _id: String = 'ERROR'; - const project: String = projObj.body[0].project; - const comments: String = projObj.body[0].comments; - const username: String = user.username; - const name: String = projObj.body[0].name; - const userId: String = user.userId; - - return request(app) - .post('/publishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name - .expect(200) - .then((res) => { - expect(res.body._id).not.toEqual(_id); - }); - }); - }); - }); - - //test getMarketplaceProjects endpoint - describe('/getMarketplaceProjects', () => { - //most recent project should be the one from publishProject - - describe('GET', () => { - it('responds with status of 200 and json object equal to unpublished project', async () => { - return request(app) - .get('/getMarketplaceProjects') - .set('Content-Type', 'application/json') - .expect(200) - .then((res) => { - expect(Array.isArray(res.body)).toBe(true); - expect(res.body[0]._id).toBeTruthy; - }); - }); - }); - }); - - //test cloneProject endpoint - describe('/cloneProject/:docId', () => { - describe('GET', () => { - it('responds with status of 200 and json object equal to cloned project', async () => { - const projObj = await request(app) - .get('/getMarketplaceProjects') - .set('Content-Type', 'application/json'); - - return request(app) - .get(`/cloneProject/${projObj.body[0]._id}`) - .set('Cookie', [`ssid=${user.userId}`]) // Set the cookie - .query({ username: user.username }) - .expect(200) - .then((res) => { - expect(res.body.forked).toBeTruthy; - expect(res.body.username).toBe(user.username); - }); - }); - it('responds with status of 500 and error', async () => { - const projObj = await request(app) - .get('/getMarketplaceProjects') - .set('Content-Type', 'application/json'); - - return request(app) - .get(`/cloneProject/${projObj.body[0]._id}`) - .set('Cookie', [`ssid=${user.userId}`]) // Set the cookie - .query({ username: [] }) - .expect(500) - .then((res) => { - expect(res.body.err).not.toBeNull(); - }); - }); - }); - }); - - //test unpublishProject endpoint - describe('/unpublishProject', () => { - describe('PATCH', () => { - it('responds with status of 200 and json object equal to unpublished project', async () => { - const response: Response = await request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }); //most recent project should be the one from publishProject - const _id: String = response.body[0]._id; - const userId: String = user.userId; - return request(app) - .patch('/unpublishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, userId }) - .expect(200) - .then((res) => { - expect(res.body._id).toBe(_id); - expect(res.body.published).toBe(false); - }); - }); - it('responds with status of 500 and error if userId and cookie ssid do not match', async () => { - const projObj: Response = await request(app) - .post('/getProjects') - .set('Accept', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId: projectToSave.userId }); - const _id: String = projObj.body[0]._id; - const project: String = projObj.body[0].project; - const comments: String = projObj.body[0].comments; - const username: String = projObj.body[0].username; - const name: String = projObj.body[0].name; - let userId: String = user.userId; - await request(app) //publishing a project first - .post('/publishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, project, comments, userId, username, name }); - - userId = 'ERROR'; - return request(app) - .patch('/unpublishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ _id, userId }) - .expect(500) - .then((res) => { - expect(res.body.err).not.toBeNull(); - }); - }); - it('responds with status of 500 and error if _id was not a string', async () => { - const userId: String = user.userId; - - return request(app) - .patch('/unpublishProject') - .set('Content-Type', 'application/json') - .set('Cookie', [`ssid=${user.userId}`]) - .send({ userId }) - .expect(500) - .then((res) => { - expect(res.body.err).not.toBeNull(); - }); - }); - }); - }); -}); - -describe('SessionController tests', () => { - describe('isLoggedIn', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - // Mock Express request and response objects and next function - const mockReq: any = { - cookies: null, //trying to trigger if cookies was not assigned - body: { - userId: 'sampleUserId' // Set up a sample userId in the request body - } - }; - const mockRes: any = { - json: jest.fn(), - status: jest.fn(), - redirect: jest.fn() - }; - const next = jest.fn(); - it('Assign userId from request body to cookieId', async () => { - // Call isLoggedIn - await sessionController.isLoggedIn(mockReq, mockRes, next); - expect(mockRes.redirect).toHaveBeenCalledWith('/'); - // Ensure that next() was called - }); - it('Trigger a database query error for findOne', async () => { - const mockFindOne = jest - .spyOn(mongoose.model('Sessions'), 'findOne') - .mockImplementation(() => { - throw new Error('Database query error'); - }); - // Call isLoggedIn - await sessionController.isLoggedIn(mockReq, mockRes, next); - // Ensure that next() was called with the error - expect(next).toHaveBeenCalledWith( - expect.objectContaining({ - log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive - }) - ); - - mockFindOne.mockRestore(); - }); - }); - - describe('startSession', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - it('Trigger a database query error for findOne', async () => { - const mockReq: any = { - cookies: projectToSave.userId, //trying to trigger if cookies was not assigned - body: { - userId: 'sampleUserId' // Set up a sample userId in the request body - } - }; - const mockRes: any = { - json: jest.fn(), - status: jest.fn(), - redirect: jest.fn(), - locals: { id: projectToSave.userId } - }; - - const next = jest.fn(); - const findOneMock = jest.spyOn( - mongoose.model('Sessions'), - 'findOne' - ) as jest.Mock; - findOneMock.mockImplementation( - (query: any, callback: (err: any, ses: any) => void) => { - callback(new Error('Database query error'), null); - } - ); - // Call startSession - await sessionController.startSession(mockReq, mockRes, next); - // Check that next() was called with the error - expect(next).toHaveBeenCalledWith( - expect.objectContaining({ - log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive - }) - ); - - findOneMock.mockRestore(); - }); - - xit('Check if a new Session is created', async () => { - //not working for some reason cannot get mocknext() to be called in test? - - const mockReq: any = { - cookies: projectToSave.userId, //trying to trigger if cookies was not assigned - body: { - userId: 'sampleUserId' // Set up a sample userId in the request body - } - }; - const mockRes: any = { - json: jest.fn(), - status: jest.fn(), - redirect: jest.fn(), - locals: { id: 'testID' } //a sesion id that doesnt exist - }; - - const mockNext = jest.fn(); - - //Call startSession - // Wrap your test logic in an async function - await sessionController.startSession(mockReq, mockRes, mockNext); - - //check if it reaches next() - //await expect(mockRes.locals.ssid).toBe('testID'); - expect(mockNext).toHaveBeenCalled(); - }); - }); -}); - -// describe('marketplaceController Middleware', () => { -// describe('getProjects tests', () => { -// it('should add the projects as an array to res.locals', () => { -// const req = {}; -// const res = { locals: {} }; -// console.log(marketplaceController.getPublishedProjects); -// console.log(typeof marketplaceController.getPublishedProjects); -// marketplaceController.getPublishedProjects(req, res, mockNext); -// expect(Array.isArray(res.locals.publishedProjects)).toBe(true); -// expect(mockNext).toHaveBeenCalled(); -// }); -// }); - -// it('should send an error response if there is an error in the middleware', () => { -// const req = { user: { isAuthenticated: false } }; -// const res = mockResponse(); - -// marketplaceController.authenticateMiddleware(req, res, mockNext); - -// expect(res.status).toHaveBeenCalledWith(500); -// expect(res.json).toHaveBeenCalledWith({ err: 'Error in marketplaceController.getPublishedProjects, check server logs for details' }); -// }); -// }); +/** + * @jest-environment node + */ + +import marketplaceController from '../server/controllers/marketplaceController'; +import sessionController from '../server/controllers/sessionController'; +import app from '../server/server'; +import mockData from '../mockData'; +import { profileEnd } from 'console'; +import { Projects, Users, Sessions } from '../server/models/reactypeModels'; +const request = require('supertest'); +const mongoose = require('mongoose'); +const mockNext = jest.fn(); // Mock nextFunction +const MONGO_DB = import.meta.env.MONGO_DB_TEST; +const { state, projectToSave, user } = mockData; +const PORT = 8080; + +beforeAll(async () => { + await mongoose.connect(MONGO_DB, { + useNewUrlParser: true, + useUnifiedTopology: true + }); +}); + +afterAll(async () => { + const result = await Projects.deleteMany({}); //clear the projects collection after tests are done + const result2 = await Users.deleteMany({ + _id: { $ne: '64f551e5b28d5292975e08c8' } + }); //clear the users collection after tests are done except for the mockdata user account + const result3 = await Sessions.deleteMany({ + cookieId: { $ne: '64f551e5b28d5292975e08c8' } + }); + await mongoose.connection.close(); +}); + +describe('Server endpoint tests', () => { + it('should pass this test request', async () => { + const response = await request(app).get('/test'); + expect(response.status).toBe(200); + expect(response.text).toBe('test request is working'); + }); + + // // test saveProject endpoint + // describe('/login', () => { + // describe('/POST', () => { + // it('responds with a status of 200 and json object equal to project sent', async () => { + // return request(app) + // .post('/login') + // .set('Cookie', [`ssid=${user.userId}`]) + // .set('Accept', 'application/json') + // .send(projectToSave) + // .expect(200) + // .expect('Content-Type', /application\/json/) + // .then((res) => expect(res.body.name).toBe(projectToSave.name)); + // }); + // // }); + // }); + // }); + + // test saveProject endpoint + describe('/saveProject', () => { + describe('POST', () => { + it('responds with a status of 200 and json object equal to project sent', async () => { + return request(app) + .post('/saveProject') + .set('Cookie', [`ssid=${user.userId}`]) + .set('Accept', 'application/json') + .send(projectToSave) + .expect(200) + .expect('Content-Type', /application\/json/) + .then((res) => expect(res.body.name).toBe(projectToSave.name)); + }); + // }); + }); + }); + // test getProjects endpoint + describe('/getProjects', () => { + describe('POST', () => { + it('responds with status of 200 and json object equal to an array of user projects', () => { + return request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }) + .expect(200) + .expect('Content-Type', /json/) + .then((res) => { + expect(Array.isArray(res.body)).toBeTruthy; + expect(res.body[0].name).toBe(state.name); + }); + }); + }); + }); + // test deleteProject endpoint + describe('/deleteProject', () => { + describe('DELETE', () => { + it('responds with status of 200 and json object equal to deleted project', async () => { + const response: Response = await request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }); + const _id: String = response.body[0]._id; + const userId: String = user.userId; + return request(app) + .delete('/deleteProject') + .set('Cookie', [`ssid=${user.userId}`]) + .set('Content-Type', 'application/json') + .send({ _id, userId }) + .expect(200) + .then((res) => expect(res.body._id).toBe(_id)); + }); + }); + }); + + //test publishProject endpoint + describe('/publishProject', () => { + describe('POST', () => { + it('responds with status of 200 and json object equal to published project', async () => { + const projObj = await request(app) + .post('/saveProject') + .set('Cookie', [`ssid=${user.userId}`]) + .set('Accept', 'application/json') + .send(projectToSave); + const _id: String = projObj.body._id; + const project: String = projObj.body.project; + const comments: String = projObj.body.comments; + const username: String = projObj.body.username; + const name: String = projObj.body.name; + const userId: String = user.userId; + return request(app) + .post('/publishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name + .expect(200) + .then((res) => { + expect(res.body._id).toBe(_id); + expect(res.body.published).toBe(true); + }); + }); + it('responds with status of 500 and error if userId and cookie ssid do not match', async () => { + const projObj: Response = await request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }); + const _id: String = projObj.body[0]._id; + const project: String = projObj.body[0].project; + const comments: String = projObj.body[0].comments; + const username: String = projObj.body[0].username; + const name: String = projObj.body[0].name; + const userId: String = 'ERROR'; + return request(app) + .post('/publishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name + .expect(500) + .then((res) => { + expect(res.body.err).not.toBeNull(); + }); + }); + it('responds with status of 500 and error if _id was not a valid mongo ObjectId', async () => { + const projObj: Response = await request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }); + const _id: String = 'ERROR'; + const project: String = projObj.body[0].project; + const comments: String = projObj.body[0].comments; + const username: String = user.username; + const name: String = projObj.body[0].name; + const userId: String = user.userId; + + return request(app) + .post('/publishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name + .expect(200) + .then((res) => { + expect(res.body._id).not.toEqual(_id); + }); + }); + }); + }); + + //test getMarketplaceProjects endpoint + describe('/getMarketplaceProjects', () => { + //most recent project should be the one from publishProject + + describe('GET', () => { + it('responds with status of 200 and json object equal to unpublished project', async () => { + return request(app) + .get('/getMarketplaceProjects') + .set('Content-Type', 'application/json') + .expect(200) + .then((res) => { + expect(Array.isArray(res.body)).toBe(true); + expect(res.body[0]._id).toBeTruthy; + }); + }); + }); + }); + + //test cloneProject endpoint + describe('/cloneProject/:docId', () => { + describe('GET', () => { + it('responds with status of 200 and json object equal to cloned project', async () => { + const projObj = await request(app) + .get('/getMarketplaceProjects') + .set('Content-Type', 'application/json'); + + return request(app) + .get(`/cloneProject/${projObj.body[0]._id}`) + .set('Cookie', [`ssid=${user.userId}`]) // Set the cookie + .query({ username: user.username }) + .expect(200) + .then((res) => { + expect(res.body.forked).toBeTruthy; + expect(res.body.username).toBe(user.username); + }); + }); + it('responds with status of 500 and error', async () => { + const projObj = await request(app) + .get('/getMarketplaceProjects') + .set('Content-Type', 'application/json'); + + return request(app) + .get(`/cloneProject/${projObj.body[0]._id}`) + .set('Cookie', [`ssid=${user.userId}`]) // Set the cookie + .query({ username: [] }) + .expect(500) + .then((res) => { + expect(res.body.err).not.toBeNull(); + }); + }); + }); + }); + + //test unpublishProject endpoint + describe('/unpublishProject', () => { + describe('PATCH', () => { + it('responds with status of 200 and json object equal to unpublished project', async () => { + const response: Response = await request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }); //most recent project should be the one from publishProject + const _id: String = response.body[0]._id; + const userId: String = user.userId; + return request(app) + .patch('/unpublishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, userId }) + .expect(200) + .then((res) => { + expect(res.body._id).toBe(_id); + expect(res.body.published).toBe(false); + }); + }); + it('responds with status of 500 and error if userId and cookie ssid do not match', async () => { + const projObj: Response = await request(app) + .post('/getProjects') + .set('Accept', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId: projectToSave.userId }); + const _id: String = projObj.body[0]._id; + const project: String = projObj.body[0].project; + const comments: String = projObj.body[0].comments; + const username: String = projObj.body[0].username; + const name: String = projObj.body[0].name; + let userId: String = user.userId; + await request(app) //publishing a project first + .post('/publishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, project, comments, userId, username, name }); + + userId = 'ERROR'; + return request(app) + .patch('/unpublishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ _id, userId }) + .expect(500) + .then((res) => { + expect(res.body.err).not.toBeNull(); + }); + }); + it('responds with status of 500 and error if _id was not a string', async () => { + const userId: String = user.userId; + + return request(app) + .patch('/unpublishProject') + .set('Content-Type', 'application/json') + .set('Cookie', [`ssid=${user.userId}`]) + .send({ userId }) + .expect(500) + .then((res) => { + expect(res.body.err).not.toBeNull(); + }); + }); + }); + }); +}); + +describe('SessionController tests', () => { + describe('isLoggedIn', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + // Mock Express request and response objects and next function + const mockReq: any = { + cookies: null, //trying to trigger if cookies was not assigned + body: { + userId: 'sampleUserId' // Set up a sample userId in the request body + } + }; + const mockRes: any = { + json: jest.fn(), + status: jest.fn(), + redirect: jest.fn() + }; + const next = jest.fn(); + it('Assign userId from request body to cookieId', async () => { + // Call isLoggedIn + await sessionController.isLoggedIn(mockReq, mockRes, next); + expect(mockRes.redirect).toHaveBeenCalledWith('/'); + // Ensure that next() was called + }); + it('Trigger a database query error for findOne', async () => { + const mockFindOne = jest + .spyOn(mongoose.model('Sessions'), 'findOne') + .mockImplementation(() => { + throw new Error('Database query error'); + }); + // Call isLoggedIn + await sessionController.isLoggedIn(mockReq, mockRes, next); + // Ensure that next() was called with the error + expect(next).toHaveBeenCalledWith( + expect.objectContaining({ + log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive + }) + ); + + mockFindOne.mockRestore(); + }); + }); + + describe('startSession', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + it('Trigger a database query error for findOne', async () => { + const mockReq: any = { + cookies: projectToSave.userId, //trying to trigger if cookies was not assigned + body: { + userId: 'sampleUserId' // Set up a sample userId in the request body + } + }; + const mockRes: any = { + json: jest.fn(), + status: jest.fn(), + redirect: jest.fn(), + locals: { id: projectToSave.userId } + }; + + const next = jest.fn(); + const findOneMock = jest.spyOn( + mongoose.model('Sessions'), + 'findOne' + ) as jest.Mock; + findOneMock.mockImplementation( + (query: any, callback: (err: any, ses: any) => void) => { + callback(new Error('Database query error'), null); + } + ); + // Call startSession + await sessionController.startSession(mockReq, mockRes, next); + // Check that next() was called with the error + expect(next).toHaveBeenCalledWith( + expect.objectContaining({ + log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive + }) + ); + + findOneMock.mockRestore(); + }); + + xit('Check if a new Session is created', async () => { + //not working for some reason cannot get mocknext() to be called in test? + + const mockReq: any = { + cookies: projectToSave.userId, //trying to trigger if cookies was not assigned + body: { + userId: 'sampleUserId' // Set up a sample userId in the request body + } + }; + const mockRes: any = { + json: jest.fn(), + status: jest.fn(), + redirect: jest.fn(), + locals: { id: 'testID' } //a sesion id that doesnt exist + }; + + const mockNext = jest.fn(); + + //Call startSession + // Wrap your test logic in an async function + await sessionController.startSession(mockReq, mockRes, mockNext); + + //check if it reaches next() + //await expect(mockRes.locals.ssid).toBe('testID'); + expect(mockNext).toHaveBeenCalled(); + }); + }); +}); + +// describe('marketplaceController Middleware', () => { +// describe('getProjects tests', () => { +// it('should add the projects as an array to res.locals', () => { +// const req = {}; +// const res = { locals: {} }; +// console.log(marketplaceController.getPublishedProjects); +// console.log(typeof marketplaceController.getPublishedProjects); +// marketplaceController.getPublishedProjects(req, res, mockNext); +// expect(Array.isArray(res.locals.publishedProjects)).toBe(true); +// expect(mockNext).toHaveBeenCalled(); +// }); +// }); + +// it('should send an error response if there is an error in the middleware', () => { +// const req = { user: { isAuthenticated: false } }; +// const res = mockResponse(); + +// marketplaceController.authenticateMiddleware(req, res, mockNext); + +// expect(res.status).toHaveBeenCalledWith(500); +// expect(res.json).toHaveBeenCalledWith({ err: 'Error in marketplaceController.getPublishedProjects, check server logs for details' }); +// }); +// }); diff --git a/__tests__/signIn.test.tsx b/__tests__/signIn.test.tsx index 50cb5f66f..c40ca9132 100644 --- a/__tests__/signIn.test.tsx +++ b/__tests__/signIn.test.tsx @@ -1,51 +1,51 @@ -import SignIn from '../app/src/components/login/SignIn'; -import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import store from '../app/src/redux/store'; -import { BrowserRouter } from 'react-router-dom'; -import '@testing-library/jest-dom'; - -function TestSignIn() { - return ( - - - - - - ); -} - -describe('sign in page', () => { - test('should render a login input', () => { - render(); - expect(screen.getByTestId('username-input')).toBeInTheDocument(); - }); - test('should render a password field', () => { - render(); - expect(screen.getByTestId('password-input')).toBeInTheDocument(); - }); - test('should render 4 login buttons', () => { - render(); - expect(screen.getAllByRole('button')).toHaveLength(4); - }); - test('should invalidate empty username field', () => { - render(); - fireEvent.click(screen.getAllByRole('button')[1]); - waitFor(() => { - expect(screen.getByText('No Username Input')).toBeInTheDocument(); - }); - }); - test('should invalidate empty password field', () => { - render(); - fireEvent.change(screen.getByRole('textbox'), { - target: { - value: 'username' - } - }); - fireEvent.click(screen.getAllByRole('button')[1]); - waitFor(() => { - expect(screen.getByText('No Password Input')).toBeInTheDocument(); - }); - }); -}); +import SignIn from '../app/src/components/login/SignIn'; +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import store from '../app/src/redux/store'; +import { BrowserRouter } from 'react-router-dom'; +import '@testing-library/jest-dom'; + +function TestSignIn() { + return ( + + + + + + ); +} + +describe('sign in page', () => { + test('should render a login input', () => { + render(); + expect(screen.getByTestId('username-input')).toBeInTheDocument(); + }); + test('should render a password field', () => { + render(); + expect(screen.getByTestId('password-input')).toBeInTheDocument(); + }); + test('should render 4 login buttons', () => { + render(); + expect(screen.getAllByRole('button')).toHaveLength(4); + }); + test('should invalidate empty username field', () => { + render(); + fireEvent.click(screen.getAllByRole('button')[1]); + waitFor(() => { + expect(screen.getByText('No Username Input')).toBeInTheDocument(); + }); + }); + test('should invalidate empty password field', () => { + render(); + fireEvent.change(screen.getByRole('textbox'), { + target: { + value: 'username' + } + }); + fireEvent.click(screen.getAllByRole('button')[1]); + waitFor(() => { + expect(screen.getByText('No Password Input')).toBeInTheDocument(); + }); + }); +}); diff --git a/__tests__/spec.ts b/__tests__/spec.ts index fe98fd3b4..a8fd4940d 100644 --- a/__tests__/spec.ts +++ b/__tests__/spec.ts @@ -1,32 +1,32 @@ -import 'regenerator-runtime/runtime'; // if there is an error with moduleNameMapper, npm -S install regenerator-runtime - -//const { Application } = require('spectron'); -// const electronPath = require('electron'); -// const path = require('path'); - -// let app; - -// // beforeAll(() => { -// // // create a new app to test with setTimeout to be 15000 because the app takes a few seconds to spin up -// // app = new Application({ -// // path: electronPath, -// // chromeDriverArgs: ['--disable-extensions'], -// // args: [path.join(__dirname, '../app/electron/main.js')] // this is the path from this test file to main.js inside electron folder -// // }); -// // return app.start(); -// // }, 15000); - -// // 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) -// xtest('Displays App window', async () => { -// const windowCount = await app.client.getWindowCount(); -// // expect(windowCount).toBe(1); // this returns true/passed if in production mode, change mode in script "test" to 'production' instead of 'test' -// expect(windowCount).toBe(2); // 'dev' or 'test' mode results in 2 windows (one for the app and one for the browser) -// }); - -// /* we want to test other functionalities of app.client such as text, title, etc. but even the examples from the official spectron website -// or github repo did not yield the same outcomes as demonstrated. So we stopped testing Electron app here */ -// // afterAll(() => { -// // if (app && app.isRunning()) { -// // return app.stop(); -// // } -// // }); +import 'regenerator-runtime/runtime'; // if there is an error with moduleNameMapper, npm -S install regenerator-runtime + +//const { Application } = require('spectron'); +// const electronPath = require('electron'); +// const path = require('path'); + +// let app; + +// // beforeAll(() => { +// // // create a new app to test with setTimeout to be 15000 because the app takes a few seconds to spin up +// // app = new Application({ +// // path: electronPath, +// // chromeDriverArgs: ['--disable-extensions'], +// // args: [path.join(__dirname, '../app/electron/main.js')] // this is the path from this test file to main.js inside electron folder +// // }); +// // return app.start(); +// // }, 15000); + +// // 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) +// xtest('Displays App window', async () => { +// const windowCount = await app.client.getWindowCount(); +// // expect(windowCount).toBe(1); // this returns true/passed if in production mode, change mode in script "test" to 'production' instead of 'test' +// expect(windowCount).toBe(2); // 'dev' or 'test' mode results in 2 windows (one for the app and one for the browser) +// }); + +// /* we want to test other functionalities of app.client such as text, title, etc. but even the examples from the official spectron website +// or github repo did not yield the same outcomes as demonstrated. So we stopped testing Electron app here */ +// // afterAll(() => { +// // if (app && app.isRunning()) { +// // return app.stop(); +// // } +// // }); diff --git a/__tests__/stateManagementReducer.test.js b/__tests__/stateManagementReducer.test.js index 3b6d5e755..6a9045c28 100644 --- a/__tests__/stateManagementReducer.test.js +++ b/__tests__/stateManagementReducer.test.js @@ -1,309 +1,309 @@ -import reducer from '../app/src/redux/reducers/slice/appStateSlice'; -import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; - -//initializing copy of initial state to be used for test suite -let state = JSON.parse(JSON.stringify(initialState)); -state.components = [ - { - id: 1, - name: 'App', - style: {}, - code: "import React, { useState, useEffect, useContext} from 'react';\n\n\n\nimport C1 from './C1'\nconst App = (props) => {\n\n\n const [appState, setAppState] = useState(1);\n\n return(\n <>\n\n \n );\n}\n\nexport default App\n", - children: [ - { - type: 'HTML Element', - typeId: 1000, - name: 'separator', - childId: 1000, - style: { border: 'none' }, - attributes: {}, - children: [] - }, - { - type: 'Component', - typeId: 2, - name: 'C1', - childId: 1, - style: {}, - attributes: {}, - children: [], - stateProps: [], - passedInProps: [] - } - ], - isPage: true, - past: [[]], - future: [], - stateProps: [], - useStateCodes: [ - 'const [appState, setAppState] = useState(1)' - ] - }, - { - id: 2, - name: 'C1', - nextChildId: 1, - style: {}, - attributes: {}, - code: "import React, { useState, useEffect, useContext} from 'react';\n\n\n\n\nconst C1 = (props) => {\n\n\n\n return(\n <>\n\n \n );\n}\n\nexport default C1\n", - children: [], - isPage: false, - past: [], - future: [], - stateProps: [], - useStateCodes: [], - passedInProps: [] - } -]; - -const findComponent = (components, componentId) => { - return components.find((elem) => elem.id === componentId); -}; - -describe('stateManagementReducer test', () => { - // TEST 'ADD STATE' - describe('addState', () => { - // setting canvas focus to root component (App) - // state.canvasFocus.componentId = 1; - // action dispatched to be tested - const action1 = { - type: 'appState/addState', - payload: { - newState: { - id: 'App-testAppState', - key: 'testAppState', - type: 'number', - value: 1 - }, - setNewState: { - id: 'App-setTestAppState', - key: 'setTestAppState', - type: 'func', - value: '' - }, - contextParam: { - allContext: [] - } - } - }; - - // setting test state - state = reducer(state, action1); - let currComponent = findComponent(state.components, 1); - - it('should add state and its setter function to the stateProps array of the current component', () => { - expect(currComponent.stateProps.length).toEqual(2); - }); - it(`state id should be 'App-testAppState'`, () => { - expect(currComponent.stateProps[0].id).toEqual('App-testAppState'); - }); - it(`state key should be 'testAppState'`, () => { - expect(currComponent.stateProps[0].key).toEqual('testAppState'); - }); - it(`state value should be 1`, () => { - expect(currComponent.stateProps[0].value).toEqual(1); - }); - it(`state value type should be 'number'`, () => { - expect(currComponent.stateProps[0].type).toEqual('number'); - }); - it(`function state id should be 'App-setTestAppState'`, () => { - expect(currComponent.stateProps[1].id).toEqual('App-setTestAppState'); - }); - it(`function state key should be 'setTestAppState'`, () => { - expect(currComponent.stateProps[1].key).toEqual('setTestAppState'); - }); - it(`function state value should be blank`, () => { - expect(currComponent.stateProps[1].value).toEqual(''); - }); - it(`function state key should be func`, () => { - expect(currComponent.stateProps[1].type).toEqual('func'); - }); - - const action2 = { - type: 'appState/addState', - payload: { - newState: { - id: 'App-testAppState2', - key: 'isLoggedIn', - type: 'boolean', - value: 'false' - }, - setNewState: { - id: 'App-setTestAppState2', - key: 'setIsLoggedIn', - type: 'func', - value: '' - }, - contextParam: { - allContext: [] - } - } - }; - - state = reducer(state, action2); - - describe('should handle value with type of boolean', () => { - it(`state key type should be boolean`, () => { - expect(state.components[0].stateProps[2].type).toEqual('boolean'); - }); - it(`state value should be false`, () => { - expect(state.components[0].stateProps[2].value).toEqual('false'); - }); - }); - }); - - // TEST 'ADD PASSEDINPROPS' - describe('addPassedInProps', () => { - state = JSON.parse(JSON.stringify(state)); - // setting canvas focus to the child component - state.canvasFocus.componentId = 2; - - // action dispatched to be tested - const action = { - type: 'appState/addPassedInProps', - payload: { - passedInProps: { - id: 'App-testAppState', - key: 'testAppState', - type: 'number', - value: 1 - }, - contextParam: { - allContext: [] - } - } - }; - - // setting test state - state = reducer(state, action); - const currComponent = findComponent(state.components, 2); - const parentComponent = findComponent(state.components, 1); - - it(`current component should have a state id: 'App-testAppState' `, () => { - expect(currComponent.passedInProps[0].id).toEqual('App-testAppState'); - }); - it(`current component should have a state key: 'testAppState' `, () => { - expect(currComponent.passedInProps[0].key).toEqual('testAppState'); - }); - it(`current component should have a state value equal to 1`, () => { - expect(currComponent.passedInProps[0].value).toEqual(1); - }); - it(`current component should have a state value type: 'number'`, () => { - expect(currComponent.passedInProps[0].type).toEqual('number'); - }); - //check parent children array to make sure it is being added here as well - it(`parent component 'passedInProps' array length should be 1`, () => { - expect(currComponent.passedInProps.length).toEqual(1); - }); - it(`parent component should have a state id: 'App-testAppState' `, () => { - expect(parentComponent.children[1].passedInProps[0].id).toEqual( - 'App-testAppState' - ); - }); - it(`parent component should have a state key: 'testAppState' `, () => { - expect(parentComponent.children[1].passedInProps[0].key).toEqual( - 'testAppState' - ); - }); - it(`parent component should have a state value equal to 1`, () => { - expect(parentComponent.children[1].passedInProps[0].value).toEqual(1); - }); - it(`parent component should have a state value type: 'number'`, () => { - expect(parentComponent.children[1].passedInProps[0].type).toEqual( - 'number' - ); - }); - }); - - // TEST 'DELETE PASSEDINPROPS' - describe('deletePassedInProps', () => { - it('should delete the state passed down from parent component in the child component', () => { - // setting canvas focus to the child component - state = JSON.parse(JSON.stringify(state)); - state.canvasFocus.componentId = 2; - - // action dispatched to be tested - const action = { - type: 'appState/deletePassedInProps', - payload: { - rowId: 'App-testAppState', - contextParam: { - allContext: [] - } - } - }; - - // setting test state - state = reducer(state, action); - const parentComponent = findComponent(state.components, 1); - const currComponent = findComponent(state.components, 2); - - expect(currComponent.passedInProps.length).toEqual(0); - expect(parentComponent.children[1].passedInProps.length).toEqual(0); // need to fix reducer - }); - }); - - // TEST 'DELETE STATE' - describe('deleteState', () => { - it('should delete all instances of state from stateProps and passedInProps', () => { - // setting canvas focus to root component - state = JSON.parse(JSON.stringify(state)); - state.canvasFocus.componentId = 1; - - // action dispatched to be tested - const action = { - type: 'appState/deleteState', - payload: { - stateProps: [], - rowId: 'App-appState', - otherId: 'App-setAppState', - contextParam: { - allContext: [] - } - } - }; - - // setting intial test state - let parentComponent = findComponent(state.components, 1); - parentComponent.children[1].passedInProps = [ - { - id: 'App-appState', - key: 'appState', - type: 'number', - value: 1 - }, - { - id: 'App-setAppState', - key: 'setAppState', - type: 'func', - value: '' - } - ]; - - let childComponent = findComponent(state.components, 2); - childComponent.passedInProps = [ - { - id: 'App-appState', - key: 'appState', - type: 'number', - value: 1 - }, - { - id: 'App-setAppState', - key: 'setAppState', - type: 'func', - value: '' - } - ]; - - // updating components after state updates - state = reducer(state, action); - parentComponent = findComponent(state.components, 1); - childComponent = findComponent(state.components, 2); - - expect(childComponent.passedInProps.length).toEqual(0); - expect(parentComponent.stateProps.length).toEqual(0); - expect(parentComponent.children[1].passedInProps.length).toEqual(0); - }); - }); -}); +import reducer from '../app/src/redux/reducers/slice/appStateSlice'; +import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; + +//initializing copy of initial state to be used for test suite +let state = JSON.parse(JSON.stringify(initialState)); +state.components = [ + { + id: 1, + name: 'App', + style: {}, + code: "import React, { useState, useEffect, useContext} from 'react';\n\n\n\nimport C1 from './C1'\nconst App = (props) => {\n\n\n const [appState, setAppState] = useState(1);\n\n return(\n <>\n\n \n );\n}\n\nexport default App\n", + children: [ + { + type: 'HTML Element', + typeId: 1000, + name: 'separator', + childId: 1000, + style: { border: 'none' }, + attributes: {}, + children: [] + }, + { + type: 'Component', + typeId: 2, + name: 'C1', + childId: 1, + style: {}, + attributes: {}, + children: [], + stateProps: [], + passedInProps: [] + } + ], + isPage: true, + past: [[]], + future: [], + stateProps: [], + useStateCodes: [ + 'const [appState, setAppState] = useState(1)' + ] + }, + { + id: 2, + name: 'C1', + nextChildId: 1, + style: {}, + attributes: {}, + code: "import React, { useState, useEffect, useContext} from 'react';\n\n\n\n\nconst C1 = (props) => {\n\n\n\n return(\n <>\n\n \n );\n}\n\nexport default C1\n", + children: [], + isPage: false, + past: [], + future: [], + stateProps: [], + useStateCodes: [], + passedInProps: [] + } +]; + +const findComponent = (components, componentId) => { + return components.find((elem) => elem.id === componentId); +}; + +describe('stateManagementReducer test', () => { + // TEST 'ADD STATE' + describe('addState', () => { + // setting canvas focus to root component (App) + // state.canvasFocus.componentId = 1; + // action dispatched to be tested + const action1 = { + type: 'appState/addState', + payload: { + newState: { + id: 'App-testAppState', + key: 'testAppState', + type: 'number', + value: 1 + }, + setNewState: { + id: 'App-setTestAppState', + key: 'setTestAppState', + type: 'func', + value: '' + }, + contextParam: { + allContext: [] + } + } + }; + + // setting test state + state = reducer(state, action1); + let currComponent = findComponent(state.components, 1); + + it('should add state and its setter function to the stateProps array of the current component', () => { + expect(currComponent.stateProps.length).toEqual(2); + }); + it(`state id should be 'App-testAppState'`, () => { + expect(currComponent.stateProps[0].id).toEqual('App-testAppState'); + }); + it(`state key should be 'testAppState'`, () => { + expect(currComponent.stateProps[0].key).toEqual('testAppState'); + }); + it(`state value should be 1`, () => { + expect(currComponent.stateProps[0].value).toEqual(1); + }); + it(`state value type should be 'number'`, () => { + expect(currComponent.stateProps[0].type).toEqual('number'); + }); + it(`function state id should be 'App-setTestAppState'`, () => { + expect(currComponent.stateProps[1].id).toEqual('App-setTestAppState'); + }); + it(`function state key should be 'setTestAppState'`, () => { + expect(currComponent.stateProps[1].key).toEqual('setTestAppState'); + }); + it(`function state value should be blank`, () => { + expect(currComponent.stateProps[1].value).toEqual(''); + }); + it(`function state key should be func`, () => { + expect(currComponent.stateProps[1].type).toEqual('func'); + }); + + const action2 = { + type: 'appState/addState', + payload: { + newState: { + id: 'App-testAppState2', + key: 'isLoggedIn', + type: 'boolean', + value: 'false' + }, + setNewState: { + id: 'App-setTestAppState2', + key: 'setIsLoggedIn', + type: 'func', + value: '' + }, + contextParam: { + allContext: [] + } + } + }; + + state = reducer(state, action2); + + describe('should handle value with type of boolean', () => { + it(`state key type should be boolean`, () => { + expect(state.components[0].stateProps[2].type).toEqual('boolean'); + }); + it(`state value should be false`, () => { + expect(state.components[0].stateProps[2].value).toEqual('false'); + }); + }); + }); + + // TEST 'ADD PASSEDINPROPS' + describe('addPassedInProps', () => { + state = JSON.parse(JSON.stringify(state)); + // setting canvas focus to the child component + state.canvasFocus.componentId = 2; + + // action dispatched to be tested + const action = { + type: 'appState/addPassedInProps', + payload: { + passedInProps: { + id: 'App-testAppState', + key: 'testAppState', + type: 'number', + value: 1 + }, + contextParam: { + allContext: [] + } + } + }; + + // setting test state + state = reducer(state, action); + const currComponent = findComponent(state.components, 2); + const parentComponent = findComponent(state.components, 1); + + it(`current component should have a state id: 'App-testAppState' `, () => { + expect(currComponent.passedInProps[0].id).toEqual('App-testAppState'); + }); + it(`current component should have a state key: 'testAppState' `, () => { + expect(currComponent.passedInProps[0].key).toEqual('testAppState'); + }); + it(`current component should have a state value equal to 1`, () => { + expect(currComponent.passedInProps[0].value).toEqual(1); + }); + it(`current component should have a state value type: 'number'`, () => { + expect(currComponent.passedInProps[0].type).toEqual('number'); + }); + //check parent children array to make sure it is being added here as well + it(`parent component 'passedInProps' array length should be 1`, () => { + expect(currComponent.passedInProps.length).toEqual(1); + }); + it(`parent component should have a state id: 'App-testAppState' `, () => { + expect(parentComponent.children[1].passedInProps[0].id).toEqual( + 'App-testAppState' + ); + }); + it(`parent component should have a state key: 'testAppState' `, () => { + expect(parentComponent.children[1].passedInProps[0].key).toEqual( + 'testAppState' + ); + }); + it(`parent component should have a state value equal to 1`, () => { + expect(parentComponent.children[1].passedInProps[0].value).toEqual(1); + }); + it(`parent component should have a state value type: 'number'`, () => { + expect(parentComponent.children[1].passedInProps[0].type).toEqual( + 'number' + ); + }); + }); + + // TEST 'DELETE PASSEDINPROPS' + describe('deletePassedInProps', () => { + it('should delete the state passed down from parent component in the child component', () => { + // setting canvas focus to the child component + state = JSON.parse(JSON.stringify(state)); + state.canvasFocus.componentId = 2; + + // action dispatched to be tested + const action = { + type: 'appState/deletePassedInProps', + payload: { + rowId: 'App-testAppState', + contextParam: { + allContext: [] + } + } + }; + + // setting test state + state = reducer(state, action); + const parentComponent = findComponent(state.components, 1); + const currComponent = findComponent(state.components, 2); + + expect(currComponent.passedInProps.length).toEqual(0); + expect(parentComponent.children[1].passedInProps.length).toEqual(0); // need to fix reducer + }); + }); + + // TEST 'DELETE STATE' + describe('deleteState', () => { + it('should delete all instances of state from stateProps and passedInProps', () => { + // setting canvas focus to root component + state = JSON.parse(JSON.stringify(state)); + state.canvasFocus.componentId = 1; + + // action dispatched to be tested + const action = { + type: 'appState/deleteState', + payload: { + stateProps: [], + rowId: 'App-appState', + otherId: 'App-setAppState', + contextParam: { + allContext: [] + } + } + }; + + // setting intial test state + let parentComponent = findComponent(state.components, 1); + parentComponent.children[1].passedInProps = [ + { + id: 'App-appState', + key: 'appState', + type: 'number', + value: 1 + }, + { + id: 'App-setAppState', + key: 'setAppState', + type: 'func', + value: '' + } + ]; + + let childComponent = findComponent(state.components, 2); + childComponent.passedInProps = [ + { + id: 'App-appState', + key: 'appState', + type: 'number', + value: 1 + }, + { + id: 'App-setAppState', + key: 'setAppState', + type: 'func', + value: '' + } + ]; + + // updating components after state updates + state = reducer(state, action); + parentComponent = findComponent(state.components, 1); + childComponent = findComponent(state.components, 2); + + expect(childComponent.passedInProps.length).toEqual(0); + expect(parentComponent.stateProps.length).toEqual(0); + expect(parentComponent.children[1].passedInProps.length).toEqual(0); + }); + }); +}); diff --git a/__tests__/tree.test.tsx b/__tests__/tree.test.tsx index 395e63da8..57215fc3b 100644 --- a/__tests__/tree.test.tsx +++ b/__tests__/tree.test.tsx @@ -1,113 +1,113 @@ -import TreeChart from '../app/src/tree/TreeChart'; -import React from 'react'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; -import { Provider } from 'react-redux'; -import store from '../app/src/redux/store'; -import 'd3'; - -let state = JSON.parse(JSON.stringify(initialState)); -state.components = [ - { - id: 1, - name: 'index', - style: {}, - code: `import React, { useState } from 'react'; - import A from '../components/A'; - import B from '../components/B'; - import Head from 'next/head'; - const index = (props): JSX.Element => { - const [value, setValue] = useState('INITIAL VALUE'); - return ( - <> - - index - - - - ); - }; - export default index; - `, - children: [ - { - childId: 1, - children: [ - { - childId: 2, - children: [], - name: 'A', - style: {}, - type: 'Component', - typeId: 2 - } - ], - name: 'div', - style: {}, - type: 'HTML Element', - typeId: 11 - }, - { - childId: 3, - children: [ - { - childId: 4, - children: [], - name: 'B', - style: {}, - type: 'Component', - typeId: 3 - } - ], - name: 'div', - style: {}, - type: 'HTML Element', - typeId: 11 - } - ], - isPage: true - }, - { - id: 2, - nextChildId: 1, - name: 'A', - style: {}, - code: '', - children: [], - isPage: false - }, - { - id: 3, - nextChildId: 1, - name: 'B', - style: {}, - code: '', - children: [], - isPage: false - } -]; - -// renders a tree of the components in tester -describe('Component Tree Render Test', () => { - test('should render full component tree based on state', () => { - render( - - - - ); - // elements that are not separators should appear in the tree - expect(screen.getByText('index')).toBeInTheDocument(); - expect(screen.getByText('A')).toBeInTheDocument(); - expect(screen.getByText('B')).toBeInTheDocument(); - // tree should not include separators - expect(screen.queryByText('separator')).toBe(null); - }); -}); +import TreeChart from '../app/src/tree/TreeChart'; +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; +import { initialState } from '../app/src/redux/reducers/slice/appStateSlice'; +import { Provider } from 'react-redux'; +import store from '../app/src/redux/store'; +import 'd3'; + +let state = JSON.parse(JSON.stringify(initialState)); +state.components = [ + { + id: 1, + name: 'index', + style: {}, + code: `import React, { useState } from 'react'; + import A from '../components/A'; + import B from '../components/B'; + import Head from 'next/head'; + const index = (props): JSX.Element => { + const [value, setValue] = useState('INITIAL VALUE'); + return ( + <> + + index + + + + ); + }; + export default index; + `, + children: [ + { + childId: 1, + children: [ + { + childId: 2, + children: [], + name: 'A', + style: {}, + type: 'Component', + typeId: 2 + } + ], + name: 'div', + style: {}, + type: 'HTML Element', + typeId: 11 + }, + { + childId: 3, + children: [ + { + childId: 4, + children: [], + name: 'B', + style: {}, + type: 'Component', + typeId: 3 + } + ], + name: 'div', + style: {}, + type: 'HTML Element', + typeId: 11 + } + ], + isPage: true + }, + { + id: 2, + nextChildId: 1, + name: 'A', + style: {}, + code: '', + children: [], + isPage: false + }, + { + id: 3, + nextChildId: 1, + name: 'B', + style: {}, + code: '', + children: [], + isPage: false + } +]; + +// renders a tree of the components in tester +describe('Component Tree Render Test', () => { + test('should render full component tree based on state', () => { + render( + + + + ); + // elements that are not separators should appear in the tree + expect(screen.getByText('index')).toBeInTheDocument(); + expect(screen.getByText('A')).toBeInTheDocument(); + expect(screen.getByText('B')).toBeInTheDocument(); + // tree should not include separators + expect(screen.queryByText('separator')).toBe(null); + }); +}); diff --git a/amplify/.config/project-config.json b/amplify/.config/project-config.json index 138a6f031..771455955 100644 --- a/amplify/.config/project-config.json +++ b/amplify/.config/project-config.json @@ -1,17 +1,17 @@ -{ - "projectName": "ReacType", - "version": "3.1", - "frontend": "javascript", - "javascript": { - "framework": "none", - "config": { - "SourceDir": "src", - "DistributionDir": "dist", - "BuildCommand": "npm run-script build", - "StartCommand": "npm run-script start" - } - }, - "providers": [ - "awscloudformation" - ] +{ + "projectName": "ReacType", + "version": "3.1", + "frontend": "javascript", + "javascript": { + "framework": "none", + "config": { + "SourceDir": "src", + "DistributionDir": "dist", + "BuildCommand": "npm run-script build", + "StartCommand": "npm run-script start" + } + }, + "providers": [ + "awscloudformation" + ] } \ No newline at end of file diff --git a/amplify/README.md b/amplify/README.md index 7c0a9e285..a2b896041 100644 --- a/amplify/README.md +++ b/amplify/README.md @@ -1,8 +1,8 @@ -# Getting Started with Amplify CLI -This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli). - -Helpful resources: -- Amplify documentation: https://docs.amplify.aws -- Amplify CLI documentation: https://docs.amplify.aws/cli -- More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files -- Join Amplify's community: https://amplify.aws/community/ +# Getting Started with Amplify CLI +This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli). + +Helpful resources: +- Amplify documentation: https://docs.amplify.aws +- Amplify CLI documentation: https://docs.amplify.aws/cli +- More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files +- Join Amplify's community: https://amplify.aws/community/ diff --git a/amplify/backend/auth/reactype24e8d371/cli-inputs.json b/amplify/backend/auth/reactype24e8d371/cli-inputs.json index 764835fd1..d147a57e8 100644 --- a/amplify/backend/auth/reactype24e8d371/cli-inputs.json +++ b/amplify/backend/auth/reactype24e8d371/cli-inputs.json @@ -1,59 +1,59 @@ -{ - "version": "1", - "cognitoConfig": { - "identityPoolName": "reactype24e8d371_identitypool_24e8d371", - "allowUnauthenticatedIdentities": true, - "resourceNameTruncated": "reacty24e8d371", - "userPoolName": "reactype24e8d371_userpool_24e8d371", - "autoVerifiedAttributes": [ - "email" - ], - "mfaConfiguration": "OFF", - "mfaTypes": [ - "SMS Text Message" - ], - "smsAuthenticationMessage": "Your authentication code is {####}", - "smsVerificationMessage": "Your verification code is {####}", - "emailVerificationSubject": "Your verification code", - "emailVerificationMessage": "Your verification code is {####}", - "defaultPasswordPolicy": false, - "passwordPolicyMinLength": 8, - "passwordPolicyCharacters": [], - "requiredAttributes": [ - "email" - ], - "aliasAttributes": [], - "userpoolClientGenerateSecret": false, - "userpoolClientRefreshTokenValidity": 30, - "userpoolClientWriteAttributes": [ - "email" - ], - "userpoolClientReadAttributes": [ - "email" - ], - "userpoolClientLambdaRole": "reacty24e8d371_userpoolclient_lambda_role", - "userpoolClientSetAttributes": false, - "sharedId": "24e8d371", - "resourceName": "reactype24e8d371", - "authSelections": "identityPoolAndUserPool", - "useDefault": "default", - "userPoolGroupList": [], - "serviceName": "Cognito", - "usernameCaseSensitive": false, - "useEnabledMfas": true, - "authRoleArn": { - "Fn::GetAtt": [ - "AuthRole", - "Arn" - ] - }, - "unauthRoleArn": { - "Fn::GetAtt": [ - "UnauthRole", - "Arn" - ] - }, - "breakCircularDependency": true, - "dependsOn": [] - } +{ + "version": "1", + "cognitoConfig": { + "identityPoolName": "reactype24e8d371_identitypool_24e8d371", + "allowUnauthenticatedIdentities": true, + "resourceNameTruncated": "reacty24e8d371", + "userPoolName": "reactype24e8d371_userpool_24e8d371", + "autoVerifiedAttributes": [ + "email" + ], + "mfaConfiguration": "OFF", + "mfaTypes": [ + "SMS Text Message" + ], + "smsAuthenticationMessage": "Your authentication code is {####}", + "smsVerificationMessage": "Your verification code is {####}", + "emailVerificationSubject": "Your verification code", + "emailVerificationMessage": "Your verification code is {####}", + "defaultPasswordPolicy": false, + "passwordPolicyMinLength": 8, + "passwordPolicyCharacters": [], + "requiredAttributes": [ + "email" + ], + "aliasAttributes": [], + "userpoolClientGenerateSecret": false, + "userpoolClientRefreshTokenValidity": 30, + "userpoolClientWriteAttributes": [ + "email" + ], + "userpoolClientReadAttributes": [ + "email" + ], + "userpoolClientLambdaRole": "reacty24e8d371_userpoolclient_lambda_role", + "userpoolClientSetAttributes": false, + "sharedId": "24e8d371", + "resourceName": "reactype24e8d371", + "authSelections": "identityPoolAndUserPool", + "useDefault": "default", + "userPoolGroupList": [], + "serviceName": "Cognito", + "usernameCaseSensitive": false, + "useEnabledMfas": true, + "authRoleArn": { + "Fn::GetAtt": [ + "AuthRole", + "Arn" + ] + }, + "unauthRoleArn": { + "Fn::GetAtt": [ + "UnauthRole", + "Arn" + ] + }, + "breakCircularDependency": true, + "dependsOn": [] + } } \ No newline at end of file diff --git a/amplify/backend/backend-config.json b/amplify/backend/backend-config.json index 40e757e58..7f7588335 100644 --- a/amplify/backend/backend-config.json +++ b/amplify/backend/backend-config.json @@ -1,35 +1,35 @@ -{ - "auth": { - "reactype24e8d371": { - "customAuth": false, - "dependsOn": [], - "frontendAuthConfig": { - "mfaConfiguration": "OFF", - "mfaTypes": [ - "SMS" - ], - "passwordProtectionSettings": { - "passwordPolicyCharacters": [], - "passwordPolicyMinLength": 8 - }, - "signupAttributes": [ - "EMAIL" - ], - "socialProviders": [], - "usernameAttributes": [], - "verificationMechanisms": [ - "EMAIL" - ] - }, - "providerPlugin": "awscloudformation", - "service": "Cognito" - } - }, - "storage": { - "ReacTypeMktImg": { - "dependsOn": [], - "providerPlugin": "awscloudformation", - "service": "S3" - } - } +{ + "auth": { + "reactype24e8d371": { + "customAuth": false, + "dependsOn": [], + "frontendAuthConfig": { + "mfaConfiguration": "OFF", + "mfaTypes": [ + "SMS" + ], + "passwordProtectionSettings": { + "passwordPolicyCharacters": [], + "passwordPolicyMinLength": 8 + }, + "signupAttributes": [ + "EMAIL" + ], + "socialProviders": [], + "usernameAttributes": [], + "verificationMechanisms": [ + "EMAIL" + ] + }, + "providerPlugin": "awscloudformation", + "service": "Cognito" + } + }, + "storage": { + "ReacTypeMktImg": { + "dependsOn": [], + "providerPlugin": "awscloudformation", + "service": "S3" + } + } } \ No newline at end of file diff --git a/amplify/backend/storage/ReacTypeMktImg/cli-inputs.json b/amplify/backend/storage/ReacTypeMktImg/cli-inputs.json index 3ef215a8c..318329b85 100644 --- a/amplify/backend/storage/ReacTypeMktImg/cli-inputs.json +++ b/amplify/backend/storage/ReacTypeMktImg/cli-inputs.json @@ -1,16 +1,16 @@ -{ - "resourceName": "ReacTypeMktImg", - "policyUUID": "f87a3e6a", - "bucketName": "reactypemktimgs", - "storageAccess": "authAndGuest", - "guestAccess": [ - "READ" - ], - "authAccess": [ - "CREATE_AND_UPDATE", - "READ", - "DELETE" - ], - "triggerFunction": "NONE", - "groupAccess": {} +{ + "resourceName": "ReacTypeMktImg", + "policyUUID": "f87a3e6a", + "bucketName": "reactypemktimgs", + "storageAccess": "authAndGuest", + "guestAccess": [ + "READ" + ], + "authAccess": [ + "CREATE_AND_UPDATE", + "READ", + "DELETE" + ], + "triggerFunction": "NONE", + "groupAccess": {} } \ No newline at end of file diff --git a/amplify/backend/tags.json b/amplify/backend/tags.json index b9321d71b..e66b26da8 100644 --- a/amplify/backend/tags.json +++ b/amplify/backend/tags.json @@ -1,10 +1,10 @@ -[ - { - "Key": "user:Stack", - "Value": "{project-env}" - }, - { - "Key": "user:Application", - "Value": "{project-name}" - } +[ + { + "Key": "user:Stack", + "Value": "{project-env}" + }, + { + "Key": "user:Application", + "Value": "{project-name}" + } ] \ No newline at end of file diff --git a/amplify/backend/types/amplify-dependent-resources-ref.d.ts b/amplify/backend/types/amplify-dependent-resources-ref.d.ts index e6dd1d4f4..869a4e4a6 100644 --- a/amplify/backend/types/amplify-dependent-resources-ref.d.ts +++ b/amplify/backend/types/amplify-dependent-resources-ref.d.ts @@ -1,19 +1,19 @@ -export type AmplifyDependentResourcesAttributes = { - "auth": { - "reactype24e8d371": { - "AppClientID": "string", - "AppClientIDWeb": "string", - "IdentityPoolId": "string", - "IdentityPoolName": "string", - "UserPoolArn": "string", - "UserPoolId": "string", - "UserPoolName": "string" - } - }, - "storage": { - "ReacTypeMktImg": { - "BucketName": "string", - "Region": "string" - } - } +export type AmplifyDependentResourcesAttributes = { + "auth": { + "reactype24e8d371": { + "AppClientID": "string", + "AppClientIDWeb": "string", + "IdentityPoolId": "string", + "IdentityPoolName": "string", + "UserPoolArn": "string", + "UserPoolId": "string", + "UserPoolName": "string" + } + }, + "storage": { + "ReacTypeMktImg": { + "BucketName": "string", + "Region": "string" + } + } } \ No newline at end of file diff --git a/amplify/cli.json b/amplify/cli.json index 1058d7b08..1dfb3291f 100644 --- a/amplify/cli.json +++ b/amplify/cli.json @@ -1,63 +1,63 @@ -{ - "features": { - "graphqltransformer": { - "addmissingownerfields": true, - "improvepluralization": false, - "validatetypenamereservedwords": true, - "useexperimentalpipelinedtransformer": true, - "enableiterativegsiupdates": true, - "secondarykeyasgsi": true, - "skipoverridemutationinputtypes": true, - "transformerversion": 2, - "suppressschemamigrationprompt": true, - "securityenhancementnotification": false, - "showfieldauthnotification": false, - "usesubusernamefordefaultidentityclaim": true, - "usefieldnameforprimarykeyconnectionfield": false, - "enableautoindexquerynames": true, - "respectprimarykeyattributesonconnectionfield": true, - "shoulddeepmergedirectiveconfigdefaults": false, - "populateownerfieldforstaticgroupauth": true - }, - "frontend-ios": { - "enablexcodeintegration": true - }, - "auth": { - "enablecaseinsensitivity": true, - "useinclusiveterminology": true, - "breakcirculardependency": true, - "forcealiasattributes": false, - "useenabledmfas": true - }, - "codegen": { - "useappsyncmodelgenplugin": true, - "usedocsgeneratorplugin": true, - "usetypesgeneratorplugin": true, - "cleangeneratedmodelsdirectory": true, - "retaincasestyle": true, - "addtimestampfields": true, - "handlelistnullabilitytransparently": true, - "emitauthprovider": true, - "generateindexrules": true, - "enabledartnullsafety": true, - "generatemodelsforlazyloadandcustomselectionset": false - }, - "appsync": { - "generategraphqlpermissions": true - }, - "latestregionsupport": { - "pinpoint": 1, - "translate": 1, - "transcribe": 1, - "rekognition": 1, - "textract": 1, - "comprehend": 1 - }, - "project": { - "overrides": true - } - }, - "debug": { - "shareProjectConfig": false - } +{ + "features": { + "graphqltransformer": { + "addmissingownerfields": true, + "improvepluralization": false, + "validatetypenamereservedwords": true, + "useexperimentalpipelinedtransformer": true, + "enableiterativegsiupdates": true, + "secondarykeyasgsi": true, + "skipoverridemutationinputtypes": true, + "transformerversion": 2, + "suppressschemamigrationprompt": true, + "securityenhancementnotification": false, + "showfieldauthnotification": false, + "usesubusernamefordefaultidentityclaim": true, + "usefieldnameforprimarykeyconnectionfield": false, + "enableautoindexquerynames": true, + "respectprimarykeyattributesonconnectionfield": true, + "shoulddeepmergedirectiveconfigdefaults": false, + "populateownerfieldforstaticgroupauth": true + }, + "frontend-ios": { + "enablexcodeintegration": true + }, + "auth": { + "enablecaseinsensitivity": true, + "useinclusiveterminology": true, + "breakcirculardependency": true, + "forcealiasattributes": false, + "useenabledmfas": true + }, + "codegen": { + "useappsyncmodelgenplugin": true, + "usedocsgeneratorplugin": true, + "usetypesgeneratorplugin": true, + "cleangeneratedmodelsdirectory": true, + "retaincasestyle": true, + "addtimestampfields": true, + "handlelistnullabilitytransparently": true, + "emitauthprovider": true, + "generateindexrules": true, + "enabledartnullsafety": true, + "generatemodelsforlazyloadandcustomselectionset": false + }, + "appsync": { + "generategraphqlpermissions": true + }, + "latestregionsupport": { + "pinpoint": 1, + "translate": 1, + "transcribe": 1, + "rekognition": 1, + "textract": 1, + "comprehend": 1 + }, + "project": { + "overrides": true + } + }, + "debug": { + "shareProjectConfig": false + } } \ No newline at end of file diff --git a/amplify/hooks/README.md b/amplify/hooks/README.md index 8fb601eae..4f0609104 100644 --- a/amplify/hooks/README.md +++ b/amplify/hooks/README.md @@ -1,7 +1,7 @@ -# Command Hooks - -Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc. - -To get started, add your script files based on the expected naming convention in this directory. - -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 +# Command Hooks + +Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc. + +To get started, add your script files based on the expected naming convention in this directory. + +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 diff --git a/app/.electron/main.ts b/app/.electron/main.ts index 6bf974c01..d304609a3 100644 --- a/app/.electron/main.ts +++ b/app/.electron/main.ts @@ -1,511 +1,602 @@ -/* -@description: main.js is what controls the lifecycle of the electron application from initialization to termination. -@actions: codes for Github Oauth has been commented out because of lack of functionality. -*/ -require('dotenv').config(); -const { DEV_PORT } = require('../../config.js'); -const path = require('path'); -import { - app, - protocol, - BrowserWindow, - session, - ipcMain, - dialog -} from 'electron'; -// The splash screen is what appears while the app is loading -// const { initSplashScreen, OfficeTemplate } = require('electron-splashscreen'); -import { resolve } from 'app-root-path'; -// to install react dev tool extension -// const { -// default: installExtension, -// REACT_DEVELOPER_TOOLS -// } = require('electron-devtools-installer'); -import debug from 'electron-debug'; -// import custom protocol in protocol.js -import Protocol from './protocol'; -// menu from another file to modularize the code - -import MenuBuilder from './menu'; - -// mode that the app is running in -const isDev = - import.meta.env.NODE_ENV === 'development' || - import.meta.env.NODE_ENV === 'test'; -const port = 8080; -const selfHost = `http://localhost:${port}`; - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let win: BrowserWindow | null; -let menuBuilder: any; - -// function to create a new browser window -// this function will be called when Electron has initialized itself -async function createWindow() { - // install react dev tools if we are in development mode - // this will happen before creating the browser window. it returns a Boolean whether the protocol of scheme 'app://' was successfully registered and a file (index-prod.html) was sent as the response - protocol.registerBufferProtocol(Protocol.scheme, Protocol.requestHandler); - - // Create the browser window. - win = new BrowserWindow({ - // full screen - width: 1920, - height: 1080, - - minWidth: 980, - // window title - title: 'ReacType', - // the browser window will not display initially as it's loading - // once the browser window renders, a function is called below that hides the splash screen and displays the browser window - show: false, - webPreferences: { - zoomFactor: 0.7, - // enable devtools when in development mode - devTools: true, - // crucial security feature - blocks rendering process from having access to node modules - nodeIntegration: true, - // web workers will not have access to node - nodeIntegrationInWorker: false, - // disallow experimental feature to allow node.js support in sub-frames (i-frames/child windows) - nodeIntegrationInSubFrames: true, - // runs electron apis and preload script in a separate JS context - // separate context has access to document/window but has it's own built-ins and is isolate from changes to global environment by located page - // Electron API only available from preload, not loaded page - contextIsolation: true, - // disables remote module. critical for ensuring that rendering process doesn't have access to node functionality - enableRemoteModule: true, - // path of preload script. preload is how the renderer page will have access to electron functionality - preload: path.join(__dirname, 'preload.js'), - nativeWindowOpen: true - } - }); - - // Splash screen that appears while loading - // const hideSplashscreen = initSplashScreen({ - // mainWindow: win, - // icon: resolve('app/src/public/icons/png/64x64.png'), - // url: OfficeTemplate, - // width: 500, - // height: 300, - // brand: 'OS Labs', - // productName: 'ReacType', - // logo: resolve('app/src/public/icons/png/64x64.png'), - // color: '#3BBCAF', - // website: 'www.reactype.io', - // text: 'Initializing ...', - // }); - // Load app - if (isDev) { - // load app from web-dev server - win.loadURL(selfHost); - } else { - // load local file if in production - win.loadURL(`${Protocol.scheme}://rse/index-prod.html`); - } - - // load page once window is loaded - win.once('ready-to-show', () => { - win!.show(); - }); - - // automatically open DevTools when opening the app - // Note: devtools is creating many errors in the logs at the moment but can't figure out how to resolve the issue - if (isDev) { - win.webContents.once('dom-ready', () => { - debug(); - win!.webContents.openDevTools(); - }); - } - - // Emitted when the window is closed. - win.on('closed', () => { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - app.quit(); - }); - - menuBuilder = MenuBuilder(win!, 'ReacType'); - menuBuilder.buildMenu(); - - // Removed this security feature for now since it's not being used - // https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content - // TODO: is this the same type of sessions that have in react type - // Could potentially remove this session capability - it appears to be more focused on approving requests from 3rd party notifications - const ses = session; - - const partition = 'default'; - ses - .fromPartition(partition) - .setPermissionRequestHandler((webContents, permission, callback) => { - const allowedPermissions: string[] = []; // Full list here: https://developer.chrome.com/extensions/declare_permissions#manifest - - if (allowedPermissions.includes(permission)) { - callback(true); // Approve permission request - } else { - console.error( - `The application tried to request permission for '${permission}'. This permission was not whitelisted and has been blocked.` - ); - - callback(false); // Deny - } - }); - - // https://electronjs.org/docs/tutorial/security#1-only-load-secure-content; - // The below code can only run when a scheme and host are defined, I thought - // we could use this over _all_ urls - ses - .fromPartition(partition) - .webRequest.onBeforeRequest( - { urls: ['http://localhost./*'] }, - (listener) => { - if (listener.url.indexOf('http://') >= 0) { - listener.callback({ - cancel: true - }); - } - } - ); -} - -// Needs to be called before app is ready; -// gives our scheme access to load relative files, -// as well as local storage, cookies, etc. -// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes -protocol.registerSchemesAsPrivileged([ - { - scheme: Protocol.scheme, - privileges: { - standard: true, - secure: true, - allowServiceWorkers: true, - supportFetchAPI: true - } - } -]); - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.on('ready', createWindow); - -// TRYING ELECTRON-WINDOW-MANAGER When the application is ready - -// Quit when all windows are closed. -app.on('window-all-closed', () => { - win!.webContents.executeJavaScript('window.localStorage.clear();'); - app.quit(); -}); - -app.on('activate', () => { - if (win === null) { - createWindow(); - } -}); - -// https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation -// limits navigation within the app to a whitelisted array -// redirects are a common attack vector - -// after the contents of the webpage are rendered, set up event listeners on the webContents -app.on('web-contents-created', (event, contents) => { - contents.on('will-navigate', (event, navigationUrl) => { - const parsedUrl = new URL(navigationUrl); - const validOrigins = [ - selfHost, - //'https://reactype-1.herokuapp.com', - 'https://reactype-caret.herokuapp.com', - `http://localhost:${DEV_PORT}`, - 'https://reactype.herokuapp.com', - 'https://github.com', - 'https://nextjs.org', - 'https://www.facebook.com', - 'https://developer.mozilla.org', - 'https://www.smashingmagazine.com', - 'https://www.html5rocks.com', - 'null', - 'app://rse/' - ]; - // Log and prevent the app from navigating to a new page if that page's origin is not whitelisted - if (!validOrigins.includes(parsedUrl.origin)) { - console.error( - `The application tried to navigate to the following address: '${parsedUrl}'. This origin is not whitelisted attempt to navigate was blocked.` - ); - // if the requested URL is not in the whitelisted array, then don't navigate there - event.preventDefault(); - } - }); - - contents.on('will-redirect', (event, navigationUrl) => { - const parsedUrl = new URL(navigationUrl); - const validOrigins = [ - selfHost, - //'https://reactype-1.herokuapp.com/', - 'https://reactype-caret.herokuapp.com', - `http://localhost:${DEV_PORT}`, - 'https://reactype.herokuapp.com', - 'https://github.com', - 'https://nextjs.org', - 'https://developer.mozilla.org', - 'https://www.facebook.com', - 'https://www.smashingmagazine.com', - 'https://www.html5rocks.com', - 'app://rse/', - 'null' - ]; - // Log and prevent the app from redirecting to a new page - if ( - !validOrigins.includes(parsedUrl.origin) && - !validOrigins.includes(parsedUrl.href) - ) { - console.error( - `The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.` - ); - - event.preventDefault(); - } - }); - - // https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation - // The web-view is used to embed guest content in a page - // This event listener deletes web-views if they happen to occur in the app - // https://www.electronjs.org/docs/api/web-contents#event-will-attach-webview - contents.on('will-attach-webview', (event, webPreferences, params) => { - // Strip away preload scripts if unused or verify their location is legitimate - delete webPreferences.preload; - delete webPreferences.preloadURL; - - // Disable Node.js integration - webPreferences.nodeIntegration = false; - }); - - // https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows - contents.on('new-window', async (event, navigationUrl) => { - // Log and prevent opening up a new window - const parsedUrl = new URL(navigationUrl); - const validOrigins = [ - selfHost, - //'https://reactype-1.herokuapp.com/', - 'https://reactype-caret.herokuapp.com', - `http://localhost:${DEV_PORT}`, - 'https://reactype.herokuapp.com', - 'https://nextjs.org', - 'https://developer.mozilla.org', - 'https://github.com', - 'https://www.facebook.com', - 'https://www.smashingmagazine.com', - 'https://www.html5rocks.com', - 'null', - 'app://rse/' - ]; - // Log and prevent the app from navigating to a new page if that page's origin is not whitelisted - if (!validOrigins.includes(parsedUrl.origin)) { - console.error( - `The application tried to open a new window at the following address: '${navigationUrl}'. This attempt was blocked.` - ); - // if the requested URL is not in the whitelisted array, then don't navigate there - event.preventDefault(); - } - }); -}); - -// Filter loading any module via remote; -// you shouldn't be using remote at all, though -// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module -app.on('remote-require', (event, webContents, moduleName) => { - event.preventDefault(); -}); - -// built-ins are modules such as "app" -app.on('remote-get-builtin', (event, webContents, moduleName) => { - event.preventDefault(); -}); - -app.on('remote-get-global', (event, webContents, globalName) => { - event.preventDefault(); -}); - -app.on('remote-get-current-window', (event, webContents) => { - event.preventDefault(); -}); - -app.on('remote-get-current-web-contents', (event, webContents) => { - event.preventDefault(); -}); - -// When a user selects "Export project", a function (chooseAppDir loaded via preload.js) -// is triggered that sends a "choose_app_dir" message to the main process -// when the "choose_app_dir" message is received it triggers this event listener -ipcMain.on('choose_app_dir', (event) => { - // dialog displays the native system's dialogue for selecting files - // once a directory is chosen send a message back to the renderer with the path of the directory - dialog - .showOpenDialog(win!, { - properties: ['openDirectory'], - buttonLabel: 'Export' - }) - .then((directory) => { - if (!directory) return; - event.sender.send('app_dir_selected', directory.filePaths[0]); - }) - .catch((err) => console.log('ERROR on "choose_app_dir" event: ', err)); -}); - -// define serverURL for cookie and auth purposes based on environment -let serverUrl = 'https://reactype-caret.herokuapp.com'; -if (isDev) { - serverUrl = `http://localhost:${DEV_PORT}`; -} - -// // for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie -ipcMain.on('set_cookie', (event) => { - session.defaultSession.cookies - .get({ url: serverUrl }) - .then((cookie) => { - // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak - // checking for a cookie inside array will only emit reply when a cookie exists - if (cookie[0]) { - event.reply('give_cookie', cookie); - } - }) - .catch((error) => { - console.log('Error giving cookies in set_cookie:', error); - }); -}); - -// again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout -ipcMain.on('delete_cookie', (event) => { - session.defaultSession.cookies - .remove(serverUrl, 'ssid') - // .then(removed => { - // }) - .catch((err) => console.log('Error deleting cookie:', err)); -}); - -// opens new window for github oauth when button on sign in page is clicked -ipcMain.on('github', (event) => { - const githubURL = isDev - ? `http://localhost:${DEV_PORT}/auth/github` - : `https://reactype-caret.herokuapp.com/auth/github`; - const options = { - client_id: import.meta.env.GITHUB_ID, - client_secret: import.meta.env.GITHUB_SECRET, - scopes: ['user:email', 'notifications'] - }; - // create new browser window object with size, title, security options - const github = new BrowserWindow({ - width: 800, - height: 600, - title: 'Github Oauth', - webPreferences: { - nodeIntegration: true, - nodeIntegrationInWorker: false, - nodeIntegrationInSubFrames: false, - contextIsolation: true, - enableRemoteModule: true, - zoomFactor: 1.0 - } - }); - - github.loadURL(githubURL); - github.show(); - const handleCallback = (url) => { - const raw_code = /code=([^&]\*)/.exec(url) || null; - const code = raw_code && raw_code.length > 1 ? raw_code[1] : null; - const error = /\?error=(.+)\$/.exec(url); - - if (code || error) { - // Close the browser if code found or error - github.destroy(); - } - - // If there is a code, proceed to get token from github - if (code) { - self.requestGithubToken(options, code); - } else if (error) { - alert( - "Oops! Something went wrong and we couldn't" + - 'log you in using Github. Please try again.' - ); - } - }; - - github.webContents.on('will-navigate', (e, url) => handleCallback(url)); - - github.webContents.on('did-finish-load', (e, url, a, b) => { - github.webContents.selectAll(); - }); - - github.webContents.on('did-get-redirect-request', (e, oldUrl, newUrl) => - handleCallback(newUrl) - ); - - // Reset the authWindow on close - github.on('close', () => (authWindow = null), false); - - // if final callback is reached and we get a redirect from server back to our app, close oauth window - github.webContents.on('will-redirect', (e, callbackUrl) => { - const matches = callbackUrl.match(/(?<=\?=).*/); - const ssid = matches ? matches[0] : ''; - callbackUrl = callbackUrl.replace(/\?=.*/, ''); - let redirectUrl = 'app://rse/'; - if (isDev) { - redirectUrl = 'http://localhost:8080/'; - } - - if (callbackUrl === redirectUrl) { - dialog.showMessageBox({ - type: 'info', - title: 'ReacType', - icon: resolve('app/src/public/icons/png/256x256.png'), - message: 'Github Oauth Successful!' - }); - github.close(); - win!.webContents - .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`) - .then((result) => win!.loadURL(`${redirectUrl}`)) - .catch((err) => console.log(err)); - } - }); -}); - -ipcMain.on('tutorial', (event) => { - // create new browser window object with size, title, security options - const tutorial = new BrowserWindow({ - width: 800, - height: 600, - minWidth: 661, - title: 'Tutorial', - webPreferences: { - nodeIntegration: true, - nodeIntegrationInWorker: false, - nodeIntegrationInSubFrames: false, - contextIsolation: true, - enableRemoteModule: true, - zoomFactor: 1.0 - } - }); - // redirects to relevant server endpoint - github.loadURL(`${serverUrl}/github`); - // show window - tutorial.show(); - // if final callback is reached and we get a redirect from server back to our app, close oauth window - github.webContents.on('will-redirect', (e, callbackUrl) => { - let redirectUrl = 'app://rse/'; - if (isDev) { - redirectUrl = 'http://localhost:8080/'; - } - if (callbackUrl === redirectUrl) { - dialog.showMessageBox({ - type: 'info', - title: 'ReacType', - icon: resolve('app/src/public/icons/png/256x256.png'), - message: 'Github Oauth Successful!' - }); - github.close(); - } - }); -}); - -module.exports = dialog; +/* +@description: main.js is what controls the lifecycle of the electron application from initialization to termination. +@actions: codes for Github Oauth has been commented out because of lack of functionality. +*/ +require('dotenv').config(); +const { DEV_PORT } = require('../../config.js'); +const path = require('path'); +import { + app, + protocol, + net, + BrowserWindow, + session, + ipcMain, + dialog, + webContents, + WebContents, + Event +} from 'electron'; +import * as fs from 'fs' +const{ default: axios } = require('axios') +// The splash screen is what appears while the app is loading +// const { initSplashScreen, OfficeTemplate } = require('electron-splashscreen'); +import { resolve } from 'app-root-path'; +// to install react dev tool extension +// const { +// default: installExtension, +// REACT_DEVELOPER_TOOLS +// } = require('electron-devtools-installer'); +import debug from 'electron-debug'; +// import custom protocol in protocol.js +// import { scheme } from './protocol'; +// menu from another file to modularize the code +import { MenuBuilder } from './menu'; +import { string } from 'prop-types'; +import { hostname } from 'os'; +console.log('Main.ts has started'); +// mode that the app is running in +const isDev = + process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'; +const port = 8080; +const selfHost = `http://localhost:${port}`; + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let win: BrowserWindow | null; +let menuBuilder: any; + +// function to create a new browser window +// this function will be called when Electron has initialized itself +async function createWindow() { + // install react dev tools if we are in development mode + // this will happen before creating the browser window. it returns a Boolean whether the protocol of scheme 'app://' was successfully registered and a file (index-prod.html) was sent as the response + + // Create the browser window. + win = new BrowserWindow({ + // full screen + width: 1920, + height: 1080, + + minWidth: 980, + // window title + title: 'ReacType', + // the browser window will not display initially as it's loading + // once the browser window renders, a function is called below that hides the splash screen and displays the browser window + show: false, + webPreferences: { + zoomFactor: 0.7, + // enable devtools when in development mode + devTools: true, + // crucial security feature - blocks rendering process from having access to node modules + nodeIntegration: true, + // web workers will not have access to node + nodeIntegrationInWorker: false, + // disallow experimental feature to allow node.js support in sub-frames (i-frames/child windows) + nodeIntegrationInSubFrames: true, + // runs electron apis and preload script in a separate JS context + // separate context has access to document/window but has it's own built-ins and is isolate from changes to global environment by located page + // Electron API only available from preload, not loaded page + contextIsolation: true, + // disables remote module. critical for ensuring that rendering process doesn't have access to node functionality + // EnableRemoteModule: true is Deprecated + // path of preload script. preload is how the renderer page will have access to electron functionality + preload: path.join(__dirname, 'preload.js') + // nativeWindowOpen: true is Deprecated + } + }); + + // Splash screen that appears while loading + // const hideSplashscreen = initSplashScreen({ + // mainWindow: win, + // icon: resolve('app/src/public/icons/png/64x64.png'), + // url: OfficeTemplate, + // width: 500, + // height: 300, + // brand: 'OS Labs', + // productName: 'ReacType', + // logo: resolve('app/src/public/icons/png/64x64.png'), + // color: '#3BBCAF', + // website: 'www.reactype.io', + // text: 'Initializing ...', + // }); + // Load app + if (isDev) { + // load app from web-dev server + win.loadURL(selfHost); + } else { + // load local file if in production + win.loadURL(`app://rse/index-prod.html`); + } + + // load page once window is loaded + win.once('ready-to-show', () => { + win!.show(); + }); + + // automatically open DevTools when opening the app + // Note: devtools is creating many errors in the logs at the moment but can't figure out how to resolve the issue + if (isDev) { + win.webContents.once('dom-ready', () => { + debug(); + win!.webContents.openDevTools(); + }); + } + + // Emitted when the window is closed. + win.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + app.quit(); + }); + + menuBuilder = MenuBuilder(win!, 'ReacType'); + menuBuilder.buildMenu(); + + // Removed this security feature for now since it's not being used + // https://electronjs.org/docs/tutorial/security#4-handle-session-permission-requests-from-remote-content + // TODO: is this the same type of sessions that have in react type + // Could potentially remove this session capability - it appears to be more focused on approving requests from 3rd party notifications + const ses = session; + + const partition = 'default'; + ses + .fromPartition(partition) + .setPermissionRequestHandler((webContents, permission, callback) => { + const allowedPermissions: string[] = []; // Full list here: https://developer.chrome.com/extensions/declare_permissions#manifest + + if (allowedPermissions.includes(permission)) { + callback(true); // Approve permission request + } else { + console.error( + `The application tried to request permission for '${permission}'. This permission was not whitelisted and has been blocked.` + ); + + callback(false); // Deny + } + }); + + // https://electronjs.org/docs/tutorial/security#1-only-load-secure-content; + // The below code can only run when a scheme and host are defined, I thought + // we could use this over _all_ urls + ses + .fromPartition(partition) + .webRequest.onBeforeRequest( + { urls: ['http://localhost./*'] }, + (details, callback) => { + if (details.url.indexOf('http://') >= 0) { + callback({ cancel: true }); + } else { + callback({}); + } + } + ); +} + +// Needs to be called before app is ready; +// gives our scheme access to load relative files, +// as well as local storage, cookies, etc. +// https://electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes +protocol.registerSchemesAsPrivileged([ + { + scheme: 'app', + privileges: { + standard: true, + secure: true, + allowServiceWorkers: true, + supportFetchAPI: true + } + } +]); + +const DIST_PATH = path.join(__dirname, '../../app/dist') + +protocol.handle('app', (request) => { + const url = new URL(request.url); + const filePath = url.pathname; + + // Construct the full path using DIST_PATH and requested file path + const fullPath = path.join(DIST_PATH, filePath); + + // Fetch the resource and return a promise + return new Promise((resolve, reject) => { + net.fetch(fullPath) + .then((response) => { + // Resolve with the fetched response + resolve(response); + }) + .catch((error) => { + // Reject with the error + reject(error); + }); + }); +}); +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow); +// TRYING ELECTRON-WINDOW-MANAGER When the application is ready + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + win!.webContents.executeJavaScript('window.localStorage.clear();'); + app.quit(); +}); + +app.on('activate', () => { + if (win === null) { + createWindow(); + } +}); + +// https://electronjs.org/docs/tutorial/security#12-disable-or-limit-navigation +// limits navigation within the app to a whitelisted array +// redirects are a common attack vector + +// after the contents of the webpage are rendered, set up event listeners on the webContents +app.on( + 'web-contents-created', + (event: Electron.Event, contents: Electron.WebContents) => { + contents.on( + 'will-navigate', + (event: Electron.Event, navigationUrl: string) => { + const parsedUrl = new URL(navigationUrl); + const validOrigins = [ + selfHost, + //'https://reactype-1.herokuapp.com', + 'https://reactype-caret.herokuapp.com', + `http://localhost:${DEV_PORT}`, + 'https://reactype.herokuapp.com', + 'https://github.com', + 'https://nextjs.org', + 'https://www.facebook.com', + 'https://developer.mozilla.org', + 'https://www.smashingmagazine.com', + 'https://www.html5rocks.com', + 'null', + 'app://rse/' + ]; + // Log and prevent the app from navigating to a new page if that page's origin is not whitelisted + if (!validOrigins.includes(parsedUrl.origin)) { + console.error( + `The application tried to navigate to the following address: '${parsedUrl}'. This origin is not whitelisted attempt to navigate was blocked.` + ); + // if the requested URL is not in the whitelisted array, then don't navigate there + event.preventDefault(); + } + } + ); + + contents.on( + 'will-redirect', + (event: Electron.Event, navigationUrl: string) => { + const parsedUrl = new URL(navigationUrl); + const validOrigins = [ + selfHost, + //'https://reactype-1.herokuapp.com/', + 'https://reactype-caret.herokuapp.com', + `http://localhost:${DEV_PORT}`, + 'https://reactype.herokuapp.com', + 'https://github.com', + 'https://nextjs.org', + 'https://developer.mozilla.org', + 'https://www.facebook.com', + 'https://www.smashingmagazine.com', + 'https://www.html5rocks.com', + 'app://rse/', + 'null' + ]; + // Log and prevent the app from redirecting to a new page + if ( + !validOrigins.includes(parsedUrl.origin) && + !validOrigins.includes(parsedUrl.href) + ) { + console.error( + `The application tried to redirect to the following address: '${navigationUrl}'. This attempt was blocked.` + ); + + event.preventDefault(); + } + } + ); + + // https://electronjs.org/docs/tutorial/security#11-verify-webview-options-before-creation + // The web-view is used to embed guest content in a page + // This event listener deletes web-views if they happen to occur in the app + // https://www.electronjs.org/docs/api/web-contents#event-will-attach-webview + contents.on( + 'will-attach-webview', + (event: Electron.Event, webPreferences: Electron.WebPreferences) => { + // Strip away preload scripts if unused or verify their location is legitimate + delete webPreferences.preload; + + // Disable Node.js integration + webPreferences.nodeIntegration = false; + } + ); + + // https://electronjs.org/docs/tutorial/security#13-disable-or-limit-creation-of-new-windows + contents.setWindowOpenHandler(({ url }) => { + // Log and prevent opening up a new window + const parsedUrl = new URL(url); + const validOrigins = [ + selfHost, + //'https://reactype-1.herokuapp.com/', + 'https://reactype-caret.herokuapp.com', + `http://localhost:${DEV_PORT}`, + 'https://reactype.herokuapp.com', + 'https://nextjs.org', + 'https://developer.mozilla.org', + 'https://github.com', + 'https://www.facebook.com', + 'https://www.smashingmagazine.com', + 'https://www.html5rocks.com', + 'null', + 'app://rse/' + ]; + // Log and prevent the app from navigating to a new page if that page's origin is not whitelisted + if (!validOrigins.includes(parsedUrl.origin)) { + console.error( + `The application tried to open a new window at the following address: '${url}'. This attempt was blocked.` + ); + // if the requested URL is not in the whitelisted array, then don't navigate there + return { action: 'deny' }; + } + return { action: 'allow' }; + }); + } +); + +// Filter loading any module via remote; +// you shouldn't be using remote at all, though +// https://electronjs.org/docs/tutorial/security#16-filter-the-remote-module +// commented out due to lack of remote usage / deprecation / all this code does is prevent the usage of remote. +// app.on('remote-require', (event, webContents, moduleName) => { +// event.preventDefault(); +// }); + +// // built-ins are modules such as "app" +// app.on('remote-get-builtin', (event, webContents, moduleName) => { +// event.preventDefault(); +// }); + +// app.on('remote-get-global', (event, webContents, globalName) => { +// event.preventDefault(); +// }); + +// app.on('remote-get-current-window', (event, webContents) => { +// event.preventDefault(); +// }); + +// app.on('remote-get-current-web-contents', (event, webContents) => { +// event.preventDefault(); +// }); + +// When a user selects "Export project", a function (chooseAppDir loaded via preload.js) +// is triggered that sends a "choose_app_dir" message to the main process +// when the "choose_app_dir" message is received it triggers this event listener +ipcMain.on('choose_app_dir', (event) => { + // dialog displays the native system's dialogue for selecting files + // once a directory is chosen send a message back to the renderer with the path of the directory + dialog + .showOpenDialog(win!, { + properties: ['openDirectory'], + buttonLabel: 'Export' + }) + .then((directory) => { + if (!directory) return; + event.sender.send('app_dir_selected', directory.filePaths[0]); + }) + .catch((err) => console.log('ERROR on "choose_app_dir" event: ', err)); +}); + +// define serverURL for cookie and auth purposes based on environment +let serverUrl = 'https://reactype-caret.herokuapp.com'; +if (isDev) { + serverUrl = `http://localhost:${DEV_PORT}`; +} + +// // for github oauth login in production, since cookies are not accessible through document.cookie on local filesystem, we need electron to grab the cookie that is set from oauth, this listens for an set cookie event from the renderer process then sends back the cookie +ipcMain.on('set_cookie', (event) => { + session.defaultSession.cookies + .get({ url: serverUrl }) + .then((cookie) => { + // this if statement is necessary or the setInterval on main app will constantly run and will emit this event.reply, causing a memory leak + // checking for a cookie inside array will only emit reply when a cookie exists + if (cookie[0]) { + event.reply('give_cookie', cookie); + } + }) + .catch((error) => { + console.log('Error giving cookies in set_cookie:', error); + }); +}); + +// again for production, document.cookie is not accessible so we need this listener on main to delete the cookie on logout +ipcMain.on('delete_cookie', (event) => { + session.defaultSession.cookies + .remove(serverUrl, 'ssid') + // .then(removed => { + // }) + .catch((err) => console.log('Error deleting cookie:', err)); +}); + +// opens new window for github oauth when button on sign in page is clicked +// ipcMain.on('github', (event) => { +// const githubURL = isDev +// ? `http://localhost:${DEV_PORT}/auth/github` +// : `https://reactype-caret.herokuapp.com/auth/github`; +// const options = { +// client_id: process.env.GITHUB_CLIENT, +// client_secret: process.env.GITHUB_SECRET, +// scopes: ['user:email', 'notifications'] +// }; +// // create new browser window object with size, title, security options +// const github: BrowserWindow | null = new BrowserWindow({ +// width: 800, +// height: 600, +// title: 'Github Oauth', +// webPreferences: { +// nodeIntegration: true, +// nodeIntegrationInWorker: false, +// nodeIntegrationInSubFrames: false, +// contextIsolation: true, +// zoomFactor: 1.0 +// } +// }); + +// github.loadURL(githubURL); +// github.show(); +// const handleCallback = (url) => { +// const raw_code = /code=([^&]\*)/.exec(url) || null; +// const code = raw_code && raw_code.length > 1 ? raw_code[1] : null; +// const error = /\?error=(.+)\$/.exec(url); + +// if (code || error) { +// // Close the browser if code found or error +// github.destroy(); +// } + +// // If there is a code, proceed to get token from github +// if (code) { +// self.requestGithubToken(options, code); +// } else if (error) { +// alert( +// "Oops! Something went wrong and we couldn't" + +// 'log you in using Github. Please try again.' +// ); +// } +// }; + +// github.webContents.on('will-navigate', (e, url) => handleCallback(url)); + +// github.webContents.on('did-finish-load', (e, url, a, b) => { +// github.webContents.selectAll(); +// }); + +// github.webContents.on( +// 'will-redirect', +// (event: Electron.Event, callbackUrl: string): void => +// handleCallback(callbackUrl) +// ); +// // Reset the authWindow on close +// github.on('close', () => { +// github = null; +// }); + +// // if final callback is reached and we get a redirect from server back to our app, close oauth window +// github.webContents.on('will-redirect', (e, callbackUrl) => { +// const matches = callbackUrl.match(/(?<=\?=).*/); +// const ssid = matches ? matches[0] : ''; +// callbackUrl = callbackUrl.replace(/\?=.*/, ''); +// let redirectUrl = 'app://rse/'; +// if (isDev) { +// redirectUrl = 'http://localhost:8080/'; +// } + +// if (callbackUrl === redirectUrl) { +// dialog.showMessageBox({ +// type: 'info', +// title: 'ReacType', +// icon: resolve('app/src/public/icons/png/256x256.png'), +// message: 'Github Oauth Successful!' +// }); +// github.close(); +// win!.webContents +// .executeJavaScript(`window.localStorage.setItem('ssid', '${ssid}')`) +// .then((result) => win!.loadURL(`${redirectUrl}`)) +// .catch((err) => console.log(err)); +// } +// }); +// }); +// updated github OAuth +ipcMain.on('github', (event, webContents: WebContents) => { + const githubAuthURL = 'https://github.com/login/oauth/authorize'; + const githubTokenURL = 'https://github.com/login/oauth/access_token'; + + const options = { + client_id: process.env.GITHUB_CLIENT, + client_secret: process.env.GITHUB_SECRET, + redirect_uri: isDev ? 'http://localhost:8080/' : 'app://rse/', // Your redirect URL + scopes: ['user:email', 'notifications'], + }; + + const authWindow = new BrowserWindow({ + width: 800, + height: 600, + title: 'GitHub OAuth', + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + }, + }); + + const authURL = `${githubAuthURL}?client_id=${options.client_id}&scope=${options.scopes.join(',')}`; + authWindow.loadURL(authURL); + + authWindow.webContents.on('will-redirect', async (event, url) => { + const rawCode = /code=([^&]*)/.exec(url) || null; + const code = rawCode && rawCode.length > 1 ? rawCode[1] : null; + const error = /\?error=(.+)$/.exec(url); + + if (code) { + try { + const { data } = await axios.post(githubTokenURL, { + code, + client_id: options.client_id, + client_secret: options.client_secret, + redirect_uri: options.redirect_uri, + }); + + const accessToken = data.access_token; + // Store accessToken securely + webContents.send('github-auth-success', accessToken); + } catch (error) { + // Handle error + console.error('Error exchanging code for token:', error); + webContents.send('github-auth-failure'); + } + } else if (error) { + // Handle error + console.error('Error during GitHub OAuth:', error[1]); + webContents.send('github-auth-failure'); + } + + authWindow.close(); + }); +}); + +// ipcMain.on('openTutorialWindow', (event) => { +// // create new browser window object with size, title, security options +// const tutorial = new BrowserWindow({ +// width: 800, +// height: 600, +// minWidth: 661, +// title: 'Tutorial', +// webPreferences: { +// nodeIntegration: true, +// nodeIntegrationInWorker: false, +// nodeIntegrationInSubFrames: false, +// contextIsolation: true, +// zoomFactor: 1.0 +// } +// }); +// // redirects to relevant server endpoint +// tutorial.loadURL(`${serverUrl}/github`); +// // show window +// tutorial.show(); +// // if final callback is reached and we get a redirect from server back to our app, close oauth window +// tutorial.webContents.on('will-redirect', (e, callbackUrl) => { +// let redirectUrl = 'app://rse/'; +// if (isDev) { +// redirectUrl = 'http://localhost:8080/'; +// } +// if (callbackUrl === redirectUrl) { +// event.sender.send('showOAuthSuccessMessage'); +// tutorial.close(); +// } +// }); +// }); + +module.exports = dialog; diff --git a/app/.electron/menu.ts b/app/.electron/menu.ts index c0814ebe2..61904449c 100644 --- a/app/.electron/menu.ts +++ b/app/.electron/menu.ts @@ -1,248 +1,246 @@ -import { Menu, BrowserWindow, Shell } from 'electron'; -const isMac = process.platform === 'darwin'; -import Protocol from './protocol'; -/* -DESCRIPTION: This file generates an array containing a menu based on the operating system the user is running. -menuBuilder: The entire file is encompassed in menuBuilder. Ultimately, menuBuilder returns a function called - buildMenu that uses defaultTemplate to construct a menu at the top of the application (as invoked in main.js) - - Standard menu roles (e.g., undo, redo, quit, paste, etc.) come from Electron API and need not be separately coded - -openTutorial: opens browser window containing tutorial on how to use the app - -Creates a browser window - -Tutorial is invoked within the "Help" menu - -defaultTemplate: returns an array of submenus (each an array) - -First, checks whether user is on a Mac (node returns 'darwin' for process.platform) - -Then generates a dropdown menu at the top of the screen (e.g., "File") accordingly - -The Mac check is necessary primarily for the first menu column, which is the name of the app - -If user is not on a Mac, alternative menus are generated - -Each menu: - -"label" is the field at the top of each menu (e.g., "File", "Edit", "View", etc.) - -"role" is a subitem within each menu (e.g., under "File," "Quit") - -"type: separator" creates a horizontal line in a menu (e.g., under "Redo" in the "Edit" menu) -*/ - -// Create a template for a menu and create menu using that template -var MenuBuilder = function (mainWindow, appName) { - // https://electronjs.org/docs/api/menu#main-process - // "roles" are predefined by Electron and used for standard actions - // https://www.electronjs.org/docs/api/menu-item - // you can also create custom menu items with their own "on click" functionality if you need to - // different roles are available between mac and windows - - function openTutorial(): void { - const tutorial = new BrowserWindow({ - width: 1180, - height: 900, - minWidth: 665, - title: 'Tutorial', - webPreferences: { - nodeIntegration: true, - nodeIntegrationInWorker: false, - nodeIntegrationInSubFrames: false, - contextIsolation: true, - enableRemoteModule: true, - zoomFactor: 1.0, - devTools: false - } - }); - if (import.meta.env.NODE_ENV === 'development') { - tutorial.loadURL(`http://localhost:8080/#/tutorial`); - } else { - tutorial.loadURL(`${Protocol.scheme}://rse/index-prod.html#/tutorial`); - } - tutorial.show(); - } - - const defaultTemplate= (): Electron.MenuItemConstructorOptions[] => [ - ...(isMac - ? [ - { - // on Mac, the first menu item name should be the name of the app - label: appName, - submenu: [ - {role: 'about'}, - {type: 'separator'}, - {role: 'services'}, - {type: 'separator'}, - {role: 'hide'}, - {role: 'hideothers'}, - {role: 'unhide'}, - {type: 'separator'}, - {role: 'quit'} - ] as Electron.MenuItemConstructorOptions[], - } , - ] - : []),Electron.MenuItemConstructorOptions[], - { - label: 'File', - submenu: [ - isMac - ? { - role: 'close' - } - : { - role: 'quit' - } - ] - }, - { - label: 'Edit', - submenu: [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - ...(isMac - ? [ - { - role: 'pasteAndMatchStyle' - }, - { - role: 'delete' - }, - { - role: 'selectAll' - }, - { - type: 'separator' - }, - { - label: 'Speech', - submenu: [ - { - role: 'startspeaking' - }, - { - role: 'stopspeaking' - } - ] - } - ] - : [ - { - role: 'delete' - }, - { - type: 'separator' - }, - { - role: 'selectAll' - } - ]) - ] - }, - { - label: 'View', - submenu: [ - { - role: 'reload' - }, - { - role: 'forcereload' - }, - { - role: 'toggledevtools' - }, - { - type: 'separator' - }, - { - role: 'resetzoom' - }, - { - role: 'zoomin' - }, - { - role: 'zoomout' - }, - { - type: 'separator' - }, - { - role: 'togglefullscreen' - } - ] - }, - - { - label: 'Window', - submenu: [ - { - role: 'minimize' - }, - { - role: 'zoom' - }, - ...(isMac - ? [ - { - type: 'separator' - }, - { - role: 'front' - }, - { - type: 'separator' - }, - { - role: 'window' - } - ] - : [ - { - role: 'close' - } - ]) - ] - }, - { - role: 'help', - submenu: [ - { - label: 'Learn More', - click: async () => { - const { shell } = require('electron'); - await shell.openExternal( - 'https://github.com/open-source-labs/ReacType' - ); - } - }, - { - label: 'Tutorial', - click: () => openTutorial() - } - ] - } - ]; - - return template; - } - - // constructs menu from default template - return { - buildMenu: function () { - const menu = Menu.buildFromTemplate(defaultTemplate()); - Menu.setApplicationMenu(menu); - - return menu; - } - }; -}; - -export { MenuBuilder }; +import { Menu, BrowserWindow, Shell } from 'electron'; +const isMac = process.platform === 'darwin'; +import {scheme, requestHandler } from './protocol'; +/* +DESCRIPTION: This file generates an array containing a menu based on the operating system the user is running. +menuBuilder: The entire file is encompassed in menuBuilder. Ultimately, menuBuilder returns a function called + buildMenu that uses defaultTemplate to construct a menu at the top of the application (as invoked in main.js) + + Standard menu roles (e.g., undo, redo, quit, paste, etc.) come from Electron API and need not be separately coded + +openTutorial: opens browser window containing tutorial on how to use the app + -Creates a browser window + -Tutorial is invoked within the "Help" menu + +defaultTemplate: returns an array of submenus (each an array) + -First, checks whether user is on a Mac (node returns 'darwin' for process.platform) + -Then generates a dropdown menu at the top of the screen (e.g., "File") accordingly + -The Mac check is necessary primarily for the first menu column, which is the name of the app + -If user is not on a Mac, alternative menus are generated + -Each menu: + -"label" is the field at the top of each menu (e.g., "File", "Edit", "View", etc.) + -"role" is a subitem within each menu (e.g., under "File," "Quit") + -"type: separator" creates a horizontal line in a menu (e.g., under "Redo" in the "Edit" menu) +*/ + +// Create a template for a menu and create menu using that template +var MenuBuilder = function (mainWindow, appName) { + // https://electronjs.org/docs/api/menu#main-process + // "roles" are predefined by Electron and used for standard actions + // https://www.electronjs.org/docs/api/menu-item + // you can also create custom menu items with their own "on click" functionality if you need to + // different roles are available between mac and windows + + // function openTutorial(): void { + // const tutorial = new BrowserWindow({ + // width: 1180, + // height: 900, + // minWidth: 665, + // title: 'Tutorial', + // webPreferences: { + // nodeIntegration: true, + // nodeIntegrationInWorker: false, + // nodeIntegrationInSubFrames: false, + // contextIsolation: true, + // zoomFactor: 1.0, + // devTools: false + // } + // }); + // if (import.meta.env.NODE_ENV === 'development') { + // tutorial.loadURL(`http://localhost:8080/#/tutorial`); + // } else { + // tutorial.loadURL(`${scheme}://rse/index-prod.html#/tutorial`); + // } + // tutorial.show(); + // } + + const defaultTemplate= (): Electron.MenuItemConstructorOptions[] => [ + ...(isMac + ? [ + { + // on Mac, the first menu item name should be the name of the app + label: appName, + submenu: [ + {role: 'about'}, + {type: 'separator'}, + {role: 'services'}, + {type: 'separator'}, + {role: 'hide'}, + {role: 'hideothers'}, + {role: 'unhide'}, + {type: 'separator'}, + {role: 'quit'} + ] as Electron.MenuItemConstructorOptions[], + } , + ] + : []),Electron.MenuItemConstructorOptions[], + { + label: 'File', + submenu: [ + isMac + ? { + role: 'close' + } + : { + role: 'quit' + } + ] + }, + { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + ...(isMac + ? [ + { + role: 'pasteAndMatchStyle' + }, + { + role: 'delete' + }, + { + role: 'selectAll' + }, + { + type: 'separator' + }, + { + label: 'Speech', + submenu: [ + { + role: 'startspeaking' + }, + { + role: 'stopspeaking' + } + ] + } + ] + : [ + { + role: 'delete' + }, + { + type: 'separator' + }, + { + role: 'selectAll' + } + ]) + ] + }, + { + label: 'View', + submenu: [ + { + role: 'reload' + }, + { + role: 'forcereload' + }, + { + role: 'toggledevtools' + }, + { + type: 'separator' + }, + { + role: 'resetzoom' + }, + { + role: 'zoomin' + }, + { + role: 'zoomout' + }, + { + type: 'separator' + }, + { + role: 'togglefullscreen' + } + ] + }, + + { + label: 'Window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'zoom' + }, + ...(isMac + ? [ + { + type: 'separator' + }, + { + role: 'front' + }, + { + type: 'separator' + }, + { + role: 'window' + } + ] + : [ + { + role: 'close' + } + ]) + ] + }, + { + role: 'help', + submenu: [ + { + label: 'Learn More', + click: async () => { + const { shell } = require('electron'); + await shell.openExternal( + 'https://github.com/open-source-labs/ReacType' + ); + } + }, + // { + // label: 'Tutorial', + // click: () => openTutorial() + // } + ] + } + ]; + + return template; + } + + // constructs menu from default template + return { + buildMenu: function () { + const menu = Menu.buildFromTemplate(defaultTemplate()); + Menu.setApplicationMenu(menu); + + return menu; + } + }; + +export { MenuBuilder }; diff --git a/app/.electron/preload.ts b/app/.electron/preload.ts index 0b10eb72c..5fbcfec69 100644 --- a/app/.electron/preload.ts +++ b/app/.electron/preload.ts @@ -1,54 +1,54 @@ -// contextBridge is what allows context to be translated between the main process and the render process -import { contextBridge } from 'electron'; -import { existsSync, writeFileSync, mkdirSync, writeFile } from 'fs'; -import formatCode from './preloadFunctions/format'; -import { - chooseAppDir, - addAppDirChosenListener, - removeAllAppDirChosenListeners -} from './preloadFunctions/chooseAppDir'; -import { - setCookie, - getCookie, - delCookie, - github, - tutorial -} from './preloadFunctions/cookies'; - -/* -DESCRIPTION: This file appears to limit the node methods the Electron app can access. - -Per the docs: - -Main World is the JavaScript context in which the renderer code runs (that is, the page) - -Isolated World is where preload scripts run - -contextBridge is a module that safely exposes APIs from the isolated context in which preload scripts run - to the context in which the website or application runs (i.e., from Isolated World to Main World) - -We likely should not change this file unless we determine additional methods are necessary -or some methods are not used. - -*/ - -// Expose protected methods that allow the renderer process to use select node methods -// without exposing all node functionality. This is a critical security feature -// 'mainWorld" is the context that the main renderer runs in -// with contextIsolation on (see webpreferences on main.js), this preload script runs isolated -// "api" is the key that injects the api into the window -// to access these keys in the renderer process, we'll do window.api -// the api object (second arg) can contain functions, strings, bools, numbers, arrays, obects in value -// data primitives sent on the bridge are immutable and changes in one context won't carry over to another context -contextBridge.exposeInMainWorld('api', { - formatCode, - chooseAppDir, - addAppDirChosenListener, - removeAllAppDirChosenListeners, - existsSync, - writeFileSync, - mkdirSync, - writeFile, - setCookie, - getCookie, - delCookie, - github, - tutorial -}); +// contextBridge is what allows context to be translated between the main process and the render process +import { contextBridge } from 'electron'; +import { existsSync, writeFileSync, mkdirSync, writeFile } from 'fs'; +import formatCode from './preloadFunctions/format'; +import { + chooseAppDir, + addAppDirChosenListener, + removeAllAppDirChosenListeners +} from './preloadFunctions/chooseAppDir'; +import { + setCookie, + getCookie, + delCookie, + github, + tutorial +} from './preloadFunctions/cookies'; + +/* +DESCRIPTION: This file appears to limit the node methods the Electron app can access. + +Per the docs: + -Main World is the JavaScript context in which the renderer code runs (that is, the page) + -Isolated World is where preload scripts run + -contextBridge is a module that safely exposes APIs from the isolated context in which preload scripts run + to the context in which the website or application runs (i.e., from Isolated World to Main World) + +We likely should not change this file unless we determine additional methods are necessary +or some methods are not used. + +*/ + +// Expose protected methods that allow the renderer process to use select node methods +// without exposing all node functionality. This is a critical security feature +// 'mainWorld" is the context that the main renderer runs in +// with contextIsolation on (see webpreferences on main.js), this preload script runs isolated +// "api" is the key that injects the api into the window +// to access these keys in the renderer process, we'll do window.api +// the api object (second arg) can contain functions, strings, bools, numbers, arrays, obects in value +// data primitives sent on the bridge are immutable and changes in one context won't carry over to another context +contextBridge.exposeInMainWorld('api', { + formatCode, + chooseAppDir, + addAppDirChosenListener, + removeAllAppDirChosenListeners, + existsSync, + writeFileSync, + mkdirSync, + writeFile, + setCookie, + getCookie, + delCookie, + github, + tutorial +}); diff --git a/app/.electron/preloadFunctions/chooseAppDir.ts b/app/.electron/preloadFunctions/chooseAppDir.ts index 969b17f56..96ec31b3e 100644 --- a/app/.electron/preloadFunctions/chooseAppDir.ts +++ b/app/.electron/preloadFunctions/chooseAppDir.ts @@ -1,31 +1,31 @@ -import { ipcRenderer, IpcRendererEvent } from 'electron'; -import { type } from 'os'; - -type AppDirSelectedCallback = (path: string) => void; - -const chooseAppDir = (): void => { - ipcRenderer.send('choose_app_dir'); -}; - -// once an app directory is chosen, the main process will send an "app_dir_selected" event -// when this event occurs, exucte the callback passed in by the user -const addAppDirChosenListener = (callback: AppDirSelectedCallback): void => { - ipcRenderer.on( - 'app_dir_selected', - (event: IpcRendererEvent, path: string) => { - callback(path); - } - ); -}; - -// removes all listeners for the app_dir_selected event -// this is important because otherwise listeners will pile up and events will trigger multiple events -const removeAllAppDirChosenListeners = (): void => { - ipcRenderer.removeAllListeners('app_dir_selected'); -}; - -export { - chooseAppDir, - addAppDirChosenListener, - removeAllAppDirChosenListeners -}; +import { ipcRenderer, IpcRendererEvent } from 'electron'; +import { type } from 'os'; + +type AppDirSelectedCallback = (path: string) => void; + +const chooseAppDir = (): void => { + ipcRenderer.send('choose_app_dir'); +}; + +// once an app directory is chosen, the main process will send an "app_dir_selected" event +// when this event occurs, exucte the callback passed in by the user +const addAppDirChosenListener = (callback: AppDirSelectedCallback): void => { + ipcRenderer.on( + 'app_dir_selected', + (event: IpcRendererEvent, path: string) => { + callback(path); + } + ); +}; + +// removes all listeners for the app_dir_selected event +// this is important because otherwise listeners will pile up and events will trigger multiple events +const removeAllAppDirChosenListeners = (): void => { + ipcRenderer.removeAllListeners('app_dir_selected'); +}; + +export { + chooseAppDir, + addAppDirChosenListener, + removeAllAppDirChosenListeners +}; diff --git a/app/.electron/preloadFunctions/cookies.ts b/app/.electron/preloadFunctions/cookies.ts index f5506f83e..da3007610 100644 --- a/app/.electron/preloadFunctions/cookies.ts +++ b/app/.electron/preloadFunctions/cookies.ts @@ -1,27 +1,27 @@ -import { ipcRenderer, IpcRendererEvent } from 'electron'; - -type GetCookieCallback = (cookie: string) => void; - -const setCookie = (): void => { - ipcRenderer.send('set_cookie'); -}; - -const getCookie = (callback: GetCookieCallback): void => { - ipcRenderer.on('give_cookie', (event: IpcRendererEvent, cookie: string) => { - callback(cookie); - }); -}; - -const delCookie = (): void => { - ipcRenderer.send('delete_cookie'); -}; - -const github = (): void => { - ipcRenderer.send('github'); -}; - -const tutorial = (): void => { - ipcRenderer.send('tutorial'); -}; - -export { setCookie, getCookie, delCookie, github, tutorial }; +import { ipcRenderer, IpcRendererEvent } from 'electron'; + +type GetCookieCallback = (cookie: string) => void; + +const setCookie = (): void => { + ipcRenderer.send('set_cookie'); +}; + +const getCookie = (callback: GetCookieCallback): void => { + ipcRenderer.on('give_cookie', (event: IpcRendererEvent, cookie: string) => { + callback(cookie); + }); +}; + +const delCookie = (): void => { + ipcRenderer.send('delete_cookie'); +}; + +const github = (): void => { + ipcRenderer.send('github'); +}; + +const tutorial = (): void => { + ipcRenderer.send('tutorial'); +}; + +export { setCookie, getCookie, delCookie, github, tutorial }; diff --git a/app/.electron/preloadFunctions/format.ts b/app/.electron/preloadFunctions/format.ts index c1ed8e506..9575cc9e6 100644 --- a/app/.electron/preloadFunctions/format.ts +++ b/app/.electron/preloadFunctions/format.ts @@ -1,16 +1,16 @@ -import { format } from 'prettier'; - -// format code using prettier -// this format function is used in the render process to format the code in the code preview -// the format function is defined in the main process because it needs to access node functionality ('fs') -const formatCode = (code: string): string => { - return format(code, { - singleQuote: true, - trailingComma: 'es5', - bracketSpacing: true, - jsxBracketSameLine: true, - parser: 'typescript' - }); -}; - -export default formatCode; +import { format } from 'prettier'; + +// format code using prettier +// this format function is used in the render process to format the code in the code preview +// the format function is defined in the main process because it needs to access node functionality ('fs') +const formatCode = (code: string): string => { + return format(code, { + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + jsxBracketSameLine: true, + parser: 'typescript' + }); +}; + +export default formatCode; diff --git a/app/.electron/protocol.ts b/app/.electron/protocol.ts index a89f0c50e..6b0a04906 100644 --- a/app/.electron/protocol.ts +++ b/app/.electron/protocol.ts @@ -1,76 +1,78 @@ -/* - @desc: register a custom protocol and specify file that will be served on request to the origin '/'. our app will be served from 'app://...' instead of the default 'file://...' - @exports: scheme, requestHandler - @usage: is used in main.js - */ - -const { stringify } = require('querystring'); - -import * as fs from 'fs'; -import * as path from 'path'; - -const DIST_PATH = path.join(__dirname, '../../app/dist'); - -const scheme = 'app'; // it will serve resources like app://..... instead of default file://... - -const mimeTypes: Record = { - '.js': 'text/javascript', - '.mjs': 'text/javascript', - '.html': 'text/html', - '.htm': 'text/html', - '.json': 'application/json', - '.css': 'text/css', - '.svg': 'application/svg+xml', - '.ico': 'image/vnd.microsoft.icon', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.map': 'text/plain' -}; - -function charset(mimeType: string): string | null { - return ['.html', '.htm', '.js', '.mjs'].some((m) => m === mimeType) - ? 'utf-8' - : null; -} -// return the file type -function mime(filename: string): string | null { - const type = mimeTypes[path.extname(`${filename || ''}`).toLowerCase()]; - return type || null; -} - -/* requestHandler - servers index-prod.html when we access the root endpoint '/' - read the file above and pass on an object includes mimeType, charset, and exisiting data read from the file -*/ -function requestHandler( - req: Electron.ProtocolRequest, - next: (response: Electron.ProtocolResponse) => void -): void { - // The URL() constructor returns a newly created URL object representing the URL defined by the parameters. - const reqUrl = new URL(req.url); - // path.normalize resolves '..' and '.' segments in sequential path segments - // url.pathname: an initial '/' followed by the path of the URL not including the query string or fragment (or the empty string if there is no path). - let reqPath = path.normalize(reqUrl.pathname); - - // when app opens, serve index-prod.html - if (reqPath === '/') { - reqPath = '/index-prod.html'; - } - // path.basename returns the last portion of a path which includes filename we want to serve - const reqFilename = path.basename(reqPath); - // use fs module to read index-prod.html (reqPath) in dist folder - fs.readFile(path.join(DIST_PATH, reqPath), (err, data) => { - const mimeType = mime(reqFilename); // returns the file type - // check if there is no error and file type is valid, pass on the info to the next middleware - if (!err && mimeType !== null) { - next({ - mimeType, - charset: charset(mimeType), - data - }); - } else { - console.error(err); - } - }); -} -export { scheme, requestHandler }; +/* + @desc: register a custom protocol and specify file that will be served on request to the origin '/'. our app will be served from 'app://...' instead of the default 'file://...' + @exports: scheme, requestHandler + @usage: is used in main.js + */ +// import { Request, Response } from 'electron'; +// const { stringify } = require('querystring'); unused +// import * as fs from 'fs'; +// import * as path from 'path'; + + + +// const DIST_PATH = path.join(__dirname, '../../app/dist'); + +// const scheme = 'app'; // it will serve resources like app://..... instead of default file://... + +// const mimeTypes: Record = { +// '.js': 'text/javascript', +// '.mjs': 'text/javascript', +// '.html': 'text/html', +// '.htm': 'text/html', +// '.json': 'application/json', +// '.css': 'text/css', +// '.svg': 'application/svg+xml', +// '.ico': 'image/vnd.microsoft.icon', +// '.png': 'image/png', +// '.jpg': 'image/jpeg', +// '.map': 'text/plain' +// }; + +// function charset(mimeType: string): string | null { +// return ['.html', '.htm', '.js', '.mjs'].some((m) => m === mimeType) +// ? 'utf-8' +// : null; +// } +// // return the file type +// function mime(filename: string): string | null { +// const type = mimeTypes[path.extname(`${filename || ''}`).toLowerCase()]; +// return type || null; +// } + +/* requestHandler + servers index-prod.html when we access the root endpoint '/' + read the file above and pass on an object includes mimeType, charset, and exisiting data read from the file +*/ +// function requestHandler( +// req: Request, +// callback: (response: Response | Promise) => void +// ): void { +// // The URL() constructor returns a newly created URL object representing the URL defined by the parameters. +// const reqUrl = new URL(req.url); +// // path.normalize resolves '..' and '.' segments in sequential path segments +// // url.pathname: an initial '/' followed by the path of the URL not including the query string or fragment (or the empty string if there is no path). +// let reqPath = path.normalize(reqUrl.pathname); + +// // when app opens, serve index-prod.html +// if (reqPath === '/') { +// reqPath = '/index-prod.html'; +// } +// // path.basename returns the last portion of a path which includes filename we want to serve +// const reqFilename = path.basename(reqPath); +// // use fs module to read index-prod.html (reqPath) in dist folder +// fs.readFile(path.join(DIST_PATH, reqPath), (err, data) => { +// const mimeType = mime(reqFilename); // returns the file type +// // check if there is no error and file type is valid, pass on the info to the next middleware +// if (!err && mimeType !== null) { +// callback({ +// mimeType, +// charset: charset(mimeType), +// data +// }); +// } else { +// console.error(err); +// } +// }); +// } + +// export { scheme }; diff --git a/app/.electron/render.ts b/app/.electron/render.ts index b956f920a..f6f79be69 100644 --- a/app/.electron/render.ts +++ b/app/.electron/render.ts @@ -1,17 +1,21 @@ - -import {remote} from 'electron'; -import {BrowserWindow} from 'electron-window-manager'; - -const win2 = browserwindow.createNew('win2', 'Windows #2'); -win2.setURL('/win2.html'); -win2.onReady(() => {...}); -win2.open() - - - -/* ; */ + +// import {remote} from 'electron'; +// import {BrowserWindow} from 'electron-window-manager'; + +// const win2 = BrowserWindow.createNew('win2', 'Windows #2'); +// win2.setURL('/win2.html'); +// win2.onReady(() => {...}); +// win2.open() + + + +/* ; */ + + +// unnecesary code / file +// win2 is unused nor needed. diff --git a/app/src/Dashboard/NavbarDash.tsx b/app/src/Dashboard/NavbarDash.tsx index 328c0ec81..7e854600d 100644 --- a/app/src/Dashboard/NavbarDash.tsx +++ b/app/src/Dashboard/NavbarDash.tsx @@ -1,206 +1,206 @@ -import React, { useState, useContext } from 'react'; -import { Theme } from '@mui/material/styles'; -import withStyles from '@mui/styles/withStyles'; -import createStyles from '@mui/styles/createStyles'; -import makeStyles from '@mui/styles/makeStyles'; -import AppBar from '@mui/material/AppBar'; -import Avatar from '@mui/material/Avatar'; -import Brightness3Icon from '@mui/icons-material/Brightness3'; -import Brightness5Icon from '@mui/icons-material/Brightness5'; -import Button from '@mui/material/Button'; -import EventNoteIcon from '@mui/icons-material/EventNote'; -import HomeIcon from '@mui/icons-material/Home'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; -import { Link } from 'react-router-dom'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import SortIcon from '@mui/icons-material/Sort'; -import StarBorderIcon from '@mui/icons-material/StarBorder'; -import PersonIcon from '@mui/icons-material/Person'; -import greenLogo from '../public/icons/png/512x512.png'; -import { setStyle } from '../redux/reducers/slice/styleSlice'; -import { useSelector, useDispatch } from 'react-redux'; - -// NavBar text and button styling -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - flexGrow: 1, - width: '100%' - }, - menuButton: { - marginRight: theme.spacing(2), - color: 'white' - }, - title: { - flexGrow: 1, - color: 'white' - }, - manageProject: { - display: 'flex', - justifyContent: 'center' - } - }) -); -// sorting options -const sortMethods = ['RATING', 'DATE', 'USER']; -// Drop down menu button for SORT PROJECTS -const StyledMenu = withStyles({ - paper: { - border: '1px solid #d3d4d5' - } -})((props) => ( - -)); -const StyledMenuItem = withStyles((theme) => ({ - root: { - '&:focus': { - '& .MuiListItemIcon-root, & .MuiListItemText-primary': { - color: theme.palette.common.white - } - } - } -}))(MenuItem); -// TO DO: set types of props validation -export default function NavBar(props) { - // TO DO: import setStyle - const classes = useStyles(); - const style = useSelector((store) => store.styleSlice); - const dispatch = useDispatch(); - const toggling = () => setIsOpen(!isOpen); - // toggle to open and close dropdown sorting menu - const [isOpen, setIsOpen] = useState(false); - // State for sort projects button - const [anchorEl, setAnchorEl] = React.useState(null); - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { - setAnchorEl(null); - }; - return ( -
- - - - - - ReacType - - -
- - - {sortMethods.map((option, index) => ( - { - props.optionClicked(option); - toggling(); - handleClose(); - }} - variant="contained" - color="primary" - style={{ minWidth: '137.69px' }} - className={classes.manageProject} - key={index} - > - - - ))} - -
- -
- - - -
-
-
-
- ); -} +import React, { useState, useContext } from 'react'; +import { Theme } from '@mui/material/styles'; +import withStyles from '@mui/styles/withStyles'; +import createStyles from '@mui/styles/createStyles'; +import makeStyles from '@mui/styles/makeStyles'; +import AppBar from '@mui/material/AppBar'; +import Avatar from '@mui/material/Avatar'; +import Brightness3Icon from '@mui/icons-material/Brightness3'; +import Brightness5Icon from '@mui/icons-material/Brightness5'; +import Button from '@mui/material/Button'; +import EventNoteIcon from '@mui/icons-material/EventNote'; +import HomeIcon from '@mui/icons-material/Home'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import { Link } from 'react-router-dom'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import SortIcon from '@mui/icons-material/Sort'; +import StarBorderIcon from '@mui/icons-material/StarBorder'; +import PersonIcon from '@mui/icons-material/Person'; +import greenLogo from '../public/icons/png/512x512.png'; +import { setStyle } from '../redux/reducers/slice/styleSlice'; +import { useSelector, useDispatch } from 'react-redux'; + +// NavBar text and button styling +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + flexGrow: 1, + width: '100%' + }, + menuButton: { + marginRight: theme.spacing(2), + color: 'white' + }, + title: { + flexGrow: 1, + color: 'white' + }, + manageProject: { + display: 'flex', + justifyContent: 'center' + } + }) +); +// sorting options +const sortMethods = ['RATING', 'DATE', 'USER']; +// Drop down menu button for SORT PROJECTS +const StyledMenu = withStyles({ + paper: { + border: '1px solid #d3d4d5' + } +})((props) => ( + +)); +const StyledMenuItem = withStyles((theme) => ({ + root: { + '&:focus': { + '& .MuiListItemIcon-root, & .MuiListItemText-primary': { + color: theme.palette.common.white + } + } + } +}))(MenuItem); +// TO DO: set types of props validation +export default function NavBar(props) { + // TO DO: import setStyle + const classes = useStyles(); + const style = useSelector((store) => store.styleSlice); + const dispatch = useDispatch(); + const toggling = () => setIsOpen(!isOpen); + // toggle to open and close dropdown sorting menu + const [isOpen, setIsOpen] = useState(false); + // State for sort projects button + const [anchorEl, setAnchorEl] = React.useState(null); + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + return ( +
+ + + + + + ReacType + + +
+ + + {sortMethods.map((option, index) => ( + { + props.optionClicked(option); + toggling(); + handleClose(); + }} + variant="contained" + color="primary" + style={{ minWidth: '137.69px' }} + className={classes.manageProject} + key={index} + > + + + ))} + +
+ +
+ + + +
+
+
+
+ ); +} diff --git a/app/src/Dashboard/Project.tsx b/app/src/Dashboard/Project.tsx index fb91843ec..a477f2e72 100644 --- a/app/src/Dashboard/Project.tsx +++ b/app/src/Dashboard/Project.tsx @@ -1,248 +1,248 @@ -import React, { useState } from 'react'; -import { useMutation } from '@apollo/client'; -import { - ADD_LIKE, - MAKE_COPY, - DELETE_PROJECT, - PUBLISH_PROJECT, - ADD_COMMENT -} from './gqlStrings'; -import CloseIcon from '@mui/icons-material/Close'; -import AddCommentIcon from '@mui/icons-material/AddComment'; -import ThumbUpAltIcon from '@mui/icons-material/ThumbUpAlt'; -import GetAppIcon from '@mui/icons-material/GetApp'; -import IconButton from '@mui/material/IconButton'; -import PublishIcon from '@mui/icons-material/Publish'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import createModal from '../components/right/createModal'; -// Variable validation using typescript -type props = { - name: string; - id: string; - userId: string; - username: string; - likes: number; - published: boolean; - comments: object[]; -}; - -// Use current user info to make a make copy of another user's project -const currUserSSID = window.localStorage.getItem('ssid') || 'unavailable'; -const currUsername = window.localStorage.getItem('username') || 'unavailable'; - -const Project = ({ - name, - likes, - id, - username, - published, - comments -}: props): JSX.Element => { - // IMPORTANT: - // 1) schema change projId => id to allows Apollo Client cache auto-update. Only works with 'id' - // 2) always request the 'id' in a mutation request - const [commentVal, setCommentVal] = useState(''); - const [modal, setModal] = useState(null); - const [addLike] = useMutation(ADD_LIKE); - const [makeCopy] = useMutation(MAKE_COPY); - const [deleteProject] = useMutation(DELETE_PROJECT); - const [publishProject] = useMutation(PUBLISH_PROJECT); - const [addComment] = useMutation(ADD_COMMENT); - - const noPointer = { cursor: 'default' }; - //Likes the project when the star icon is clicked - function handleLike(e) { - e.preventDefault(); - const myVar = { - variables: { - projId: id, - likes: likes + 1 - } - }; - addLike(myVar); - } - //Makes a copy of the public project and saves as a user project - function handleDownload(e) { - e.preventDefault(); - const myVar = { - variables: { - projId: id, - userId: currUserSSID, - username: currUsername - } - }; - makeCopy(myVar); - } - //Publishes project from user dashboard to public dashboard - function handlePublish(e) { - e.preventDefault(); - const myVar = { - variables: { - projId: id, - published: !published - } - }; - publishProject(myVar); - } - //Adds the comment to the project - function handleComment(e) { - e.preventDefault(); - const myVar = { - variables: { - projId: id, - comment: commentVal, - username: currUsername - } - }; - addComment(myVar); - } - //sets state of commentVal to what the user types in to comment - function handleChange(e) { - e.preventDefault(); - let commentValue = e.target.value; - setCommentVal(commentValue); - } - const recentComments = []; - if (comments?.length > 0) { - const reversedCommentArray = comments.slice(0).reverse(); - const min = Math.min(6, reversedCommentArray.length); - for (let i = 0; i < min; i++) { - recentComments.push( -

- {reversedCommentArray[i].username}: - {reversedCommentArray[i].text} -

- ); - } - } - // Closes out the open modal - const closeModal = () => setModal(''); - // Creates modal that asks if user wants to delete project - const deleteProjectModal = () => { - //Deletes project from the database - const handleDelete = (e) => { - e.preventDefault(); - const myVar = { - variables: { - projId: id - } - }; - deleteProject(myVar); - }; - // Set modal options - const children = ( - - - - - - ); - - // Create modal - setModal( - createModal({ - closeModal, - children, - message: 'Are you sure you want to delete this project?', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - return ( -
-
- {currUsername === username ? ( - - - - ) : ( - '' - )} -
- -

Project: {name}

-

Author: {username}

-

Likes: {likes}

-
-
- -
- - - - {currUsername !== username ? ( - - - - ) : ( - '' - )} - {currUsername === username ? ( - - - - ) : ( - '' - )} -
-
-
{recentComments}
-
- - -
- {modal} -
- ); -}; -export default Project; +import React, { useState } from 'react'; +import { useMutation } from '@apollo/client'; +import { + ADD_LIKE, + MAKE_COPY, + DELETE_PROJECT, + PUBLISH_PROJECT, + ADD_COMMENT +} from './gqlStrings'; +import CloseIcon from '@mui/icons-material/Close'; +import AddCommentIcon from '@mui/icons-material/AddComment'; +import ThumbUpAltIcon from '@mui/icons-material/ThumbUpAlt'; +import GetAppIcon from '@mui/icons-material/GetApp'; +import IconButton from '@mui/material/IconButton'; +import PublishIcon from '@mui/icons-material/Publish'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import createModal from '../components/right/createModal'; +// Variable validation using typescript +type props = { + name: string; + id: string; + userId: string; + username: string; + likes: number; + published: boolean; + comments: object[]; +}; + +// Use current user info to make a make copy of another user's project +const currUserSSID = window.localStorage.getItem('ssid') || 'unavailable'; +const currUsername = window.localStorage.getItem('username') || 'unavailable'; + +const Project = ({ + name, + likes, + id, + username, + published, + comments +}: props): JSX.Element => { + // IMPORTANT: + // 1) schema change projId => id to allows Apollo Client cache auto-update. Only works with 'id' + // 2) always request the 'id' in a mutation request + const [commentVal, setCommentVal] = useState(''); + const [modal, setModal] = useState(null); + const [addLike] = useMutation(ADD_LIKE); + const [makeCopy] = useMutation(MAKE_COPY); + const [deleteProject] = useMutation(DELETE_PROJECT); + const [publishProject] = useMutation(PUBLISH_PROJECT); + const [addComment] = useMutation(ADD_COMMENT); + + const noPointer = { cursor: 'default' }; + //Likes the project when the star icon is clicked + function handleLike(e) { + e.preventDefault(); + const myVar = { + variables: { + projId: id, + likes: likes + 1 + } + }; + addLike(myVar); + } + //Makes a copy of the public project and saves as a user project + function handleDownload(e) { + e.preventDefault(); + const myVar = { + variables: { + projId: id, + userId: currUserSSID, + username: currUsername + } + }; + makeCopy(myVar); + } + //Publishes project from user dashboard to public dashboard + function handlePublish(e) { + e.preventDefault(); + const myVar = { + variables: { + projId: id, + published: !published + } + }; + publishProject(myVar); + } + //Adds the comment to the project + function handleComment(e) { + e.preventDefault(); + const myVar = { + variables: { + projId: id, + comment: commentVal, + username: currUsername + } + }; + addComment(myVar); + } + //sets state of commentVal to what the user types in to comment + function handleChange(e) { + e.preventDefault(); + let commentValue = e.target.value; + setCommentVal(commentValue); + } + const recentComments = []; + if (comments?.length > 0) { + const reversedCommentArray = comments.slice(0).reverse(); + const min = Math.min(6, reversedCommentArray.length); + for (let i = 0; i < min; i++) { + recentComments.push( +

+ {reversedCommentArray[i].username}: + {reversedCommentArray[i].text} +

+ ); + } + } + // Closes out the open modal + const closeModal = () => setModal(''); + // Creates modal that asks if user wants to delete project + const deleteProjectModal = () => { + //Deletes project from the database + const handleDelete = (e) => { + e.preventDefault(); + const myVar = { + variables: { + projId: id + } + }; + deleteProject(myVar); + }; + // Set modal options + const children = ( + + + + + + ); + + // Create modal + setModal( + createModal({ + closeModal, + children, + message: 'Are you sure you want to delete this project?', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + return ( +
+
+ {currUsername === username ? ( + + + + ) : ( + '' + )} +
+ +

Project: {name}

+

Author: {username}

+

Likes: {likes}

+
+
+ +
+ + + + {currUsername !== username ? ( + + + + ) : ( + '' + )} + {currUsername === username ? ( + + + + ) : ( + '' + )} +
+
+
{recentComments}
+
+ + +
+ {modal} +
+ ); +}; +export default Project; diff --git a/app/src/Dashboard/ProjectContainer.tsx b/app/src/Dashboard/ProjectContainer.tsx index 931ae2b0b..ef486b2df 100644 --- a/app/src/Dashboard/ProjectContainer.tsx +++ b/app/src/Dashboard/ProjectContainer.tsx @@ -1,190 +1,190 @@ -import React, { useState} from 'react'; -import { ThemeProvider, Theme, StyledEngineProvider, useTheme } from '@mui/material/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import { useQuery } from '@apollo/client'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import Box from '@mui/material/Box'; -import { GET_PROJECTS } from './gqlStrings'; -import Project from './Project'; -import NavBarDash from './NavbarDash'; -import { useSelector } from 'react-redux'; -import { theme1, theme2 } from '../public/styles/theme'; - - -declare module '@mui/styles/defaultTheme' { - interface DefaultTheme extends Theme {} -} - - -// Implement Apollo Client useQuery hook to retrieve data from the server through graphQL. This includes 2 steps: -// 1) Impliment Apollo Provider in the top component in ./src/index.js, this allows children components access to the queried data -// 2) useQuery hook will update the data stored in Apollo Client's cache and automatically trigger child components rendering - - -// setting light and dark themes (navbar and background); linked to theme.ts -const lightTheme = theme1; -const darkTheme = theme2; // dark mode color in theme.ts not reached - -const arrToComponent = arr => - arr.map((proj, index) => ( - - )); - -// Start Pulled from materialUI to create a tab panel -const a11yProps = (index: any) => ({ - id: `vertical-tab-${index}`, - 'aria-controls': `vertical-tabpanel-${index}` -}); - -interface LinkTabProps { - label?: string; - href?: string; -} -const LinkTab = (props: LinkTabProps) => ( - ) => { - event.preventDefault(); - }} - {...props} - /> -); -const TabPanelItem = (props: TabPanelProps): JSX.Element => { - const theme = useTheme(); - const { children, index, value, ...other } = props; - return ( - - ); -}; - -const useStyles = makeStyles(theme => ({ - root: { - flexGrow: 1, - // backgroundColor: theme.palette.background.paper, - display: 'flex' - }, - tabs: { - // borderRight: `1px solid ${theme.palette.divider}` - } -})); -// End of prefab code to generate a tab panel -const ProjectContainer = (): JSX.Element => { - const classes = useStyles(); - const [value, setValue] = useState(0); - const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { - setValue(newValue); - }; - // old code from project container - const myVar = {}; - // Need this for the individual user dasboard, for now, dashboard shows all projects from all users - const userSSID = window.localStorage.getItem('ssid') || 'unavailable'; - const username = window.localStorage.getItem('username') || 'unavailable'; - const [isThemeLight, setTheme] = useState(true); - const style = useSelector(store => store.styleSlice) - // hook for sorting menu - const [selectedOption, setSelectedOption] = useState('RATING'); - const sortByRating = projects => { - // generate a sorted array of public projects based on likes - const sortedRatings = projects.sort((a, b) => b.likes - a.likes); - return sortedRatings; - }; - const sortByDate = projects => { - // generate a sorted array of public projects based on date - const sortedRatings = projects.sort((a, b) => b.createdAt - a.createdAt); - return sortedRatings; - }; - const sortByUser = projects => { - // generate a sorted array of public projects based on username - const sortedRatings = projects.sort((a, b) => b.username - a.username); - return sortedRatings; - }; - // function for selecting drop down sorting menu - const optionClicked = value => { - setSelectedOption(value); - }; - // useQuery hook abstracts fetch request - const { loading, error, data } = useQuery(GET_PROJECTS, { - pollInterval: 2000, - variables: myVar - }); - if (loading) return

Loading...

; - if (error) return

Error :{error}

; - // based on resolver(getAllProject) for this query, the data is stored in the data object with the key 'getAllProjects' - const projects = data.getAllProjects; - - //create array to hold the data recieved in the public dashboard the will be conditionally rendered - let sortedProjects = projects.filter(proj => { - return proj.published; - }); - const userProjects = projects.filter(proj => { - return proj.username === username; - }); - // checking which sorting method was selected from drop down menu and invoking correct sorting function - if (selectedOption === 'DATE') sortedProjects = sortByDate(sortedProjects); - else if (selectedOption === 'USER') - sortedProjects = sortByUser(sortedProjects); - else if (selectedOption === 'RATING') - sortedProjects = sortByRating(sortedProjects); - // create array to hold the components Project of loggin-in users - // generate an array of Project components based on queried data - const userDisplay = arrToComponent(userProjects); - // create an array of components Project that will be conditionally rendered - const sortedDisplay = arrToComponent(sortedProjects); - // old code from Project Container - return ( -
- - -
- -
- - - - - -

Shared Dashboard

-
{sortedDisplay}
-
- -

Private Dashboard

-
{userDisplay}
-
-
-
-
-
-
- ); -}; -export default ProjectContainer; +import React, { useState} from 'react'; +import { ThemeProvider, Theme, StyledEngineProvider, useTheme } from '@mui/material/styles'; +import makeStyles from '@mui/styles/makeStyles'; +import { useQuery } from '@apollo/client'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Box from '@mui/material/Box'; +import { GET_PROJECTS } from './gqlStrings'; +import Project from './Project'; +import NavBarDash from './NavbarDash'; +import { useSelector } from 'react-redux'; +import { theme1, theme2 } from '../public/styles/theme'; + + +declare module '@mui/styles/defaultTheme' { + interface DefaultTheme extends Theme {} +} + + +// Implement Apollo Client useQuery hook to retrieve data from the server through graphQL. This includes 2 steps: +// 1) Impliment Apollo Provider in the top component in ./src/index.js, this allows children components access to the queried data +// 2) useQuery hook will update the data stored in Apollo Client's cache and automatically trigger child components rendering + + +// setting light and dark themes (navbar and background); linked to theme.ts +const lightTheme = theme1; +const darkTheme = theme2; // dark mode color in theme.ts not reached + +const arrToComponent = arr => + arr.map((proj, index) => ( + + )); + +// Start Pulled from materialUI to create a tab panel +const a11yProps = (index: any) => ({ + id: `vertical-tab-${index}`, + 'aria-controls': `vertical-tabpanel-${index}` +}); + +interface LinkTabProps { + label?: string; + href?: string; +} +const LinkTab = (props: LinkTabProps) => ( + ) => { + event.preventDefault(); + }} + {...props} + /> +); +const TabPanelItem = (props: TabPanelProps): JSX.Element => { + const theme = useTheme(); + const { children, index, value, ...other } = props; + return ( + + ); +}; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + // backgroundColor: theme.palette.background.paper, + display: 'flex' + }, + tabs: { + // borderRight: `1px solid ${theme.palette.divider}` + } +})); +// End of prefab code to generate a tab panel +const ProjectContainer = (): JSX.Element => { + const classes = useStyles(); + const [value, setValue] = useState(0); + const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { + setValue(newValue); + }; + // old code from project container + const myVar = {}; + // Need this for the individual user dasboard, for now, dashboard shows all projects from all users + const userSSID = window.localStorage.getItem('ssid') || 'unavailable'; + const username = window.localStorage.getItem('username') || 'unavailable'; + const [isThemeLight, setTheme] = useState(true); + const style = useSelector(store => store.styleSlice) + // hook for sorting menu + const [selectedOption, setSelectedOption] = useState('RATING'); + const sortByRating = projects => { + // generate a sorted array of public projects based on likes + const sortedRatings = projects.sort((a, b) => b.likes - a.likes); + return sortedRatings; + }; + const sortByDate = projects => { + // generate a sorted array of public projects based on date + const sortedRatings = projects.sort((a, b) => b.createdAt - a.createdAt); + return sortedRatings; + }; + const sortByUser = projects => { + // generate a sorted array of public projects based on username + const sortedRatings = projects.sort((a, b) => b.username - a.username); + return sortedRatings; + }; + // function for selecting drop down sorting menu + const optionClicked = value => { + setSelectedOption(value); + }; + // useQuery hook abstracts fetch request + const { loading, error, data } = useQuery(GET_PROJECTS, { + pollInterval: 2000, + variables: myVar + }); + if (loading) return

Loading...

; + if (error) return

Error :{error}

; + // based on resolver(getAllProject) for this query, the data is stored in the data object with the key 'getAllProjects' + const projects = data.getAllProjects; + + //create array to hold the data recieved in the public dashboard the will be conditionally rendered + let sortedProjects = projects.filter(proj => { + return proj.published; + }); + const userProjects = projects.filter(proj => { + return proj.username === username; + }); + // checking which sorting method was selected from drop down menu and invoking correct sorting function + if (selectedOption === 'DATE') sortedProjects = sortByDate(sortedProjects); + else if (selectedOption === 'USER') + sortedProjects = sortByUser(sortedProjects); + else if (selectedOption === 'RATING') + sortedProjects = sortByRating(sortedProjects); + // create array to hold the components Project of loggin-in users + // generate an array of Project components based on queried data + const userDisplay = arrToComponent(userProjects); + // create an array of components Project that will be conditionally rendered + const sortedDisplay = arrToComponent(sortedProjects); + // old code from Project Container + return ( +
+ + +
+ +
+ + + + + +

Shared Dashboard

+
{sortedDisplay}
+
+ +

Private Dashboard

+
{userDisplay}
+
+
+
+
+
+
+ ); +}; +export default ProjectContainer; diff --git a/app/src/Dashboard/gqlStrings.ts b/app/src/Dashboard/gqlStrings.ts index 97ec1a082..680623f5a 100644 --- a/app/src/Dashboard/gqlStrings.ts +++ b/app/src/Dashboard/gqlStrings.ts @@ -1,58 +1,58 @@ -import { gql } from '@apollo/client'; -// Query -export const GET_PROJECTS = gql`query GetAllProjects($userId: ID) { - getAllProjects(userId: $userId) { - name - likes - id - userId - username - published - comments { - username - text - } - } -}`; - -// Mutation -export const ADD_LIKE = gql` - mutation AddLike($projId: ID!, $likes: Int!) { - addLike(projId: $projId, likes: $likes) - { - id - } - }`; - -export const MAKE_COPY = gql` - mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { - makeCopy(userId: $userId, projId: $projId, username: $username) - { - id - } - }`; - -export const DELETE_PROJECT = gql` - mutation DeleteProject($projId: ID!) { - deleteProject(projId: $projId) - { - id - } - }`; - -export const PUBLISH_PROJECT = gql` - mutation Publish($projId: ID!, $published: Boolean!) { - publishProject(projId: $projId, published: $published) - { - id - published - } - }`; - - export const ADD_COMMENT = gql` - mutation AddComment($projId: ID!, $comment: String!, $username: String!) { - addComment(projId: $projId, comment: $comment, username: $username) - { - id - } - }`; +import { gql } from '@apollo/client'; +// Query +export const GET_PROJECTS = gql`query GetAllProjects($userId: ID) { + getAllProjects(userId: $userId) { + name + likes + id + userId + username + published + comments { + username + text + } + } +}`; + +// Mutation +export const ADD_LIKE = gql` + mutation AddLike($projId: ID!, $likes: Int!) { + addLike(projId: $projId, likes: $likes) + { + id + } + }`; + +export const MAKE_COPY = gql` + mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { + makeCopy(userId: $userId, projId: $projId, username: $username) + { + id + } + }`; + +export const DELETE_PROJECT = gql` + mutation DeleteProject($projId: ID!) { + deleteProject(projId: $projId) + { + id + } + }`; + +export const PUBLISH_PROJECT = gql` + mutation Publish($projId: ID!, $published: Boolean!) { + publishProject(projId: $projId, published: $published) + { + id + published + } + }`; + + export const ADD_COMMENT = gql` + mutation AddComment($projId: ID!, $comment: String!, $username: String!) { + addComment(projId: $projId, comment: $comment, username: $username) + { + id + } + }`; diff --git a/app/src/Dashboard/styles.css b/app/src/Dashboard/styles.css index 89500f729..95e45bd0a 100644 --- a/app/src/Dashboard/styles.css +++ b/app/src/Dashboard/styles.css @@ -1,91 +1,91 @@ -.project { - display: flex; - flex-direction: column; - align-items: stretch; - margin: 5px; - border: 1px solid #f0f0f0; - border-radius: 5px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); - height: 500px; - width: 400px; - justify-content: space-between; -} - -.dashboardContainer { - height: 100%; - width: 100%; -} - -.userDashboard { - display: flex; - flex-direction: row; -} - -.projectPanel { - width: 100%; -} - -.projectContainer { - display: flex; - flex-direction: column-reverse; - flex-flow: row wrap; - flex-grow: 1; - overflow-y: scroll; -} - -.projectInfo { - text-align: center; -} - -.commentContainer { - text-align: center; - height: 400px; - overflow-y: scroll; -} - -.commentBtn { - color: #bebebed8; -} - -.comment { - font-size: 125%; -} - -.renderedCom { - text-align: center; -} - -.commentInput { - display: flex; - height: 100px; - width: 100%; - border: 1px solid #f0f0f0; - position: relative; -} - -.commentInput > * { - flex: auto; -} - -.icons { - width: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.header { - background-color: #0671e3; - color: rgba(255, 255, 255, 0.897); - width: 100%; - position: relative; -} - -.commentField { - border: 1px solid #f0f0f0; - padding-left: 2%; -} - -h1 { - text-align: center; -} +.project { + display: flex; + flex-direction: column; + align-items: stretch; + margin: 5px; + border: 1px solid #f0f0f0; + border-radius: 5px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + height: 500px; + width: 400px; + justify-content: space-between; +} + +.dashboardContainer { + height: 100%; + width: 100%; +} + +.userDashboard { + display: flex; + flex-direction: row; +} + +.projectPanel { + width: 100%; +} + +.projectContainer { + display: flex; + flex-direction: column-reverse; + flex-flow: row wrap; + flex-grow: 1; + overflow-y: scroll; +} + +.projectInfo { + text-align: center; +} + +.commentContainer { + text-align: center; + height: 400px; + overflow-y: scroll; +} + +.commentBtn { + color: #bebebed8; +} + +.comment { + font-size: 125%; +} + +.renderedCom { + text-align: center; +} + +.commentInput { + display: flex; + height: 100px; + width: 100%; + border: 1px solid #f0f0f0; + position: relative; +} + +.commentInput > * { + flex: auto; +} + +.icons { + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.header { + background-color: #0671e3; + color: rgba(255, 255, 255, 0.897); + width: 100%; + position: relative; +} + +.commentField { + border: 1px solid #f0f0f0; + padding-left: 2%; +} + +h1 { + text-align: center; +} diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index f97e98a27..d4b65db9e 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -1,24 +1,24 @@ -import '../public/styles/style.css'; - -import React, { useEffect } from 'react'; -import { toggleLoggedIn } from '../redux/reducers/slice/appStateSlice'; -import { useDispatch } from 'react-redux'; - -import AppContainer from '../containers/AppContainer'; - -export const App: React.FC = (): JSX.Element => { - const dispatch = useDispatch(); - useEffect(() => { - if (window.localStorage.getItem('ssid') !== 'guest') { - dispatch(toggleLoggedIn(true)); - } - }, []); - - return ( -
- -
- ); -}; - -export default App; +import '../public/styles/style.css'; + +import React, { useEffect } from 'react'; +import { toggleLoggedIn } from '../redux/reducers/slice/appStateSlice'; +import { useDispatch } from 'react-redux'; + +import AppContainer from '../containers/AppContainer'; + +export const App: React.FC = (): JSX.Element => { + const dispatch = useDispatch(); + useEffect(() => { + if (window.localStorage.getItem('ssid') !== 'guest') { + dispatch(toggleLoggedIn(true)); + } + }, []); + + return ( +
+ +
+ ); +}; + +export default App; diff --git a/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx index ff1d5e4f4..8d8f5804a 100644 --- a/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx @@ -1,140 +1,140 @@ -import React, { useState, Fragment } from 'react'; -import DataTable from '../CreateTab/components/DataTable'; -import ContextDropDown from './components/ContextDropDown'; -import ComponentDropDown from './components/ComponentDropDrown'; -import Divider from '@mui/material/Divider'; -import Grid from '@mui/material/Grid'; -import ComponentTable from './components/ComponentTable'; -import { Button } from '@mui/material'; -import DoubleArrowIcon from '@mui/icons-material/DoubleArrow'; -import { addComponentToContext } from '../../../redux/reducers/slice/contextReducer'; -import { useSelector, useDispatch } from 'react-redux'; -import { deleteElement } from '../../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../../redux/store'; -import { emitEvent } from '../../../../src/helperFunctions/socket'; - -const AssignContainer = () => { - const dispatch = useDispatch(); - const defaultTableData = [{ key: 'Key', value: 'Value' }]; - const [tableState, setTableState] = React.useState(defaultTableData); - const [contextInput, setContextInput] = React.useState(null); - const [componentInput, setComponentInput] = React.useState(null); - const [componentTable, setComponentTable] = useState([]); - const { state, contextParam } = useSelector((store: RootState) => ({ - state: store.appState, - contextParam: store.contextSlice - })); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - //sets table data if it exists - const renderTable = (targetContext) => { - targetContext?.values && setTableState(targetContext.values); - }; - - //construct data for table displaying component table - const renderComponentTable = (targetComponent) => { - //target Component is main - const listOfContexts = []; - if (!Array.isArray(state) && targetComponent?.name) { - contextParam.allContext.forEach((context) => { - if (context.components.includes(targetComponent.name)) { - listOfContexts.push(context.name); - } - }); - setComponentTable(listOfContexts); - } - }; - - //handling assignment of contexts to components - const handleAssignment = () => { - if ( - contextInput === '' || - contextInput === null || - componentInput === '' || - componentInput === null - ) - return; - - dispatch( - addComponentToContext({ - context: contextInput, - component: componentInput - }) - ); - //trigger generateCode(), update code preview tab - dispatch(deleteElement({ id: 'FAKE_ID', contextParam: contextParam })); - - if (roomCode) { - emitEvent('assignContextActions', roomCode, { - context: contextInput, - component: componentInput, - id: 'FAKE_ID', - contextParam: contextParam - }); - } - - renderComponentTable(componentInput); - }; - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default AssignContainer; +import React, { useState, Fragment } from 'react'; +import DataTable from '../CreateTab/components/DataTable'; +import ContextDropDown from './components/ContextDropDown'; +import ComponentDropDown from './components/ComponentDropDrown'; +import Divider from '@mui/material/Divider'; +import Grid from '@mui/material/Grid'; +import ComponentTable from './components/ComponentTable'; +import { Button } from '@mui/material'; +import DoubleArrowIcon from '@mui/icons-material/DoubleArrow'; +import { addComponentToContext } from '../../../redux/reducers/slice/contextReducer'; +import { useSelector, useDispatch } from 'react-redux'; +import { deleteElement } from '../../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../../redux/store'; +import { emitEvent } from '../../../../src/helperFunctions/socket'; + +const AssignContainer = () => { + const dispatch = useDispatch(); + const defaultTableData = [{ key: 'Key', value: 'Value' }]; + const [tableState, setTableState] = React.useState(defaultTableData); + const [contextInput, setContextInput] = React.useState(null); + const [componentInput, setComponentInput] = React.useState(null); + const [componentTable, setComponentTable] = useState([]); + const { state, contextParam } = useSelector((store: RootState) => ({ + state: store.appState, + contextParam: store.contextSlice + })); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + //sets table data if it exists + const renderTable = (targetContext) => { + targetContext?.values && setTableState(targetContext.values); + }; + + //construct data for table displaying component table + const renderComponentTable = (targetComponent) => { + //target Component is main + const listOfContexts = []; + if (!Array.isArray(state) && targetComponent?.name) { + contextParam.allContext.forEach((context) => { + if (context.components.includes(targetComponent.name)) { + listOfContexts.push(context.name); + } + }); + setComponentTable(listOfContexts); + } + }; + + //handling assignment of contexts to components + const handleAssignment = () => { + if ( + contextInput === '' || + contextInput === null || + componentInput === '' || + componentInput === null + ) + return; + + dispatch( + addComponentToContext({ + context: contextInput, + component: componentInput + }) + ); + //trigger generateCode(), update code preview tab + dispatch(deleteElement({ id: 'FAKE_ID', contextParam: contextParam })); + + if (roomCode) { + emitEvent('assignContextActions', roomCode, { + context: contextInput, + component: componentInput, + id: 'FAKE_ID', + contextParam: contextParam + }); + } + + renderComponentTable(componentInput); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default AssignContainer; diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx index b06802471..32f4f8f22 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx @@ -1,104 +1,104 @@ -import React, { Fragment } from 'react'; -import TextField from '@mui/material/TextField'; -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; -import Box from '@mui/material/Box'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../../redux/store'; - -const filter = createFilterOptions(); - -const ComponentDropDown = ({ - renderComponentTable, - componentInput, - setComponentInput -}) => { - const { state } = useSelector((store: RootState) => ({ - state: store.appState - })); - - const onChange = (event, newValue) => { - if (typeof newValue === 'string') { - setComponentInput({ - name: newValue - }); - } else if (newValue && newValue.inputValue) { - // Create a new contextInput from the user input - setComponentInput({ - name: newValue.inputValue, - values: [] - }); - renderComponentTable(newValue); - } else { - setComponentInput(newValue); - renderComponentTable(newValue); - } - }; - - const filterOptions = (options, params) => { - const filtered = filter(options, params); - const { inputValue } = params; - // Suggest the creation of a new contextInput - const isExisting = options.some((option) => inputValue === option.name); - if (inputValue !== '' && !isExisting) { - filtered.push({ - inputValue, - name: `Add "${inputValue}"` - }); - } - - return filtered; - }; - - const getOptionLabel = (option) => { - // Value selected with enter, right from the input - if (typeof option === 'string') { - return option; - } - // Add "xxx" option created dynamically - if (option.inputValue) { - return option.inputValue; - } - // Regular option - return option.name; - }; - - const renderOption = (props, option) => ( -
  • - {option.name} -
  • - ); - - return ( - - - ( - - )} - /> - - - ); -}; - -export default ComponentDropDown; +import React, { Fragment } from 'react'; +import TextField from '@mui/material/TextField'; +import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; +import Box from '@mui/material/Box'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../../redux/store'; + +const filter = createFilterOptions(); + +const ComponentDropDown = ({ + renderComponentTable, + componentInput, + setComponentInput +}) => { + const { state } = useSelector((store: RootState) => ({ + state: store.appState + })); + + const onChange = (event, newValue) => { + if (typeof newValue === 'string') { + setComponentInput({ + name: newValue + }); + } else if (newValue && newValue.inputValue) { + // Create a new contextInput from the user input + setComponentInput({ + name: newValue.inputValue, + values: [] + }); + renderComponentTable(newValue); + } else { + setComponentInput(newValue); + renderComponentTable(newValue); + } + }; + + const filterOptions = (options, params) => { + const filtered = filter(options, params); + const { inputValue } = params; + // Suggest the creation of a new contextInput + const isExisting = options.some((option) => inputValue === option.name); + if (inputValue !== '' && !isExisting) { + filtered.push({ + inputValue, + name: `Add "${inputValue}"` + }); + } + + return filtered; + }; + + const getOptionLabel = (option) => { + // Value selected with enter, right from the input + if (typeof option === 'string') { + return option; + } + // Add "xxx" option created dynamically + if (option.inputValue) { + return option.inputValue; + } + // Regular option + return option.name; + }; + + const renderOption = (props, option) => ( +
  • + {option.name} +
  • + ); + + return ( + + + ( + + )} + /> + + + ); +}; + +export default ComponentDropDown; diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx index 76397b480..426780e61 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx @@ -1,52 +1,52 @@ -import React from 'react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; -import { styled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: theme.palette.common.white - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14 - } -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.action.hover - }, - // hide last border - '&:last-child td, &:last-child th': { - border: 0 - } -})); - -export default function DataTable({ target }) { - return ( - - - - - Contexts Consumed - - - - {target.map((data, index) => ( - - - {data} - - - ))} - -
    -
    - ); +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { styled } from '@mui/material/styles'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14 + } +})); + +const StyledTableRow = styled(TableRow)(({ theme }) => ({ + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover + }, + // hide last border + '&:last-child td, &:last-child th': { + border: 0 + } +})); + +export default function DataTable({ target }) { + return ( + + + + + Contexts Consumed + + + + {target.map((data, index) => ( + + + {data} + + + ))} + +
    +
    + ); } \ No newline at end of file diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx index f1591ebe4..6ba077992 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx @@ -1,102 +1,102 @@ -import React, { Fragment, useState, useEffect } from 'react'; -import TextField from '@mui/material/TextField'; -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; -import Box from '@mui/material/Box'; - - -const filter = createFilterOptions(); - -const ContextDropDown = ({ - contextStore, - renderTable, - contextInput, - setContextInput -}) => { - const { allContext } = contextStore; - - const onChange = (event, newValue) => { - if (typeof newValue === 'string') { - setContextInput({ - name: newValue - }); - } else if (newValue && newValue.inputValue) { - // Create a new contextInput from the user input - setContextInput({ - name: newValue.inputValue, - values: [] - }); - renderTable(newValue); - } else { - setContextInput(newValue); - renderTable(newValue); - } - }; - - const filterOptions = (options, params) => { - const filtered = filter(options, params); - const { inputValue } = params; - // Suggest the creation of a new contextInput - const isExisting = options.some((option) => inputValue === option.name); - if (inputValue !== '' && !isExisting) { - filtered.push({ - inputValue, - name: `Add "${inputValue}"` - }); - } - - return filtered; - }; - - const getOptionLabel = (option) => { - // Value selected with enter, right from the input - if (typeof option === 'string') { - return option; - } - // Add "xxx" option created dynamically - if (option.inputValue) { - return option.inputValue; - } - // Regular option - return option.name; - }; - - const renderOption = (props, option) => ( -
  • - {option.name} -
  • - ); - - return ( - - - ( - - )} - /> - - - ); -}; - -export default ContextDropDown; +import React, { Fragment, useState, useEffect } from 'react'; +import TextField from '@mui/material/TextField'; +import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; +import Box from '@mui/material/Box'; + + +const filter = createFilterOptions(); + +const ContextDropDown = ({ + contextStore, + renderTable, + contextInput, + setContextInput +}) => { + const { allContext } = contextStore; + + const onChange = (event, newValue) => { + if (typeof newValue === 'string') { + setContextInput({ + name: newValue + }); + } else if (newValue && newValue.inputValue) { + // Create a new contextInput from the user input + setContextInput({ + name: newValue.inputValue, + values: [] + }); + renderTable(newValue); + } else { + setContextInput(newValue); + renderTable(newValue); + } + }; + + const filterOptions = (options, params) => { + const filtered = filter(options, params); + const { inputValue } = params; + // Suggest the creation of a new contextInput + const isExisting = options.some((option) => inputValue === option.name); + if (inputValue !== '' && !isExisting) { + filtered.push({ + inputValue, + name: `Add "${inputValue}"` + }); + } + + return filtered; + }; + + const getOptionLabel = (option) => { + // Value selected with enter, right from the input + if (typeof option === 'string') { + return option; + } + // Add "xxx" option created dynamically + if (option.inputValue) { + return option.inputValue; + } + // Regular option + return option.name; + }; + + const renderOption = (props, option) => ( +
  • + {option.name} +
  • + ); + + return ( + + + ( + + )} + /> + + + ); +}; + +export default ContextDropDown; diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx index 5906b3093..5296d6b9d 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx @@ -1,72 +1,72 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: theme.palette.common.white - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14 - } -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.action.hover - }, - // hide last border - '&:last-child td, &:last-child th': { - border: 0 - } -})); - -function createData( - name: string, - calories: number, - fat: number, - carbs: number, - protein: number -) { - return { name, calories, fat, carbs, protein }; -} - -const rows = [ - createData('Frozen yoghurt', 159, 6.0, 24, 4.0), - createData('Ice cream sandwich', 237, 9.0, 37, 4.3), - createData('Eclair', 262, 16.0, 24, 6.0), - createData('Cupcake', 305, 3.7, 67, 4.3), - createData('Gingerbread', 356, 16.0, 49, 3.9) -]; - -export default function ContextTable() { - return ( - - - - - Context - Component - - - - {rows.map((row) => ( - - - {row.name} - - {row.calories} - - ))} - -
    -
    - ); -} +import * as React from 'react'; +import { styled } from '@mui/material/styles'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14 + } +})); + +const StyledTableRow = styled(TableRow)(({ theme }) => ({ + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover + }, + // hide last border + '&:last-child td, &:last-child th': { + border: 0 + } +})); + +function createData( + name: string, + calories: number, + fat: number, + carbs: number, + protein: number +) { + return { name, calories, fat, carbs, protein }; +} + +const rows = [ + createData('Frozen yoghurt', 159, 6.0, 24, 4.0), + createData('Ice cream sandwich', 237, 9.0, 37, 4.3), + createData('Eclair', 262, 16.0, 24, 6.0), + createData('Cupcake', 305, 3.7, 67, 4.3), + createData('Gingerbread', 356, 16.0, 49, 3.9) +]; + +export default function ContextTable() { + return ( + + + + + Context + Component + + + + {rows.map((row) => ( + + + {row.name} + + {row.calories} + + ))} + +
    +
    + ); +} diff --git a/app/src/components/ContextAPIManager/ContextManager.tsx b/app/src/components/ContextAPIManager/ContextManager.tsx index 8927f9dc0..9f3bf7c0b 100644 --- a/app/src/components/ContextAPIManager/ContextManager.tsx +++ b/app/src/components/ContextAPIManager/ContextManager.tsx @@ -1,64 +1,64 @@ -import React, { useContext } from 'react'; -import { makeStyles } from '@mui/styles'; -import Box from '@mui/material/Box'; -import Tab from '@mui/material/Tab'; -import TabContext from '@mui/lab/TabContext'; -import TabList from '@mui/lab/TabList'; -import TabPanel from '@mui/lab/TabPanel'; - -import CreateContainer from './CreateTab/CreateContainer'; -import AssignContainer from './AssignTab/AssignContainer'; -import DisplayContainer from './DisplayTab/DisplayContainer'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; - -const useStyles = makeStyles({ - contextContainer: { - height: 'fit-content' - } -}); - -const ContextManager = (props): JSX.Element => { - const style = useSelector((store: RootState) => store.styleSlice); - const classes = useStyles(); - const [value, setValue] = React.useState('1'); - - const handleChange = (event: React.SyntheticEvent, newValue: string) => { - setValue(newValue); - }; - - const color = 'white'; - - return ( - -
    - - - - - - - - - - - - - - - - - - - - -
    -
    - ); -}; - -export default ContextManager; +import React, { useContext } from 'react'; +import { makeStyles } from '@mui/styles'; +import Box from '@mui/material/Box'; +import Tab from '@mui/material/Tab'; +import TabContext from '@mui/lab/TabContext'; +import TabList from '@mui/lab/TabList'; +import TabPanel from '@mui/lab/TabPanel'; + +import CreateContainer from './CreateTab/CreateContainer'; +import AssignContainer from './AssignTab/AssignContainer'; +import DisplayContainer from './DisplayTab/DisplayContainer'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; + +const useStyles = makeStyles({ + contextContainer: { + height: 'fit-content' + } +}); + +const ContextManager = (props): JSX.Element => { + const style = useSelector((store: RootState) => store.styleSlice); + const classes = useStyles(); + const [value, setValue] = React.useState('1'); + + const handleChange = (event: React.SyntheticEvent, newValue: string) => { + setValue(newValue); + }; + + const color = 'white'; + + return ( + +
    + + + + + + + + + + + + + + + + + + + + +
    +
    + ); +}; + +export default ContextManager; diff --git a/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx index 2bc87a598..e013cb919 100644 --- a/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx @@ -1,161 +1,161 @@ -import React from 'react'; -import Divider from '@mui/material/Divider'; -import Grid from '@mui/material/Grid'; -import DataTable from './components/DataTable'; -import AddDataForm from './components/AddDataForm'; -import AddContextForm from './components/AddContextForm'; -import { Typography } from '@mui/material'; -import { - addContext, - deleteContext, - addContextValues -} from '../../../redux/reducers/slice/contextReducer'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '../../../redux/store'; -import { emitEvent } from '../../../../src/helperFunctions/socket'; - -const CreateContainer = () => { - const state = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const [contextInput, setContextInput] = React.useState(''); - const [currentContext, setCurrentContext] = React.useState(''); - const [errorMsg, setErrorMsg] = React.useState(''); - const [errorStatus, setErrorStatus] = React.useState(false); - const currentKeyValues = state.allContext.find( - (obj) => obj.name === currentContext - )?.values || [{ key: 'Enter Key', value: 'Enter value' }]; - const dispatch = useDispatch(); - - //update data store when user adds a new context - const handleClickSelectContext = () => { - let letters = /[a-zA-Z]/; - let error; - //checking for input error / setting error type - if (!contextInput || contextInput.trim() === '') { - error = 'empty'; - } else if (!contextInput.charAt(0).match(letters)) { - error = 'letters'; - } else if (!contextInput.match(/^[0-9a-zA-Z]+$/)) { - error = 'symbolsDetected'; - } else if ( - state.allContext.some( - (context) => context.name.toLowerCase() === contextInput.toLowerCase() - ) - ) { - error = 'dupe'; - } - - if (error !== undefined) { - triggerError(error); - return; - } - - dispatch(addContext({ name: contextInput })); - - if (roomCode) { - emitEvent('addContextAction', roomCode, { name: contextInput }); - } - - setContextInput(''); - }; - - const triggerError = (type: String) => { - setErrorStatus(true); - switch (type) { - case 'empty': - setErrorMsg('Context name cannot be blank.'); - break; - case 'dupe': - setErrorMsg('Context name already exists.'); - break; - case 'letters': - setErrorMsg('Context name must start with a letter.'); - break; - case 'symbolsDetected': - setErrorMsg('Context name must not contain symbols.'); - break; - } - }; - - //update data store when user add new key-value pair to context - const handleClickInputData = (name, { inputKey, inputValue }) => { - dispatch(addContextValues({ name, inputKey, inputValue })); - - if (roomCode) { - emitEvent('addContextValuesAction', roomCode, { - name, - inputKey, - inputValue - }); - } - }; - - //update data store when user deletes context - const handleDeleteContextClick = () => { - dispatch(deleteContext({ name: currentContext })); - - if (roomCode) { - emitEvent('deleteContextAction', roomCode, { name: currentContext }); - } - - setContextInput(''); - setCurrentContext(''); - }; - - return ( - <> - - - - - - - - - - - - - - - - - Context Data Table - - - - - - ); -}; - -export default CreateContainer; +import React from 'react'; +import Divider from '@mui/material/Divider'; +import Grid from '@mui/material/Grid'; +import DataTable from './components/DataTable'; +import AddDataForm from './components/AddDataForm'; +import AddContextForm from './components/AddContextForm'; +import { Typography } from '@mui/material'; +import { + addContext, + deleteContext, + addContextValues +} from '../../../redux/reducers/slice/contextReducer'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '../../../redux/store'; +import { emitEvent } from '../../../../src/helperFunctions/socket'; + +const CreateContainer = () => { + const state = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const [contextInput, setContextInput] = React.useState(''); + const [currentContext, setCurrentContext] = React.useState(''); + const [errorMsg, setErrorMsg] = React.useState(''); + const [errorStatus, setErrorStatus] = React.useState(false); + const currentKeyValues = state.allContext.find( + (obj) => obj.name === currentContext + )?.values || [{ key: 'Enter Key', value: 'Enter value' }]; + const dispatch = useDispatch(); + + //update data store when user adds a new context + const handleClickSelectContext = () => { + let letters = /[a-zA-Z]/; + let error; + //checking for input error / setting error type + if (!contextInput || contextInput.trim() === '') { + error = 'empty'; + } else if (!contextInput.charAt(0).match(letters)) { + error = 'letters'; + } else if (!contextInput.match(/^[0-9a-zA-Z]+$/)) { + error = 'symbolsDetected'; + } else if ( + state.allContext.some( + (context) => context.name.toLowerCase() === contextInput.toLowerCase() + ) + ) { + error = 'dupe'; + } + + if (error !== undefined) { + triggerError(error); + return; + } + + dispatch(addContext({ name: contextInput })); + + if (roomCode) { + emitEvent('addContextAction', roomCode, { name: contextInput }); + } + + setContextInput(''); + }; + + const triggerError = (type: String) => { + setErrorStatus(true); + switch (type) { + case 'empty': + setErrorMsg('Context name cannot be blank.'); + break; + case 'dupe': + setErrorMsg('Context name already exists.'); + break; + case 'letters': + setErrorMsg('Context name must start with a letter.'); + break; + case 'symbolsDetected': + setErrorMsg('Context name must not contain symbols.'); + break; + } + }; + + //update data store when user add new key-value pair to context + const handleClickInputData = (name, { inputKey, inputValue }) => { + dispatch(addContextValues({ name, inputKey, inputValue })); + + if (roomCode) { + emitEvent('addContextValuesAction', roomCode, { + name, + inputKey, + inputValue + }); + } + }; + + //update data store when user deletes context + const handleDeleteContextClick = () => { + dispatch(deleteContext({ name: currentContext })); + + if (roomCode) { + emitEvent('deleteContextAction', roomCode, { name: currentContext }); + } + + setContextInput(''); + setCurrentContext(''); + }; + + return ( + <> + + + + + + + + + + + + + + + + + Context Data Table + + + + + + ); +}; + +export default CreateContainer; diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx index beae68878..2cfb0a1f7 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx @@ -1,163 +1,163 @@ -import React, { Fragment, useState } from 'react'; -import TextField from '@mui/material/TextField'; -import Select from '@mui/material/Select'; -import Snackbar from '@mui/material/Snackbar'; -import Button from '@mui/material/Button'; -import Box from '@mui/material/Box'; -import FormControl from '@mui/material/FormControl'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import { InputLabel, MenuItem, Typography } from '@mui/material'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../../redux/store'; - -const AddContextForm = ({ - contextStore, - handleClickSelectContext, - handleDeleteContextClick, - contextInput, - setContextInput, - currentContext, - setCurrentContext, - errorMsg, - errorStatus, - setErrorStatus -}) => { - const { allContext } = contextStore; - const [btnDisabled, setBtnDisabled] = useState(false); - const [open, setOpen] = useState(false); - - const color = 'white'; - - //handler for submitting new context for creation - const handleSubmit = () => { - handleClickSelectContext(); - setOpen(true); - }; - - //form control for new context field - const handleChange = (e) => { - setErrorStatus(false); - setOpen(false); - setContextInput(e.target.value); - }; - - //event handle for confirmation modal - const handleClose = ( - event: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === 'clickaway') { - return; - } - - setOpen(false); - }; - - const Alert = React.forwardRef(function Alert( - props, - ref - ) { - return ; - }); - - //creating options for context dropdown - const contexts = allContext.length ? ( - allContext.map((context, index) => { - return ( - - {context.name} - - ); - }) - ) : ( - No Contexts Created - ); - - return ( - - - Create Context - - - - - - Context Created - - - - - - Select Context - - - - select context - - - - - - ); -}; - -export default AddContextForm; +import React, { Fragment, useState } from 'react'; +import TextField from '@mui/material/TextField'; +import Select from '@mui/material/Select'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import FormControl from '@mui/material/FormControl'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import { InputLabel, MenuItem, Typography } from '@mui/material'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../../redux/store'; + +const AddContextForm = ({ + contextStore, + handleClickSelectContext, + handleDeleteContextClick, + contextInput, + setContextInput, + currentContext, + setCurrentContext, + errorMsg, + errorStatus, + setErrorStatus +}) => { + const { allContext } = contextStore; + const [btnDisabled, setBtnDisabled] = useState(false); + const [open, setOpen] = useState(false); + + const color = 'white'; + + //handler for submitting new context for creation + const handleSubmit = () => { + handleClickSelectContext(); + setOpen(true); + }; + + //form control for new context field + const handleChange = (e) => { + setErrorStatus(false); + setOpen(false); + setContextInput(e.target.value); + }; + + //event handle for confirmation modal + const handleClose = ( + event: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const Alert = React.forwardRef(function Alert( + props, + ref + ) { + return ; + }); + + //creating options for context dropdown + const contexts = allContext.length ? ( + allContext.map((context, index) => { + return ( + + {context.name} + + ); + }) + ) : ( + No Contexts Created + ); + + return ( + + + Create Context + + + + + + Context Created + + + + + + Select Context + + + + select context + + + + + + ); +}; + +export default AddContextForm; diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx index de38a087f..a49dd8b35 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx @@ -1,76 +1,76 @@ -import React from 'react'; -import TextField from '@mui/material/TextField'; -import Button from '@mui/material/Button'; -import Box from '@mui/material/Box'; -import { Typography } from '@mui/material'; -// import { useSelector } from 'react-redux'; -// import { RootState } from '../../../../redux/store'; - -const AddDataForm = ({ handleClickInputData, currentContext }) => { - //const [contextInput, setContextInput] = React.useState(null); - const defaultInputData = { inputKey: '', inputValue: '' }; - const [dataContext, setDataContext] = React.useState(defaultInputData); - - const saveData = () => { - setDataContext(defaultInputData); - if (dataContext.inputKey === '' || dataContext.inputValue === '') { - window.alert('empty key or value'); - return; - } - handleClickInputData(currentContext, dataContext); - }; - const color = 'white'; - - const handleChange = (e) => { - setDataContext((prevDataContext) => { - return { - ...prevDataContext, - [e.target.name]: e.target.value - }; - }); - }; - - return ( - <> - - Add Context Data - - - handleChange(e)} - InputProps={{ style: { color: color } }} - style={{ border: '1px solid black', width: '205px' }} - /> - handleChange(e)} - style={{ border: '1px solid black', width: '205px' }} - InputProps={{ style: { color: color } }} - /> - - - - ); -}; - -export default AddDataForm; +import React from 'react'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import { Typography } from '@mui/material'; +// import { useSelector } from 'react-redux'; +// import { RootState } from '../../../../redux/store'; + +const AddDataForm = ({ handleClickInputData, currentContext }) => { + //const [contextInput, setContextInput] = React.useState(null); + const defaultInputData = { inputKey: '', inputValue: '' }; + const [dataContext, setDataContext] = React.useState(defaultInputData); + + const saveData = () => { + setDataContext(defaultInputData); + if (dataContext.inputKey === '' || dataContext.inputValue === '') { + window.alert('empty key or value'); + return; + } + handleClickInputData(currentContext, dataContext); + }; + const color = 'white'; + + const handleChange = (e) => { + setDataContext((prevDataContext) => { + return { + ...prevDataContext, + [e.target.name]: e.target.value + }; + }); + }; + + return ( + <> + + Add Context Data + + + handleChange(e)} + InputProps={{ style: { color: color } }} + style={{ border: '1px solid black', width: '205px' }} + /> + handleChange(e)} + style={{ border: '1px solid black', width: '205px' }} + InputProps={{ style: { color: color } }} + /> + + + + ); +}; + +export default AddDataForm; diff --git a/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx index 2f9bd24d8..838c24ece 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx @@ -1,68 +1,68 @@ -import React from 'react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; -import { styled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: 'theme.palette.common.white' - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14 - } -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.action.hover - }, - // hide last border - '&:last-child td, &:last-child th': { - border: 0 - } -})); - -export default function DataTable({ target, currentContext }) { - return ( - <> - - - - - - {currentContext ? currentContext : 'Context Name'} - - - - - {target.map((data, index) => ( - - - {data.key} - - - {data.value} - - - ))} - -
    -
    - - ); -} +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { styled } from '@mui/material/styles'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: 'theme.palette.common.white' + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14 + } +})); + +const StyledTableRow = styled(TableRow)(({ theme }) => ({ + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover + }, + // hide last border + '&:last-child td, &:last-child th': { + border: 0 + } +})); + +export default function DataTable({ target, currentContext }) { + return ( + <> + + + + + + {currentContext ? currentContext : 'Context Name'} + + + + + {target.map((data, index) => ( + + + {data.key} + + + {data.value} + + + ))} + +
    +
    + + ); +} diff --git a/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx index 3d5b59b19..7f115a3b1 100644 --- a/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx +++ b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx @@ -1,53 +1,53 @@ -import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { Chart } from 'react-google-charts'; -import Grid from '@mui/material/Grid'; -import { RootState } from '../../../redux/store'; - -const DisplayContainer = () => { - const allContext = useSelector( - (store: RootState) => store.contextSlice.allContext - ); - const [contextData, setContextData] = useState([]); - - useEffect(() => { - transformData(); - }, []); - - //formats context data for use in react google charts - const transformData = () => { - const formattedData = allContext - .map((obj) => { - return obj.components.map((component) => { - return [`App ⎯⎯ ${obj.name} ⎯⎯ ${component}`]; - }); - }) - .flat(); - setContextData([['Phrases'], ...formattedData]); - }; - - //format options for google chart - const options = { - wordtree: { - format: 'implicit', - word: 'App' - }, - backgroundColor: '#1E2024' - }; - - return ( - - {contextData.length < 2 &&

    No Contexts Consumed

    } - - - -
    - ); -}; -export default DisplayContainer; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { Chart } from 'react-google-charts'; +import Grid from '@mui/material/Grid'; +import { RootState } from '../../../redux/store'; + +const DisplayContainer = () => { + const allContext = useSelector( + (store: RootState) => store.contextSlice.allContext + ); + const [contextData, setContextData] = useState([]); + + useEffect(() => { + transformData(); + }, []); + + //formats context data for use in react google charts + const transformData = () => { + const formattedData = allContext + .map((obj) => { + return obj.components.map((component) => { + return [`App ⎯⎯ ${obj.name} ⎯⎯ ${component}`]; + }); + }) + .flat(); + setContextData([['Phrases'], ...formattedData]); + }; + + //format options for google chart + const options = { + wordtree: { + format: 'implicit', + word: 'App' + }, + backgroundColor: '#1E2024' + }; + + return ( + + {contextData.length < 2 &&

    No Contexts Consumed

    } + + + +
    + ); +}; +export default DisplayContainer; diff --git a/app/src/components/StateManagement/CreateTab/CreateContainer.tsx b/app/src/components/StateManagement/CreateTab/CreateContainer.tsx index cdd6b324f..08da0456c 100644 --- a/app/src/components/StateManagement/CreateTab/CreateContainer.tsx +++ b/app/src/components/StateManagement/CreateTab/CreateContainer.tsx @@ -1,14 +1,14 @@ -import React from 'react'; -import Grid from '@mui/material/Grid'; -import StatePropsPanel from './components/StatePropsPanel'; - -const CreateContainer = ({isThemeLight, data}) => { - - return ( - - - - ); -}; - -export default CreateContainer; +import React from 'react'; +import Grid from '@mui/material/Grid'; +import StatePropsPanel from './components/StatePropsPanel'; + +const CreateContainer = ({isThemeLight, data}) => { + + return ( + + + + ); +}; + +export default CreateContainer; diff --git a/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx b/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx index f43c57d31..7f455e05e 100644 --- a/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx +++ b/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx @@ -1,621 +1,621 @@ -import React, { useState, useEffect } from 'react'; -import { Theme } from '@mui/material/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import { useDispatch, useSelector } from 'react-redux'; -import { addState } from '../../../../redux/reducers/slice/appStateSlice'; -import { - FormControl, - FormHelperText, - MenuItem, - InputLabel, - Select, - TextField, - Button -} from '@mui/material'; -import TableStateProps from './TableStateProps'; -import TableParentProps from './TableParentProps'; -import TablePassedInProps from './TablePassedInProps'; -import { RootState } from '../../../../redux/store'; -import { emitEvent } from '../../../../helperFunctions/socket'; - -const StatePropsPanel = ({ isThemeLight, data }): JSX.Element => { - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - const classes = useStyles(); - const [inputKey, setInputKey] = useState(''); - const [inputValue, setInputValue] = useState(''); - const [inputType, setInputType] = useState(''); - const [errorStatus, setErrorStatus] = useState(false); - const [inputTypeError, setInputTypeError] = useState(''); - const [newVal, setNewVal] = useState('test'); - const [errorMsg, setErrorMsg] = useState(''); - const currentId = state.canvasFocus.componentId; - const currentComponent = state.components[currentId - 1]; - const [parentProps, setParentProps] = useState([]); - const [parentPassedInProps, setParentPassedInProps] = useState([]); - const [parentName, setParentName] = useState('No Parents'); - const [parentComponent, setParentComponent] = useState({}); - const [rows1, setRows1] = useState(currentComponent.stateProps); - const [propNum, setPropNum] = useState(1); - - // convert value to correct type based on user input - const typeConversion = (value: string, type: string) => { - switch (type) { - case 'string': { - setInputTypeError(''); - return String(value); - } - case 'number': { - setInputTypeError(''); - return Number(value); - } - case 'boolean': { - setInputTypeError(''); - return value === 'true'; - } - case 'array': - try { - let retVal = JSON.parse(value); - if (Array.isArray(retVal)) { - setInputTypeError(''); - return retVal; - } else { - throw new Error('Input was not an array!'); - } - } catch { - setInputTypeError(type); - return null; - } - case 'object': { - try { - let retVal = JSON.parse(value); - - if (typeof retVal === 'object' && !Array.isArray(retVal)) { - setInputTypeError(''); - return retVal; - } else { - throw new Error('Input was not an object (excluding Arrays)!'); - } - } catch { - setInputTypeError(type); - return null; - } - } - default: { - setInputTypeError(''); - return value; - } - } - }; - - // clears the input key, value, and type on Form - const clearForm = () => { - setInputKey(''); - setInputValue(''); - setInputType(''); - }; - - useEffect(() => { - setNewVal(typeConversion(inputValue, inputType)); - }, [inputType, inputValue]); - - // submit new stateProps entries to state context - const submitNewState = (e) => { - e.preventDefault(); - - // don't allow them to submit state without all fields - if (!inputKey || !inputType || !inputValue) { - setErrorStatus(true); - setErrorMsg('All fields are required'); - return; - } - - const statesArray = currentComponent.stateProps; - //loop though array, access each obj at key property - let keyToInt = parseInt(inputKey[0]); - if (!isNaN(keyToInt)) { - setErrorStatus(true); - setErrorMsg('Key name can not start with int.'); - return; - } - - // check here to see if state has already been created with the submitted key - for (let i = 0; i < state.components.length; i++) { - for (let j = 0; j < state.components[i].stateProps.length; j++) { - if (inputKey === state.components[i].stateProps[j]['key']) { - setErrorStatus(true); - setErrorMsg('Key name already in use.'); - return; - } else { - setErrorStatus(false); - setErrorMsg(''); - } - } - } - setPropNum((prev) => prev + 1); - const newState = { - // id name of state will be the parent component name concated with propNum. it will start at 1 and increase by 1 for each new state added - id: `${currentComponent.name}-${inputKey}`, - key: inputKey, - value: newVal, - type: inputType - }; - - const setNewState = { - // id name of state will be the parent component name concated with propNum. it will start at 1 and increase by 1 for each new state added - id: `${currentComponent.name}-set${inputKey - .slice(0, 1) - .toUpperCase()}${inputKey.slice(1)}`, - key: `set${inputKey.slice(0, 1).toUpperCase()}${inputKey.slice(1)}`, - value: '', - type: 'func' - }; - if (!inputTypeError) { - dispatch( - addState({ - newState: newState, - setNewState: setNewState, - contextParam: contextParam - }) - ); - - if (roomCode) { - emitEvent('addStateAction', roomCode, { - newState: newState, - setNewState: setNewState, - contextParam: contextParam - }); - } - - setRows1([...rows1, newState]); - setErrorStatus(false); - clearForm(); - } - }; - - // find table row using its id and if it exists, populate form with its details - const handlerRowSelect = (table) => { - let exists = false; - currentComponent.stateProps.forEach((stateProp) => { - // if stateProp id matches current row's id (table.row.id), flip exists to true - if (stateProp.id === table.row.id) exists = true; - }); - // if row id exists, populate form with corresponding inputs (key, value, type) from table row - if (exists) { - setInputKey(table.row.key); - setInputType(table.row.type); - setInputValue(table.row.value ? JSON.stringify(table.row.value) : ''); - } else clearForm(); - }; - //use effect to populate parent props table on load and every time canvas focus changes - useEffect(() => { - const parentInfo = findParent(currentId); - - setParentProps(parentInfo.parentProps); - setParentName(parentInfo.parentName); - setParentComponent(parentInfo.parentComponent); - setParentPassedInProps(parentInfo.parentPassedInProps); - }, [currentId]); - - const findParent = (childId) => { - let arr = []; - - for (let i = 0; i < data.length; i++) { - let currComponent = data[i]; - for (let j = 0; j < currComponent.children.length; j++) { - let currChild = currComponent.children[j]; - if (currChild.typeId === childId) { - const currComponentCopy = JSON.parse(JSON.stringify(currComponent)); - - return { - parentProps: currComponentCopy.stateProps, - parentName: currComponentCopy.name, - parentComponent: currComponentCopy, - parentPassedInProps: currComponentCopy.passedInProps - }; - } - } - } - return { parentProps: [], parentName: '' }; - }; - - return ( -
    -
    - -

    - Create New State -

    - setInputKey(e.target.value)} - helperText={errorStatus ? errorMsg : ''} - className={ - isThemeLight - ? `${classes.rootLight} ${classes.inputTextLight}` - : `${classes.rootDark} ${classes.inputTextDark}` - } - /> - setInputValue(e.target.value)} - className={ - isThemeLight - ? `${classes.rootLight} ${classes.inputTextLight}` - : `${classes.rootDark} ${classes.inputTextDark}` - } - /> - - - type - - - - {inputTypeError === 'object' - ? 'JSON object form: {"key": value}' - : inputTypeError === 'array' - ? 'Array form: [value]' - : 'Required'} - - -
    - -
    -
    -
    -
    -
    -
    -

    - Current Component State:{' '} - {state.components[state.canvasFocus.componentId - 1].name} -

    - -
    - -
    -

    - Available Props from Parent:{' '} - {parentName ? parentName : 'No Parents'} -

    - -
    - -
    - - - -
    - -
    -

    - Passed in Props from Parent:{' '} - {parentName ? parentName : 'No Parents'} -

    - -
    -
    -
    - ); -}; -const useStyles = makeStyles((theme: Theme) => ({ - inputField: { - marginTop: '10px', - borderRadius: '5px', - whiteSpace: 'nowrap', - overflowX: 'hidden', - textOverflow: 'ellipsis', - backgroundColor: 'rgba(255,255,255,0.15)', - margin: '0px 0px 0px 10px', - width: '140px', - height: '30px', - borderColor: 'white' - }, - inputWrapper: { - textAlign: 'center', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: '15px' - }, - addComponentWrapper: { - padding: 'auto', - marginLeft: '21px', - display: 'inline-block', - width: '100%' - }, - rootCheckBox: { - borderColor: '#0671e3', - padding: '0px' - }, - rootCheckBoxLabel: { - borderColor: '#0671e3' - }, - panelWrapper: { - width: '100%', - marginTop: '15px', - display: 'flex', - flexDirection: 'column', - alignItems: 'center' - }, - panelWrapperList: { - minHeight: '120px', - marginLeft: '-15px', - marginRight: '-15px', - width: '300px', - display: 'flex', - flexDirection: 'column', - alignItems: 'center' - }, - dragComponents: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - textAlign: 'center', - width: '500px', - backgroundColor: '#0671e3', - border: '5px solid #0671e3' - }, - panelSubheader: { - textAlign: 'center', - color: '#fff' - }, - input: {}, - newComponent: { - color: '#3c59ba', - fontSize: '95%', - marginBottom: '20px' - }, - inputLabel: { - fontSize: '1em', - marginLeft: '10px' - }, - btnGroup: { - display: 'flex', - flexDirection: 'column' - }, - addComponentButton: { - height: '42px', - width: '100px', - fontFamily: 'Roboto, Raleway, sans-serif', - fontSize: '14.5px', - textAlign: 'center', - margin: '-20px 0px 5px 150px', - border: ' 1px solid #0671E3', - borderRadius: '8px', - transition: '0.3s' - }, - rootToggle: { - color: '#696969', - fontSize: '0.85rem' - }, - lightThemeFontColor: { - color: 'white' - }, - darkThemeFontColor: { - color: '#fff' - }, - greyThemeFontColor: { - color: 'white' - }, - formControl: { - margin: '8px', - minWidth: 120 - }, - selectEmpty: { - marginTop: '16px' - }, - color: { - color: '#fff' - }, - rootLight: { - '& .MuiFormLabel-root': { - color: 'white' - }, - margin: '5px' - }, - rootDark: { - '& .MuiFormLabel-root': {}, - '& .MuiOutlinedInput-notchedOutline': {}, - margin: '5px' - }, - underlineDark: { - borderBottom: '1px solid white' - }, - rootUnderlineDark: { - '& .-icon': { - color: '#fff' - }, - '&::before': { - borderBottom: '1px solid #fff' - } - }, - rootUnderlineLight: { - '& .-icon': { - color: 'rgba(0,0,0,0.54)' - }, - '&::before': { - borderBottom: '1px solid rgba(0,0,0,0.54)' - } - }, - inputTextDark: { - '& .MuiInputBase-input': { - color: 'white' - } - }, - inputTextLight: { - '& .MuiInputBase-input': { - color: 'white' - } - } -})); - -export default StatePropsPanel; +import React, { useState, useEffect } from 'react'; +import { Theme } from '@mui/material/styles'; +import makeStyles from '@mui/styles/makeStyles'; +import { useDispatch, useSelector } from 'react-redux'; +import { addState } from '../../../../redux/reducers/slice/appStateSlice'; +import { + FormControl, + FormHelperText, + MenuItem, + InputLabel, + Select, + TextField, + Button +} from '@mui/material'; +import TableStateProps from './TableStateProps'; +import TableParentProps from './TableParentProps'; +import TablePassedInProps from './TablePassedInProps'; +import { RootState } from '../../../../redux/store'; +import { emitEvent } from '../../../../helperFunctions/socket'; + +const StatePropsPanel = ({ isThemeLight, data }): JSX.Element => { + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + const classes = useStyles(); + const [inputKey, setInputKey] = useState(''); + const [inputValue, setInputValue] = useState(''); + const [inputType, setInputType] = useState(''); + const [errorStatus, setErrorStatus] = useState(false); + const [inputTypeError, setInputTypeError] = useState(''); + const [newVal, setNewVal] = useState('test'); + const [errorMsg, setErrorMsg] = useState(''); + const currentId = state.canvasFocus.componentId; + const currentComponent = state.components[currentId - 1]; + const [parentProps, setParentProps] = useState([]); + const [parentPassedInProps, setParentPassedInProps] = useState([]); + const [parentName, setParentName] = useState('No Parents'); + const [parentComponent, setParentComponent] = useState({}); + const [rows1, setRows1] = useState(currentComponent.stateProps); + const [propNum, setPropNum] = useState(1); + + // convert value to correct type based on user input + const typeConversion = (value: string, type: string) => { + switch (type) { + case 'string': { + setInputTypeError(''); + return String(value); + } + case 'number': { + setInputTypeError(''); + return Number(value); + } + case 'boolean': { + setInputTypeError(''); + return value === 'true'; + } + case 'array': + try { + let retVal = JSON.parse(value); + if (Array.isArray(retVal)) { + setInputTypeError(''); + return retVal; + } else { + throw new Error('Input was not an array!'); + } + } catch { + setInputTypeError(type); + return null; + } + case 'object': { + try { + let retVal = JSON.parse(value); + + if (typeof retVal === 'object' && !Array.isArray(retVal)) { + setInputTypeError(''); + return retVal; + } else { + throw new Error('Input was not an object (excluding Arrays)!'); + } + } catch { + setInputTypeError(type); + return null; + } + } + default: { + setInputTypeError(''); + return value; + } + } + }; + + // clears the input key, value, and type on Form + const clearForm = () => { + setInputKey(''); + setInputValue(''); + setInputType(''); + }; + + useEffect(() => { + setNewVal(typeConversion(inputValue, inputType)); + }, [inputType, inputValue]); + + // submit new stateProps entries to state context + const submitNewState = (e) => { + e.preventDefault(); + + // don't allow them to submit state without all fields + if (!inputKey || !inputType || !inputValue) { + setErrorStatus(true); + setErrorMsg('All fields are required'); + return; + } + + const statesArray = currentComponent.stateProps; + //loop though array, access each obj at key property + let keyToInt = parseInt(inputKey[0]); + if (!isNaN(keyToInt)) { + setErrorStatus(true); + setErrorMsg('Key name can not start with int.'); + return; + } + + // check here to see if state has already been created with the submitted key + for (let i = 0; i < state.components.length; i++) { + for (let j = 0; j < state.components[i].stateProps.length; j++) { + if (inputKey === state.components[i].stateProps[j]['key']) { + setErrorStatus(true); + setErrorMsg('Key name already in use.'); + return; + } else { + setErrorStatus(false); + setErrorMsg(''); + } + } + } + setPropNum((prev) => prev + 1); + const newState = { + // id name of state will be the parent component name concated with propNum. it will start at 1 and increase by 1 for each new state added + id: `${currentComponent.name}-${inputKey}`, + key: inputKey, + value: newVal, + type: inputType + }; + + const setNewState = { + // id name of state will be the parent component name concated with propNum. it will start at 1 and increase by 1 for each new state added + id: `${currentComponent.name}-set${inputKey + .slice(0, 1) + .toUpperCase()}${inputKey.slice(1)}`, + key: `set${inputKey.slice(0, 1).toUpperCase()}${inputKey.slice(1)}`, + value: '', + type: 'func' + }; + if (!inputTypeError) { + dispatch( + addState({ + newState: newState, + setNewState: setNewState, + contextParam: contextParam + }) + ); + + if (roomCode) { + emitEvent('addStateAction', roomCode, { + newState: newState, + setNewState: setNewState, + contextParam: contextParam + }); + } + + setRows1([...rows1, newState]); + setErrorStatus(false); + clearForm(); + } + }; + + // find table row using its id and if it exists, populate form with its details + const handlerRowSelect = (table) => { + let exists = false; + currentComponent.stateProps.forEach((stateProp) => { + // if stateProp id matches current row's id (table.row.id), flip exists to true + if (stateProp.id === table.row.id) exists = true; + }); + // if row id exists, populate form with corresponding inputs (key, value, type) from table row + if (exists) { + setInputKey(table.row.key); + setInputType(table.row.type); + setInputValue(table.row.value ? JSON.stringify(table.row.value) : ''); + } else clearForm(); + }; + //use effect to populate parent props table on load and every time canvas focus changes + useEffect(() => { + const parentInfo = findParent(currentId); + + setParentProps(parentInfo.parentProps); + setParentName(parentInfo.parentName); + setParentComponent(parentInfo.parentComponent); + setParentPassedInProps(parentInfo.parentPassedInProps); + }, [currentId]); + + const findParent = (childId) => { + let arr = []; + + for (let i = 0; i < data.length; i++) { + let currComponent = data[i]; + for (let j = 0; j < currComponent.children.length; j++) { + let currChild = currComponent.children[j]; + if (currChild.typeId === childId) { + const currComponentCopy = JSON.parse(JSON.stringify(currComponent)); + + return { + parentProps: currComponentCopy.stateProps, + parentName: currComponentCopy.name, + parentComponent: currComponentCopy, + parentPassedInProps: currComponentCopy.passedInProps + }; + } + } + } + return { parentProps: [], parentName: '' }; + }; + + return ( +
    +
    + +

    + Create New State +

    + setInputKey(e.target.value)} + helperText={errorStatus ? errorMsg : ''} + className={ + isThemeLight + ? `${classes.rootLight} ${classes.inputTextLight}` + : `${classes.rootDark} ${classes.inputTextDark}` + } + /> + setInputValue(e.target.value)} + className={ + isThemeLight + ? `${classes.rootLight} ${classes.inputTextLight}` + : `${classes.rootDark} ${classes.inputTextDark}` + } + /> + + + type + + + + {inputTypeError === 'object' + ? 'JSON object form: {"key": value}' + : inputTypeError === 'array' + ? 'Array form: [value]' + : 'Required'} + + +
    + +
    +
    +
    +
    +
    +
    +

    + Current Component State:{' '} + {state.components[state.canvasFocus.componentId - 1].name} +

    + +
    + +
    +

    + Available Props from Parent:{' '} + {parentName ? parentName : 'No Parents'} +

    + +
    + +
    + + + +
    + +
    +

    + Passed in Props from Parent:{' '} + {parentName ? parentName : 'No Parents'} +

    + +
    +
    +
    + ); +}; +const useStyles = makeStyles((theme: Theme) => ({ + inputField: { + marginTop: '10px', + borderRadius: '5px', + whiteSpace: 'nowrap', + overflowX: 'hidden', + textOverflow: 'ellipsis', + backgroundColor: 'rgba(255,255,255,0.15)', + margin: '0px 0px 0px 10px', + width: '140px', + height: '30px', + borderColor: 'white' + }, + inputWrapper: { + textAlign: 'center', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: '15px' + }, + addComponentWrapper: { + padding: 'auto', + marginLeft: '21px', + display: 'inline-block', + width: '100%' + }, + rootCheckBox: { + borderColor: '#0671e3', + padding: '0px' + }, + rootCheckBoxLabel: { + borderColor: '#0671e3' + }, + panelWrapper: { + width: '100%', + marginTop: '15px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center' + }, + panelWrapperList: { + minHeight: '120px', + marginLeft: '-15px', + marginRight: '-15px', + width: '300px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center' + }, + dragComponents: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + width: '500px', + backgroundColor: '#0671e3', + border: '5px solid #0671e3' + }, + panelSubheader: { + textAlign: 'center', + color: '#fff' + }, + input: {}, + newComponent: { + color: '#3c59ba', + fontSize: '95%', + marginBottom: '20px' + }, + inputLabel: { + fontSize: '1em', + marginLeft: '10px' + }, + btnGroup: { + display: 'flex', + flexDirection: 'column' + }, + addComponentButton: { + height: '42px', + width: '100px', + fontFamily: 'Roboto, Raleway, sans-serif', + fontSize: '14.5px', + textAlign: 'center', + margin: '-20px 0px 5px 150px', + border: ' 1px solid #0671E3', + borderRadius: '8px', + transition: '0.3s' + }, + rootToggle: { + color: '#696969', + fontSize: '0.85rem' + }, + lightThemeFontColor: { + color: 'white' + }, + darkThemeFontColor: { + color: '#fff' + }, + greyThemeFontColor: { + color: 'white' + }, + formControl: { + margin: '8px', + minWidth: 120 + }, + selectEmpty: { + marginTop: '16px' + }, + color: { + color: '#fff' + }, + rootLight: { + '& .MuiFormLabel-root': { + color: 'white' + }, + margin: '5px' + }, + rootDark: { + '& .MuiFormLabel-root': {}, + '& .MuiOutlinedInput-notchedOutline': {}, + margin: '5px' + }, + underlineDark: { + borderBottom: '1px solid white' + }, + rootUnderlineDark: { + '& .-icon': { + color: '#fff' + }, + '&::before': { + borderBottom: '1px solid #fff' + } + }, + rootUnderlineLight: { + '& .-icon': { + color: 'rgba(0,0,0,0.54)' + }, + '&::before': { + borderBottom: '1px solid rgba(0,0,0,0.54)' + } + }, + inputTextDark: { + '& .MuiInputBase-input': { + color: 'white' + } + }, + inputTextLight: { + '& .MuiInputBase-input': { + color: 'white' + } + } +})); + +export default StatePropsPanel; diff --git a/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx b/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx index cf8a1567f..8a155b122 100644 --- a/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx @@ -1,164 +1,164 @@ -import React, { useState, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; -import Button from '@mui/material/Button'; -import makeStyles from '@mui/styles/makeStyles'; -import AddIcon from '@mui/icons-material/Add'; -import { addPassedInProps } from '../../../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../../../redux/store'; -import { emitEvent } from '../../../../helperFunctions/socket'; - -const TableParentProps = (props) => { - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - const classes = useStyles(); - const currentId = state.canvasFocus.componentId; - const currentComponent = state.components[currentId - 1]; - const [editRowsModel] = useState({}); - const [gridColumns, setGridColumns] = useState([]); - const parentProps = props.parentProps; - const parentPassedInProps = props.parentPassedInProps; - const parentComponent = props.parentComponent; - const columnTabs = [ - { - field: 'id', - headerName: 'ID', - width: 30, - editable: false - }, - { - field: 'key', - headerName: 'Key', - width: 90, - editable: true - }, - { - field: 'value', - headerName: 'Initial Value', - width: 100, - editable: true - }, - { - field: 'type', - headerName: 'Type', - width: 90, - editable: false - }, - { - field: 'delete', - headerName: '+', - width: 30, - editable: false, - renderCell: function renderCell(params: any) { - return ( - - ); - } - } - ]; - const addProps = (parentComponentProps, rowId) => { - // get the current focused component - // remove the state that the button is clicked - // send a dispatch to rerender the table - dispatch( - addPassedInProps({ - passedInProps: parentComponentProps, - rowId: rowId, - parentComponent: parentComponent, - contextParam: contextParam - }) - ); - - if (roomCode) { - emitEvent('addPassedInPropsAction', roomCode, { - passedInProps: parentComponentProps, - rowId: rowId, - parentComponent: parentComponent, - contextParam: contextParam - }); - } - }; - - useEffect(() => { - setGridColumns(columnTabs); - }, [props.isThemeLight]); - - // determine whether or not to include delete column in data grid - useEffect(() => { - if (props.canDeleteState) { - setGridColumns(columnTabs); - } else { - setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); - } - }, [state.canvasFocus.componentId]); - - let rows; - - // check if current component is a root component-- if yes, it shouldn't have any parent props - if (currentComponent.name === 'App' || currentComponent.name === 'index') { - rows = []; - } else { - if (parentProps) { - rows = parentProps; - if (parentPassedInProps) { - rows = [...rows, ...parentPassedInProps]; - } - } - } - - return ( -
    - -
    - ); -}; - -const useStyles = makeStyles({ - themeLight: { - color: 'white', - '& button:hover': { - backgroundColor: 'LightGray' - }, - '& button': { - color: 'white' - }, - '& .MuiTablePagination-root': { - color: 'white' - } - }, - themeDark: { - color: 'white', - '& .MuiTablePagination-root': { - color: 'white' - }, - '& .MuiIconButton-root': { - color: 'white' - }, - '& .MuiSvgIcon-root': { - color: 'white' - }, - '& .MuiDataGrid-window': { - backgroundColor: 'rgba(0,0,0,0.54)' - } - } -}); - -export default TableParentProps; +import React, { useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; +import Button from '@mui/material/Button'; +import makeStyles from '@mui/styles/makeStyles'; +import AddIcon from '@mui/icons-material/Add'; +import { addPassedInProps } from '../../../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../../../redux/store'; +import { emitEvent } from '../../../../helperFunctions/socket'; + +const TableParentProps = (props) => { + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + const classes = useStyles(); + const currentId = state.canvasFocus.componentId; + const currentComponent = state.components[currentId - 1]; + const [editRowsModel] = useState({}); + const [gridColumns, setGridColumns] = useState([]); + const parentProps = props.parentProps; + const parentPassedInProps = props.parentPassedInProps; + const parentComponent = props.parentComponent; + const columnTabs = [ + { + field: 'id', + headerName: 'ID', + width: 30, + editable: false + }, + { + field: 'key', + headerName: 'Key', + width: 90, + editable: true + }, + { + field: 'value', + headerName: 'Initial Value', + width: 100, + editable: true + }, + { + field: 'type', + headerName: 'Type', + width: 90, + editable: false + }, + { + field: 'delete', + headerName: '+', + width: 30, + editable: false, + renderCell: function renderCell(params: any) { + return ( + + ); + } + } + ]; + const addProps = (parentComponentProps, rowId) => { + // get the current focused component + // remove the state that the button is clicked + // send a dispatch to rerender the table + dispatch( + addPassedInProps({ + passedInProps: parentComponentProps, + rowId: rowId, + parentComponent: parentComponent, + contextParam: contextParam + }) + ); + + if (roomCode) { + emitEvent('addPassedInPropsAction', roomCode, { + passedInProps: parentComponentProps, + rowId: rowId, + parentComponent: parentComponent, + contextParam: contextParam + }); + } + }; + + useEffect(() => { + setGridColumns(columnTabs); + }, [props.isThemeLight]); + + // determine whether or not to include delete column in data grid + useEffect(() => { + if (props.canDeleteState) { + setGridColumns(columnTabs); + } else { + setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); + } + }, [state.canvasFocus.componentId]); + + let rows; + + // check if current component is a root component-- if yes, it shouldn't have any parent props + if (currentComponent.name === 'App' || currentComponent.name === 'index') { + rows = []; + } else { + if (parentProps) { + rows = parentProps; + if (parentPassedInProps) { + rows = [...rows, ...parentPassedInProps]; + } + } + } + + return ( +
    + +
    + ); +}; + +const useStyles = makeStyles({ + themeLight: { + color: 'white', + '& button:hover': { + backgroundColor: 'LightGray' + }, + '& button': { + color: 'white' + }, + '& .MuiTablePagination-root': { + color: 'white' + } + }, + themeDark: { + color: 'white', + '& .MuiTablePagination-root': { + color: 'white' + }, + '& .MuiIconButton-root': { + color: 'white' + }, + '& .MuiSvgIcon-root': { + color: 'white' + }, + '& .MuiDataGrid-window': { + backgroundColor: 'rgba(0,0,0,0.54)' + } + } +}); + +export default TableParentProps; diff --git a/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx b/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx index 46d1db99a..1dfc45dcd 100644 --- a/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx @@ -1,150 +1,150 @@ -import React, { useState, useEffect } from 'react'; -import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; -import Button from '@mui/material/Button'; -import ClearIcon from '@mui/icons-material/Clear'; -import makeStyles from '@mui/styles/makeStyles'; -import { useDispatch, useSelector } from 'react-redux'; -import { deletePassedInProps } from '../../../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../../../redux/store'; -import { ColumnTab } from '../../../../interfaces/Interfaces'; -import { emitEvent } from '../../../../helperFunctions/socket'; - -const TablePassedInProps = (props) => { - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - const classes = useStyles(); - const [editRowsModel] = useState({}); - const [gridColumns, setGridColumns] = useState([]); - const currentId = state.canvasFocus.componentId; - const currentComponent = state.components[currentId - 1]; - const passedInProps = - currentComponent.name !== 'App' && currentComponent.name !== 'index' - ? currentComponent.passedInProps - : []; - - //formatting for data grid columns - const columnTabs: ColumnTab[] = [ - { - field: 'id', - headerName: 'ID', - width: 30, - editable: false - }, - { - field: 'key', - headerName: 'Key', - width: 90, - editable: true - }, - { - field: 'value', - headerName: 'Initial Value', - width: 100, - editable: true - }, - { - field: 'type', - headerName: 'Type', - width: 90, - editable: false - }, - { - field: 'delete', - headerName: 'X', - width: 30, - editable: false, - align: 'left', - renderCell: function renderCell(params: any) { - return ( - - ); - } - } - ]; - - const deleteProps = (rowId) => { - // get the current focused component - // remove the state that the button is clicked - // send a dispatch to rerender the table - dispatch(deletePassedInProps({ rowId: rowId, contextParam: contextParam })); - - if (roomCode) { - emitEvent('deletePassedInPropsAction', roomCode, { - rowId: rowId, - contextParam: contextParam - }); - } - }; - - useEffect(() => { - setGridColumns(columnTabs); - }, [props.isThemeLight]); - - useEffect(() => { - if (props.canDeleteState) { - setGridColumns(columnTabs); - } else { - setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); - } - }, [state.canvasFocus.componentId]); - - // fill data grid rows with all of the passed in props from parent component (if there are any) - let rows: any = passedInProps?.slice(); - //let rows: readonly StateProp[] = passedInProps?.slice() || []; - - return ( -
    - -
    - ); -}; -// colors of state mgmt modal -const useStyles = makeStyles({ - themeLight: { - color: 'white', - '& button:hover': { - backgroundColor: 'LightGray' - }, - '& button': { - color: 'white' - }, - '& .MuiTablePagination-root': { - color: 'rbga(0,0,0,0.54)' - } - }, - themeDark: { - color: 'white', - '& .MuiTablePagination-root': { - color: 'white' - }, - '& .MuiIconButton-root': { - color: 'white' - }, - '& .MuiSvgIcon-root': { - color: 'white' - }, - '& .MuiDataGrid-window': { - backgroundColor: 'rgba(0,0,0,0.54)' - } - } -}); - -export default TablePassedInProps; +import React, { useState, useEffect } from 'react'; +import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; +import Button from '@mui/material/Button'; +import ClearIcon from '@mui/icons-material/Clear'; +import makeStyles from '@mui/styles/makeStyles'; +import { useDispatch, useSelector } from 'react-redux'; +import { deletePassedInProps } from '../../../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../../../redux/store'; +import { ColumnTab } from '../../../../interfaces/Interfaces'; +import { emitEvent } from '../../../../helperFunctions/socket'; + +const TablePassedInProps = (props) => { + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + const classes = useStyles(); + const [editRowsModel] = useState({}); + const [gridColumns, setGridColumns] = useState([]); + const currentId = state.canvasFocus.componentId; + const currentComponent = state.components[currentId - 1]; + const passedInProps = + currentComponent.name !== 'App' && currentComponent.name !== 'index' + ? currentComponent.passedInProps + : []; + + //formatting for data grid columns + const columnTabs: ColumnTab[] = [ + { + field: 'id', + headerName: 'ID', + width: 30, + editable: false + }, + { + field: 'key', + headerName: 'Key', + width: 90, + editable: true + }, + { + field: 'value', + headerName: 'Initial Value', + width: 100, + editable: true + }, + { + field: 'type', + headerName: 'Type', + width: 90, + editable: false + }, + { + field: 'delete', + headerName: 'X', + width: 30, + editable: false, + align: 'left', + renderCell: function renderCell(params: any) { + return ( + + ); + } + } + ]; + + const deleteProps = (rowId) => { + // get the current focused component + // remove the state that the button is clicked + // send a dispatch to rerender the table + dispatch(deletePassedInProps({ rowId: rowId, contextParam: contextParam })); + + if (roomCode) { + emitEvent('deletePassedInPropsAction', roomCode, { + rowId: rowId, + contextParam: contextParam + }); + } + }; + + useEffect(() => { + setGridColumns(columnTabs); + }, [props.isThemeLight]); + + useEffect(() => { + if (props.canDeleteState) { + setGridColumns(columnTabs); + } else { + setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); + } + }, [state.canvasFocus.componentId]); + + // fill data grid rows with all of the passed in props from parent component (if there are any) + let rows: any = passedInProps?.slice(); + //let rows: readonly StateProp[] = passedInProps?.slice() || []; + + return ( +
    + +
    + ); +}; +// colors of state mgmt modal +const useStyles = makeStyles({ + themeLight: { + color: 'white', + '& button:hover': { + backgroundColor: 'LightGray' + }, + '& button': { + color: 'white' + }, + '& .MuiTablePagination-root': { + color: 'rbga(0,0,0,0.54)' + } + }, + themeDark: { + color: 'white', + '& .MuiTablePagination-root': { + color: 'white' + }, + '& .MuiIconButton-root': { + color: 'white' + }, + '& .MuiSvgIcon-root': { + color: 'white' + }, + '& .MuiDataGrid-window': { + backgroundColor: 'rgba(0,0,0,0.54)' + } + } +}); + +export default TablePassedInProps; diff --git a/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx b/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx index 26f8666e2..033b3d727 100644 --- a/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx @@ -1,163 +1,163 @@ -import React, { useState, useEffect } from 'react'; -import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; -import Button from '@mui/material/Button'; -import ClearIcon from '@mui/icons-material/Clear'; -import makeStyles from '@mui/styles/makeStyles'; -import { StatePropsPanelProps } from '../../../../interfaces/Interfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { deleteState } from '../../../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../../../redux/store'; -import { ColumnTab } from '../../../../interfaces/Interfaces'; -import { emitEvent } from '../../../../helperFunctions/socket'; - -// updates state mgmt boxes and data grid -const TableStateProps = (props) => { - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - const classes = useStyles(); - const [editRowsModel] = useState({}); - const [gridColumns, setGridColumns] = useState([]); - const currentId = state.canvasFocus.componentId; - const currentComponent = state.components[currentId - 1]; - - // formatting for data grid columns - const columnTabs: ColumnTab[] = [ - { - field: 'id', - headerName: 'ID', - width: 30, - editable: false - }, - { - field: 'key', - headerName: 'Key', - width: 90, - editable: true - }, - { - field: 'value', - headerName: 'Initial Value', - width: 100, - editable: true, - valueGetter: (param) => { - //to display the actual object or array instead of [object Object], leave undefined if it is setter function - if (param.row.type === 'func') return; - return JSON.stringify(param.row.value); - } - }, - { - field: 'type', - headerName: 'Type', - width: 90, - editable: false - }, - { - field: 'delete', - headerName: 'X', - width: 30, - editable: false, - renderCell: function renderCell(params: any) { - return ( - - ); - } - } - ]; - - const handleDeleteState = (selectedId) => { - const currentId = state.canvasFocus.componentId; - const currentComponent = state.components[currentId - 1]; - const filtered = currentComponent.stateProps.filter( - (element) => element.id !== selectedId - ); - dispatch( - deleteState({ - stateProps: filtered, - rowId: selectedId, - contextParam: contextParam - }) - ); - - if (roomCode) { - emitEvent('deleteStateAction', roomCode, { - stateProps: filtered, - rowId: selectedId, - contextParam: contextParam - }); - } - }; - - useEffect(() => { - setGridColumns(columnTabs); - }, [props.isThemeLight]); - - const { selectHandler }: StatePropsPanelProps = props; - - useEffect(() => { - if (props.canDeleteState) { - setGridColumns(columnTabs); - } else { - setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); - } - }, [state.canvasFocus.componentId]); - - // rows to show are either from current component or from a given provider - let rows = []; - currentComponent.stateProps?.forEach((prop) => { - rows.push(prop); - }); - - return ( -
    - -
    - ); -}; - -const useStyles = makeStyles({ - themeLight: { - color: 'white', - '& button:hover': { - backgroundColor: 'LightGray' - }, - '& button': { - color: 'white' - } - }, - themeDark: { - color: 'white', - '& .MuiTablePagination-root': { - color: 'white' - }, - '& .MuiIconButton-root': { - color: 'white' - }, - '& .MuiSvgIcon-root': { - color: 'white' - }, - '& .MuiDataGrid-window': { - backgroundColor: 'rgba(0,0,0,0.54)' - } - } -}); - -export default TableStateProps; +import React, { useState, useEffect } from 'react'; +import { DataGrid, GridEditRowsModel } from '@mui/x-data-grid'; +import Button from '@mui/material/Button'; +import ClearIcon from '@mui/icons-material/Clear'; +import makeStyles from '@mui/styles/makeStyles'; +import { StatePropsPanelProps } from '../../../../interfaces/Interfaces'; +import { useDispatch, useSelector } from 'react-redux'; +import { deleteState } from '../../../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../../../redux/store'; +import { ColumnTab } from '../../../../interfaces/Interfaces'; +import { emitEvent } from '../../../../helperFunctions/socket'; + +// updates state mgmt boxes and data grid +const TableStateProps = (props) => { + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + const classes = useStyles(); + const [editRowsModel] = useState({}); + const [gridColumns, setGridColumns] = useState([]); + const currentId = state.canvasFocus.componentId; + const currentComponent = state.components[currentId - 1]; + + // formatting for data grid columns + const columnTabs: ColumnTab[] = [ + { + field: 'id', + headerName: 'ID', + width: 30, + editable: false + }, + { + field: 'key', + headerName: 'Key', + width: 90, + editable: true + }, + { + field: 'value', + headerName: 'Initial Value', + width: 100, + editable: true, + valueGetter: (param) => { + //to display the actual object or array instead of [object Object], leave undefined if it is setter function + if (param.row.type === 'func') return; + return JSON.stringify(param.row.value); + } + }, + { + field: 'type', + headerName: 'Type', + width: 90, + editable: false + }, + { + field: 'delete', + headerName: 'X', + width: 30, + editable: false, + renderCell: function renderCell(params: any) { + return ( + + ); + } + } + ]; + + const handleDeleteState = (selectedId) => { + const currentId = state.canvasFocus.componentId; + const currentComponent = state.components[currentId - 1]; + const filtered = currentComponent.stateProps.filter( + (element) => element.id !== selectedId + ); + dispatch( + deleteState({ + stateProps: filtered, + rowId: selectedId, + contextParam: contextParam + }) + ); + + if (roomCode) { + emitEvent('deleteStateAction', roomCode, { + stateProps: filtered, + rowId: selectedId, + contextParam: contextParam + }); + } + }; + + useEffect(() => { + setGridColumns(columnTabs); + }, [props.isThemeLight]); + + const { selectHandler }: StatePropsPanelProps = props; + + useEffect(() => { + if (props.canDeleteState) { + setGridColumns(columnTabs); + } else { + setGridColumns(columnTabs.slice(0, gridColumns.length - 1)); + } + }, [state.canvasFocus.componentId]); + + // rows to show are either from current component or from a given provider + let rows = []; + currentComponent.stateProps?.forEach((prop) => { + rows.push(prop); + }); + + return ( +
    + +
    + ); +}; + +const useStyles = makeStyles({ + themeLight: { + color: 'white', + '& button:hover': { + backgroundColor: 'LightGray' + }, + '& button': { + color: 'white' + } + }, + themeDark: { + color: 'white', + '& .MuiTablePagination-root': { + color: 'white' + }, + '& .MuiIconButton-root': { + color: 'white' + }, + '& .MuiSvgIcon-root': { + color: 'white' + }, + '& .MuiDataGrid-window': { + backgroundColor: 'rgba(0,0,0,0.54)' + } + } +}); + +export default TableStateProps; diff --git a/app/src/components/StateManagement/DisplayTab/DataTable.tsx b/app/src/components/StateManagement/DisplayTab/DataTable.tsx index 6d066e906..41c18a9ce 100644 --- a/app/src/components/StateManagement/DisplayTab/DataTable.tsx +++ b/app/src/components/StateManagement/DisplayTab/DataTable.tsx @@ -1,111 +1,111 @@ -import React from 'react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; -import { styled } from '@mui/material/styles'; -import TableCell, { tableCellClasses } from '@mui/material/TableCell'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../redux/store' - -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, - }, - [`&.${tableCellClasses.body}`]: { - color: theme.palette.common.black, - fontSize: 14, - }, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.action.hover - }, - // hide last border - '&:last-child td, &:last-child th': { - border: 0, - }, -})); - -export default function DataTable(props) { - const { - currComponentState, parentProps, clickedComp, data, - } = props; - const state = useSelector((store:RootState) => store.appState) - - // determine if the current component is a root component - let isRoot = false; - - for (let i = 0; i < data.length; i++) { - if (data[i].name === clickedComp) { - if (state.rootComponents.includes(data[i].id)) isRoot = true; - } - } - - return ( - - - - {/* we are checking if the clicked component is a root component-- if yes, it doesn't have any parents so don't need passed-in props table */} - {(!isRoot - && ( - <> - - - - Props Passed in from Parent: - - - - - - Key - Type - Initial Value - - {parentProps ? parentProps.map((data, index) => ( - - {data.key} - {data.type} - {data.value} - - )) : ''} - - - ) - )} - - {/* The below table will contain the state initialized within the clicked component */} - - - - State Initialized in Current Component: - - - - - - Key - Type - Initial Value - - {currComponentState ? currComponentState.map((data, index) => ( - - {data.key} - {data.type} - {data.value} - - )) : ''} - -
    -
    - ); +import React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import { styled } from '@mui/material/styles'; +import TableCell, { tableCellClasses } from '@mui/material/TableCell'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../redux/store' + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + [`&.${tableCellClasses.body}`]: { + color: theme.palette.common.black, + fontSize: 14, + }, +})); + +const StyledTableRow = styled(TableRow)(({ theme }) => ({ + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover + }, + // hide last border + '&:last-child td, &:last-child th': { + border: 0, + }, +})); + +export default function DataTable(props) { + const { + currComponentState, parentProps, clickedComp, data, + } = props; + const state = useSelector((store:RootState) => store.appState) + + // determine if the current component is a root component + let isRoot = false; + + for (let i = 0; i < data.length; i++) { + if (data[i].name === clickedComp) { + if (state.rootComponents.includes(data[i].id)) isRoot = true; + } + } + + return ( + + + + {/* we are checking if the clicked component is a root component-- if yes, it doesn't have any parents so don't need passed-in props table */} + {(!isRoot + && ( + <> + + + + Props Passed in from Parent: + + + + + + Key + Type + Initial Value + + {parentProps ? parentProps.map((data, index) => ( + + {data.key} + {data.type} + {data.value} + + )) : ''} + + + ) + )} + + {/* The below table will contain the state initialized within the clicked component */} + + + + State Initialized in Current Component: + + + + + + Key + Type + Initial Value + + {currComponentState ? currComponentState.map((data, index) => ( + + {data.key} + {data.type} + {data.value} + + )) : ''} + +
    +
    + ); } \ No newline at end of file diff --git a/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx b/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx index 22a7d8ff1..fe9eb81f5 100644 --- a/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx +++ b/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx @@ -1,74 +1,74 @@ -import React, { useState } from 'react'; -import Divider from '@mui/material/Divider'; -import Grid from '@mui/material/Grid'; -import { Typography } from '@mui/material'; -import DataTable from './DataTable'; -import Tree from './Tree'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../redux/store'; - -function DisplayContainer({ data, props }) { - // "data" is referring to components from state - passed in from StateManagement - // grabbing intialized state from App using UseContext - const [currComponentState, setCurrComponentState] = useState([]); - const [parentProps, setParentProps] = useState([]); - const state = useSelector((store: RootState) => store.appState); - - let root = ''; - - // check the canvasFocus - // if canvasFocus is a root component, use that root component as "root" - if (state.rootComponents.includes(state.canvasFocus.componentId)) { - for (let i = 0; i < data.length; i++) { - if (data[i].id === state.canvasFocus.componentId) root = data[i].name; - } - } else if (state.projectType === 'Classic React') { - // else default to the main root component (aka app or index depending on react vs next/gatsby) - root = 'App'; - } else { - root = 'index'; - } - - // root becomes default value of clickedComp - const [clickedComp, setClickedComp] = useState(root); - - return ( -
    - - - - - Click on a component in the graph to see its state - - - Total State for {clickedComp} - - - -
    - ); -} -export default DisplayContainer; +import React, { useState } from 'react'; +import Divider from '@mui/material/Divider'; +import Grid from '@mui/material/Grid'; +import { Typography } from '@mui/material'; +import DataTable from './DataTable'; +import Tree from './Tree'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../redux/store'; + +function DisplayContainer({ data, props }) { + // "data" is referring to components from state - passed in from StateManagement + // grabbing intialized state from App using UseContext + const [currComponentState, setCurrComponentState] = useState([]); + const [parentProps, setParentProps] = useState([]); + const state = useSelector((store: RootState) => store.appState); + + let root = ''; + + // check the canvasFocus + // if canvasFocus is a root component, use that root component as "root" + if (state.rootComponents.includes(state.canvasFocus.componentId)) { + for (let i = 0; i < data.length; i++) { + if (data[i].id === state.canvasFocus.componentId) root = data[i].name; + } + } else if (state.projectType === 'Classic React') { + // else default to the main root component (aka app or index depending on react vs next/gatsby) + root = 'App'; + } else { + root = 'index'; + } + + // root becomes default value of clickedComp + const [clickedComp, setClickedComp] = useState(root); + + return ( +
    + + + + + Click on a component in the graph to see its state + + + Total State for {clickedComp} + + + +
    + ); +} +export default DisplayContainer; diff --git a/app/src/components/StateManagement/DisplayTab/Tree.tsx b/app/src/components/StateManagement/DisplayTab/Tree.tsx index 415eacc1e..fa521151d 100644 --- a/app/src/components/StateManagement/DisplayTab/Tree.tsx +++ b/app/src/components/StateManagement/DisplayTab/Tree.tsx @@ -1,203 +1,203 @@ -import React, { useRef, useEffect} from 'react'; -import { - select, hierarchy, tree, linkHorizontal, -} from 'd3'; -import cloneDeep from 'lodash/cloneDeep'; -import useResizeObserver from './useResizeObserver'; -import { useSelector } from 'react-redux'; -import { ChildElement } from '../../../interfaces/Interfaces'; -import { RootState } from '../../../redux/store'; - -function usePrevious(value) { - const ref = useRef(); - useEffect(() => ref.current = value); - return ref.current; -} - -function Tree({ - data, setCurrComponentState, setParentProps, setClickedComp, -}) { - const state = useSelector((store:RootState) => store.appState) - // Provide types for the refs. - // In this case HTMLDivElement for the wrapperRef and SVGSVGElement for the svgRef. - // create mutable ref objects with initial values of null - const svgRef = useRef(null); - const wrapperRef = useRef(null); - const xPosition = 50; - const textAndBorderColor = '#bdbdbd'; - const dimensions = useResizeObserver(wrapperRef); - // we save data to see if it changed - const previouslyRenderedData = usePrevious(data); - // function to filter out separators to prevent render on tree chart - const removeHTMLElements = (arr: ChildElement[]) => { - for (let i = 0; i < arr.length; i++) { - if (arr[i] === undefined) continue; - // if element is separator, remove it - if (arr[i].type === 'HTML Element') { - arr.splice(i, 1); - i -= 1; - } - // if element has a children array and that array has length, recursive call - else if (arr[i].type === 'Component' && arr[i].children.length) { - // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) - if (arr[i].type === 'Component') arr[i] = cloneDeep(data.find((component) => component.name === arr[i].name)); - removeHTMLElements(arr[i].children); - } - } - return arr; - }; - - // create a deep clone of data to avoid mutating the actual children array in removing separators - const dataDeepClone = cloneDeep(data); - - if (state.projectType === 'Next.js') { - dataDeepClone.forEach((element) => { - element.children = sanitize(element.children).filter((element) => !Array.isArray(element)); - }); - - function sanitize(children) { - return children.map((child) => { - if (child.name === 'Switch' || child.name === 'Route') return sanitize(child.children); - return child; - }); - } - } - - // remove separators and update components to current versions - dataDeepClone.forEach((component) => removeHTMLElements(component.children)); - - // will be called initially and on every data change - useEffect(() => { - const svg = select(svgRef.current); - // use dimensions from useResizeObserver, - // but use getBoundingClientRect on initial render - // (dimensions are null for the first render) - - const { width, height } = dimensions || wrapperRef.current.getBoundingClientRect(); - // transform hierarchical data - - let root; - let rootName; - - if (state.rootComponents.includes(state.canvasFocus.componentId)) { - // find out if canvasFocus is a root component - // if yes, set root of tree to be that canvasFocus component - // find that component inside dataDeepClone - for (let i = 0; i < dataDeepClone.length; i++) { - if (dataDeepClone[i].id === state.canvasFocus.componentId) { - root = hierarchy(dataDeepClone[i]); - rootName = dataDeepClone[i].name; - } - } - } else { - // if no, set root of tree to be app/index - root = hierarchy(dataDeepClone[0]); - rootName = dataDeepClone[0].name; - } - - setClickedComp(rootName); - - const treeLayout = tree().size([height, width - 125]); - // Returns a new link generator with horizontal display. - // To visualize links in a tree diagram rooted on the left edge of the display - const linkGenerator = linkHorizontal() - .x((link) => link.y) - .y((link) => link.x); - - // insert our data into the tree layout - treeLayout(root); - - svg - .selectAll('.node') - .data(root.descendants()) - .join((enter) => enter.append('circle').attr('opacity', 0)) - .attr('class', 'node') - /* - The cx, cy attributes are associated with the circle and ellipse elements and designate the centre of each shape. The coordinates are set from the top, left hand corner of the web page. - cx: The position of the centre of the element in the x axis measured from the left side of the screen. - cy: The position of the centre of the element in the y axis measured from the top of the screen. - */ - // translate (x, y) - .attr('cx', (node) => node.y) - .attr('cy', (node) => node.x) - .attr('r', 10) - .attr('opacity', 1) - .style('fill', 'white') - .attr('transform', `translate(${xPosition}, 0)`) - .on('click', (element) => { - const nameOfClicked = element.srcElement.__data__.data.name; - let passedInProps; - let componentStateProps; - - // iterate through data array to find stateProps and passedInProps - for (let i = 0; i < data.length; i++) { - if (data[i].name === nameOfClicked) { - componentStateProps = data[i].stateProps; - passedInProps = data[i].passedInProps; - } - } - setCurrComponentState(componentStateProps); - setParentProps(passedInProps); - setClickedComp(nameOfClicked); - }); - - // link - lines that connect the nodes - const enteringAndUpdatingLinks = svg - .selectAll('.link') - .data(root.links()) - .join('path') - .attr('class', 'link') - .attr('d', linkGenerator) - .attr('stroke', 'white') - .attr('fill', 'none') - .attr('opacity', 1) - .attr('transform', `translate(${xPosition}, 0)`); - if (data !== previouslyRenderedData) { - enteringAndUpdatingLinks - .attr('stroke-dashoffset', function () { - return this.length; - }) - .attr('stroke-dashoffset', 0); - } - - // label - the names of each html element (node) - svg - .selectAll('.label') - .data(root.descendants()) - .join((enter) => enter.append('text').attr('opacity', 0)) - .attr('class', 'label') - .attr('x', (node) => node.y) - .attr('y', (node) => node.x - 20) - .attr('text-anchor', 'middle') - .attr('font-size', 18) - .style('fill', 'white') - .text((node) => node.data.name) - .attr('opacity', 1) - .attr('transform', `translate(${xPosition}, 0)`); - }, [data, dimensions, previouslyRenderedData]); - - const treeStyles = { - height: '400px', - width: '100%', - margin: '10px 10px 10px 10px', - overflow: 'auto', - alignItems: 'center', - }; - - const wrapperStyles = { - borderRadius: '10px', - width: '100%', - height: '90%', - display: 'flex', - justifyContent: 'center', - backgroundColor: '#1E2024' - }; - - return ( -
    - -
    - ); -} - -export default Tree; +import React, { useRef, useEffect} from 'react'; +import { + select, hierarchy, tree, linkHorizontal, +} from 'd3'; +import cloneDeep from 'lodash/cloneDeep'; +import useResizeObserver from './useResizeObserver'; +import { useSelector } from 'react-redux'; +import { ChildElement } from '../../../interfaces/Interfaces'; +import { RootState } from '../../../redux/store'; + +function usePrevious(value) { + const ref = useRef(); + useEffect(() => ref.current = value); + return ref.current; +} + +function Tree({ + data, setCurrComponentState, setParentProps, setClickedComp, +}) { + const state = useSelector((store:RootState) => store.appState) + // Provide types for the refs. + // In this case HTMLDivElement for the wrapperRef and SVGSVGElement for the svgRef. + // create mutable ref objects with initial values of null + const svgRef = useRef(null); + const wrapperRef = useRef(null); + const xPosition = 50; + const textAndBorderColor = '#bdbdbd'; + const dimensions = useResizeObserver(wrapperRef); + // we save data to see if it changed + const previouslyRenderedData = usePrevious(data); + // function to filter out separators to prevent render on tree chart + const removeHTMLElements = (arr: ChildElement[]) => { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === undefined) continue; + // if element is separator, remove it + if (arr[i].type === 'HTML Element') { + arr.splice(i, 1); + i -= 1; + } + // if element has a children array and that array has length, recursive call + else if (arr[i].type === 'Component' && arr[i].children.length) { + // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) + if (arr[i].type === 'Component') arr[i] = cloneDeep(data.find((component) => component.name === arr[i].name)); + removeHTMLElements(arr[i].children); + } + } + return arr; + }; + + // create a deep clone of data to avoid mutating the actual children array in removing separators + const dataDeepClone = cloneDeep(data); + + if (state.projectType === 'Next.js') { + dataDeepClone.forEach((element) => { + element.children = sanitize(element.children).filter((element) => !Array.isArray(element)); + }); + + function sanitize(children) { + return children.map((child) => { + if (child.name === 'Switch' || child.name === 'Route') return sanitize(child.children); + return child; + }); + } + } + + // remove separators and update components to current versions + dataDeepClone.forEach((component) => removeHTMLElements(component.children)); + + // will be called initially and on every data change + useEffect(() => { + const svg = select(svgRef.current); + // use dimensions from useResizeObserver, + // but use getBoundingClientRect on initial render + // (dimensions are null for the first render) + + const { width, height } = dimensions || wrapperRef.current.getBoundingClientRect(); + // transform hierarchical data + + let root; + let rootName; + + if (state.rootComponents.includes(state.canvasFocus.componentId)) { + // find out if canvasFocus is a root component + // if yes, set root of tree to be that canvasFocus component + // find that component inside dataDeepClone + for (let i = 0; i < dataDeepClone.length; i++) { + if (dataDeepClone[i].id === state.canvasFocus.componentId) { + root = hierarchy(dataDeepClone[i]); + rootName = dataDeepClone[i].name; + } + } + } else { + // if no, set root of tree to be app/index + root = hierarchy(dataDeepClone[0]); + rootName = dataDeepClone[0].name; + } + + setClickedComp(rootName); + + const treeLayout = tree().size([height, width - 125]); + // Returns a new link generator with horizontal display. + // To visualize links in a tree diagram rooted on the left edge of the display + const linkGenerator = linkHorizontal() + .x((link) => link.y) + .y((link) => link.x); + + // insert our data into the tree layout + treeLayout(root); + + svg + .selectAll('.node') + .data(root.descendants()) + .join((enter) => enter.append('circle').attr('opacity', 0)) + .attr('class', 'node') + /* + The cx, cy attributes are associated with the circle and ellipse elements and designate the centre of each shape. The coordinates are set from the top, left hand corner of the web page. + cx: The position of the centre of the element in the x axis measured from the left side of the screen. + cy: The position of the centre of the element in the y axis measured from the top of the screen. + */ + // translate (x, y) + .attr('cx', (node) => node.y) + .attr('cy', (node) => node.x) + .attr('r', 10) + .attr('opacity', 1) + .style('fill', 'white') + .attr('transform', `translate(${xPosition}, 0)`) + .on('click', (element) => { + const nameOfClicked = element.srcElement.__data__.data.name; + let passedInProps; + let componentStateProps; + + // iterate through data array to find stateProps and passedInProps + for (let i = 0; i < data.length; i++) { + if (data[i].name === nameOfClicked) { + componentStateProps = data[i].stateProps; + passedInProps = data[i].passedInProps; + } + } + setCurrComponentState(componentStateProps); + setParentProps(passedInProps); + setClickedComp(nameOfClicked); + }); + + // link - lines that connect the nodes + const enteringAndUpdatingLinks = svg + .selectAll('.link') + .data(root.links()) + .join('path') + .attr('class', 'link') + .attr('d', linkGenerator) + .attr('stroke', 'white') + .attr('fill', 'none') + .attr('opacity', 1) + .attr('transform', `translate(${xPosition}, 0)`); + if (data !== previouslyRenderedData) { + enteringAndUpdatingLinks + .attr('stroke-dashoffset', function () { + return this.length; + }) + .attr('stroke-dashoffset', 0); + } + + // label - the names of each html element (node) + svg + .selectAll('.label') + .data(root.descendants()) + .join((enter) => enter.append('text').attr('opacity', 0)) + .attr('class', 'label') + .attr('x', (node) => node.y) + .attr('y', (node) => node.x - 20) + .attr('text-anchor', 'middle') + .attr('font-size', 18) + .style('fill', 'white') + .text((node) => node.data.name) + .attr('opacity', 1) + .attr('transform', `translate(${xPosition}, 0)`); + }, [data, dimensions, previouslyRenderedData]); + + const treeStyles = { + height: '400px', + width: '100%', + margin: '10px 10px 10px 10px', + overflow: 'auto', + alignItems: 'center', + }; + + const wrapperStyles = { + borderRadius: '10px', + width: '100%', + height: '90%', + display: 'flex', + justifyContent: 'center', + backgroundColor: '#1E2024' + }; + + return ( +
    + +
    + ); +} + +export default Tree; diff --git a/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts b/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts index d9adf16b1..74fb71fe0 100644 --- a/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts +++ b/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts @@ -1,23 +1,23 @@ -import { useEffect, useState } from 'react'; -import ResizeObserver from 'resize-observer-polyfill'; - -const useResizeObserver = (ref) => { - const [dimensions, setDimensions] = useState(null); - useEffect(() => { - // the element being observed (div with green border) - const observeTarget = ref.current; - const resizeObserver = new ResizeObserver((entries) => { - entries.forEach((entry) => { - // contentRect is an object containing the dimensions of the observed element - setDimensions(entry.contentRect); - }); - }); - resizeObserver.observe(observeTarget); - return () => { - resizeObserver.unobserve(observeTarget); - }; - }, [ref]); - return dimensions; -}; - -export default useResizeObserver; +import { useEffect, useState } from 'react'; +import ResizeObserver from 'resize-observer-polyfill'; + +const useResizeObserver = (ref) => { + const [dimensions, setDimensions] = useState(null); + useEffect(() => { + // the element being observed (div with green border) + const observeTarget = ref.current; + const resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + // contentRect is an object containing the dimensions of the observed element + setDimensions(entry.contentRect); + }); + }); + resizeObserver.observe(observeTarget); + return () => { + resizeObserver.unobserve(observeTarget); + }; + }, [ref]); + return dimensions; +}; + +export default useResizeObserver; diff --git a/app/src/components/StateManagement/StateManagement.tsx b/app/src/components/StateManagement/StateManagement.tsx index c86aaad2f..9fdde9e6e 100644 --- a/app/src/components/StateManagement/StateManagement.tsx +++ b/app/src/components/StateManagement/StateManagement.tsx @@ -1,66 +1,66 @@ -import React from 'react'; -import { makeStyles } from '@mui/styles'; -import Box from '@mui/material/Box'; -import Tab from '@mui/material/Tab'; -import TabContext from '@mui/lab/TabContext'; -import TabList from '@mui/lab/TabList'; -import TabPanel from '@mui/lab/TabPanel'; -import { useSelector } from 'react-redux'; -import CreateContainer from './CreateTab/CreateContainer'; -import DisplayContainer from './DisplayTab/DisplayContainer'; -import { RootState } from '../../redux/store'; - -const useStyles = makeStyles({ - contextContainer: { - backgroundColor: 'white', - height: 'fit-content', - width: 'fit-content', - minWidth: '100%' - } -}); - -const StateManager = (props): JSX.Element => { - const state = useSelector((store: RootState) => store.appState); - - const { components } = state; - const classes = useStyles(); - const [value, setValue] = React.useState('1'); - - const handleChange = (event: React.SyntheticEvent, newValue: string) => { - setValue(newValue); - }; - - // add hook here to access which component has been clicked - // then this will re-render the dataTable - - return ( - -
    - - - - - - - - - - - - - - - - -
    -
    - ); -}; - -export default StateManager; +import React from 'react'; +import { makeStyles } from '@mui/styles'; +import Box from '@mui/material/Box'; +import Tab from '@mui/material/Tab'; +import TabContext from '@mui/lab/TabContext'; +import TabList from '@mui/lab/TabList'; +import TabPanel from '@mui/lab/TabPanel'; +import { useSelector } from 'react-redux'; +import CreateContainer from './CreateTab/CreateContainer'; +import DisplayContainer from './DisplayTab/DisplayContainer'; +import { RootState } from '../../redux/store'; + +const useStyles = makeStyles({ + contextContainer: { + backgroundColor: 'white', + height: 'fit-content', + width: 'fit-content', + minWidth: '100%' + } +}); + +const StateManager = (props): JSX.Element => { + const state = useSelector((store: RootState) => store.appState); + + const { components } = state; + const classes = useStyles(); + const [value, setValue] = React.useState('1'); + + const handleChange = (event: React.SyntheticEvent, newValue: string) => { + setValue(newValue); + }; + + // add hook here to access which component has been clicked + // then this will re-render the dataTable + + return ( + +
    + + + + + + + + + + + + + + + + +
    +
    + ); +}; + +export default StateManager; diff --git a/app/src/components/bottom/BottomTabs.tsx b/app/src/components/bottom/BottomTabs.tsx index eec11af21..8bd5a3978 100644 --- a/app/src/components/bottom/BottomTabs.tsx +++ b/app/src/components/bottom/BottomTabs.tsx @@ -1,245 +1,245 @@ -import React, { useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { changeProjectType } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import { MeetingProvider } from '@videosdk.live/react-sdk'; -const videoSDKToken = `${import.meta.env.VITE_VIDEOSDK_TOKEN}`; -import Chatroom from './ChatRoom'; -import CreationPanel from './CreationPanel'; -import CustomizationPanel from '../../containers/CustomizationPanel'; -import StylesEditor from './StylesEditor'; -import Tree from '../../tree/TreeChart'; -import ContextManager from '../ContextAPIManager/ContextManager'; -import StateManager from '../StateManagement/StateManagement'; -import MUIProps from './MUIProps'; -import makeStyles from '@mui/styles/makeStyles'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import Box from '@mui/material/Box'; -import FormControl from '@mui/material/FormControl'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; -import arrow from '../main/Arrow'; - - -const BottomTabs = (props): JSX.Element => { - const { setBottomShow, isThemeLight } = props; - const dispatch = useDispatch(); - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const collaborationRoom = useSelector((store: RootState) => store.roomSlice); - - const [tab, setTab] = useState(0); - const classes = useStyles(); - const [theme, setTheme] = useState('solarized_light'); - - const handleChange = (event: React.ChangeEvent, value: number) => { - setTab(value); - }; - - const handleProjectChange = (event) => { - const projectType = event.target.value; - dispatch(changeProjectType({ projectType, contextParam })); - }; - const { components } = state; - - arrow.renderArrow(state.canvasFocus?.childId); - - const showBottomPanel = () => { - setBottomShow(true); - }; - - return ( - -
    - - - - - - - - - - - - -
    - - - -
    -
    -
    - {tab === 0 && } - {tab === 1 && } - {tab === 2 && } - {tab === 3 && } - {tab === 4 && } - {tab === 5 && } - {tab === 6 && ()} - {tab === 7 && } -
    -
    -
    - ); -}; - -const useStyles = makeStyles((theme) => ({ - root: { - flexGrow: 1, - height: '100%', - color: '#E8E8E8' - }, - rootLight: { - backgroundColor: '#0671e3' - }, - bottomHeader: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - Width: '200px' - }, - tabsRoot: { - minHeight: '50%' - }, - tabsIndicator: { - backgroundColor: '#0671E3' - }, - tabRoot: { - textTransform: 'initial', - minWidth: 170, - height: 60, - fontFamily: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - 'Arial', - 'sans-serif', - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"' - ].join(','), - '&:hover': { - color: 'white', - opacity: 1 - }, - fontWeight: 300, - '&$tabSelected': { - color: 'white', - backgroundColor: '#2D313A' - }, - '&:focus': { - color: 'white' - } - }, - tabSelected: {}, - typography: { - padding: '24px' - }, - padding: { - padding: `0 16px` - }, - switch: { - marginRight: '10px', - marginTop: '2px' - }, - projectTypeWrapper: { - marginTop: '10px', - marginBotton: '10px', - marginLeft: '10px' - }, - projectSelector: { - backgroundColor: '#131416', - color: 'white', - margin: '0 10px 10px 0' - } -})); - -export default BottomTabs; +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { changeProjectType } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import { MeetingProvider } from '@videosdk.live/react-sdk'; +const videoSDKToken = `${import.meta.env.VITE_VIDEOSDK_TOKEN}`; +import Chatroom from './ChatRoom'; +import CreationPanel from './CreationPanel'; +import CustomizationPanel from '../../containers/CustomizationPanel'; +import StylesEditor from './StylesEditor'; +import Tree from '../../tree/TreeChart'; +import ContextManager from '../ContextAPIManager/ContextManager'; +import StateManager from '../StateManagement/StateManagement'; +import MUIProps from './MUIProps'; +import makeStyles from '@mui/styles/makeStyles'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Box from '@mui/material/Box'; +import FormControl from '@mui/material/FormControl'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import arrow from '../main/Arrow'; + + +const BottomTabs = (props): JSX.Element => { + const { setBottomShow, isThemeLight } = props; + const dispatch = useDispatch(); + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const collaborationRoom = useSelector((store: RootState) => store.roomSlice); + + const [tab, setTab] = useState(0); + const classes = useStyles(); + const [theme, setTheme] = useState('solarized_light'); + + const handleChange = (event: React.ChangeEvent, value: number) => { + setTab(value); + }; + + const handleProjectChange = (event) => { + const projectType = event.target.value; + dispatch(changeProjectType({ projectType, contextParam })); + }; + const { components } = state; + + arrow.renderArrow(state.canvasFocus?.childId); + + const showBottomPanel = () => { + setBottomShow(true); + }; + + return ( + +
    + + + + + + + + + + + + +
    + + + +
    +
    +
    + {tab === 0 && } + {tab === 1 && } + {tab === 2 && } + {tab === 3 && } + {tab === 4 && } + {tab === 5 && } + {tab === 6 && ()} + {tab === 7 && } +
    +
    +
    + ); +}; + +const useStyles = makeStyles((theme) => ({ + root: { + flexGrow: 1, + height: '100%', + color: '#E8E8E8' + }, + rootLight: { + backgroundColor: '#0671e3' + }, + bottomHeader: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + Width: '200px' + }, + tabsRoot: { + minHeight: '50%' + }, + tabsIndicator: { + backgroundColor: '#0671E3' + }, + tabRoot: { + textTransform: 'initial', + minWidth: 170, + height: 60, + fontFamily: [ + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"' + ].join(','), + '&:hover': { + color: 'white', + opacity: 1 + }, + fontWeight: 300, + '&$tabSelected': { + color: 'white', + backgroundColor: '#2D313A' + }, + '&:focus': { + color: 'white' + } + }, + tabSelected: {}, + typography: { + padding: '24px' + }, + padding: { + padding: `0 16px` + }, + switch: { + marginRight: '10px', + marginTop: '2px' + }, + projectTypeWrapper: { + marginTop: '10px', + marginBotton: '10px', + marginLeft: '10px' + }, + projectSelector: { + backgroundColor: '#131416', + color: 'white', + margin: '0 10px 10px 0' + } +})); + +export default BottomTabs; diff --git a/app/src/components/bottom/ChatRoom.tsx b/app/src/components/bottom/ChatRoom.tsx index 128b2845b..bfac4968d 100644 --- a/app/src/components/bottom/ChatRoom.tsx +++ b/app/src/components/bottom/ChatRoom.tsx @@ -1,237 +1,237 @@ -import { useState, useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; -import { emitEvent } from '../../helperFunctions/socket'; -import Videomeeting from './VideoMeeting'; -import { Send } from '@mui/icons-material'; - -const Chatroom = (props): JSX.Element => { - const { userName, roomCode, messages, userJoinCollabRoom } = useSelector( - (store: RootState) => store.roomSlice - ); - - const [inputContent, setInputContent] = useState(''); - - const wrapperStyles = { - border: '1px solid #31343A', - borderRadius: '15px', - width: '70%', - height: '100%', - display: 'flex', - flexDirection: 'column', - alignSelf: 'center', - padding: '12px 20px', - backgroundColor: '#1B1B1B', - overflow: 'auto' - }; - - const inputContainerStyles = { - width: '100%', - paddingLeft: '20px', - paddingTop: '10px', - display: 'flex', - justifyContent: 'center' - }; - - const inputStyles = { - width: '72%', - padding: '10px 12px', - borderRadius: '50px', - backgroundColor: '#1B1B1B', - color: 'white', - border: '1px solid #31343A', - marginLeft: '28px' - }; - - const buttonStyles = { - padding: '5px 7px', - marginLeft: '10px', - backgroundColor: '#0671E3', - color: 'white', - border: 'none', - borderRadius: '50%', - cursor: 'pointer' - }; - - const handleSubmit = (e) => { - e.preventDefault(); - if (inputContent !== '') { - emitEvent('send-chat-message', roomCode, { - userName, - message: inputContent - }); - setInputContent(''); - } - }; - - const handleMessageContainerStyle = (message: object) => { - if (message['type'] === 'activity') { - return { - color: '#E8E9EB', - fontSize: '12px', - alignSelf: 'center', - margin: '3px 0' - }; - } else { - if (message['userName'] === userName) - return { - alignSelf: 'end', - padding: '8px 15px', - backgroundColor: '#0671E3', - borderRadius: '15.5px', - margin: '3px 0', - maxWidth: '300px' - }; - return { - color: 'white', - padding: '8px 15px', - backgroundColor: '#333333', - borderRadius: '15.5px', - margin: '3px 0', - maxWidth: '300px' - }; - } - }; - - const renderMessages = () => { - return messages.map((message, index) => { - if (message.type === 'activity') { - return ( -
    - {message.message} -
    - ); - } else if (message.type === 'chat') { - if (message.userName === userName) { - return ( -
    - {message.message} -
    - ); - } else - return ( -
    -
    - {message.userName} -
    -
    - {message.message} -
    -
    - ); - } - return null; - }); - }; - - const containerRef = useRef(null); - - // Scroll to the bottom of the container whenever new messages are added - useEffect(() => { - if (containerRef.current) { - containerRef.current.scrollTop = containerRef.current.scrollHeight; - } - }, [messages]); - - return ( -
    -
    - - {userJoinCollabRoom && ( -
    -
    -
    - {renderMessages()} -
    -
    -
    - setInputContent(e.target.value)} - value={inputContent} - style={inputStyles} - /> - -
    -
    -
    -
    - )} -
    - {!userJoinCollabRoom && ( -
    -

    - Please join a collaboration room to enable this function -

    -
    - )} -
    - ); -}; -export default Chatroom; +import { useState, useRef, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { emitEvent } from '../../helperFunctions/socket'; +import Videomeeting from './VideoMeeting'; +import { Send } from '@mui/icons-material'; + +const Chatroom = (props): JSX.Element => { + const { userName, roomCode, messages, userJoinCollabRoom } = useSelector( + (store: RootState) => store.roomSlice + ); + + const [inputContent, setInputContent] = useState(''); + + const wrapperStyles = { + border: '1px solid #31343A', + borderRadius: '15px', + width: '70%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignSelf: 'center', + padding: '12px 20px', + backgroundColor: '#1B1B1B', + overflow: 'auto' + }; + + const inputContainerStyles = { + width: '100%', + paddingLeft: '20px', + paddingTop: '10px', + display: 'flex', + justifyContent: 'center' + }; + + const inputStyles = { + width: '72%', + padding: '10px 12px', + borderRadius: '50px', + backgroundColor: '#1B1B1B', + color: 'white', + border: '1px solid #31343A', + marginLeft: '28px' + }; + + const buttonStyles = { + padding: '5px 7px', + marginLeft: '10px', + backgroundColor: '#0671E3', + color: 'white', + border: 'none', + borderRadius: '50%', + cursor: 'pointer' + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (inputContent !== '') { + emitEvent('send-chat-message', roomCode, { + userName, + message: inputContent + }); + setInputContent(''); + } + }; + + const handleMessageContainerStyle = (message: object) => { + if (message['type'] === 'activity') { + return { + color: '#E8E9EB', + fontSize: '12px', + alignSelf: 'center', + margin: '3px 0' + }; + } else { + if (message['userName'] === userName) + return { + alignSelf: 'end', + padding: '8px 15px', + backgroundColor: '#0671E3', + borderRadius: '15.5px', + margin: '3px 0', + maxWidth: '300px' + }; + return { + color: 'white', + padding: '8px 15px', + backgroundColor: '#333333', + borderRadius: '15.5px', + margin: '3px 0', + maxWidth: '300px' + }; + } + }; + + const renderMessages = () => { + return messages.map((message, index) => { + if (message.type === 'activity') { + return ( +
    + {message.message} +
    + ); + } else if (message.type === 'chat') { + if (message.userName === userName) { + return ( +
    + {message.message} +
    + ); + } else + return ( +
    +
    + {message.userName} +
    +
    + {message.message} +
    +
    + ); + } + return null; + }); + }; + + const containerRef = useRef(null); + + // Scroll to the bottom of the container whenever new messages are added + useEffect(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }, [messages]); + + return ( +
    +
    + + {userJoinCollabRoom && ( +
    +
    +
    + {renderMessages()} +
    +
    +
    + setInputContent(e.target.value)} + value={inputContent} + style={inputStyles} + /> + +
    +
    +
    +
    + )} +
    + {!userJoinCollabRoom && ( +
    +

    + Please join a collaboration room to enable this function +

    +
    + )} +
    + ); +}; +export default Chatroom; diff --git a/app/src/components/bottom/CodePreview.tsx b/app/src/components/bottom/CodePreview.tsx index 74edf7200..07c223262 100644 --- a/app/src/components/bottom/CodePreview.tsx +++ b/app/src/components/bottom/CodePreview.tsx @@ -1,114 +1,114 @@ -import 'ace-builds/src-noconflict/ace'; -import 'ace-builds/src-min-noconflict/ext-searchbox'; -import 'ace-builds/src-noconflict/mode-javascript'; -import 'ace-builds/src-noconflict/theme-dracula'; -import 'ace-builds/src-noconflict/theme-clouds_midnight'; - -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { - codePreviewInput, - codePreviewSave -} from '../../redux/reducers/slice/codePreviewSlice'; -import { useDispatch, useSelector } from 'react-redux'; - -import AceEditor from 'react-ace'; -import { Component } from '../../interfaces/Interfaces'; -import { RootState } from '../../redux/store'; -import { fetchPlugin } from '../../plugins/fetch-plugin'; -import { unpkgPathPlugin } from '../../plugins/unpkg-path-plugin'; -import useResizeObserver from '../../tree/useResizeObserver'; -import { initializeEsbuild } from '../../helperFunctions/esbuildService'; - -const CodePreview: React.FC<{ - theme: string | null; - setTheme: any | null; - // zoom: number; // This is added if you want the Code Editor to zoom in/out - containerRef: any; -}> = ({ theme, setTheme, zoom, containerRef }) => { - const ref = useRef(); - - const dispatch = useDispatch(); - - const wrapper = useRef(); - const dimensions = useResizeObserver(wrapper); - const { height } = dimensions || 0; - const state = useSelector((store: RootState) => store.appState); - const [, setDivHeight] = useState(0); - let currentComponent = state.components.find( - (elem: Component) => elem.id === state.canvasFocus.componentId - ); - - const [input, setInput] = useState(''); - - useEffect(() => { - //Starts the Web Assembly service - initializeEsbuild(); - }, []); - - useEffect(() => { - setDivHeight(height); - }, [height]); - - useEffect(() => { - setInput(currentComponent.code); - dispatch(codePreviewInput(currentComponent.code)); - }, [currentComponent, state.components]); - - /** - * Handler thats listens to changes in code editor - * @param {string} data - Code entered by the user - */ - const handleChange = async (data) => { - setInput(data); - dispatch(codePreviewInput(data)); - if (!ref.current) { - return; - } - let result = await ref.current.build({ - entryPoints: ['index.js'], - bundle: true, - write: false, - incremental: true, - minify: true, - plugins: [unpkgPathPlugin(), fetchPlugin(data)], - define: { - 'import.meta.env.NODE_ENV': '"production"', - global: 'window' - } - }); - dispatch(codePreviewSave(result.outputFiles[0].text)); - }; - - return ( -
    -
    - -
    - ); -}; - -export default CodePreview; +import 'ace-builds/src-noconflict/ace'; +import 'ace-builds/src-min-noconflict/ext-searchbox'; +import 'ace-builds/src-noconflict/mode-javascript'; +import 'ace-builds/src-noconflict/theme-dracula'; +import 'ace-builds/src-noconflict/theme-clouds_midnight'; + +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { + codePreviewInput, + codePreviewSave +} from '../../redux/reducers/slice/codePreviewSlice'; +import { useDispatch, useSelector } from 'react-redux'; + +import AceEditor from 'react-ace'; +import { Component } from '../../interfaces/Interfaces'; +import { RootState } from '../../redux/store'; +import { fetchPlugin } from '../../plugins/fetch-plugin'; +import { unpkgPathPlugin } from '../../plugins/unpkg-path-plugin'; +import useResizeObserver from '../../tree/useResizeObserver'; +import { initializeEsbuild } from '../../helperFunctions/esbuildService'; + +const CodePreview: React.FC<{ + theme: string | null; + setTheme: any | null; + // zoom: number; // This is added if you want the Code Editor to zoom in/out + containerRef: any; +}> = ({ theme, setTheme, zoom, containerRef }) => { + const ref = useRef(); + + const dispatch = useDispatch(); + + const wrapper = useRef(); + const dimensions = useResizeObserver(wrapper); + const { height } = dimensions || 0; + const state = useSelector((store: RootState) => store.appState); + const [, setDivHeight] = useState(0); + let currentComponent = state.components.find( + (elem: Component) => elem.id === state.canvasFocus.componentId + ); + + const [input, setInput] = useState(''); + + useEffect(() => { + //Starts the Web Assembly service + initializeEsbuild(); + }, []); + + useEffect(() => { + setDivHeight(height); + }, [height]); + + useEffect(() => { + setInput(currentComponent.code); + dispatch(codePreviewInput(currentComponent.code)); + }, [currentComponent, state.components]); + + /** + * Handler thats listens to changes in code editor + * @param {string} data - Code entered by the user + */ + const handleChange = async (data) => { + setInput(data); + dispatch(codePreviewInput(data)); + if (!ref.current) { + return; + } + let result = await ref.current.build({ + entryPoints: ['index.js'], + bundle: true, + write: false, + incremental: true, + minify: true, + plugins: [unpkgPathPlugin(), fetchPlugin(data)], + define: { + 'import.meta.env.NODE_ENV': '"production"', + global: 'window' + } + }); + dispatch(codePreviewSave(result.outputFiles[0].text)); + }; + + return ( +
    +
    + +
    + ); +}; + +export default CodePreview; diff --git a/app/src/components/bottom/CreationPanel.tsx b/app/src/components/bottom/CreationPanel.tsx index d00616ada..65e8472bd 100644 --- a/app/src/components/bottom/CreationPanel.tsx +++ b/app/src/components/bottom/CreationPanel.tsx @@ -1,19 +1,19 @@ -import React from 'react'; -import ComponentPanel from '../right/ComponentPanel'; -import HTMLPanel from '../left/HTMLPanel'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; - -// Creation panel holds all of the creation functionality of the application. ComponentPanel, HTMLPanel, and StatePropsPanel are all hanged here. -// This allows users to create all aspects of this application in one place. -const CreationPanel = (props): JSX.Element => { - const style = useSelector((store: RootState) => store.styleSlice); - return ( -
    - - -
    - ); -}; - -export default CreationPanel; +import React from 'react'; +import ComponentPanel from '../right/ComponentPanel'; +import HTMLPanel from '../left/HTMLPanel'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; + +// Creation panel holds all of the creation functionality of the application. ComponentPanel, HTMLPanel, and StatePropsPanel are all hanged here. +// This allows users to create all aspects of this application in one place. +const CreationPanel = (props): JSX.Element => { + const style = useSelector((store: RootState) => store.styleSlice); + return ( +
    + + +
    + ); +}; + +export default CreationPanel; diff --git a/app/src/components/bottom/StylesEditor.tsx b/app/src/components/bottom/StylesEditor.tsx index 2de9d1a11..e46d062ed 100644 --- a/app/src/components/bottom/StylesEditor.tsx +++ b/app/src/components/bottom/StylesEditor.tsx @@ -1,84 +1,84 @@ -import 'ace-builds/src-noconflict/ace'; -import 'ace-builds/src-noconflict/mode-css'; -import 'ace-builds/src-noconflict/theme-dracula'; -import 'ace-builds/src-min-noconflict/ext-searchbox'; -import 'ace-builds/src-noconflict/ext-language_tools'; - -import React, { useRef, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import AceEditor from 'react-ace'; -import Fab from '@mui/material/Fab'; -import { RootState } from '../../redux/store'; -import SaveIcon from '@mui/icons-material/Save'; -import { updateStylesheet } from '../../redux/reducers/slice/appStateSlice'; -import { emitEvent } from '../../helperFunctions/socket'; - -const StylesEditor: React.FC<{ - theme: string | null; - setTheme: any | null; -}> = ({ theme, setTheme }) => { - const wrapper = useRef(); - const stylesheet = useSelector( - (state: RootState) => state.appState.stylesheet - ); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - //sets state for what text is currently in the csseditor - const [css, setCss] = useState(stylesheet); - - const dispatch = useDispatch(); - - //on save, updates the state based on above hook and rerenders the demo - const saveCss = (e) => { - e.preventDefault(); - dispatch(updateStylesheet(css)); - if (roomCode) { - emitEvent('updateCSSAction', roomCode, css); - } - }; - - //handles changes in the ace editor - const handleChange = (text) => { - setCss(text); - }; - - return ( -
    - - - - -
    - ); -}; - -export default StylesEditor; +import 'ace-builds/src-noconflict/ace'; +import 'ace-builds/src-noconflict/mode-css'; +import 'ace-builds/src-noconflict/theme-dracula'; +import 'ace-builds/src-min-noconflict/ext-searchbox'; +import 'ace-builds/src-noconflict/ext-language_tools'; + +import React, { useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import AceEditor from 'react-ace'; +import Fab from '@mui/material/Fab'; +import { RootState } from '../../redux/store'; +import SaveIcon from '@mui/icons-material/Save'; +import { updateStylesheet } from '../../redux/reducers/slice/appStateSlice'; +import { emitEvent } from '../../helperFunctions/socket'; + +const StylesEditor: React.FC<{ + theme: string | null; + setTheme: any | null; +}> = ({ theme, setTheme }) => { + const wrapper = useRef(); + const stylesheet = useSelector( + (state: RootState) => state.appState.stylesheet + ); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + //sets state for what text is currently in the csseditor + const [css, setCss] = useState(stylesheet); + + const dispatch = useDispatch(); + + //on save, updates the state based on above hook and rerenders the demo + const saveCss = (e) => { + e.preventDefault(); + dispatch(updateStylesheet(css)); + if (roomCode) { + emitEvent('updateCSSAction', roomCode, css); + } + }; + + //handles changes in the ace editor + const handleChange = (text) => { + setCss(text); + }; + + return ( +
    + + + + +
    + ); +}; + +export default StylesEditor; diff --git a/app/src/components/bottom/UseStateModal.tsx b/app/src/components/bottom/UseStateModal.tsx index adc7774d2..0a55704b1 100644 --- a/app/src/components/bottom/UseStateModal.tsx +++ b/app/src/components/bottom/UseStateModal.tsx @@ -1,63 +1,63 @@ -import React, { useState, useRef } from 'react'; -import Modal from '@mui/material/Modal'; -import TableStateProps from '../StateManagement/CreateTab/components/TableStateProps'; - -function UseStateModal({ updateAttributeWithState, attributeToChange }) { - const [open, setOpen] = useState(false); - const [stateKey, setStateKey] = useState(''); - const [statePropsId, setStatePropsId] = useState(-1); - const [componentProviderId, setComponentProviderId] = useState(1); - const container = useRef(null); - // table to choose state from - const body = ( -
    -
    - Choose State - -
    -
    -
    - { - updateAttributeWithState( - attributeToChange, - componentProviderId, - statePropsId > 0 ? statePropsId : table.row.id, - table.row, - stateKey + table.row.key - ); - setStateKey(''); - setStatePropsId(-1); - setOpen(false); - }} - isThemeLight={true} - /> -
    -
    -
    - ); - - return ( -
    - - - {body} - -
    - ); -} - -export default UseStateModal; +import React, { useState, useRef } from 'react'; +import Modal from '@mui/material/Modal'; +import TableStateProps from '../StateManagement/CreateTab/components/TableStateProps'; + +function UseStateModal({ updateAttributeWithState, attributeToChange }) { + const [open, setOpen] = useState(false); + const [stateKey, setStateKey] = useState(''); + const [statePropsId, setStatePropsId] = useState(-1); + const [componentProviderId, setComponentProviderId] = useState(1); + const container = useRef(null); + // table to choose state from + const body = ( +
    +
    + Choose State + +
    +
    +
    + { + updateAttributeWithState( + attributeToChange, + componentProviderId, + statePropsId > 0 ? statePropsId : table.row.id, + table.row, + stateKey + table.row.key + ); + setStateKey(''); + setStatePropsId(-1); + setOpen(false); + }} + isThemeLight={true} + /> +
    +
    +
    + ); + + return ( +
    + + + {body} + +
    + ); +} + +export default UseStateModal; diff --git a/app/src/components/bottom/VideoMeeting.tsx b/app/src/components/bottom/VideoMeeting.tsx index 097941f93..de61a861e 100644 --- a/app/src/components/bottom/VideoMeeting.tsx +++ b/app/src/components/bottom/VideoMeeting.tsx @@ -1,257 +1,257 @@ -import { useState, useRef, useEffect, useMemo } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '../../redux/store'; -import { - setUserJoinMeetingStatus, - setMeetingParticipants, - setUseMic, - setUseWebcam -} from '../../redux/reducers/slice/roomSlice'; -import { - MeetingConsumer, - useMeeting, - useParticipant -} from '@videosdk.live/react-sdk'; -import ReactPlayer from 'react-player'; -import Button from '@mui/material/Button'; -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; -import MicOffIcon from '@mui/icons-material/MicOff'; -import MicIcon from '@mui/icons-material/Mic'; -import VideocamIcon from '@mui/icons-material/Videocam'; -import VideocamOffIcon from '@mui/icons-material/VideocamOff'; -import VideoMeetingControl from './VideoMeetingControl'; - -const Videomeeting = (props): JSX.Element => { - const dispatch = useDispatch(); - const { - meetingId, - userJoinCollabRoom, - userJoinMeetingStatus, - meetingParticipants, - useMic, - useWebcam - } = useSelector((store: RootState) => store.roomSlice); - - const micRef = useRef(null); - - const TurnOffCameraDisplay = () => { - return ( -
    - -
    - ); - }; - - const onMeetingLeave = () => { - dispatch(setUserJoinMeetingStatus(null)); - dispatch(setUseWebcam(false)); - dispatch(setUseMic(false)); - }; - - const handleUserInfoStyle = (isLocalParticipant: boolean) => { - if (isLocalParticipant) return { color: '#0671E3', alignItems: 'center' }; - else return { color: 'white', alignItems: 'center' }; - }; - - const ParticipantView = ({ participantId, isLocalParticipant }) => { - const { webcamStream, micStream, webcamOn, micOn, isLocal, displayName } = - useParticipant(participantId); - - const videoStream = useMemo(() => { - if (webcamOn && webcamStream) { - const mediaStream = new MediaStream(); - mediaStream.addTrack(webcamStream.track); - return mediaStream; - } - }, [webcamStream, webcamOn]); - - useEffect(() => { - if (micRef.current) { - if (micOn && micStream) { - const mediaStream = new MediaStream(); - mediaStream.addTrack(micStream.track); - - micRef.current.srcObject = mediaStream; - - micRef.current - .play() - .catch((error) => - console.error('videoElem.current.play() failed', error) - ); - } else { - micRef.current.srcObject = null; - } - } - }, [micStream, micOn]); - - return ( - <> - {userJoinMeetingStatus === 'JOINED' && ( - <> -
    -
    - - )} - - ); - }; - - const MeetingView = ({ onMeetingLeave }) => { - const { join, localParticipant, leave } = useMeeting(); - - const { participants } = useMeeting({ - onMeetingJoined: () => { - dispatch(setUserJoinMeetingStatus('JOINED')); - }, - onMeetingLeft: () => { - onMeetingLeave(); - } - }); - - const meetingParticipantsId = [...participants.keys()]; - - if ( - JSON.stringify(meetingParticipantsId) !== - JSON.stringify(meetingParticipants) && - meetingParticipantsId.length > 0 - ) { - dispatch(setMeetingParticipants(meetingParticipantsId)); - } - - const joinMeeting = () => { - dispatch(setUserJoinMeetingStatus('JOINING')); - join(); - }; - - if (!userJoinCollabRoom && userJoinMeetingStatus !== null) { - leave(); - onMeetingLeave(); - dispatch(setUserJoinMeetingStatus(null)); - } - - return ( -
    -
    - -
    - {[...meetingParticipantsId].map((participantId, idx) => ( - - ))} -
    -
    - {userJoinMeetingStatus === 'JOINING' &&

    Joining the meeting...

    } - {userJoinCollabRoom && userJoinMeetingStatus === null && ( - - )} -
    - ); - }; - - return ( - meetingId && ( - - {() => } - - ) - ); -}; - -export default Videomeeting; +import { useState, useRef, useEffect, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { + setUserJoinMeetingStatus, + setMeetingParticipants, + setUseMic, + setUseWebcam +} from '../../redux/reducers/slice/roomSlice'; +import { + MeetingConsumer, + useMeeting, + useParticipant +} from '@videosdk.live/react-sdk'; +import ReactPlayer from 'react-player'; +import Button from '@mui/material/Button'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import MicOffIcon from '@mui/icons-material/MicOff'; +import MicIcon from '@mui/icons-material/Mic'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import VideocamOffIcon from '@mui/icons-material/VideocamOff'; +import VideoMeetingControl from './VideoMeetingControl'; + +const Videomeeting = (props): JSX.Element => { + const dispatch = useDispatch(); + const { + meetingId, + userJoinCollabRoom, + userJoinMeetingStatus, + meetingParticipants, + useMic, + useWebcam + } = useSelector((store: RootState) => store.roomSlice); + + const micRef = useRef(null); + + const TurnOffCameraDisplay = () => { + return ( +
    + +
    + ); + }; + + const onMeetingLeave = () => { + dispatch(setUserJoinMeetingStatus(null)); + dispatch(setUseWebcam(false)); + dispatch(setUseMic(false)); + }; + + const handleUserInfoStyle = (isLocalParticipant: boolean) => { + if (isLocalParticipant) return { color: '#0671E3', alignItems: 'center' }; + else return { color: 'white', alignItems: 'center' }; + }; + + const ParticipantView = ({ participantId, isLocalParticipant }) => { + const { webcamStream, micStream, webcamOn, micOn, isLocal, displayName } = + useParticipant(participantId); + + const videoStream = useMemo(() => { + if (webcamOn && webcamStream) { + const mediaStream = new MediaStream(); + mediaStream.addTrack(webcamStream.track); + return mediaStream; + } + }, [webcamStream, webcamOn]); + + useEffect(() => { + if (micRef.current) { + if (micOn && micStream) { + const mediaStream = new MediaStream(); + mediaStream.addTrack(micStream.track); + + micRef.current.srcObject = mediaStream; + + micRef.current + .play() + .catch((error) => + console.error('videoElem.current.play() failed', error) + ); + } else { + micRef.current.srcObject = null; + } + } + }, [micStream, micOn]); + + return ( + <> + {userJoinMeetingStatus === 'JOINED' && ( + <> +
    +
    + + )} + + ); + }; + + const MeetingView = ({ onMeetingLeave }) => { + const { join, localParticipant, leave } = useMeeting(); + + const { participants } = useMeeting({ + onMeetingJoined: () => { + dispatch(setUserJoinMeetingStatus('JOINED')); + }, + onMeetingLeft: () => { + onMeetingLeave(); + } + }); + + const meetingParticipantsId = [...participants.keys()]; + + if ( + JSON.stringify(meetingParticipantsId) !== + JSON.stringify(meetingParticipants) && + meetingParticipantsId.length > 0 + ) { + dispatch(setMeetingParticipants(meetingParticipantsId)); + } + + const joinMeeting = () => { + dispatch(setUserJoinMeetingStatus('JOINING')); + join(); + }; + + if (!userJoinCollabRoom && userJoinMeetingStatus !== null) { + leave(); + onMeetingLeave(); + dispatch(setUserJoinMeetingStatus(null)); + } + + return ( +
    +
    + +
    + {[...meetingParticipantsId].map((participantId, idx) => ( + + ))} +
    +
    + {userJoinMeetingStatus === 'JOINING' &&

    Joining the meeting...

    } + {userJoinCollabRoom && userJoinMeetingStatus === null && ( + + )} +
    + ); + }; + + return ( + meetingId && ( + + {() => } + + ) + ); +}; + +export default Videomeeting; diff --git a/app/src/components/bottom/VideoMeetingControl.tsx b/app/src/components/bottom/VideoMeetingControl.tsx index b8de16245..24f046eeb 100644 --- a/app/src/components/bottom/VideoMeetingControl.tsx +++ b/app/src/components/bottom/VideoMeetingControl.tsx @@ -1,176 +1,176 @@ -import React, { useState, useCallback } from 'react'; -import { useMeeting } from '@videosdk.live/react-sdk'; -import { useSelector, useDispatch } from 'react-redux'; -import CallEndIcon from '@mui/icons-material/CallEnd'; -import MicOffIcon from '@mui/icons-material/MicOff'; -import MicIcon from '@mui/icons-material/Mic'; -import VideocamIcon from '@mui/icons-material/Videocam'; -import VideocamOffIcon from '@mui/icons-material/VideocamOff'; - -import { setUseMic, setUseWebcam } from '../../redux/reducers/slice/roomSlice'; -import { RootState } from '../../redux/store'; - -interface VideoMeetingControlProps { - userJoinMeetingStatus: string; - useWebcam: boolean; - useMic: boolean; -} - -enum ButtonType { - CALL_END = 'Call End', - MIC = 'Mic', - WEBCAM = 'Webcam' -} - -const VideoMeetingControl: React.FC = () => { - const { leave, toggleMic, toggleWebcam } = useMeeting(); - - const [callEndHovered, setCallEndHovered] = useState(false); - const [micHovered, setMicHovered] = useState(false); - const [webcamHovered, setWebcamHovered] = useState(false); - - const dispatch = useDispatch(); - const { userJoinMeetingStatus, useMic, useWebcam } = useSelector( - (store: RootState) => store.roomSlice - ); - - const handleButtonHover = useCallback((button: string, hovered: boolean) => { - switch (button) { - case ButtonType.CALL_END: - setCallEndHovered(hovered); - break; - case ButtonType.MIC: - setMicHovered(hovered); - break; - default: - setWebcamHovered(hovered); - } - }, []); - - return ( - userJoinMeetingStatus === 'JOINED' && ( -
    - {/* Mic Button */} -
    handleButtonHover(ButtonType.MIC, true)} - onMouseLeave={() => handleButtonHover(ButtonType.MIC, false)} - > - -
    - {/* Webcam Button */} -
    handleButtonHover(ButtonType.WEBCAM, true)} - onMouseLeave={() => handleButtonHover(ButtonType.WEBCAM, false)} - > - -
    - {/* Call End Button */} -
    handleButtonHover(ButtonType.CALL_END, true)} - onMouseLeave={() => handleButtonHover(ButtonType.CALL_END, false)} - > - -
    -
    - ) - ); -}; - -export default VideoMeetingControl; +import React, { useState, useCallback } from 'react'; +import { useMeeting } from '@videosdk.live/react-sdk'; +import { useSelector, useDispatch } from 'react-redux'; +import CallEndIcon from '@mui/icons-material/CallEnd'; +import MicOffIcon from '@mui/icons-material/MicOff'; +import MicIcon from '@mui/icons-material/Mic'; +import VideocamIcon from '@mui/icons-material/Videocam'; +import VideocamOffIcon from '@mui/icons-material/VideocamOff'; + +import { setUseMic, setUseWebcam } from '../../redux/reducers/slice/roomSlice'; +import { RootState } from '../../redux/store'; + +interface VideoMeetingControlProps { + userJoinMeetingStatus: string; + useWebcam: boolean; + useMic: boolean; +} + +enum ButtonType { + CALL_END = 'Call End', + MIC = 'Mic', + WEBCAM = 'Webcam' +} + +const VideoMeetingControl: React.FC = () => { + const { leave, toggleMic, toggleWebcam } = useMeeting(); + + const [callEndHovered, setCallEndHovered] = useState(false); + const [micHovered, setMicHovered] = useState(false); + const [webcamHovered, setWebcamHovered] = useState(false); + + const dispatch = useDispatch(); + const { userJoinMeetingStatus, useMic, useWebcam } = useSelector( + (store: RootState) => store.roomSlice + ); + + const handleButtonHover = useCallback((button: string, hovered: boolean) => { + switch (button) { + case ButtonType.CALL_END: + setCallEndHovered(hovered); + break; + case ButtonType.MIC: + setMicHovered(hovered); + break; + default: + setWebcamHovered(hovered); + } + }, []); + + return ( + userJoinMeetingStatus === 'JOINED' && ( +
    + {/* Mic Button */} +
    handleButtonHover(ButtonType.MIC, true)} + onMouseLeave={() => handleButtonHover(ButtonType.MIC, false)} + > + +
    + {/* Webcam Button */} +
    handleButtonHover(ButtonType.WEBCAM, true)} + onMouseLeave={() => handleButtonHover(ButtonType.WEBCAM, false)} + > + +
    + {/* Call End Button */} +
    handleButtonHover(ButtonType.CALL_END, true)} + onMouseLeave={() => handleButtonHover(ButtonType.CALL_END, false)} + > + +
    +
    + ) + ); +}; + +export default VideoMeetingControl; diff --git a/app/src/components/form/Selector.tsx b/app/src/components/form/Selector.tsx index f82d18267..cfc650985 100644 --- a/app/src/components/form/Selector.tsx +++ b/app/src/components/form/Selector.tsx @@ -1,70 +1,70 @@ -import React from 'react'; -import FormControl from '@mui/material/FormControl'; -import Select from '@mui/material/Select'; -import MenuItem from '@mui/material/MenuItem'; - -type Props = { - items: []; - classes: { - configRow: any; - configType: any; - lightThemeFontColor: { color: String }; - formControl: any; - select: any; - selectInput: any; - darkThemeFontColor: { color: String }; - }; - isThemeLight: Boolean; - title: String; - selectValue: any; - handleChange: any; - name: String; -}; - -const FormSelector = (props): JSX.Element => { - const items = []; - let key = 1; - props.items.forEach((el) => { - items.push( - - {el.text} - - ); - key++; - }); - return ( -
    -
    -

    {props.title}

    -
    -
    - - - -
    -
    - ); -}; - -export default FormSelector; +import React from 'react'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; + +type Props = { + items: []; + classes: { + configRow: any; + configType: any; + lightThemeFontColor: { color: String }; + formControl: any; + select: any; + selectInput: any; + darkThemeFontColor: { color: String }; + }; + isThemeLight: Boolean; + title: String; + selectValue: any; + handleChange: any; + name: String; +}; + +const FormSelector = (props): JSX.Element => { + const items = []; + let key = 1; + props.items.forEach((el) => { + items.push( + + {el.text} + + ); + key++; + }); + return ( +
    +
    +

    {props.title}

    +
    +
    + + + +
    +
    + ); +}; + +export default FormSelector; diff --git a/app/src/components/left/ContentArea.tsx b/app/src/components/left/ContentArea.tsx index 8590dfac7..4f4538fba 100644 --- a/app/src/components/left/ContentArea.tsx +++ b/app/src/components/left/ContentArea.tsx @@ -1,49 +1,49 @@ -import { Box } from '@mui/material'; -import ComponentsContainer from './ComponentsContainer'; -import ElementsContainer from './ElementsContainer'; -import React from 'react'; -import RoomsContainer from './RoomsContainer'; -import ProfilePage from './ProfilePage'; -import Settings from './Settings'; - -interface ContentAreaProps { - activeTab: number | null; - isVisible: boolean; -} - -const TabPanel: React.FC<{ - children: React.ReactNode; - activeTab: number | null; - index: number; -}> = ({ children, activeTab, index }) => { - return ( - - ); -}; - -const panels = [ - , - , - , - , - -]; - -const ContentArea: React.FC = ({ activeTab, isVisible }) => { - return ( -
    -
    - {panels.map((panel, idx) => ( - - {panel} - - ))} -
    -
    - ); -}; - -export default ContentArea; +import { Box } from '@mui/material'; +import ComponentsContainer from './ComponentsContainer'; +import ElementsContainer from './ElementsContainer'; +import React from 'react'; +import RoomsContainer from './RoomsContainer'; +import ProfilePage from './ProfilePage'; +import Settings from './Settings'; + +interface ContentAreaProps { + activeTab: number | null; + isVisible: boolean; +} + +const TabPanel: React.FC<{ + children: React.ReactNode; + activeTab: number | null; + index: number; +}> = ({ children, activeTab, index }) => { + return ( + + ); +}; + +const panels = [ + , + , + , + , + +]; + +const ContentArea: React.FC = ({ activeTab, isVisible }) => { + return ( +
    +
    + {panels.map((panel, idx) => ( + + {panel} + + ))} +
    +
    + ); +}; + +export default ContentArea; diff --git a/app/src/components/left/ElementsContainer.tsx b/app/src/components/left/ElementsContainer.tsx index b1fb2a433..8680937c6 100644 --- a/app/src/components/left/ElementsContainer.tsx +++ b/app/src/components/left/ElementsContainer.tsx @@ -55,10 +55,12 @@ const ElementsContainer = (props): JSX.Element => { > {' '} - { // moved ComponentDrag to DragDropPanel - /*
    + { + // moved ComponentDrag to DragDropPanel + /*
    -
    */} +
    */ + } ); }; diff --git a/app/src/components/left/HTMLPanel.tsx b/app/src/components/left/HTMLPanel.tsx index f1389214a..611d1abf1 100644 --- a/app/src/components/left/HTMLPanel.tsx +++ b/app/src/components/left/HTMLPanel.tsx @@ -1,375 +1,375 @@ -import { Button, InputLabel } from '@mui/material'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { RootState } from '../../redux/store'; -import TextField from '@mui/material/TextField'; -import { addElement } from '../../redux/reducers/slice/appStateSlice'; -import makeStyles from '@mui/styles/makeStyles'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; -import { emitEvent } from '../../helperFunctions/socket'; - -/* -DESCRIPTION: This is the bottom half of the left panel, starting from the 'HTML - Elements' header. The boxes containing each HTML element are rendered in - HTMLItem, which itself is rendered by this component. - - !!! TO NAME HTML ELEMENTS in the LEFT panel !!! - -Central state contains all available HTML elements (stored in the HTMLTypes property). - The data for HTMLTypes is stored in HTMLTypes.tsx and is added to central state in - initialState.tsx. - -Hook state: - -tag: -*/ - -const HTMLPanel = (props): JSX.Element => { - const classes = useStyles(); - const [tag, setTag] = useState(''); - const [name, setName] = useState(''); - const [errorMsg, setErrorMsg] = useState(''); - const [errorStatus, setErrorStatus] = useState(false); - const [alertOpen, setAlertOpen] = React.useState(false); - const state = useSelector((store: RootState) => store.appState); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - const currentID = useSelector( - (store: RootState) => store.appState.customElementId - ); - - const dispatch = useDispatch(); - - const handleTagChange = (e: React.ChangeEvent) => { - resetError(); - setTag(e.target.value); - }; - - const handleNameChange = (e: React.ChangeEvent) => { - resetError(); - setName(e.target.value); - }; - - const checkNameDupe = (inputName: String): boolean => { - let checkList = state.HTMLTypes.slice(); - - // checks to see if inputted comp name already exists - let dupe = false; - checkList.forEach((HTMLTag) => { - if ( - HTMLTag.name.toLowerCase() === inputName.toLowerCase() || - HTMLTag.tag.toLowerCase() === inputName.toLowerCase() - ) { - dupe = true; - } - }); - return dupe; - }; - - const triggerError = (type: String) => { - setErrorStatus(true); - if (type === 'empty') { - setErrorMsg('* Input cannot be blank. *'); - } else if (type === 'dupe') { - setErrorMsg('* Input already exists. *'); - } else if (type === 'letters') { - setErrorMsg('* Input must start with a letter. *'); - } else if (type === 'symbolsDetected') { - setErrorMsg('* Input must not contain symbols. *'); - } else if (type === 'length') { - setErrorMsg('* Input cannot exceed 10 characters. *'); - } - }; - - const resetError = () => { - setErrorStatus(false); - }; - - const createOption = (inputTag: String, inputName: String) => { - // format name so first letter is capitalized and there are no whitespaces - let inputNameClean = inputName.replace(/\s+/g, ''); - const formattedName = - inputNameClean.charAt(0).toUpperCase() + inputNameClean.slice(1); - // add new component to state - const newElement = { - id: currentID, - tag: inputTag, - name: formattedName, - style: {}, - placeHolderShort: name, - placeHolderLong: '', - icon: null - }; - - dispatch(addElement(newElement)); - - if (roomCode) { - emitEvent('addElementAction', roomCode, newElement); - } - - // setCurrentID(currentID + 1); - setTag(''); - setName(''); - }; - - const alphanumeric = (input: string): boolean => { - let letterNumber = /^[0-9a-zA-Z]+$/; - if (input.match(letterNumber)) return true; - return false; - }; - - const handleSubmit = (e) => { - e.preventDefault(); - - if (tag.trim() === '' || name.trim() === '') { - triggerError('empty'); - return; - } else if ( - !tag.charAt(0).match(/[a-zA-Z]/) || - !name.charAt(0).match(/[a-zA-Z]/) - ) { - triggerError('letters'); - return; - } else if (!alphanumeric(tag) || !alphanumeric(name)) { - triggerError('symbolsDetected'); - return; - } else if (checkNameDupe(tag) || checkNameDupe(name)) { - triggerError('dupe'); - return; - } else if (name.length > 10) { - triggerError('length'); - return; - } - createOption(tag, name); - resetError(); - }; - - const handleCreateElement = useCallback((e) => { - if ( - e.key === 'Enter' && - e.target.tagName !== 'TEXTAREA' && - e.target.id !== 'filled-hidden-label-small' - ) { - e.preventDefault(); - document.getElementById('submitButton').click(); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', handleCreateElement); - return () => { - document.removeEventListener('keydown', handleCreateElement); - }; - }, []); - - const handleAlertOpen = () => { - setAlertOpen(true); - }; - - const handleAlertClose = ( - event: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === 'clickaway') { - return; - } - setAlertOpen(false); - }; - - const Alert = React.forwardRef(function Alert( - props, - ref - ) { - return ; - }); - - return ( - <> -
    -
    -
    -
    -

    New HTML Tag

    - - Tag - - - - {(!tag.charAt(0).match(/[A-Za-z]/) || - !alphanumeric(tag) || - tag.trim() === '' || - checkNameDupe(tag)) && ( - - {errorMsg} - - )} - -

    - - Element Name - - - {(!name.charAt(0).match(/[A-Za-z]/) || - !alphanumeric(name) || - name.trim() === '' || - name.length > 10 || - checkNameDupe(name)) && ( - - {errorMsg} - - )} -

    - - -
    -
    -
    - <> - - - HTML Tag Created! - - - - - ); -}; - -const useStyles = makeStyles({ - inputField: { - marginTop: '10px', - borderRadius: '5px', - whiteSpace: 'nowrap', - overflowX: 'hidden', - textOverflow: 'ellipsis', - backgroundColor: 'rgba(255,255,255,0.15)', - margin: '0px 0px 0px 10px', - width: '140px', - height: '30px' - }, - inputWrapper: { - textAlign: 'center', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-evenly', - marginBottom: '15px', - width: '100%' - }, - addComponentWrapper: { - width: '100%' - }, - input: { - width: '500px', - whiteSpace: 'nowrap', - overflowX: 'hidden', - textOverflow: 'ellipsis', - margin: '0px 0px 0px 0px', - alignSelf: 'center' - }, - inputLabel: { - fontSize: '1em', - marginLeft: '10px' - }, - addElementButton: { - height: '50px', - width: '150px', - fontFamily: 'Roboto, Raleway, sans-serif', - fontSize: '15.5px', - textAlign: 'center', - transition: '0.3s', - borderRadius: '10px', - alignSelf: 'end', - border: '1px solid #0671E3' - }, - lightThemeFontColor: { - color: 'white', - '& .MuiInputBase-root': { - color: 'rgba (0, 0, 0, 0.54)' - } - }, - darkThemeFontColor: { - color: '#ffffff', - '& .MuiInputBase-root': { - color: '#fff' - } - }, - errorMessage: { - display: 'flex', - alignSelf: 'center', - fontSize: '11px', - marginTop: '10px', - width: '150px' - }, - errorMessageLight: { - color: '#6B6B6B' - }, - errorMessageDark: { - color: 'white' - } -}); - -export default HTMLPanel; +import { Button, InputLabel } from '@mui/material'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { RootState } from '../../redux/store'; +import TextField from '@mui/material/TextField'; +import { addElement } from '../../redux/reducers/slice/appStateSlice'; +import makeStyles from '@mui/styles/makeStyles'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import { emitEvent } from '../../helperFunctions/socket'; + +/* +DESCRIPTION: This is the bottom half of the left panel, starting from the 'HTML + Elements' header. The boxes containing each HTML element are rendered in + HTMLItem, which itself is rendered by this component. + + !!! TO NAME HTML ELEMENTS in the LEFT panel !!! + +Central state contains all available HTML elements (stored in the HTMLTypes property). + The data for HTMLTypes is stored in HTMLTypes.tsx and is added to central state in + initialState.tsx. + +Hook state: + -tag: +*/ + +const HTMLPanel = (props): JSX.Element => { + const classes = useStyles(); + const [tag, setTag] = useState(''); + const [name, setName] = useState(''); + const [errorMsg, setErrorMsg] = useState(''); + const [errorStatus, setErrorStatus] = useState(false); + const [alertOpen, setAlertOpen] = React.useState(false); + const state = useSelector((store: RootState) => store.appState); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + const currentID = useSelector( + (store: RootState) => store.appState.customElementId + ); + + const dispatch = useDispatch(); + + const handleTagChange = (e: React.ChangeEvent) => { + resetError(); + setTag(e.target.value); + }; + + const handleNameChange = (e: React.ChangeEvent) => { + resetError(); + setName(e.target.value); + }; + + const checkNameDupe = (inputName: String): boolean => { + let checkList = state.HTMLTypes.slice(); + + // checks to see if inputted comp name already exists + let dupe = false; + checkList.forEach((HTMLTag) => { + if ( + HTMLTag.name.toLowerCase() === inputName.toLowerCase() || + HTMLTag.tag.toLowerCase() === inputName.toLowerCase() + ) { + dupe = true; + } + }); + return dupe; + }; + + const triggerError = (type: String) => { + setErrorStatus(true); + if (type === 'empty') { + setErrorMsg('* Input cannot be blank. *'); + } else if (type === 'dupe') { + setErrorMsg('* Input already exists. *'); + } else if (type === 'letters') { + setErrorMsg('* Input must start with a letter. *'); + } else if (type === 'symbolsDetected') { + setErrorMsg('* Input must not contain symbols. *'); + } else if (type === 'length') { + setErrorMsg('* Input cannot exceed 10 characters. *'); + } + }; + + const resetError = () => { + setErrorStatus(false); + }; + + const createOption = (inputTag: String, inputName: String) => { + // format name so first letter is capitalized and there are no whitespaces + let inputNameClean = inputName.replace(/\s+/g, ''); + const formattedName = + inputNameClean.charAt(0).toUpperCase() + inputNameClean.slice(1); + // add new component to state + const newElement = { + id: currentID, + tag: inputTag, + name: formattedName, + style: {}, + placeHolderShort: name, + placeHolderLong: '', + icon: null + }; + + dispatch(addElement(newElement)); + + if (roomCode) { + emitEvent('addElementAction', roomCode, newElement); + } + + // setCurrentID(currentID + 1); + setTag(''); + setName(''); + }; + + const alphanumeric = (input: string): boolean => { + let letterNumber = /^[0-9a-zA-Z]+$/; + if (input.match(letterNumber)) return true; + return false; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + if (tag.trim() === '' || name.trim() === '') { + triggerError('empty'); + return; + } else if ( + !tag.charAt(0).match(/[a-zA-Z]/) || + !name.charAt(0).match(/[a-zA-Z]/) + ) { + triggerError('letters'); + return; + } else if (!alphanumeric(tag) || !alphanumeric(name)) { + triggerError('symbolsDetected'); + return; + } else if (checkNameDupe(tag) || checkNameDupe(name)) { + triggerError('dupe'); + return; + } else if (name.length > 10) { + triggerError('length'); + return; + } + createOption(tag, name); + resetError(); + }; + + const handleCreateElement = useCallback((e) => { + if ( + e.key === 'Enter' && + e.target.tagName !== 'TEXTAREA' && + e.target.id !== 'filled-hidden-label-small' + ) { + e.preventDefault(); + document.getElementById('submitButton').click(); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', handleCreateElement); + return () => { + document.removeEventListener('keydown', handleCreateElement); + }; + }, []); + + const handleAlertOpen = () => { + setAlertOpen(true); + }; + + const handleAlertClose = ( + event: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === 'clickaway') { + return; + } + setAlertOpen(false); + }; + + const Alert = React.forwardRef(function Alert( + props, + ref + ) { + return ; + }); + + return ( + <> +
    +
    +
    +
    +

    New HTML Tag

    + + Tag + + + + {(!tag.charAt(0).match(/[A-Za-z]/) || + !alphanumeric(tag) || + tag.trim() === '' || + checkNameDupe(tag)) && ( + + {errorMsg} + + )} + +

    + + Element Name + + + {(!name.charAt(0).match(/[A-Za-z]/) || + !alphanumeric(name) || + name.trim() === '' || + name.length > 10 || + checkNameDupe(name)) && ( + + {errorMsg} + + )} +

    + + +
    +
    +
    + <> + + + HTML Tag Created! + + + + + ); +}; + +const useStyles = makeStyles({ + inputField: { + marginTop: '10px', + borderRadius: '5px', + whiteSpace: 'nowrap', + overflowX: 'hidden', + textOverflow: 'ellipsis', + backgroundColor: 'rgba(255,255,255,0.15)', + margin: '0px 0px 0px 10px', + width: '140px', + height: '30px' + }, + inputWrapper: { + textAlign: 'center', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-evenly', + marginBottom: '15px', + width: '100%' + }, + addComponentWrapper: { + width: '100%' + }, + input: { + width: '500px', + whiteSpace: 'nowrap', + overflowX: 'hidden', + textOverflow: 'ellipsis', + margin: '0px 0px 0px 0px', + alignSelf: 'center' + }, + inputLabel: { + fontSize: '1em', + marginLeft: '10px' + }, + addElementButton: { + height: '50px', + width: '150px', + fontFamily: 'Roboto, Raleway, sans-serif', + fontSize: '15.5px', + textAlign: 'center', + transition: '0.3s', + borderRadius: '10px', + alignSelf: 'end', + border: '1px solid #0671E3' + }, + lightThemeFontColor: { + color: 'white', + '& .MuiInputBase-root': { + color: 'rgba (0, 0, 0, 0.54)' + } + }, + darkThemeFontColor: { + color: '#ffffff', + '& .MuiInputBase-root': { + color: '#fff' + } + }, + errorMessage: { + display: 'flex', + alignSelf: 'center', + fontSize: '11px', + marginTop: '10px', + width: '150px' + }, + errorMessageLight: { + color: '#6B6B6B' + }, + errorMessageDark: { + color: 'white' + } +}); + +export default HTMLPanel; diff --git a/app/src/components/left/ProfilePage.tsx b/app/src/components/left/ProfilePage.tsx index 173ca0e2f..013c89a23 100644 --- a/app/src/components/left/ProfilePage.tsx +++ b/app/src/components/left/ProfilePage.tsx @@ -1,161 +1,161 @@ -import React, { useState, useEffect } from 'react'; -import { RootState } from '../../redux/store'; -import makeStyles from '@mui/styles/makeStyles'; -import { useSelector } from 'react-redux'; -import { - Box, - Card, - CardActions, - CardContent, - Button, - Typography, - Divider -} from '@mui/material'; - -const bull = ( - - • - -); - -const ProfilePage = () => { - const classes = useStyles(); - const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - - useEffect(() => { - const storedUsername = window.localStorage.getItem('username'); - const storedEmail = window.localStorage.getItem('email'); - console.log(localStorage); - - if (storedUsername) { - setUsername(storedUsername); - } - - if (storedUsername) { - setEmail(storedEmail); - } - }, []); - - return ( - - - - Hello, - - - {username ? username : 'Guest'} - - - Welcome to Reactype! - - {email ? ( - - Email: -
    - {email} -
    - ) : null} -
    - - - - - - Apps - - - Create a web app or a native mobile app to build a custom internal - tool for your business. - - - - - - - - Resources - - - Securely connect your data and display it inside of Reactype apps. - - - - - - - - - - - Database - - - Easily store data in a free SQL database and power your Reactype app. - - - - - - - -
    - ); -}; - -const useStyles = makeStyles({ - panelWrapper: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - flexGrow: 1, - overflow: 'auto' - }, - panelWrapperList: { - minHeight: '120px' - }, - lightThemeFontColor: { - color: '#fff' - }, - darkThemeFontColor: { - color: '#fff' - } -}); - -export default ProfilePage; +import React, { useState, useEffect } from 'react'; +import { RootState } from '../../redux/store'; +import makeStyles from '@mui/styles/makeStyles'; +import { useSelector } from 'react-redux'; +import { + Box, + Card, + CardActions, + CardContent, + Button, + Typography, + Divider +} from '@mui/material'; + +const bull = ( + + • + +); + +const ProfilePage = () => { + const classes = useStyles(); + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + + useEffect(() => { + const storedUsername = window.localStorage.getItem('username'); + const storedEmail = window.localStorage.getItem('email'); + console.log(localStorage); + + if (storedUsername) { + setUsername(storedUsername); + } + + if (storedUsername) { + setEmail(storedEmail); + } + }, []); + + return ( + + + + Hello, + + + {username ? username : 'Guest'} + + + Welcome to Reactype! + + {email ? ( + + Email: +
    + {email} +
    + ) : null} +
    + + + + + + Apps + + + Create a web app or a native mobile app to build a custom internal + tool for your business. + + + + + + + + Resources + + + Securely connect your data and display it inside of Reactype apps. + + + + + + + + + + + Database + + + Easily store data in a free SQL database and power your Reactype app. + + + + + + + +
    + ); +}; + +const useStyles = makeStyles({ + panelWrapper: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexGrow: 1, + overflow: 'auto' + }, + panelWrapperList: { + minHeight: '120px' + }, + lightThemeFontColor: { + color: '#fff' + }, + darkThemeFontColor: { + color: '#fff' + } +}); + +export default ProfilePage; diff --git a/app/src/components/left/RoomsContainer.tsx b/app/src/components/left/RoomsContainer.tsx index 6a562fd8f..944acceba 100644 --- a/app/src/components/left/RoomsContainer.tsx +++ b/app/src/components/left/RoomsContainer.tsx @@ -1,595 +1,595 @@ -import { useState } from 'react'; -import { Stack, Typography } from '@mui/material'; -import { useDispatch, useSelector } from 'react-redux'; -import Box from '@mui/material/Box'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import Button from '@mui/material/Button'; -import { RootState } from '../../redux/store'; -import TextField from '@mui/material/TextField'; -import { BottomPanelObj } from '../../interfaces/Interfaces'; -import { - allCooperativeState, - addChild, - changeFocus, - deleteChild, - changePosition, - resetState, - updateStateUsed, - updateUseContext, - updateCss, - updateAttributes, - updateEvents, - addComponent, - addElement, - addState, - deleteState, - addPassedInProps, - deletePassedInProps, - deleteElement, - resetAllState, - updateStylesheet -} from '../../redux/reducers/slice/appStateSlice'; -import { - addContext, - deleteContext, - addContextValues -} from '../../redux/reducers/slice/contextReducer'; -import { - setRoomCode, - setUserName, - setUserJoinCollabRoom, - setUserList, - setMeetingId, - setMessages, - setEmptyMessages, - setPassword, - setUseMic, - setUseWebcam -} from '../../redux/reducers/slice/roomSlice'; -import { codePreviewCooperative } from '../../redux/reducers/slice/codePreviewSlice'; -import { cooperativeStyle } from '../../redux/reducers/slice/styleSlice'; -import store from '../../redux/store'; -import { initializeSocket, getSocket } from '../../helperFunctions/socket'; -import { - AddContextPayload, - AddContextValuesPayload, - DeleteContextPayload, - addComponentToContext -} from '../../../src/redux/reducers/slice/contextReducer'; - -const RoomsContainer = () => { - const [isJoinCallabRoom, setIsJoinCollabRoom] = useState(false); - const [joinedPasswordAttempt, setJoinedPasswordAttempt] = useState(''); - const [isPasswordAttemptIncorrect, setIsPasswordAttemptIncorrect] = - useState(true); - const [isCollabRoomTaken, setIsCollabRoomTaken] = useState(false); - const [isRoomAvailable, setIsRoomAvailable] = useState(true); - - const dispatch = useDispatch(); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - const userName = useSelector((store: RootState) => store.roomSlice.userName); - const userList = useSelector((store: RootState) => store.roomSlice.userList); - const roomPassword = useSelector( - (store: RootState) => store.roomSlice.password - ); - - - const userJoinCollabRoom = useSelector( - (store: RootState) => store.roomSlice.userJoinCollabRoom - ); - - const messages = useSelector((store: RootState) => store.roomSlice.messages); - - const initSocketConnection = ( - roomCode: string, - roomPassword: string, - method: string - ) => { - // helper function to create socket connection - initializeSocket(); - // assign socket to result of helper function to return socket created - const socket = getSocket(); - // if socket was created correctly and exists - if (socket) { - //run everytime when a client connects to server - socket.on('connect', () => { - socket.emit( - 'creating a room', - userName, - roomCode, - roomPassword, - method - ); - }); - - socket.on('wrong password', () => { - setIsPasswordAttemptIncorrect(false); - }); - - socket.on('correct password', () => { - setIsPasswordAttemptIncorrect(true); - addNewUserToCollabRoom(); - }); - - socket.on('user created a new room', () => { - addNewUserToCollabRoom(); - }); - - socket.on('room is already taken', () => { - setIsCollabRoomTaken(true); - }); - - socket.on('room does not exist', () => { - setIsRoomAvailable(false); - - }); - //If you are the host: send current state to server when a new user joins - socket.on('requesting state from host', (callback) => { - const newState = store.getState(); //pull the current state - callback(newState); //send it to backend server - }); - - //If you are the new user: receive the state from the host - socket.on('server emitting state from host', (state, callback) => { - //dispatching new state to change user current state - // console.log('state received by new join:', state); - store.dispatch(allCooperativeState(state.appState)); - store.dispatch(codePreviewCooperative(state.codePreviewCooperative)); - store.dispatch(cooperativeStyle(state.styleSlice)); - callback({ status: 'confirmed' }); - }); - - // update user list when there's a change: new join or leave the room - socket.on('update room information', (messageData) => { - //console.log('user list received from server'); - if (messageData.userList) dispatch(setUserList(messageData.userList)); - if (messageData.meetingId) - dispatch(setMeetingId(messageData.meetingId)); - }); - - socket.on('new chat message', (messageData) => { - if ( - messages.length === 0 || - JSON.stringify(messageData) !== JSON.stringify(messages[-1]) - ) { - dispatch(setMessages(messageData)); - } - }); - - // dispatch add child to local state when element has been added by another user - socket.on('child data from server', (childData: object) => { - // console.log('child data received by users', childData); - store.dispatch(addChild(childData)); - }); - - // dispatch changeFocus to local state when another user has changed focus by selecting element on canvas - socket.on('focus data from server', (focusData: object) => { - // console.log('focus data received from server', focusData); - store.dispatch(changeFocus(focusData)); - }); - - // dispatch deleteChild to local state when another user has deleted an element - socket.on('delete data from server', (deleteData: object) => { - // console.log('delete data received from server', deleteData); - store.dispatch(deleteChild(deleteData)); - }); - - // dispatch delete element to local state when another user has deleted an element - socket.on( - 'delete element data from server', - (deleteElementData: object) => { - // console.log('delete element data received from server', deleteElementData); - store.dispatch(deleteElement(deleteElementData)); - } - ); - - // dispatch clear canvas action to local state when the host of the room has clear canvas - socket.on('clear canvas from server', () => { - store.dispatch(resetAllState()); - }); - - // dispatch all updates to local state when another user has saved from Bottom Panel - socket.on('update data from server', (updateData: BottomPanelObj) => { - // console.log('update data received from server', updateData); - store.dispatch( - updateStateUsed({ - stateUsedObj: updateData.stateUsedObj, - contextParam: updateData.contextParam - }) - ); - store.dispatch( - updateUseContext({ - useContextObj: updateData.useContextObj, - contextParam: updateData.contextParam - }) - ); - store.dispatch( - updateCss({ - style: updateData.style, - contextParam: updateData.contextParam - }) - ); - store.dispatch( - updateAttributes({ - attributes: updateData.attributes, - contextParam: updateData.contextParam - }) - ); - store.dispatch( - updateEvents({ - events: updateData.events, - contextParam: updateData.contextParam - }) - ); - }); - - // dispatch update style in local state when CSS panel is updated on their side - socket.on('update css data from server', (cssData: object) => { - // console.log('CSS data received from server', cssData); - store.dispatch(updateStylesheet(cssData)); - }); - - // dispatch new item position in local state when item position is changed by another user - socket.on( - 'item position data from server', - (itemPositionData: object) => { - // console.log( - // 'item position data received from server', - // itemPositionData - // ); - store.dispatch(changePosition(itemPositionData)); - } - ); - - // dispatch addComponent to local state when new component is created by another user - socket.on('new component data from server', (newComponent: object) => { - store.dispatch(addComponent(newComponent)); - }); - - // dispatch addElement to local state when new element is created by another user - socket.on('new element data from server', (newElement: object) => { - store.dispatch(addElement(newElement)); - }); - - // dispatch addState to local state when component state has been changed by another user - socket.on( - 'new component state data from server', - (componentState: object) => { - store.dispatch(addState(componentState)); - } - ); - - // dispatch deleteState to local state when component state has been deleted by another user - socket.on( - 'delete component state data from server', - (componentStateDelete: object) => { - store.dispatch(deleteState(componentStateDelete)); - } - ); - - // dispatch addPassedInProps to local state when p.I.P have been added by another user - socket.on( - 'new PassedInProps data from server', - (passedInProps: object) => { - store.dispatch(addPassedInProps(passedInProps)); - } - ); - - // dispatch deletePassedInProps to local state when p.I.P have been deleted by another user - socket.on( - 'PassedInProps delete data from server', - (passedInProps: object) => { - store.dispatch(deletePassedInProps(passedInProps)); - } - ); - - // dispatch addContext to local state when context has been changed by another user - socket.on('new context from server', (context: AddContextPayload) => { - store.dispatch(addContext(context)); - }); - - // dispatch addContextValues to local state when context values are added by another user - socket.on( - 'new context value from server', - (contextVal: AddContextValuesPayload) => { - store.dispatch(addContextValues(contextVal)); - } - ); - - // dispatch deleteContext to local state when context is deleted by another user - socket.on( - 'delete context data from server', - (context: DeleteContextPayload) => { - store.dispatch(deleteContext(context)); - } - ); - - // dispatch addComponentToContext to local state when context is assigned to component by another user - socket.on('assign context data from server', (data) => { - store.dispatch( - addComponentToContext({ - context: data.context, - component: data.component - }) - ); - store.dispatch( - deleteElement({ id: 'FAKE_ID', contextParam: data.contextParam }) - ); - }); - } - }; - - const createNewCollabRoom = () => { - if (userList.length !== 0) { - dispatch(setUserList([])); - } - - initSocketConnection(roomCode, roomPassword, 'CREATE'); - }; - - const addNewUserToCollabRoom = () => { - dispatch(setRoomCode(roomCode)); - dispatch(setPassword(roomPassword)); - dispatch(setUserJoinCollabRoom(true)); - }; - - const joinExistingCollabRoom = async () => { - if (userList.length !== 0) { - dispatch(setUserList([])); - } - - initSocketConnection(roomCode, joinedPasswordAttempt, 'JOIN'); - }; - - const leaveRoom = () => { - let socket = getSocket(); - - if (socket) { - socket.disconnect(); - } - - dispatch(setRoomCode('')); - dispatch(setUserName('')); - dispatch(setUserList([])); - dispatch(setUserJoinCollabRoom(false)); //false: join room UI appear - dispatch(resetState('')); - dispatch(setPassword('')); - dispatch(setEmptyMessages([])); - dispatch(setUseMic(false)); - dispatch(setUseWebcam(false)); - }; - - const checkInputField = (...inputs) => { - let userName: string = inputs[0].trim(); - let roomCode: string = inputs[1].trim(); - let password: string = inputs[2].trim(); - return ( - userName.length === 0 || roomCode.length === 0 || password.length === 0 - ); - }; - - const handleKeyDown = (e) => { - if (e.key === 'Enter' && e.target.id === 'filled-hidden-label-small') { - e.preventDefault(); - createNewCollabRoom(); - } - }; - - const userColors = [ - '#0671e3', - '#2fd64d', - '#f0c000', - '#fb4c64', - '#be5be8', - '#fe9c06', - '#f6352b', - '#1667d1', - '#1667d1', - '#50ed6a' - ]; - - return ( -
    - - - Live Room: {roomCode} - - {userJoinCollabRoom ? ( - <> - - Nickname: {userName} - - - Users: {userList.length} - - - - {userList.map((user, index) => ( - - - - ))} - - - - - ) : ( - <> - dispatch(setUserName(e.target.value))} - /> - {isJoinCallabRoom ? ( - dispatch(setRoomCode(e.target.value))} - className="enterRoomInput" - onKeyDown={handleKeyDown} - helperText={ - isRoomAvailable === false ? `Room doesn't exist` : '' - } - /> - ) : ( - dispatch(setRoomCode(e.target.value))} - className="enterRoomInput" - onKeyDown={handleKeyDown} - helperText={isCollabRoomTaken ? 'Room name already taken' : ''} - /> - )} - {isJoinCallabRoom ? ( - setJoinedPasswordAttempt(e.target.value)} - /> - ) : ( - dispatch(setPassword(e.target.value))} - /> - )} - - - setIsJoinCollabRoom(!isJoinCallabRoom)} - sx={{ - color: 'grey', - '&:hover': { - textDecoration: 'underline' - } - }} - > - {isJoinCallabRoom ? 'Start a new room' : 'Join a room'} - - - )} - -
    - ); -}; - -export default RoomsContainer; +import { useState } from 'react'; +import { Stack, Typography } from '@mui/material'; +import { useDispatch, useSelector } from 'react-redux'; +import Box from '@mui/material/Box'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Button from '@mui/material/Button'; +import { RootState } from '../../redux/store'; +import TextField from '@mui/material/TextField'; +import { BottomPanelObj } from '../../interfaces/Interfaces'; +import { + allCooperativeState, + addChild, + changeFocus, + deleteChild, + changePosition, + resetState, + updateStateUsed, + updateUseContext, + updateCss, + updateAttributes, + updateEvents, + addComponent, + addElement, + addState, + deleteState, + addPassedInProps, + deletePassedInProps, + deleteElement, + resetAllState, + updateStylesheet +} from '../../redux/reducers/slice/appStateSlice'; +import { + addContext, + deleteContext, + addContextValues +} from '../../redux/reducers/slice/contextReducer'; +import { + setRoomCode, + setUserName, + setUserJoinCollabRoom, + setUserList, + setMeetingId, + setMessages, + setEmptyMessages, + setPassword, + setUseMic, + setUseWebcam +} from '../../redux/reducers/slice/roomSlice'; +import { codePreviewCooperative } from '../../redux/reducers/slice/codePreviewSlice'; +import { cooperativeStyle } from '../../redux/reducers/slice/styleSlice'; +import store from '../../redux/store'; +import { initializeSocket, getSocket } from '../../helperFunctions/socket'; +import { + AddContextPayload, + AddContextValuesPayload, + DeleteContextPayload, + addComponentToContext +} from '../../../src/redux/reducers/slice/contextReducer'; + +const RoomsContainer = () => { + const [isJoinCallabRoom, setIsJoinCollabRoom] = useState(false); + const [joinedPasswordAttempt, setJoinedPasswordAttempt] = useState(''); + const [isPasswordAttemptIncorrect, setIsPasswordAttemptIncorrect] = + useState(true); + const [isCollabRoomTaken, setIsCollabRoomTaken] = useState(false); + const [isRoomAvailable, setIsRoomAvailable] = useState(true); + + const dispatch = useDispatch(); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + const userName = useSelector((store: RootState) => store.roomSlice.userName); + const userList = useSelector((store: RootState) => store.roomSlice.userList); + const roomPassword = useSelector( + (store: RootState) => store.roomSlice.password + ); + + + const userJoinCollabRoom = useSelector( + (store: RootState) => store.roomSlice.userJoinCollabRoom + ); + + const messages = useSelector((store: RootState) => store.roomSlice.messages); + + const initSocketConnection = ( + roomCode: string, + roomPassword: string, + method: string + ) => { + // helper function to create socket connection + initializeSocket(); + // assign socket to result of helper function to return socket created + const socket = getSocket(); + // if socket was created correctly and exists + if (socket) { + //run everytime when a client connects to server + socket.on('connect', () => { + socket.emit( + 'creating a room', + userName, + roomCode, + roomPassword, + method + ); + }); + + socket.on('wrong password', () => { + setIsPasswordAttemptIncorrect(false); + }); + + socket.on('correct password', () => { + setIsPasswordAttemptIncorrect(true); + addNewUserToCollabRoom(); + }); + + socket.on('user created a new room', () => { + addNewUserToCollabRoom(); + }); + + socket.on('room is already taken', () => { + setIsCollabRoomTaken(true); + }); + + socket.on('room does not exist', () => { + setIsRoomAvailable(false); + + }); + //If you are the host: send current state to server when a new user joins + socket.on('requesting state from host', (callback) => { + const newState = store.getState(); //pull the current state + callback(newState); //send it to backend server + }); + + //If you are the new user: receive the state from the host + socket.on('server emitting state from host', (state, callback) => { + //dispatching new state to change user current state + // console.log('state received by new join:', state); + store.dispatch(allCooperativeState(state.appState)); + store.dispatch(codePreviewCooperative(state.codePreviewCooperative)); + store.dispatch(cooperativeStyle(state.styleSlice)); + callback({ status: 'confirmed' }); + }); + + // update user list when there's a change: new join or leave the room + socket.on('update room information', (messageData) => { + //console.log('user list received from server'); + if (messageData.userList) dispatch(setUserList(messageData.userList)); + if (messageData.meetingId) + dispatch(setMeetingId(messageData.meetingId)); + }); + + socket.on('new chat message', (messageData) => { + if ( + messages.length === 0 || + JSON.stringify(messageData) !== JSON.stringify(messages[-1]) + ) { + dispatch(setMessages(messageData)); + } + }); + + // dispatch add child to local state when element has been added by another user + socket.on('child data from server', (childData: object) => { + // console.log('child data received by users', childData); + store.dispatch(addChild(childData)); + }); + + // dispatch changeFocus to local state when another user has changed focus by selecting element on canvas + socket.on('focus data from server', (focusData: object) => { + // console.log('focus data received from server', focusData); + store.dispatch(changeFocus(focusData)); + }); + + // dispatch deleteChild to local state when another user has deleted an element + socket.on('delete data from server', (deleteData: object) => { + // console.log('delete data received from server', deleteData); + store.dispatch(deleteChild(deleteData)); + }); + + // dispatch delete element to local state when another user has deleted an element + socket.on( + 'delete element data from server', + (deleteElementData: object) => { + // console.log('delete element data received from server', deleteElementData); + store.dispatch(deleteElement(deleteElementData)); + } + ); + + // dispatch clear canvas action to local state when the host of the room has clear canvas + socket.on('clear canvas from server', () => { + store.dispatch(resetAllState()); + }); + + // dispatch all updates to local state when another user has saved from Bottom Panel + socket.on('update data from server', (updateData: BottomPanelObj) => { + // console.log('update data received from server', updateData); + store.dispatch( + updateStateUsed({ + stateUsedObj: updateData.stateUsedObj, + contextParam: updateData.contextParam + }) + ); + store.dispatch( + updateUseContext({ + useContextObj: updateData.useContextObj, + contextParam: updateData.contextParam + }) + ); + store.dispatch( + updateCss({ + style: updateData.style, + contextParam: updateData.contextParam + }) + ); + store.dispatch( + updateAttributes({ + attributes: updateData.attributes, + contextParam: updateData.contextParam + }) + ); + store.dispatch( + updateEvents({ + events: updateData.events, + contextParam: updateData.contextParam + }) + ); + }); + + // dispatch update style in local state when CSS panel is updated on their side + socket.on('update css data from server', (cssData: object) => { + // console.log('CSS data received from server', cssData); + store.dispatch(updateStylesheet(cssData)); + }); + + // dispatch new item position in local state when item position is changed by another user + socket.on( + 'item position data from server', + (itemPositionData: object) => { + // console.log( + // 'item position data received from server', + // itemPositionData + // ); + store.dispatch(changePosition(itemPositionData)); + } + ); + + // dispatch addComponent to local state when new component is created by another user + socket.on('new component data from server', (newComponent: object) => { + store.dispatch(addComponent(newComponent)); + }); + + // dispatch addElement to local state when new element is created by another user + socket.on('new element data from server', (newElement: object) => { + store.dispatch(addElement(newElement)); + }); + + // dispatch addState to local state when component state has been changed by another user + socket.on( + 'new component state data from server', + (componentState: object) => { + store.dispatch(addState(componentState)); + } + ); + + // dispatch deleteState to local state when component state has been deleted by another user + socket.on( + 'delete component state data from server', + (componentStateDelete: object) => { + store.dispatch(deleteState(componentStateDelete)); + } + ); + + // dispatch addPassedInProps to local state when p.I.P have been added by another user + socket.on( + 'new PassedInProps data from server', + (passedInProps: object) => { + store.dispatch(addPassedInProps(passedInProps)); + } + ); + + // dispatch deletePassedInProps to local state when p.I.P have been deleted by another user + socket.on( + 'PassedInProps delete data from server', + (passedInProps: object) => { + store.dispatch(deletePassedInProps(passedInProps)); + } + ); + + // dispatch addContext to local state when context has been changed by another user + socket.on('new context from server', (context: AddContextPayload) => { + store.dispatch(addContext(context)); + }); + + // dispatch addContextValues to local state when context values are added by another user + socket.on( + 'new context value from server', + (contextVal: AddContextValuesPayload) => { + store.dispatch(addContextValues(contextVal)); + } + ); + + // dispatch deleteContext to local state when context is deleted by another user + socket.on( + 'delete context data from server', + (context: DeleteContextPayload) => { + store.dispatch(deleteContext(context)); + } + ); + + // dispatch addComponentToContext to local state when context is assigned to component by another user + socket.on('assign context data from server', (data) => { + store.dispatch( + addComponentToContext({ + context: data.context, + component: data.component + }) + ); + store.dispatch( + deleteElement({ id: 'FAKE_ID', contextParam: data.contextParam }) + ); + }); + } + }; + + const createNewCollabRoom = () => { + if (userList.length !== 0) { + dispatch(setUserList([])); + } + + initSocketConnection(roomCode, roomPassword, 'CREATE'); + }; + + const addNewUserToCollabRoom = () => { + dispatch(setRoomCode(roomCode)); + dispatch(setPassword(roomPassword)); + dispatch(setUserJoinCollabRoom(true)); + }; + + const joinExistingCollabRoom = async () => { + if (userList.length !== 0) { + dispatch(setUserList([])); + } + + initSocketConnection(roomCode, joinedPasswordAttempt, 'JOIN'); + }; + + const leaveRoom = () => { + let socket = getSocket(); + + if (socket) { + socket.disconnect(); + } + + dispatch(setRoomCode('')); + dispatch(setUserName('')); + dispatch(setUserList([])); + dispatch(setUserJoinCollabRoom(false)); //false: join room UI appear + dispatch(resetState('')); + dispatch(setPassword('')); + dispatch(setEmptyMessages([])); + dispatch(setUseMic(false)); + dispatch(setUseWebcam(false)); + }; + + const checkInputField = (...inputs) => { + let userName: string = inputs[0].trim(); + let roomCode: string = inputs[1].trim(); + let password: string = inputs[2].trim(); + return ( + userName.length === 0 || roomCode.length === 0 || password.length === 0 + ); + }; + + const handleKeyDown = (e) => { + if (e.key === 'Enter' && e.target.id === 'filled-hidden-label-small') { + e.preventDefault(); + createNewCollabRoom(); + } + }; + + const userColors = [ + '#0671e3', + '#2fd64d', + '#f0c000', + '#fb4c64', + '#be5be8', + '#fe9c06', + '#f6352b', + '#1667d1', + '#1667d1', + '#50ed6a' + ]; + + return ( +
    + + + Live Room: {roomCode} + + {userJoinCollabRoom ? ( + <> + + Nickname: {userName} + + + Users: {userList.length} + + + + {userList.map((user, index) => ( + + + + ))} + + + + + ) : ( + <> + dispatch(setUserName(e.target.value))} + /> + {isJoinCallabRoom ? ( + dispatch(setRoomCode(e.target.value))} + className="enterRoomInput" + onKeyDown={handleKeyDown} + helperText={ + isRoomAvailable === false ? `Room doesn't exist` : '' + } + /> + ) : ( + dispatch(setRoomCode(e.target.value))} + className="enterRoomInput" + onKeyDown={handleKeyDown} + helperText={isCollabRoomTaken ? 'Room name already taken' : ''} + /> + )} + {isJoinCallabRoom ? ( + setJoinedPasswordAttempt(e.target.value)} + /> + ) : ( + dispatch(setPassword(e.target.value))} + /> + )} + + + setIsJoinCollabRoom(!isJoinCallabRoom)} + sx={{ + color: 'grey', + '&:hover': { + textDecoration: 'underline' + } + }} + > + {isJoinCallabRoom ? 'Start a new room' : 'Join a room'} + + + )} + +
    + ); +}; + +export default RoomsContainer; diff --git a/app/src/components/login/SignUp.tsx b/app/src/components/login/SignUp.tsx index 1e8825a30..726d26645 100644 --- a/app/src/components/login/SignUp.tsx +++ b/app/src/components/login/SignUp.tsx @@ -1,334 +1,334 @@ -import React, { useState } from 'react'; -import { - RouteComponentProps, - Link as RouteLink, - withRouter, - useHistory -} from 'react-router-dom'; -import { SigninDark } from '../../../../app/src/public/styles/theme'; -import { - StyledEngineProvider, - Theme, - ThemeProvider -} from '@mui/material/styles'; -import { - Box, - Avatar, - Button, - Container, - CssBaseline, - Grid, - TextField, - Typography -} from '@mui/material'; - -import AssignmentIcon from '@mui/icons-material/Assignment'; -import { LoginInt } from '../../interfaces/Interfaces'; - -import makeStyles from '@mui/styles/makeStyles'; -import { - newUserIsCreated, - handleChange, - resetErrorValidation, - validateInputs, - setErrorMessages -} from '../../helperFunctions/auth'; - -declare module '@mui/styles/defaultTheme' { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface DefaultTheme extends Theme {} -} - -function Copyright() { - return ( - - {'Copyright © ReacType '} - {new Date().getFullYear()} - - ); -} - -const useStyles = makeStyles((theme) => ({ - paper: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center' - }, - avatar: { - backgroundColor: 'white' - }, - form: { - width: '100%' // Fix IE 11 issue. - }, - submit: {}, - root: { - '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: 'white' - } - } -})); - -const SignUp: React.FC = () => { - const classes = useStyles(); - const history = useHistory(); - const [email, setEmail] = useState(''); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [passwordVerify, setPasswordVerify] = useState(''); - const [invalidEmailMsg, setInvalidEmailMsg] = useState(''); - const [invalidUsernameMsg, setInvalidUserMsg] = useState(''); - const [invalidPasswordMsg, setInvalidPasswordMsg] = useState(''); - const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState(''); - const [invalidEmail, setInvalidEmail] = useState(false); - const [invalidUsername, setInvalidUser] = useState(false); - const [invalidPassword, setInvalidPassword] = useState(false); - const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false); - - // define error setters to pass to resetErrorValidation function - const errorSetters = { - setInvalidEmail, - setInvalidEmailMsg, - setInvalidUser, - setInvalidUserMsg, - setInvalidPassword, - setInvalidPasswordMsg, - setInvalidVerifyPassword, - setInvalidVerifyPasswordMsg - }; - // define handle change setters to pass to handleChange function - const handleChangeSetters = { - setEmail, - setUsername, - setPassword, - setPasswordVerify - }; - - /** - * Handles input changes for form fields and updates the state accordingly. - * This function delegates to the `handleChange` function, passing the event - * and the `handleChangeSetters` for updating the specific state tied to the input fields. - * @param {React.ChangeEvent} e - The event object that triggered the change. - */ - const handleInputChange = (e: React.ChangeEvent): void => { - handleChange(e, handleChangeSetters); - }; - - /** - * Handles the form submission for user registration. Prevents default form behavior, - * validates user inputs, and attempts to register a new user. Redirects to home on success, - * otherwise displays error messages based on the response. - * @param {React.MouseEvent} e - The event object that triggered the submission. - */ - const handleSignUp = async ( - e: React.MouseEvent - ) => { - e.preventDefault(); - resetErrorValidation(errorSetters); // Reset validation errors before a new signup attempt. - const isValid = validateInputs({ - email, - username, - password, - passwordVerify, - errorSetters - }); // Validate Inputs using Auth helper function - - if (!isValid) { - console.log('Validation failed, account not created.'); - return; - } - try { - const userCreated = await newUserIsCreated(username, email, password); - if (userCreated === 'Success') { - console.log('Account creation successful, redirecting...'); - history.push('/'); - } else { - switch (userCreated) { - case 'Email Taken': - setErrorMessages('email', 'Email Taken', errorSetters); - break; - case 'Username Taken': - setErrorMessages('username', 'Username Taken', errorSetters); - break; - default: - console.log( - 'Signup failed: Unknown or unhandled error', - userCreated - ); - } - } - } catch (error) { - console.error('Error during signup in handleSignUp:', error); - } - }; - - return ( - - - - -
    - - - - - Sign up - -
    - - - - - - - - - - - - - - - - - By signing up, you agree to our - - {' '} - Terms , Privacy Policy - {' '} - and - {' '} - Cookies Policy - {' '} - - - - - - - Already have an account? - Log in{' '} - - -
    -
    - - - -
    -
    -
    - ); -}; - -export default withRouter(SignUp); +import React, { useState } from 'react'; +import { + RouteComponentProps, + Link as RouteLink, + withRouter, + useHistory +} from 'react-router-dom'; +import { SigninDark } from '../../../../app/src/public/styles/theme'; +import { + StyledEngineProvider, + Theme, + ThemeProvider +} from '@mui/material/styles'; +import { + Box, + Avatar, + Button, + Container, + CssBaseline, + Grid, + TextField, + Typography +} from '@mui/material'; + +import AssignmentIcon from '@mui/icons-material/Assignment'; +import { LoginInt } from '../../interfaces/Interfaces'; + +import makeStyles from '@mui/styles/makeStyles'; +import { + newUserIsCreated, + handleChange, + resetErrorValidation, + validateInputs, + setErrorMessages +} from '../../helperFunctions/auth'; + +declare module '@mui/styles/defaultTheme' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface DefaultTheme extends Theme {} +} + +function Copyright() { + return ( + + {'Copyright © ReacType '} + {new Date().getFullYear()} + + ); +} + +const useStyles = makeStyles((theme) => ({ + paper: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center' + }, + avatar: { + backgroundColor: 'white' + }, + form: { + width: '100%' // Fix IE 11 issue. + }, + submit: {}, + root: { + '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: 'white' + } + } +})); + +const SignUp: React.FC = () => { + const classes = useStyles(); + const history = useHistory(); + const [email, setEmail] = useState(''); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [passwordVerify, setPasswordVerify] = useState(''); + const [invalidEmailMsg, setInvalidEmailMsg] = useState(''); + const [invalidUsernameMsg, setInvalidUserMsg] = useState(''); + const [invalidPasswordMsg, setInvalidPasswordMsg] = useState(''); + const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState(''); + const [invalidEmail, setInvalidEmail] = useState(false); + const [invalidUsername, setInvalidUser] = useState(false); + const [invalidPassword, setInvalidPassword] = useState(false); + const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false); + + // define error setters to pass to resetErrorValidation function + const errorSetters = { + setInvalidEmail, + setInvalidEmailMsg, + setInvalidUser, + setInvalidUserMsg, + setInvalidPassword, + setInvalidPasswordMsg, + setInvalidVerifyPassword, + setInvalidVerifyPasswordMsg + }; + // define handle change setters to pass to handleChange function + const handleChangeSetters = { + setEmail, + setUsername, + setPassword, + setPasswordVerify + }; + + /** + * Handles input changes for form fields and updates the state accordingly. + * This function delegates to the `handleChange` function, passing the event + * and the `handleChangeSetters` for updating the specific state tied to the input fields. + * @param {React.ChangeEvent} e - The event object that triggered the change. + */ + const handleInputChange = (e: React.ChangeEvent): void => { + handleChange(e, handleChangeSetters); + }; + + /** + * Handles the form submission for user registration. Prevents default form behavior, + * validates user inputs, and attempts to register a new user. Redirects to home on success, + * otherwise displays error messages based on the response. + * @param {React.MouseEvent} e - The event object that triggered the submission. + */ + const handleSignUp = async ( + e: React.MouseEvent + ) => { + e.preventDefault(); + resetErrorValidation(errorSetters); // Reset validation errors before a new signup attempt. + const isValid = validateInputs({ + email, + username, + password, + passwordVerify, + errorSetters + }); // Validate Inputs using Auth helper function + + if (!isValid) { + console.log('Validation failed, account not created.'); + return; + } + try { + const userCreated = await newUserIsCreated(username, email, password); + if (userCreated === 'Success') { + console.log('Account creation successful, redirecting...'); + history.push('/'); + } else { + switch (userCreated) { + case 'Email Taken': + setErrorMessages('email', 'Email Taken', errorSetters); + break; + case 'Username Taken': + setErrorMessages('username', 'Username Taken', errorSetters); + break; + default: + console.log( + 'Signup failed: Unknown or unhandled error', + userCreated + ); + } + } + } catch (error) { + console.error('Error during signup in handleSignUp:', error); + } + }; + + return ( + + + + +
    + + + + + Sign up + +
    + + + + + + + + + + + + + + + + + By signing up, you agree to our + + {' '} + Terms , Privacy Policy + {' '} + and + {' '} + Cookies Policy + {' '} + + + + + + + Already have an account? + Log in{' '} + + +
    +
    + + + +
    +
    +
    + ); +}; + +export default withRouter(SignUp); diff --git a/app/src/components/main/AddLink.tsx b/app/src/components/main/AddLink.tsx index e11979667..fd2b50660 100644 --- a/app/src/components/main/AddLink.tsx +++ b/app/src/components/main/AddLink.tsx @@ -1,104 +1,104 @@ -import React, { useState } from 'react'; -import FormControl from '@mui/material/FormControl'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; -import { InputLabel } from '@mui/material'; -import { useDispatch, useSelector } from 'react-redux'; -import { updateAttributes } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; - -function AddLink({ id, onClickHandler, linkDisplayed }) { - const { state, contextParam, isThemeLight } = useSelector( - (store: RootState) => ({ - state: store.appState, - contextParam: store.contextSlice, - isThemeLight: store.styleSlice - }) - ); - const dispatch = useDispatch(); - //this function allows the link to be functional when it's nested - function deepIterate(arr) { - const output = []; - for (let i = 0; i < arr.length; i++) { - if (arr[i].typeId === 1000) continue; - output.push(arr[i]); - if (arr[i].children.length) { - output.push(...deepIterate(arr[i].children)); - } - } - return output; - } - - const handlePageSelect = (event) => { - const currComponent = state.components.find( - (element) => element.id === state.canvasFocus.componentId - ); - deepIterate(currComponent.children).some((element) => { - if (element.childId === id) { - const state = JSON.parse(JSON.stringify(element)); - state.childId = id; - state.attributes.compLink = event.target.value; - dispatch( - updateAttributes({ attributes: state, contextParam: contextParam }) - ); - return true; - } - }); - }; - - const pagesItems = state.components.filter((comp) => - state.rootComponents.includes(comp.id) - ); - const dropDown = [ - - ].concat( - pagesItems.map((comp) => ( - - {comp.name} - - )) - ); - - return ( -
    - - - Pages - - - -
    - ); -} - -export default AddLink; +import React, { useState } from 'react'; +import FormControl from '@mui/material/FormControl'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import { InputLabel } from '@mui/material'; +import { useDispatch, useSelector } from 'react-redux'; +import { updateAttributes } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; + +function AddLink({ id, onClickHandler, linkDisplayed }) { + const { state, contextParam, isThemeLight } = useSelector( + (store: RootState) => ({ + state: store.appState, + contextParam: store.contextSlice, + isThemeLight: store.styleSlice + }) + ); + const dispatch = useDispatch(); + //this function allows the link to be functional when it's nested + function deepIterate(arr) { + const output = []; + for (let i = 0; i < arr.length; i++) { + if (arr[i].typeId === 1000) continue; + output.push(arr[i]); + if (arr[i].children.length) { + output.push(...deepIterate(arr[i].children)); + } + } + return output; + } + + const handlePageSelect = (event) => { + const currComponent = state.components.find( + (element) => element.id === state.canvasFocus.componentId + ); + deepIterate(currComponent.children).some((element) => { + if (element.childId === id) { + const state = JSON.parse(JSON.stringify(element)); + state.childId = id; + state.attributes.compLink = event.target.value; + dispatch( + updateAttributes({ attributes: state, contextParam: contextParam }) + ); + return true; + } + }); + }; + + const pagesItems = state.components.filter((comp) => + state.rootComponents.includes(comp.id) + ); + const dropDown = [ + + ].concat( + pagesItems.map((comp) => ( + + {comp.name} + + )) + ); + + return ( +
    + + + Pages + + + +
    + ); +} + +export default AddLink; diff --git a/app/src/components/main/AddRoute.tsx b/app/src/components/main/AddRoute.tsx index b061cb8e2..5807b1b1b 100644 --- a/app/src/components/main/AddRoute.tsx +++ b/app/src/components/main/AddRoute.tsx @@ -1,42 +1,42 @@ -import { AddRoutes } from '../../interfaces/Interfaces'; -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { addChild } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import { emitEvent } from '../../helperFunctions/socket'; - -function AddRoute({ id }: AddRoutes): JSX.Element { - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - const dispatch = useDispatch(); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const handleClick = (id: number): void => { - dispatch( - addChild({ - type: 'HTML Element', - typeId: -1, - childId: id, // this is the id of the parent to attach it to - contextParam: contextParam - }) - ); - if (roomCode) { - emitEvent('addChildAction', roomCode, { - type: 'HTML Element', - typeId: -1, - childId: id, - contextParam: contextParam - }); - - console.log('emit addChildAction event is triggered in AddRoute!'); - } - }; - - return ( -
    - -
    - ); -} - -export default AddRoute; +import { AddRoutes } from '../../interfaces/Interfaces'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { addChild } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import { emitEvent } from '../../helperFunctions/socket'; + +function AddRoute({ id }: AddRoutes): JSX.Element { + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + const dispatch = useDispatch(); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const handleClick = (id: number): void => { + dispatch( + addChild({ + type: 'HTML Element', + typeId: -1, + childId: id, // this is the id of the parent to attach it to + contextParam: contextParam + }) + ); + if (roomCode) { + emitEvent('addChildAction', roomCode, { + type: 'HTML Element', + typeId: -1, + childId: id, + contextParam: contextParam + }); + + console.log('emit addChildAction event is triggered in AddRoute!'); + } + }; + + return ( +
    + +
    + ); +} + +export default AddRoute; diff --git a/app/src/components/main/Arrow.tsx b/app/src/components/main/Arrow.tsx index 91c3a8910..c43a4cf34 100644 --- a/app/src/components/main/Arrow.tsx +++ b/app/src/components/main/Arrow.tsx @@ -1,87 +1,87 @@ -import { Arrow } from '../../interfaces/Interfaces' -const arrow: Arrow = { - renderArrow: (id) => { - if(id != null) { - let canvasEle = document.getElementById(`canv${id}`) - let renderEle = document.getElementById(`rend${id}`) - if( canvasEle === null || renderEle === null) { - return; - } else { - let canvasElePosition = canvasEle.getBoundingClientRect(); - let renderElePosition = renderEle.getBoundingClientRect(); - const canvasEleX = canvasElePosition.left + canvasElePosition.width; - const canvasEleY = canvasElePosition.top + (canvasElePosition.height / 2); - const renderEleX = renderElePosition.left; - const renderEleY = renderElePosition.top; - arrow.lineDraw(canvasEleX, canvasEleY, renderEleX, renderEleY); - } - } - }, - - createLineElement: (x, y, length, angle) => { - let styles = 'border: 1px solid black;' - + 'width: ' + length + 'px;' - + 'height: 0px;' - + 'z-index: 9999999999;' - + '-moz-transform: rotate(' + angle + 'rad);' - + '-webkit-transform: rotate(' + angle + 'rad);' - + '-o-transform: rotate(' + angle + 'rad);' - + '-ms-transform: rotate(' + angle + 'rad);' - + 'position: absolute;' - + 'top: ' + y + 'px;' - + 'left: ' + x + 'px;'; - let line = document.createElement("div"); - line.setAttribute("class", "line"); - line.setAttribute('style', styles); - return line; - }, - - createHeadElement: (x, y, length, angle) => { - let styles = 'width: 13px;' - + 'height: 13px;' - + 'border: solid 2px;' - + 'border-radius: 50%;' - + 'z-index: 9999999999;' - + 'background-color: #00FFFF;' - + 'border-color: #bbb;' - + 'rotate: ' + angle + 'rad;' - + 'position: absolute;' - + 'top: ' + -6.5 + 'px;' - + 'left: ' + 0 + (length - 12) + 'px;'; - let head = document.createElement("div"); - head.setAttribute("class", "head"); - head.setAttribute('style', styles); - return head; - }, - - lineDraw: (x1, y1, x2, y2) => { - let a = x1 - x2, - b = y1 - y2, - c = Math.sqrt(a * a + b * b); - let sx = (x1 + x2) / 2, - sy = (y1 + y2) / 2; - let x = sx - c / 2, - y = sy; - let alpha = Math.PI - Math.atan2(-b, a); - let line = arrow.createLineElement(x, y, c, alpha); - let head = arrow.createHeadElement(x, y, c, alpha); - arrow.deleteLines(); - document.getElementsByClassName("main")[0].append(line); - document.getElementsByClassName("line")[0].append(head); - }, - - deleteLines: () => { - let lineArray = document.getElementsByClassName("line"); - let lineArrayIterable = Array.from(lineArray); - let headArray = document.getElementsByClassName("head"); - let headArrayIterable = Array.from(headArray); - for(const line of lineArrayIterable) { - line.remove(); - } - for(const head of headArrayIterable) { - head.remove(); - } - } -} - -export default arrow; +import { Arrow } from '../../interfaces/Interfaces' +const arrow: Arrow = { + renderArrow: (id) => { + if(id != null) { + let canvasEle = document.getElementById(`canv${id}`) + let renderEle = document.getElementById(`rend${id}`) + if( canvasEle === null || renderEle === null) { + return; + } else { + let canvasElePosition = canvasEle.getBoundingClientRect(); + let renderElePosition = renderEle.getBoundingClientRect(); + const canvasEleX = canvasElePosition.left + canvasElePosition.width; + const canvasEleY = canvasElePosition.top + (canvasElePosition.height / 2); + const renderEleX = renderElePosition.left; + const renderEleY = renderElePosition.top; + arrow.lineDraw(canvasEleX, canvasEleY, renderEleX, renderEleY); + } + } + }, + + createLineElement: (x, y, length, angle) => { + let styles = 'border: 1px solid black;' + + 'width: ' + length + 'px;' + + 'height: 0px;' + + 'z-index: 9999999999;' + + '-moz-transform: rotate(' + angle + 'rad);' + + '-webkit-transform: rotate(' + angle + 'rad);' + + '-o-transform: rotate(' + angle + 'rad);' + + '-ms-transform: rotate(' + angle + 'rad);' + + 'position: absolute;' + + 'top: ' + y + 'px;' + + 'left: ' + x + 'px;'; + let line = document.createElement("div"); + line.setAttribute("class", "line"); + line.setAttribute('style', styles); + return line; + }, + + createHeadElement: (x, y, length, angle) => { + let styles = 'width: 13px;' + + 'height: 13px;' + + 'border: solid 2px;' + + 'border-radius: 50%;' + + 'z-index: 9999999999;' + + 'background-color: #00FFFF;' + + 'border-color: #bbb;' + + 'rotate: ' + angle + 'rad;' + + 'position: absolute;' + + 'top: ' + -6.5 + 'px;' + + 'left: ' + 0 + (length - 12) + 'px;'; + let head = document.createElement("div"); + head.setAttribute("class", "head"); + head.setAttribute('style', styles); + return head; + }, + + lineDraw: (x1, y1, x2, y2) => { + let a = x1 - x2, + b = y1 - y2, + c = Math.sqrt(a * a + b * b); + let sx = (x1 + x2) / 2, + sy = (y1 + y2) / 2; + let x = sx - c / 2, + y = sy; + let alpha = Math.PI - Math.atan2(-b, a); + let line = arrow.createLineElement(x, y, c, alpha); + let head = arrow.createHeadElement(x, y, c, alpha); + arrow.deleteLines(); + document.getElementsByClassName("main")[0].append(line); + document.getElementsByClassName("line")[0].append(head); + }, + + deleteLines: () => { + let lineArray = document.getElementsByClassName("line"); + let lineArrayIterable = Array.from(lineArray); + let headArray = document.getElementsByClassName("head"); + let headArrayIterable = Array.from(headArray); + for(const line of lineArrayIterable) { + line.remove(); + } + for(const head of headArrayIterable) { + head.remove(); + } + } +} + +export default arrow; diff --git a/app/src/components/main/DirectChildComponent.tsx b/app/src/components/main/DirectChildComponent.tsx index 3b29b2b02..95d022906 100644 --- a/app/src/components/main/DirectChildComponent.tsx +++ b/app/src/components/main/DirectChildComponent.tsx @@ -1,82 +1,82 @@ -import React from 'react'; -import { Component, ChildElement } from '../../interfaces/Interfaces'; -import { useDrag } from 'react-dnd'; -import { ItemTypes } from '../../constants/ItemTypes'; -import DeleteButton from './DeleteButton'; -import { combineStyles } from '../../helperFunctions/combineStyles'; -import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; -import { useDispatch, useSelector } from 'react-redux'; -import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import { emitEvent } from '../../helperFunctions/socket'; - -function DirectChildComponent({ childId, type, typeId, name }: ChildElement) { - const state = useSelector((store: RootState) => store.appState); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - - // find the top-level component corresponding to this instance of the component - // find the current component to render on the canvas - const referencedComponent: Component = state.components.find( - (elem: Component) => elem.id === typeId - ); - - const [{ isDragging }, drag] = useDrag({ - // setting item attributes to be referenced when updating state with new instance of dragged item - item: { - type: ItemTypes.INSTANCE, - newInstance: false, - childId: childId, - instanceType: type, - instanceTypeId: typeId - }, - collect: (monitor) => ({ - isDragging: !!monitor.isDragging() - }) - }); - const changeFocusFunction = (componentId: number, childId: number | null) => { - dispatch(changeFocus({ componentId, childId })); - if (roomCode) { - emitEvent('changeFocusAction', roomCode, { - componentId: componentId, - childId: childId - }); - // console.log('emit focus event from DirectChildComponent'); - } - }; - - // onClickHandler is responsible for changing the focused component and child component - function onClickHandler(event) { - event.stopPropagation(); - changeFocusFunction(state.canvasFocus.componentId, childId); - } - // combine all styles so that higher priority style specifications overrule lower priority style specifications - // priority order is 1) style directly set for this child (style), 2) style of the referenced component, and 3) default styling - const interactiveStyle = { - border: - state.canvasFocus.childId === childId - ? '3px solid #0671e3' - : '1px Solid grey', - boxShadow: - state.canvasFocus.childId === childId ? '1px 1px 3px #a7cced' : '' - }; - - const combinedStyle = combineStyles( - combineStyles(globalDefaultStyle, referencedComponent.style), - interactiveStyle - ); - // Renders name and not children of subcomponents to clean up Canvas view when dragging components - // into the main canvas. To render html elements on canvas, import and invoke renderChildren - return ( -
    - - {/* render name and delete button X */} - {name} - - -
    - ); -} - -export default DirectChildComponent; +import React from 'react'; +import { Component, ChildElement } from '../../interfaces/Interfaces'; +import { useDrag } from 'react-dnd'; +import { ItemTypes } from '../../constants/ItemTypes'; +import DeleteButton from './DeleteButton'; +import { combineStyles } from '../../helperFunctions/combineStyles'; +import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; +import { useDispatch, useSelector } from 'react-redux'; +import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import { emitEvent } from '../../helperFunctions/socket'; + +function DirectChildComponent({ childId, type, typeId, name }: ChildElement) { + const state = useSelector((store: RootState) => store.appState); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + + // find the top-level component corresponding to this instance of the component + // find the current component to render on the canvas + const referencedComponent: Component = state.components.find( + (elem: Component) => elem.id === typeId + ); + + const [{ isDragging }, drag] = useDrag({ + // setting item attributes to be referenced when updating state with new instance of dragged item + item: { + type: ItemTypes.INSTANCE, + newInstance: false, + childId: childId, + instanceType: type, + instanceTypeId: typeId + }, + collect: (monitor) => ({ + isDragging: !!monitor.isDragging() + }) + }); + const changeFocusFunction = (componentId: number, childId: number | null) => { + dispatch(changeFocus({ componentId, childId })); + if (roomCode) { + emitEvent('changeFocusAction', roomCode, { + componentId: componentId, + childId: childId + }); + // console.log('emit focus event from DirectChildComponent'); + } + }; + + // onClickHandler is responsible for changing the focused component and child component + function onClickHandler(event) { + event.stopPropagation(); + changeFocusFunction(state.canvasFocus.componentId, childId); + } + // combine all styles so that higher priority style specifications overrule lower priority style specifications + // priority order is 1) style directly set for this child (style), 2) style of the referenced component, and 3) default styling + const interactiveStyle = { + border: + state.canvasFocus.childId === childId + ? '3px solid #0671e3' + : '1px Solid grey', + boxShadow: + state.canvasFocus.childId === childId ? '1px 1px 3px #a7cced' : '' + }; + + const combinedStyle = combineStyles( + combineStyles(globalDefaultStyle, referencedComponent.style), + interactiveStyle + ); + // Renders name and not children of subcomponents to clean up Canvas view when dragging components + // into the main canvas. To render html elements on canvas, import and invoke renderChildren + return ( +
    + + {/* render name and delete button X */} + {name} + + +
    + ); +} + +export default DirectChildComponent; diff --git a/app/src/components/main/DirectChildHTML.tsx b/app/src/components/main/DirectChildHTML.tsx index 5c1a3f98a..02529887b 100644 --- a/app/src/components/main/DirectChildHTML.tsx +++ b/app/src/components/main/DirectChildHTML.tsx @@ -1,90 +1,90 @@ -import React from 'react'; -import { ChildElement, HTMLType } from '../../interfaces/Interfaces'; -import { useDrag } from 'react-dnd'; -import { ItemTypes } from '../../constants/ItemTypes'; -import { combineStyles } from '../../helperFunctions/combineStyles'; -import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; -import DeleteButton from './DeleteButton'; -import { useDispatch, useSelector } from 'react-redux'; -import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import { emitEvent } from '../../helperFunctions/socket'; - -function DirectChildHTML({ childId, name, type, typeId, style }: ChildElement) { - const state = useSelector((store: RootState) => store.appState); - - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - - // find the HTML element corresponding with this instance of an HTML element - // find the current component to render on the canvas - const HTMLType: HTMLType = state.HTMLTypes.find( - (type: HTMLType) => type.id === typeId - ); - // hook that allows component to be draggable - const [{ isDragging }, drag] = useDrag({ - // setting item attributes to be referenced when updating state with new instance of dragged item - item: { - type: ItemTypes.INSTANCE, - newInstance: false, - childId: childId, - instanceType: type, - instanceTypeId: typeId - }, - collect: (monitor: any) => ({ - isDragging: !!monitor.isDragging() - }) - }); - - const changeFocusFunction = (componentId: number, childId: number | null) => { - dispatch(changeFocus({ componentId, childId })); - if (roomCode) { - emitEvent('changeFocusAction', roomCode, { - componentId: componentId, - childId: childId - }); - // console.log('emit focus event from DirectChildHTML'); - } - }; - - // onClickHandler is responsible for changing the focused component and child component - function onClickHandler(event) { - event.stopPropagation(); - changeFocusFunction(state.canvasFocus.componentId, childId); - } - - // combine all styles so that higher priority style specifications overrule lower priority style specifications - // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling - const interactiveStyle = { - border: - state.canvasFocus.childId === childId - ? '2px solid #0671e3' - : '1px solid #31343A' - }; - - const combinedStyle = combineStyles( - combineStyles(combineStyles(globalDefaultStyle, HTMLType.style), style), - interactiveStyle - ); - - return ( -
    - - {HTMLType.placeHolderShort} - - -
    - ); -} - -export default DirectChildHTML; +import React from 'react'; +import { ChildElement, HTMLType } from '../../interfaces/Interfaces'; +import { useDrag } from 'react-dnd'; +import { ItemTypes } from '../../constants/ItemTypes'; +import { combineStyles } from '../../helperFunctions/combineStyles'; +import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; +import DeleteButton from './DeleteButton'; +import { useDispatch, useSelector } from 'react-redux'; +import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import { emitEvent } from '../../helperFunctions/socket'; + +function DirectChildHTML({ childId, name, type, typeId, style }: ChildElement) { + const state = useSelector((store: RootState) => store.appState); + + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + + // find the HTML element corresponding with this instance of an HTML element + // find the current component to render on the canvas + const HTMLType: HTMLType = state.HTMLTypes.find( + (type: HTMLType) => type.id === typeId + ); + // hook that allows component to be draggable + const [{ isDragging }, drag] = useDrag({ + // setting item attributes to be referenced when updating state with new instance of dragged item + item: { + type: ItemTypes.INSTANCE, + newInstance: false, + childId: childId, + instanceType: type, + instanceTypeId: typeId + }, + collect: (monitor: any) => ({ + isDragging: !!monitor.isDragging() + }) + }); + + const changeFocusFunction = (componentId: number, childId: number | null) => { + dispatch(changeFocus({ componentId, childId })); + if (roomCode) { + emitEvent('changeFocusAction', roomCode, { + componentId: componentId, + childId: childId + }); + // console.log('emit focus event from DirectChildHTML'); + } + }; + + // onClickHandler is responsible for changing the focused component and child component + function onClickHandler(event) { + event.stopPropagation(); + changeFocusFunction(state.canvasFocus.componentId, childId); + } + + // combine all styles so that higher priority style specifications overrule lower priority style specifications + // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling + const interactiveStyle = { + border: + state.canvasFocus.childId === childId + ? '2px solid #0671e3' + : '1px solid #31343A' + }; + + const combinedStyle = combineStyles( + combineStyles(combineStyles(globalDefaultStyle, HTMLType.style), style), + interactiveStyle + ); + + return ( +
    + + {HTMLType.placeHolderShort} + + +
    + ); +} + +export default DirectChildHTML; diff --git a/app/src/components/main/DirectChildHTMLNestable.tsx b/app/src/components/main/DirectChildHTMLNestable.tsx index 333ca87ac..ced8168fd 100644 --- a/app/src/components/main/DirectChildHTMLNestable.tsx +++ b/app/src/components/main/DirectChildHTMLNestable.tsx @@ -1,239 +1,239 @@ -import React, { useRef } from 'react'; -import { ChildElement, HTMLType } from '../../interfaces/Interfaces'; -import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; -import { ItemTypes } from '../../constants/ItemTypes'; -import { combineStyles } from '../../helperFunctions/combineStyles'; -import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; -import renderChildren from '../../helperFunctions/renderChildren'; -import DeleteButton from './DeleteButton'; -import validateNewParent from '../../helperFunctions/changePositionValidation'; -import componentNest from '../../helperFunctions/componentNestValidation'; -import AddRoute from './AddRoute'; -import AddLink from './AddLink'; -import { useDispatch, useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; -import { emitEvent } from '../../helperFunctions/socket'; - -import { - changeFocus, - changePosition, - addChild, - snapShotAction -} from '../../redux/reducers/slice/appStateSlice'; - -function DirectChildHTMLNestable({ - childId, - type, - typeId, - style, - children, - name, - attributes -}: ChildElement): React.JSX.Element { - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - - const dispatch = useDispatch(); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const ref = useRef(null); - - // takes a snapshot of state to be used in UNDO and REDO cases. snapShotFunc is also invoked in Canvas.tsx - const snapShotFunc = () => { - //makes a deep clone of state - const deepCopiedState = JSON.parse(JSON.stringify(state)); - const focusIndex = state.canvasFocus.componentId - 1; - //pushes the last user action on the canvas into the past array of Component - dispatch( - snapShotAction({ - focusIndex: focusIndex, - deepCopiedState: deepCopiedState - }) - ); - }; - - // find the HTML element corresponding with this instance of an HTML element - // find the current component to render on the canvas - const HTMLType: HTMLType = state.HTMLTypes.find( - (type: HTMLType) => type.id === typeId - ); - - // hook that allows component to be draggable - const [{ isDragging }, drag] = useDrag({ - // setting item attributes to be referenced when updating state with new instance of dragged item - item: { - type: ItemTypes.INSTANCE, - newInstance: false, - childId: childId, - instanceType: type, - instanceTypeId: typeId, - name: name - }, - canDrag: HTMLType.id !== 1000, // dragging not permitted if element is separator - collect: (monitor: any) => { - return { - isDragging: !!monitor.isDragging() - }; - } - }); - - // both useDrop and useDrag used here to allow canvas components to be both a drop target and drag source - const [{ isOver }, drop] = useDrop({ - accept: ItemTypes.INSTANCE, - // triggered on drop - drop: (item: any, monitor: DropTargetMonitor) => { - const didDrop = monitor.didDrop(); - // takes a snapshot of state to be used in UNDO and REDO cases - snapShotFunc(); - if (didDrop) { - return; - } - // updates state with new instance - // if item dropped is going to be a new instance (i.e. it came from the left panel), then create a new child component - if (item.newInstance) { - if ( - (item.instanceType === 'Component' && - componentNest( - state.components[item.instanceTypeId - 1].children, - childId - )) || - item.instanceType !== 'Component' - ) { - dispatch( - addChild({ - type: item.instanceType, - typeId: item.instanceTypeId, - childId: childId, - contextParam: contextParam - }) - ); - if (roomCode) { - emitEvent('addChildAction', roomCode, { - type: item.instanceType, - typeId: item.instanceTypeId, - childId: childId, - contextParam: contextParam - }); - - // console.log( - // 'emit addChildAction event is triggered in DirectChildHTMLNestable' - // ); - } - } - } - // if item is not a new instance, change position of element dragged inside div so that the div is the new parent - else { - // check to see if the selected child is trying to nest within itself - if (validateNewParent(state, item.childId, childId) === true) { - dispatch( - changePosition({ - currentChildId: item.childId, - newParentChildId: childId, - contextParam: contextParam - }) - ); - if (roomCode) { - emitEvent('changePositionAction', roomCode, { - currentChildId: item.childId, - newParentChildId: childId, - contextParam: contextParam - }); - - // console.log( - // 'emit changePosition event is triggered in DirectChildHTMLNestable' - // ); - } - } - } - }, - - collect: (monitor: any) => { - return { - isOver: !!monitor.isOver({ shallow: true }) - }; - } - }); - - const changeFocusFunction = (componentId: number, childId: number | null) => { - dispatch(changeFocus({ componentId, childId })); - if (roomCode) { - emitEvent('changeFocusAction', roomCode, { - componentId: componentId, - childId: childId - }); - // console.log('emit focus event from DirectChildHTMLNestable'); - } - }; - - // onClickHandler is responsible for changing the focused component and child component - function onClickHandler(event) { - event.stopPropagation(); - changeFocusFunction(state.canvasFocus.componentId, childId); - } - - // combine all styles so that higher priority style specifications overrule lower priority style specifications - // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling - const defaultNestableStyle = { ...globalDefaultStyle }; - const interactiveStyle = { - border: - state.canvasFocus.childId === childId - ? '2px solid #0671e3' - : '1px solid #31343A' - }; - - // interactive style to change color when nested element is hovered over - if (isOver) defaultNestableStyle['#3c59ba']; - defaultNestableStyle['backgroundColor'] = isOver - ? '#3c59ba' - : defaultNestableStyle['backgroundColor']; - - const combinedStyle = combineStyles( - combineStyles(combineStyles(defaultNestableStyle, HTMLType.style), style), - interactiveStyle - ); - - drag(drop(ref)); - - const routeButton = []; - if (typeId === 17) { - routeButton.push(); - } - if (typeId === 19) { - routeButton.push( - - ); - } - - return ( -
    - - - {HTMLType.placeHolderShort} - - - {attributes && attributes.compLink ? ` ${attributes.compLink}` : ''} - - {routeButton} - - - {renderChildren(children)} -
    - ); -} - -export default DirectChildHTMLNestable; +import React, { useRef } from 'react'; +import { ChildElement, HTMLType } from '../../interfaces/Interfaces'; +import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; +import { ItemTypes } from '../../constants/ItemTypes'; +import { combineStyles } from '../../helperFunctions/combineStyles'; +import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; +import renderChildren from '../../helperFunctions/renderChildren'; +import DeleteButton from './DeleteButton'; +import validateNewParent from '../../helperFunctions/changePositionValidation'; +import componentNest from '../../helperFunctions/componentNestValidation'; +import AddRoute from './AddRoute'; +import AddLink from './AddLink'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { emitEvent } from '../../helperFunctions/socket'; + +import { + changeFocus, + changePosition, + addChild, + snapShotAction +} from '../../redux/reducers/slice/appStateSlice'; + +function DirectChildHTMLNestable({ + childId, + type, + typeId, + style, + children, + name, + attributes +}: ChildElement): React.JSX.Element { + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + + const dispatch = useDispatch(); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const ref = useRef(null); + + // takes a snapshot of state to be used in UNDO and REDO cases. snapShotFunc is also invoked in Canvas.tsx + const snapShotFunc = () => { + //makes a deep clone of state + const deepCopiedState = JSON.parse(JSON.stringify(state)); + const focusIndex = state.canvasFocus.componentId - 1; + //pushes the last user action on the canvas into the past array of Component + dispatch( + snapShotAction({ + focusIndex: focusIndex, + deepCopiedState: deepCopiedState + }) + ); + }; + + // find the HTML element corresponding with this instance of an HTML element + // find the current component to render on the canvas + const HTMLType: HTMLType = state.HTMLTypes.find( + (type: HTMLType) => type.id === typeId + ); + + // hook that allows component to be draggable + const [{ isDragging }, drag] = useDrag({ + // setting item attributes to be referenced when updating state with new instance of dragged item + item: { + type: ItemTypes.INSTANCE, + newInstance: false, + childId: childId, + instanceType: type, + instanceTypeId: typeId, + name: name + }, + canDrag: HTMLType.id !== 1000, // dragging not permitted if element is separator + collect: (monitor: any) => { + return { + isDragging: !!monitor.isDragging() + }; + } + }); + + // both useDrop and useDrag used here to allow canvas components to be both a drop target and drag source + const [{ isOver }, drop] = useDrop({ + accept: ItemTypes.INSTANCE, + // triggered on drop + drop: (item: any, monitor: DropTargetMonitor) => { + const didDrop = monitor.didDrop(); + // takes a snapshot of state to be used in UNDO and REDO cases + snapShotFunc(); + if (didDrop) { + return; + } + // updates state with new instance + // if item dropped is going to be a new instance (i.e. it came from the left panel), then create a new child component + if (item.newInstance) { + if ( + (item.instanceType === 'Component' && + componentNest( + state.components[item.instanceTypeId - 1].children, + childId + )) || + item.instanceType !== 'Component' + ) { + dispatch( + addChild({ + type: item.instanceType, + typeId: item.instanceTypeId, + childId: childId, + contextParam: contextParam + }) + ); + if (roomCode) { + emitEvent('addChildAction', roomCode, { + type: item.instanceType, + typeId: item.instanceTypeId, + childId: childId, + contextParam: contextParam + }); + + // console.log( + // 'emit addChildAction event is triggered in DirectChildHTMLNestable' + // ); + } + } + } + // if item is not a new instance, change position of element dragged inside div so that the div is the new parent + else { + // check to see if the selected child is trying to nest within itself + if (validateNewParent(state, item.childId, childId) === true) { + dispatch( + changePosition({ + currentChildId: item.childId, + newParentChildId: childId, + contextParam: contextParam + }) + ); + if (roomCode) { + emitEvent('changePositionAction', roomCode, { + currentChildId: item.childId, + newParentChildId: childId, + contextParam: contextParam + }); + + // console.log( + // 'emit changePosition event is triggered in DirectChildHTMLNestable' + // ); + } + } + } + }, + + collect: (monitor: any) => { + return { + isOver: !!monitor.isOver({ shallow: true }) + }; + } + }); + + const changeFocusFunction = (componentId: number, childId: number | null) => { + dispatch(changeFocus({ componentId, childId })); + if (roomCode) { + emitEvent('changeFocusAction', roomCode, { + componentId: componentId, + childId: childId + }); + // console.log('emit focus event from DirectChildHTMLNestable'); + } + }; + + // onClickHandler is responsible for changing the focused component and child component + function onClickHandler(event) { + event.stopPropagation(); + changeFocusFunction(state.canvasFocus.componentId, childId); + } + + // combine all styles so that higher priority style specifications overrule lower priority style specifications + // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling + const defaultNestableStyle = { ...globalDefaultStyle }; + const interactiveStyle = { + border: + state.canvasFocus.childId === childId + ? '2px solid #0671e3' + : '1px solid #31343A' + }; + + // interactive style to change color when nested element is hovered over + if (isOver) defaultNestableStyle['#3c59ba']; + defaultNestableStyle['backgroundColor'] = isOver + ? '#3c59ba' + : defaultNestableStyle['backgroundColor']; + + const combinedStyle = combineStyles( + combineStyles(combineStyles(defaultNestableStyle, HTMLType.style), style), + interactiveStyle + ); + + drag(drop(ref)); + + const routeButton = []; + if (typeId === 17) { + routeButton.push(); + } + if (typeId === 19) { + routeButton.push( + + ); + } + + return ( +
    + + + {HTMLType.placeHolderShort} + + + {attributes && attributes.compLink ? ` ${attributes.compLink}` : ''} + + {routeButton} + + + {renderChildren(children)} +
    + ); +} + +export default DirectChildHTMLNestable; diff --git a/app/src/components/main/IndirectChild.tsx b/app/src/components/main/IndirectChild.tsx index 337c3359e..056062db3 100644 --- a/app/src/components/main/IndirectChild.tsx +++ b/app/src/components/main/IndirectChild.tsx @@ -1,51 +1,51 @@ -import React from 'react'; -import { combineStyles } from '../../helperFunctions/combineStyles'; -import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; -import { Component } from '../../interfaces/Interfaces'; -import DeleteButton from './DeleteButton'; -import { useDispatch, useSelector } from 'react-redux'; -import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; - -function IndirectChild({ - style, - children, - placeHolder, - linkId, - childId, - name -}) { - const state = useSelector((store:RootState) => store.appState); - const dispatch = useDispatch(); - let combinedStyle = combineStyles(globalDefaultStyle, style); - // when a user clicks a link, the focus should change to that component - function onClickHandlerRoute(event) { - event.stopPropagation(); - dispatch(changeFocus({ componentId: linkId, childId: null})); - } - let linkName: string; - // if there's a link in this component, then include a link - if (linkId) { - linkName = state.components.find( - (comp: Component) => comp.id === linkId - ).name; - combinedStyle = combineStyles(combinedStyle, { color: 'blue' }); - } - - return ( -
    - {` ( ${childId} )`} - - - - {linkId ? ( -
    {linkName}
    - ) : ( - placeHolder - )} - {children} -
    - ); -} - -export default IndirectChild; +import React from 'react'; +import { combineStyles } from '../../helperFunctions/combineStyles'; +import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; +import { Component } from '../../interfaces/Interfaces'; +import DeleteButton from './DeleteButton'; +import { useDispatch, useSelector } from 'react-redux'; +import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; + +function IndirectChild({ + style, + children, + placeHolder, + linkId, + childId, + name +}) { + const state = useSelector((store:RootState) => store.appState); + const dispatch = useDispatch(); + let combinedStyle = combineStyles(globalDefaultStyle, style); + // when a user clicks a link, the focus should change to that component + function onClickHandlerRoute(event) { + event.stopPropagation(); + dispatch(changeFocus({ componentId: linkId, childId: null})); + } + let linkName: string; + // if there's a link in this component, then include a link + if (linkId) { + linkName = state.components.find( + (comp: Component) => comp.id === linkId + ).name; + combinedStyle = combineStyles(combinedStyle, { color: 'blue' }); + } + + return ( +
    + {` ( ${childId} )`} + + + + {linkId ? ( +
    {linkName}
    + ) : ( + placeHolder + )} + {children} +
    + ); +} + +export default IndirectChild; diff --git a/app/src/components/main/RouteLink.tsx b/app/src/components/main/RouteLink.tsx index db9e1a120..0ddd2d2f1 100644 --- a/app/src/components/main/RouteLink.tsx +++ b/app/src/components/main/RouteLink.tsx @@ -1,76 +1,76 @@ -import React from 'react'; -import { Component, ChildElement } from '../../interfaces/Interfaces'; -import { useDrag } from 'react-dnd'; -import { ItemTypes } from '../../constants/ItemTypes'; -import { combineStyles } from '../../helperFunctions/combineStyles'; -import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; -import { useDispatch, useSelector } from 'react-redux'; -import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; - -function RouteLink({ - childId, - type, - typeId, - style -}: ChildElement): JSX.Element { - const state = useSelector((store: RootState) => store.appState); - const dispatch = useDispatch(); - - // find the name of the Component corresponding with this link - const routeName: string = state.components.find( - (comp: Component) => comp.id === typeId - ).name; - - // hook that allows component to be draggable - const [{ isDragging }, drag] = useDrag({ - // setting item attributes to be referenced when updating state with new instance of dragged item - item: { - type: ItemTypes.INSTANCE, - newInstance: false, - childId: childId, - instanceType: type, - instanceTypeId: typeId - }, - collect: (monitor: any) => ({ - isDragging: !!monitor.isDragging() - }) - }); - const changeFocusFunction = (componentId: number, childId: number | null) => { - dispatch(changeFocus({ componentId, childId })); - }; - // onClickHandler is responsible for changing the focused component and child component - function onClickHandlerFocus(event) { - event.stopPropagation(); - changeFocusFunction(state.canvasFocus.componentId, childId); - } - // the route handler will change the focus of the canvas to the component referenced in the route link when the text is selected - function onClickHandlerRoute(event) { - event.stopPropagation(); - changeFocusFunction(typeId, null); - } - // combine all styles so that higher priority style specifications overrule lower priority style specifications - // priority order is 1) style directly set for this child (style), 2) style for the routeLink component, and 3) default styling - const routeStyle = { - color: 'blue' - }; - const interactiveStyle = { - border: - state.canvasFocus.childId === childId - ? '3px solid #a7cced' - : '1px Solid grey', - boxShadow: - state.canvasFocus.childId === childId ? '1px 5px 3px rgb(11,212,112)' : '' - }; - const combinedStyle = combineStyles( - combineStyles(combineStyles(globalDefaultStyle, routeStyle), style), - interactiveStyle - ); - return ( -
    -
    {routeName}
    -
    - ); -} - -export default RouteLink; +import React from 'react'; +import { Component, ChildElement } from '../../interfaces/Interfaces'; +import { useDrag } from 'react-dnd'; +import { ItemTypes } from '../../constants/ItemTypes'; +import { combineStyles } from '../../helperFunctions/combineStyles'; +import globalDefaultStyle from '../../public/styles/globalDefaultStyles'; +import { useDispatch, useSelector } from 'react-redux'; +import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; + +function RouteLink({ + childId, + type, + typeId, + style +}: ChildElement): JSX.Element { + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); + + // find the name of the Component corresponding with this link + const routeName: string = state.components.find( + (comp: Component) => comp.id === typeId + ).name; + + // hook that allows component to be draggable + const [{ isDragging }, drag] = useDrag({ + // setting item attributes to be referenced when updating state with new instance of dragged item + item: { + type: ItemTypes.INSTANCE, + newInstance: false, + childId: childId, + instanceType: type, + instanceTypeId: typeId + }, + collect: (monitor: any) => ({ + isDragging: !!monitor.isDragging() + }) + }); + const changeFocusFunction = (componentId: number, childId: number | null) => { + dispatch(changeFocus({ componentId, childId })); + }; + // onClickHandler is responsible for changing the focused component and child component + function onClickHandlerFocus(event) { + event.stopPropagation(); + changeFocusFunction(state.canvasFocus.componentId, childId); + } + // the route handler will change the focus of the canvas to the component referenced in the route link when the text is selected + function onClickHandlerRoute(event) { + event.stopPropagation(); + changeFocusFunction(typeId, null); + } + // combine all styles so that higher priority style specifications overrule lower priority style specifications + // priority order is 1) style directly set for this child (style), 2) style for the routeLink component, and 3) default styling + const routeStyle = { + color: 'blue' + }; + const interactiveStyle = { + border: + state.canvasFocus.childId === childId + ? '3px solid #a7cced' + : '1px Solid grey', + boxShadow: + state.canvasFocus.childId === childId ? '1px 5px 3px rgb(11,212,112)' : '' + }; + const combinedStyle = combineStyles( + combineStyles(combineStyles(globalDefaultStyle, routeStyle), style), + interactiveStyle + ); + return ( +
    +
    {routeName}
    +
    + ); +} + +export default RouteLink; diff --git a/app/src/components/marketplace/MarketplaceCard.tsx b/app/src/components/marketplace/MarketplaceCard.tsx index a83ea6dad..1f7140a20 100644 --- a/app/src/components/marketplace/MarketplaceCard.tsx +++ b/app/src/components/marketplace/MarketplaceCard.tsx @@ -1,226 +1,226 @@ -import { - Avatar, - Card, - CardActions, - CardContent, - CardHeader, - CardMedia, - Collapse, - IconButton, - Menu, - MenuItem, - Typography, - styled -} from '@mui/material'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import React, { useEffect } from 'react'; -import { - openProject, - resetAllState -} from '../../redux/reducers/slice/appStateSlice'; -import { useDispatch, useSelector } from 'react-redux'; -import { MoreVert } from '@mui/icons-material'; -import { RootState } from '../../redux/store'; -import Snackbar from '@mui/material/Snackbar'; -import axios from 'axios'; -// import imageSrc from '../../../../resources/marketplace_images/marketplace_image.png'; -import { red } from '@mui/material/colors'; -import { saveProject } from '../../helperFunctions/projectGetSaveDel'; -import { useHistory } from 'react-router-dom'; -import { Amplify, Storage } from 'aws-amplify'; -import awsconfig from '../../../../src/custom-aws-exports'; - -interface Project { - forked: String; - comments: string[]; - createdAt: Date; - likes: number; - name: string; - project: object; - published: boolean; - userId: number; - username: string; - _id: number; -} - -const ITEM_HEIGHT = 48; -const MarketplaceCard = ({ proj }: { proj: Project }) => { - const dispatch = useDispatch(); - const history = useHistory(); - const [anchorEl, setAnchorEl] = React.useState(null); - const [s3ImgURL, setS3ImgURL] = React.useState(null); - const open = Boolean(anchorEl); - const [alertOpen, setAlertOpen] = React.useState(false); - const [guest, setGuest] = React.useState(null); - const state = useSelector((store: RootState) => store.appState); - - - useEffect(() => { - async function s3ImgFetch() { - Amplify.configure(awsconfig); - try { - const objId: string = proj._id.toString(); - // the below functions are commented out as not to incur too many charges - const response: string = await Storage.get(objId); - // const response: string = await Storage.get('test'); - setS3ImgURL(response); - } catch (error) { - console.error(`Error fetching image preview for ${proj._id}: `, error); - } - } - s3ImgFetch(); - }, []); - - - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClone = async () => { - if(state.isLoggedIn){ - // creates a copy of the project - const docId = proj._id; - const response = await axios.get(`/cloneProject/${docId}`); //passing in username as a query param is query params - const project = response.data; - setAlertOpen(true); - setAnchorEl(null); - return { - _id: project._id, - name: project.name, - published: project.published, - ...project.project - }; - } - setGuest(true); - }; - - const handleCloneOpen = async () => { - if(state.isLoggedIn){ - const project = await handleClone(); - history.push('/'); - dispatch(openProject(project)); - } - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleAlertClose = ( - event: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === 'clickaway') { - return; - } - setAlertOpen(false); - setAnchorEl(null); - }; - - const Alert = React.forwardRef(function Alert( - props, - ref - ) { - return ; - }); - - return ( - <> - - - - {proj.username.slice(0,1).toUpperCase()} - - } - action={ - - - - } - title={proj.name} - subheader={proj.username} - /> - - - Clone - - - Clone and open - - - - - Project Cloned! - - - - - Please login to clone! - - - - - ); -}; - -export default MarketplaceCard; +import { + Avatar, + Card, + CardActions, + CardContent, + CardHeader, + CardMedia, + Collapse, + IconButton, + Menu, + MenuItem, + Typography, + styled +} from '@mui/material'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import React, { useEffect } from 'react'; +import { + openProject, + resetAllState +} from '../../redux/reducers/slice/appStateSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import { MoreVert } from '@mui/icons-material'; +import { RootState } from '../../redux/store'; +import Snackbar from '@mui/material/Snackbar'; +import axios from 'axios'; +// import imageSrc from '../../../../resources/marketplace_images/marketplace_image.png'; +import { red } from '@mui/material/colors'; +import { saveProject } from '../../helperFunctions/projectGetSaveDel'; +import { useHistory } from 'react-router-dom'; +import { Amplify, Storage } from 'aws-amplify'; +import awsconfig from '../../../../src/custom-aws-exports'; + +interface Project { + forked: String; + comments: string[]; + createdAt: Date; + likes: number; + name: string; + project: object; + published: boolean; + userId: number; + username: string; + _id: number; +} + +const ITEM_HEIGHT = 48; +const MarketplaceCard = ({ proj }: { proj: Project }) => { + const dispatch = useDispatch(); + const history = useHistory(); + const [anchorEl, setAnchorEl] = React.useState(null); + const [s3ImgURL, setS3ImgURL] = React.useState(null); + const open = Boolean(anchorEl); + const [alertOpen, setAlertOpen] = React.useState(false); + const [guest, setGuest] = React.useState(null); + const state = useSelector((store: RootState) => store.appState); + + + useEffect(() => { + async function s3ImgFetch() { + Amplify.configure(awsconfig); + try { + const objId: string = proj._id.toString(); + // the below functions are commented out as not to incur too many charges + const response: string = await Storage.get(objId); + // const response: string = await Storage.get('test'); + setS3ImgURL(response); + } catch (error) { + console.error(`Error fetching image preview for ${proj._id}: `, error); + } + } + s3ImgFetch(); + }, []); + + + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClone = async () => { + if(state.isLoggedIn){ + // creates a copy of the project + const docId = proj._id; + const response = await axios.get(`/cloneProject/${docId}`); //passing in username as a query param is query params + const project = response.data; + setAlertOpen(true); + setAnchorEl(null); + return { + _id: project._id, + name: project.name, + published: project.published, + ...project.project + }; + } + setGuest(true); + }; + + const handleCloneOpen = async () => { + if(state.isLoggedIn){ + const project = await handleClone(); + history.push('/'); + dispatch(openProject(project)); + } + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleAlertClose = ( + event: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === 'clickaway') { + return; + } + setAlertOpen(false); + setAnchorEl(null); + }; + + const Alert = React.forwardRef(function Alert( + props, + ref + ) { + return ; + }); + + return ( + <> + + + + {proj.username.slice(0,1).toUpperCase()} + + } + action={ + + + + } + title={proj.name} + subheader={proj.username} + /> + + + Clone + + + Clone and open + + + + + Project Cloned! + + + + + Please login to clone! + + + + + ); +}; + +export default MarketplaceCard; diff --git a/app/src/components/marketplace/MarketplaceCardContainer.tsx b/app/src/components/marketplace/MarketplaceCardContainer.tsx index 05a00a15f..e6509fe71 100644 --- a/app/src/components/marketplace/MarketplaceCardContainer.tsx +++ b/app/src/components/marketplace/MarketplaceCardContainer.tsx @@ -1,27 +1,27 @@ -import { Container, Grid } from '@mui/material'; - -import MarketplaceCard from './MarketplaceCard'; -import React from 'react'; - -const MarketplaceCardContainer = ({ displayProjects }) => { - return ( - <> - - - {displayProjects.map((proj, i) => ( - - - - ))} - - - - ); -}; - -export default MarketplaceCardContainer; +import { Container, Grid } from '@mui/material'; + +import MarketplaceCard from './MarketplaceCard'; +import React from 'react'; + +const MarketplaceCardContainer = ({ displayProjects }) => { + return ( + <> + + + {displayProjects.map((proj, i) => ( + + + + ))} + + + + ); +}; + +export default MarketplaceCardContainer; diff --git a/app/src/components/marketplace/Searchbar.tsx b/app/src/components/marketplace/Searchbar.tsx index 441f73d48..f2fa928d8 100644 --- a/app/src/components/marketplace/Searchbar.tsx +++ b/app/src/components/marketplace/Searchbar.tsx @@ -1,70 +1,70 @@ -import React, {useEffect, useState} from 'react'; -import TextField from '@mui/material/TextField'; - -const SearchBar = ({marketplaceProjects, updateDisplayProjects }):JSX.Element => { - //passing down all marketplaceProjects - const [searchText, setSearchText] = useState(''); - - const handleChange = (e) => { - const newText = e.target.value - - setSearchText(newText) - - } - - useEffect(()=>{ - if(searchText === ''){ - - updateDisplayProjects(marketplaceProjects) - return; - } - function searching () { - - //more strict pattern not currently used - //const patternString = '(?:^|[^a-zA-Z])' + searchText.toLowerCase() + '(?:$|[^a-zA-Z])'; - //(?: [non-capturing group] means to ignore the items inside the parens in terms of looking for that string order in the target - //^ refers to the literal beginning of a start of a line or string - //| pipe operator is an OR statement - //[^a-zA-Z] [] is grouping characters, the ^ at the beginning means to look for things that are not letters (lowercase or uppercase) - //a-zA-Z letters (lowercase and uppercase) and underscore symbol - //All together: match either the start/end of a line/string or any character that is not a letter. - //Looks for anything that has the searchText in between non-letter characters - const patternString2 = '(?:^|.)' + searchText.toLowerCase() + '(?:$|.)'; - //Only difference is that (?:^|.) (?:$|.) look for anything that matches the string in between any other characters for the username search - //test3 and 1test) would both match for string 'test' - - const searchResults = marketplaceProjects.reduce((results, curr) => { - const lowName = curr.name.toLowerCase() - const lowUsername = curr.username.toLowerCase() - if(lowName.match(patternString2) || lowUsername.match(patternString2)) - results.push(curr) - return results; - }, []) - updateDisplayProjects(searchResults) - } - - // simple debounce - const debounceTimer = setTimeout(() => { - searching(); - }, 800); - // clears the timer if searchText is changed before the setTimeout duration is reached - return () => clearTimeout(debounceTimer); - - }, [searchText]) - - - return ( - - - - ); -}; - -export default SearchBar; +import React, {useEffect, useState} from 'react'; +import TextField from '@mui/material/TextField'; + +const SearchBar = ({marketplaceProjects, updateDisplayProjects }):JSX.Element => { + //passing down all marketplaceProjects + const [searchText, setSearchText] = useState(''); + + const handleChange = (e) => { + const newText = e.target.value + + setSearchText(newText) + + } + + useEffect(()=>{ + if(searchText === ''){ + + updateDisplayProjects(marketplaceProjects) + return; + } + function searching () { + + //more strict pattern not currently used + //const patternString = '(?:^|[^a-zA-Z])' + searchText.toLowerCase() + '(?:$|[^a-zA-Z])'; + //(?: [non-capturing group] means to ignore the items inside the parens in terms of looking for that string order in the target + //^ refers to the literal beginning of a start of a line or string + //| pipe operator is an OR statement + //[^a-zA-Z] [] is grouping characters, the ^ at the beginning means to look for things that are not letters (lowercase or uppercase) + //a-zA-Z letters (lowercase and uppercase) and underscore symbol + //All together: match either the start/end of a line/string or any character that is not a letter. + //Looks for anything that has the searchText in between non-letter characters + const patternString2 = '(?:^|.)' + searchText.toLowerCase() + '(?:$|.)'; + //Only difference is that (?:^|.) (?:$|.) look for anything that matches the string in between any other characters for the username search + //test3 and 1test) would both match for string 'test' + + const searchResults = marketplaceProjects.reduce((results, curr) => { + const lowName = curr.name.toLowerCase() + const lowUsername = curr.username.toLowerCase() + if(lowName.match(patternString2) || lowUsername.match(patternString2)) + results.push(curr) + return results; + }, []) + updateDisplayProjects(searchResults) + } + + // simple debounce + const debounceTimer = setTimeout(() => { + searching(); + }, 800); + // clears the timer if searchText is changed before the setTimeout duration is reached + return () => clearTimeout(debounceTimer); + + }, [searchText]) + + + return ( + + + + ); +}; + +export default SearchBar; diff --git a/app/src/components/right/ComponentPanel.tsx b/app/src/components/right/ComponentPanel.tsx index f9ceb572c..4c7c269bf 100644 --- a/app/src/components/right/ComponentPanel.tsx +++ b/app/src/components/right/ComponentPanel.tsx @@ -1,374 +1,374 @@ -import { Button, Checkbox, FormControlLabel, InputLabel } from '@mui/material'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { RootState } from '../../redux/store'; -import TextField from '@mui/material/TextField'; -import { addComponent } from '../../redux/reducers/slice/appStateSlice'; -import makeStyles from '@mui/styles/makeStyles'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; -import { emitEvent } from '../../helperFunctions/socket'; - -// The component panel section of the left panel displays all components and has the ability to add new components -const ComponentPanel = ({ isThemeLight }): JSX.Element => { - const classes = useStyles(); - // const { state, contextParam } = useSelector((store: RootState) => ({ - // state: store.appState, - // contextParam: store.contextSlice - // })); - - const state = useSelector((store: RootState) => store.appState); - const contextParam = useSelector((store: RootState) => store.contextSlice); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - - const dispatch = useDispatch(); - - //state hooks for inputted component name, component id and array of components - const [errorStatus, setErrorStatus] = useState(false); - // errorMsg refers to the error message on the new component text field - const [errorMsg, setErrorMsg] = useState(''); - const [compName, setCompName] = useState(''); - const [isRoot, setIsRoot] = useState(false); - const [alertOpen, setAlertOpen] = React.useState(false); - - // function to create error message for component name input - const triggerError = (type: String) => { - setErrorStatus(true); - if (type === 'empty') { - setErrorMsg('Component name cannot be blank.'); - } else if (type === 'dupe') { - setErrorMsg('Component name already exists.'); - } else if (type === 'letters') { - setErrorMsg('Component name must start with a letter.'); - } else if (type === 'symbolsDetected') { - setErrorMsg('Component name must not contain symbols.'); - } else if (type === 'rootDupe') { - setErrorMsg('Component name cannot be root component name.'); - } - }; - - const handleNameInput = (e: React.ChangeEvent) => { - setErrorStatus(false); - setCompName(e.target.value); - }; - - // "Root" components are not draggable into the main canvas - // If next.js or Gatsby.js mode is on, root components will be separate pages - const toggleRootStatus = (e: React.ChangeEvent) => { - setIsRoot(isRoot ? false : true); - }; - - // Add a new component - const createOption = (inputName: String) => { - // format name so first letter is capitalized and there are no white spaces - let inputNameClean = inputName.replace(/\s+/g, ''); // removes spaces - const formattedName = - state.projectType === 'Classic React' - ? inputNameClean.charAt(0).toUpperCase() + inputNameClean.slice(1) // capitalizes first letter - : inputNameClean; - // add new component to state - dispatch( - addComponent({ - componentName: formattedName, - root: isRoot, - contextParam: contextParam - }) - ); - - if (roomCode) { - emitEvent('addComponentAction', roomCode, { - componentName: formattedName, - root: isRoot, - contextParam: contextParam - }); - } - - // reset root toggle back to default position - setIsRoot(false); - // reset name field - setCompName(''); - }; - - const handleNameSubmit = () => { - // creates a component if no error conditions triggered - let letters = /[a-zA-Z]/; - let error; - if (compName.trim() === '') { - error = 'empty'; - } else if (!compName.charAt(0).match(letters)) { - error = 'letters'; - } else if (!compName.match(/^[0-9a-zA-Z]+$/)) { - error = 'symbolsDetected'; - } else if ( - state.components.some( - (comp) => comp.name.toLowerCase() === compName.toLowerCase() - ) - ) { - error = 'dupe'; - } else if ( - compName.toLowerCase() === 'index' || - compName.toLowerCase() === 'app' - ) { - error = 'rootDupe'; - } else { - createOption(compName); - setErrorStatus(false); - setAlertOpen(true); - return; - } - triggerError(error); - }; - - const keyBindCreateComponent = useCallback((e) => { - if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') { - e.preventDefault(); - document.getElementById('addComponentButton').click(); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', keyBindCreateComponent); - return () => { - document.removeEventListener('keydown', keyBindCreateComponent); - }; - }, []); - - const handleAlertClose = ( - event: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === 'clickaway') { - return; - } - setAlertOpen(false); - }; - - const Alert = React.forwardRef(function Alert( - props, - ref - ) { - return ; - }); - - return ( - <> -
    - {/* Add a new component*/} -
    -

    - New Component -

    - {/* input for new component */} -
    -
    -
    - - Name - -
    - -
    -
    - -
    - setIsRoot(!isRoot)} - /> - } - label={ - state.projectType === 'Next.js' || - state.projectType === 'Gatsby.js' - ? 'Page' - : 'Root' - } // name varies depending on mode - className={ - isThemeLight - ? `${classes.rootCheckBoxLabel} ${classes.lightThemeFontColor}` - : `${classes.rootCheckBoxLabel} ${classes.darkThemeFontColor}` - } - labelPlacement="top" - /> -
    -
    -
    -
    - -
    -
    - -
    -
    - <> - - - Component Created! - - - - - ); -}; - -const useStyles = makeStyles({ - inputField: { - width: '500px', - marginTop: '10px', - whiteSpace: 'nowrap', - overflowX: 'hidden', - textOverflow: 'ellipsis', - margin: '0px 0px 0px 10px', - border: '0px solid grey' - }, - inputWrapper: { - textAlign: 'center', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: '15px' - }, - panelWrapper: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - flexGrow: 1, - color: '#000000' - }, - addComponentWrapper: { - padding: 'auto', - margin: '0 auto', - display: 'inline-block' - }, - rootCheckBox: { - borderColor: '#0671E3', - padding: '7px 0' - }, - rootCheckBoxLabel: { - borderColor: '#0671e3' - }, - newComponent: { - color: '#C6C6C6', - marginBottom: '25px' - }, - inputLabel: { - fontSize: '1em', - marginLeft: '10px' - }, - btnGroup: { - display: 'flex', - flexDirection: 'column' - }, - addComponentButton: { - backgroundColor: 'transparent', - height: '40px', - width: '100px', - fontFamily: 'Roboto, Raleway, sans-serif', - fontSize: '90%', - textAlign: 'center', - borderStyle: 'none', - transition: '0.3s', - borderRadius: '25px', - marginRight: '65px' - }, - rootToggle: { - color: '#696969', - fontSize: '0.85rem' - }, - lightThemeFontColor: { - color: 'white', - '& .MuiInputBase-root': { - color: 'rgba (0, 0, 0, 0.54)' - } - }, - darkThemeFontColor: { - color: '#fff', - '& .MuiInputBase-root': { - color: '#fff' - } - } -}); - -export default ComponentPanel; +import { Button, Checkbox, FormControlLabel, InputLabel } from '@mui/material'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { RootState } from '../../redux/store'; +import TextField from '@mui/material/TextField'; +import { addComponent } from '../../redux/reducers/slice/appStateSlice'; +import makeStyles from '@mui/styles/makeStyles'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import { emitEvent } from '../../helperFunctions/socket'; + +// The component panel section of the left panel displays all components and has the ability to add new components +const ComponentPanel = ({ isThemeLight }): JSX.Element => { + const classes = useStyles(); + // const { state, contextParam } = useSelector((store: RootState) => ({ + // state: store.appState, + // contextParam: store.contextSlice + // })); + + const state = useSelector((store: RootState) => store.appState); + const contextParam = useSelector((store: RootState) => store.contextSlice); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + + const dispatch = useDispatch(); + + //state hooks for inputted component name, component id and array of components + const [errorStatus, setErrorStatus] = useState(false); + // errorMsg refers to the error message on the new component text field + const [errorMsg, setErrorMsg] = useState(''); + const [compName, setCompName] = useState(''); + const [isRoot, setIsRoot] = useState(false); + const [alertOpen, setAlertOpen] = React.useState(false); + + // function to create error message for component name input + const triggerError = (type: String) => { + setErrorStatus(true); + if (type === 'empty') { + setErrorMsg('Component name cannot be blank.'); + } else if (type === 'dupe') { + setErrorMsg('Component name already exists.'); + } else if (type === 'letters') { + setErrorMsg('Component name must start with a letter.'); + } else if (type === 'symbolsDetected') { + setErrorMsg('Component name must not contain symbols.'); + } else if (type === 'rootDupe') { + setErrorMsg('Component name cannot be root component name.'); + } + }; + + const handleNameInput = (e: React.ChangeEvent) => { + setErrorStatus(false); + setCompName(e.target.value); + }; + + // "Root" components are not draggable into the main canvas + // If next.js or Gatsby.js mode is on, root components will be separate pages + const toggleRootStatus = (e: React.ChangeEvent) => { + setIsRoot(isRoot ? false : true); + }; + + // Add a new component + const createOption = (inputName: String) => { + // format name so first letter is capitalized and there are no white spaces + let inputNameClean = inputName.replace(/\s+/g, ''); // removes spaces + const formattedName = + state.projectType === 'Classic React' + ? inputNameClean.charAt(0).toUpperCase() + inputNameClean.slice(1) // capitalizes first letter + : inputNameClean; + // add new component to state + dispatch( + addComponent({ + componentName: formattedName, + root: isRoot, + contextParam: contextParam + }) + ); + + if (roomCode) { + emitEvent('addComponentAction', roomCode, { + componentName: formattedName, + root: isRoot, + contextParam: contextParam + }); + } + + // reset root toggle back to default position + setIsRoot(false); + // reset name field + setCompName(''); + }; + + const handleNameSubmit = () => { + // creates a component if no error conditions triggered + let letters = /[a-zA-Z]/; + let error; + if (compName.trim() === '') { + error = 'empty'; + } else if (!compName.charAt(0).match(letters)) { + error = 'letters'; + } else if (!compName.match(/^[0-9a-zA-Z]+$/)) { + error = 'symbolsDetected'; + } else if ( + state.components.some( + (comp) => comp.name.toLowerCase() === compName.toLowerCase() + ) + ) { + error = 'dupe'; + } else if ( + compName.toLowerCase() === 'index' || + compName.toLowerCase() === 'app' + ) { + error = 'rootDupe'; + } else { + createOption(compName); + setErrorStatus(false); + setAlertOpen(true); + return; + } + triggerError(error); + }; + + const keyBindCreateComponent = useCallback((e) => { + if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') { + e.preventDefault(); + document.getElementById('addComponentButton').click(); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', keyBindCreateComponent); + return () => { + document.removeEventListener('keydown', keyBindCreateComponent); + }; + }, []); + + const handleAlertClose = ( + event: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === 'clickaway') { + return; + } + setAlertOpen(false); + }; + + const Alert = React.forwardRef(function Alert( + props, + ref + ) { + return ; + }); + + return ( + <> +
    + {/* Add a new component*/} +
    +

    + New Component +

    + {/* input for new component */} +
    +
    +
    + + Name + +
    + +
    +
    + +
    + setIsRoot(!isRoot)} + /> + } + label={ + state.projectType === 'Next.js' || + state.projectType === 'Gatsby.js' + ? 'Page' + : 'Root' + } // name varies depending on mode + className={ + isThemeLight + ? `${classes.rootCheckBoxLabel} ${classes.lightThemeFontColor}` + : `${classes.rootCheckBoxLabel} ${classes.darkThemeFontColor}` + } + labelPlacement="top" + /> +
    +
    +
    +
    + +
    +
    + +
    +
    + <> + + + Component Created! + + + + + ); +}; + +const useStyles = makeStyles({ + inputField: { + width: '500px', + marginTop: '10px', + whiteSpace: 'nowrap', + overflowX: 'hidden', + textOverflow: 'ellipsis', + margin: '0px 0px 0px 10px', + border: '0px solid grey' + }, + inputWrapper: { + textAlign: 'center', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: '15px' + }, + panelWrapper: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexGrow: 1, + color: '#000000' + }, + addComponentWrapper: { + padding: 'auto', + margin: '0 auto', + display: 'inline-block' + }, + rootCheckBox: { + borderColor: '#0671E3', + padding: '7px 0' + }, + rootCheckBoxLabel: { + borderColor: '#0671e3' + }, + newComponent: { + color: '#C6C6C6', + marginBottom: '25px' + }, + inputLabel: { + fontSize: '1em', + marginLeft: '10px' + }, + btnGroup: { + display: 'flex', + flexDirection: 'column' + }, + addComponentButton: { + backgroundColor: 'transparent', + height: '40px', + width: '100px', + fontFamily: 'Roboto, Raleway, sans-serif', + fontSize: '90%', + textAlign: 'center', + borderStyle: 'none', + transition: '0.3s', + borderRadius: '25px', + marginRight: '65px' + }, + rootToggle: { + color: '#696969', + fontSize: '0.85rem' + }, + lightThemeFontColor: { + color: 'white', + '& .MuiInputBase-root': { + color: 'rgba (0, 0, 0, 0.54)' + } + }, + darkThemeFontColor: { + color: '#fff', + '& .MuiInputBase-root': { + color: '#fff' + } + } +}); + +export default ComponentPanel; diff --git a/app/src/components/right/ComponentPanelItem.tsx b/app/src/components/right/ComponentPanelItem.tsx index a6fc640ee..985a67131 100644 --- a/app/src/components/right/ComponentPanelItem.tsx +++ b/app/src/components/right/ComponentPanelItem.tsx @@ -70,9 +70,9 @@ const ComponentPanelItem: React.FC<{ borderRadius: '10px', borderColor: '#2D313A', margin: '5px 0px', - width: '10rem', - height: '3rem', - position: 'relative', + width: '10rem', + height: '3rem', + position: 'relative' }} > {isFocus &&
    } @@ -95,16 +95,16 @@ const ComponentPanelItem: React.FC<{ const useStyles = makeStyles({ nameContainer: { display: 'flex', - alignItems: 'center', + alignItems: 'center' }, focusMark: { - border: '2px solid #0671e3', - borderRadius: '5%', - position: 'absolute', - top: '0', - left: '0', - right: '0', - bottom: '0', + border: '2px solid #0671e3', + borderRadius: '5%', + position: 'absolute', + top: '0', + left: '0', + right: '0', + bottom: '0' }, lightTheme: { color: 'rgba (0, 0, 0, 0.54)' diff --git a/app/src/components/right/ComponentPanelRoutingItem.tsx b/app/src/components/right/ComponentPanelRoutingItem.tsx index 006bfae75..cfe124034 100644 --- a/app/src/components/right/ComponentPanelRoutingItem.tsx +++ b/app/src/components/right/ComponentPanelRoutingItem.tsx @@ -1,134 +1,134 @@ -import React, { useState } from 'react'; - -import Grid from '@mui/material/Grid'; -import { ItemTypes } from '../../constants/ItemTypes'; -// ------------------------------------------------ -import MenuItem from '@mui/material/MenuItem'; -import { RootState } from '../../redux/store'; -import Select from '@mui/material/Select'; -import makeStyles from '@mui/styles/makeStyles'; -import { useDrag } from 'react-dnd'; -import { useSelector } from 'react-redux'; - -// ------------------------------------------------ - -/* -N.B.: RENDERED ONLY IN NEXT.JS MODE - -DESCRIPTION: This is the box beneath the "Navigation" heading. It allows insertion of links - ("routing items") between pages (which are listed in the "Pages" menu, located above in the app). -First, this component gathers all Pages (as listed in the Pages menu) and puts them in an - array of names of those Pages (navigableComponents). -Next, it sets route (hook state) to the first value in navigableComponents and checks whether - that value (referencedComponent) still exists in the app's central state (Redux). If it does, - the variable routeId is set to the id property of referencedComponent. If it doesn't, - referencedComponent is replaced by index (the only Page guaranteed to exist) in navigableComponents. -Dragging works in the same manner as in ComponentPanelItem.tsx -*/ -// a component panel routing item is a Next.js component that allows the user to navigate between pages -const ComponentPanelRoutingItem: React.FC<{}> = () => { - const classes = useStyles(); - ('s there, '); - const state = useSelector((store: RootState) => store.appState); - - // find the root components that can be associated with a route - // These will be the components that are displayed in the dropdown adjacent to "Route Link" - let navigableComponents = state.components - .filter((comp) => state.rootComponents.includes(comp.id)) - .map((comp) => comp.name); - - // set state for the route currently selected in the dropdown - const [route, setRoute] = useState(navigableComponents[0]); - - let routeId; - // check if the component in the drop down still references an existing component - const referencedComponent = state.components.find( - (comp) => comp.name === route - ); - // if so, set the route id for that component to the id of the referenced component - if (referencedComponent) routeId = referencedComponent.id; - // otherwise, set the component name and and id to the root component - else { - setRoute(state.components[0].name); - routeId = 1; - } - // on switching to another Page in the dropdown menu, update hook state - const handleRouteChange = (event) => { - setRoute(event.target.value); - }; - // useDrag hook allows components in left panel to be drag source - const [{ isDragging }, drag] = useDrag({ - item: { - type: ItemTypes.INSTANCE, - newInstance: true, - instanceType: 'Route Link', - instanceTypeId: routeId - }, - canDrag: true, - collect: (monitor: any) => ({ - isDragging: !!monitor.isDragging() - }) - }); - - return ( - - {/* Route Link component */} -
    -

    Route Link

    - {/* Select is the dropdown menu */} - -
    -
    - ); -}; - -const useStyles = makeStyles({ - activeFocus: { - backgroundColor: '#808080' - }, - focusMark: { - backgroundColor: '#808080', - position: 'absolute', - width: '12px', - height: '12px', - borderRadius: '12px', - left: '-35px', - top: '30px' - }, - routeSelector: { - backgroundColor: '#808080', - marginLeft: '20px', - color: '#fff', - height: '60%', - alignSelf: 'center', - minWidth: '100px' - } -}); - -export default ComponentPanelRoutingItem; +import React, { useState } from 'react'; + +import Grid from '@mui/material/Grid'; +import { ItemTypes } from '../../constants/ItemTypes'; +// ------------------------------------------------ +import MenuItem from '@mui/material/MenuItem'; +import { RootState } from '../../redux/store'; +import Select from '@mui/material/Select'; +import makeStyles from '@mui/styles/makeStyles'; +import { useDrag } from 'react-dnd'; +import { useSelector } from 'react-redux'; + +// ------------------------------------------------ + +/* +N.B.: RENDERED ONLY IN NEXT.JS MODE + +DESCRIPTION: This is the box beneath the "Navigation" heading. It allows insertion of links + ("routing items") between pages (which are listed in the "Pages" menu, located above in the app). +First, this component gathers all Pages (as listed in the Pages menu) and puts them in an + array of names of those Pages (navigableComponents). +Next, it sets route (hook state) to the first value in navigableComponents and checks whether + that value (referencedComponent) still exists in the app's central state (Redux). If it does, + the variable routeId is set to the id property of referencedComponent. If it doesn't, + referencedComponent is replaced by index (the only Page guaranteed to exist) in navigableComponents. +Dragging works in the same manner as in ComponentPanelItem.tsx +*/ +// a component panel routing item is a Next.js component that allows the user to navigate between pages +const ComponentPanelRoutingItem: React.FC<{}> = () => { + const classes = useStyles(); + ('s there, '); + const state = useSelector((store: RootState) => store.appState); + + // find the root components that can be associated with a route + // These will be the components that are displayed in the dropdown adjacent to "Route Link" + let navigableComponents = state.components + .filter((comp) => state.rootComponents.includes(comp.id)) + .map((comp) => comp.name); + + // set state for the route currently selected in the dropdown + const [route, setRoute] = useState(navigableComponents[0]); + + let routeId; + // check if the component in the drop down still references an existing component + const referencedComponent = state.components.find( + (comp) => comp.name === route + ); + // if so, set the route id for that component to the id of the referenced component + if (referencedComponent) routeId = referencedComponent.id; + // otherwise, set the component name and and id to the root component + else { + setRoute(state.components[0].name); + routeId = 1; + } + // on switching to another Page in the dropdown menu, update hook state + const handleRouteChange = (event) => { + setRoute(event.target.value); + }; + // useDrag hook allows components in left panel to be drag source + const [{ isDragging }, drag] = useDrag({ + item: { + type: ItemTypes.INSTANCE, + newInstance: true, + instanceType: 'Route Link', + instanceTypeId: routeId + }, + canDrag: true, + collect: (monitor: any) => ({ + isDragging: !!monitor.isDragging() + }) + }); + + return ( + + {/* Route Link component */} +
    +

    Route Link

    + {/* Select is the dropdown menu */} + +
    +
    + ); +}; + +const useStyles = makeStyles({ + activeFocus: { + backgroundColor: '#808080' + }, + focusMark: { + backgroundColor: '#808080', + position: 'absolute', + width: '12px', + height: '12px', + borderRadius: '12px', + left: '-35px', + top: '30px' + }, + routeSelector: { + backgroundColor: '#808080', + marginLeft: '20px', + color: '#fff', + height: '60%', + alignSelf: 'center', + minWidth: '100px' + } +}); + +export default ComponentPanelRoutingItem; diff --git a/app/src/components/right/DeleteProjects.tsx b/app/src/components/right/DeleteProjects.tsx index 788f1b24e..6b7554e54 100644 --- a/app/src/components/right/DeleteProjects.tsx +++ b/app/src/components/right/DeleteProjects.tsx @@ -1,185 +1,185 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Button from '@mui/material/Button'; -import Avatar from '@mui/material/Avatar'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemAvatar from '@mui/material/ListItemAvatar'; -import ListItemText from '@mui/material/ListItemText'; -import DialogTitle from '@mui/material/DialogTitle'; -import Dialog from '@mui/material/Dialog'; -import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; -import { blue } from '@mui/material/colors'; -import { - getProjects, - deleteProject -} from '../../helperFunctions/projectGetSaveDel'; -import localforage from 'localforage'; -import { useSelector, useDispatch } from 'react-redux'; -import { - setInitialState, - initialState -} from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; -export interface ProjectDialogProps { - deleteAlert: () => void; - open: boolean; - projects: Array; - onClose: () => void; -} -// The options to be rendered when dialog is open -function ProjectsDialog(props: ProjectDialogProps) { - const classes = useStyles(); - const { onClose, open, projects, deleteAlert } = props; - const state = useSelector((store: RootState) => store.appState); - const dispatch = useDispatch(); - - // If no projects selected, keep the name of the current displayed - const handleClose = () => { - // onClose(selectedValue); - onClose(); - }; - - // If new project selected, close and set value to new project name - const handleDelete = (value: string) => { - const selectedProject = projects.filter( - (project: any) => project._id === value - )[0]; - deleteProject(selectedProject); - // localforage.removeItem(window.localStorage.getItem('ssid')); - dispatch(setInitialState(initialState)); - deleteAlert(); - onClose(); - }; - - return ( - <> - - - DELETE PROJECTS - - - User Projects - - - {projects - .filter( - (project: any) => - project.forked === undefined || project.forked === false - ) - .map((project: any, index: number) => ( - handleDelete(project._id)} - key={index} - > - - - - - - - - ))} - - - {' '} - Marketplace Projects - - - {projects - .filter((project: any) => project.forked === true) - .map((project: any, index: number) => ( - handleDelete(project._id)} - key={index} - > - - - - - - - - ))} - - - - ); -} - -export default function ProjectsFolder(props) { - const [open, setOpen] = useState(false); - const [projects, setProjects] = useState([{ hello: 'cat' }]); - - const handleClickOpen = () => { - getProjects().then((data) => { - if (data) { - setProjects(data); - setOpen(true); - } - }); - }; - - const handleClose = () => { - setOpen(false); - }; - - const keyBindDeleteProject = useCallback((e) => { - if ( - (e.key === 'Backspace' && e.metaKey) || - (e.key === 'Backspace' && e.ctrlKey) - ) { - e.preventDefault(); - handleClickOpen(); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', keyBindDeleteProject); - return () => { - document.removeEventListener('keydown', keyBindDeleteProject); - }; - }, []); - - return ( -
    - - -
    - ); -} - -const useStyles = makeStyles({ - button: { - width: '55%', - backgroundColor: 'rgba(1,212,109,0.1)', - fontSize: '1em', - minWidth: '300px', - marginTop: '10px', - marginBottom: '10px' - }, - avatar: { - backgroundColor: blue[100], - color: blue[600] - } -}); +import React, { useState, useCallback, useEffect } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import Button from '@mui/material/Button'; +import Avatar from '@mui/material/Avatar'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemText from '@mui/material/ListItemText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Dialog from '@mui/material/Dialog'; +import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; +import { blue } from '@mui/material/colors'; +import { + getProjects, + deleteProject +} from '../../helperFunctions/projectGetSaveDel'; +import localforage from 'localforage'; +import { useSelector, useDispatch } from 'react-redux'; +import { + setInitialState, + initialState +} from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +export interface ProjectDialogProps { + deleteAlert: () => void; + open: boolean; + projects: Array; + onClose: () => void; +} +// The options to be rendered when dialog is open +function ProjectsDialog(props: ProjectDialogProps) { + const classes = useStyles(); + const { onClose, open, projects, deleteAlert } = props; + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); + + // If no projects selected, keep the name of the current displayed + const handleClose = () => { + // onClose(selectedValue); + onClose(); + }; + + // If new project selected, close and set value to new project name + const handleDelete = (value: string) => { + const selectedProject = projects.filter( + (project: any) => project._id === value + )[0]; + deleteProject(selectedProject); + // localforage.removeItem(window.localStorage.getItem('ssid')); + dispatch(setInitialState(initialState)); + deleteAlert(); + onClose(); + }; + + return ( + <> + + + DELETE PROJECTS + + + User Projects + + + {projects + .filter( + (project: any) => + project.forked === undefined || project.forked === false + ) + .map((project: any, index: number) => ( + handleDelete(project._id)} + key={index} + > + + + + + + + + ))} + + + {' '} + Marketplace Projects + + + {projects + .filter((project: any) => project.forked === true) + .map((project: any, index: number) => ( + handleDelete(project._id)} + key={index} + > + + + + + + + + ))} + + + + ); +} + +export default function ProjectsFolder(props) { + const [open, setOpen] = useState(false); + const [projects, setProjects] = useState([{ hello: 'cat' }]); + + const handleClickOpen = () => { + getProjects().then((data) => { + if (data) { + setProjects(data); + setOpen(true); + } + }); + }; + + const handleClose = () => { + setOpen(false); + }; + + const keyBindDeleteProject = useCallback((e) => { + if ( + (e.key === 'Backspace' && e.metaKey) || + (e.key === 'Backspace' && e.ctrlKey) + ) { + e.preventDefault(); + handleClickOpen(); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', keyBindDeleteProject); + return () => { + document.removeEventListener('keydown', keyBindDeleteProject); + }; + }, []); + + return ( +
    + + +
    + ); +} + +const useStyles = makeStyles({ + button: { + width: '55%', + backgroundColor: 'rgba(1,212,109,0.1)', + fontSize: '1em', + minWidth: '300px', + marginTop: '10px', + marginBottom: '10px' + }, + avatar: { + backgroundColor: blue[100], + color: blue[600] + } +}); diff --git a/app/src/components/right/ExportButton.tsx b/app/src/components/right/ExportButton.tsx index 88c647c35..d5d32ec5d 100644 --- a/app/src/components/right/ExportButton.tsx +++ b/app/src/components/right/ExportButton.tsx @@ -1,212 +1,212 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -//these 3 lines below are for the electron version -import GetAppIcon from '@mui/icons-material/GetApp'; -import Button from '@mui/material/Button'; -import exportProject from '../../utils/exportProject.util'; -import createModal from './createModal'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; -import zipFiles from '../../helperFunctions/zipFiles'; - -export default function ExportButton() { - const [modal, setModal] = useState(null); - const state = useSelector((store: RootState) => store.appState); - - const genOptions: string[] = ['Export components']; - - // Closes out the open modal - const closeModal = () => setModal(''); - - const showGenerateAppModal = () => { - const children = ( - - {genOptions.map((option: string, i: number) => ( - chooseGenOptions()} - style={{ - border: '1px solid #3c59ba', - marginBottom: '2%', - marginTop: '5%' - }} - > - - - ))} - - ); - - const chooseGenOptions = () => { - zipFiles(state); - closeModal(); - }; - - setModal( - createModal({ - closeModal, - children, - message: 'Click to download in zip file:', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - const exportKeyBind = useCallback((e) => { - //Export Project - (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey) - ? showGenerateAppModal() - : ''; - }, []); - - useEffect(() => { - document.addEventListener('keydown', exportKeyBind); - return () => { - document.removeEventListener('keydown', exportKeyBind); - }; - }, []); - return ( -
    - - {modal} -
    - ); -} - -// This code is commented out below for v16, it is export function for electron, which may be useful for future devs -//The below code is exclusive to ReacType's Electron App -//If you would like to deploy the app, please comment out the exportButton function above and uncomment the code below - -// export default function ExportButton() { -// const [modal, setModal] = useState(null); -// const state = useSelector(store => store.appState) - -// const genOptions: string[] = [ -// 'Export components', -// 'Export components with application files' -// ]; -// let genOption = 0; - -// // Closes out the open modal -// const closeModal = () => setModal(''); - -// const showGenerateAppModal = () => { -// const children = ( -// -// {genOptions.map((option: string, i: number) => ( -// chooseGenOptions(i)} -// style={{ -// border: '1px solid #3f51b5', -// marginBottom: '2%', -// marginTop: '5%' -// }} -// > -// -// -// ))} -// -// -// -// -// -// ); -// let testchecked = 0; -// // helper function called by showGenerateAppModal -// // this function will prompt the user to choose an app directory once they've chosen their export option -// const chooseGenOptions = (genOpt: number) => { -// // set export option: 0 --> export only components, 1 --> export full project -// genOption = genOpt; - -// //This is exclusive to the electron app -// window.api.chooseAppDir(); -// testchecked = document.getElementById('tests').checked; -// testerFunc(); -// closeModal(); -// }; - -// // removes all listeners for the app_dir_selected event -// // this is important because otherwise listeners will pile up and events will trigger multiple events -// //This is exclusive to the electron app -// window.api.removeAllAppDirChosenListeners(); - -// // add listener for when an app directory is chosen -// // when a directory is chosen, the callback will export the project to the chosen folder -// // Note: this listener is imported from the main process via preload.js - -// // This is exclusive to the electron app -// window.api.addAppDirChosenListener(path => { -// exportProject( -// path, -// state.name -// ? state.name -// : 'New_ReacType_Project_' + Math.ceil(Math.random() * 99).toString(), -// genOption, -// testchecked, -// state.projectType, -// state.components, -// state.rootComponents -// ); -// }); - -// setModal( -// createModal({ -// closeModal, -// children, -// message: 'Choose export preference:', -// primBtnLabel: null, -// primBtnAction: null, -// secBtnAction: null, -// secBtnLabel: null, -// open: true -// }) -// ); -// }; - -// const exportKeyBind = useCallback(e => { -// //Export Project -// (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey) -// ? showGenerateAppModal() -// : ''; -// }, []); - -// useEffect(() => { -// document.addEventListener('keydown', exportKeyBind); -// return () => { -// document.removeEventListener('keydown', exportKeyBind); -// }; -// }, []); -// return ( -//
    -// -// {modal} -//
    -// ); -// } +import React, { useState, useCallback, useEffect } from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +//these 3 lines below are for the electron version +import GetAppIcon from '@mui/icons-material/GetApp'; +import Button from '@mui/material/Button'; +import exportProject from '../../utils/exportProject.util'; +import createModal from './createModal'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import zipFiles from '../../helperFunctions/zipFiles'; + +export default function ExportButton() { + const [modal, setModal] = useState(null); + const state = useSelector((store: RootState) => store.appState); + + const genOptions: string[] = ['Export components']; + + // Closes out the open modal + const closeModal = () => setModal(''); + + const showGenerateAppModal = () => { + const children = ( + + {genOptions.map((option: string, i: number) => ( + chooseGenOptions()} + style={{ + border: '1px solid #3c59ba', + marginBottom: '2%', + marginTop: '5%' + }} + > + + + ))} + + ); + + const chooseGenOptions = () => { + zipFiles(state); + closeModal(); + }; + + setModal( + createModal({ + closeModal, + children, + message: 'Click to download in zip file:', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + const exportKeyBind = useCallback((e) => { + //Export Project + (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey) + ? showGenerateAppModal() + : ''; + }, []); + + useEffect(() => { + document.addEventListener('keydown', exportKeyBind); + return () => { + document.removeEventListener('keydown', exportKeyBind); + }; + }, []); + return ( +
    + + {modal} +
    + ); +} + +// This code is commented out below for v16, it is export function for electron, which may be useful for future devs +//The below code is exclusive to ReacType's Electron App +//If you would like to deploy the app, please comment out the exportButton function above and uncomment the code below + +// export default function ExportButton() { +// const [modal, setModal] = useState(null); +// const state = useSelector(store => store.appState) + +// const genOptions: string[] = [ +// 'Export components', +// 'Export components with application files' +// ]; +// let genOption = 0; + +// // Closes out the open modal +// const closeModal = () => setModal(''); + +// const showGenerateAppModal = () => { +// const children = ( +// +// {genOptions.map((option: string, i: number) => ( +// chooseGenOptions(i)} +// style={{ +// border: '1px solid #3f51b5', +// marginBottom: '2%', +// marginTop: '5%' +// }} +// > +// +// +// ))} +// +// +// +// +// +// ); +// let testchecked = 0; +// // helper function called by showGenerateAppModal +// // this function will prompt the user to choose an app directory once they've chosen their export option +// const chooseGenOptions = (genOpt: number) => { +// // set export option: 0 --> export only components, 1 --> export full project +// genOption = genOpt; + +// //This is exclusive to the electron app +// window.api.chooseAppDir(); +// testchecked = document.getElementById('tests').checked; +// testerFunc(); +// closeModal(); +// }; + +// // removes all listeners for the app_dir_selected event +// // this is important because otherwise listeners will pile up and events will trigger multiple events +// //This is exclusive to the electron app +// window.api.removeAllAppDirChosenListeners(); + +// // add listener for when an app directory is chosen +// // when a directory is chosen, the callback will export the project to the chosen folder +// // Note: this listener is imported from the main process via preload.js + +// // This is exclusive to the electron app +// window.api.addAppDirChosenListener(path => { +// exportProject( +// path, +// state.name +// ? state.name +// : 'New_ReacType_Project_' + Math.ceil(Math.random() * 99).toString(), +// genOption, +// testchecked, +// state.projectType, +// state.components, +// state.rootComponents +// ); +// }); + +// setModal( +// createModal({ +// closeModal, +// children, +// message: 'Choose export preference:', +// primBtnLabel: null, +// primBtnAction: null, +// secBtnAction: null, +// secBtnLabel: null, +// open: true +// }) +// ); +// }; + +// const exportKeyBind = useCallback(e => { +// //Export Project +// (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey) +// ? showGenerateAppModal() +// : ''; +// }, []); + +// useEffect(() => { +// document.addEventListener('keydown', exportKeyBind); +// return () => { +// document.removeEventListener('keydown', exportKeyBind); +// }; +// }, []); +// return ( +//
    +// +// {modal} +//
    +// ); +// } diff --git a/app/src/components/right/LoginButton.tsx b/app/src/components/right/LoginButton.tsx index c2e83d39e..94bdc3b8b 100644 --- a/app/src/components/right/LoginButton.tsx +++ b/app/src/components/right/LoginButton.tsx @@ -1,101 +1,101 @@ -import React from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { toggleLoggedIn } from '../../redux/reducers/slice/appStateSlice'; -import serverConfig from '../../serverConfig.js'; -const { DEV_PORT, API_BASE_URL, API_BASE_URL2 } = serverConfig; -// const config = require('../../../../config.js'); -import { RootState } from '../../redux/store'; -import Cookies from 'js-cookie'; -// note that API_BASE_URL is assigned to different pages on dev mode vs prod mode -// const { DEV_PORT, API_BASE_URL, API_BASE_URL2 } = config; -const isDev = import.meta.env.NODE_ENV === 'development'; -let serverURL = API_BASE_URL; - -//check if we're in dev mode -if (isDev) { - serverURL = `http://localhost:${DEV_PORT}`; -} - -export default function LoginButton() { - const state = useSelector((store: RootState) => store.appState); - const dispatch = useDispatch(); - - const handleLogout = () => { - const projects = fetch(`${serverURL}/logout`, { - method: 'GET', - headers: { - 'content-type': 'application/json' - }, - // need credentials for userid pull from cookie - credentials: 'include' - }) - .then((res) => res.json()) - .then((data) => { - return data; - }) - .catch((err) => console.log(`Error getting project ${err}`)); - - window.localStorage.clear(); - - if (state.isLoggedIn) { - dispatch(toggleLoggedIn(false)); - } - - window.location.href = state.isLoggedIn - ? `${API_BASE_URL2}/#/login` - : API_BASE_URL; - }; - - const handleLogin = () => { - window.localStorage.clear(); - window.location.href = `${API_BASE_URL2}/#/login`; - }; - - if (state.isLoggedIn) { - return ( - - ); - } - - return ( - - ); -} +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { toggleLoggedIn } from '../../redux/reducers/slice/appStateSlice'; +import serverConfig from '../../serverConfig.js'; +const { DEV_PORT, API_BASE_URL, API_BASE_URL2 } = serverConfig; +// const config = require('../../../../config.js'); +import { RootState } from '../../redux/store'; +import Cookies from 'js-cookie'; +// note that API_BASE_URL is assigned to different pages on dev mode vs prod mode +// const { DEV_PORT, API_BASE_URL, API_BASE_URL2 } = config; +const isDev = import.meta.env.NODE_ENV === 'development'; +let serverURL = API_BASE_URL; + +//check if we're in dev mode +if (isDev) { + serverURL = `http://localhost:${DEV_PORT}`; +} + +export default function LoginButton() { + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); + + const handleLogout = () => { + const projects = fetch(`${serverURL}/logout`, { + method: 'GET', + headers: { + 'content-type': 'application/json' + }, + // need credentials for userid pull from cookie + credentials: 'include' + }) + .then((res) => res.json()) + .then((data) => { + return data; + }) + .catch((err) => console.log(`Error getting project ${err}`)); + + window.localStorage.clear(); + + if (state.isLoggedIn) { + dispatch(toggleLoggedIn(false)); + } + + window.location.href = state.isLoggedIn + ? `${API_BASE_URL2}/#/login` + : API_BASE_URL; + }; + + const handleLogin = () => { + window.localStorage.clear(); + window.location.href = `${API_BASE_URL2}/#/login`; + }; + + if (state.isLoggedIn) { + return ( + + ); + } + + return ( + + ); +} diff --git a/app/src/components/right/OpenProjects.tsx b/app/src/components/right/OpenProjects.tsx index f2def7324..c73d80a5f 100644 --- a/app/src/components/right/OpenProjects.tsx +++ b/app/src/components/right/OpenProjects.tsx @@ -1,164 +1,164 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Button from '@mui/material/Button'; -import Avatar from '@mui/material/Avatar'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemAvatar from '@mui/material/ListItemAvatar'; -import ListItemText from '@mui/material/ListItemText'; -import DialogTitle from '@mui/material/DialogTitle'; -import Dialog from '@mui/material/Dialog'; -import FolderOpenIcon from '@mui/icons-material/FolderOpen'; -import { blue } from '@mui/material/colors'; -import { getProjects } from '../../helperFunctions/projectGetSaveDel'; -import { useDispatch, useSelector } from 'react-redux'; -import { openProject } from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; - -export interface ProjectDialogProps { - openAlert: () => void; - open: boolean; - projects: Array; - onClose: () => void; -} -// The options to be rendered when dialog is open -function ProjectsDialog(props: ProjectDialogProps) { - const classes = useStyles(); - const { onClose, open, projects, openAlert } = props; - const state = useSelector((store: RootState) => store.appState); - const dispatch = useDispatch(); - // If no projects selected, keep the name of the current displayed - const handleClose = () => { - onClose(); - }; - // If new project selected, close and set value to new project name - const handleListItemClick = (value: string) => { - const selectedProject = projects.filter( - (project: any) => project._id === value - )[0]; - // dispatch({ type: 'OPEN PROJECT', payload: selectedProject }); - dispatch(openProject(selectedProject)); - openAlert(); - onClose(); - }; - - return ( - - - SAVED PROJECTS - - - User Projects - - - {projects - .filter( - (project: any) => - project.forked === undefined || project.forked === false - ) - .map((project: any, index: number) => ( - handleListItemClick(project._id)} - key={index} - > - - - - - - - - ))} - - {/* this section handles the projects cloned from the marketplace */} - - Marketplace Projects - - - {projects - .filter((project: any) => project.forked === true) - .map((project: any, index: number) => ( - handleListItemClick(project._id)} - key={index} - > - - - - - - - - ))} - - - ); -} -export default function ProjectsFolder(props: any) { - const [open, setOpen] = useState(false); - const [projects, setProjects] = useState([{ hello: 'cat' }]); - - const handleClickOpen = () => { - getProjects().then((data) => { - if (data) { - setProjects(data); - setOpen(true); - } - }); - }; - const handleClose = () => { - setOpen(false); - }; - const keyBindOpenProject = useCallback((e) => { - if ((e.key === 'o' && e.metaKey) || (e.key === 'o' && e.ctrlKey)) { - e.preventDefault(); - handleClickOpen(); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', keyBindOpenProject); - return () => { - document.removeEventListener('keydown', keyBindOpenProject); - }; - }, []); - return ( -
    - - -
    - ); -} -const useStyles = makeStyles({ - button: { - width: '55%', - backgroundColor: 'rgba(1,212,109,0.1)', - fontSize: '1em', - minWidth: '300px', - marginTop: '10px', - marginBotton: '10px' - }, - avatar: { - backgroundColor: blue[100], - color: blue[600] - } -}); +import React, { useState, useCallback, useEffect } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import Button from '@mui/material/Button'; +import Avatar from '@mui/material/Avatar'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import ListItemText from '@mui/material/ListItemText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Dialog from '@mui/material/Dialog'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import { blue } from '@mui/material/colors'; +import { getProjects } from '../../helperFunctions/projectGetSaveDel'; +import { useDispatch, useSelector } from 'react-redux'; +import { openProject } from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; + +export interface ProjectDialogProps { + openAlert: () => void; + open: boolean; + projects: Array; + onClose: () => void; +} +// The options to be rendered when dialog is open +function ProjectsDialog(props: ProjectDialogProps) { + const classes = useStyles(); + const { onClose, open, projects, openAlert } = props; + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); + // If no projects selected, keep the name of the current displayed + const handleClose = () => { + onClose(); + }; + // If new project selected, close and set value to new project name + const handleListItemClick = (value: string) => { + const selectedProject = projects.filter( + (project: any) => project._id === value + )[0]; + // dispatch({ type: 'OPEN PROJECT', payload: selectedProject }); + dispatch(openProject(selectedProject)); + openAlert(); + onClose(); + }; + + return ( + + + SAVED PROJECTS + + + User Projects + + + {projects + .filter( + (project: any) => + project.forked === undefined || project.forked === false + ) + .map((project: any, index: number) => ( + handleListItemClick(project._id)} + key={index} + > + + + + + + + + ))} + + {/* this section handles the projects cloned from the marketplace */} + + Marketplace Projects + + + {projects + .filter((project: any) => project.forked === true) + .map((project: any, index: number) => ( + handleListItemClick(project._id)} + key={index} + > + + + + + + + + ))} + + + ); +} +export default function ProjectsFolder(props: any) { + const [open, setOpen] = useState(false); + const [projects, setProjects] = useState([{ hello: 'cat' }]); + + const handleClickOpen = () => { + getProjects().then((data) => { + if (data) { + setProjects(data); + setOpen(true); + } + }); + }; + const handleClose = () => { + setOpen(false); + }; + const keyBindOpenProject = useCallback((e) => { + if ((e.key === 'o' && e.metaKey) || (e.key === 'o' && e.ctrlKey)) { + e.preventDefault(); + handleClickOpen(); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', keyBindOpenProject); + return () => { + document.removeEventListener('keydown', keyBindOpenProject); + }; + }, []); + return ( +
    + + +
    + ); +} +const useStyles = makeStyles({ + button: { + width: '55%', + backgroundColor: 'rgba(1,212,109,0.1)', + fontSize: '1em', + minWidth: '300px', + marginTop: '10px', + marginBotton: '10px' + }, + avatar: { + backgroundColor: blue[100], + color: blue[600] + } +}); diff --git a/app/src/components/right/ProjectManager.tsx b/app/src/components/right/ProjectManager.tsx index c933f5134..570a141af 100644 --- a/app/src/components/right/ProjectManager.tsx +++ b/app/src/components/right/ProjectManager.tsx @@ -1,171 +1,171 @@ -//note for future developers - this may be electron specific - without signin working on the browser it is difficult to test -import React, { useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import { withRouter } from 'react-router-dom'; -import exportProject from '../../utils/exportProject.util'; -import createModal from '../right/createModal'; -import { useDispatch, useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; -import { resetState } from '../../redux/reducers/slice/appStateSlice'; -// ProjectManager function moved to NavBar.tsx -const ProjectManager = () => { - // state to keep track of whether a modal should display - const [modal, setModal] = useState(null); - const state = useSelector((store: RootState) => store.appState); - const dispatch = useDispatch(); - - // State to keep track of how the user wants their components to be exported - // GenOption = 0 --> export only components - // GenOption = 1 --> export an entire project w/ webpack, server, etc. - const genOptions: string[] = [ - 'Export components', - 'Export components with application files' - ]; - let genOption: number = 0; - - // Closes out the open modal - const closeModal = () => setModal(''); - - // Creates modal that asks if user wants to clear workspace - // If user clears their workspace, then their components are removed from state and the modal is closed - const clearWorkspace = () => { - // Reset state for project to initial state - const resetStates = () => { - dispatch(resetState({})); - }; - - // Set modal options - const children = ( - - - - - - ); - - // create modal - setModal( - createModal({ - closeModal, - children, - message: 'Are you sure you want to delete all data?', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - // ----------------------------------CREATE MODAL FOR EXPORT OPTIONS (moved to NavBar.tsx)------------------------------------- - - const showGenerateAppModal = () => { - const children = ( - - {genOptions.map((option: string, i: number) => ( - chooseGenOptions(i)} - style={{ - border: '1px solid #3c59ba', - marginBottom: '2%', - marginTop: '5%' - }} - > - - - ))} - - ); - - // helper function called by showGenerateAppModal - // this function will prompt the user to choose an app directory once they've chosen their export option - const chooseGenOptions = (genOpt: number) => { - // set export option: 0 --> export only components, 1 --> export full project - genOption = genOpt; - window.api.chooseAppDir(); - closeModal(); - }; - - // removes all listeners for the app_dir_selected event - // this is important because otherwise listeners will pile up and events will trigger multiple events - window.api.removeAllAppDirChosenListeners(); - - // add listener for when an app directory is chosen - // when a directory is chosen, the callback will export the project to the chosen folder - // Note: this listener is imported from the main process via preload.js - window.api.addAppDirChosenListener((path) => { - exportProject( - path, - state.name - ? state.name - : 'New_ReacType_Project_' + Math.ceil(Math.random() * 99).toString(), - genOption, - state.projectType, - state.components, - state.rootComponents - ); - }); - - setModal( - createModal({ - closeModal, - children, - message: 'Choose export preference:', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - return
    {modal}
    ; -}; - -const useStyles = makeStyles({ - logoutButton: { - position: 'absolute', - bottom: '50px', - right: '150px' - }, - btnGroup: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - position: 'absolute', - bottom: '40px', - left: '0px' - }, - - button: { - backgroundColor: 'rgba(1,212,109,0.1)', - fontSize: '1em', - minWidth: '300px', - marginTop: '10px', - marginBotton: '10px' - } -}); - -export default withRouter(ProjectManager); +//note for future developers - this may be electron specific - without signin working on the browser it is difficult to test +import React, { useState } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import { withRouter } from 'react-router-dom'; +import exportProject from '../../utils/exportProject.util'; +import createModal from '../right/createModal'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { resetState } from '../../redux/reducers/slice/appStateSlice'; +// ProjectManager function moved to NavBar.tsx +const ProjectManager = () => { + // state to keep track of whether a modal should display + const [modal, setModal] = useState(null); + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); + + // State to keep track of how the user wants their components to be exported + // GenOption = 0 --> export only components + // GenOption = 1 --> export an entire project w/ webpack, server, etc. + const genOptions: string[] = [ + 'Export components', + 'Export components with application files' + ]; + let genOption: number = 0; + + // Closes out the open modal + const closeModal = () => setModal(''); + + // Creates modal that asks if user wants to clear workspace + // If user clears their workspace, then their components are removed from state and the modal is closed + const clearWorkspace = () => { + // Reset state for project to initial state + const resetStates = () => { + dispatch(resetState({})); + }; + + // Set modal options + const children = ( + + + + + + ); + + // create modal + setModal( + createModal({ + closeModal, + children, + message: 'Are you sure you want to delete all data?', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + // ----------------------------------CREATE MODAL FOR EXPORT OPTIONS (moved to NavBar.tsx)------------------------------------- + + const showGenerateAppModal = () => { + const children = ( + + {genOptions.map((option: string, i: number) => ( + chooseGenOptions(i)} + style={{ + border: '1px solid #3c59ba', + marginBottom: '2%', + marginTop: '5%' + }} + > + + + ))} + + ); + + // helper function called by showGenerateAppModal + // this function will prompt the user to choose an app directory once they've chosen their export option + const chooseGenOptions = (genOpt: number) => { + // set export option: 0 --> export only components, 1 --> export full project + genOption = genOpt; + window.api.chooseAppDir(); + closeModal(); + }; + + // removes all listeners for the app_dir_selected event + // this is important because otherwise listeners will pile up and events will trigger multiple events + window.api.removeAllAppDirChosenListeners(); + + // add listener for when an app directory is chosen + // when a directory is chosen, the callback will export the project to the chosen folder + // Note: this listener is imported from the main process via preload.js + window.api.addAppDirChosenListener((path) => { + exportProject( + path, + state.name + ? state.name + : 'New_ReacType_Project_' + Math.ceil(Math.random() * 99).toString(), + genOption, + state.projectType, + state.components, + state.rootComponents + ); + }); + + setModal( + createModal({ + closeModal, + children, + message: 'Choose export preference:', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + return
    {modal}
    ; +}; + +const useStyles = makeStyles({ + logoutButton: { + position: 'absolute', + bottom: '50px', + right: '150px' + }, + btnGroup: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + position: 'absolute', + bottom: '40px', + left: '0px' + }, + + button: { + backgroundColor: 'rgba(1,212,109,0.1)', + fontSize: '1em', + minWidth: '300px', + marginTop: '10px', + marginBotton: '10px' + } +}); + +export default withRouter(ProjectManager); diff --git a/app/src/components/right/SaveProjectButton.tsx b/app/src/components/right/SaveProjectButton.tsx index a27165b90..124292598 100644 --- a/app/src/components/right/SaveProjectButton.tsx +++ b/app/src/components/right/SaveProjectButton.tsx @@ -1,108 +1,108 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import Button from '@mui/material/Button'; -import TextField from '@mui/material/TextField'; -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogTitle from '@mui/material/DialogTitle'; -import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; -import { saveProject } from '../../helperFunctions/projectGetSaveDel'; -import {useDispatch, useSelector} from 'react-redux' -import {updateProjectName, updateProjectId} from '../../redux/reducers/slice/appStateSlice'; -import { RootState } from '../../redux/store'; -import { State } from '../../interfaces/Interfaces'; - -export default function FormDialog() { - const [open, setOpen] = useState(false); -const state = useSelector((store:RootState) => store.appState); -const dispatch = useDispatch(); - const [projectName, setProjectName] = useState(''); - const [invalidProjectName, setInvalidProjectName] = useState(false); - const [invalidProjectNameMessage, setInvalidProjectNameMessage] = useState( - '' - ); - - const handleClickOpen = () => { - setInvalidProjectName(false); - setOpen(true); - }; - - const handleSave = () => { - if (state.isLoggedIn === true && projectName !== '') { - // Update the project name to global state - // Needed to disable delete button - // Switch to Thunk - // If errors occur on the backend, the project name still gets updated - - dispatch(updateProjectName(projectName)) - saveProject(projectName, state).then((project: State) => dispatch(updateProjectId(project._id)))//updates the slice with new _id from mongo - setOpen(false); - } else { - setInvalidProjectName(true); - setInvalidProjectNameMessage('Please Enter'); - } - }; - const handleClose = () => { - setInvalidProjectName(false); - setInvalidProjectNameMessage(''); - setOpen(false); - }; - const handleChange = (e: React.ChangeEvent) => { - setProjectName(e.target.value); - }; - const saveKeyBind = useCallback((e) => { - //Save Project As, the || is for Mac or Windows - (e.key === 's' && e.metaKey && !e.shiftKey || e.key === 's' && e.ctrlKey && !e.shiftKey) ? handleClickOpen() : ''; - }, []); - - useEffect(() => { - document.addEventListener('keydown', saveKeyBind); - return () => { - document.removeEventListener('keydown', saveKeyBind) - } - }, []); - return ( -
    - - - Save Project - - - - - - - - -
    - ); -} - +import React, { useState, useCallback, useEffect } from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; +import { saveProject } from '../../helperFunctions/projectGetSaveDel'; +import {useDispatch, useSelector} from 'react-redux' +import {updateProjectName, updateProjectId} from '../../redux/reducers/slice/appStateSlice'; +import { RootState } from '../../redux/store'; +import { State } from '../../interfaces/Interfaces'; + +export default function FormDialog() { + const [open, setOpen] = useState(false); +const state = useSelector((store:RootState) => store.appState); +const dispatch = useDispatch(); + const [projectName, setProjectName] = useState(''); + const [invalidProjectName, setInvalidProjectName] = useState(false); + const [invalidProjectNameMessage, setInvalidProjectNameMessage] = useState( + '' + ); + + const handleClickOpen = () => { + setInvalidProjectName(false); + setOpen(true); + }; + + const handleSave = () => { + if (state.isLoggedIn === true && projectName !== '') { + // Update the project name to global state + // Needed to disable delete button + // Switch to Thunk + // If errors occur on the backend, the project name still gets updated + + dispatch(updateProjectName(projectName)) + saveProject(projectName, state).then((project: State) => dispatch(updateProjectId(project._id)))//updates the slice with new _id from mongo + setOpen(false); + } else { + setInvalidProjectName(true); + setInvalidProjectNameMessage('Please Enter'); + } + }; + const handleClose = () => { + setInvalidProjectName(false); + setInvalidProjectNameMessage(''); + setOpen(false); + }; + const handleChange = (e: React.ChangeEvent) => { + setProjectName(e.target.value); + }; + const saveKeyBind = useCallback((e) => { + //Save Project As, the || is for Mac or Windows + (e.key === 's' && e.metaKey && !e.shiftKey || e.key === 's' && e.ctrlKey && !e.shiftKey) ? handleClickOpen() : ''; + }, []); + + useEffect(() => { + document.addEventListener('keydown', saveKeyBind); + return () => { + document.removeEventListener('keydown', saveKeyBind) + } + }, []); + return ( +
    + + + Save Project + + + + + + + + +
    + ); +} + diff --git a/app/src/components/right/SimpleModal.tsx b/app/src/components/right/SimpleModal.tsx index 5585fa2c1..e1125b35b 100644 --- a/app/src/components/right/SimpleModal.tsx +++ b/app/src/components/right/SimpleModal.tsx @@ -1,108 +1,108 @@ -import React, { Fragment } from 'react'; -import Modal from '@mui/material/Modal'; -import withStyles from '@mui/styles/withStyles'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import IconButton from '@mui/material/IconButton'; -import CloseIcon from '@mui/icons-material/Close'; - -const styles = (theme: any): any => ({ - paper: { - position: 'absolute', - width: 'auto', - maxWidth: '500px', - height: 'auto', - maxHeight: '500px', - backgroundColor: '#31343A', - color: 'white', - borderRadius: '10px', - boxShadow: theme.shadows[5], - padding: '4%', - minWidth: '500px', - minHeight: '300px' - }, - button: { - marginTop: '0%', - height: 'auto', - marginLeft: '3%', - borderRadius: '4px', - float: 'right' - } -}); - -const SimpleModal = (props) => { - const { - classes, - open, - message, - primBtnLabel, - secBtnLabel, - primBtnAction, - secBtnAction, - closeModal, - children = null - } = props; - - return ( - - -
    - - - - - {message} - -
    {children}
    -
    - {secBtnLabel ? ( - - ) : null} - {primBtnLabel ? ( - - ) : null} -
    -
    -
    -
    - ); -}; - -export default withStyles(styles)(SimpleModal); +import React, { Fragment } from 'react'; +import Modal from '@mui/material/Modal'; +import withStyles from '@mui/styles/withStyles'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; + +const styles = (theme: any): any => ({ + paper: { + position: 'absolute', + width: 'auto', + maxWidth: '500px', + height: 'auto', + maxHeight: '500px', + backgroundColor: '#31343A', + color: 'white', + borderRadius: '10px', + boxShadow: theme.shadows[5], + padding: '4%', + minWidth: '500px', + minHeight: '300px' + }, + button: { + marginTop: '0%', + height: 'auto', + marginLeft: '3%', + borderRadius: '4px', + float: 'right' + } +}); + +const SimpleModal = (props) => { + const { + classes, + open, + message, + primBtnLabel, + secBtnLabel, + primBtnAction, + secBtnAction, + closeModal, + children = null + } = props; + + return ( + + +
    + + + + + {message} + +
    {children}
    +
    + {secBtnLabel ? ( + + ) : null} + {primBtnLabel ? ( + + ) : null} +
    +
    +
    +
    + ); +}; + +export default withStyles(styles)(SimpleModal); diff --git a/app/src/components/right/createModal.tsx b/app/src/components/right/createModal.tsx index b3c01ecf6..f50c818b8 100644 --- a/app/src/components/right/createModal.tsx +++ b/app/src/components/right/createModal.tsx @@ -1,38 +1,38 @@ -import React from 'react'; -import SimpleModal from './SimpleModal'; - -type Props = { - open: boolean; - message: string; - primBtnLabel: any; - secBtnLabel: any; - primBtnAction: any; - secBtnAction: any; - children: any; - closeModal: any; -}; - -const createModal = ({ - open = true, - message, - primBtnLabel, - secBtnLabel = null, - primBtnAction, - secBtnAction = null, - children = null, - closeModal -}: Props) => ( - - {children} - -); - -export default createModal; +import React from 'react'; +import SimpleModal from './SimpleModal'; + +type Props = { + open: boolean; + message: string; + primBtnLabel: any; + secBtnLabel: any; + primBtnAction: any; + secBtnAction: any; + children: any; + closeModal: any; +}; + +const createModal = ({ + open = true, + message, + primBtnLabel, + secBtnLabel = null, + primBtnAction, + secBtnAction = null, + children = null, + closeModal +}: Props) => ( + + {children} + +); + +export default createModal; diff --git a/app/src/components/top/NavBar.tsx b/app/src/components/top/NavBar.tsx index 2c9ff55f8..910a042bf 100644 --- a/app/src/components/top/NavBar.tsx +++ b/app/src/components/top/NavBar.tsx @@ -1,273 +1,273 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; -import MoreVertIcon from '@mui/icons-material/MoreVert'; -import NavBarButtons from './NavBarButtons'; -import NewExportButton from './NewExportButton'; -import { RootState } from '../../redux/store'; -import logo from '../../public/icons/win/logo.png'; -import { useSelector, useDispatch } from 'react-redux'; -import { - publishProject, - unpublishProject -} from '../../helperFunctions/projectGetSaveDel'; -import PublishModal from './PublishModal'; -import { - updateProjectId, - updateProjectName, - updateProjectPublished, - toggleScreenshotTrigger -} from '../../redux/reducers/slice/appStateSlice'; -import { State } from '../../interfaces/Interfaces'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; - -const NavBar: React.FC = () => { - const [dropMenu, setDropMenu] = useState(false); - const state = useSelector((store: RootState) => store.appState); - const [publishModalOpen, setPublishModalOpen] = useState(false); - const [projectName, setProjectName] = useState(state.name || ''); - const [invalidProjectName, setInvalidProjectName] = useState(false); - const [invalidProjectNameMessage, setInvalidProjectNameMessage] = - useState(''); - const urlAdd = useHistory(); - const isMarketplace = urlAdd.location.pathname === '/marketplace'; - - const dispatch = useDispatch(); - const menuButtonRef = useRef(null); - const [alertOpen, setAlertOpen] = React.useState(false); - const [alertOpen2, setAlertOpen2] = React.useState(false); - const [deleteAlert, setDeleteAlert] = React.useState(false); - const [openAlert, setOpenAlert] = React.useState(false); - const [loginAlert, setLoginAlert] = React.useState(false); - - useEffect(() => { - setProjectName(state.name); - }, [state.name]); - - const deleteAlertOpen = () => { - setDeleteAlert(true); - }; - - const openProjectAlert = () => { - setOpenAlert(true); - }; - - const buttonContainerStyle = { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start' - }; - - const buttonStyle = { - backgroundColor: '#0671E3', - border: 'none', - color: 'white', - fontSize: '12px', - padding: '8px 15px', - cursor: 'pointer', - marginRight: '10px', - marginLeft: '5px', - borderRadius: '10px' - }; - - const moreVertButtonStyle = { - backgroundColor: '#1E2024', - padding: '0', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start', - borderRadius: '4px', - width: '30px', - minWidth: '20px', - marginLeft: '0px', - marginRight: '10px', - boxShadow: 'none' - }; - - const handlePublish = () => { - if (state.isLoggedIn === true && projectName === '') { - setInvalidProjectName(true); - setPublishModalOpen(true); - return; - } else if (state.isLoggedIn === false) { - setLoginAlert(true); - } else { - publishProject(projectName, state) - .then((newProject: State) => { - setPublishModalOpen(false); - dispatch(updateProjectId(newProject._id)); - dispatch(updateProjectName(newProject.name)); - dispatch(updateProjectPublished(newProject.published)); - dispatch(toggleScreenshotTrigger()); - setAlertOpen(true); - }) - .catch((error) => { - console.error('Error publishing project:', error.message); - }); - } - }; - - const handleUnpublish = () => { - unpublishProject(state) - .then((unpublishedProject: State) => { - dispatch(updateProjectPublished(false)); - setAlertOpen2(true); - }) - .catch((error) => { - console.error('Error unpublishing project:', error.message); - }); - }; - - const handleAlertClose = ( - event: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === 'clickaway') { - return; - } - setAlertOpen(false); - setAlertOpen2(false); - setDeleteAlert(false); - setOpenAlert(false); - setLoginAlert(false); - }; - - const Alert = React.forwardRef(function Alert( - props, - ref - ) { - return ; - }); - - return ( -
    - -
    - - - Published Project! - - - - - Unpublished Project! - - - - - Project Deleted! - - - - - Opened Project! - - - - - Login to Publish! - - -
    -
    - ); -}; - +import React, { useEffect, useRef, useState } from 'react'; +import { Link, useHistory } from 'react-router-dom'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import NavBarButtons from './NavBarButtons'; +import NewExportButton from './NewExportButton'; +import { RootState } from '../../redux/store'; +import logo from '../../public/icons/win/logo.png'; +import { useSelector, useDispatch } from 'react-redux'; +import { + publishProject, + unpublishProject +} from '../../helperFunctions/projectGetSaveDel'; +import PublishModal from './PublishModal'; +import { + updateProjectId, + updateProjectName, + updateProjectPublished, + toggleScreenshotTrigger +} from '../../redux/reducers/slice/appStateSlice'; +import { State } from '../../interfaces/Interfaces'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; + +const NavBar: React.FC = () => { + const [dropMenu, setDropMenu] = useState(false); + const state = useSelector((store: RootState) => store.appState); + const [publishModalOpen, setPublishModalOpen] = useState(false); + const [projectName, setProjectName] = useState(state.name || ''); + const [invalidProjectName, setInvalidProjectName] = useState(false); + const [invalidProjectNameMessage, setInvalidProjectNameMessage] = + useState(''); + const urlAdd = useHistory(); + const isMarketplace = urlAdd.location.pathname === '/marketplace'; + + const dispatch = useDispatch(); + const menuButtonRef = useRef(null); + const [alertOpen, setAlertOpen] = React.useState(false); + const [alertOpen2, setAlertOpen2] = React.useState(false); + const [deleteAlert, setDeleteAlert] = React.useState(false); + const [openAlert, setOpenAlert] = React.useState(false); + const [loginAlert, setLoginAlert] = React.useState(false); + + useEffect(() => { + setProjectName(state.name); + }, [state.name]); + + const deleteAlertOpen = () => { + setDeleteAlert(true); + }; + + const openProjectAlert = () => { + setOpenAlert(true); + }; + + const buttonContainerStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start' + }; + + const buttonStyle = { + backgroundColor: '#0671E3', + border: 'none', + color: 'white', + fontSize: '12px', + padding: '8px 15px', + cursor: 'pointer', + marginRight: '10px', + marginLeft: '5px', + borderRadius: '10px' + }; + + const moreVertButtonStyle = { + backgroundColor: '#1E2024', + padding: '0', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + borderRadius: '4px', + width: '30px', + minWidth: '20px', + marginLeft: '0px', + marginRight: '10px', + boxShadow: 'none' + }; + + const handlePublish = () => { + if (state.isLoggedIn === true && projectName === '') { + setInvalidProjectName(true); + setPublishModalOpen(true); + return; + } else if (state.isLoggedIn === false) { + setLoginAlert(true); + } else { + publishProject(projectName, state) + .then((newProject: State) => { + setPublishModalOpen(false); + dispatch(updateProjectId(newProject._id)); + dispatch(updateProjectName(newProject.name)); + dispatch(updateProjectPublished(newProject.published)); + dispatch(toggleScreenshotTrigger()); + setAlertOpen(true); + }) + .catch((error) => { + console.error('Error publishing project:', error.message); + }); + } + }; + + const handleUnpublish = () => { + unpublishProject(state) + .then((unpublishedProject: State) => { + dispatch(updateProjectPublished(false)); + setAlertOpen2(true); + }) + .catch((error) => { + console.error('Error unpublishing project:', error.message); + }); + }; + + const handleAlertClose = ( + event: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === 'clickaway') { + return; + } + setAlertOpen(false); + setAlertOpen2(false); + setDeleteAlert(false); + setOpenAlert(false); + setLoginAlert(false); + }; + + const Alert = React.forwardRef(function Alert( + props, + ref + ) { + return ; + }); + + return ( +
    + +
    + + + Published Project! + + + + + Unpublished Project! + + + + + Project Deleted! + + + + + Opened Project! + + + + + Login to Publish! + + +
    +
    + ); +}; + export default NavBar; \ No newline at end of file diff --git a/app/src/components/top/NavBarButtons.tsx b/app/src/components/top/NavBarButtons.tsx index be0725b6c..d0d8b265e 100644 --- a/app/src/components/top/NavBarButtons.tsx +++ b/app/src/components/top/NavBarButtons.tsx @@ -1,280 +1,280 @@ -import React, { Ref, useEffect, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { Button } from '@mui/material'; -import DeleteProjects from '../right/DeleteProjects'; -import ExportButton from '../right/ExportButton'; -import { Link } from 'react-router-dom'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import LoginButton from '../right/LoginButton'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import ProjectsFolder from '../right/OpenProjects'; -import { RootState } from '../../redux/store'; -import SaveProjectButton from '../right/SaveProjectButton'; -import serverConfig from '../../serverConfig.js'; - -import createModal from '../right/createModal'; -import createStyles from '@mui/styles/createStyles'; -import makeStyles from '@mui/styles/makeStyles'; -import { resetAllState } from '../../redux/reducers/slice/appStateSlice'; -import { setStyle } from '../../redux/reducers/slice/styleSlice'; -import store from '../../redux/store'; -import withStyles from '@mui/styles/withStyles'; -import { emitEvent } from '../../helperFunctions/socket'; - -const { API_BASE_URL } = serverConfig; - -const useStyles = makeStyles((theme) => - createStyles({ - root: { - flexGrow: 1, - width: '100%' - }, - menuButton: { - marginRight: theme.spacing(1), - color: 'white' - }, - title: { - flexGrow: 1, - color: 'white' - }, - manageProject: { - display: 'flex', - justifyContent: 'center', - width: '100px', - overflow: 'none' - } - }) -); - -interface StyledMenuProps extends React.PropsWithChildren<{}> { - id: string; - anchorEl: HTMLElement | null; - keepMounted: boolean; - open: boolean; - onClose: () => void; -} - -const StyledMenu = withStyles({ - paper: { - border: '1px solid #d3d4d5' - } -})((props: StyledMenuProps) => ( - -)); - -const StyledMenuItem = withStyles((theme) => ({ - root: { - '&:focus': { - '& .MuiListItemIcon-root, & .MuiListItemText-primary': { - color: theme.palette.common.white - } - } - } -}))(MenuItem); - -// where the main function starts // -const navbarDropDown = (props) => { - const dispatch = useDispatch(); - - const [modal, setModal] = React.useState(null); - const [anchorEl, setAnchorEl] = React.useState(null); - const classes = useStyles(); - - // const { state } = useSelector((store: RootState) => ({ - // state: store.appState - // })); - const state = useSelector((store: RootState) => store.appState); - const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); - const userName = useSelector((store: RootState) => store.roomSlice.userName); - - const closeModal = () => setModal(''); - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const clearWorkspace = () => { - // Reset state for project to initial state - const resetState = () => { - if (roomCode) emitEvent('clearCanvasAction', roomCode, userName); - else dispatch(resetAllState()); - }; - // Set modal options - const children = ( - - - - - - ); - - // Create modal - setModal( - createModal({ - closeModal, - children, - message: 'Are you sure you want to delete all data?', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - const handleClose = () => { - setAnchorEl(null); - props.setDropMenu(false); - }; - - let showMenu = props.dropMenu ? 'navDropDown' : 'hideNavDropDown'; - - const useOutsideClick = () => { - const dropdownRef = useRef(null); - - useEffect(() => { - const handleClick = (event) => { - if ( - (event.type === 'click' && - dropdownRef.current && - !dropdownRef.current.contains(event.target) && - !props.menuButtonRef.current.contains(event.target)) || - (event.type === 'message' && event.data === 'iframeClicked') - ) { - handleClose(); - } - }; - window.addEventListener('click', handleClick, true); - window.addEventListener('message', handleClick); - return () => { - window.removeEventListener('click', handleClick, true); - window.removeEventListener('message', handleClick); - }; - }, [dropdownRef]); - - return dropdownRef; - }; - - const ref = useOutsideClick(); - - return ( -
    - - - - - {state.isLoggedIn && ( - - )} - - - - - - - - - - - - - - - - {modal} -
    - ); -}; - -export default navbarDropDown; +import React, { Ref, useEffect, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Button } from '@mui/material'; +import DeleteProjects from '../right/DeleteProjects'; +import ExportButton from '../right/ExportButton'; +import { Link } from 'react-router-dom'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import LoginButton from '../right/LoginButton'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import ProjectsFolder from '../right/OpenProjects'; +import { RootState } from '../../redux/store'; +import SaveProjectButton from '../right/SaveProjectButton'; +import serverConfig from '../../serverConfig.js'; +import createModal from '../right/createModal'; +import createStyles from '@mui/styles/createStyles'; +import makeStyles from '@mui/styles/makeStyles'; +import { resetAllState } from '../../redux/reducers/slice/appStateSlice'; +import { setStyle } from '../../redux/reducers/slice/styleSlice'; +import store from '../../redux/store'; +import withStyles from '@mui/styles/withStyles'; +import { emitEvent } from '../../helperFunctions/socket'; + +const { API_BASE_URL } = serverConfig; + +const useStyles = makeStyles((theme) => + createStyles({ + root: { + flexGrow: 1, + width: '100%' + }, + menuButton: { + marginRight: theme.spacing(1), + color: 'white' + }, + title: { + flexGrow: 1, + color: 'white' + }, + manageProject: { + display: 'flex', + justifyContent: 'center', + width: '100px', + overflow: 'none' + } + }) +); + +interface StyledMenuProps extends React.PropsWithChildren<{}> { + id: string; + anchorEl: HTMLElement | null; + keepMounted: boolean; + open: boolean; + onClose: () => void; +} + +const StyledMenu = withStyles({ + paper: { + border: '1px solid #d3d4d5' + } +})((props: StyledMenuProps) => ( + +)); + +const StyledMenuItem = withStyles((theme) => ({ + root: { + '&:focus': { + '& .MuiListItemIcon-root, & .MuiListItemText-primary': { + color: theme.palette.common.white + } + } + } +}))(MenuItem); + +// where the main function starts // +const navbarDropDown = (props) => { + const dispatch = useDispatch(); + const [modal, setModal] = React.useState(null); + const [anchorEl, setAnchorEl] = React.useState(null); + const classes = useStyles(); + + // const { state } = useSelector((store: RootState) => ({ + // state: store.appState + // })); + const state = useSelector((store: RootState) => store.appState); + const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); + const userName = useSelector((store: RootState) => store.roomSlice.userName); + + const closeModal = () => setModal(''); + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + + props.setDropDownMenu(true) + }; + + const clearWorkspace = () => { + // Reset state for project to initial state + const resetState = () => { + if (roomCode) emitEvent('clearCanvasAction', roomCode, userName); + else dispatch(resetAllState()); + }; + // Set modal options + const children = ( + + + + + + ); + + // Create modal + setModal( + createModal({ + closeModal, + children, + message: 'Are you sure you want to delete all data?', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + const handleClose = () => { + setAnchorEl(null); + props.setDropMenu(false); + }; + + let showMenu = props.dropMenu ? 'navDropDown' : 'hideNavDropDown'; + + const useOutsideClick = () => { + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClick = (event) => { + if ( + (event.type === 'click' && + dropdownRef.current && + !dropdownRef.current.contains(event.target) && + !props.menuButtonRef.current.contains(event.target)) || + (event.type === 'message' && event.data === 'iframeClicked') + ) { + handleClose(); + } + }; + window.addEventListener('click', handleClick, true); + window.addEventListener('message', handleClick); + return () => { + window.removeEventListener('click', handleClick, true); + window.removeEventListener('message', handleClick); + }; + }, [dropdownRef]); + + return dropdownRef; + }; + + const ref = useOutsideClick(); + + return ( +
    + + + + + {state.isLoggedIn && ( + + )} + + + + + + + + + + + + + + + + {modal} +
    + ); +}; + +export default navbarDropDown; diff --git a/app/src/components/top/NewExportButton.tsx b/app/src/components/top/NewExportButton.tsx index e7b1dd397..7f5abeffe 100644 --- a/app/src/components/top/NewExportButton.tsx +++ b/app/src/components/top/NewExportButton.tsx @@ -1,89 +1,89 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import createModal from '../../components/right/createModal'; -import zipFiles from '../../helperFunctions/zipFiles'; // Import your zipFiles function -import { useSelector } from 'react-redux'; -import { RootState } from '../../redux/store'; - -export default function NewExportButton(): React.JSX.Element { - const [modal, setModal] = useState(null); - const state = useSelector((store: RootState) => store.appState); - - const genOptions: string[] = ['Export components']; - - const closeModal = () => setModal(''); - - const buttonStyle = { - backgroundColor: '#2D313A', - border: 'none', - color: 'white', - fontSize: '12px', - padding: '8px 15px', - cursor: 'pointer', - marginRight: '6px', - borderRadius: '10px' - }; - - const showGenerateAppModal = () => { - const children = ( - - {genOptions.map((option: string, i: number) => ( - chooseGenOptions()} - style={{ - border: '1px solid #3c59ba', - marginBottom: '2%', - marginTop: '5%' - }} - > - - - ))} - - ); - - const chooseGenOptions = () => { - zipFiles(state); - closeModal(); - }; - - setModal( - createModal({ - closeModal, - children, - message: 'Click to download in zip file:', - primBtnLabel: null, - primBtnAction: null, - secBtnAction: null, - secBtnLabel: null, - open: true - }) - ); - }; - - const exportKeyBind = useCallback((e: KeyboardEvent) => { - if ((e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey)) { - showGenerateAppModal(); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', exportKeyBind); - return () => { - document.removeEventListener('keydown', exportKeyBind); - }; - }, []); - - return ( -
    - - {modal} -
    - ); +import React, { useState, useCallback, useEffect } from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import createModal from '../../components/right/createModal'; +import zipFiles from '../../helperFunctions/zipFiles'; // Import your zipFiles function +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; + +export default function NewExportButton(): React.JSX.Element { + const [modal, setModal] = useState(null); + const state = useSelector((store: RootState) => store.appState); + + const genOptions: string[] = ['Export components']; + + const closeModal = () => setModal(''); + + const buttonStyle = { + backgroundColor: '#2D313A', + border: 'none', + color: 'white', + fontSize: '12px', + padding: '8px 15px', + cursor: 'pointer', + marginRight: '6px', + borderRadius: '10px' + }; + + const showGenerateAppModal = () => { + const children = ( + + {genOptions.map((option: string, i: number) => ( + chooseGenOptions()} + style={{ + border: '1px solid #3c59ba', + marginBottom: '2%', + marginTop: '5%' + }} + > + + + ))} + + ); + + const chooseGenOptions = () => { + zipFiles(state); + closeModal(); + }; + + setModal( + createModal({ + closeModal, + children, + message: 'Click to download in zip file:', + primBtnLabel: null, + primBtnAction: null, + secBtnAction: null, + secBtnLabel: null, + open: true + }) + ); + }; + + const exportKeyBind = useCallback((e: KeyboardEvent) => { + if ((e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey)) { + showGenerateAppModal(); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', exportKeyBind); + return () => { + document.removeEventListener('keydown', exportKeyBind); + }; + }, []); + + return ( +
    + + {modal} +
    + ); } \ No newline at end of file diff --git a/app/src/components/top/PublishModal.tsx b/app/src/components/top/PublishModal.tsx index 1516751a5..9d00bc5ee 100644 --- a/app/src/components/top/PublishModal.tsx +++ b/app/src/components/top/PublishModal.tsx @@ -1,54 +1,54 @@ -import React from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import DialogActions from '@mui/material/DialogActions'; -import Button from '@mui/material/Button'; -import TextField from '@mui/material/TextField'; - -const PublishModal = ({ - open, - onClose, - onSave, - projectName, - onChange, - invalidProjectName, - invalidProjectNameMessage, -}) => { - return ( - - Publish Project - - - - - - - - - ); -}; - -export default PublishModal; +import React from 'react'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; + +const PublishModal = ({ + open, + onClose, + onSave, + projectName, + onChange, + invalidProjectName, + invalidProjectNameMessage, +}) => { + return ( + + Publish Project + + + + + + + + + ); +}; + +export default PublishModal; diff --git a/app/src/constants/ErrorMessages.ts b/app/src/constants/ErrorMessages.ts index abcabee53..1a14633f9 100644 --- a/app/src/constants/ErrorMessages.ts +++ b/app/src/constants/ErrorMessages.ts @@ -1,10 +1,10 @@ -export default { - deleteIndexTitle: 'You cannot delete index.', - deleteIndexMessage: 'You must have at least one page.', - deleteComponentTitle: 'You cannot delete this component.', - deleteComponentMessage: - 'Reusable components inside of a page must be deleted from the page.', - deleteLinkedPageTitle: 'Delete linked page error.', - deleteLinkedPageMessage: - 'Please delete the links to this page before deleting the page.' -}; +export default { + deleteIndexTitle: 'You cannot delete index.', + deleteIndexMessage: 'You must have at least one page.', + deleteComponentTitle: 'You cannot delete this component.', + deleteComponentMessage: + 'Reusable components inside of a page must be deleted from the page.', + deleteLinkedPageTitle: 'Delete linked page error.', + deleteLinkedPageMessage: + 'Please delete the links to this page before deleting the page.' +}; diff --git a/app/src/constants/ItemTypes.ts b/app/src/constants/ItemTypes.ts index 0dec08d2c..20b56497f 100644 --- a/app/src/constants/ItemTypes.ts +++ b/app/src/constants/ItemTypes.ts @@ -1,5 +1,5 @@ -import { DragItemType } from '../interfaces/Interfaces'; -const ItemTypes: DragItemType = { - INSTANCE: 'instance' -}; -export { ItemTypes }; +import { DragItemType } from '../interfaces/Interfaces'; +const ItemTypes: DragItemType = { + INSTANCE: 'instance' +}; +export { ItemTypes }; diff --git a/app/src/constants/Styling.ts b/app/src/constants/Styling.ts index 7d8abc1b8..c341f1d37 100644 --- a/app/src/constants/Styling.ts +++ b/app/src/constants/Styling.ts @@ -1,6 +1,6 @@ -export default { - neonGreen: '#189bd7', - darkBlue: '#0671e3', - darkGray: '#252526', - tutorialGray: '#f2f0f0' -}; +export default { + neonGreen: '#189bd7', + darkBlue: '#0671e3', + darkGray: '#252526', + tutorialGray: '#f2f0f0' +}; diff --git a/app/src/containers/AppContainer.tsx b/app/src/containers/AppContainer.tsx index 62c6d841b..ecaaf42ea 100644 --- a/app/src/containers/AppContainer.tsx +++ b/app/src/containers/AppContainer.tsx @@ -1,51 +1,51 @@ -import React, { useEffect, useState } from 'react'; -import { - StyledEngineProvider, - Theme, - ThemeProvider -} from '@mui/material/styles'; -import { theme1, theme2 } from '../public/styles/theme'; -import { useDispatch, useSelector } from 'react-redux'; - -import LeftContainer from './LeftContainer'; -import MainContainer from './MainContainer'; - -import MarketplaceContainer from './MarketplaceContainer'; -import NavBar from '../components/top/NavBar'; -import { RootState } from '../redux/store'; - -import { setStyle } from '../redux/reducers/slice/styleSlice'; -import { useHistory } from 'react-router-dom'; -declare module '@mui/styles/defaultTheme' { - interface DefaultTheme extends Theme {} -} - -// setting light and dark themes (navbar and background); linked to theme.ts -const lightTheme = theme1; -const darkTheme = theme2; // dark mode color in theme.ts not reached -const AppContainer: React.FC = () => { - //useHistory hook to grab the url, if it is /marketplace then selectively render MarketplaceContainer - const urlAdd = useHistory(); - const isMarketplace = urlAdd.location.pathname === '/marketplace'; - - return ( - - -
    - -
    -
    - {isMarketplace ? ( - - ) : ( - <> - - - - )} -
    -
    -
    - ); -}; -export default AppContainer; +import React, { useEffect, useState } from 'react'; +import { + StyledEngineProvider, + Theme, + ThemeProvider +} from '@mui/material/styles'; +import { theme1, theme2 } from '../public/styles/theme'; +import { useDispatch, useSelector } from 'react-redux'; + +import LeftContainer from './LeftContainer'; +import MainContainer from './MainContainer'; + +import MarketplaceContainer from './MarketplaceContainer'; +import NavBar from '../components/top/NavBar'; +import { RootState } from '../redux/store'; + +import { setStyle } from '../redux/reducers/slice/styleSlice'; +import { useHistory } from 'react-router-dom'; +declare module '@mui/styles/defaultTheme' { + interface DefaultTheme extends Theme {} +} + +// setting light and dark themes (navbar and background); linked to theme.ts +const lightTheme = theme1; +const darkTheme = theme2; // dark mode color in theme.ts not reached +const AppContainer: React.FC = () => { + //useHistory hook to grab the url, if it is /marketplace then selectively render MarketplaceContainer + const urlAdd = useHistory(); + const isMarketplace = urlAdd.location.pathname === '/marketplace'; + + return ( + + +
    + +
    +
    + {isMarketplace ? ( + + ) : ( + <> + + + + )} +
    +
    +
    + ); +}; +export default AppContainer; diff --git a/app/src/containers/LeftContainer.tsx b/app/src/containers/LeftContainer.tsx index 7967e5766..506f282aa 100644 --- a/app/src/containers/LeftContainer.tsx +++ b/app/src/containers/LeftContainer.tsx @@ -1,25 +1,25 @@ -import React, { useState, useEffect } from 'react'; -import ContentArea from '../components/left/ContentArea'; -import Sidebar from '../components/left/Sidebar'; - -const App = () => { - const [activeTab, setActiveTab] = useState(0); - const [isVisible, setIsVisible] = useState(true); - - const toggleVisibility = (state: boolean) => { - setIsVisible(state); - }; - - return ( -
    - - -
    - ); -}; - -export default App; +import React, { useState, useEffect } from 'react'; +import ContentArea from '../components/left/ContentArea'; +import Sidebar from '../components/left/Sidebar'; + +const App = () => { + const [activeTab, setActiveTab] = useState(0); + const [isVisible, setIsVisible] = useState(true); + + const toggleVisibility = (state: boolean) => { + setIsVisible(state); + }; + + return ( +
    + + +
    + ); +}; + +export default App; diff --git a/app/src/containers/MainContainer.tsx b/app/src/containers/MainContainer.tsx index 8c4dc742d..867ffe608 100644 --- a/app/src/containers/MainContainer.tsx +++ b/app/src/containers/MainContainer.tsx @@ -1,142 +1,142 @@ -import React, { useRef, useEffect, useState } from 'react'; -import BottomPanel from '../components/bottom/BottomPanel'; -import CanvasContainer from '../components/main/CanvasContainer'; -import DemoRender from '../components/main/DemoRender'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from '../redux/store'; -import { toggleScreenshotTrigger } from '../redux/reducers/slice/appStateSlice'; -import html2canvas from 'html2canvas'; -import { Buffer } from 'buffer'; -import { Amplify, Storage } from 'aws-amplify'; -import awsconfig from '../../../src/custom-aws-exports'; - -const MainContainer = (props): JSX.Element => { - const [bottomShow, setBottomShow] = useState(false); - const dispatch = useDispatch(); - const screenshotTrigger = useSelector( - (store: RootState) => store.appState.screenshotTrigger - ); - const id: string = useSelector((store: RootState) => store.appState._id); - // const { style } = useSelector((store: RootState) => ({ - // style: store.styleSlice - // })); - const style = useSelector((store: RootState) => store.styleSlice); - const containerRef: React.RefObject = useRef(null); - - // useEffect hook to detect and execute changes in screenshotTrigger, taking a screenshot of the canvas when a project is published on NavBar - useEffect(() => { - const handleScreenshot = async (): Promise => { - if (screenshotTrigger) { - try { - const canvas: HTMLCanvasElement = await html2canvas( - containerRef.current - ); - const screenshotURL: string = canvas.toDataURL('image/png'); - const imgBuffer: Buffer | void = Buffer.from( - screenshotURL.replace(/^data:image\/\w+;base64,/, ''), - 'base64' - ); - dispatch(toggleScreenshotTrigger()); - return imgBuffer; - } catch (error) { - alert('Error capturing screenshot: ' + error); - } - } - }; - handleScreenshot().then((imgBuffer: Buffer | void) => { - if (imgBuffer) { - uploadScreenshotS3(imgBuffer); - } - }); - }, [screenshotTrigger]); - - const uploadScreenshotS3 = async (imgBuffer) => { - Amplify.configure(awsconfig); - - async function checkStorageConnection() { - try { - // await Storage.list(''); // This is just a test operation - // console.log('Connected to AWS S3 successfully.'); - } catch (error) { - console.error('Error connecting to AWS S3:', error); - } - } - - // Call this function to check the connection - checkStorageConnection(); - try { - await Storage.put(id, imgBuffer, { - contentType: 'image/png' - }); - } catch (error) { - alert('Error uploading screenshot: ' + error); - } - }; - - //Logic to close the bottompanel when clicking outside of it - const useOutsideClick = () => { - const bottomPanelRef = useRef(null); - - useEffect(() => { - const handleClick = (event) => { - if ( - (event.type === 'click' && - bottomPanelRef.current && - !bottomPanelRef.current.contains(event.target) && - event.target.getAttribute('role') != 'menu' && - !event.target.classList.contains('MuiInput-input')) || - (event.type === 'message' && event.data === 'iframeClicked') - ) { - //menuButtonRef is to ensure that handleClose does not get invoked when the user clicks on the menu dropdown button - handleClose(); - } - }; - window.addEventListener('click', handleClick, true); - window.addEventListener('message', handleClick); //to capture clicks in the iframe - - return () => { - window.removeEventListener('click', handleClick, true); - window.addEventListener('message', handleClick); //cleanup for memory purposes. ensures handleclick isn't called after the component is no longer rendered - }; - }, [bottomPanelRef]); - - return bottomPanelRef; - }; - - const ref = useOutsideClick(); - - const handleClose = () => { - setBottomShow(false); - }; - - const hideBottomPanelStyles = { - maxHeight: '64px', - boxSizing: 'border-box', - transition: 'all 0.5s ease-in-out' - }; - - const showBottomPanelStyles = { - maxHeight: '100%', - transition: 'all 0.5s ease-in-out' - }; - - return ( -
    -
    - - -
    -
    - -
    -
    - ); -}; -export default MainContainer; +import React, { useRef, useEffect, useState } from 'react'; +import BottomPanel from '../components/bottom/BottomPanel'; +import CanvasContainer from '../components/main/CanvasContainer'; +import DemoRender from '../components/main/DemoRender'; +import { useSelector, useDispatch } from 'react-redux'; +import { RootState } from '../redux/store'; +import { toggleScreenshotTrigger } from '../redux/reducers/slice/appStateSlice'; +import html2canvas from 'html2canvas'; +import { Buffer } from 'buffer'; +import { Amplify, Storage } from 'aws-amplify'; +import awsconfig from '../../../src/custom-aws-exports'; + +const MainContainer = (props): JSX.Element => { + const [bottomShow, setBottomShow] = useState(false); + const dispatch = useDispatch(); + const screenshotTrigger = useSelector( + (store: RootState) => store.appState.screenshotTrigger + ); + const id: string = useSelector((store: RootState) => store.appState._id); + // const { style } = useSelector((store: RootState) => ({ + // style: store.styleSlice + // })); + const style = useSelector((store: RootState) => store.styleSlice); + const containerRef: React.RefObject = useRef(null); + + // useEffect hook to detect and execute changes in screenshotTrigger, taking a screenshot of the canvas when a project is published on NavBar + useEffect(() => { + const handleScreenshot = async (): Promise => { + if (screenshotTrigger) { + try { + const canvas: HTMLCanvasElement = await html2canvas( + containerRef.current + ); + const screenshotURL: string = canvas.toDataURL('image/png'); + const imgBuffer: Buffer | void = Buffer.from( + screenshotURL.replace(/^data:image\/\w+;base64,/, ''), + 'base64' + ); + dispatch(toggleScreenshotTrigger()); + return imgBuffer; + } catch (error) { + alert('Error capturing screenshot: ' + error); + } + } + }; + handleScreenshot().then((imgBuffer: Buffer | void) => { + if (imgBuffer) { + uploadScreenshotS3(imgBuffer); + } + }); + }, [screenshotTrigger]); + + const uploadScreenshotS3 = async (imgBuffer) => { + Amplify.configure(awsconfig); + + async function checkStorageConnection() { + try { + // await Storage.list(''); // This is just a test operation + // console.log('Connected to AWS S3 successfully.'); + } catch (error) { + console.error('Error connecting to AWS S3:', error); + } + } + + // Call this function to check the connection + checkStorageConnection(); + try { + await Storage.put(id, imgBuffer, { + contentType: 'image/png' + }); + } catch (error) { + alert('Error uploading screenshot: ' + error); + } + }; + + //Logic to close the bottompanel when clicking outside of it + const useOutsideClick = () => { + const bottomPanelRef = useRef(null); + + useEffect(() => { + const handleClick = (event) => { + if ( + (event.type === 'click' && + bottomPanelRef.current && + !bottomPanelRef.current.contains(event.target) && + event.target.getAttribute('role') != 'menu' && + !event.target.classList.contains('MuiInput-input')) || + (event.type === 'message' && event.data === 'iframeClicked') + ) { + //menuButtonRef is to ensure that handleClose does not get invoked when the user clicks on the menu dropdown button + handleClose(); + } + }; + window.addEventListener('click', handleClick, true); + window.addEventListener('message', handleClick); //to capture clicks in the iframe + + return () => { + window.removeEventListener('click', handleClick, true); + window.addEventListener('message', handleClick); //cleanup for memory purposes. ensures handleclick isn't called after the component is no longer rendered + }; + }, [bottomPanelRef]); + + return bottomPanelRef; + }; + + const ref = useOutsideClick(); + + const handleClose = () => { + setBottomShow(false); + }; + + const hideBottomPanelStyles = { + maxHeight: '64px', + boxSizing: 'border-box', + transition: 'all 0.5s ease-in-out' + }; + + const showBottomPanelStyles = { + maxHeight: '100%', + transition: 'all 0.5s ease-in-out' + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; +export default MainContainer; diff --git a/app/src/containers/MarketplaceContainer.tsx b/app/src/containers/MarketplaceContainer.tsx index 7c9378f96..1228db9f3 100644 --- a/app/src/containers/MarketplaceContainer.tsx +++ b/app/src/containers/MarketplaceContainer.tsx @@ -1,89 +1,89 @@ -import MarketplaceCardContainer from '../components/marketplace/MarketplaceCardContainer'; -import SearchBar from '../components/marketplace/Searchbar'; -import React, {useEffect, useState} from 'react'; -import axios from 'axios'; -import { CircularProgress } from '@mui/material'; - -const MarketplaceContainer = () => { - - const [marketplaceProjects, setMarketplaceProjects] = useState([]); - const [displayProjects, setDisplayProjects] = useState([]); - useEffect(() => { - async function marketplaceFetch() { - try { - const response = await axios.get('/getMarketplaceProjects', { - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, - }); - setMarketplaceProjects(response.data); - setDisplayProjects(response.data); - } catch (error) { - console.error('Error fetching MP data:', error); - } - } - marketplaceFetch(); - - }, []); - - - const updateDisplayProjects = (searchResults) => { - setDisplayProjects(searchResults);//have to pass this down as a prop so that the setting is done outside of Rendering otherwise callstack issues - }; - - return ( -
    -
    -

    Discover components built with ReacType

    -

    - Browse, save, and customize the latest components built by the - community -

    - -
    - {displayProjects.length ? : - - (/*while marketplaceProjects is length 0 means it is not done fetching. Add loading...*/ - marketplaceProjects.length ? -

    - No Results Found! -

    - : -

    {/*added a circular progress bar*/} - Loading... -

    - ) - } -
    - ); -}; - -const containerStyles: React.CSSProperties = { - backgroundColor: '#111', - minHeight: '100vh', - width: '100vw', - color: 'white', - paddingBottom: '15vh', - overflow: 'auto', - -}; - -const contentStyles: React.CSSProperties = { - textAlign: 'center', - padding: '100px 0' -}; - -const headingStyles: React.CSSProperties = { - fontSize: '2.5rem', - color: '#AAA', - fontWeight: 'normal', - marginBottom: '20px' -}; - -const subheadingStyles: React.CSSProperties = { - fontSize: '1.25rem', - color: '#777' -}; - -export default MarketplaceContainer; +import MarketplaceCardContainer from '../components/marketplace/MarketplaceCardContainer'; +import SearchBar from '../components/marketplace/Searchbar'; +import React, {useEffect, useState} from 'react'; +import axios from 'axios'; +import { CircularProgress } from '@mui/material'; + +const MarketplaceContainer = () => { + + const [marketplaceProjects, setMarketplaceProjects] = useState([]); + const [displayProjects, setDisplayProjects] = useState([]); + useEffect(() => { + async function marketplaceFetch() { + try { + const response = await axios.get('/getMarketplaceProjects', { + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, + }); + setMarketplaceProjects(response.data); + setDisplayProjects(response.data); + } catch (error) { + console.error('Error fetching MP data:', error); + } + } + marketplaceFetch(); + + }, []); + + + const updateDisplayProjects = (searchResults) => { + setDisplayProjects(searchResults);//have to pass this down as a prop so that the setting is done outside of Rendering otherwise callstack issues + }; + + return ( +
    +
    +

    Discover components built with ReacType

    +

    + Browse, save, and customize the latest components built by the + community +

    + +
    + {displayProjects.length ? : + + (/*while marketplaceProjects is length 0 means it is not done fetching. Add loading...*/ + marketplaceProjects.length ? +

    + No Results Found! +

    + : +

    {/*added a circular progress bar*/} + Loading... +

    + ) + } +
    + ); +}; + +const containerStyles: React.CSSProperties = { + backgroundColor: '#111', + minHeight: '100vh', + width: '100vw', + color: 'white', + paddingBottom: '15vh', + overflow: 'auto', + +}; + +const contentStyles: React.CSSProperties = { + textAlign: 'center', + padding: '100px 0' +}; + +const headingStyles: React.CSSProperties = { + fontSize: '2.5rem', + color: '#AAA', + fontWeight: 'normal', + marginBottom: '20px' +}; + +const subheadingStyles: React.CSSProperties = { + fontSize: '1.25rem', + color: '#777' +}; + +export default MarketplaceContainer; diff --git a/app/src/helperFunctions/changePositionValidation.ts b/app/src/helperFunctions/changePositionValidation.ts index e86c803d3..1fc86e8ed 100644 --- a/app/src/helperFunctions/changePositionValidation.ts +++ b/app/src/helperFunctions/changePositionValidation.ts @@ -1,50 +1,50 @@ -// This function will evaluate the target destination when moving an element on the canvas -// If the target destination is actually a nested component within its own children array -// the new target parent is not a valid parent to change position - -import { State, ChildElement } from '../interfaces/Interfaces'; - -const validateNewParent = ( - state: State, - currentChildId: number, - toTargetParentId: number -): boolean => { - const focusIndex = state.canvasFocus.componentId - 1; - const childrenArray = state.components[focusIndex].children; - // Checks to see if a Parent is trying to nest inside one of its children - const selfNestingCheck = ( - array: ChildElement[], - nestedChild = false, - nestedParent = false - ): boolean => { - for (const element of array) { - if ( - element.childId === toTargetParentId && - nestedChild === true && - element.typeId > 1000 // check if not a separator (1000) or previously dragged component (> 1000) - ) - return (nestedParent = true); - else if ( - element.childId === currentChildId && - element.children.length > 0 && - nestedChild === false - ) - nestedParent = selfNestingCheck( - element.children, - (nestedChild = true), - nestedParent - ); - else if (element.children.length > 0 && nestedChild === false) - nestedParent = selfNestingCheck( - element.children, - nestedChild, - nestedParent - ); - } - return nestedParent; - }; - const parentNestingIntoChild = selfNestingCheck(childrenArray); - if (parentNestingIntoChild === true) return false; - return true; -}; -export default validateNewParent; +// This function will evaluate the target destination when moving an element on the canvas +// If the target destination is actually a nested component within its own children array +// the new target parent is not a valid parent to change position + +import { State, ChildElement } from '../interfaces/Interfaces'; + +const validateNewParent = ( + state: State, + currentChildId: number, + toTargetParentId: number +): boolean => { + const focusIndex = state.canvasFocus.componentId - 1; + const childrenArray = state.components[focusIndex].children; + // Checks to see if a Parent is trying to nest inside one of its children + const selfNestingCheck = ( + array: ChildElement[], + nestedChild = false, + nestedParent = false + ): boolean => { + for (const element of array) { + if ( + element.childId === toTargetParentId && + nestedChild === true && + element.typeId > 1000 // check if not a separator (1000) or previously dragged component (> 1000) + ) + return (nestedParent = true); + else if ( + element.childId === currentChildId && + element.children.length > 0 && + nestedChild === false + ) + nestedParent = selfNestingCheck( + element.children, + (nestedChild = true), + nestedParent + ); + else if (element.children.length > 0 && nestedChild === false) + nestedParent = selfNestingCheck( + element.children, + nestedChild, + nestedParent + ); + } + return nestedParent; + }; + const parentNestingIntoChild = selfNestingCheck(childrenArray); + if (parentNestingIntoChild === true) return false; + return true; +}; +export default validateNewParent; diff --git a/app/src/helperFunctions/cloneDeep.ts b/app/src/helperFunctions/cloneDeep.ts index 656f41866..dc6a20734 100644 --- a/app/src/helperFunctions/cloneDeep.ts +++ b/app/src/helperFunctions/cloneDeep.ts @@ -1,29 +1,29 @@ -// index signatures -function cloneDeep( - value: { [key: string]: any } | any[] -): { [key: string]: any } | any { - if (Array.isArray(value)) { - const result: any[] = []; - value.forEach(el => { - if (typeof el === 'object') { - result.push(cloneDeep(el)); - } else { - result.push(el); - } - }); - return result; - } - if (typeof value === 'object' && value !== null) { - const result: { [key: string]: any } = {}; - Object.keys(value).forEach(key => { - if (typeof value[key] === 'object') { - result[key] = cloneDeep(value[key]); - } else { - result[key] = value[key]; - } - }); - return result; - } - return value; -} -export default cloneDeep; +// index signatures +function cloneDeep( + value: { [key: string]: any } | any[] +): { [key: string]: any } | any { + if (Array.isArray(value)) { + const result: any[] = []; + value.forEach(el => { + if (typeof el === 'object') { + result.push(cloneDeep(el)); + } else { + result.push(el); + } + }); + return result; + } + if (typeof value === 'object' && value !== null) { + const result: { [key: string]: any } = {}; + Object.keys(value).forEach(key => { + if (typeof value[key] === 'object') { + result[key] = cloneDeep(value[key]); + } else { + result[key] = value[key]; + } + }); + return result; + } + return value; +} +export default cloneDeep; diff --git a/app/src/helperFunctions/combineStyles.ts b/app/src/helperFunctions/combineStyles.ts index dea047ee9..dd80cbbff 100644 --- a/app/src/helperFunctions/combineStyles.ts +++ b/app/src/helperFunctions/combineStyles.ts @@ -1,15 +1,15 @@ -export const combineStyles = ( - defaultStyle: Object, - priorityStyle: Object -): Object => { - // initialize output object that's a copy of priority styles - const combinedStyle = { ...priorityStyle }; - // iterate through each style in default style - // if property is not in the output object, add it to the output object - for (let i in defaultStyle) { - if (!combinedStyle.hasOwnProperty(i)) { - combinedStyle[i] = defaultStyle[i]; - } - } - return combinedStyle; -}; +export const combineStyles = ( + defaultStyle: Object, + priorityStyle: Object +): Object => { + // initialize output object that's a copy of priority styles + const combinedStyle = { ...priorityStyle }; + // iterate through each style in default style + // if property is not in the output object, add it to the output object + for (let i in defaultStyle) { + if (!combinedStyle.hasOwnProperty(i)) { + combinedStyle[i] = defaultStyle[i]; + } + } + return combinedStyle; +}; diff --git a/app/src/helperFunctions/componentNestValidation.ts b/app/src/helperFunctions/componentNestValidation.ts index d18830ba9..c162ab2ed 100644 --- a/app/src/helperFunctions/componentNestValidation.ts +++ b/app/src/helperFunctions/componentNestValidation.ts @@ -1,15 +1,15 @@ -// This function is used in both DirectChildHTMLNestable and SeparatorChild to ensure that user created components do not nest within themselves. -// To allow such nesting would be a certain paradox and locks the application. -// This check is done right after the drag functionality resolves and releases. Nothing is done if a component is found trying to nest within itself. -import { ChildElement } from '../interfaces/Interfaces'; - -const componentNest = (children: ChildElement[], nestId: Number) => { - let notNested = true; - for (const element of children) { - if (element.childId === nestId) return false; - else if (element.children.length > 0) - notNested = componentNest(element.children, nestId); - } - return notNested; -}; -export default componentNest; +// This function is used in both DirectChildHTMLNestable and SeparatorChild to ensure that user created components do not nest within themselves. +// To allow such nesting would be a certain paradox and locks the application. +// This check is done right after the drag functionality resolves and releases. Nothing is done if a component is found trying to nest within itself. +import { ChildElement } from '../interfaces/Interfaces'; + +const componentNest = (children: ChildElement[], nestId: Number) => { + let notNested = true; + for (const element of children) { + if (element.childId === nestId) return false; + else if (element.children.length > 0) + notNested = componentNest(element.children, nestId); + } + return notNested; +}; +export default componentNest; diff --git a/app/src/helperFunctions/cssRefresh.tsx b/app/src/helperFunctions/cssRefresh.tsx index 0043e30b2..de3d1b136 100644 --- a/app/src/helperFunctions/cssRefresh.tsx +++ b/app/src/helperFunctions/cssRefresh.tsx @@ -1,18 +1,18 @@ -// Removes old link to css and creates a new stylesheet link on demo render -// this is not currently being used for the website version -const cssRefresher = (): void => { - const oldStylesheet = document.getElementById('stylesheet'); - if (oldStylesheet !== null) oldStylesheet.remove(); - const newStylesheet = document.createElement('LINK') as HTMLLinkElement; - newStylesheet.rel = 'stylesheet'; - newStylesheet.type = 'text/css'; - newStylesheet.href = 'fake.css'; - newStylesheet.id = 'stylesheet'; - const renderFocusElement = document.getElementById('renderFocus'); - if (renderFocusElement !== null) { - renderFocusElement.appendChild(newStylesheet); - } -}; -export default cssRefresher; - - +// Removes old link to css and creates a new stylesheet link on demo render +// this is not currently being used for the website version +const cssRefresher = (): void => { + const oldStylesheet = document.getElementById('stylesheet'); + if (oldStylesheet !== null) oldStylesheet.remove(); + const newStylesheet = document.createElement('LINK') as HTMLLinkElement; + newStylesheet.rel = 'stylesheet'; + newStylesheet.type = 'text/css'; + newStylesheet.href = 'fake.css'; + newStylesheet.id = 'stylesheet'; + const renderFocusElement = document.getElementById('renderFocus'); + if (renderFocusElement !== null) { + renderFocusElement.appendChild(newStylesheet); + } +}; +export default cssRefresher; + + diff --git a/app/src/helperFunctions/esbuildService.ts b/app/src/helperFunctions/esbuildService.ts index b2ede9007..816a5b16b 100644 --- a/app/src/helperFunctions/esbuildService.ts +++ b/app/src/helperFunctions/esbuildService.ts @@ -1,15 +1,15 @@ -import * as esbuild from 'esbuild-wasm'; - -/* Singleton pattern for initializing esbuild */ -/* This ensures that esbuild is only initialized once, regardless of how many times CodePreview is mounted and unmounted */ -let isEsbuildInitialized = false; - -export const initializeEsbuild = async () => { - if (!isEsbuildInitialized) { - await esbuild.initialize({ - worker: true, - wasmURL: 'https://unpkg.com/esbuild-wasm@0.8.27/esbuild.wasm' - }); - isEsbuildInitialized = true; - } -}; +import * as esbuild from 'esbuild-wasm'; + +/* Singleton pattern for initializing esbuild */ +/* This ensures that esbuild is only initialized once, regardless of how many times CodePreview is mounted and unmounted */ +let isEsbuildInitialized = false; + +export const initializeEsbuild = async () => { + if (!isEsbuildInitialized) { + await esbuild.initialize({ + worker: true, + wasmURL: 'https://unpkg.com/esbuild-wasm@0.8.27/esbuild.wasm' + }); + isEsbuildInitialized = true; + } +}; diff --git a/app/src/helperFunctions/manageSeparators.ts b/app/src/helperFunctions/manageSeparators.ts index bb1ba88b6..30cdc9d99 100644 --- a/app/src/helperFunctions/manageSeparators.ts +++ b/app/src/helperFunctions/manageSeparators.ts @@ -1,97 +1,97 @@ -import { ChildElement, ManageSeparators } from '../interfaces/Interfaces'; - -const separator = { - id: 1000, - tag: 'separator', - name: 'separator', - style: { border: 'none' }, - placeHolderShort: '', - placeHolderLong: '', - icon: '', - framework: '', - nestable: true -}; -const manageSeparators: ManageSeparators = { - nextTopSeparatorId: 1000, - // this function checks for two separators in a row or missing separators and adds/removes as needed - handleSeparators: (arr, str) => { - if ( - (str === 'delete' || str === 'change position') && - arr.length === 1 && - arr[0].name === 'separator' - ) { - arr.splice(0, 1); - } - for (let index = 0; index < arr.length; index++) { - if ( - arr[index].name === 'separator' && - arr[index + 1].name === 'separator' - ) { - arr.splice(index, 1); // removes extra separator from array - } - // check for duplicated separator at the end of array and remove it if separator is at the last index - if (arr[arr.length - 1].name === 'separator') arr.splice(arr.length - 1, 1); - // check for missing separators // cooment - if ( - arr[index].name !== 'separator' && - (index === 0 || arr[index - 1].name !== 'separator') - ) { - // initialize topSeparator inside the if condition so that every time this condition evaluated to true, - // a new topSeparator with incremented id will be created - const topSeparator: ChildElement = { - type: 'HTML Element', - typeId: separator.id, - name: 'separator', - childId: manageSeparators.nextTopSeparatorId, - style: separator.style, - attributes: {}, // Added - events: {}, // Added - stateProps: [], // Added - passedInProps: [], // Added - children: [], - - }; - // add a topSeparator before the element that does not have one - arr.splice(index, 0, topSeparator); - // update this value in state - manageSeparators.nextTopSeparatorId += 1; - } - // check is length is > 0 or it is a nested element - if ( - arr[index].name !== 'input' && - arr[index].name !== 'img' && - arr[index].children?.length - ) { - // recursive call if children array - str === 'delete' || str === 'change position' - ? manageSeparators.handleSeparators(arr[index].children, str) - : manageSeparators.handleSeparators(arr[index].children); - } - } - return manageSeparators.nextTopSeparatorId; - - }, - - // this function replaces separators onto which an element is dropped with the element itself - mergeSeparator: (arr, index) => { - return arr.map((child) => { - // Added additional nested types for lists - if ( - (child.name === 'div' || - child.name === 'form' || - child.name === 'ol' || - child.name === 'ul') && - child?.children?.length - ) { - const divContents = manageSeparators.mergeSeparator( - child.children, - index - ) - return { ...child, children: divContents }; - } else if (child.name === 'separator' && child?.children?.length) { - return child.children[index]; - } else return child; - }); - } -} -export default manageSeparators; +import { ChildElement, ManageSeparators } from '../interfaces/Interfaces'; + +const separator = { + id: 1000, + tag: 'separator', + name: 'separator', + style: { border: 'none' }, + placeHolderShort: '', + placeHolderLong: '', + icon: '', + framework: '', + nestable: true +}; +const manageSeparators: ManageSeparators = { + nextTopSeparatorId: 1000, + // this function checks for two separators in a row or missing separators and adds/removes as needed + handleSeparators: (arr, str) => { + if ( + (str === 'delete' || str === 'change position') && + arr.length === 1 && + arr[0].name === 'separator' + ) { + arr.splice(0, 1); + } + for (let index = 0; index < arr.length; index++) { + if ( + arr[index].name === 'separator' && + arr[index + 1].name === 'separator' + ) { + arr.splice(index, 1); // removes extra separator from array + } + // check for duplicated separator at the end of array and remove it if separator is at the last index + if (arr[arr.length - 1].name === 'separator') arr.splice(arr.length - 1, 1); + // check for missing separators // cooment + if ( + arr[index].name !== 'separator' && + (index === 0 || arr[index - 1].name !== 'separator') + ) { + // initialize topSeparator inside the if condition so that every time this condition evaluated to true, + // a new topSeparator with incremented id will be created + const topSeparator: ChildElement = { + type: 'HTML Element', + typeId: separator.id, + name: 'separator', + childId: manageSeparators.nextTopSeparatorId, + style: separator.style, + attributes: {}, // Added + events: {}, // Added + stateProps: [], // Added + passedInProps: [], // Added + children: [], + + }; + // add a topSeparator before the element that does not have one + arr.splice(index, 0, topSeparator); + // update this value in state + manageSeparators.nextTopSeparatorId += 1; + } + // check is length is > 0 or it is a nested element + if ( + arr[index].name !== 'input' && + arr[index].name !== 'img' && + arr[index].children?.length + ) { + // recursive call if children array + str === 'delete' || str === 'change position' + ? manageSeparators.handleSeparators(arr[index].children, str) + : manageSeparators.handleSeparators(arr[index].children); + } + } + return manageSeparators.nextTopSeparatorId; + + }, + + // this function replaces separators onto which an element is dropped with the element itself + mergeSeparator: (arr, index) => { + return arr.map((child) => { + // Added additional nested types for lists + if ( + (child.name === 'div' || + child.name === 'form' || + child.name === 'ol' || + child.name === 'ul') && + child?.children?.length + ) { + const divContents = manageSeparators.mergeSeparator( + child.children, + index + ) + return { ...child, children: divContents }; + } else if (child.name === 'separator' && child?.children?.length) { + return child.children[index]; + } else return child; + }); + } +} +export default manageSeparators; diff --git a/app/src/helperFunctions/projectGetSaveDel.ts b/app/src/helperFunctions/projectGetSaveDel.ts index fcfd4aed2..1bb3eb637 100644 --- a/app/src/helperFunctions/projectGetSaveDel.ts +++ b/app/src/helperFunctions/projectGetSaveDel.ts @@ -1,162 +1,162 @@ -import { State } from '../interfaces/Interfaces'; - -const isDev = import.meta.env.NODE_ENV === 'development'; -import serverConfig from '../serverConfig.js'; - -const { DEV_PORT, API_BASE_URL } = serverConfig; -// import config from '../../../config.js'; -// const { DEV_PORT, API_BASE_URL } = config; -let serverURL = API_BASE_URL; - -//check if we're in dev mode -if (isDev) { - serverURL = `http://localhost:${DEV_PORT}`; -} - -export const getProjects = (): Promise => { - const projects = fetch(`${serverURL}/getProjects`, { - method: 'POST', - headers: { - 'content-type': 'application/json' - }, - // need credentials for userid pull from cookie - credentials: 'include' - }) - .then((res) => res.json()) - .then((data) => { - return data; - }) - .catch((err) => console.log(`Error getting project ${err}`)); - return projects; //returns an array of projects with _id, name, project -}; - -export const saveProject = ( - name: string, - workspace: State -): Promise => { - const newProject = { ...workspace }; - delete newProject._id; - delete newProject.name; //deleting the _id from the current state slice. We don't actually want it in the project object in the mongo db document - const body = JSON.stringify({ - name, - project: { ...newProject }, - comments: [] - }); - const project = fetch(`${serverURL}/saveProject`, { - method: 'POST', - headers: { - 'content-type': 'application/json' - }, - credentials: 'include', - body - }) - .then((res) => res.json()) - .then((data) => { - return { - _id: data._id, - name: data.name, - published: data.published, - ...data.project - }; //passing up what is needed for the global appstateslice - }) - .catch((err) => console.log(`Error saving project ${err}`)); - return project; //returns _id in addition to the project object from the document -}; - -export const publishProject = ( - name: string, - workspace: State -): Promise => { - const newProject = { ...workspace }; - delete newProject.name; - const body = JSON.stringify({ - _id: workspace._id, - name: name, - project: { ...newProject }, - comments: [] - }); - - const response = fetch(`${serverURL}/publishProject`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'include', - body - }); - - const publishedProject = response - .then((res) => res.json()) - .then((data) => { - return { - _id: data._id, - name: data.name, - published: data.published, - ...data.project - }; - }) - .catch((err) => { - console.log(`Error publishing project ${err}`); - throw err; - }); - - return publishedProject; -}; - -export const unpublishProject = (projectData: State): Promise => { - const body = JSON.stringify({ - _id: projectData._id - }); - - const response = fetch(`${serverURL}/unpublishProject`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'include', - body - }); - - const unpublishedProject = response - .then((res) => res.json()) - .then((data) => { - return { - _id: data._id, - name: data.name, - published: data.published, - ...data.project - }; - }) - .catch((err) => { - console.log(`Error unpublishing project ${err}`); - throw err; - }); - - return unpublishedProject; -}; - -export const deleteProject = (project: any): Promise => { - const body = JSON.stringify({ - _id: project._id - // userId: window.localStorage.getItem('ssid') - }); - const deletedProject = fetch(`${serverURL}/deleteProject`, { - method: 'DELETE', - credentials: 'include', - headers: { - 'Content-Type': 'application/json' - }, - body - }) - .then((res) => res.json()) - .then((data) => { - return { - _id: data._id, - name: data.name, - published: data.published, - ...data.project - }; - }) - .catch((err) => console.log(`Error deleting project ${err}`)); - return deletedProject; -}; +import { State } from '../interfaces/Interfaces'; + +const isDev = import.meta.env.NODE_ENV === 'development'; +import serverConfig from '../serverConfig.js'; + +const { DEV_PORT, API_BASE_URL } = serverConfig; +// import config from '../../../config.js'; +// const { DEV_PORT, API_BASE_URL } = config; +let serverURL = API_BASE_URL; + +//check if we're in dev mode +if (isDev) { + serverURL = `http://localhost:${DEV_PORT}`; +} + +export const getProjects = (): Promise => { + const projects = fetch(`${serverURL}/getProjects`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + // need credentials for userid pull from cookie + credentials: 'include' + }) + .then((res) => res.json()) + .then((data) => { + return data; + }) + .catch((err) => console.log(`Error getting project ${err}`)); + return projects; //returns an array of projects with _id, name, project +}; + +export const saveProject = ( + name: string, + workspace: State +): Promise => { + const newProject = { ...workspace }; + delete newProject._id; + delete newProject.name; //deleting the _id from the current state slice. We don't actually want it in the project object in the mongo db document + const body = JSON.stringify({ + name, + project: { ...newProject }, + comments: [] + }); + const project = fetch(`${serverURL}/saveProject`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + credentials: 'include', + body + }) + .then((res) => res.json()) + .then((data) => { + return { + _id: data._id, + name: data.name, + published: data.published, + ...data.project + }; //passing up what is needed for the global appstateslice + }) + .catch((err) => console.log(`Error saving project ${err}`)); + return project; //returns _id in addition to the project object from the document +}; + +export const publishProject = ( + name: string, + workspace: State +): Promise => { + const newProject = { ...workspace }; + delete newProject.name; + const body = JSON.stringify({ + _id: workspace._id, + name: name, + project: { ...newProject }, + comments: [] + }); + + const response = fetch(`${serverURL}/publishProject`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'include', + body + }); + + const publishedProject = response + .then((res) => res.json()) + .then((data) => { + return { + _id: data._id, + name: data.name, + published: data.published, + ...data.project + }; + }) + .catch((err) => { + console.log(`Error publishing project ${err}`); + throw err; + }); + + return publishedProject; +}; + +export const unpublishProject = (projectData: State): Promise => { + const body = JSON.stringify({ + _id: projectData._id + }); + + const response = fetch(`${serverURL}/unpublishProject`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'include', + body + }); + + const unpublishedProject = response + .then((res) => res.json()) + .then((data) => { + return { + _id: data._id, + name: data.name, + published: data.published, + ...data.project + }; + }) + .catch((err) => { + console.log(`Error unpublishing project ${err}`); + throw err; + }); + + return unpublishedProject; +}; + +export const deleteProject = (project: any): Promise => { + const body = JSON.stringify({ + _id: project._id + // userId: window.localStorage.getItem('ssid') + }); + const deletedProject = fetch(`${serverURL}/deleteProject`, { + method: 'DELETE', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + }, + body + }) + .then((res) => res.json()) + .then((data) => { + return { + _id: data._id, + name: data.name, + published: data.published, + ...data.project + }; + }) + .catch((err) => console.log(`Error deleting project ${err}`)); + return deletedProject; +}; diff --git a/app/src/helperFunctions/randomPassword.ts b/app/src/helperFunctions/randomPassword.ts index fefbba33c..3ab38a68a 100644 --- a/app/src/helperFunctions/randomPassword.ts +++ b/app/src/helperFunctions/randomPassword.ts @@ -1,21 +1,21 @@ -const randomPassword = () => { - function getRandomSpecialChar() { - const code = Math.round(Math.random() * (38 - 37) + 37); - return String.fromCharCode(code); - } - function getRandomDigit() { - const code = Math.round(Math.random() * (57 - 48) + 48); - return String.fromCharCode(code); - } - function getRandomLetter() { - const code = Math.round(Math.random() * (90 - 65) + 65); - return String.fromCharCode(code); - } - let password = ''; - - for (let i = 0; i < 6; i += 1) { - password += getRandomLetter() + getRandomDigit() + getRandomSpecialChar(); - } - return password; -}; -export default randomPassword; +const randomPassword = () => { + function getRandomSpecialChar() { + const code = Math.round(Math.random() * (38 - 37) + 37); + return String.fromCharCode(code); + } + function getRandomDigit() { + const code = Math.round(Math.random() * (57 - 48) + 48); + return String.fromCharCode(code); + } + function getRandomLetter() { + const code = Math.round(Math.random() * (90 - 65) + 65); + return String.fromCharCode(code); + } + let password = ''; + + for (let i = 0; i < 6; i += 1) { + password += getRandomLetter() + getRandomDigit() + getRandomSpecialChar(); + } + return password; +}; +export default randomPassword; diff --git a/app/src/helperFunctions/socket.ts b/app/src/helperFunctions/socket.ts index f03029425..3de9c703c 100644 --- a/app/src/helperFunctions/socket.ts +++ b/app/src/helperFunctions/socket.ts @@ -1,30 +1,30 @@ -import { io } from 'socket.io-client'; -// import config from '../../../config.js'; -import serverConfig from '../serverConfig'; -const { API_BASE_URL } = serverConfig; -let socket = null; - -export const initializeSocket = () => { - socket = io(API_BASE_URL, { - transports: ['websocket'], - // will force new socket connection if re-joining to prevent double emits - forceNew: true - }); -}; - -// export socket to ensure a single socket instance across the entire app -export const getSocket = () => { - return socket; -}; - -export const disconnectSocket = () => { - if (socket) { - socket.disconnect(); - } -}; - -export const emitEvent = (event, roomCode, data) => { - if (socket) { - socket.emit(event, roomCode, data); - } -}; +import { io } from 'socket.io-client'; +// import config from '../../../config.js'; +import serverConfig from '../serverConfig'; +const { API_BASE_URL } = serverConfig; +let socket = null; + +export const initializeSocket = () => { + socket = io(API_BASE_URL, { + transports: ['websocket'], + // will force new socket connection if re-joining to prevent double emits + forceNew: true + }); +}; + +// export socket to ensure a single socket instance across the entire app +export const getSocket = () => { + return socket; +}; + +export const disconnectSocket = () => { + if (socket) { + socket.disconnect(); + } +}; + +export const emitEvent = (event, roomCode, data) => { + if (socket) { + socket.emit(event, roomCode, data); + } +}; diff --git a/app/src/helperFunctions/zipFiles.ts b/app/src/helperFunctions/zipFiles.ts index 7b15eb9e4..2a7a1f217 100644 --- a/app/src/helperFunctions/zipFiles.ts +++ b/app/src/helperFunctions/zipFiles.ts @@ -1,33 +1,33 @@ -import { saveAs } from 'file-saver'; -import JSZip from'jszip'; -import { State } from '../interfaces/Interfaces'; - -//function to create a zip file for export in web app -const zipFiles = (state: State) => { - //initializes zip - var zip = new JSZip(); - let reacTypeApp = zip.folder('ReacTypeApp'); - //creates component folder inside of zip folder - let componentFolder = reacTypeApp.folder('componentfolder'); - //writes a file with default index.html code - reacTypeApp.file( - 'index.html', - ' ReacType App
    ' - ); - //writes each component as its own file in the component folder - for (let i in state.components) { - componentFolder.file( - `${state.components[i].name}.jsx`, - state.components[i].code - ); - } - //writes our css file - reacTypeApp.file('style.css', state.stylesheet); - //zips the file and saves to local machine - zip.generateAsync({ type: 'blob' }).then(function (content) { - // see FileSaver.js - saveAs(content, 'ReacTypeApp.zip'); - }); -}; - -export default zipFiles; +import { saveAs } from 'file-saver'; +import JSZip from'jszip'; +import { State } from '../interfaces/Interfaces'; + +//function to create a zip file for export in web app +const zipFiles = (state: State) => { + //initializes zip + var zip = new JSZip(); + let reacTypeApp = zip.folder('ReacTypeApp'); + //creates component folder inside of zip folder + let componentFolder = reacTypeApp.folder('componentfolder'); + //writes a file with default index.html code + reacTypeApp.file( + 'index.html', + ' ReacType App
    ' + ); + //writes each component as its own file in the component folder + for (let i in state.components) { + componentFolder.file( + `${state.components[i].name}.jsx`, + state.components[i].code + ); + } + //writes our css file + reacTypeApp.file('style.css', state.stylesheet); + //zips the file and saves to local machine + zip.generateAsync({ type: 'blob' }).then(function (content) { + // see FileSaver.js + saveAs(content, 'ReacTypeApp.zip'); + }); +}; + +export default zipFiles; diff --git a/app/src/index.tsx b/app/src/index.tsx index d7b42c362..1e48b41c4 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -1,94 +1,100 @@ -import 'babel-polyfill'; - -import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; -import { - Redirect, - Route, - HashRouter as Router, - Switch -} from 'react-router-dom'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import App from './components/App'; -import FBPassWord from './components/login/FBPassWord'; -import ProjectDashboard from './Dashboard/ProjectContainer'; -import { useState, useEffect } from 'react'; -import { Provider } from 'react-redux'; -import ReactDOM from 'react-dom'; -import SignIn from './components/login/SignIn'; -import SignUp from './components/login/SignUp'; -import Tutorial from './tutorial/Tutorial'; -import TutorialPage from './tutorial/TutorialPage'; -import store from './redux/store'; - -const client = new ApolloClient({ - uri: 'https://reactype-caret.herokuapp.com/graphql', - cache: new InMemoryCache() -}); - -const isDev = import.meta.env.NODE_ENV === 'development'; -import serverConfig from './serverConfig.js'; - -const { DEV_PORT, API_BASE_URL } = serverConfig; -let serverURL = API_BASE_URL; - -if (isDev) { - serverURL = `http://localhost:${DEV_PORT}`; -} - -const PrivateRoute = ({ component: Component, ...rest }) => { - const [isLoggedIn, setIsLoggedIn] = useState(null); - - useEffect(() => { - const projects = fetch(`${serverURL}/loggedIn`, { - method: 'GET', - headers: { - 'content-type': 'application/json' - }, - credentials: 'include' - }) - .then((res) => res.json()) - .then((data) => { - setIsLoggedIn(data); - }) - .catch((err) => console.log(`Error getting project ${err}`)); - }, []); - - return ( - { - if ( - isLoggedIn === true || - window.localStorage.getItem('ssid') === 'guest' - ) { - return ; - } else if (isLoggedIn !== null) { - return ; - } - }} - /> - ); -}; - -ReactDOM.render( - - - - - - - - - - - - - - - - - - , - document.getElementById('app') -); +import 'babel-polyfill'; + +import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; +import { + Redirect, + Route, + HashRouter as Router, + Switch, +} from 'react-router-dom'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import App from './components/App'; +import FBPassWord from './components/login/FBPassWord'; +import ProjectDashboard from './Dashboard/ProjectContainer'; +import { useState, useEffect } from 'react'; +import { Provider } from 'react-redux'; +import ReactDOM from 'react-dom'; +import SignIn from './components/login/SignIn'; +import SignUp from './components/login/SignUp'; +import Tutorial from './tutorial/Tutorial'; +import TutorialPage from './tutorial/TutorialPage'; +import store from './redux/store'; + +const client = new ApolloClient({ + uri: 'https://reactype-caret.herokuapp.com/graphql', + cache: new InMemoryCache() +}); + +const isDev = import.meta.env.NODE_ENV === 'development'; +import serverConfig from './serverConfig.js'; + +const { DEV_PORT, API_BASE_URL } = serverConfig; +let serverURL = API_BASE_URL; + +if (isDev) { + serverURL = `http://localhost:${DEV_PORT}`; +} + +const PrivateRoute = ({ component: Component, ...rest }) => { + const [isLoggedIn, setIsLoggedIn] = useState(null); + + useEffect(() => { + const projects = fetch(`${serverURL}/loggedIn`, { + method: 'GET', + headers: { + 'content-type': 'application/json' + }, + credentials: 'include' + }) + .then((res) => { + if(!res.ok){ + throw new Error('Network Response was not ok') + } + return res.json(); + }) + .then((data) => { + setIsLoggedIn(data); + }) + .catch((err) => { + console.log(`Error getting project ${err}`)}); + }, []); + + return ( + { + if ( + isLoggedIn === true || + window.localStorage.getItem('ssid') === 'guest' + ) { + return ; + } else if (isLoggedIn === false || isLoggedIn !== null) { + return ; + } + }} + /> + ); +}; + +ReactDOM.render( + + + + + + + + + + + + + + + + + + , + document.getElementById('app') +); diff --git a/app/src/interfaces/declarations.d.ts b/app/src/interfaces/declarations.d.ts index da7fb7f60..6de422b18 100644 --- a/app/src/interfaces/declarations.d.ts +++ b/app/src/interfaces/declarations.d.ts @@ -1,5 +1,5 @@ -declare module '*.png'; -declare module '*.jpg'; -declare module '*.jpeg'; -declare module '*.svg'; -declare module '*.gif'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.svg'; +declare module '*.gif'; diff --git a/app/src/interfaces/global.ts b/app/src/interfaces/global.ts index 7713eb45b..228853811 100644 --- a/app/src/interfaces/global.ts +++ b/app/src/interfaces/global.ts @@ -1,8 +1,8 @@ -export {}; -declare global { - interface Window { - api: any; - } -} - -let api = window.api; +export {}; +declare global { + interface Window { + api: any; + } +} + +let api = window.api; diff --git a/app/src/plugins/fetch-plugin.ts b/app/src/plugins/fetch-plugin.ts index e1636d671..741413d0f 100644 --- a/app/src/plugins/fetch-plugin.ts +++ b/app/src/plugins/fetch-plugin.ts @@ -1,63 +1,63 @@ -import * as esbuild from 'esbuild-wasm'; -import axios from 'axios'; -import localForage from 'localforage'; -const fileCache = localForage.createInstance({ - name: 'filecache' -}); -export const fetchPlugin = (inputCode: string) => { - return { - name: 'fetch-plugin', - setup(build: esbuild.PluginBuild) { - build.onLoad({ filter: /(^index\.js$)/ }, () => { - return { - loader: 'jsx', - contents: inputCode, - }; - }); - build.onLoad({ filter: /.*/}, async (args: any) => { - const cachedResult = await fileCache.getItem(args.path); - if(cachedResult) { - return cachedResult; - } - }); - build.onLoad({ filter: /.css$/ }, async (args: any) => { - const { data, request } = await axios.get(args.path); - const escaped = data - .replace(/\n/g, '') - .replace(/"/g, '\\"') - .replace(/'/g, "\\'") - const contents = - ` - const style = document.createElement('style'); - style.innerText = '${escaped}'; - document.head.appendChild(style) - `; - const result: esbuild.OnLoadResult = { - loader: 'jsx', - contents, - resolveDir: new URL('./', request.responseURL).pathname - } - await fileCache.setItem(args.path, result); - return result; - }); - - - build.onLoad({ filter: /.*/ }, async (args: any) => { - - const cachedResult = await fileCache.getItem(args.path); - if(cachedResult) { - return cachedResult; - } - const { data, request } = await axios.get(args.path); - - const result: esbuild.OnLoadResult = { - loader: 'jsx', - contents: data, - resolveDir: new URL('./', request.responseURL).pathname - } - await fileCache.setItem(args.path, result); - return result; - }); - } - }; +import * as esbuild from 'esbuild-wasm'; +import axios from 'axios'; +import localForage from 'localforage'; +const fileCache = localForage.createInstance({ + name: 'filecache' +}); +export const fetchPlugin = (inputCode: string) => { + return { + name: 'fetch-plugin', + setup(build: esbuild.PluginBuild) { + build.onLoad({ filter: /(^index\.js$)/ }, () => { + return { + loader: 'jsx', + contents: inputCode, + }; + }); + build.onLoad({ filter: /.*/}, async (args: any) => { + const cachedResult = await fileCache.getItem(args.path); + if(cachedResult) { + return cachedResult; + } + }); + build.onLoad({ filter: /.css$/ }, async (args: any) => { + const { data, request } = await axios.get(args.path); + const escaped = data + .replace(/\n/g, '') + .replace(/"/g, '\\"') + .replace(/'/g, "\\'") + const contents = + ` + const style = document.createElement('style'); + style.innerText = '${escaped}'; + document.head.appendChild(style) + `; + const result: esbuild.OnLoadResult = { + loader: 'jsx', + contents, + resolveDir: new URL('./', request.responseURL).pathname + } + await fileCache.setItem(args.path, result); + return result; + }); + + + build.onLoad({ filter: /.*/ }, async (args: any) => { + + const cachedResult = await fileCache.getItem(args.path); + if(cachedResult) { + return cachedResult; + } + const { data, request } = await axios.get(args.path); + + const result: esbuild.OnLoadResult = { + loader: 'jsx', + contents: data, + resolveDir: new URL('./', request.responseURL).pathname + } + await fileCache.setItem(args.path, result); + return result; + }); + } + }; } \ No newline at end of file diff --git a/app/src/plugins/unpkg-path-plugin.ts b/app/src/plugins/unpkg-path-plugin.ts index 697b44eb1..eaa0f2d83 100644 --- a/app/src/plugins/unpkg-path-plugin.ts +++ b/app/src/plugins/unpkg-path-plugin.ts @@ -1,27 +1,27 @@ -import * as esbuild from 'esbuild-wasm'; -export const unpkgPathPlugin = () => { - return { - name: 'unpkg-path-plugin', - setup(build: esbuild.PluginBuild) { - // Handle root entry file of index.js - build.onResolve({ filter: /(^index\.js$)/ }, () => { - return { path: 'index.js', namespace: 'a' }; - }); - // Handle relative paths in a module - build.onResolve({ filter: /^\.+\// }, (args: any) => { - const seeURL = new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href; - return { - namespace: 'a', - path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href - }; - }); - // Handle main file of a module - build.onResolve({ filter: /.*/ }, async (args: any) => { - return { - namespace: 'a', - path: `https://unpkg.com/${args.path}` - } - }); - }, - }; +import * as esbuild from 'esbuild-wasm'; +export const unpkgPathPlugin = () => { + return { + name: 'unpkg-path-plugin', + setup(build: esbuild.PluginBuild) { + // Handle root entry file of index.js + build.onResolve({ filter: /(^index\.js$)/ }, () => { + return { path: 'index.js', namespace: 'a' }; + }); + // Handle relative paths in a module + build.onResolve({ filter: /^\.+\// }, (args: any) => { + const seeURL = new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href; + return { + namespace: 'a', + path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href + }; + }); + // Handle main file of a module + build.onResolve({ filter: /.*/ }, async (args: any) => { + return { + namespace: 'a', + path: `https://unpkg.com/${args.path}` + } + }); + }, + }; }; \ No newline at end of file diff --git a/app/src/public/index-prod.ejs b/app/src/public/index-prod.ejs index 932ab1ae9..8ccd08c60 100644 --- a/app/src/public/index-prod.ejs +++ b/app/src/public/index-prod.ejs @@ -1,22 +1,22 @@ - - - - - - - ReacType - - - -
    - - + + + + + + + ReacType + + + +
    + + diff --git a/app/src/public/index.ejs b/app/src/public/index.ejs index 3d58977cb..eb41e7194 100644 --- a/app/src/public/index.ejs +++ b/app/src/public/index.ejs @@ -1,27 +1,27 @@ - - - - - - - ReacType - - - - - -
    - - + + + + + + + ReacType + + + + + +
    + + diff --git a/app/src/public/styles/globalDefaultStyles.ts b/app/src/public/styles/globalDefaultStyles.ts index db79ebc1b..f8f6f3ded 100644 --- a/app/src/public/styles/globalDefaultStyles.ts +++ b/app/src/public/styles/globalDefaultStyles.ts @@ -1,20 +1,20 @@ -import { autoUpdater } from "electron"; - -const globalDefaultStyle: Object = { - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - boxSizing: 'border-box', - padding: '10px 20px 10px 20px', - margin: '10px', - borderRadius: '10px', - border: '10px Solid grey', - fontFamily: 'Roboto', - color: '#f1efea', - maxWidth: 'fit-content', - minWidth: '250px', - cursor: 'grab', - backgroundColor: 'rgba(0, 0, 0, 0.2)', -}; - -export default globalDefaultStyle; +import { autoUpdater } from "electron"; + +const globalDefaultStyle: Object = { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + boxSizing: 'border-box', + padding: '10px 20px 10px 20px', + margin: '10px', + borderRadius: '10px', + border: '10px Solid grey', + fontFamily: 'Roboto', + color: '#f1efea', + maxWidth: 'fit-content', + minWidth: '250px', + cursor: 'grab', + backgroundColor: 'rgba(0, 0, 0, 0.2)', +}; + +export default globalDefaultStyle; diff --git a/app/src/public/styles/theme.ts b/app/src/public/styles/theme.ts index afc18f316..45638a5c9 100644 --- a/app/src/public/styles/theme.ts +++ b/app/src/public/styles/theme.ts @@ -1,62 +1,62 @@ -import { createTheme, adaptV4Theme } from '@mui/material/styles'; -// theme creator: https://bareynol.github.io/mui-theme-creator/ - -export const theme1 = createTheme({ - palette: { - mode: 'dark', - primary: { - main: '#0671e3' // navy blue - }, - secondary: { - main: '#0671e3' // light blue - }, - background: { - paper: '#0671e3' - } - } -}); - -export const theme2 = createTheme({ - palette: { - mode: 'light', - primary: { - main: '#0671e3' - }, - secondary: { - main: '#0671e3' - }, - background: { - paper: '#0671e3' - } - } -}); - -export const SigninDark = createTheme({ - palette: { - mode: 'dark', - primary: { - main: '#3c59ba' - }, - secondary: { - main: '#17a2b8' - }, - background: { - paper: '#2997ff' - } - } -}); - -export const SigninLight = createTheme({ - palette: { - mode: 'light', - primary: { - main: '#3c59ba' - }, - secondary: { - main: '#17a2b8' - }, - background: { - paper: '#0671e3' - } - } -}); +import { createTheme, adaptV4Theme } from '@mui/material/styles'; +// theme creator: https://bareynol.github.io/mui-theme-creator/ + +export const theme1 = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#0671e3' // navy blue + }, + secondary: { + main: '#0671e3' // light blue + }, + background: { + paper: '#0671e3' + } + } +}); + +export const theme2 = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#0671e3' + }, + secondary: { + main: '#0671e3' + }, + background: { + paper: '#0671e3' + } + } +}); + +export const SigninDark = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#3c59ba' + }, + secondary: { + main: '#17a2b8' + }, + background: { + paper: '#2997ff' + } + } +}); + +export const SigninLight = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#3c59ba' + }, + secondary: { + main: '#17a2b8' + }, + background: { + paper: '#0671e3' + } + } +}); diff --git a/app/src/redux/HTMLTypes.ts b/app/src/redux/HTMLTypes.ts index c0d3fcf39..f756e18b5 100644 --- a/app/src/redux/HTMLTypes.ts +++ b/app/src/redux/HTMLTypes.ts @@ -1,242 +1,242 @@ -import { TroubleshootSharp } from '@mui/icons-material'; -import { HTMLType } from '../interfaces/Interfaces'; - -//properties for all HTML components - -const HTMLTypes: HTMLType[] = [ - { - id: 11, - tag: 'div', - name: 'Div', - style: {}, - placeHolderShort: 'div', - placeHolderLong: '', - icon: 'Code', - framework: 'reactClassic', - nestable: true - }, - // do not move this separator element out of index 1 in this array - // in componentReducer.ts, separator is referenced as 'initialState.HTMLTypes[1]' - { - id: 1000, - tag: 'separator', - name: 'separator', - style: { border: 'none' }, - placeHolderShort: '', - placeHolderLong: '', - icon: null, - framework: '', - nestable: true - }, - { - id: 1, - tag: 'a', - name: 'Link', - style: {}, - placeHolderShort: 'link', - placeHolderLong: '', - icon: 'Link', - framework: 'reactClassic', - nestable: true - }, - { - id: 2, - tag: 'h1', - name: 'Header 1', - style: {}, - placeHolderShort: 'header 1', - placeHolderLong: '', - icon: 'TextFormat', - framework: 'reactClassic', - nestable: true - }, - { - id: 4, - tag: 'h2', - name: 'Header 2', - style: {}, - placeHolderShort: 'header 2', - placeHolderLong: '', - icon: 'TextFormat', - framework: 'reactClassic', - nestable: true - }, - { - id: 6, - tag: 'span', - name: 'Span', - style: {}, - placeHolderShort: 'span', - placeHolderLong: '', - icon: 'ShortTextOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 8, - tag: 'p', - name: 'Paragraph', - style: {}, - placeHolderShort: 'paragraph', - placeHolderLong: '', - icon: 'NotesOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 9, - tag: 'form', - name: 'Form', - style: {}, - placeHolderShort: 'form', - placeHolderLong: '', - icon: 'Description', - framework: 'reactClassic', - nestable: true - }, - { - id: 10, - tag: 'input', - name: 'Input', - style: {}, - placeHolderShort: 'input', - placeHolderLong: '', - icon: 'EditOutlined', - framework: 'reactClassic', - nestable: false - }, - { - id: 5, - tag: 'button', - name: 'Button', - style: {}, - placeHolderShort: 'button', - placeHolderLong: '', - icon: 'EditAttributes', - framework: 'reactClassic', - nestable: true - }, - - { - id: 12, - tag: 'img', - name: 'Img', - style: {}, - placeHolderShort: 'image', - placeHolderLong: '', - icon: 'Image', - framework: 'reactClassic', - nestable: false - }, - { - id: 13, - tag: 'label', - name: 'Label', - style: {}, - placeHolderShort: 'label', - placeHolderLong: '', - icon: 'MoreOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 14, - tag: 'menu', - name: 'Menu', - style: {}, - placeHolderShort: 'menu', - placeHolderLong: '', - icon: 'FeaturedPlayListOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 15, - tag: 'ol', - name: 'Ordered List', - style: {}, - placeHolderShort: 'ordered list', - placeHolderLong: '', - icon: 'FormatListNumberedOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 16, - tag: 'ul', - name: 'Unordered List', - style: {}, - placeHolderShort: 'unordered list', - placeHolderLong: '', - icon: 'FormatListBulletedOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 3, - tag: 'li', - name: 'List', - style: {}, - placeHolderShort: 'list item', - placeHolderLong: '', - icon: 'ListAltOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 17, - tag: 'Switch', - name: 'Switch', - style: {}, - placeHolderShort: 'Switch', - placeHolderLong: '', - icon: 'ToggleOffOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: -1, - tag: 'Route', - name: 'Route', - style: {}, - placeHolderShort: 'Route', - placeHolderLong: '', - icon: 'RouteOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 18, - tag: 'Link', - name: 'LinkTo', - style: {}, - placeHolderShort: 'LinkTo', - placeHolderLong: '', - icon: 'DatasetLinkedOutlined', - framework: 'reactClassic', - nestable: true - }, - { - id: 19, - tag: 'Link', - name: 'LinkHref', - style: {}, - placeHolderShort: 'LinkHref', - placeHolderLong: '', - icon: 'Link', - framework: 'nextjs', - nestable: true - }, - { - id: 20, - tag: 'Image', - name: 'Image', - style: {}, - placeHolderShort: 'Image', - placeHolderLong: '', - icon: 'ImageIcon', - framework: 'nextjs', - nestable: false - } -]; -export default HTMLTypes; +import { TroubleshootSharp } from '@mui/icons-material'; +import { HTMLType } from '../interfaces/Interfaces'; + +//properties for all HTML components + +const HTMLTypes: HTMLType[] = [ + { + id: 11, + tag: 'div', + name: 'Div', + style: {}, + placeHolderShort: 'div', + placeHolderLong: '', + icon: 'Code', + framework: 'reactClassic', + nestable: true + }, + // do not move this separator element out of index 1 in this array + // in componentReducer.ts, separator is referenced as 'initialState.HTMLTypes[1]' + { + id: 1000, + tag: 'separator', + name: 'separator', + style: { border: 'none' }, + placeHolderShort: '', + placeHolderLong: '', + icon: null, + framework: '', + nestable: true + }, + { + id: 1, + tag: 'a', + name: 'Link', + style: {}, + placeHolderShort: 'link', + placeHolderLong: '', + icon: 'Link', + framework: 'reactClassic', + nestable: true + }, + { + id: 2, + tag: 'h1', + name: 'Header 1', + style: {}, + placeHolderShort: 'header 1', + placeHolderLong: '', + icon: 'TextFormat', + framework: 'reactClassic', + nestable: true + }, + { + id: 4, + tag: 'h2', + name: 'Header 2', + style: {}, + placeHolderShort: 'header 2', + placeHolderLong: '', + icon: 'TextFormat', + framework: 'reactClassic', + nestable: true + }, + { + id: 6, + tag: 'span', + name: 'Span', + style: {}, + placeHolderShort: 'span', + placeHolderLong: '', + icon: 'ShortTextOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 8, + tag: 'p', + name: 'Paragraph', + style: {}, + placeHolderShort: 'paragraph', + placeHolderLong: '', + icon: 'NotesOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 9, + tag: 'form', + name: 'Form', + style: {}, + placeHolderShort: 'form', + placeHolderLong: '', + icon: 'Description', + framework: 'reactClassic', + nestable: true + }, + { + id: 10, + tag: 'input', + name: 'Input', + style: {}, + placeHolderShort: 'input', + placeHolderLong: '', + icon: 'EditOutlined', + framework: 'reactClassic', + nestable: false + }, + { + id: 5, + tag: 'button', + name: 'Button', + style: {}, + placeHolderShort: 'button', + placeHolderLong: '', + icon: 'EditAttributes', + framework: 'reactClassic', + nestable: true + }, + + { + id: 12, + tag: 'img', + name: 'Img', + style: {}, + placeHolderShort: 'image', + placeHolderLong: '', + icon: 'Image', + framework: 'reactClassic', + nestable: false + }, + { + id: 13, + tag: 'label', + name: 'Label', + style: {}, + placeHolderShort: 'label', + placeHolderLong: '', + icon: 'MoreOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 14, + tag: 'menu', + name: 'Menu', + style: {}, + placeHolderShort: 'menu', + placeHolderLong: '', + icon: 'FeaturedPlayListOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 15, + tag: 'ol', + name: 'Ordered List', + style: {}, + placeHolderShort: 'ordered list', + placeHolderLong: '', + icon: 'FormatListNumberedOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 16, + tag: 'ul', + name: 'Unordered List', + style: {}, + placeHolderShort: 'unordered list', + placeHolderLong: '', + icon: 'FormatListBulletedOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 3, + tag: 'li', + name: 'List', + style: {}, + placeHolderShort: 'list item', + placeHolderLong: '', + icon: 'ListAltOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 17, + tag: 'Switch', + name: 'Switch', + style: {}, + placeHolderShort: 'Switch', + placeHolderLong: '', + icon: 'ToggleOffOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: -1, + tag: 'Route', + name: 'Route', + style: {}, + placeHolderShort: 'Route', + placeHolderLong: '', + icon: 'RouteOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 18, + tag: 'Link', + name: 'LinkTo', + style: {}, + placeHolderShort: 'LinkTo', + placeHolderLong: '', + icon: 'DatasetLinkedOutlined', + framework: 'reactClassic', + nestable: true + }, + { + id: 19, + tag: 'Link', + name: 'LinkHref', + style: {}, + placeHolderShort: 'LinkHref', + placeHolderLong: '', + icon: 'Link', + framework: 'nextjs', + nestable: true + }, + { + id: 20, + tag: 'Image', + name: 'Image', + style: {}, + placeHolderShort: 'Image', + placeHolderLong: '', + icon: 'ImageIcon', + framework: 'nextjs', + nestable: false + } +]; +export default HTMLTypes; diff --git a/app/src/redux/reducers/rootReducer.ts b/app/src/redux/reducers/rootReducer.ts index e936d9265..5c2915bd1 100644 --- a/app/src/redux/reducers/rootReducer.ts +++ b/app/src/redux/reducers/rootReducer.ts @@ -1,18 +1,18 @@ -import { combineReducers } from '@reduxjs/toolkit'; - -// Need to import each slice which will be combined in the rootReducer -import codePreviewReducer from './slice/codePreviewSlice'; -import contextReducer from './slice/contextReducer'; -import appStateReducer from './slice/appStateSlice'; -import styleReducer from './slice/styleSlice'; -import roomReducer from './slice/roomSlice'; - -const rootReducer = combineReducers({ - codePreviewSlice: codePreviewReducer, - contextSlice: contextReducer, - appState: appStateReducer, - styleSlice: styleReducer, - roomSlice: roomReducer -}); - -export default rootReducer; +import { combineReducers } from '@reduxjs/toolkit'; + +// Need to import each slice which will be combined in the rootReducer +import codePreviewReducer from './slice/codePreviewSlice'; +import contextReducer from './slice/contextReducer'; +import appStateReducer from './slice/appStateSlice'; +import styleReducer from './slice/styleSlice'; +import roomReducer from './slice/roomSlice'; + +const rootReducer = combineReducers({ + codePreviewSlice: codePreviewReducer, + contextSlice: contextReducer, + appState: appStateReducer, + styleSlice: styleReducer, + roomSlice: roomReducer +}); + +export default rootReducer; diff --git a/app/src/redux/reducers/slice/codePreviewSlice.ts b/app/src/redux/reducers/slice/codePreviewSlice.ts index 1e50df056..8e558a9cb 100644 --- a/app/src/redux/reducers/slice/codePreviewSlice.ts +++ b/app/src/redux/reducers/slice/codePreviewSlice.ts @@ -1,29 +1,29 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - code: ``, - input: `` -}; - -//realtime updates for the code preview - -const codePreviewSlice = createSlice({ - name: 'codePreview', - initialState, - reducers: { - codePreviewSave: (state, action) => { - state.code = action.payload; - }, - codePreviewInput: (state, action) => { - state.input = action.payload; - }, - codePreviewCooperative: (state, action) => { - return Object.assign({}, state, action.payload); - } - } -}); - -export const { codePreviewSave, codePreviewInput, codePreviewCooperative } = - codePreviewSlice.actions; - -export default codePreviewSlice.reducer; +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + code: ``, + input: `` +}; + +//realtime updates for the code preview + +const codePreviewSlice = createSlice({ + name: 'codePreview', + initialState, + reducers: { + codePreviewSave: (state, action) => { + state.code = action.payload; + }, + codePreviewInput: (state, action) => { + state.input = action.payload; + }, + codePreviewCooperative: (state, action) => { + return Object.assign({}, state, action.payload); + } + } +}); + +export const { codePreviewSave, codePreviewInput, codePreviewCooperative } = + codePreviewSlice.actions; + +export default codePreviewSlice.reducer; diff --git a/app/src/redux/reducers/slice/contextReducer.ts b/app/src/redux/reducers/slice/contextReducer.ts index 17915b25a..051de4b31 100644 --- a/app/src/redux/reducers/slice/contextReducer.ts +++ b/app/src/redux/reducers/slice/contextReducer.ts @@ -1,105 +1,105 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -// -------------------------// -// interfaces for all methods // calling actions payloads type referencss assigning type // typesccript -//Defined Slice State and Action Types on this page -interface Context { - name: string; - values: Array<{ key: string; value: string }>; - components: string[]; -} - -export interface AddContextPayload { - name: string; -} - -export interface AddContextValuesPayload { - name: string; - inputKey: string; - inputValue: string; -} - -export interface DeleteContextPayload { - name: string; -} - -interface AddComponentToContextPayload { - context: { name: string }; - component: { name: string }; -} -// did not do getall context and allcontext cooperative -// most imprtant because it allows reference from the intial state to all the interfaces -interface ContextState { - allContext: Context[]; -} -// -------------------------// - -const initialState: ContextState = { - allContext: [] -}; - -const contextReducerSlice = createSlice({ - name: 'context', - initialState, - reducers: { - addContext: (state, action: PayloadAction) => { - let newName = action.payload.name.trim(); - newName = newName.charAt(0).toUpperCase() + newName.slice(1); - const newContext = { - name: newName, - values: [], - components: [] - }; - state.allContext = [...state.allContext, newContext]; - }, - addContextValues: ( - state, - action: PayloadAction - ) => { - const newAllContext = [...state.allContext]; - - for (let i = 0; i < newAllContext.length; i += 1) { - if (newAllContext[i].name === action.payload.name) { - newAllContext[i].values.push({ - key: action.payload.inputKey, - value: action.payload.inputValue - }); - } - } - state.allContext = newAllContext; - }, - deleteContext: (state, action: PayloadAction) => { - const tempState = [...state.allContext]; - const remains = tempState.filter((el) => el.name !== action.payload.name); - state.allContext = remains; - }, - addComponentToContext: ( - state, - action: PayloadAction - ) => { - const newTempState = [...state.allContext]; - for (let i = 0; i < newTempState.length; i += 1) { - if (newTempState[i].name === action.payload.context.name) { - newTempState[i].components.push(action.payload.component.name); - } - } - state.allContext = newTempState; - }, - getAllContext: (state, action) => { - state = state; - }, - allContextCooperative: (state, action) => { - return Object.assign({}, state, action.payload); - } - } -}); - -export const { - addContext, - addContextValues, - deleteContext, - getAllContext, - addComponentToContext, - allContextCooperative -} = contextReducerSlice.actions; -export default contextReducerSlice.reducer; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// -------------------------// +// interfaces for all methods // calling actions payloads type referencss assigning type // typesccript +//Defined Slice State and Action Types on this page +interface Context { + name: string; + values: Array<{ key: string; value: string }>; + components: string[]; +} + +export interface AddContextPayload { + name: string; +} + +export interface AddContextValuesPayload { + name: string; + inputKey: string; + inputValue: string; +} + +export interface DeleteContextPayload { + name: string; +} + +interface AddComponentToContextPayload { + context: { name: string }; + component: { name: string }; +} +// did not do getall context and allcontext cooperative +// most imprtant because it allows reference from the intial state to all the interfaces +interface ContextState { + allContext: Context[]; +} +// -------------------------// + +const initialState: ContextState = { + allContext: [] +}; + +const contextReducerSlice = createSlice({ + name: 'context', + initialState, + reducers: { + addContext: (state, action: PayloadAction) => { + let newName = action.payload.name.trim(); + newName = newName.charAt(0).toUpperCase() + newName.slice(1); + const newContext = { + name: newName, + values: [], + components: [] + }; + state.allContext = [...state.allContext, newContext]; + }, + addContextValues: ( + state, + action: PayloadAction + ) => { + const newAllContext = [...state.allContext]; + + for (let i = 0; i < newAllContext.length; i += 1) { + if (newAllContext[i].name === action.payload.name) { + newAllContext[i].values.push({ + key: action.payload.inputKey, + value: action.payload.inputValue + }); + } + } + state.allContext = newAllContext; + }, + deleteContext: (state, action: PayloadAction) => { + const tempState = [...state.allContext]; + const remains = tempState.filter((el) => el.name !== action.payload.name); + state.allContext = remains; + }, + addComponentToContext: ( + state, + action: PayloadAction + ) => { + const newTempState = [...state.allContext]; + for (let i = 0; i < newTempState.length; i += 1) { + if (newTempState[i].name === action.payload.context.name) { + newTempState[i].components.push(action.payload.component.name); + } + } + state.allContext = newTempState; + }, + getAllContext: (state, action) => { + state = state; + }, + allContextCooperative: (state, action) => { + return Object.assign({}, state, action.payload); + } + } +}); + +export const { + addContext, + addContextValues, + deleteContext, + getAllContext, + addComponentToContext, + allContextCooperative +} = contextReducerSlice.actions; +export default contextReducerSlice.reducer; diff --git a/app/src/redux/reducers/slice/roomSlice.ts b/app/src/redux/reducers/slice/roomSlice.ts index e1367034f..e14125a75 100644 --- a/app/src/redux/reducers/slice/roomSlice.ts +++ b/app/src/redux/reducers/slice/roomSlice.ts @@ -1,77 +1,77 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - roomCode: '', - meetingId: '', - userName: '', - userList: [], - userJoinCollabRoom: false, - userJoinMeetingStatus: null, - meetingParticipants: [], - messages: [], - password: '', - useMic: false, - useWebcam: false -}; - -const roomSlice = createSlice({ - name: 'room', - initialState, - reducers: { - setRoomCode: (state, action) => { - state.roomCode = action.payload; - }, - setMeetingId: (state, action) => { - state.meetingId = action.payload; - }, - setUserName: (state, action) => { - state.userName = action.payload; - }, - setUserList: (state, action) => { - state.userList = action.payload; - }, - setUserJoinCollabRoom: (state, action) => { - state.userJoinCollabRoom = action.payload; - }, - setUserJoinMeetingStatus: (state, action) => { - state.userJoinMeetingStatus = action.payload; - }, - setMeetingParticipants: (state, action) => { - state.meetingParticipants = action.payload; - }, - setMessages: (state, action) => { - state.messages = [...state.messages, action.payload]; - }, - setEmptyMessages: (state, action) => { - state.messages = []; - }, - setPassword: (state, action) => { - state.password = action.payload; - }, - setUseMic: (state, action) => { - if (action.payload === null) state.useMic = !state.useMic; - else state.useMic = action.payload; - }, - setUseWebcam: (state, action) => { - if (action.payload === null) state.useWebcam = !state.useWebcam; - else state.useWebcam = action.payload; - } - } -}); - -export const { - setRoomCode, - setMeetingId, - setUserName, - setUserList, - setUserJoinCollabRoom, - setUserJoinMeetingStatus, - setMeetingParticipants, - setMessages, - setEmptyMessages, - setPassword, - setUseMic, - setUseWebcam -} = roomSlice.actions; - -export default roomSlice.reducer; +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + roomCode: '', + meetingId: '', + userName: '', + userList: [], + userJoinCollabRoom: false, + userJoinMeetingStatus: null, + meetingParticipants: [], + messages: [], + password: '', + useMic: false, + useWebcam: false +}; + +const roomSlice = createSlice({ + name: 'room', + initialState, + reducers: { + setRoomCode: (state, action) => { + state.roomCode = action.payload; + }, + setMeetingId: (state, action) => { + state.meetingId = action.payload; + }, + setUserName: (state, action) => { + state.userName = action.payload; + }, + setUserList: (state, action) => { + state.userList = action.payload; + }, + setUserJoinCollabRoom: (state, action) => { + state.userJoinCollabRoom = action.payload; + }, + setUserJoinMeetingStatus: (state, action) => { + state.userJoinMeetingStatus = action.payload; + }, + setMeetingParticipants: (state, action) => { + state.meetingParticipants = action.payload; + }, + setMessages: (state, action) => { + state.messages = [...state.messages, action.payload]; + }, + setEmptyMessages: (state, action) => { + state.messages = []; + }, + setPassword: (state, action) => { + state.password = action.payload; + }, + setUseMic: (state, action) => { + if (action.payload === null) state.useMic = !state.useMic; + else state.useMic = action.payload; + }, + setUseWebcam: (state, action) => { + if (action.payload === null) state.useWebcam = !state.useWebcam; + else state.useWebcam = action.payload; + } + } +}); + +export const { + setRoomCode, + setMeetingId, + setUserName, + setUserList, + setUserJoinCollabRoom, + setUserJoinMeetingStatus, + setMeetingParticipants, + setMessages, + setEmptyMessages, + setPassword, + setUseMic, + setUseWebcam +} = roomSlice.actions; + +export default roomSlice.reducer; diff --git a/app/src/redux/reducers/slice/styleSlice.ts b/app/src/redux/reducers/slice/styleSlice.ts index 49474d396..2877c00f7 100644 --- a/app/src/redux/reducers/slice/styleSlice.ts +++ b/app/src/redux/reducers/slice/styleSlice.ts @@ -1,28 +1,28 @@ -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; - -type StyleState = { - style: any; // Replace with your specific type - isThemeLight: boolean; -}; - -const initialState: StyleState = { - style: null, - isThemeLight: true -}; - -const styleSlice = createSlice({ - name: 'style', - initialState, - reducers: { - setStyle: (state, action: PayloadAction) => { - state.style = action.payload; - }, - cooperativeStyle: (state, action: PayloadAction>) => { - return { ...state, ...action.payload }; - } - } -}); - -export const { setStyle, cooperativeStyle } = styleSlice.actions; - -export default styleSlice.reducer; +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; + +type StyleState = { + style: any; // Replace with your specific type + isThemeLight: boolean; +}; + +const initialState: StyleState = { + style: null, + isThemeLight: true +}; + +const styleSlice = createSlice({ + name: 'style', + initialState, + reducers: { + setStyle: (state, action: PayloadAction) => { + state.style = action.payload; + }, + cooperativeStyle: (state, action: PayloadAction>) => { + return { ...state, ...action.payload }; + } + } +}); + +export const { setStyle, cooperativeStyle } = styleSlice.actions; + +export default styleSlice.reducer; diff --git a/app/src/redux/store.ts b/app/src/redux/store.ts index 35d3c477f..3121a51d6 100644 --- a/app/src/redux/store.ts +++ b/app/src/redux/store.ts @@ -1,26 +1,26 @@ -import { configureStore } from '@reduxjs/toolkit'; -import rootReducer from './reducers/rootReducer'; - -const store = configureStore({ - reducer: rootReducer, - middleware: (getDefaultMiddleware) => { - let ignoredPaths: string[] = []; - - for (let i = 0; i < 21; i++) { - ignoredPaths.push(`appState.HTMLTypes.${i}.icon`); - ignoredPaths.push(`appState.HTMLTypes.${i}.icon.$$typeof`); - } - - return getDefaultMiddleware({ - serializableCheck: { - ignoredPaths - } - }); - } -}); - -export type RootState = ReturnType; - -export type AppStore = typeof store; - -export default store; +import { configureStore } from '@reduxjs/toolkit'; +import rootReducer from './reducers/rootReducer'; + +const store = configureStore({ + reducer: rootReducer, + middleware: (getDefaultMiddleware) => { + let ignoredPaths: string[] = []; + + for (let i = 0; i < 21; i++) { + ignoredPaths.push(`appState.HTMLTypes.${i}.icon`); + ignoredPaths.push(`appState.HTMLTypes.${i}.icon.$$typeof`); + } + + return getDefaultMiddleware({ + serializableCheck: { + ignoredPaths + } + }); + } +}); + +export type RootState = ReturnType; + +export type AppStore = typeof store; + +export default store; diff --git a/app/src/serverConfig.js b/app/src/serverConfig.js index 9158634a5..58500025f 100644 --- a/app/src/serverConfig.js +++ b/app/src/serverConfig.js @@ -1,14 +1,14 @@ -const isProduction = process.env.NODE_ENV === 'production'; -const serverConfig = { - DEV_PORT: 5656, - API_BASE_URL: isProduction - ? 'https://app.reactype.dev' - : 'http://localhost:5656', - // : 'http://localhost:8080', - API_BASE_URL2: isProduction - ? 'https://app.reactype.dev' - : 'http://localhost:8080' -}; -// module.exports = config; - -export default serverConfig; +const isProduction = process.env.NODE_ENV === 'production'; +const serverConfig = { + DEV_PORT: 5656, + API_BASE_URL: isProduction + ? 'https://app.reactype.dev' + : 'http://localhost:5656', + // : 'http://localhost:8080', + API_BASE_URL2: isProduction + ? 'https://app.reactype.dev' + : 'http://localhost:8080', +}; +// module.exports = config; + +export default serverConfig; diff --git a/app/src/tree/TreeChart.tsx b/app/src/tree/TreeChart.tsx index 8ba481ef2..ae101fd35 100644 --- a/app/src/tree/TreeChart.tsx +++ b/app/src/tree/TreeChart.tsx @@ -1,179 +1,179 @@ -import React, { useRef, useEffect, useContext, Children } from 'react'; -import { select, hierarchy, tree, linkHorizontal } from 'd3'; -import cloneDeep from 'lodash/cloneDeep'; -import useResizeObserver from './useResizeObserver'; -import { useSelector } from 'react-redux'; - -function usePrevious(value) { - const ref = useRef(); // creates a ref obj w/ current: value - useEffect(() => { - ref.current = value; - }); - return ref.current; -} - -function TreeChart({ data }) { - // data is components from state - passed in from BottomTabs - const state = useSelector((store) => store.appState); - - const canvasId = state.canvasFocus.componentId; - - const svgRef = useRef(); - const wrapperRef = useRef(); - - const xPosition = 50; - const dimensions = useResizeObserver(wrapperRef); - // we save data to see if it changed - const previouslyRenderedData = usePrevious(data); - // function to filter out separators to prevent render on tree chart - - const removeSeparators = (arr: object[]) => { - // loop over array - for (let i = 0; i < arr.length; i++) { - if (arr[i] === undefined) continue; - // if element is separator, remove it - if (arr[i].name === 'separator') { - arr.splice(i, 1); - i -= 1; - } - // if element has a children array and that array has length, recursive call - else if ( - (arr[i].name === 'div' || - arr[i].name === 'form' || - arr[i].type === 'Component' || - arr[i].name === 'Link' || - arr[i].name === 'Switch' || - arr[i].name === 'Route' || - arr[i].name === 'menu' || - arr[i].name === 'ul' || - arr[i].name === 'ol' || - arr[i].name === 'li') && - arr[i].children.length - ) { - // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) - if (arr[i].type === 'Component') - arr[i] = cloneDeep( - data.find((component) => component.name === arr[i].name) - ); - removeSeparators(arr[i].children); - } - } - // return mutated array - return arr; - }; - // create a deep clone of data to avoid mutating the actual children array in removing separators - const dataDeepClone = cloneDeep(data); - - //Miko left off - if (state.projectType === 'Next.js') { - dataDeepClone.forEach((element) => { - element.children = sanitize(element.children).filter( - (element) => !Array.isArray(element) - ); - }); - - function sanitize(children) { - return children.map((child) => { - if (child.name === 'Switch' || child.name === 'Route') { - return sanitize(child.children); - } else { - return child; - } - }); - } - } - - // remove separators and update components to current versions - dataDeepClone.forEach((component) => { - removeSeparators(component.children); - }); - // will be called initially and on every data change - useEffect(() => { - const svg = select(svgRef.current); - // use dimensions from useResizeObserver, - // but use getBoundingClientRect on initial render - // (dimensions are null for the first render) - const { width, height } = - dimensions || wrapperRef.current.getBoundingClientRect(); - // transform hierarchical data - const root = hierarchy(dataDeepClone[canvasId - 1]); // pass in clone here instead of data - const treeLayout = tree().size([height, width - 125]); - // Returns a new link generator with horizontal display. - // To visualize links in a tree diagram rooted on the left edge of the display - const linkGenerator = linkHorizontal() - .x((link) => link.y) - .y((link) => link.x); - // insert our data into the tree layout - treeLayout(root); - // node - each element in the tree - svg - .selectAll('.node') - .data(root.descendants()) - .join((enter) => enter.append('circle').attr('opacity', 0)) - .attr('class', 'node') - /* - The cx, cy attributes are associated with the circle and ellipse elements and designate the centre of each shape. The coordinates are set from the top, left hand corner of the web page. - cx: The position of the centre of the element in the x axis measured from the left side of the screen. - cy: The position of the centre of the element in the y axis measured from the top of the screen. - */ - // translate (x, y) - .attr('cx', (node) => node.y) - .attr('cy', (node) => node.x) - .attr('r', 4) // radius of circle - .attr('opacity', 1) - .style('fill', 'white') - .attr('transform', `translate(${xPosition}, 0)`); - // link - lines that connect the nodes - const enteringAndUpdatingLinks = svg - .selectAll('.link') - .data(root.links()) - .join('path') - .attr('class', 'link') - .attr('d', linkGenerator) - .attr('stroke', 'white') - .attr('fill', 'none') - .attr('opacity', 1) - .attr('transform', `translate(${xPosition}, 0)`); - if (data !== previouslyRenderedData) { - enteringAndUpdatingLinks - .attr('stroke-dashoffset', function () { - return this.length; - }) - .attr('stroke-dashoffset', 0); - } - // label - the names of each html element (node) - svg - .selectAll('.label') - .data(root.descendants()) - .join((enter) => enter.append('text').attr('opacity', 0)) - .attr('class', 'label') - .attr('x', (node) => node.y) - .attr('y', (node) => node.x - 12) - .attr('text-anchor', 'middle') - .attr('font-size', 18) - .style('fill', 'white') - .text((node) => node.data.name) - .attr('opacity', 1) - .attr('transform', `translate(${xPosition}, 0)`); - }, [state.components, dimensions, previouslyRenderedData, canvasId]); - const treeStyles = { - height: '100%', - width: `100%`, - margin: '10px 10px 10px 10px', - overflow: 'auto' - }; - const wrapperStyles = { - borderRadius: '10px', - width: '100%', - height: '97%', - display: 'flex', - justifyContent: 'center', - backgroundColor: '#1E2024' - }; - return ( -
    - -
    - ); -} -export default TreeChart; +import React, { useRef, useEffect, useContext, Children } from 'react'; +import { select, hierarchy, tree, linkHorizontal } from 'd3'; +import cloneDeep from 'lodash/cloneDeep'; +import useResizeObserver from './useResizeObserver'; +import { useSelector } from 'react-redux'; + +function usePrevious(value) { + const ref = useRef(); // creates a ref obj w/ current: value + useEffect(() => { + ref.current = value; + }); + return ref.current; +} + +function TreeChart({ data }) { + // data is components from state - passed in from BottomTabs + const state = useSelector((store) => store.appState); + + const canvasId = state.canvasFocus.componentId; + + const svgRef = useRef(); + const wrapperRef = useRef(); + + const xPosition = 50; + const dimensions = useResizeObserver(wrapperRef); + // we save data to see if it changed + const previouslyRenderedData = usePrevious(data); + // function to filter out separators to prevent render on tree chart + + const removeSeparators = (arr: object[]) => { + // loop over array + for (let i = 0; i < arr.length; i++) { + if (arr[i] === undefined) continue; + // if element is separator, remove it + if (arr[i].name === 'separator') { + arr.splice(i, 1); + i -= 1; + } + // if element has a children array and that array has length, recursive call + else if ( + (arr[i].name === 'div' || + arr[i].name === 'form' || + arr[i].type === 'Component' || + arr[i].name === 'Link' || + arr[i].name === 'Switch' || + arr[i].name === 'Route' || + arr[i].name === 'menu' || + arr[i].name === 'ul' || + arr[i].name === 'ol' || + arr[i].name === 'li') && + arr[i].children.length + ) { + // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) + if (arr[i].type === 'Component') + arr[i] = cloneDeep( + data.find((component) => component.name === arr[i].name) + ); + removeSeparators(arr[i].children); + } + } + // return mutated array + return arr; + }; + // create a deep clone of data to avoid mutating the actual children array in removing separators + const dataDeepClone = cloneDeep(data); + + //Miko left off + if (state.projectType === 'Next.js') { + dataDeepClone.forEach((element) => { + element.children = sanitize(element.children).filter( + (element) => !Array.isArray(element) + ); + }); + + function sanitize(children) { + return children.map((child) => { + if (child.name === 'Switch' || child.name === 'Route') { + return sanitize(child.children); + } else { + return child; + } + }); + } + } + + // remove separators and update components to current versions + dataDeepClone.forEach((component) => { + removeSeparators(component.children); + }); + // will be called initially and on every data change + useEffect(() => { + const svg = select(svgRef.current); + // use dimensions from useResizeObserver, + // but use getBoundingClientRect on initial render + // (dimensions are null for the first render) + const { width, height } = + dimensions || wrapperRef.current.getBoundingClientRect(); + // transform hierarchical data + const root = hierarchy(dataDeepClone[canvasId - 1]); // pass in clone here instead of data + const treeLayout = tree().size([height, width - 125]); + // Returns a new link generator with horizontal display. + // To visualize links in a tree diagram rooted on the left edge of the display + const linkGenerator = linkHorizontal() + .x((link) => link.y) + .y((link) => link.x); + // insert our data into the tree layout + treeLayout(root); + // node - each element in the tree + svg + .selectAll('.node') + .data(root.descendants()) + .join((enter) => enter.append('circle').attr('opacity', 0)) + .attr('class', 'node') + /* + The cx, cy attributes are associated with the circle and ellipse elements and designate the centre of each shape. The coordinates are set from the top, left hand corner of the web page. + cx: The position of the centre of the element in the x axis measured from the left side of the screen. + cy: The position of the centre of the element in the y axis measured from the top of the screen. + */ + // translate (x, y) + .attr('cx', (node) => node.y) + .attr('cy', (node) => node.x) + .attr('r', 4) // radius of circle + .attr('opacity', 1) + .style('fill', 'white') + .attr('transform', `translate(${xPosition}, 0)`); + // link - lines that connect the nodes + const enteringAndUpdatingLinks = svg + .selectAll('.link') + .data(root.links()) + .join('path') + .attr('class', 'link') + .attr('d', linkGenerator) + .attr('stroke', 'white') + .attr('fill', 'none') + .attr('opacity', 1) + .attr('transform', `translate(${xPosition}, 0)`); + if (data !== previouslyRenderedData) { + enteringAndUpdatingLinks + .attr('stroke-dashoffset', function () { + return this.length; + }) + .attr('stroke-dashoffset', 0); + } + // label - the names of each html element (node) + svg + .selectAll('.label') + .data(root.descendants()) + .join((enter) => enter.append('text').attr('opacity', 0)) + .attr('class', 'label') + .attr('x', (node) => node.y) + .attr('y', (node) => node.x - 12) + .attr('text-anchor', 'middle') + .attr('font-size', 18) + .style('fill', 'white') + .text((node) => node.data.name) + .attr('opacity', 1) + .attr('transform', `translate(${xPosition}, 0)`); + }, [state.components, dimensions, previouslyRenderedData, canvasId]); + const treeStyles = { + height: '100%', + width: `100%`, + margin: '10px 10px 10px 10px', + overflow: 'auto' + }; + const wrapperStyles = { + borderRadius: '10px', + width: '100%', + height: '97%', + display: 'flex', + justifyContent: 'center', + backgroundColor: '#1E2024' + }; + return ( +
    + +
    + ); +} +export default TreeChart; diff --git a/app/src/tree/useResizeObserver.ts b/app/src/tree/useResizeObserver.ts index d9adf16b1..74fb71fe0 100644 --- a/app/src/tree/useResizeObserver.ts +++ b/app/src/tree/useResizeObserver.ts @@ -1,23 +1,23 @@ -import { useEffect, useState } from 'react'; -import ResizeObserver from 'resize-observer-polyfill'; - -const useResizeObserver = (ref) => { - const [dimensions, setDimensions] = useState(null); - useEffect(() => { - // the element being observed (div with green border) - const observeTarget = ref.current; - const resizeObserver = new ResizeObserver((entries) => { - entries.forEach((entry) => { - // contentRect is an object containing the dimensions of the observed element - setDimensions(entry.contentRect); - }); - }); - resizeObserver.observe(observeTarget); - return () => { - resizeObserver.unobserve(observeTarget); - }; - }, [ref]); - return dimensions; -}; - -export default useResizeObserver; +import { useEffect, useState } from 'react'; +import ResizeObserver from 'resize-observer-polyfill'; + +const useResizeObserver = (ref) => { + const [dimensions, setDimensions] = useState(null); + useEffect(() => { + // the element being observed (div with green border) + const observeTarget = ref.current; + const resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + // contentRect is an object containing the dimensions of the observed element + setDimensions(entry.contentRect); + }); + }); + resizeObserver.observe(observeTarget); + return () => { + resizeObserver.unobserve(observeTarget); + }; + }, [ref]); + return dimensions; +}; + +export default useResizeObserver; diff --git a/app/src/tutorial/CSSEditor.tsx b/app/src/tutorial/CSSEditor.tsx index 4dd6c5565..f72161d16 100644 --- a/app/src/tutorial/CSSEditor.tsx +++ b/app/src/tutorial/CSSEditor.tsx @@ -1,39 +1,39 @@ -import React from 'react'; -import cssEditorTab from '../../../resources/csseditor_tutorial_images/CSSEditorTab.png'; -import cssEdit from '../../../resources/csseditor_tutorial_images/DirectCSSEdit.gif'; -import copyPaste from '../../../resources/csseditor_tutorial_images/CopyPasteCSS.gif'; - -const CSSEditor: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    CSS Editor

    -
    -

    The CSS editor tab is located in the bottom panel of the application and is where the CSS file's - code is located.

    - You are able to freely edit the CSS code directly and have the changes reflected immediately in the demo render panel. - You can also copy and paste your own CSS code into the editor to take full control of custom CSS classes.
    -

    -
    - -
    -
    -

    Edit CSS code

    -

    To edit the CSS code in the CSS editor, make the desired changes directly within the editor.
    -

    -
    - -

    -

    Or copy and paste your own CSS code directly into the editor. -


    -
    - -
    -
    -
    - ); -}; - -export default CSSEditor; +import React from 'react'; +import cssEditorTab from '../../../resources/csseditor_tutorial_images/CSSEditorTab.png'; +import cssEdit from '../../../resources/csseditor_tutorial_images/DirectCSSEdit.gif'; +import copyPaste from '../../../resources/csseditor_tutorial_images/CopyPasteCSS.gif'; + +const CSSEditor: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    CSS Editor

    +
    +

    The CSS editor tab is located in the bottom panel of the application and is where the CSS file's + code is located.

    + You are able to freely edit the CSS code directly and have the changes reflected immediately in the demo render panel. + You can also copy and paste your own CSS code into the editor to take full control of custom CSS classes.
    +

    +
    + +
    +
    +

    Edit CSS code

    +

    To edit the CSS code in the CSS editor, make the desired changes directly within the editor.
    +

    +
    + +

    +

    Or copy and paste your own CSS code directly into the editor. +


    +
    + +
    +
    +
    + ); +}; + +export default CSSEditor; diff --git a/app/src/tutorial/Canvas.tsx b/app/src/tutorial/Canvas.tsx index 3ef4d2160..7012453d5 100644 --- a/app/src/tutorial/Canvas.tsx +++ b/app/src/tutorial/Canvas.tsx @@ -1,44 +1,44 @@ -import React from 'react'; -import canvas1 from '../../../resources/canvas_tutorial_images/canvas1.png'; -import drag1 from '../../../resources/canvas_tutorial_images/drag1.gif'; -import undoRedo from '../../../resources/canvas_tutorial_images/undoRedo.gif'; - -const Canvas: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Canvas

    -
    -

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

    -
    - -
    -
    -

    Drag-n-Drop

    -

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

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

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

    -
    - -
    -
    -

    Undo and Redo

    -

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

    -
    - -
    -
    -
    - ); -}; - -export default Canvas; +import React from 'react'; +import canvas1 from '../../../resources/canvas_tutorial_images/canvas1.png'; +import drag1 from '../../../resources/canvas_tutorial_images/drag1.gif'; +import undoRedo from '../../../resources/canvas_tutorial_images/undoRedo.gif'; + +const Canvas: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Canvas

    +
    +

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

    +
    + +
    +
    +

    Drag-n-Drop

    +

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

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

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

    +
    + +
    +
    +

    Undo and Redo

    +

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

    +
    + +
    +
    +
    + ); +}; + +export default Canvas; diff --git a/app/src/tutorial/CodePreview.tsx b/app/src/tutorial/CodePreview.tsx index 35ddb59f7..49250a3f2 100644 --- a/app/src/tutorial/CodePreview.tsx +++ b/app/src/tutorial/CodePreview.tsx @@ -1,26 +1,26 @@ -import React from 'react'; -import codePreview from '../../../resources/code_preview_images/CodePreview.png'; - -const CodePreview: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Code Preview

    -
    -

    The code preview is located at the bottom center panel of the page on the fourth tab.

    - The code preview will generate the lines of code for functional components. As you drag and drop elements or components onto the setPage('Canvas')} >canvas - , the code preview will populate and generate the corresponding lines of code in real-time.

    - Adding a setPage('States')} >state will also generate the corresponding line of code for the state in the code preview.

    - Code preview will also change depending on whether Classic React / Gatsby.js / Next.js is chosen.

    - To learn more about the setPage('Canvas')} >canvas, click setPage('Canvas')} >"here"

    -
    - -
    -
    - ); -}; - -export default CodePreview; - +import React from 'react'; +import codePreview from '../../../resources/code_preview_images/CodePreview.png'; + +const CodePreview: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Code Preview

    +
    +

    The code preview is located at the bottom center panel of the page on the fourth tab.

    + The code preview will generate the lines of code for functional components. As you drag and drop elements or components onto the setPage('Canvas')} >canvas + , the code preview will populate and generate the corresponding lines of code in real-time.

    + Adding a setPage('States')} >state will also generate the corresponding line of code for the state in the code preview.

    + Code preview will also change depending on whether Classic React / Gatsby.js / Next.js is chosen.

    + To learn more about the setPage('Canvas')} >canvas, click setPage('Canvas')} >"here"

    +
    + +
    +
    + ); +}; + +export default CodePreview; + diff --git a/app/src/tutorial/ComponentTree.tsx b/app/src/tutorial/ComponentTree.tsx index 57fe1cbc6..afccfc238 100644 --- a/app/src/tutorial/ComponentTree.tsx +++ b/app/src/tutorial/ComponentTree.tsx @@ -1,45 +1,45 @@ -import React from 'react'; -import tree1 from '../../../resources/tree_tutorial_images/tree1.png'; -import tree2 from '../../../resources/tree_tutorial_images/tree2.png'; -import tree3 from '../../../resources/tree_tutorial_images/tree3.png'; -import tree4 from '../../../resources/tree_tutorial_images/tree4.png'; -import tree5 from '../../../resources/tree_tutorial_images/tree5.png'; - -const ComponentTree: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    React Component Tree

    -
    -

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

    -
    - -
    -
    -

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

    -
    - -
    -
    -

    setPage('Reusable Components')} >Reusable components are shown attached to the current page along with their subtrees of components and setPage('HTML Elements')} >HTML elements.

    -
    - -
    -
    -

    setPage('HTML Elements')} >HTML elements are shown by their tag name.

    -
    - -
    -
    -

    You can also view the tree for each setPage('Reusable Components')} >reusable component.

    -
    - -
    -
    -
    - ); -}; - -export default ComponentTree; +import React from 'react'; +import tree1 from '../../../resources/tree_tutorial_images/tree1.png'; +import tree2 from '../../../resources/tree_tutorial_images/tree2.png'; +import tree3 from '../../../resources/tree_tutorial_images/tree3.png'; +import tree4 from '../../../resources/tree_tutorial_images/tree4.png'; +import tree5 from '../../../resources/tree_tutorial_images/tree5.png'; + +const ComponentTree: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    React Component Tree

    +
    +

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

    +
    + +
    +
    +

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

    +
    + +
    +
    +

    setPage('Reusable Components')} >Reusable components are shown attached to the current page along with their subtrees of components and setPage('HTML Elements')} >HTML elements.

    +
    + +
    +
    +

    setPage('HTML Elements')} >HTML elements are shown by their tag name.

    +
    + +
    +
    +

    You can also view the tree for each setPage('Reusable Components')} >reusable component.

    +
    + +
    +
    +
    + ); +}; + +export default ComponentTree; diff --git a/app/src/tutorial/Customization.tsx b/app/src/tutorial/Customization.tsx index acb76f0ce..72b615ee6 100644 --- a/app/src/tutorial/Customization.tsx +++ b/app/src/tutorial/Customization.tsx @@ -1,172 +1,172 @@ -import React from 'react'; -import display from '../../../resources/customizing_elements_images/Display.png'; -import height from '../../../resources/customizing_elements_images/Height.png'; -import width from '../../../resources/customizing_elements_images/Width.png'; -import backgroundColor from '../../../resources/customizing_elements_images/BackgroundColor.png'; -import text from '../../../resources/customizing_elements_images/textState.png'; -import link from '../../../resources/customizing_elements_images/linkState.png'; -import cssClasses from '../../../resources/customizing_elements_images/CSS.png'; -import textGif from '../../../resources/customizing_elements_images/text.gif'; - -const Customization: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Customization

    -
    -

    - Customize your HTML elements on the canvas with the below features. Any changes
    - made in the setPage('Customization')}> - customization panel will be reflected immediately in the setPage('Code Preview')}> - code preview and demo render panel. See your changes in real time to decide what's best!

    - To customize an HTML element, drag it onto the canvas and select it on the canvas. Then use the desired customization feature. - Once done, press the save button to save your customization changes. -

    -
    -

    Display

    -
    - -
    -

    - After having moved a{' '} - setPage('Pages')}> - page - - ,{' '} - setPage('Reusable Components')} - > - component - - ,{' '} - setPage('Route Links')} - > - route link - - , or{' '} - setPage('HTML Elements')} - > - element - {' '} - into the canvas, select the one that needs customizing simply by - clicking on it. -

    -

    - If the display option 'flex' is chosen, a few more sub-options are - displayed under the display option. -

    -
    -

    Width

    -
    - -
    -

    - Change the width of each{' '} - setPage('Reusable Components')} - > - component - - ,{' '} - setPage('Route Links')} - > - route link - - , or{' '} - setPage('HTML Elements')} - > - element - - . -

    -
    -

    Height

    -
    - -
    -

    - Change the height of each{' '} - setPage('Reusable Components')} - > - component - - ,{' '} - setPage('Route Links')} - > - route link - - , or{' '} - setPage('HTML Elements')} - > - element - - . -

    -
    -

    Background Color

    -
    - -
    -

    - Select an element and type in the color you wish to change the background - color to and then click save. -

    -
    -

    Text

    -
    - -
    -

    - Add HTML text to a selected element on the canvas by typing in the desired text. -

    -

    - You can also add state to the text of your element by clicking the "Use State" button. As shown in the example below, when you click "Use State", a window will pop up, showing all state available in the current component. You can click on any of these state variables and it will applied to the HTML text. When you click "Save", you can see a live Demo Render of your customization changes. -

    -

    -

    - -
    -

    -
    -

    Link

    -
    - -
    -

    - Add a hyperlink to a selected element on the canvas by typing in the url. -

    -
    -

    CSS Classes

    -
    - -
    -

    - Change the CSS class of a selected element on the canvas by typing in the class name.
    - ReactType also comes with a default CSS file that is shown in the setPage('CSS Editor')} >CSS editor. - Add your own CSS classes to the setPage('CSS Editor')} >CSS editor or make changes to it to use - custom CSS classes. -

    -
    -
    - ); -}; - -export default Customization; +import React from 'react'; +import display from '../../../resources/customizing_elements_images/Display.png'; +import height from '../../../resources/customizing_elements_images/Height.png'; +import width from '../../../resources/customizing_elements_images/Width.png'; +import backgroundColor from '../../../resources/customizing_elements_images/BackgroundColor.png'; +import text from '../../../resources/customizing_elements_images/textState.png'; +import link from '../../../resources/customizing_elements_images/linkState.png'; +import cssClasses from '../../../resources/customizing_elements_images/CSS.png'; +import textGif from '../../../resources/customizing_elements_images/text.gif'; + +const Customization: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Customization

    +
    +

    + Customize your HTML elements on the canvas with the below features. Any changes
    + made in the setPage('Customization')}> + customization panel will be reflected immediately in the setPage('Code Preview')}> + code preview and demo render panel. See your changes in real time to decide what's best!

    + To customize an HTML element, drag it onto the canvas and select it on the canvas. Then use the desired customization feature. + Once done, press the save button to save your customization changes. +

    +
    +

    Display

    +
    + +
    +

    + After having moved a{' '} + setPage('Pages')}> + page + + ,{' '} + setPage('Reusable Components')} + > + component + + ,{' '} + setPage('Route Links')} + > + route link + + , or{' '} + setPage('HTML Elements')} + > + element + {' '} + into the canvas, select the one that needs customizing simply by + clicking on it. +

    +

    + If the display option 'flex' is chosen, a few more sub-options are + displayed under the display option. +

    +
    +

    Width

    +
    + +
    +

    + Change the width of each{' '} + setPage('Reusable Components')} + > + component + + ,{' '} + setPage('Route Links')} + > + route link + + , or{' '} + setPage('HTML Elements')} + > + element + + . +

    +
    +

    Height

    +
    + +
    +

    + Change the height of each{' '} + setPage('Reusable Components')} + > + component + + ,{' '} + setPage('Route Links')} + > + route link + + , or{' '} + setPage('HTML Elements')} + > + element + + . +

    +
    +

    Background Color

    +
    + +
    +

    + Select an element and type in the color you wish to change the background + color to and then click save. +

    +
    +

    Text

    +
    + +
    +

    + Add HTML text to a selected element on the canvas by typing in the desired text. +

    +

    + You can also add state to the text of your element by clicking the "Use State" button. As shown in the example below, when you click "Use State", a window will pop up, showing all state available in the current component. You can click on any of these state variables and it will applied to the HTML text. When you click "Save", you can see a live Demo Render of your customization changes. +

    +

    +

    + +
    +

    +
    +

    Link

    +
    + +
    +

    + Add a hyperlink to a selected element on the canvas by typing in the url. +

    +
    +

    CSS Classes

    +
    + +
    +

    + Change the CSS class of a selected element on the canvas by typing in the class name.
    + ReactType also comes with a default CSS file that is shown in the setPage('CSS Editor')} >CSS editor. + Add your own CSS classes to the setPage('CSS Editor')} >CSS editor or make changes to it to use + custom CSS classes. +

    +
    +
    + ); +}; + +export default Customization; diff --git a/app/src/tutorial/HtmlElements.tsx b/app/src/tutorial/HtmlElements.tsx index 962c740ae..15a89dd04 100644 --- a/app/src/tutorial/HtmlElements.tsx +++ b/app/src/tutorial/HtmlElements.tsx @@ -1,46 +1,46 @@ -import React from 'react'; -import defaultElements from '../../../resources/html_elements_tutorial_images/defaultElements.png'; -import createNew from '../../../resources/html_elements_tutorial_images/createNew.png'; -import newTag from '../../../resources/html_elements_tutorial_images/newTag.png'; -import codeSnippet from '../../../resources/html_elements_tutorial_images/codeSnippet.png'; - -const HtmlElements: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    HTML Elements

    -
    -

    ReacType has default elements to help get you started.
    - You can drag and drop elements into the setPage('Canvas')} >Canvas to create a React Component with HTML Elements.

    -
    - -
    -
    -

    Custom Elements

    -

    You can create new custom elements to better suit your needs.
    - Click here for a link to more HTML tags that you can add.
    - "Tag" should be the HTML tag you are creating and "Tag Name" should be something that makes it easy to remember what this tag is/does.
    - You can also create your own custom elements besides the standard HTML Elements. For example you can create an element <hello><hello> and it will work! You can add functionality to these elements once you export your project. Just be sure to import them into the files where you are using them! For more information on how to create custom tags check out these resources from HTML5Rocks and smashing magazine. -

    -
    - -
    -
    -

    Persisting Elements

    -

    Saving the project (available only to users) will allow you to save custom elements that you created. However, when opening a new project, only the tags saved for each specific project will show up again.
    - In order to save custom tags across multiple projects, we recommend creating custom tags first, then saving multiple projects with the custom tags. This will allow access to custom tags across multiple projects.

    -
    -

    Customization

    -

    Add attributes to elements in the generated code in the code preview. When making changes/editing the code, please make sure not to add anymore components/elements to the canvas. This should be the final step before exporting your project. Please see setPage('Code Preview')}>Code Preview for more details on when/how to make changes to your code in ReacType.

    -
    - -
    -
    -
    - ); -}; - -export default HtmlElements; - +import React from 'react'; +import defaultElements from '../../../resources/html_elements_tutorial_images/defaultElements.png'; +import createNew from '../../../resources/html_elements_tutorial_images/createNew.png'; +import newTag from '../../../resources/html_elements_tutorial_images/newTag.png'; +import codeSnippet from '../../../resources/html_elements_tutorial_images/codeSnippet.png'; + +const HtmlElements: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    HTML Elements

    +
    +

    ReacType has default elements to help get you started.
    + You can drag and drop elements into the setPage('Canvas')} >Canvas to create a React Component with HTML Elements.

    +
    + +
    +
    +

    Custom Elements

    +

    You can create new custom elements to better suit your needs.
    + Click here for a link to more HTML tags that you can add.
    + "Tag" should be the HTML tag you are creating and "Tag Name" should be something that makes it easy to remember what this tag is/does.
    + You can also create your own custom elements besides the standard HTML Elements. For example you can create an element <hello><hello> and it will work! You can add functionality to these elements once you export your project. Just be sure to import them into the files where you are using them! For more information on how to create custom tags check out these resources from HTML5Rocks and smashing magazine. +

    +
    + +
    +
    +

    Persisting Elements

    +

    Saving the project (available only to users) will allow you to save custom elements that you created. However, when opening a new project, only the tags saved for each specific project will show up again.
    + In order to save custom tags across multiple projects, we recommend creating custom tags first, then saving multiple projects with the custom tags. This will allow access to custom tags across multiple projects.

    +
    +

    Customization

    +

    Add attributes to elements in the generated code in the code preview. When making changes/editing the code, please make sure not to add anymore components/elements to the canvas. This should be the final step before exporting your project. Please see setPage('Code Preview')}>Code Preview for more details on when/how to make changes to your code in ReacType.

    +
    + +
    +
    +
    + ); +}; + +export default HtmlElements; + diff --git a/app/src/tutorial/KeyboardShortcuts.tsx b/app/src/tutorial/KeyboardShortcuts.tsx index 4acd6d4c8..85e84da6f 100644 --- a/app/src/tutorial/KeyboardShortcuts.tsx +++ b/app/src/tutorial/KeyboardShortcuts.tsx @@ -1,38 +1,38 @@ -import React from 'react'; - -const KeyboardShortcuts: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Keyboard Shortcuts

    -
    -

    Mac

    -
      -
    • Export Project: Command + e
    • -
    • Undo: Command + z
    • -
    • Redo: Command + Shift + z
    • -
    • Save Project As: Command + s
    • -
    • Save Project: Command + shift + s
    • -
    • Delete HTML Tag on Canvas: Backspace
    • -
    • Delete Project: Command + Backspace
    • -
    • Open Project: Command + o
    • -
    -

    Windows

    -
      -
    • Export Project: Control + e
    • -
    • Undo: Control + z
    • -
    • Redo: Control + Shift + z
    • -
    • Save Project As: Control + s
    • -
    • Save Project: Control + shift + s
    • -
    • Delete HTML Tag on Canvas: Backspace
    • -
    • Delete Project: Control + Backspace
    • -
    • Open Project: Control + o
    • -
    -
    -
    - ); -}; - -export default KeyboardShortcuts; +import React from 'react'; + +const KeyboardShortcuts: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Keyboard Shortcuts

    +
    +

    Mac

    +
      +
    • Export Project: Command + e
    • +
    • Undo: Command + z
    • +
    • Redo: Command + Shift + z
    • +
    • Save Project As: Command + s
    • +
    • Save Project: Command + shift + s
    • +
    • Delete HTML Tag on Canvas: Backspace
    • +
    • Delete Project: Command + Backspace
    • +
    • Open Project: Command + o
    • +
    +

    Windows

    +
      +
    • Export Project: Control + e
    • +
    • Undo: Control + z
    • +
    • Redo: Control + Shift + z
    • +
    • Save Project As: Control + s
    • +
    • Save Project: Control + shift + s
    • +
    • Delete HTML Tag on Canvas: Backspace
    • +
    • Delete Project: Control + Backspace
    • +
    • Open Project: Control + o
    • +
    +
    +
    + ); +}; + +export default KeyboardShortcuts; diff --git a/app/src/tutorial/Pages.tsx b/app/src/tutorial/Pages.tsx index df9a1747d..7d43c9525 100644 --- a/app/src/tutorial/Pages.tsx +++ b/app/src/tutorial/Pages.tsx @@ -1,53 +1,53 @@ -import React from 'react'; -import pages from '../../../resources/pages_images/Pages.png'; -import toggle from '../../../resources/pages_images/Toggle.png'; -import deletePage from '../../../resources/pages_images/DeletePage.png'; -import pagesPanel from '../../../resources/pages_images/PagesPanel.png'; -import pageSwapping from '../../../resources/pages_images/PagesSwapping.gif'; - -const Pages: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Pages

    -
    -

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

    -
    - -
    -
    - -
    -
    -

    - Switch between pages by selecting the Page and customize it by dragging - the elements you want into the{' '} - setPage('Canvas')}> - canvas - {' '} - of the page you're on. (Note the red dot next to the page name signals - which page you are currently on). -

    -
    - -
    - -
    -

    - Delete the selected page by simply clicking the delete button below the style attribute dropdowns. -

    -
    - -
    -
    -
    - ); -}; - -export default Pages; +import React from 'react'; +import pages from '../../../resources/pages_images/Pages.png'; +import toggle from '../../../resources/pages_images/Toggle.png'; +import deletePage from '../../../resources/pages_images/DeletePage.png'; +import pagesPanel from '../../../resources/pages_images/PagesPanel.png'; +import pageSwapping from '../../../resources/pages_images/PagesSwapping.gif'; + +const Pages: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Pages

    +
    +

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

    +
    + +
    +
    + +
    +
    +

    + Switch between pages by selecting the Page and customize it by dragging + the elements you want into the{' '} + setPage('Canvas')}> + canvas + {' '} + of the page you're on. (Note the red dot next to the page name signals + which page you are currently on). +

    +
    + +
    + +
    +

    + Delete the selected page by simply clicking the delete button below the style attribute dropdowns. +

    +
    + +
    +
    +
    + ); +}; + +export default Pages; diff --git a/app/src/tutorial/ReusableComponents.tsx b/app/src/tutorial/ReusableComponents.tsx index a5b086e90..68997ab4b 100644 --- a/app/src/tutorial/ReusableComponents.tsx +++ b/app/src/tutorial/ReusableComponents.tsx @@ -1,39 +1,39 @@ -import React from 'react'; -import reusableComponents1 from '../../../resources/reusable_components_tutorial_images/reusablecomponents1.png'; -import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusablecomponents2.png'; -import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusablecomponents3.png'; - -const ReusableComponents: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Reusable Components

    -
    -

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

    -

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

    -
    - -
    -
    -

    The Components you create will populate on the left panel under the section 'Reusable Components'.

    -
    - -
    -
    -

    After creating the desired Component, you can now drag-n-drop to the Canvas. - If you'd like to know about about the drag-n-drop functionality, please locate the setPage('Canvas')} >Canvas Tutorial for more information on how it works. -

    -
    - -
    -

    You can place a reusable component inside setPage('Pages')} >Pages and populate the component itself with other setPage('HTML_Elements')} >HTML Elements.

    -
    -
    - ); -}; - -export default ReusableComponents; - +import React from 'react'; +import reusableComponents1 from '../../../resources/reusable_components_tutorial_images/reusablecomponents1.png'; +import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusablecomponents2.png'; +import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusablecomponents3.png'; + +const ReusableComponents: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Reusable Components

    +
    +

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

    +

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

    +
    + +
    +
    +

    The Components you create will populate on the left panel under the section 'Reusable Components'.

    +
    + +
    +
    +

    After creating the desired Component, you can now drag-n-drop to the Canvas. + If you'd like to know about about the drag-n-drop functionality, please locate the setPage('Canvas')} >Canvas Tutorial for more information on how it works. +

    +
    + +
    +

    You can place a reusable component inside setPage('Pages')} >Pages and populate the component itself with other setPage('HTML_Elements')} >HTML Elements.

    +
    +
    + ); +}; + +export default ReusableComponents; + diff --git a/app/src/tutorial/RouteLinks.tsx b/app/src/tutorial/RouteLinks.tsx index 786d09564..cd895ef4d 100644 --- a/app/src/tutorial/RouteLinks.tsx +++ b/app/src/tutorial/RouteLinks.tsx @@ -1,44 +1,44 @@ -import React from 'react'; -import links2 from '../../../resources/route_links_tutorial_images/links2.PNG'; -import links3 from '../../../resources/route_links_tutorial_images/links3.PNG'; -import links4 from '../../../resources/route_links_tutorial_images/links4.PNG'; -import links6 from '../../../resources/route_links_tutorial_images/links6.PNG'; -import linksCanvas from '../../../resources/route_links_tutorial_images/links-canvas.PNG'; - -const RouteLinks: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Next.js Route Links

    -
    -

    Route Links are only available for Next.js and Gatsby.js projects.

    -

    Users are able to drag-and-drop 'Link' components onto the canvas which allow navigation to different setPage('Pages')} >pages.

    -
    - -
    -
    -

    Each page found in the ' setPage('Pages')} >Pages' section can be navigated to via a 'Link'. Clicking on the 'Route Link' dropdown will display all the created pages in your application.

    -
    - - -
    -
    -

    The code generator will automatically import Link from 'next/link' and create your Link component in the bottom panel.

    -
    - -
    -
    -

    Clicking on a Link component on the setPage('Canvas')} >canvas will navigate to the corresponding page.

    -
    - -
    -
    -

    For more information on 'Link' for Next.js, please visit the official documentation section at nextjs.org. For more information on 'Link' for Gatsby.js, please visit the official documentation section at www.gatsbyjs.com.

    -
    -
    - ); -}; - -export default RouteLinks; +import React from 'react'; +import links2 from '../../../resources/route_links_tutorial_images/links2.png'; +import links3 from '../../../resources/route_links_tutorial_images/links3.png'; +import links4 from '../../../resources/route_links_tutorial_images/links4.png'; +import links6 from '../../../resources/route_links_tutorial_images/links6.png'; +import linksCanvas from '../../../resources/route_links_tutorial_images/links-canvas.png'; + +const RouteLinks: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Next.js Route Links

    +
    +

    Route Links are only available for Next.js and Gatsby.js projects.

    +

    Users are able to drag-and-drop 'Link' components onto the canvas which allow navigation to different setPage('Pages')} >pages.

    +
    + +
    +
    +

    Each page found in the ' setPage('Pages')} >Pages' section can be navigated to via a 'Link'. Clicking on the 'Route Link' dropdown will display all the created pages in your application.

    +
    + + +
    +
    +

    The code generator will automatically import Link from 'next/link' and create your Link component in the bottom panel.

    +
    + +
    +
    +

    Clicking on a Link component on the setPage('Canvas')} >canvas will navigate to the corresponding page.

    +
    + +
    +
    +

    For more information on 'Link' for Next.js, please visit the official documentation section at nextjs.org. For more information on 'Link' for Gatsby.js, please visit the official documentation section at www.gatsbyjs.com.

    +
    +
    + ); +}; + +export default RouteLinks; diff --git a/app/src/tutorial/States.tsx b/app/src/tutorial/States.tsx index fd25d2b88..5762fe643 100644 --- a/app/src/tutorial/States.tsx +++ b/app/src/tutorial/States.tsx @@ -1,87 +1,87 @@ -import React from 'react'; -import createState from '../../../resources/state_tutorial_images/CreateState.png'; -import codePreview from '../../../resources/state_tutorial_images/CodePreview.png'; -import fullStateManageTab from '../../../resources/state_tutorial_images/fullStateManageTab.png'; -import table1 from '../../../resources/state_tutorial_images/table1.gif'; -import table3 from '../../../resources/state_tutorial_images/table3.gif'; -import deleteState2 from '../../../resources/state_tutorial_images/delete.gif'; -import display from '../../../resources/state_tutorial_images/display.gif'; -import codePreview2 from '../../../resources/state_tutorial_images/CodePreview2.png'; - -const States: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    State Management

    -
    -

    Step 1: Click on the State Management Tab

    -

    - Within this tab, there are two sections: "Create/Edit" and "Display". -

    -

    - -
    -

    -

    Step 2: Create a New State Variable

    -

    - In the "Create/Edit" section of the State Manager, you can declare a new state variable. First make sure that you have selected the correct component on the left. This new state variable will be created inside the component highlighted with a red dot (as shown below). -

    You can create your state variable with a key, an intial value, and a type (ex: string, number, boolean, etc).

    -

    - -
    -

    - You will now see your new state variable in the "Current Component State" table. Also, you can see the React Hook that has been created for this state variable. -

    -
    - -
    -

    -

    Step 3: Pass in Props from Parent Component

    -

    - In the "Create/Edit" section of the State Manager, you can also view props available from the parent component. You can choose to import any of the state variables, with or without their associated React Hooks. -

    Just click the plus sign next to the state you want to import into the current component. This will automatically update the "Passed In Props from Parent" table on the far right.

    -

    - -
    -

    -

    Step 4: Delete Any Unnecessary State Variables

    -

    - If there are any state variables you want to delete, you can do so in the "Create/Edit" tab! -

    You can delete a state variable that was created in the current component by clicking the X next to it in the "Current Component State" table.

    - You can also delete state from the "Passed In Props From Parent" table. This will delete the passed in props from the current component, but won't delete the original state variable inside the parent component. -

    - For example, in the gif below, we can see the "num" state variable being deleted from the component "Row". Its associated React Hook is also automatically deleted. -

    - Then, the "setDarkMode" React Hook is deleted from "Passed In Props From Parent: App". However, if we click over to the "App" parent component, the "setDarkMode" React Hook is still available. -

    -

    - -
    -

    -

    -

    Step 5: Visualize the State Flow of Your Prototype Application

    -

    - Once you've added all your state variables to your components, you can click on the "Display" section of the State Manager. Here, you can see a tree diagram that shows the relationship between the components you have created. Click on each component to see its state variables -- including passed in props!

    - On the left side of the screen, click on other root components (if using Classic React) or pages (if using Next.js or GatsbyJS) to see their tree diagrams too. -

    -

    - -
    -

    - Created state variables and React Hooks will also be visible in the setPage('Code Preview')} >code preview.

    - You can see below that the "num" and its associated React Hook have been passed in to the "Row" component from its parent component. -

    - On line 10, the state variable "darkMode" has been passed into the "Row" component from its parent, but without its React Hook, since we chose not to pass the hook down from the parent component. -

    -
    - -
    -

    -
    -
    - ); -}; - -export default States; +import React from 'react'; +import createState from '../../../resources/state_tutorial_images/CreateState.png'; +import codePreview from '../../../resources/state_tutorial_images/CodePreview.png'; +import fullStateManageTab from '../../../resources/state_tutorial_images/fullStateManageTab.png'; +import table1 from '../../../resources/state_tutorial_images/table1.gif'; +import table3 from '../../../resources/state_tutorial_images/table3.gif'; +import deleteState2 from '../../../resources/state_tutorial_images/delete.gif'; +import display from '../../../resources/state_tutorial_images/display.gif'; +import codePreview2 from '../../../resources/state_tutorial_images/CodePreview2.png'; + +const States: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    State Management

    +
    +

    Step 1: Click on the State Management Tab

    +

    + Within this tab, there are two sections: "Create/Edit" and "Display". +

    +

    + +
    +

    +

    Step 2: Create a New State Variable

    +

    + In the "Create/Edit" section of the State Manager, you can declare a new state variable. First make sure that you have selected the correct component on the left. This new state variable will be created inside the component highlighted with a red dot (as shown below). +

    You can create your state variable with a key, an intial value, and a type (ex: string, number, boolean, etc).

    +

    + +
    +

    + You will now see your new state variable in the "Current Component State" table. Also, you can see the React Hook that has been created for this state variable. +

    +
    + +
    +

    +

    Step 3: Pass in Props from Parent Component

    +

    + In the "Create/Edit" section of the State Manager, you can also view props available from the parent component. You can choose to import any of the state variables, with or without their associated React Hooks. +

    Just click the plus sign next to the state you want to import into the current component. This will automatically update the "Passed In Props from Parent" table on the far right.

    +

    + +
    +

    +

    Step 4: Delete Any Unnecessary State Variables

    +

    + If there are any state variables you want to delete, you can do so in the "Create/Edit" tab! +

    You can delete a state variable that was created in the current component by clicking the X next to it in the "Current Component State" table.

    + You can also delete state from the "Passed In Props From Parent" table. This will delete the passed in props from the current component, but won't delete the original state variable inside the parent component. +

    + For example, in the gif below, we can see the "num" state variable being deleted from the component "Row". Its associated React Hook is also automatically deleted. +

    + Then, the "setDarkMode" React Hook is deleted from "Passed In Props From Parent: App". However, if we click over to the "App" parent component, the "setDarkMode" React Hook is still available. +

    +

    + +
    +

    +

    +

    Step 5: Visualize the State Flow of Your Prototype Application

    +

    + Once you've added all your state variables to your components, you can click on the "Display" section of the State Manager. Here, you can see a tree diagram that shows the relationship between the components you have created. Click on each component to see its state variables -- including passed in props!

    + On the left side of the screen, click on other root components (if using Classic React) or pages (if using Next.js or GatsbyJS) to see their tree diagrams too. +

    +

    + +
    +

    + Created state variables and React Hooks will also be visible in the setPage('Code Preview')} >code preview.

    + You can see below that the "num" and its associated React Hook have been passed in to the "Row" component from its parent component. +

    + On line 10, the state variable "darkMode" has been passed into the "Row" component from its parent, but without its React Hook, since we chose not to pass the hook down from the parent component. +

    +
    + +
    +

    +
    +
    + ); +}; + +export default States; diff --git a/app/src/tutorial/Styling.tsx b/app/src/tutorial/Styling.tsx index 55f59a520..4faeb3dd0 100644 --- a/app/src/tutorial/Styling.tsx +++ b/app/src/tutorial/Styling.tsx @@ -1,44 +1,44 @@ -import React from 'react'; -import lighting from '../../../resources/customizing_elements_images/Lighting.png'; -import resize from '../../../resources/customizing_elements_images/Resize.gif'; -import codeChange from '../../../resources/customizing_elements_images/CodeChange.png'; - -const Styling: React.FC<{ - classes: any; - setPage: Function; -}> = ({ classes, setPage }) => { - return ( -
    -

    Styling Features

    -
    -

    Dark Mode

    -
    - -
    -

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

    -
    -

    Resize Code Preview and Component Tree

    -
    - -
    -

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

    -
    -

    Customize Code Preview

    -
    - -
    -

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

    -
    -
    - ); -}; -export default Styling; +import React from 'react'; +import lighting from '../../../resources/customizing_elements_images/Lighting.png'; +import resize from '../../../resources/customizing_elements_images/Resize.gif'; +import codeChange from '../../../resources/customizing_elements_images/CodeChange.png'; + +const Styling: React.FC<{ + classes: any; + setPage: Function; +}> = ({ classes, setPage }) => { + return ( +
    +

    Styling Features

    +
    +

    Dark Mode

    +
    + +
    +

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

    +
    +

    Resize Code Preview and Component Tree

    +
    + +
    +

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

    +
    +

    Customize Code Preview

    +
    + +
    +

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

    +
    +
    + ); +}; +export default Styling; diff --git a/app/src/tutorial/Tutorial.tsx b/app/src/tutorial/Tutorial.tsx index 391219289..f4797fbe1 100644 --- a/app/src/tutorial/Tutorial.tsx +++ b/app/src/tutorial/Tutorial.tsx @@ -1,162 +1,164 @@ -import React from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import CardActions from '@mui/material/CardActions'; -import Typography from '@mui/material/Typography'; -import Container from '@mui/material/Container'; -import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import Styling from '../constants/Styling'; -import MenuBookIcon from '@mui/icons-material/MenuBook'; -import LinkIcon from '@mui/icons-material/Link'; -import CodeIcon from '@mui/icons-material/Code'; -import AddToPhotosIcon from '@mui/icons-material/AddToPhotos'; -import TvIcon from '@mui/icons-material/Tv'; -import AccountTreeIcon from '@mui/icons-material/AccountTree'; -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; -import StyleIcon from '@mui/icons-material/Style'; -import ColorLensIcon from '@mui/icons-material/ColorLens'; -import SwapVertIcon from '@mui/icons-material/SwapVert'; -import KeyboardIcon from '@mui/icons-material/Keyboard'; -import BrushIcon from '@mui/icons-material/Brush'; -import CloseIcon from '@mui/icons-material/Close'; -import Button from '@mui/material/Button'; - -const useStyles = makeStyles({ - root: { - display: 'flex', - flexDirection: 'column', - width: 350, - height: 300, - margin: 20, - border: `1px solid gray`, - backgroundColor: Styling.tutorialGray, - color: 'white', - borderRadius: '5%', - boxShadow: '10px 10px 10px gray' - }, - bullet: { - display: 'inline-block', - margin: '0 2px', - transform: 'scale(0.8)' - }, - title: { - color: Styling.darkBlue, - fontSize: 28, - fontWeight: 500 - }, - pos: { - marginBottom: 12, - margin: 20 - }, - icons: { - color: Styling.darkBlue, - fontSize: 125 - }, - pageTitle: { - fontSize: 100, - color: Styling.darkBlue, - boxShadow: '10px 10px 10px #00001a', - border: `1px solid ${Styling.darkBlue}`, - padding: '20px', - backgroundColor: 'white', - borderRadius: '10px' - }, - container: { - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - alignItems: 'center', - backgroundColor: 'lightgray' - }, - wrapper: { - display: 'flex', - justifyContent: 'center', - flexWrap: 'wrap', - width: '75%' - }, - cardWrapper: { - display: 'flex', - flexDirection: 'column', - flexBasis: '33.333333%' - }, - cardActions: { - alignSelf: 'center', - justifySelf: 'center' - } -}); - -const Tutorial: React.FC = () => { - const classes = useStyles(); - const topics = [ - 'Pages', - 'Route Links', - 'Code Preview', - 'Reusable Components', - 'Canvas', - 'Component Tree', - 'HTML Elements', - 'Styling', - 'Customization', - 'States', - 'CSS Editor', - 'Keyboard Shortcuts' - ]; - const icons = [ - , - , - , - , - , - , - , - , - , - , - , - - ]; - const body = document.querySelector('body'); - body.style.overflowY = 'auto'; - body.style.backgroundColor = Styling.tutorialGray; - const cards = topics.map((topic, i) => { - return ( -
    - - - - {topic} - - - {icons[i]} - - - -
    - ); - }); - return ( - <> -
    - {/* */} - - {/* */} -
    - -

    ReacType Tutorial

    -
    {cards}
    -
    - - ); -}; -export default withRouter(Tutorial); +import React, { useEffect } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; +import Styling from '../constants/Styling'; +import MenuBookIcon from '@mui/icons-material/MenuBook'; +import LinkIcon from '@mui/icons-material/Link'; +import CodeIcon from '@mui/icons-material/Code'; +import AddToPhotosIcon from '@mui/icons-material/AddToPhotos'; +import TvIcon from '@mui/icons-material/Tv'; +import AccountTreeIcon from '@mui/icons-material/AccountTree'; +import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; +import StyleIcon from '@mui/icons-material/Style'; +import ColorLensIcon from '@mui/icons-material/ColorLens'; +import SwapVertIcon from '@mui/icons-material/SwapVert'; +import KeyboardIcon from '@mui/icons-material/Keyboard'; +import BrushIcon from '@mui/icons-material/Brush'; +import CloseIcon from '@mui/icons-material/Close'; +import Button from '@mui/material/Button'; + +const useStyles = makeStyles({ + root: { + display: 'flex', + flexDirection: 'column', + width: 350, + height: 300, + margin: 20, + border: `1px solid gray`, + backgroundColor: Styling.tutorialGray, + color: 'white', + borderRadius: '5%', + boxShadow: '10px 10px 10px gray' + }, + bullet: { + display: 'inline-block', + margin: '0 2px', + transform: 'scale(0.8)' + }, + title: { + color: Styling.darkBlue, + fontSize: 28, + fontWeight: 500 + }, + pos: { + marginBottom: 12, + margin: 20 + }, + icons: { + color: Styling.darkBlue, + fontSize: 125 + }, + pageTitle: { + fontSize: 100, + color: Styling.darkBlue, + boxShadow: '10px 10px 10px #00001a', + border: `1px solid ${Styling.darkBlue}`, + padding: '20px', + backgroundColor: 'white', + borderRadius: '10px' + }, + container: { + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + alignItems: 'center', + backgroundColor: 'lightgray' + }, + wrapper: { + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap', + width: '75%' + }, + cardWrapper: { + display: 'flex', + flexDirection: 'column', + flexBasis: '33.333333%' + }, + cardActions: { + alignSelf: 'center', + justifySelf: 'center' + } +}); + +const Tutorial: React.FC = () => { + const classes = useStyles(); + const topics = [ + 'Pages', + 'Route Links', + 'Code Preview', + 'Reusable Components', + 'Canvas', + 'Component Tree', + 'HTML Elements', + 'Styling', + 'Customization', + 'States', + 'CSS Editor', + 'Keyboard Shortcuts' + ]; + const icons = [ + , + , + , + , + , + , + , + , + , + , + , + + ]; + + const body = document.querySelector('body'); + body.style.overflowY = 'auto'; + body.style.backgroundColor = Styling.tutorialGray; + const cards = topics.map((topic, i) => { + return ( +
    + + + + {topic} + + + {icons[i]} + + + +
    + ); + }); + return ( + <> +
    + + + +
    + +

    ReacType Tutorial

    +
    {cards}
    +
    + + ); +}; + +export default Tutorial; diff --git a/app/src/tutorial/TutorialPage.tsx b/app/src/tutorial/TutorialPage.tsx index be9365016..fd5bdc971 100644 --- a/app/src/tutorial/TutorialPage.tsx +++ b/app/src/tutorial/TutorialPage.tsx @@ -1,224 +1,224 @@ -import React, { useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; -import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import Pages from './Pages'; -import Customization from './Customization'; -import Canvas from './Canvas'; -import Styling from './Styling'; -import RouteLinks from './RouteLinks'; -import ReusableComponents from './ReusableComponents'; -import ComponentTree from './ComponentTree'; -import HTMLElements from './HtmlElements'; -import CodePreview from './CodePreview'; -import State from './States'; -import DemoRender from './KeyboardShortcuts'; -import CSSEditor from './CSSEditor'; - -const useStyles = makeStyles({ - title: { - color: '#14151f', - fontSize: 54 - }, - text: { - color: '#2e2f3e', - fontSize: 24, - maxWidth: '1065px' - }, - wrapper: { - margin: '30px 30px 30px 30px', - width: 'auto' - }, - img: { - borderRadius: '3px', - width: '100%' - }, - smallImg: { - borderRadius: '3px', - height: '300px', - marginRight: '20px' - }, - medImg: { - borderRadius: '3px', - height: '500px', - marginRight: '20px' - }, - imgWrapper: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - width: 'auto' - }, - notLink: { - color: 'blue', - textDecoration: 'underline', - cursor: 'pointer' - }, - sidebar: { - borderLeft: '2px solid lightgrey', - width: '30%', - minHeight: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'flex-start', - alignItems: 'center', - fontSize: '1.5em' - } -}); - -const tutorialPageStyle = { - tutorial_page: { - display: 'flex', - justifyContent: 'center', - backgroundColor: '#f2f0f0', - width: '100%', - height: '100%' - }, - main_tutorial: { - width: '70%', - minHeight: '100%' - }, - list: { - listStyle: 'none' - }, - listItem: { - margin: '1em' - }, - tutHomeButton: { - padding: '4px 16px', - fontSize: '1.2em' - } -}; - -const TutorialPage: React.FC = (props) => { - const classes = useStyles(); - const [page, setPage] = useState(props.match.params.learn); - - return ( -
    -
    - {page === 'Pages' && } - {page === 'Route Links' && ( - - )} - {page === 'Code Preview' && ( - - )} - {page === 'Reusable Components' && ( - - )} - {page === 'Canvas' && } - {page === 'Component Tree' && ( - - )} - {page === 'HTML Elements' && ( - - )} - {page === 'Styling' && } - {page === 'Customization' && ( - - )} - {page === 'States' && } - {page === 'CSS Editor' && ( - - )} - {page === 'Keyboard Shortcuts' && ( - - )} -
    -
    -
    - - - -
      -
    • setPage('Pages')} - > - Pages -
    • -
    • setPage('Route Links')} - > - Route Links -
    • -
    • setPage('Code Preview')} - > - Code Preview -
    • -
    • setPage('Reusable Components')} - > - Reusable Components -
    • -
    • setPage('Canvas')} - > - Canvas -
    • -
    • setPage('Component Tree')} - > - Component Tree -
    • -
    • setPage('HTML Elements')} - > - HTML Elements -
    • -
    • setPage('Styling')} - > - Styling -
    • -
    • setPage('Customization')} - > - Customization -
    • -
    • setPage('States')} - > - States -
    • -
    • setPage('CSS Editor')} - > - CSS Editor -
    • -
    • setPage('Keyboard Shortcuts')} - > - Keyboard Shortcuts -
    • -
    -
    -
    - ); -}; - -export default withRouter(TutorialPage); +import React, { useState , useEffect } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; +import Pages from './Pages'; +import Customization from './Customization'; +import Canvas from './Canvas'; +import Styling from './Styling'; +import RouteLinks from './RouteLinks'; +import ReusableComponents from './ReusableComponents'; +import ComponentTree from './ComponentTree'; +import HTMLElements from './HtmlElements'; +import CodePreview from './CodePreview'; +import State from './States'; +import DemoRender from './KeyboardShortcuts'; +import CSSEditor from './CSSEditor'; + +const useStyles = makeStyles({ + title: { + color: '#14151f', + fontSize: 54 + }, + text: { + color: '#2e2f3e', + fontSize: 24, + maxWidth: '1065px' + }, + wrapper: { + margin: '30px 30px 30px 30px', + width: 'auto' + }, + img: { + borderRadius: '3px', + width: '100%' + }, + smallImg: { + borderRadius: '3px', + height: '300px', + marginRight: '20px' + }, + medImg: { + borderRadius: '3px', + height: '500px', + marginRight: '20px' + }, + imgWrapper: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + width: 'auto' + }, + notLink: { + color: 'blue', + textDecoration: 'underline', + cursor: 'pointer' + }, + sidebar: { + borderLeft: '2px solid lightgrey', + width: '30%', + minHeight: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'center', + fontSize: '1.5em' + } +}); + +const tutorialPageStyle = { + tutorial_page: { + display: 'flex', + justifyContent: 'center', + backgroundColor: '#f2f0f0', + width: '100%', + height: '100%' + }, + main_tutorial: { + width: '70%', + minHeight: '100%' + }, + list: { + listStyle: 'none' + }, + listItem: { + margin: '1em' + }, + tutHomeButton: { + padding: '4px 16px', + fontSize: '1.2em' + } +}; + +const TutorialPage: React.FC> = (props) => { + const classes = useStyles(); + const [page, setPage] = useState(props.match.params.learn); + + return ( +
    +
    + {page === 'Pages' && } + {page === 'Route Links' && ( + + )} + {page === 'Code Preview' && ( + + )} + {page === 'Reusable Components' && ( + + )} + {page === 'Canvas' && } + {page === 'Component Tree' && ( + + )} + {page === 'HTML Elements' && ( + + )} + {page === 'Styling' && } + {page === 'Customization' && ( + + )} + {page === 'States' && } + {page === 'CSS Editor' && ( + + )} + {page === 'Keyboard Shortcuts' && ( + + )} +
    +
    +
    + + + +
      +
    • setPage('Pages')} + > + Pages +
    • +
    • setPage('Route Links')} + > + Route Links +
    • +
    • setPage('Code Preview')} + > + Code Preview +
    • +
    • setPage('Reusable Components')} + > + Reusable Components +
    • +
    • setPage('Canvas')} + > + Canvas +
    • +
    • setPage('Component Tree')} + > + Component Tree +
    • +
    • setPage('HTML Elements')} + > + HTML Elements +
    • +
    • setPage('Styling')} + > + Styling +
    • +
    • setPage('Customization')} + > + Customization +
    • +
    • setPage('States')} + > + States +
    • +
    • setPage('CSS Editor')} + > + CSS Editor +
    • +
    • setPage('Keyboard Shortcuts')} + > + Keyboard Shortcuts +
    • +
    +
    +
    + ); +}; + +export default TutorialPage; diff --git a/app/src/utils/createApplication.util.ts b/app/src/utils/createApplication.util.ts index 6aa7a03b0..1240b0d33 100644 --- a/app/src/utils/createApplication.util.ts +++ b/app/src/utils/createApplication.util.ts @@ -1,394 +1,394 @@ -import { Component } from '../interfaces/Interfaces'; -// Create all files necessary to run a classic react application -import createFiles from './createFiles.util'; -import createTestSuiteClassic from './createTestSuiteClassic.util'; -import store from '../redux/store'; - -const camelToKebab = (camel: string) => { - return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); -}; -const compToCSS = (component: Component) => { - const name = component.name; - const styleObj = component.style; - let cssClass = ` - .${name} { - `; - Object.keys(styleObj).forEach((property) => { - let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; - `; - cssClass += cssStyle; - }); - cssClass += `} - `; - return cssClass; -}; -function createIndexHtml(path, appName) { - let dir = path; - let dirSrc; - let dirServer; - let dirComponent; - let dirContext; - if (!dir.match(/`${appName}`|\*$/)) { - dir = `${dir}/${appName}`; - if (!window.api.existsSync(dir)) { - window.api.mkdirSync(dir); - dirSrc = `${dir}/src`; - window.api.mkdirSync(dirSrc); - dirServer = `${dir}/server`; - window.api.mkdirSync(dirServer); - dirComponent = `${dirSrc}/components`; - window.api.mkdirSync(dirComponent); - //create directory for contexts - dirContext = `${dirSrc}/contexts`; - window.api.mkdirSync(dirContext); - } - } - const filePath: string = `${dir}/index.html`; - const data: string = ` - - - - ReacType App - - -
    - - - - `; - window.api.writeFileSync(filePath, data, (err) => { - if (err) { - console.log('index.html error:', err.message); - } else { - console.log('index.html written successfully'); - } - }); -} -export const createIndexTsx = (path, appName) => { - const filePath = `${path}/${appName}/src/index.tsx`; - const data = `import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './components/App'; -import './default.css'; -ReactDOM.render(, document.getElementById('root')); - `; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('index.tsx error:', err.message); - } else { - console.log('index.tsx written successfully'); - } - }); -}; -export const createDefaultCSS = (path, appName, components) => { - const filePath = `${path}/${appName}/src/default.css`; - let data = `#root div { - box-sizing: border-box; - width: 100%; - border: 1px solid rgba(0,0,0,0.25); - padding: 12px; - text-align: center; - font-family: Helvetica, Arial; -} -`; - components.forEach((comp) => { - data += compToCSS(comp); - }); - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('default.css error:', err.message); - } else { - console.log('default.css written successfully'); - } - }); -}; -export const createPackage = (path, appName, test) => { - const filePath = `${path}/${appName}/package.json`; - let tsjest = `, - "@types/enzyme": "^3.10.9", - "@types/jest": "^27.0.1", - "babel-jest": "^27.2.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", - "jest": "^27.2.0", - "@types/enzyme-adapter-react-16": "^1.0.6", - "ts-jest": "^27.0.5", - "enzyme-to-json": "^3.6.2"`; - const data = `{ - "name": "reactype", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "node server/server.js", - "build": "cross-env NODE_ENV=production webpack", - "dev": "cross-env NODE_ENV=development webpack-dev-server"${ - test - ? `, - "test": "jest"` - : '' - } - }, - "nodemonConfig": { - "ignore": [ - "build", - "src" - ] - }, - "keywords": [], - "author": "", - "license": "MIT", - "dependencies": { - "@types/react": "^16.8.13", - "@types/react-dom": "^16.8.4", - "express": "^4.17.1", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "webpack": "^4.29.6" - }, - "devDependencies": { - "@babel/core": "^7.4.3", - "@babel/preset-env": "^7.4.3", - "@babel/preset-react": "^7.0.0", - "@babel/preset-typescript": "^7.3.3", - "babel-loader": "^8.0.5", - "cross-env": "^5.2.0", - "css-loader": "^2.1.1", - "file-loader": "^3.0.1", - "isomorphic-fetch": "^2.2.1", - "node-sass": "^7.0.1", - "nodemon": "^1.18.9", - "postcss-loader": "^3.0.0", - "sass-loader": "^7.1.0", - "source-map-loader": "^0.2.4", - "style-loader": "^0.23.1", - "tslint": "^5.15.0", - "tslint-config-prettier": "^1.18.0", - "tslint-react": "^4.0.0", - "typescript": "^3.8.3", - "webpack": "^4.29.6", - "webpack-cli": "^3.3.0", - "webpack-dev-server": "^3.2.1"${test ? tsjest : ''} - } -} - `; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('package.json error:', err.message); - } else { - console.log('package.json written successfully'); - } - }); -}; -export const createWebpack = (path, appName) => { - const filePath = `${path}/${appName}/webpack.config.js`; - const data = `var status = import.meta.env.NODE_ENV; //taken from script so we don't have to flip mode when using development/production -var path = require('path'); -module.exports = { - entry: './src/index.tsx', - output: { - path: path.resolve(__dirname, 'build'), - filename: 'bundle.js', - }, - // Enable sourcemaps for debugging webpack's output. - devtool: 'source-map', - resolve: { - // Add '.ts' and '.tsx' as resolvable extensions. - extensions: ['.ts', '.tsx', '.js', '.json'], - }, - mode: status, - devServer: { - publicPath: '/build/', - }, - module: { - rules: [ - // All files with a '.ts' or '.tsx' extension will be handled by babel-loader - { test: /.tsx?$/, exclude: /node-modules/, loader: 'babel-loader' }, - - // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. - { enforce: 'pre', test: /.js$/, exclude: /node-modules/, loader: 'source-map-loader' }, - { - test: /.scss$/, - use: [ - 'style-loader', // creates style nodes from JS strings - 'css-loader', // translates CSS into CommonJS - 'sass-loader', // compiles Sass to CSS, using Node Sass by default - ], - }, - { - test: /\.css$/, - use: [ - 'style-loader', - 'css-loader' - ] - } - ], - }, -}; - `; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('webpack error:', err.message); - } else { - console.log('webpack written successfully'); - } - }); -}; -export const createBabel = (path, appName) => { - const filePath = `${path}/${appName}/.babelrc`; - const data = `{ - "presets": ["@babel/env", "@babel/react", "@babel/typescript"] -} -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('babelrc error:', err.message); - } else { - console.log('babelrc written successfully'); - } - }); -}; -export const createTsConfig = (path, appName) => { - const filePath = `${path}/${appName}/tsconfig.json`; - const data = `{ - "compilerOptions": { - "outDir": "./dist/", - "sourceMap": true, - "noImplicitAny": true, - "module": "commonjs", - "target": "es6", - "jsx": "react", - "lib": ["dom", "es6"], - "moduleResolution": "node", - "esModuleInterop": true - }, - "include": ["./src/**/*"] -} -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('TSConfig error:', err.message); - } else { - console.log('TSConfig written successfully'); - } - }); -}; -export const createTsLint = (path, appName) => { - const filePath = `${path}/${appName}/tslint.json`; - const data = `{ - "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], - "tslint.autoFixOnSave": true, - "linterOptions": { - "exclude": ["config/**/*.js", "node_modules/**/*.ts"] - }, - "rules": { - "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], - "jsx-boolean-value": false, - "jsx-no-lambda": false, - "jsx-no-multiline-js": false, - "object-literal-sort-keys": false, - "member-ordering": false, - "no-console": false, - "ordered-imports": false, - "comment-format": false - } -} -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('TSLint error:', err.message); - } else { - console.log('TSLint written successfully'); - } - }); -}; -export const createServer = (path, appName) => { - const filePath = `${path}/${appName}/server/server.js`; - const data = `const express = require('express'); -const path = require('path'); -const app = express(); -app.get('/testDev', (req, res) => { - res.send({ dev: 'testDev endpoint hit' }); -}); -// statically serve everything in the build folder on the route '/build' -app.use('/build', express.static(path.join(__dirname, '../build'))); -// serve index.html on the route '/' -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, '../index.html')); -}); -app.listen(8080, () => { - console.log('listening on port 8080'); -}); //listens on port 8080 -> http://localhost:8080/ -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('server file error:', err.message); - } else { - console.log('server file written successfully'); - } - }); -}; - -//Generate files for all existing contexts in the current application -export const createContext = (path, appName) => { - // const store = useStore(); - const { allContext } = store.getState().contextSlice; - - for (const context of allContext) { - const cached = {}; - for (const ele of context.values) { - cached[ele.key] = ele.value; - } - const filePath = `${path}/${appName}/src/contexts/${context.name}.js`; - const data = `import {createContext, useState} from 'react' -export const ${context.name} = createContext(); - -const ${context.name}Provider = (props) => { - const [${context.name}State] = useState( - ${JSON.stringify(cached)} - ) -} - -return ( - <${context.name}.Provider value={${context.name}State}> - {props.children} - -); -export default ${context.name}Provider -`; - window.api.writeFileSync(filePath, data, (err) => { - if (err) { - console.log('server file error:', err.message); - } else { - console.log('server file written successfully'); - } - }); - } -}; -async function createApplicationUtil({ - path, - appName, - components, - testchecked -}: { - path: string; - appName: string; - components: Component[]; - testchecked: boolean; -}) { - await createIndexHtml(path, appName); - await createIndexTsx(path, appName); - await createDefaultCSS(path, appName, components); - await createPackage(path, appName, testchecked); - await createWebpack(path, appName); - await createBabel(path, appName); - await createTsConfig(path, appName); - await createTsLint(path, appName); - await createServer(path, appName); - await createContext(path, appName); - if (testchecked) { - await createTestSuiteClassic({ path, appName, components, testchecked }); - } - await createFiles(components, path, appName, true); -} -export default createApplicationUtil; +import { Component } from '../interfaces/Interfaces'; +// Create all files necessary to run a classic react application +import createFiles from './createFiles.util'; +import createTestSuiteClassic from './createTestSuiteClassic.util'; +import store from '../redux/store'; + +const camelToKebab = (camel: string) => { + return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +}; +const compToCSS = (component: Component) => { + const name = component.name; + const styleObj = component.style; + let cssClass = ` + .${name} { + `; + Object.keys(styleObj).forEach((property) => { + let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; + `; + cssClass += cssStyle; + }); + cssClass += `} + `; + return cssClass; +}; +function createIndexHtml(path, appName) { + let dir = path; + let dirSrc; + let dirServer; + let dirComponent; + let dirContext; + if (!dir.match(/`${appName}`|\*$/)) { + dir = `${dir}/${appName}`; + if (!window.api.existsSync(dir)) { + window.api.mkdirSync(dir); + dirSrc = `${dir}/src`; + window.api.mkdirSync(dirSrc); + dirServer = `${dir}/server`; + window.api.mkdirSync(dirServer); + dirComponent = `${dirSrc}/components`; + window.api.mkdirSync(dirComponent); + //create directory for contexts + dirContext = `${dirSrc}/contexts`; + window.api.mkdirSync(dirContext); + } + } + const filePath: string = `${dir}/index.html`; + const data: string = ` + + + + ReacType App + + +
    + + + + `; + window.api.writeFileSync(filePath, data, (err) => { + if (err) { + console.log('index.html error:', err.message); + } else { + console.log('index.html written successfully'); + } + }); +} +export const createIndexTsx = (path, appName) => { + const filePath = `${path}/${appName}/src/index.tsx`; + const data = `import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './components/App'; +import './default.css'; +ReactDOM.render(, document.getElementById('root')); + `; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('index.tsx error:', err.message); + } else { + console.log('index.tsx written successfully'); + } + }); +}; +export const createDefaultCSS = (path, appName, components) => { + const filePath = `${path}/${appName}/src/default.css`; + let data = `#root div { + box-sizing: border-box; + width: 100%; + border: 1px solid rgba(0,0,0,0.25); + padding: 12px; + text-align: center; + font-family: Helvetica, Arial; +} +`; + components.forEach((comp) => { + data += compToCSS(comp); + }); + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('default.css error:', err.message); + } else { + console.log('default.css written successfully'); + } + }); +}; +export const createPackage = (path, appName, test) => { + const filePath = `${path}/${appName}/package.json`; + let tsjest = `, + "@types/enzyme": "^3.10.9", + "@types/jest": "^27.0.1", + "babel-jest": "^27.2.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.6", + "jest": "^27.2.0", + "@types/enzyme-adapter-react-16": "^1.0.6", + "ts-jest": "^27.0.5", + "enzyme-to-json": "^3.6.2"`; + const data = `{ + "name": "reactype", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node server/server.js", + "build": "cross-env NODE_ENV=production webpack", + "dev": "cross-env NODE_ENV=development webpack-dev-server"${ + test + ? `, + "test": "jest"` + : '' + } + }, + "nodemonConfig": { + "ignore": [ + "build", + "src" + ] + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@types/react": "^16.8.13", + "@types/react-dom": "^16.8.4", + "express": "^4.17.1", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "webpack": "^4.29.6" + }, + "devDependencies": { + "@babel/core": "^7.4.3", + "@babel/preset-env": "^7.4.3", + "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.3.3", + "babel-loader": "^8.0.5", + "cross-env": "^5.2.0", + "css-loader": "^2.1.1", + "file-loader": "^3.0.1", + "isomorphic-fetch": "^2.2.1", + "node-sass": "^7.0.1", + "nodemon": "^1.18.9", + "postcss-loader": "^3.0.0", + "sass-loader": "^7.1.0", + "source-map-loader": "^0.2.4", + "style-loader": "^0.23.1", + "tslint": "^5.15.0", + "tslint-config-prettier": "^1.18.0", + "tslint-react": "^4.0.0", + "typescript": "^3.8.3", + "webpack": "^4.29.6", + "webpack-cli": "^3.3.0", + "webpack-dev-server": "^3.2.1"${test ? tsjest : ''} + } +} + `; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('package.json error:', err.message); + } else { + console.log('package.json written successfully'); + } + }); +}; +export const createWebpack = (path, appName) => { + const filePath = `${path}/${appName}/webpack.config.js`; + const data = `var status = import.meta.env.NODE_ENV; //taken from script so we don't have to flip mode when using development/production +var path = require('path'); +module.exports = { + entry: './src/index.tsx', + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js', + }, + // Enable sourcemaps for debugging webpack's output. + devtool: 'source-map', + resolve: { + // Add '.ts' and '.tsx' as resolvable extensions. + extensions: ['.ts', '.tsx', '.js', '.json'], + }, + mode: status, + devServer: { + publicPath: '/build/', + }, + module: { + rules: [ + // All files with a '.ts' or '.tsx' extension will be handled by babel-loader + { test: /.tsx?$/, exclude: /node-modules/, loader: 'babel-loader' }, + + // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. + { enforce: 'pre', test: /.js$/, exclude: /node-modules/, loader: 'source-map-loader' }, + { + test: /.scss$/, + use: [ + 'style-loader', // creates style nodes from JS strings + 'css-loader', // translates CSS into CommonJS + 'sass-loader', // compiles Sass to CSS, using Node Sass by default + ], + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader' + ] + } + ], + }, +}; + `; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('webpack error:', err.message); + } else { + console.log('webpack written successfully'); + } + }); +}; +export const createBabel = (path, appName) => { + const filePath = `${path}/${appName}/.babelrc`; + const data = `{ + "presets": ["@babel/env", "@babel/react", "@babel/typescript"] +} +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('babelrc error:', err.message); + } else { + console.log('babelrc written successfully'); + } + }); +}; +export const createTsConfig = (path, appName) => { + const filePath = `${path}/${appName}/tsconfig.json`; + const data = `{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "noImplicitAny": true, + "module": "commonjs", + "target": "es6", + "jsx": "react", + "lib": ["dom", "es6"], + "moduleResolution": "node", + "esModuleInterop": true + }, + "include": ["./src/**/*"] +} +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('TSConfig error:', err.message); + } else { + console.log('TSConfig written successfully'); + } + }); +}; +export const createTsLint = (path, appName) => { + const filePath = `${path}/${appName}/tslint.json`; + const data = `{ + "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], + "tslint.autoFixOnSave": true, + "linterOptions": { + "exclude": ["config/**/*.js", "node_modules/**/*.ts"] + }, + "rules": { + "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], + "jsx-boolean-value": false, + "jsx-no-lambda": false, + "jsx-no-multiline-js": false, + "object-literal-sort-keys": false, + "member-ordering": false, + "no-console": false, + "ordered-imports": false, + "comment-format": false + } +} +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('TSLint error:', err.message); + } else { + console.log('TSLint written successfully'); + } + }); +}; +export const createServer = (path, appName) => { + const filePath = `${path}/${appName}/server/server.js`; + const data = `const express = require('express'); +const path = require('path'); +const app = express(); +app.get('/testDev', (req, res) => { + res.send({ dev: 'testDev endpoint hit' }); +}); +// statically serve everything in the build folder on the route '/build' +app.use('/build', express.static(path.join(__dirname, '../build'))); +// serve index.html on the route '/' +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../index.html')); +}); +app.listen(8080, () => { + console.log('listening on port 8080'); +}); //listens on port 8080 -> http://localhost:8080/ +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('server file error:', err.message); + } else { + console.log('server file written successfully'); + } + }); +}; + +//Generate files for all existing contexts in the current application +export const createContext = (path, appName) => { + // const store = useStore(); + const { allContext } = store.getState().contextSlice; + + for (const context of allContext) { + const cached = {}; + for (const ele of context.values) { + cached[ele.key] = ele.value; + } + const filePath = `${path}/${appName}/src/contexts/${context.name}.js`; + const data = `import {createContext, useState} from 'react' +export const ${context.name} = createContext(); + +const ${context.name}Provider = (props) => { + const [${context.name}State] = useState( + ${JSON.stringify(cached)} + ) +} + +return ( + <${context.name}.Provider value={${context.name}State}> + {props.children} + +); +export default ${context.name}Provider +`; + window.api.writeFileSync(filePath, data, (err) => { + if (err) { + console.log('server file error:', err.message); + } else { + console.log('server file written successfully'); + } + }); + } +}; +async function createApplicationUtil({ + path, + appName, + components, + testchecked +}: { + path: string; + appName: string; + components: Component[]; + testchecked: boolean; +}) { + await createIndexHtml(path, appName); + await createIndexTsx(path, appName); + await createDefaultCSS(path, appName, components); + await createPackage(path, appName, testchecked); + await createWebpack(path, appName); + await createBabel(path, appName); + await createTsConfig(path, appName); + await createTsLint(path, appName); + await createServer(path, appName); + await createContext(path, appName); + if (testchecked) { + await createTestSuiteClassic({ path, appName, components, testchecked }); + } + await createFiles(components, path, appName, true); +} +export default createApplicationUtil; diff --git a/app/src/utils/createFiles.util.ts b/app/src/utils/createFiles.util.ts index 0c40757bf..d1afad131 100644 --- a/app/src/utils/createFiles.util.ts +++ b/app/src/utils/createFiles.util.ts @@ -1,43 +1,43 @@ -// Creates all component files (but not the full application files) and places them in a "components" directory -const createFiles = ( - components: any, - path: string, - appName: string, - exportAppBool: boolean -) => { - let dir = path; - if (exportAppBool === false) { - if (!dir.match(/components|\*$/)) { - if (window.api.existsSync(`${dir}/src`)) { - dir = `${dir}/src`; - } - dir = `${dir}/components`; - if (!window.api.existsSync(dir)) { - window.api.mkdirSync(dir); - } - } - } else if (exportAppBool) { - if (!dir.match(/${appName}|\*$/)) { - dir = `${dir}/${appName}/src/components`; - } - } - const promises: Array = []; - components.forEach((component: any) => { - const newPromise = new Promise((resolve, reject) => { - window.api.writeFileSync( - `${dir}/${component.name}.tsx`, - - //this formatCodefunction has asynchronous issue - // window.api.formatCode(component.code), - component.code, - (err: any) => { - if (err) return reject(err.message); - return resolve(path); - } - ); - }); - promises.push(newPromise); - }); - return Promise.all(promises); -}; -export default createFiles; +// Creates all component files (but not the full application files) and places them in a "components" directory +const createFiles = ( + components: any, + path: string, + appName: string, + exportAppBool: boolean +) => { + let dir = path; + if (exportAppBool === false) { + if (!dir.match(/components|\*$/)) { + if (window.api.existsSync(`${dir}/src`)) { + dir = `${dir}/src`; + } + dir = `${dir}/components`; + if (!window.api.existsSync(dir)) { + window.api.mkdirSync(dir); + } + } + } else if (exportAppBool) { + if (!dir.match(/${appName}|\*$/)) { + dir = `${dir}/${appName}/src/components`; + } + } + const promises: Array = []; + components.forEach((component: any) => { + const newPromise = new Promise((resolve, reject) => { + window.api.writeFileSync( + `${dir}/${component.name}.tsx`, + + //this formatCodefunction has asynchronous issue + // window.api.formatCode(component.code), + component.code, + (err: any) => { + if (err) return reject(err.message); + return resolve(path); + } + ); + }); + promises.push(newPromise); + }); + return Promise.all(promises); +}; +export default createFiles; diff --git a/app/src/utils/createGatsbyApp.util.ts b/app/src/utils/createGatsbyApp.util.ts index bad733d5e..f00a1befa 100644 --- a/app/src/utils/createGatsbyApp.util.ts +++ b/app/src/utils/createGatsbyApp.util.ts @@ -1,189 +1,189 @@ -import createGatsbyFiles from './createGatsbyFiles.util'; -import createTestSuite from './createTestSuite.util'; -import { Component } from '../interfaces/Interfaces'; - -const camelToKebab= (camel:string) => { - return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); -}; - -const compToCSS = (component: Component) => { - const name = component.name; - const styleObj = component.style; - let cssClass = ` - .${name} { - `; - Object.keys(styleObj).forEach(property => { - let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; - `; - cssClass += cssStyle; - }) - cssClass += `} - `; - return cssClass; -} -//createPackage -export const createPackage = (path, appName, test) => { - const filePath = `${path}/${appName}/package.json`; - let tsjest = `, - "@types/enzyme": "^3.10.9", - "@types/jest": "^27.0.1", - "babel-jest": "^27.2.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", - "jest": "^27.2.0", - "@types/react-dom": "^17.0.9", - "@types/enzyme-adapter-react-16": "^1.0.6", - "@types/react-test-renderer": "^17.0.1", - "babel-preset-gatsby": "^1.13.0", - "identity-obj-proxy": "^3.0.0", - "ts-jest": "^27.0.5"`; - const data = ` - { - "name": "reactype-gatsby", - "version": "1.0.0", - "description": "", - "scripts": { - "dev": "gatsby develop", - "build": "gatsby build", - "start": "npm run dev"${ - test ? `, - "test": "jest"`: '' } - }, - "dependencies": { - "gatsby": "^2.26.1", - "react": "16.13.1", - "react-dom": "16.13.1" - }, - "devDependencies": { - "@types/node": "^14.0.20", - "@types/react": "^16.9.41", - "typescript": "^3.9.6"${ - test ? tsjest : '' } - } - } - `; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('package.json error:', err.message); - } else { - console.log('package.json written successfully'); - } - }); -}; -//createTSConfig (empty) -export const createTsConfig = (path:string, appName:string) => { - const filePath:string = `${path}/${appName}/tsconfig.json`; - //running 'gatsby dev' will autopopulate this with default values - const data:string = `{ - "compilerOptions": { - "outDir": "./dist/", - "sourceMap": true, - "noImplicitAny": true, - "module": "commonjs", - "target": "esnext", - "jsx": "react", - "lib": ["dom", "esnext"], - "moduleResolution": "node", - "esModuleInterop": true - }, - "include": ["./src/**/*"] -} -`; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('TSConfig error:', err.message); - } else { - console.log('TSConfig written successfully'); - } - }); -}; - -//createDefaultCSS -export const createDefaultCSS = (path:string, appName:string, components) => { - const filePath = `${path}/${appName}/global.css`; - let data = ` - #__gatsby div { - box-sizing: border-box; - width: 100%; - border: 1px solid rgba(0,0,0,0.25); - padding: 12px; - text-align: center; - font-family: Helvetica, Arial; - } - `; - console.log(components); - components.forEach(comp => { - data += compToCSS(comp); - }) - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('global.css error:', err.message); - } else { - console.log('global.css written successfully'); - } - }); -} - -export const initFolders = (path:string, appName: string) => { - let dir = path; - let dirPages; - let dirComponents; - dir = `${dir}/${appName}`; - if (!window.api.existsSync(dir)) { - window.api.mkdirSync(dir); - window.api.mkdirSync(`${dir}/src`) - dirPages = `${dir}/src/pages`; - window.api.mkdirSync(dirPages); - dirComponents = `${dir}/src/components`; - window.api.mkdirSync(dirComponents); - } -}; - -//createBaseTsx -export const createBaseTsx = (path: string, appName: string) => { - const filePath:string = `${path}/${appName}/src/pages/_app.tsx`; - const data:string = ` - import React from 'react'; - import '../global.css'; - const Base = ({ Component }):JSX.Element => { - return ( - <> - - - ) - } - export default Base; - `; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('_app.tsx error:', err.message); - } else { - console.log('_app.tsx written successfully'); - } - }); -}; - -async function createGatsbyAppUtil({ - path, - appName, - components, - rootComponents, - testchecked, -}: { - path: string; - appName: string; - components: Component[]; - rootComponents: number[]; - testchecked: boolean; -}) { - await initFolders(path, appName); - await createBaseTsx(path, appName); - await createDefaultCSS(path, appName, components); - await createPackage(path, appName, testchecked); - await createTsConfig(path, appName); - if (testchecked) { - await createTestSuite({path, appName, components, rootComponents, testchecked}); - } - await createGatsbyFiles(components, path, appName, rootComponents); -} -export default createGatsbyAppUtil; +import createGatsbyFiles from './createGatsbyFiles.util'; +import createTestSuite from './createTestSuite.util'; +import { Component } from '../interfaces/Interfaces'; + +const camelToKebab= (camel:string) => { + return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +}; + +const compToCSS = (component: Component) => { + const name = component.name; + const styleObj = component.style; + let cssClass = ` + .${name} { + `; + Object.keys(styleObj).forEach(property => { + let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; + `; + cssClass += cssStyle; + }) + cssClass += `} + `; + return cssClass; +} +//createPackage +export const createPackage = (path, appName, test) => { + const filePath = `${path}/${appName}/package.json`; + let tsjest = `, + "@types/enzyme": "^3.10.9", + "@types/jest": "^27.0.1", + "babel-jest": "^27.2.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.6", + "jest": "^27.2.0", + "@types/react-dom": "^17.0.9", + "@types/enzyme-adapter-react-16": "^1.0.6", + "@types/react-test-renderer": "^17.0.1", + "babel-preset-gatsby": "^1.13.0", + "identity-obj-proxy": "^3.0.0", + "ts-jest": "^27.0.5"`; + const data = ` + { + "name": "reactype-gatsby", + "version": "1.0.0", + "description": "", + "scripts": { + "dev": "gatsby develop", + "build": "gatsby build", + "start": "npm run dev"${ + test ? `, + "test": "jest"`: '' } + }, + "dependencies": { + "gatsby": "^2.26.1", + "react": "16.13.1", + "react-dom": "16.13.1" + }, + "devDependencies": { + "@types/node": "^14.0.20", + "@types/react": "^16.9.41", + "typescript": "^3.9.6"${ + test ? tsjest : '' } + } + } + `; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('package.json error:', err.message); + } else { + console.log('package.json written successfully'); + } + }); +}; +//createTSConfig (empty) +export const createTsConfig = (path:string, appName:string) => { + const filePath:string = `${path}/${appName}/tsconfig.json`; + //running 'gatsby dev' will autopopulate this with default values + const data:string = `{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "noImplicitAny": true, + "module": "commonjs", + "target": "esnext", + "jsx": "react", + "lib": ["dom", "esnext"], + "moduleResolution": "node", + "esModuleInterop": true + }, + "include": ["./src/**/*"] +} +`; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('TSConfig error:', err.message); + } else { + console.log('TSConfig written successfully'); + } + }); +}; + +//createDefaultCSS +export const createDefaultCSS = (path:string, appName:string, components) => { + const filePath = `${path}/${appName}/global.css`; + let data = ` + #__gatsby div { + box-sizing: border-box; + width: 100%; + border: 1px solid rgba(0,0,0,0.25); + padding: 12px; + text-align: center; + font-family: Helvetica, Arial; + } + `; + console.log(components); + components.forEach(comp => { + data += compToCSS(comp); + }) + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('global.css error:', err.message); + } else { + console.log('global.css written successfully'); + } + }); +} + +export const initFolders = (path:string, appName: string) => { + let dir = path; + let dirPages; + let dirComponents; + dir = `${dir}/${appName}`; + if (!window.api.existsSync(dir)) { + window.api.mkdirSync(dir); + window.api.mkdirSync(`${dir}/src`) + dirPages = `${dir}/src/pages`; + window.api.mkdirSync(dirPages); + dirComponents = `${dir}/src/components`; + window.api.mkdirSync(dirComponents); + } +}; + +//createBaseTsx +export const createBaseTsx = (path: string, appName: string) => { + const filePath:string = `${path}/${appName}/src/pages/_app.tsx`; + const data:string = ` + import React from 'react'; + import '../global.css'; + const Base = ({ Component }):JSX.Element => { + return ( + <> + + + ) + } + export default Base; + `; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('_app.tsx error:', err.message); + } else { + console.log('_app.tsx written successfully'); + } + }); +}; + +async function createGatsbyAppUtil({ + path, + appName, + components, + rootComponents, + testchecked, +}: { + path: string; + appName: string; + components: Component[]; + rootComponents: number[]; + testchecked: boolean; +}) { + await initFolders(path, appName); + await createBaseTsx(path, appName); + await createDefaultCSS(path, appName, components); + await createPackage(path, appName, testchecked); + await createTsConfig(path, appName); + if (testchecked) { + await createTestSuite({path, appName, components, rootComponents, testchecked}); + } + await createGatsbyFiles(components, path, appName, rootComponents); +} +export default createGatsbyAppUtil; diff --git a/app/src/utils/createGatsbyFiles.util.ts b/app/src/utils/createGatsbyFiles.util.ts index 19e52ca49..4a218cef0 100644 --- a/app/src/utils/createGatsbyFiles.util.ts +++ b/app/src/utils/createGatsbyFiles.util.ts @@ -1,41 +1,41 @@ -// Create all component files for a Gatsby.js application -// all components are stored in a src folder -// "Root" level components are stored in a pages directory -// all other components will be in a components directory -import { Component } from '../interfaces/Interfaces'; -const isRoot = (component: Component, rootArray: number[]) => { - return rootArray.includes(component.id) ? true : false; -}; -const createGatsbyFiles = ( - components: Component[], - path: string, - appName: string, - rootComponents: number[] -) => { - let dir = path; - dir = `${dir}/${appName}`; - const promises: Array = []; - components.forEach((component: Component) => { - let code: string; - let fileName: string; - if (isRoot(component, rootComponents)) { - if (component.id === 1) { - // first root component must be index.tsx - fileName = `${dir}/src/pages/index.tsx`; - } else { - fileName = `${dir}/src/pages/${component.name}.tsx`; - } - } else { - fileName = `${dir}/src/components/${component.name}.tsx`; - } - const newPromise = new Promise((resolve, reject) => { - window.api.writeFileSync(fileName, component.code, (err: any) => { - if (err) return reject(err.message); - return resolve(path); - }); - }); - promises.push(newPromise); - }); - return Promise.all(promises); -}; -export default createGatsbyFiles; +// Create all component files for a Gatsby.js application +// all components are stored in a src folder +// "Root" level components are stored in a pages directory +// all other components will be in a components directory +import { Component } from '../interfaces/Interfaces'; +const isRoot = (component: Component, rootArray: number[]) => { + return rootArray.includes(component.id) ? true : false; +}; +const createGatsbyFiles = ( + components: Component[], + path: string, + appName: string, + rootComponents: number[] +) => { + let dir = path; + dir = `${dir}/${appName}`; + const promises: Array = []; + components.forEach((component: Component) => { + let code: string; + let fileName: string; + if (isRoot(component, rootComponents)) { + if (component.id === 1) { + // first root component must be index.tsx + fileName = `${dir}/src/pages/index.tsx`; + } else { + fileName = `${dir}/src/pages/${component.name}.tsx`; + } + } else { + fileName = `${dir}/src/components/${component.name}.tsx`; + } + const newPromise = new Promise((resolve, reject) => { + window.api.writeFileSync(fileName, component.code, (err: any) => { + if (err) return reject(err.message); + return resolve(path); + }); + }); + promises.push(newPromise); + }); + return Promise.all(promises); +}; +export default createGatsbyFiles; diff --git a/app/src/utils/createNextApp.util.ts b/app/src/utils/createNextApp.util.ts index e66a8b0e7..4959bad15 100644 --- a/app/src/utils/createNextApp.util.ts +++ b/app/src/utils/createNextApp.util.ts @@ -1,206 +1,206 @@ -// Create all files necessary to run a next.js application -import createNextFiles from './createNextFiles.util'; -import { Component } from '../interfaces/Interfaces'; -import createTestSuiteNext from './createTestSuiteNext.util'; -const camelToKebab= (camel:string) => { - return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); -}; -const compToCSS = (component: Component) => { - const name = component.name; - const styleObj = component.style; - let cssClass = ` - .${name} { - `; - Object.keys(styleObj).forEach(property => { - let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; - `; - cssClass += cssStyle; - }) - cssClass += `} - `; - return cssClass; -} -//createPackage -export const createPackage = (path, appName, test) => { - const filePath = `${path}/${appName}/package.json`; - let testpackages = `, - "@types/enzyme": "^3.10.9", - "@types/jest": "^27.0.1", - "babel-jest": "^27.2.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", - "jest": "^27.2.0", - "@types/enzyme-adapter-react-16": "^1.0.6", - "identity-obj-proxy": "^3.0.0", - "ts-jest": "^27.0.5"`; - const data = ` -{ - "name": "reactype-next", - "version": "1.0.0", - "description": "", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start"${ - test ? `, - "test": "jest"`: '' } - }, - "dependencies": { - "next": "9.3.5", - "react": "16.13.1", - "react-dom": "16.13.1" - }, - "devDependencies": { - "@types/node": "^14.0.20", - "@types/react": "^16.9.41", - "@types/react-dom": "^17.0.9", - "typescript": "^3.9.6"${ - test ? testpackages : '' - } - } -} -`; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('createNextApp.util package.json error:', err.message); - } else { - console.log('createNextApp.util package.json written successfully'); - } - }); -}; -//createTSConfig (empty) -export const createTsConfig = (path, appName) => { - const filePath = `${path}/${appName}/tsconfig.json`; - //running 'next dev' will autopopulate this with default values - const data:string = `{ - "compileOnSave": false, - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "jsx": "preserve", - "allowJs": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "removeComments": false, - "preserveConstEnums": true, - "sourceMap": true, - "skipLibCheck": true, - "baseUrl": ".", - "lib": [ - "dom", - "es2016" - ], - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "isolatedModules": true - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} -`; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('TSConfig error:', err.message); - } else { - console.log('TSConfig written successfully'); - } - }); -}; - -//createDefaultCSS -export const createDefaultCSS = (path, appName, components) => { - const filePath = `${path}/${appName}/global.css`; - let data = ` - #__next div { - box-sizing: border-box; - width: 100%; - border: 1px solid rgba(0,0,0,0.25); - padding: 12px; - text-align: center; - font-family: Helvetica, Arial; - } - `; - components.forEach(comp => { - data += compToCSS(comp); - }) - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('global.css error:', err.message); - } else { - console.log('global.css written successfully'); - } - }); -} - -export const initFolders = (path:string, appName: string) => { - let dir = path; - let dirPages; - let dirComponents; - dir = `${dir}/${appName}`; - if(!window.api.existsSync(dir)){ - window.api.mkdirSync(dir); - dirPages = `${dir}/pages`; - window.api.mkdirSync(dirPages); - dirComponents = `${dir}/components`; - window.api.mkdirSync(dirComponents); - } -} -//createBaseTsx -export const createBaseTsx = (path, appName) => { - const filePath:string = `${path}/${appName}/pages/_app.tsx`; - const data:string = ` - import React from 'react'; - import '../global.css'; - const Base = ({ Component }):JSX.Element => { - return ( - <> - - - ) - } - export default Base; - `; - window.api.writeFile(filePath, data, err => { - if (err) { - console.log('_app.tsx error:', err.message); - } else { - console.log('_app.tsx written successfully'); - } - }); -}; - -async function createNextAppUtil({ - path, - appName, - components, - rootComponents, - testchecked, -}: { - path: string; - appName: string; - components: Component[]; - rootComponents: number[]; - testchecked: boolean; -}) { - await initFolders(path, appName); - await createBaseTsx(path, appName); - await createDefaultCSS(path, appName, components); - await createPackage(path, appName, testchecked); - await createTsConfig(path, appName); - if (testchecked) { - await createTestSuiteNext({path, appName, components, rootComponents, testchecked}); - } - await createNextFiles(components, path, appName, rootComponents); -} -export default createNextAppUtil; +// Create all files necessary to run a next.js application +import createNextFiles from './createNextFiles.util'; +import { Component } from '../interfaces/Interfaces'; +import createTestSuiteNext from './createTestSuiteNext.util'; +const camelToKebab= (camel:string) => { + return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); +}; +const compToCSS = (component: Component) => { + const name = component.name; + const styleObj = component.style; + let cssClass = ` + .${name} { + `; + Object.keys(styleObj).forEach(property => { + let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; + `; + cssClass += cssStyle; + }) + cssClass += `} + `; + return cssClass; +} +//createPackage +export const createPackage = (path, appName, test) => { + const filePath = `${path}/${appName}/package.json`; + let testpackages = `, + "@types/enzyme": "^3.10.9", + "@types/jest": "^27.0.1", + "babel-jest": "^27.2.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.6", + "jest": "^27.2.0", + "@types/enzyme-adapter-react-16": "^1.0.6", + "identity-obj-proxy": "^3.0.0", + "ts-jest": "^27.0.5"`; + const data = ` +{ + "name": "reactype-next", + "version": "1.0.0", + "description": "", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start"${ + test ? `, + "test": "jest"`: '' } + }, + "dependencies": { + "next": "9.3.5", + "react": "16.13.1", + "react-dom": "16.13.1" + }, + "devDependencies": { + "@types/node": "^14.0.20", + "@types/react": "^16.9.41", + "@types/react-dom": "^17.0.9", + "typescript": "^3.9.6"${ + test ? testpackages : '' + } + } +} +`; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('createNextApp.util package.json error:', err.message); + } else { + console.log('createNextApp.util package.json written successfully'); + } + }); +}; +//createTSConfig (empty) +export const createTsConfig = (path, appName) => { + const filePath = `${path}/${appName}/tsconfig.json`; + //running 'next dev' will autopopulate this with default values + const data:string = `{ + "compileOnSave": false, + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "jsx": "preserve", + "allowJs": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "removeComments": false, + "preserveConstEnums": true, + "sourceMap": true, + "skipLibCheck": true, + "baseUrl": ".", + "lib": [ + "dom", + "es2016" + ], + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} +`; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('TSConfig error:', err.message); + } else { + console.log('TSConfig written successfully'); + } + }); +}; + +//createDefaultCSS +export const createDefaultCSS = (path, appName, components) => { + const filePath = `${path}/${appName}/global.css`; + let data = ` + #__next div { + box-sizing: border-box; + width: 100%; + border: 1px solid rgba(0,0,0,0.25); + padding: 12px; + text-align: center; + font-family: Helvetica, Arial; + } + `; + components.forEach(comp => { + data += compToCSS(comp); + }) + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('global.css error:', err.message); + } else { + console.log('global.css written successfully'); + } + }); +} + +export const initFolders = (path:string, appName: string) => { + let dir = path; + let dirPages; + let dirComponents; + dir = `${dir}/${appName}`; + if(!window.api.existsSync(dir)){ + window.api.mkdirSync(dir); + dirPages = `${dir}/pages`; + window.api.mkdirSync(dirPages); + dirComponents = `${dir}/components`; + window.api.mkdirSync(dirComponents); + } +} +//createBaseTsx +export const createBaseTsx = (path, appName) => { + const filePath:string = `${path}/${appName}/pages/_app.tsx`; + const data:string = ` + import React from 'react'; + import '../global.css'; + const Base = ({ Component }):JSX.Element => { + return ( + <> + + + ) + } + export default Base; + `; + window.api.writeFile(filePath, data, err => { + if (err) { + console.log('_app.tsx error:', err.message); + } else { + console.log('_app.tsx written successfully'); + } + }); +}; + +async function createNextAppUtil({ + path, + appName, + components, + rootComponents, + testchecked, +}: { + path: string; + appName: string; + components: Component[]; + rootComponents: number[]; + testchecked: boolean; +}) { + await initFolders(path, appName); + await createBaseTsx(path, appName); + await createDefaultCSS(path, appName, components); + await createPackage(path, appName, testchecked); + await createTsConfig(path, appName); + if (testchecked) { + await createTestSuiteNext({path, appName, components, rootComponents, testchecked}); + } + await createNextFiles(components, path, appName, rootComponents); +} +export default createNextAppUtil; diff --git a/app/src/utils/createNextFiles.util.ts b/app/src/utils/createNextFiles.util.ts index 31ff60a04..76a87bd3b 100644 --- a/app/src/utils/createNextFiles.util.ts +++ b/app/src/utils/createNextFiles.util.ts @@ -1,45 +1,45 @@ -// Create all component files for a next.js application -// "Root" level components are stored in a pages directory -// all other components will be in a components directory -import { Component } from '../interfaces/Interfaces'; - -const isRoot = (component: Component, rootArray: number[]) => { - return rootArray.includes(component.id) ? true : false; -}; - -const createNextFiles = ( - components: Component[], - path: string, - appName: string, - rootComponents: number[] -) => { - let dir = path; - dir = `${dir}/${appName}`; - - const promises: Array = []; - components.forEach((component: Component) => { - let code: string; - let fileName: string; - if (isRoot(component, rootComponents)) { - if (component.id === 1) { - // first root component must be index.tsx - fileName = `${dir}/pages/index.tsx`; - } else { - fileName = `${dir}/pages/${component.name}.tsx`; - } - } else { - fileName = `${dir}/components/${component.name}.tsx`; - } - const newPromise = new Promise((resolve, reject) => { - window.api.writeFileSync(fileName, component.code, (err: any) => { - if (err) return reject(err.message); - return resolve(path); - }); - }); - - promises.push(newPromise); - }); - return Promise.all(promises); -}; - -export default createNextFiles; +// Create all component files for a next.js application +// "Root" level components are stored in a pages directory +// all other components will be in a components directory +import { Component } from '../interfaces/Interfaces'; + +const isRoot = (component: Component, rootArray: number[]) => { + return rootArray.includes(component.id) ? true : false; +}; + +const createNextFiles = ( + components: Component[], + path: string, + appName: string, + rootComponents: number[] +) => { + let dir = path; + dir = `${dir}/${appName}`; + + const promises: Array = []; + components.forEach((component: Component) => { + let code: string; + let fileName: string; + if (isRoot(component, rootComponents)) { + if (component.id === 1) { + // first root component must be index.tsx + fileName = `${dir}/pages/index.tsx`; + } else { + fileName = `${dir}/pages/${component.name}.tsx`; + } + } else { + fileName = `${dir}/components/${component.name}.tsx`; + } + const newPromise = new Promise((resolve, reject) => { + window.api.writeFileSync(fileName, component.code, (err: any) => { + if (err) return reject(err.message); + return resolve(path); + }); + }); + + promises.push(newPromise); + }); + return Promise.all(promises); +}; + +export default createNextFiles; diff --git a/app/src/utils/createNonce.ts b/app/src/utils/createNonce.ts index 79ddc04ae..fd260514d 100644 --- a/app/src/utils/createNonce.ts +++ b/app/src/utils/createNonce.ts @@ -1,12 +1,12 @@ -const { v4: uuidv4 } = require('uuid'); -// const Buffer = require('buffer'); - - -// const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); -// module.exports = buf; - -// Generate an arbitrary number -// this arbitrary number will be used in CspHtmlWebpackPlugin and HtmlWebpackPlugin configuration in webpack -module.exports = function () { - return new Buffer.from(uuidv4()).toString('base64'); -}; +const { v4: uuidv4 } = require('uuid'); +// const Buffer = require('buffer'); + + +// const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); +// module.exports = buf; + +// Generate an arbitrary number +// this arbitrary number will be used in CspHtmlWebpackPlugin and HtmlWebpackPlugin configuration in webpack +module.exports = function () { + return new Buffer.from(uuidv4()).toString('base64'); +}; diff --git a/app/src/utils/createTestSuite.util.ts b/app/src/utils/createTestSuite.util.ts index 0a1cd31ad..888381aa7 100644 --- a/app/src/utils/createTestSuite.util.ts +++ b/app/src/utils/createTestSuite.util.ts @@ -1,204 +1,204 @@ -// create config files -// add webpack dependencies -// create tests for components -const initFolders = (path: string, appName: string) => { - let dir = path; - dir = `${dir}/${appName}`; - if (!window.api.existsSync(`${dir}/__mocks__`)) { - window.api.mkdirSync(`${dir}/__mocks__`); - } - if (!window.api.existsSync(`${dir}/__tests__`)) { - window.api.mkdirSync(`${dir}/__tests__`); - } -}; - -const createMocksFiles = (path: string, appName: string) => { - const filePath: string = `${path}/${appName}/__mocks__/file-mock.js`; - let data = `module.exports = "test-file-stub";`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('createTestSuite.util createMocksFiles error', err.message); - } else { - console.log('createTestSuite.util createMocksFiles written successfully'); - } - }); -}; - -const createTestsFiles = (path: string, appName: string) => { - const filePath: string = `${path}/${appName}/__mocks__/gatspy.js`; - let data = ` - const React = require("react") - const gatsby = jest.requireActual("gatsby") - module.exports = { - ...gatsby, - Link: jest.fn().mockImplementation( - ({ - activeClassName, - activeStyle, - getProps, - innerRef, - partiallyActive, - ref, - replace, - to, - ...rest - }) => - React.createElement("a", { - ...rest, - href: to, - }) - ), - StaticQuery: jest.fn(), - useStaticQuery: jest.fn(), - } -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('createTestSuite.util createTestsFiles error', err.message); - } else { - console.log('createTestSuite.util createTestsFiles written successfully'); - } - }); -}; - -async function createJestConfigFile(path: String, appName: String) { - const filePath: string = `${path}/${appName}/jest.config.js`; - const data: string = ` - module.exports = { - transform: { - "^.+\\.tsx?$": "ts-jest", - "^.+\\.jsx?$": "/jest-preprocess.js", - }, - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", - moduleNameMapper: { - ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", - ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js", - "^uuid$": "uuid", - }, - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], - testPathIgnorePatterns: ["node_modules", ".cache"], - transformIgnorePatterns: ["node_modules/(?!(gatsby)/)"], - globals: { - __PATH_PREFIX__: '', - } - } - `; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createJestConfigFile error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createJestConfigFile written successfully' - ); - } - }); -} - -async function createJestPreprocessFile(path: string, appName: string) { - const filePath: string = `${path}/${appName}/jest-preprocess.js`; - const data: string = ` - const babelOptions = { - presets: ["babel-preset-gatsby"], - } - module.exports = require("babel-jest").default.createTransformer(babelOptions)`; - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createJestPreprocessFile error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createJestPreprocessFile written successfully' - ); - } - }); -} - -async function createComponentTests( - path: string, - appName: string, - components: Component[] -) { - const filePath: string = `${path}/${appName}/__tests__/test.tsx`; - - let data: string = ` - import React from "react" - import Enzyme, { shallow } from 'enzyme'; - import Adapter from 'enzyme-adapter-react-16'; - Enzyme.configure({ adapter: new Adapter() }); - `; - - components.forEach((page) => { - let importString = ''; - if (page.isPage) { - importString = ` - import ${capitalize(page.name)} from "../src/pages/${page.name}";`; - data = data + importString; - } else { - importString = ` - import ${capitalize(page.name)} from "../src/components/${page.name}";`; - data = data + importString; - } - }); - - components.forEach((page) => { - data = - data + - ` - describe("${capitalize(page.name)}", () => {`; - data = - data + - ` - it("renders correctly", () => { - const tree = shallow(<${capitalize(page.name)} />); - expect(tree).toMatchSnapshot(); - })`; - data = - data + - ` - });`; - }); - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createComponentTests error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createComponentTests written successfully' - ); - } - }); -} - -const capitalize = (string: string) => { - return string.charAt(0).toUpperCase() + string.slice(1); -}; -async function createTestSuite({ - path, - appName, - components, - rootComponents, - testchecked -}: { - path: string; - appName: string; - components: Component[]; - rootComponents: number[]; - testchecked: boolean; -}) { - await initFolders(path, appName); - await createMocksFiles(path, appName); - await createTestsFiles(path, appName); - await createJestConfigFile(path, appName); - await createJestPreprocessFile(path, appName); - await createComponentTests(path, appName, components); -} - -export default createTestSuite; +// create config files +// add webpack dependencies +// create tests for components +const initFolders = (path: string, appName: string) => { + let dir = path; + dir = `${dir}/${appName}`; + if (!window.api.existsSync(`${dir}/__mocks__`)) { + window.api.mkdirSync(`${dir}/__mocks__`); + } + if (!window.api.existsSync(`${dir}/__tests__`)) { + window.api.mkdirSync(`${dir}/__tests__`); + } +}; + +const createMocksFiles = (path: string, appName: string) => { + const filePath: string = `${path}/${appName}/__mocks__/file-mock.js`; + let data = `module.exports = "test-file-stub";`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('createTestSuite.util createMocksFiles error', err.message); + } else { + console.log('createTestSuite.util createMocksFiles written successfully'); + } + }); +}; + +const createTestsFiles = (path: string, appName: string) => { + const filePath: string = `${path}/${appName}/__mocks__/gatspy.js`; + let data = ` + const React = require("react") + const gatsby = jest.requireActual("gatsby") + module.exports = { + ...gatsby, + Link: jest.fn().mockImplementation( + ({ + activeClassName, + activeStyle, + getProps, + innerRef, + partiallyActive, + ref, + replace, + to, + ...rest + }) => + React.createElement("a", { + ...rest, + href: to, + }) + ), + StaticQuery: jest.fn(), + useStaticQuery: jest.fn(), + } +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('createTestSuite.util createTestsFiles error', err.message); + } else { + console.log('createTestSuite.util createTestsFiles written successfully'); + } + }); +}; + +async function createJestConfigFile(path: String, appName: String) { + const filePath: string = `${path}/${appName}/jest.config.js`; + const data: string = ` + module.exports = { + transform: { + "^.+\\.tsx?$": "ts-jest", + "^.+\\.jsx?$": "/jest-preprocess.js", + }, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", + moduleNameMapper: { + ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", + ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js", + "^uuid$": "uuid", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testPathIgnorePatterns: ["node_modules", ".cache"], + transformIgnorePatterns: ["node_modules/(?!(gatsby)/)"], + globals: { + __PATH_PREFIX__: '', + } + } + `; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createJestConfigFile error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createJestConfigFile written successfully' + ); + } + }); +} + +async function createJestPreprocessFile(path: string, appName: string) { + const filePath: string = `${path}/${appName}/jest-preprocess.js`; + const data: string = ` + const babelOptions = { + presets: ["babel-preset-gatsby"], + } + module.exports = require("babel-jest").default.createTransformer(babelOptions)`; + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createJestPreprocessFile error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createJestPreprocessFile written successfully' + ); + } + }); +} + +async function createComponentTests( + path: string, + appName: string, + components: Component[] +) { + const filePath: string = `${path}/${appName}/__tests__/test.tsx`; + + let data: string = ` + import React from "react" + import Enzyme, { shallow } from 'enzyme'; + import Adapter from 'enzyme-adapter-react-16'; + Enzyme.configure({ adapter: new Adapter() }); + `; + + components.forEach((page) => { + let importString = ''; + if (page.isPage) { + importString = ` + import ${capitalize(page.name)} from "../src/pages/${page.name}";`; + data = data + importString; + } else { + importString = ` + import ${capitalize(page.name)} from "../src/components/${page.name}";`; + data = data + importString; + } + }); + + components.forEach((page) => { + data = + data + + ` + describe("${capitalize(page.name)}", () => {`; + data = + data + + ` + it("renders correctly", () => { + const tree = shallow(<${capitalize(page.name)} />); + expect(tree).toMatchSnapshot(); + })`; + data = + data + + ` + });`; + }); + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createComponentTests error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createComponentTests written successfully' + ); + } + }); +} + +const capitalize = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; +async function createTestSuite({ + path, + appName, + components, + rootComponents, + testchecked +}: { + path: string; + appName: string; + components: Component[]; + rootComponents: number[]; + testchecked: boolean; +}) { + await initFolders(path, appName); + await createMocksFiles(path, appName); + await createTestsFiles(path, appName); + await createJestConfigFile(path, appName); + await createJestPreprocessFile(path, appName); + await createComponentTests(path, appName, components); +} + +export default createTestSuite; diff --git a/app/src/utils/createTestSuiteClassic.util.ts b/app/src/utils/createTestSuiteClassic.util.ts index 27f092219..351c4ff67 100644 --- a/app/src/utils/createTestSuiteClassic.util.ts +++ b/app/src/utils/createTestSuiteClassic.util.ts @@ -1,141 +1,141 @@ -import { Component } from '../interfaces/Interfaces'; - -const initFolders = (path: string, appName: string) => { - let dir = path; - dir = `${dir}/${appName}`; - if (!window.api.existsSync(`${dir}/__tests__`)) { - window.api.mkdirSync(`${dir}/__tests__`); - } -}; - -const createJestConfigFile = (path: string, appName: string) => { - const filePath: string = `${path}/${appName}/jest.config.js`; - const data: String = ` -module.exports = { - snapshotSerializers: ["enzyme-to-json/serializer"], - transform: { - "^.+\\.tsx?$": "ts-jest", - "^.+\\.jsx?$": "/jest-preprocess.js", - }, - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], - testPathIgnorePatterns: ["node_moules", ".cache"], - globals: { - __PATH_PREFIX__: "", - } -} `; - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuiteClassic.util createJestConfigFile error:', - err.message - ); - } else { - console.log( - 'createTestSuiteClassic.util createJestConfigFile written successfully' - ); - } - }); -}; - -const createJestPreprocessFile = (path: string, appName: string) => { - const filePath: string = `${path}/${appName}/jest-preprocess.js`; - const data: string = ` - module.exports = require("babel-jest")`; - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createJestPreprocessFile error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createJestPreprocessFile written successfully' - ); - } - }); -}; - -async function createComponentTests( - path: string, - appName: string, - components: Component[], -) { - const filePath: string = `${path}/${appName}/__tests__/test.tsx`; - let data: string = ` - import { shallow } from 'enzyme' - import React from 'react'; - - import * as Enzyme from 'enzyme' - import Adapter from 'enzyme-adapter-react-16' - - Enzyme.configure({ - adapter: new Adapter(), -}) - `; - - components.forEach((page) => { - let importString = ` - import ${capitalize(page.name)} from "../src/components/${page.name}";`; - data = data + importString; - }); - - components.forEach((page) => { - data = - data + - ` - - describe("${capitalize(page.name)}", () => {`; - - data = - data + - ` - it('renders snapshots, too', () => { - const wrapper = shallow(< ${capitalize(page.name)} />) - expect(wrapper).toMatchSnapshot() - })`; - - data = - data + - ` - });`; - }); - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createComponentTests error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createComponentTests written successfully' - ); - } - }); -} - -const capitalize = (string: string) => { - return string.charAt(0).toUpperCase() + string.slice(1); -}; - -async function createTestSuiteClassic({ - path, - appName, - components, - testchecked -}: { - path: string; - appName: string; - components: Component[]; - testchecked: boolean; -}) { - await initFolders(path, appName); - await createJestConfigFile(path, appName); - await createJestPreprocessFile(path, appName); - await createComponentTests(path, appName, components); -} - -export default createTestSuiteClassic; +import { Component } from '../interfaces/Interfaces'; + +const initFolders = (path: string, appName: string) => { + let dir = path; + dir = `${dir}/${appName}`; + if (!window.api.existsSync(`${dir}/__tests__`)) { + window.api.mkdirSync(`${dir}/__tests__`); + } +}; + +const createJestConfigFile = (path: string, appName: string) => { + const filePath: string = `${path}/${appName}/jest.config.js`; + const data: String = ` +module.exports = { + snapshotSerializers: ["enzyme-to-json/serializer"], + transform: { + "^.+\\.tsx?$": "ts-jest", + "^.+\\.jsx?$": "/jest-preprocess.js", + }, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testPathIgnorePatterns: ["node_moules", ".cache"], + globals: { + __PATH_PREFIX__: "", + } +} `; + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuiteClassic.util createJestConfigFile error:', + err.message + ); + } else { + console.log( + 'createTestSuiteClassic.util createJestConfigFile written successfully' + ); + } + }); +}; + +const createJestPreprocessFile = (path: string, appName: string) => { + const filePath: string = `${path}/${appName}/jest-preprocess.js`; + const data: string = ` + module.exports = require("babel-jest")`; + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createJestPreprocessFile error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createJestPreprocessFile written successfully' + ); + } + }); +}; + +async function createComponentTests( + path: string, + appName: string, + components: Component[], +) { + const filePath: string = `${path}/${appName}/__tests__/test.tsx`; + let data: string = ` + import { shallow } from 'enzyme' + import React from 'react'; + + import * as Enzyme from 'enzyme' + import Adapter from 'enzyme-adapter-react-16' + + Enzyme.configure({ + adapter: new Adapter(), +}) + `; + + components.forEach((page) => { + let importString = ` + import ${capitalize(page.name)} from "../src/components/${page.name}";`; + data = data + importString; + }); + + components.forEach((page) => { + data = + data + + ` + + describe("${capitalize(page.name)}", () => {`; + + data = + data + + ` + it('renders snapshots, too', () => { + const wrapper = shallow(< ${capitalize(page.name)} />) + expect(wrapper).toMatchSnapshot() + })`; + + data = + data + + ` + });`; + }); + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createComponentTests error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createComponentTests written successfully' + ); + } + }); +} + +const capitalize = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; + +async function createTestSuiteClassic({ + path, + appName, + components, + testchecked +}: { + path: string; + appName: string; + components: Component[]; + testchecked: boolean; +}) { + await initFolders(path, appName); + await createJestConfigFile(path, appName); + await createJestPreprocessFile(path, appName); + await createComponentTests(path, appName, components); +} + +export default createTestSuiteClassic; diff --git a/app/src/utils/createTestSuiteNext.util.ts b/app/src/utils/createTestSuiteNext.util.ts index ef8b608e9..71f7d0610 100644 --- a/app/src/utils/createTestSuiteNext.util.ts +++ b/app/src/utils/createTestSuiteNext.util.ts @@ -1,227 +1,227 @@ -const initFolders = (path: string, appName: string) => { - let dir = path; - dir = `${dir}/${appName}`; - if (!window.api.existsSync(`${dir}/__mocks__`)) { - window.api.mkdirSync(`${dir}/__mocks__`); - } - if (!window.api.existsSync(`${dir}/__tests__`)) { - window.api.mkdirSync(`${dir}/__tests__`); - } -}; - -async function createJestConfigFile(path: String, appName: String) { - const filePath: string = `${path}/${appName}/jest.config.js`; - const data: string = ` -module.exports = { - moduleFileExtensions: [ - "ts", - "tsx", - "js" - ], - transform: { - "^.+\\.tsx?$": "ts-jest", - "^.+\\.jsx?$": "/jest-preprocess.js", - }, - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", - globals: { - "ts-jest": { - babelConfig: true, - tsconfig: "jest.tsconfig.json" - } - }, - coveragePathIgnorePatterns: [ - "/node_modules/", - "enzyme.js" - ], - coverageReporters: [ - "json", - "lcov", - "text", - "text-summary" - ], - moduleNameMapper: { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js", - "\\.(css|less|scss)$": "identity-obj-proxy", - "^uuid$": "uuid", - } -} -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuiteNext.util createJestConfigFile error:', - err.message - ); - } else { - console.log( - 'createTestSuitNext.util createJestConfigFile written successfully' - ); - } - }); -} - -async function createJestTsconfigJsonFile(path: String, appName: String) { - const filePath: string = `${path}/${appName}/jest.tsconfig.json`; - const data: string = ` -{ - "compilerOptions": { - "module": "commonjs", - "target": "esnext", - "jsx": "react", - "sourceMap": false, - "experimentalDecorators": true, - "noImplicitUseStrict": true, - "moduleResolution": "node", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "lib": [ - "es2017", - "dom" - ], - "typeRoots": [ - "node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "out", - ".next" - ] -} -`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuiteNext.util createJestTsconfigJsonFile error:', - err.message - ); - } else { - console.log( - 'createTestSuitNext.util createJestTsconfigJsonFile written successfully' - ); - } - }); -} - -async function createJestPreprocessFile(path: string, appName: string) { - const filePath: string = `${path}/${appName}/jest-preprocess.js`; - const data: string = ` -const babelOptions = { - presets: ["next/babel"], -} - -module.exports = require("babel-jest").default.createTransformer(babelOptions)`; - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuite.util createJestPreprocessFile error:', - err.message - ); - } else { - console.log( - 'createTestSuit.util createJestPreprocessFile written successfully' - ); - } - }); -} - -async function createEnzymeFile(path: string, appName: string) { - const filePath: string = `${path}/${appName}/enzyme.js`; - const data: string = `const Adapter = require('enzyme-adapter-react-16'); -require('enzyme').configure({adapter: new Adapter()});`; - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log('createTestSuite.util createEnzymeFile error:', err.message); - } else { - console.log('createTestSuit.util createEnzymeFile written successfully'); - } - }); -} - -async function createComponentTests( - path: string, - appName: string, - components: Component[] -) { - const filePath: string = `${path}/${appName}/__tests__/test.tsx`; - - let data: string = ` - import { shallow } from 'enzyme' - import React from 'react'; - `; - - components.forEach((page) => { - let importString = ''; - if (page.isPage) { - importString = ` -import ${capitalize(page.name)} from "../pages/${page.name}";`; - data = data + importString; - } else { - importString = ` -import ${capitalize(page.name)} from "../components/${page.name}";`; - data = data + importString; - } - }); - - components.forEach((page) => { - data = - data + - ` - - describe("${capitalize(page.name)}", () => {`; - - data = - data + - ` - it('renders snapshots, too', () => { - const wrapper = shallow(< ${capitalize(page.name)} />) - expect(wrapper).toMatchSnapshot() - })`; - - data = - data + - ` - });`; - }); - - window.api.writeFile(filePath, data, (err) => { - if (err) { - console.log( - 'createTestSuiteNext.util createComponentTests error:', - err.message - ); - } else { - console.log( - 'createTestSuitNext.util createComponentTests written successfully' - ); - } - }); -} - -const capitalize = (string: string) => { - return string.charAt(0).toUpperCase() + string.slice(1); -}; - -async function createTestSuite({ - path, - appName, - components, - rootComponents, - testchecked -}: { - path: string; - appName: string; - components: Component[]; - rootComponents: number[]; - testchecked: boolean; -}) { - await initFolders(path, appName); - await createJestConfigFile(path, appName); - await createJestTsconfigJsonFile(path, appName); - await createJestPreprocessFile(path, appName); - await createEnzymeFile(path, appName); - await createComponentTests(path, appName, components); -} - -export default createTestSuite; +const initFolders = (path: string, appName: string) => { + let dir = path; + dir = `${dir}/${appName}`; + if (!window.api.existsSync(`${dir}/__mocks__`)) { + window.api.mkdirSync(`${dir}/__mocks__`); + } + if (!window.api.existsSync(`${dir}/__tests__`)) { + window.api.mkdirSync(`${dir}/__tests__`); + } +}; + +async function createJestConfigFile(path: String, appName: String) { + const filePath: string = `${path}/${appName}/jest.config.js`; + const data: string = ` +module.exports = { + moduleFileExtensions: [ + "ts", + "tsx", + "js" + ], + transform: { + "^.+\\.tsx?$": "ts-jest", + "^.+\\.jsx?$": "/jest-preprocess.js", + }, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", + globals: { + "ts-jest": { + babelConfig: true, + tsconfig: "jest.tsconfig.json" + } + }, + coveragePathIgnorePatterns: [ + "/node_modules/", + "enzyme.js" + ], + coverageReporters: [ + "json", + "lcov", + "text", + "text-summary" + ], + moduleNameMapper: { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js", + "\\.(css|less|scss)$": "identity-obj-proxy", + "^uuid$": "uuid", + } +} +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuiteNext.util createJestConfigFile error:', + err.message + ); + } else { + console.log( + 'createTestSuitNext.util createJestConfigFile written successfully' + ); + } + }); +} + +async function createJestTsconfigJsonFile(path: String, appName: String) { + const filePath: string = `${path}/${appName}/jest.tsconfig.json`; + const data: string = ` +{ + "compilerOptions": { + "module": "commonjs", + "target": "esnext", + "jsx": "react", + "sourceMap": false, + "experimentalDecorators": true, + "noImplicitUseStrict": true, + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "lib": [ + "es2017", + "dom" + ], + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "out", + ".next" + ] +} +`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuiteNext.util createJestTsconfigJsonFile error:', + err.message + ); + } else { + console.log( + 'createTestSuitNext.util createJestTsconfigJsonFile written successfully' + ); + } + }); +} + +async function createJestPreprocessFile(path: string, appName: string) { + const filePath: string = `${path}/${appName}/jest-preprocess.js`; + const data: string = ` +const babelOptions = { + presets: ["next/babel"], +} + +module.exports = require("babel-jest").default.createTransformer(babelOptions)`; + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuite.util createJestPreprocessFile error:', + err.message + ); + } else { + console.log( + 'createTestSuit.util createJestPreprocessFile written successfully' + ); + } + }); +} + +async function createEnzymeFile(path: string, appName: string) { + const filePath: string = `${path}/${appName}/enzyme.js`; + const data: string = `const Adapter = require('enzyme-adapter-react-16'); +require('enzyme').configure({adapter: new Adapter()});`; + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log('createTestSuite.util createEnzymeFile error:', err.message); + } else { + console.log('createTestSuit.util createEnzymeFile written successfully'); + } + }); +} + +async function createComponentTests( + path: string, + appName: string, + components: Component[] +) { + const filePath: string = `${path}/${appName}/__tests__/test.tsx`; + + let data: string = ` + import { shallow } from 'enzyme' + import React from 'react'; + `; + + components.forEach((page) => { + let importString = ''; + if (page.isPage) { + importString = ` +import ${capitalize(page.name)} from "../pages/${page.name}";`; + data = data + importString; + } else { + importString = ` +import ${capitalize(page.name)} from "../components/${page.name}";`; + data = data + importString; + } + }); + + components.forEach((page) => { + data = + data + + ` + + describe("${capitalize(page.name)}", () => {`; + + data = + data + + ` + it('renders snapshots, too', () => { + const wrapper = shallow(< ${capitalize(page.name)} />) + expect(wrapper).toMatchSnapshot() + })`; + + data = + data + + ` + });`; + }); + + window.api.writeFile(filePath, data, (err) => { + if (err) { + console.log( + 'createTestSuiteNext.util createComponentTests error:', + err.message + ); + } else { + console.log( + 'createTestSuitNext.util createComponentTests written successfully' + ); + } + }); +} + +const capitalize = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; + +async function createTestSuite({ + path, + appName, + components, + rootComponents, + testchecked +}: { + path: string; + appName: string; + components: Component[]; + rootComponents: number[]; + testchecked: boolean; +}) { + await initFolders(path, appName); + await createJestConfigFile(path, appName); + await createJestTsconfigJsonFile(path, appName); + await createJestPreprocessFile(path, appName); + await createEnzymeFile(path, appName); + await createComponentTests(path, appName, components); +} + +export default createTestSuite; diff --git a/app/src/utils/exportProject.util.ts b/app/src/utils/exportProject.util.ts index 76a8dc76c..cb773afb6 100644 --- a/app/src/utils/exportProject.util.ts +++ b/app/src/utils/exportProject.util.ts @@ -1,36 +1,36 @@ -import createApplicationUtil from './createApplication.util'; -import createNextApp from './createNextApp.util'; -import createFiles from './createFiles.util'; -import createGatsbyApp from './createGatsbyApp.util'; - -// When a user clicks the "Export project" function from the app, this function is invoked -const exportProject = ( - path: string, - appName: string, - genOption: number, - projectType: string, - components: any, - rootComponents: number[], - tests?: boolean -) => { - // Create fully functional classic react application - if (genOption === 1 && projectType === 'Classic React') { - createApplicationUtil({ path, appName, components, testchecked: tests }).catch(err => - console.log(err) - ); - } // export all component files, but don't create all application files - else if (genOption === 0) { - createFiles(components, path, appName, false); - } // Create fully functional Next.js and Gatsby.js application files - else if (genOption === 1 && projectType === 'Next.js') { - createNextApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => - console.log(err) - ); - } else if (genOption === 1 && projectType === 'Gatsby.js') { - createGatsbyApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => - console.log(err)); - } - -}; - -export default exportProject; +import createApplicationUtil from './createApplication.util'; +import createNextApp from './createNextApp.util'; +import createFiles from './createFiles.util'; +import createGatsbyApp from './createGatsbyApp.util'; + +// When a user clicks the "Export project" function from the app, this function is invoked +const exportProject = ( + path: string, + appName: string, + genOption: number, + projectType: string, + components: any, + rootComponents: number[], + tests?: boolean +) => { + // Create fully functional classic react application + if (genOption === 1 && projectType === 'Classic React') { + createApplicationUtil({ path, appName, components, testchecked: tests }).catch(err => + console.log(err) + ); + } // export all component files, but don't create all application files + else if (genOption === 0) { + createFiles(components, path, appName, false); + } // Create fully functional Next.js and Gatsby.js application files + else if (genOption === 1 && projectType === 'Next.js') { + createNextApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => + console.log(err) + ); + } else if (genOption === 1 && projectType === 'Gatsby.js') { + createGatsbyApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => + console.log(err)); + } + +}; + +export default exportProject; diff --git a/config.js b/config.js index 463d1814b..751a15e35 100644 --- a/config.js +++ b/config.js @@ -1,14 +1,12 @@ -const isProduction = process.env.NODE_ENV === 'production'; -const config = { - DEV_PORT: 5656, - API_BASE_URL: isProduction - ? 'https://app.reactype.dev' - : 'http://localhost:5656', - // : 'http://localhost:8080', - API_BASE_URL2: isProduction - ? 'https://app.reactype.dev' - : 'http://localhost:8080' -}; -module.exports = config; - -// export default config; +const isProduction = process.env.NODE_ENV === 'production'; +const config = { + DEV_PORT: 5656, + API_BASE_URL: isProduction + ? 'https://app.reactype.dev' + : 'http://localhost:5656', + // : 'http://localhost:8080', + API_BASE_URL2: isProduction + ? 'https://app.reactype.dev' + : 'http://localhost:8080', +}; +module.exports = config; diff --git a/contributors.md b/contributors.md index 31d2a83e0..6a2e8fe60 100644 --- a/contributors.md +++ b/contributors.md @@ -1,92 +1,92 @@ -| Name | LinkedIn | GitHub | -|---------------------|-------------------------------------------------------------|----------------------------------------------------| -| Aaron Bumanglag | [LinkedIn](https://linkedin.com/in/akbuma) | [GitHub](https://github.com/akbuma) | -| Adam Singer | [LinkedIn](https://linkedin.com/in/adsing) | [GitHub](https://github.com/spincycle01) | -| Adam Vanek | [LinkedIn](https://www.linkedin.com/in/atvanek) | [GitHub](https://github.com/atvanek) | -| Abeer Faizan | [LinkedIn](https://www.linkedin.com/in/abeerfaizan) | [GitHub](https://github.com/abeer-f) | -| Ahnaf Khan | [LinkedIn](https://www.linkedin.com/in/ahnaf-khan-844a70193) | [GitHub](https://github.com/AhnafKhvn) | -| Alex Wolinsky | [LinkedIn](https://www.linkedin.com/in/alex-wolinsky-80ab591b2/) | [GitHub](https://github.com/aw2934/) | -| Alex Yu | [LinkedIn](https://www.linkedin.com/in/alexjihunyu/) | [GitHub](https://github.com/buddhajjigae) | -| Andrew Cho | [LinkedIn](https://www.linkedin.com/in/andrewjcho84/) | [GitHub](https://github.com/andrewjcho84) | -| Anthony Torrero | [LinkedIn](https://www.linkedin.com/in/anthony-torrero-4b8798159/) | [GitHub](https://github.com/Anthonytorrero) | -| Ben Cauffman | [LinkedIn](https://www.linkedin.com/in/benjamin-cauffman/) | [GitHub](https://github.com/BenCauffman) | -| Bianca Picasso | [LinkedIn](https://www.linkedin.com/in/bianca-picasso) | [GitHub](https://github.com/BiancaPicasso) | -| Brett Webster | [LinkedIn](https://www.linkedin.com/in/brett-webster-cfa-383b961) | [GitHub](https://github.com/brett-webster) | -| Brian Han | [LinkedIn](https://www.linkedin.com/in/brianjisoohan/) | [GitHub](https://github.com/brianjshan) | -| Brian Yan | [LinkedIn](https://www.linkedin.com/in/brianyan7/) | [GitHub](https://github.com/BrianYanGitHub) | -| Bryan Chau | [LinkedIn](https://www.linkedin.com/in/chaubryan1/) | [GitHub](https://github.com/bchauu) | -| Calvin Cao | [LinkedIn](http://www.linkedin.com/in/calvincao9/) | [GitHub](https://github.com/calvincao) | -| Carly Jackson | [LinkedIn](https://www.linkedin.com/in/carly-jackson-ab9010231/) | [GitHub](https://github.com/carlyjackson) | -| Charles Finocchiaro | [LinkedIn](https://www.linkedin.com/in/charles-finocchiaro-62440040/) | [GitHub](https://github.com/null267) | -| Chelsey Fewer | [LinkedIn](https://www.linkedin.com/in/chelsey-fewer/) | [GitHub](https://github.com/chelseyeslehc) | -| Chris Tang | [LinkedIn](https://www.linkedin.com/in/chrisjtang/) | [GitHub](https://github.com/chrisjtang) | -| Christian Padilla | [LinkedIn](https://linkedin.com/in/ChristianEdwardPadilla) | [GitHub](https://github.com/ChristianEdwardPadilla) | -| Crystal Lim | [LinkedIn](https://linkedin.com/in/crystallim) | [GitHub](https://github.com/crlim) | -| Cyrus Burns | [LinkedIn](https://www.linkedin.com/in/cyburns/) | [GitHub](https://github.com/cyburns) | -| Danial Reilley | [LinkedIn](https://linkedin.com/in/daniel-reilley) | [GitHub](https://github.com/dreille) | -| Darin Ngau | [LinkedIn](https://www.linkedin.com/in/darin-ngau/) | [GitHub](https://github.com/dnngau) | -| Daryl Foster | [LinkedIn](https://www.linkedin.com/in/darylfosterma/) | [GitHub](https://github.com/MadinventorZero) | -| Denton Wong | [LinkedIn](https://www.linkedin.com/in/denton-wong/) | [GitHub](https://github.com/dentonwong) | -| Diego Vazquez | [LinkedIn](https://www.linkedin.com/in/diegovazquezny/) | [GitHub](https://github.com/diegovazquezny) | -| Edward Park | [LinkedIn](https://www.linkedin.com/in/edwardparkwork/) | [GitHub](https://github.com/eddypjr) | -| Elena Conn | [LinkedIn](https://www.linkedin.com/in/elena-conn-366346123/) | [GitHub](https://github.com/elenaconn) | -| Eliot Nguyen | [LinkedIn](https://linkedin.com/in/ibeeliot) | [GitHub](https://github.com/ibeeliot) | -| Evan Crews | [LinkedIn](https://www.linkedin.com/in/evan-crews/) | [GitHub](https://github.com/Evan-Crews) | -| Fredo Chen | [LinkedIn](https://www.linkedin.com/in/fredochen/) | [GitHub](https://github.com/fredosauce) | -| Garrett Huston | [LinkedIn](https://www.linkedin.com/in/garrett-hutson/) | [GitHub](https://github.com/GarrettHutson) | -| Hadrian Chan | [LinkedIn](https://www.linkedin.com/in/hadrian-chan-445a8622a) | [GitHub](https://github.com/HadriChan) | -| Hernan Damazo | [LinkedIn](https://www.linkedin.com/in/raul-hernan-damazo-chang-9440ab191/) | [GitHub](https://github.com/raulclassico7) | -| Huy Pham | [LinkedIn](https://www.linkedin.com/in/huypham048) | [GitHub](https://github.com/huypham048) | -| Ian Davis | [LinkedIn](https://www.linkedin.com/in/icdavis/) | [GitHub](https://github.com/iancdavis) | -| Jesse Zuniga | [LinkedIn](https://linkedin.com/in/jesse-zuniga) | [GitHub](https://github.com/jzuniga206) | -| Jin Soo Lim | [LinkedIn](https://www.linkedin.com/in/jin-soo-lim-3a567b1b3/) | [GitHub](https://github.com/jinsoolim) | -| Jon Wage | [LinkedIn](http://linkedin.com/in/johnwage) | [GitHub](http://github.com/johnwage) | -| Jonathan Calvo Ramirez | [LinkedIn](https://www.linkedin.com/in/jonathan-calvo/) | [GitHub](https://github.com/jonocr) | -| Julie Wu | [LinkedIn](https://www.linkedin.com/in/jwuarchitect/) | [GitHub](https://github.com/yutingwu4) | -| Katrina Henderson | [LinkedIn](https://www.linkedin.com/in/katrinahenderson/) | [GitHub](https://github.com/kchender) | -| Ken Bains | [LinkedIn](https://www.linkedin.com/in/ken-bains) | [GitHub](https://github.com/ken-Bains) | -| Kevin Park | [LinkedIn](https://www.linkedin.com/in/xkevinpark/) | [GitHub](https://github.com/xkevinpark) | -| Khuong Nguyen | [LinkedIn](https://www.linkedin.com/in/khuong-nguyen/) | [GitHub](https://github.com/khuongdn16) | -| Laura Forden | [LinkedIn](https://www.linkedin.com/in/la-forden/) | [GitHub](https://github.com/lauraafor) | -| Lauren Leer | [LinkedIn](https://www.linkedin.com/in/lauren-leer/) | [GitHub](https://github.com/LALeer) | -| Liam Roh | [LinkedIn](https://www.linkedin.com/in/liam-roh/) | [GitHub](https://github.com/liamroh) | -| Lillian Wimberly | [LinkedIn](https://www.linkedin.com/in/lillianwimberly/) | [GitHub](https://github.com/lillwimberly) | -| Linh Tran | [LinkedIn](https://www.linkedin.com/in/linhtran51/) | [GitHub](https://github.com/Linhatran) | -| Luke Madden | [LinkedIn](https://www.linkedin.com/in/lukemadden/) | [GitHub](https://github.com/lukemadden) | -| Matteo Diterlizzi | [LinkedIn](https://www.linkedin.com/in/matteo-diterlizzi-564166107/) | [GitHub](https://github.com/MatteoDiter) | -| Michael Ng | [LinkedIn](https://www.linkedin.com/in/michaelng2/) | [GitHub](https://github.com/MikoGome) | -| Mike Dunnmon | [LinkedIn](https://www.linkedin.com/in/michaeldunnmon/) | [GitHub](https://github.com/mdunnmon) | -| Miles Wright | [LinkedIn](https://www.linkedin.com/in/miles-m-wright) | [GitHub](https://github.com/Miles818) | -| Mitchel Severe | [LinkedIn](https://www.linkedin.com/in/misevere/) | [GitHub](https://github.com/mitchelsevere) | -| Nam Ha | [LinkedIn](https://www.linkedin.com/in/namos2502) | [GitHub](https://github.com/namos2502) | -| Natalie Vick | [LinkedIn](https://www.linkedin.com/in/vicknatalie/) | [GitHub](https://github.com/natattackvick) | -| Nel Malikova | [LinkedIn](https://www.linkedin.com/in/gmalikova/) | [GitHub](https://github.com/gmal1) | -| Philip Hua | [LinkedIn](https://www.linkedin.com/in/philip-minh-hua) | [GitHub](https://github.com/pmhua) | -| Rachel Kucharski | [LinkedIn](https://www.linkedin.com/in/rachelkucharski/) | [GitHub](https://github.com/rachelk585) | -| Rick McGrath | [LinkedIn](https://www.linkedin.com/in/rick-mcgrath-b1617126b) | [GitHub](https://github.com/r-mcgrath) | -| Ron Fu | [LinkedIn](https://www.linkedin.com/in/ronfu) | [GitHub](https://github.com/rfvisuals) | -| Rose Jiang | [LinkedIn](https://www.linkedin.com/in/rose-jiang/) | [GitHub](https://github.com/jujupro) | -| Salvatore Saluga | [LinkedIn](https://www.linkedin.com/in/salvatore-saluga) | [GitHub](https://github.com/SalSaluga) | -| Sang-Hoon (Sean) Kil | [LinkedIn](https://www.linkedin.com/in/sanghkil/) | [GitHub](https://github.com/Skilzsz) | -| Sean Sadykoff | [LinkedIn](https://www.linkedin.com/in/sean-sadykoff/) | [GitHub](https://github.com/sean1292) | -| Shana Hoehn | [LinkedIn](https://www.linkedin.com/in/shana-hoehn-70297b169/) | [GitHub](https://github.com/slhoehn) | -| Shirley Liu | [LinkedIn](https://www.linkedin.com/in/yijunliu/) | [GitHub](https://github.com/yijunliu90) | -| Shlomo Porges | [LinkedIn](https://linkedin.com/shlomoporges) | [GitHub](https://github.com/ShlomoPorges) | -| Sonya Hu | [LinkedIn](https://www.linkedin.com/in/sonyahu25) | [GitHub](https://github.com/sonyahu15) | -| Sophia Bui | [LinkedIn](https://linkedin.com/in/sophiabui) | [GitHub](https://github.com/sophia-bui) | -| Sophia Huttner | [LinkedIn](https://www.linkedin.com/in/sophia-huttner-68315975/) | [GitHub](https://github.com/sophjean) | -| Stephen Kim | [LinkedIn](https://www.linkedin.com/in/stephenkim612/) | [GitHub](https://github.com/stephenkim612) | -| Stormi Hashimoto | [LinkedIn](https://www.linkedin.com/in/stormikph/) | [GitHub](https://github.com/stormikph) | -| Thomas Lukasiewicz | [LinkedIn](https://www.linkedin.com/in/thomas-lukasiewicz-27676273/) | [GitHub](https://github.com/tlukasiewicz89) | -| Tolga Mizrakci | [LinkedIn](https://linkedin.com/in/tolga-mizrakci) | [GitHub](https://github.com/tolgamizrakci) | -| Tony Ito-Cole | [LinkedIn](https://linkedin.com/in/tony-ito-cole) | [GitHub](https://github.com/tonyito) | -| Tyler Sullberg | [LinkedIn](https://www.linkedin.com/in/tyler-sullberg) | [GitHub](https://github.com/tsully) | -| Ulrich Neujahr | [LinkedIn](https://www.linkedin.com/in/nobrackets/) | [GitHub](https://github.com/nobrackets) | -| Victor Martins | [LinkedIn](https://www.linkedin.com/in/victor-martins-542611186/) | [GitHub](https://github.com/martins5225) | -| William Cheng | [LinkedIn](https://www.linkedin.com/in/william-cheng-0723/) | [GitHub](https://github.com/WilliamCheng12345) | -| William Rittwage | [LinkedIn](https://www.linkedin.com/in/william-rittwage) | [GitHub](https://github.com/wbrittwage) | -| William Yoon | [LinkedIn](https://www.linkedin.com/in/williamdyoon/) | [GitHub](https://github.com/williamdyoon) | -| Xiao Wang | [LinkedIn](https://www.linkedin.com/in/xiao-wang-03183285/) | [GitHub](https://github.com/wang9hu) | -| Yameng Zhang | [LinkedIn](https://www.linkedin.com/in/yameng-zhang612/) | [GitHub](https://github.com/Eliza612) | -| Yohan Jeon | [LinkedIn](https://www.linkedin.com/in/yohan-jeon1) | [GitHub](https://github.com/Yoheze) | -| Yuanji Huang | [LinkedIn](https://www.linkedin.com/in/yuanjihuang/) | [GitHub](https://github.com/kr1spybacon) | +| Name | LinkedIn | GitHub | +|---------------------|-------------------------------------------------------------|----------------------------------------------------| +| Aaron Bumanglag | [LinkedIn](https://linkedin.com/in/akbuma) | [GitHub](https://github.com/akbuma) | +| Adam Singer | [LinkedIn](https://linkedin.com/in/adsing) | [GitHub](https://github.com/spincycle01) | +| Adam Vanek | [LinkedIn](https://www.linkedin.com/in/atvanek) | [GitHub](https://github.com/atvanek) | +| Abeer Faizan | [LinkedIn](https://www.linkedin.com/in/abeerfaizan) | [GitHub](https://github.com/abeer-f) | +| Ahnaf Khan | [LinkedIn](https://www.linkedin.com/in/ahnaf-khan-844a70193) | [GitHub](https://github.com/AhnafKhvn) | +| Alex Wolinsky | [LinkedIn](https://www.linkedin.com/in/alex-wolinsky-80ab591b2/) | [GitHub](https://github.com/aw2934/) | +| Alex Yu | [LinkedIn](https://www.linkedin.com/in/alexjihunyu/) | [GitHub](https://github.com/buddhajjigae) | +| Andrew Cho | [LinkedIn](https://www.linkedin.com/in/andrewjcho84/) | [GitHub](https://github.com/andrewjcho84) | +| Anthony Torrero | [LinkedIn](https://www.linkedin.com/in/anthony-torrero-4b8798159/) | [GitHub](https://github.com/Anthonytorrero) | +| Ben Cauffman | [LinkedIn](https://www.linkedin.com/in/benjamin-cauffman/) | [GitHub](https://github.com/BenCauffman) | +| Bianca Picasso | [LinkedIn](https://www.linkedin.com/in/bianca-picasso) | [GitHub](https://github.com/BiancaPicasso) | +| Brett Webster | [LinkedIn](https://www.linkedin.com/in/brett-webster-cfa-383b961) | [GitHub](https://github.com/brett-webster) | +| Brian Han | [LinkedIn](https://www.linkedin.com/in/brianjisoohan/) | [GitHub](https://github.com/brianjshan) | +| Brian Yan | [LinkedIn](https://www.linkedin.com/in/brianyan7/) | [GitHub](https://github.com/BrianYanGitHub) | +| Bryan Chau | [LinkedIn](https://www.linkedin.com/in/chaubryan1/) | [GitHub](https://github.com/bchauu) | +| Calvin Cao | [LinkedIn](http://www.linkedin.com/in/calvincao9/) | [GitHub](https://github.com/calvincao) | +| Carly Jackson | [LinkedIn](https://www.linkedin.com/in/carly-jackson-ab9010231/) | [GitHub](https://github.com/carlyjackson) | +| Charles Finocchiaro | [LinkedIn](https://www.linkedin.com/in/charles-finocchiaro-62440040/) | [GitHub](https://github.com/null267) | +| Chelsey Fewer | [LinkedIn](https://www.linkedin.com/in/chelsey-fewer/) | [GitHub](https://github.com/chelseyeslehc) | +| Chris Tang | [LinkedIn](https://www.linkedin.com/in/chrisjtang/) | [GitHub](https://github.com/chrisjtang) | +| Christian Padilla | [LinkedIn](https://linkedin.com/in/ChristianEdwardPadilla) | [GitHub](https://github.com/ChristianEdwardPadilla) | +| Crystal Lim | [LinkedIn](https://linkedin.com/in/crystallim) | [GitHub](https://github.com/crlim) | +| Cyrus Burns | [LinkedIn](https://www.linkedin.com/in/cyburns/) | [GitHub](https://github.com/cyburns) | +| Danial Reilley | [LinkedIn](https://linkedin.com/in/daniel-reilley) | [GitHub](https://github.com/dreille) | +| Darin Ngau | [LinkedIn](https://www.linkedin.com/in/darin-ngau/) | [GitHub](https://github.com/dnngau) | +| Daryl Foster | [LinkedIn](https://www.linkedin.com/in/darylfosterma/) | [GitHub](https://github.com/MadinventorZero) | +| Denton Wong | [LinkedIn](https://www.linkedin.com/in/denton-wong/) | [GitHub](https://github.com/dentonwong) | +| Diego Vazquez | [LinkedIn](https://www.linkedin.com/in/diegovazquezny/) | [GitHub](https://github.com/diegovazquezny) | +| Edward Park | [LinkedIn](https://www.linkedin.com/in/edwardparkwork/) | [GitHub](https://github.com/eddypjr) | +| Elena Conn | [LinkedIn](https://www.linkedin.com/in/elena-conn-366346123/) | [GitHub](https://github.com/elenaconn) | +| Eliot Nguyen | [LinkedIn](https://linkedin.com/in/ibeeliot) | [GitHub](https://github.com/ibeeliot) | +| Evan Crews | [LinkedIn](https://www.linkedin.com/in/evan-crews/) | [GitHub](https://github.com/Evan-Crews) | +| Fredo Chen | [LinkedIn](https://www.linkedin.com/in/fredochen/) | [GitHub](https://github.com/fredosauce) | +| Garrett Huston | [LinkedIn](https://www.linkedin.com/in/garrett-hutson/) | [GitHub](https://github.com/GarrettHutson) | +| Hadrian Chan | [LinkedIn](https://www.linkedin.com/in/hadrian-chan-445a8622a) | [GitHub](https://github.com/HadriChan) | +| Hernan Damazo | [LinkedIn](https://www.linkedin.com/in/raul-hernan-damazo-chang-9440ab191/) | [GitHub](https://github.com/raulclassico7) | +| Huy Pham | [LinkedIn](https://www.linkedin.com/in/huypham048) | [GitHub](https://github.com/huypham048) | +| Ian Davis | [LinkedIn](https://www.linkedin.com/in/icdavis/) | [GitHub](https://github.com/iancdavis) | +| Jesse Zuniga | [LinkedIn](https://linkedin.com/in/jesse-zuniga) | [GitHub](https://github.com/jzuniga206) | +| Jin Soo Lim | [LinkedIn](https://www.linkedin.com/in/jin-soo-lim-3a567b1b3/) | [GitHub](https://github.com/jinsoolim) | +| Jon Wage | [LinkedIn](http://linkedin.com/in/johnwage) | [GitHub](http://github.com/johnwage) | +| Jonathan Calvo Ramirez | [LinkedIn](https://www.linkedin.com/in/jonathan-calvo/) | [GitHub](https://github.com/jonocr) | +| Julie Wu | [LinkedIn](https://www.linkedin.com/in/jwuarchitect/) | [GitHub](https://github.com/yutingwu4) | +| Katrina Henderson | [LinkedIn](https://www.linkedin.com/in/katrinahenderson/) | [GitHub](https://github.com/kchender) | +| Ken Bains | [LinkedIn](https://www.linkedin.com/in/ken-bains) | [GitHub](https://github.com/ken-Bains) | +| Kevin Park | [LinkedIn](https://www.linkedin.com/in/xkevinpark/) | [GitHub](https://github.com/xkevinpark) | +| Khuong Nguyen | [LinkedIn](https://www.linkedin.com/in/khuong-nguyen/) | [GitHub](https://github.com/khuongdn16) | +| Laura Forden | [LinkedIn](https://www.linkedin.com/in/la-forden/) | [GitHub](https://github.com/lauraafor) | +| Lauren Leer | [LinkedIn](https://www.linkedin.com/in/lauren-leer/) | [GitHub](https://github.com/LALeer) | +| Liam Roh | [LinkedIn](https://www.linkedin.com/in/liam-roh/) | [GitHub](https://github.com/liamroh) | +| Lillian Wimberly | [LinkedIn](https://www.linkedin.com/in/lillianwimberly/) | [GitHub](https://github.com/lillwimberly) | +| Linh Tran | [LinkedIn](https://www.linkedin.com/in/linhtran51/) | [GitHub](https://github.com/Linhatran) | +| Luke Madden | [LinkedIn](https://www.linkedin.com/in/lukemadden/) | [GitHub](https://github.com/lukemadden) | +| Matteo Diterlizzi | [LinkedIn](https://www.linkedin.com/in/matteo-diterlizzi-564166107/) | [GitHub](https://github.com/MatteoDiter) | +| Michael Ng | [LinkedIn](https://www.linkedin.com/in/michaelng2/) | [GitHub](https://github.com/MikoGome) | +| Mike Dunnmon | [LinkedIn](https://www.linkedin.com/in/michaeldunnmon/) | [GitHub](https://github.com/mdunnmon) | +| Miles Wright | [LinkedIn](https://www.linkedin.com/in/miles-m-wright) | [GitHub](https://github.com/Miles818) | +| Mitchel Severe | [LinkedIn](https://www.linkedin.com/in/misevere/) | [GitHub](https://github.com/mitchelsevere) | +| Nam Ha | [LinkedIn](https://www.linkedin.com/in/namos2502) | [GitHub](https://github.com/namos2502) | +| Natalie Vick | [LinkedIn](https://www.linkedin.com/in/vicknatalie/) | [GitHub](https://github.com/natattackvick) | +| Nel Malikova | [LinkedIn](https://www.linkedin.com/in/gmalikova/) | [GitHub](https://github.com/gmal1) | +| Philip Hua | [LinkedIn](https://www.linkedin.com/in/philip-minh-hua) | [GitHub](https://github.com/pmhua) | +| Rachel Kucharski | [LinkedIn](https://www.linkedin.com/in/rachelkucharski/) | [GitHub](https://github.com/rachelk585) | +| Rick McGrath | [LinkedIn](https://www.linkedin.com/in/rick-mcgrath-b1617126b) | [GitHub](https://github.com/r-mcgrath) | +| Ron Fu | [LinkedIn](https://www.linkedin.com/in/ronfu) | [GitHub](https://github.com/rfvisuals) | +| Rose Jiang | [LinkedIn](https://www.linkedin.com/in/rose-jiang/) | [GitHub](https://github.com/jujupro) | +| Salvatore Saluga | [LinkedIn](https://www.linkedin.com/in/salvatore-saluga) | [GitHub](https://github.com/SalSaluga) | +| Sang-Hoon (Sean) Kil | [LinkedIn](https://www.linkedin.com/in/sanghkil/) | [GitHub](https://github.com/Skilzsz) | +| Sean Sadykoff | [LinkedIn](https://www.linkedin.com/in/sean-sadykoff/) | [GitHub](https://github.com/sean1292) | +| Shana Hoehn | [LinkedIn](https://www.linkedin.com/in/shana-hoehn-70297b169/) | [GitHub](https://github.com/slhoehn) | +| Shirley Liu | [LinkedIn](https://www.linkedin.com/in/yijunliu/) | [GitHub](https://github.com/yijunliu90) | +| Shlomo Porges | [LinkedIn](https://linkedin.com/shlomoporges) | [GitHub](https://github.com/ShlomoPorges) | +| Sonya Hu | [LinkedIn](https://www.linkedin.com/in/sonyahu25) | [GitHub](https://github.com/sonyahu15) | +| Sophia Bui | [LinkedIn](https://linkedin.com/in/sophiabui) | [GitHub](https://github.com/sophia-bui) | +| Sophia Huttner | [LinkedIn](https://www.linkedin.com/in/sophia-huttner-68315975/) | [GitHub](https://github.com/sophjean) | +| Stephen Kim | [LinkedIn](https://www.linkedin.com/in/stephenkim612/) | [GitHub](https://github.com/stephenkim612) | +| Stormi Hashimoto | [LinkedIn](https://www.linkedin.com/in/stormikph/) | [GitHub](https://github.com/stormikph) | +| Thomas Lukasiewicz | [LinkedIn](https://www.linkedin.com/in/thomas-lukasiewicz-27676273/) | [GitHub](https://github.com/tlukasiewicz89) | +| Tolga Mizrakci | [LinkedIn](https://linkedin.com/in/tolga-mizrakci) | [GitHub](https://github.com/tolgamizrakci) | +| Tony Ito-Cole | [LinkedIn](https://linkedin.com/in/tony-ito-cole) | [GitHub](https://github.com/tonyito) | +| Tyler Sullberg | [LinkedIn](https://www.linkedin.com/in/tyler-sullberg) | [GitHub](https://github.com/tsully) | +| Ulrich Neujahr | [LinkedIn](https://www.linkedin.com/in/nobrackets/) | [GitHub](https://github.com/nobrackets) | +| Victor Martins | [LinkedIn](https://www.linkedin.com/in/victor-martins-542611186/) | [GitHub](https://github.com/martins5225) | +| William Cheng | [LinkedIn](https://www.linkedin.com/in/william-cheng-0723/) | [GitHub](https://github.com/WilliamCheng12345) | +| William Rittwage | [LinkedIn](https://www.linkedin.com/in/william-rittwage) | [GitHub](https://github.com/wbrittwage) | +| William Yoon | [LinkedIn](https://www.linkedin.com/in/williamdyoon/) | [GitHub](https://github.com/williamdyoon) | +| Xiao Wang | [LinkedIn](https://www.linkedin.com/in/xiao-wang-03183285/) | [GitHub](https://github.com/wang9hu) | +| Yameng Zhang | [LinkedIn](https://www.linkedin.com/in/yameng-zhang612/) | [GitHub](https://github.com/Eliza612) | +| Yohan Jeon | [LinkedIn](https://www.linkedin.com/in/yohan-jeon1) | [GitHub](https://github.com/Yoheze) | +| Yuanji Huang | [LinkedIn](https://www.linkedin.com/in/yuanjihuang/) | [GitHub](https://github.com/kr1spybacon) | diff --git a/electron-builder.yml b/electron-builder.yml index d421ce48e..914c5d6ee 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,20 +1,20 @@ -appId: com.electron.reactype -copyright: Copyright © 2023 -productName: ReacType -directories: - buildResources: resources -win: - target: - - nsis - - msi -linux: - target: - - deb - - rpm - - snap - - AppImage - maintainer: reactype.io@gmail.com -mac: - category: public.app-category.developer-tools - target: dmg - icon: resources/icon.icns +appId: com.electron.reactype +copyright: Copyright © 2023 +productName: ReacType +directories: + buildResources: resources +win: + target: + - nsis + - msi +linux: + target: + - deb + - rpm + - snap + - AppImage + maintainer: reactype.io@gmail.com +mac: + category: public.app-category.developer-tools + target: dmg + icon: resources/icon.icns diff --git a/index.html b/index.html index 3980b49a8..30ccbbd71 100644 --- a/index.html +++ b/index.html @@ -1,20 +1,20 @@ - - - - - - ReacType - - - - - -
    - - - - + + + + + + ReacType + + + + + +
    + + + + diff --git a/mockData.ts b/mockData.ts index 9a7c18a92..c36fd0182 100644 --- a/mockData.ts +++ b/mockData.ts @@ -1,114 +1,114 @@ -import HTMLTypes from "./app/src/redux/HTMLTypes"; - -const mockObj = { - //these user credentials were created via the signup page - //once connecting to new mongo cluster, you must create new login credentials to test - user: { - username: 'test', - email: 'test@gmail.com', - password: 'password1!', - userId: '64f551e5b28d5292975e08c8' - }, - - state: { - name: 'test', - isLoggedIn: false, - components: [ - { - id: 1, - name: 'index', - style: {}, - code: '
    Drag in a component or HTML element into the canvas!
    ', - children: [] - } - ], - projectType: 'Next.js', - rootComponents: [1], - canvasFocus: { componentId: 1, childId: null }, - nextComponentId: 2, - nextChildId: 1, - nextTopSeparatorId: 1000, - HTMLTypes: HTMLTypes, // left as is for now - tailwind: false, - stylesheet: '', - codePreview: false, - }, - - projectToSave: { - _id: '', - name: 'super test project', - userId: '64f551e5b28d5292975e08c8', - username: 'test', - forked: false, - published: false, - isLoggedIn: false, - project: { - name: 'test', - isLoggedIn: false, - components: [ - { - id: 1, - name: 'index', - style: {}, - code: '
    Drag in a component or HTML element into the canvas!
    ', - children: [] - } - ], - projectType: 'Next.js', - rootComponents: [1], - canvasFocus: { componentId: 1, childId: null }, - nextComponentId: 2, - nextChildId: 1, - nextTopSeparatorId: 1000, - HTMLTypes: HTMLTypes, // left as is for now - tailwind: false, - stylesheet: '', - codePreview: false, - } - }, -//The following is for graphQL - GET_PROJECTS: `query GetAllProjects($userId: ID) { - getAllProjects(userId: $userId) { - name - likes - id - userId - username - published - } - }`, - - ADD_LIKE: `mutation AddLike($projId: ID!, $likes: Int!) { - addLike(projId: $projId, likes: $likes) - { - id - likes - } - }`, - - PUBLISH_PROJECT: `mutation Publish($projId: ID!, $published: Boolean!) { - publishProject(projId: $projId, published: $published) - { - id - published - } - }`, - - MAKE_COPY: `mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { - makeCopy(userId: $userId, projId: $projId, username: $username) - { - id - userId - username - } - }`, - - DELETE_PROJECT: `mutation DeleteProject($projId: ID!) { - deleteProject(projId: $projId) - { - id - } - }` -}; - -export default mockObj; +import HTMLTypes from "./app/src/redux/HTMLTypes"; + +const mockObj = { + //these user credentials were created via the signup page + //once connecting to new mongo cluster, you must create new login credentials to test + user: { + username: 'test', + email: 'test@gmail.com', + password: 'password1!', + userId: '64f551e5b28d5292975e08c8' + }, + + state: { + name: 'test', + isLoggedIn: false, + components: [ + { + id: 1, + name: 'index', + style: {}, + code: '
    Drag in a component or HTML element into the canvas!
    ', + children: [] + } + ], + projectType: 'Next.js', + rootComponents: [1], + canvasFocus: { componentId: 1, childId: null }, + nextComponentId: 2, + nextChildId: 1, + nextTopSeparatorId: 1000, + HTMLTypes: HTMLTypes, // left as is for now + tailwind: false, + stylesheet: '', + codePreview: false, + }, + + projectToSave: { + _id: '', + name: 'super test project', + userId: '64f551e5b28d5292975e08c8', + username: 'test', + forked: false, + published: false, + isLoggedIn: false, + project: { + name: 'test', + isLoggedIn: false, + components: [ + { + id: 1, + name: 'index', + style: {}, + code: '
    Drag in a component or HTML element into the canvas!
    ', + children: [] + } + ], + projectType: 'Next.js', + rootComponents: [1], + canvasFocus: { componentId: 1, childId: null }, + nextComponentId: 2, + nextChildId: 1, + nextTopSeparatorId: 1000, + HTMLTypes: HTMLTypes, // left as is for now + tailwind: false, + stylesheet: '', + codePreview: false, + } + }, +//The following is for graphQL + GET_PROJECTS: `query GetAllProjects($userId: ID) { + getAllProjects(userId: $userId) { + name + likes + id + userId + username + published + } + }`, + + ADD_LIKE: `mutation AddLike($projId: ID!, $likes: Int!) { + addLike(projId: $projId, likes: $likes) + { + id + likes + } + }`, + + PUBLISH_PROJECT: `mutation Publish($projId: ID!, $published: Boolean!) { + publishProject(projId: $projId, published: $published) + { + id + published + } + }`, + + MAKE_COPY: `mutation MakeCopy ($userId: ID!, $projId: ID!, $username: String!) { + makeCopy(userId: $userId, projId: $projId, username: $username) + { + id + userId + username + } + }`, + + DELETE_PROJECT: `mutation DeleteProject($projId: ID!) { + deleteProject(projId: $projId) + { + id + } + }` +}; + +export default mockObj; diff --git a/package-lock.json b/package-lock.json index d87fdd9af..94f118dbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@aws-amplify/core": "^5.8.4", "@babel/cli": "^7.23.4", "@babel/register": "^7.22.15", + "@electron/remote": "^2.1.2", "@graphql-tools/schema": "^9.0.19", "@mui/icons-material": "^5.15.7", "@mui/lab": "^5.0.0-alpha.93", @@ -54,7 +55,6 @@ "html2canvas": "^1.4.1", "identity-obj-proxy": "^3.0.0", "js-cookie": "^3.0.5", - "jsdoc": "^4.0.2", "jszip": "^3.10.1", "localforage": "^1.10.0", "lodash": "^4.17.21", @@ -104,6 +104,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^12.1.5", "@types/chai": "^4.3.11", + "@types/electron": "^1.6.10", "@types/jest": "^28.1.8", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", @@ -9231,7 +9232,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", - "dev": true, "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", @@ -9366,6 +9366,14 @@ "node": ">= 10.0.0" } }, + "node_modules/@electron/remote": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@electron/remote/-/remote-2.1.2.tgz", + "integrity": "sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==", + "peerDependencies": { + "electron": ">= 13.0.0" + } + }, "node_modules/@electron/universal": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.4.1.tgz", @@ -13888,7 +13896,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, "engines": { "node": ">=10" }, @@ -14215,7 +14222,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -14481,7 +14487,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", @@ -14544,6 +14549,16 @@ "@types/ms": "*" } }, + "node_modules/@types/electron": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@types/electron/-/electron-1.6.10.tgz", + "integrity": "sha512-MOCVyzIwkBEloreoCVrTV108vSf8fFIJPsGruLCoAoBZdxtnJUqKA4lNonf/2u1twSjAspPEfmEheC+TLm/cMw==", + "deprecated": "This is a stub types definition for electron (https://github.com/electron/electron). electron provides its own type definitions, so you don't need @types/electron installed!", + "dev": true, + "dependencies": { + "electron": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", @@ -14633,8 +14648,7 @@ "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, "node_modules/@types/http-errors": { "version": "2.0.4", @@ -14740,7 +14754,6 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -14930,7 +14943,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -15121,7 +15133,6 @@ "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, "optional": true, "dependencies": { "@types/node": "*" @@ -18183,7 +18194,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "dev": true + "devOptional": true }, "node_modules/bowser": { "version": "2.11.0", @@ -18301,7 +18312,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "engines": { "node": "*" } @@ -18462,7 +18472,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, "engines": { "node": ">=10.6.0" } @@ -18471,7 +18480,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -18489,7 +18497,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -19135,7 +19142,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, "dependencies": { "mimic-response": "^1.0.0" }, @@ -20350,7 +20356,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -20365,7 +20370,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -20468,7 +20472,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, "engines": { "node": ">=10" } @@ -20594,7 +20597,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "devOptional": true }, "node_modules/dezalgo": { "version": "1.0.4", @@ -21020,7 +21023,6 @@ "version": "28.0.0", "resolved": "https://registry.npmjs.org/electron/-/electron-28.0.0.tgz", "integrity": "sha512-eDhnCFBvG0PGFVEpNIEdBvyuGUBsFdlokd+CtuCe2ER3P+17qxaRfWRxMmksCOKgDHb5Wif5UxqOkZSlA4snlw==", - "dev": true, "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -21338,7 +21340,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -21471,7 +21472,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "engines": { "node": ">=6" } @@ -21750,7 +21750,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "devOptional": true }, "node_modules/esbuild": { "version": "0.14.54", @@ -23095,7 +23095,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -23115,7 +23114,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -23243,7 +23241,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, "dependencies": { "pend": "~1.2.0" } @@ -23841,7 +23838,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, + "devOptional": true, "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", @@ -23858,7 +23855,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -23870,7 +23867,7 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -23893,7 +23890,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, + "devOptional": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -24004,7 +24001,6 @@ "version": "11.8.6", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", @@ -24599,8 +24595,7 @@ "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "node_modules/http-call": { "version": "5.3.0", @@ -24715,7 +24710,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" @@ -24728,7 +24722,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, "engines": { "node": ">=10" }, @@ -27379,7 +27372,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "devOptional": true }, "node_modules/json5": { "version": "2.2.3", @@ -28373,7 +28366,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, "engines": { "node": ">=8" } @@ -28502,7 +28494,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, + "devOptional": true, "dependencies": { "escape-string-regexp": "^4.0.0" }, @@ -29369,7 +29361,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, "engines": { "node": ">=4" } @@ -30053,7 +30044,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, "engines": { "node": ">=10" }, @@ -30549,7 +30539,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, "engines": { "node": ">=8" } @@ -30895,8 +30884,7 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, "node_modules/performance-now": { "version": "2.1.0", @@ -31319,7 +31307,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -31421,7 +31408,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -32444,8 +32430,7 @@ "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" }, "node_modules/resolve-cwd": { "version": "3.0.0", @@ -32494,7 +32479,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -32581,7 +32565,7 @@ "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, + "devOptional": true, "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", @@ -32598,7 +32582,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true + "devOptional": true }, "node_modules/robust-predicates": { "version": "3.0.2", @@ -32848,7 +32832,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true + "devOptional": true }, "node_modules/send": { "version": "0.18.0", @@ -32917,7 +32901,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, + "devOptional": true, "dependencies": { "type-fest": "^0.13.1" }, @@ -32932,7 +32916,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -33820,7 +33804,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dev": true, "dependencies": { "debug": "^4.1.0" }, @@ -36534,7 +36517,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/package.json b/package.json index 33d2f0d95..86e7773b3 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "@aws-amplify/core": "^5.8.4", "@babel/cli": "^7.23.4", "@babel/register": "^7.22.15", + "@electron/remote": "^2.1.2", "@graphql-tools/schema": "^9.0.19", "@mui/icons-material": "^5.15.7", "@mui/lab": "^5.0.0-alpha.93", @@ -167,7 +168,6 @@ "html2canvas": "^1.4.1", "identity-obj-proxy": "^3.0.0", "js-cookie": "^3.0.5", - "jsdoc": "^4.0.2", "jszip": "^3.10.1", "localforage": "^1.10.0", "lodash": "^4.17.21", @@ -217,6 +217,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^12.1.5", "@types/chai": "^4.3.11", + "@types/electron": "^1.6.10", "@types/jest": "^28.1.8", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", diff --git a/playwright.config.ts b/playwright.config.ts index e87e2e4db..e80c64d09 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,77 +1,77 @@ -// import { defineConfig, devices } from '@playwright/test'; - -// /** -// * Read environment variables from file. -// * https://github.com/motdotla/dotenv -// */ -// // require('dotenv').config(); - -// /** -// * See https://playwright.dev/docs/test-configuration. -// */ -// export default defineConfig({ -// testDir: './__tests__/playwright', -// /* Run tests in files in parallel */ -// fullyParallel: true, -// /* Fail the build on CI if you accidentally left test.only in the source code. */ -// forbidOnly: !!import.meta.env.CI, -// /* Retry on CI only */ -// retries: import.meta.env.CI ? 2 : 0, -// /* Opt out of parallel tests on CI. */ -// workers: import.meta.env.CI ? 1 : undefined, -// /* Reporter to use. See https://playwright.dev/docs/test-reporters */ -// reporter: 'html', -// /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ -// use: { -// /* Base URL to use in actions like `await page.goto('/')`. */ -// // baseURL: 'http://127.0.0.1:3000', - -// /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ -// trace: 'on-first-retry' -// }, - -// /* Configure projects for major browsers */ -// projects: [ -// { -// name: 'chromium', -// use: { ...devices['Desktop Chrome'] } -// }, - -// { -// name: 'firefox', -// use: { ...devices['Desktop Firefox'] } -// }, - -// { -// name: 'webkit', -// use: { ...devices['Desktop Safari'] } -// } - -// /* Test against mobile viewports. */ -// // { -// // name: 'Mobile Chrome', -// // use: { ...devices['Pixel 5'] }, -// // }, -// // { -// // name: 'Mobile Safari', -// // use: { ...devices['iPhone 12'] }, -// // }, - -// /* Test against branded browsers. */ -// // { -// // name: 'Microsoft Edge', -// // use: { ...devices['Desktop Edge'], channel: 'msedge' }, -// // }, -// // { -// // name: 'Google Chrome', -// // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, -// // }, -// ] - -// /* Run your local dev server before starting the tests */ -// // webServer: { -// // command: 'npm run start', -// // url: 'http://127.0.0.1:3000', -// // reuseExistingServer: !import.meta.env.CI, -// // }, -// }); +// import { defineConfig, devices } from '@playwright/test'; + +// /** +// * Read environment variables from file. +// * https://github.com/motdotla/dotenv +// */ +// // require('dotenv').config(); + +// /** +// * See https://playwright.dev/docs/test-configuration. +// */ +// export default defineConfig({ +// testDir: './__tests__/playwright', +// /* Run tests in files in parallel */ +// fullyParallel: true, +// /* Fail the build on CI if you accidentally left test.only in the source code. */ +// forbidOnly: !!import.meta.env.CI, +// /* Retry on CI only */ +// retries: import.meta.env.CI ? 2 : 0, +// /* Opt out of parallel tests on CI. */ +// workers: import.meta.env.CI ? 1 : undefined, +// /* Reporter to use. See https://playwright.dev/docs/test-reporters */ +// reporter: 'html', +// /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ +// use: { +// /* Base URL to use in actions like `await page.goto('/')`. */ +// // baseURL: 'http://127.0.0.1:3000', + +// /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ +// trace: 'on-first-retry' +// }, + +// /* Configure projects for major browsers */ +// projects: [ +// { +// name: 'chromium', +// use: { ...devices['Desktop Chrome'] } +// }, + +// { +// name: 'firefox', +// use: { ...devices['Desktop Firefox'] } +// }, + +// { +// name: 'webkit', +// use: { ...devices['Desktop Safari'] } +// } + +// /* Test against mobile viewports. */ +// // { +// // name: 'Mobile Chrome', +// // use: { ...devices['Pixel 5'] }, +// // }, +// // { +// // name: 'Mobile Safari', +// // use: { ...devices['iPhone 12'] }, +// // }, + +// /* Test against branded browsers. */ +// // { +// // name: 'Microsoft Edge', +// // use: { ...devices['Desktop Edge'], channel: 'msedge' }, +// // }, +// // { +// // name: 'Google Chrome', +// // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, +// // }, +// ] + +// /* Run your local dev server before starting the tests */ +// // webServer: { +// // command: 'npm run start', +// // url: 'http://127.0.0.1:3000', +// // reuseExistingServer: !import.meta.env.CI, +// // }, +// }); diff --git a/prettierrc.json b/prettierrc.json index 276a47a73..d06320fb8 100644 --- a/prettierrc.json +++ b/prettierrc.json @@ -1,5 +1,5 @@ -{ - "printWidth": 80, - "singleQuote": true, - "trailingComma": "none" -} +{ + "printWidth": 80, + "singleQuote": true, + "trailingComma": "none" +} diff --git a/server/README.md b/server/README.md index 508584524..7b803d1ee 100644 --- a/server/README.md +++ b/server/README.md @@ -1,22 +1,22 @@ -

    - -

    ReacType Server

    -

    - -**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. - -**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: - -- [Alex Yu](https://www.linkedin.com/in/alexjihunyu/) [@buddhajjigae](https://github.com/buddhajjigae) -- [Daryl Foster](https://www.linkedin.com/in/darylfosterma/) [@MadinventorZero](https://github.com/MadinventorZero) -- [Jonathan Calvo Ramirez](https://www.linkedin.com/in/jonathan-calvo/) [@jonocr](https://github.com/jonocr) -- [Kevin Park](https://www.linkedin.com/in/xkevinpark/) [@xkevinpark](https://github.com/xkevinpark) -- [William Yoon](https://www.linkedin.com/in/williamdyoon/) [@williamdyoon](https://github.com/williamdyoon) - -Redeployment should also be done with only the server subtree and not the entire repo. See this article about deploying just a subdirectory. - -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. -DEV_PORT can be defined in the config.js file on the root directory. -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. - -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. +

    + +

    ReacType Server

    +

    + +**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. + +**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: + +- [Alex Yu](https://www.linkedin.com/in/alexjihunyu/) [@buddhajjigae](https://github.com/buddhajjigae) +- [Daryl Foster](https://www.linkedin.com/in/darylfosterma/) [@MadinventorZero](https://github.com/MadinventorZero) +- [Jonathan Calvo Ramirez](https://www.linkedin.com/in/jonathan-calvo/) [@jonocr](https://github.com/jonocr) +- [Kevin Park](https://www.linkedin.com/in/xkevinpark/) [@xkevinpark](https://github.com/xkevinpark) +- [William Yoon](https://www.linkedin.com/in/williamdyoon/) [@williamdyoon](https://github.com/williamdyoon) + +Redeployment should also be done with only the server subtree and not the entire repo. See this article about deploying just a subdirectory. + +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. +DEV_PORT can be defined in the config.js file on the root directory. +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. + +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. diff --git a/server/assets/renderDemo.css b/server/assets/renderDemo.css index ed2005be0..1ee1f871e 100644 --- a/server/assets/renderDemo.css +++ b/server/assets/renderDemo.css @@ -1,181 +1,181 @@ -@import url(https://fonts.googleapis.com/css?family=Roboto:300); - -.tst-text-color { - color: #cc0707; -} -.tst-btn { - height: 20px; - width: 100px; - background-color: #000000; -} - -.tst-login-page { - width: 360px; - padding: 8% 0 0; - margin: auto; -} -.tst-form { - position: relative; - z-index: 1; - background: #0671e3; - max-width: 360px; - margin: 0 auto 100px; - padding: 45px; - text-align: center; - box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); -} -.tst-form input { - font-family: 'Roboto', sans-serif; - outline: 0; - background: #f2f2f2; - width: 100%; - border: 0; - margin: 0 0 15px; - padding: 15px; - box-sizing: border-box; - font-size: 14px; -} -.tst-form button { - font-family: 'Roboto', sans-serif; - text-transform: uppercase; - outline: 0; - background: #4caf50; - width: 100%; - border: 0; - padding: 15px; - color: #ffffff; - font-size: 14px; - -webkit-transition: all 0.3 ease; - transition: all 0.3 ease; - cursor: pointer; -} -.tst-form button:hover, -.form button:active, -.form button:focus { - background: #0671e3; -} -.tst-form .message { - margin: 15px 0 0; - color: #b3b3b3; - font-size: 12px; -} -.tst-form .message-a { - color: #313fbd; - text-decoration: none; -} -.tst-form .register-form { - display: none; -} -.tst-container { - position: relative; - z-index: 1; - max-width: 300px; - margin: 0 auto; -} -.tst-container:before, -.container:after { - content: ''; - display: block; - clear: both; -} -.tst-container .info { - margin: 50px auto; - text-align: center; -} -.tst-container .info h1 { - margin: 0 0 15px; - padding: 0; - font-size: 36px; - font-weight: 300; - color: #1a1a1a; -} -.tst-container .info span { - color: #4d4d4d; - font-size: 12px; -} -.tst-container .info span a { - color: #000000; - text-decoration: none; -} -.tst-container .info span .fa { - color: #ef3b3a; -} -.tst-container-body { - height: 100vh; - background: #76b852; /* fallback for old browsers */ - background: -webkit-linear-gradient(right, #5278b8, #2a66ce); - background: -moz-linear-gradient(right, #5278b8, #2a66ce); - background: -o-linear-gradient(right, #5278b8, #2a66ce); - background: linear-gradient(to left, #5278b8, #2a66ce); - font-family: Roboto, Raleway, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.tst-list-example .body-container { - margin: 0; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background-color: #333; -} - -.tst-list-example ul { - padding: 0; - list-style-type: none; -} - -.tst-list-example li { - font-size: 25px; - width: 8em; - height: 2em; - color: orange; - border-left: 0.08em solid; - position: relative; - margin-top: 0.8em; - cursor: pointer; -} - -.tst-list-example li::before, -.tst-list-example li::after { - content: ''; - position: absolute; - width: inherit; - border-left: inherit; - z-index: -1; -} - -.tst-list-example li::before { - height: 80%; - top: 10%; - left: calc(-0.15em - 0.08em * 2); - filter: brightness(0.8); -} - -.tst-list-example li::after { - height: 60%; - top: 20%; - left: calc(-0.15em * 2 - 0.08em * 3); - filter: brightness(0.6); -} - -.tst-list-example li span { - position: relative; - height: 120%; - top: -10%; - box-sizing: border-box; - border: 0.08em solid; - background-color: #333; - display: flex; - align-items: center; - justify-content: center; - font-family: sans-serif; - text-transform: capitalize; - transform: translateX(calc(-0.15em * 3 - 0.08em * 2)); - transition: 0.3s; -} - -.tst-list-example li:hover span { - transform: translateX(0.15em); -} +@import url(https://fonts.googleapis.com/css?family=Roboto:300); + +.tst-text-color { + color: #cc0707; +} +.tst-btn { + height: 20px; + width: 100px; + background-color: #000000; +} + +.tst-login-page { + width: 360px; + padding: 8% 0 0; + margin: auto; +} +.tst-form { + position: relative; + z-index: 1; + background: #0671e3; + max-width: 360px; + margin: 0 auto 100px; + padding: 45px; + text-align: center; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); +} +.tst-form input { + font-family: 'Roboto', sans-serif; + outline: 0; + background: #f2f2f2; + width: 100%; + border: 0; + margin: 0 0 15px; + padding: 15px; + box-sizing: border-box; + font-size: 14px; +} +.tst-form button { + font-family: 'Roboto', sans-serif; + text-transform: uppercase; + outline: 0; + background: #4caf50; + width: 100%; + border: 0; + padding: 15px; + color: #ffffff; + font-size: 14px; + -webkit-transition: all 0.3 ease; + transition: all 0.3 ease; + cursor: pointer; +} +.tst-form button:hover, +.form button:active, +.form button:focus { + background: #0671e3; +} +.tst-form .message { + margin: 15px 0 0; + color: #b3b3b3; + font-size: 12px; +} +.tst-form .message-a { + color: #313fbd; + text-decoration: none; +} +.tst-form .register-form { + display: none; +} +.tst-container { + position: relative; + z-index: 1; + max-width: 300px; + margin: 0 auto; +} +.tst-container:before, +.container:after { + content: ''; + display: block; + clear: both; +} +.tst-container .info { + margin: 50px auto; + text-align: center; +} +.tst-container .info h1 { + margin: 0 0 15px; + padding: 0; + font-size: 36px; + font-weight: 300; + color: #1a1a1a; +} +.tst-container .info span { + color: #4d4d4d; + font-size: 12px; +} +.tst-container .info span a { + color: #000000; + text-decoration: none; +} +.tst-container .info span .fa { + color: #ef3b3a; +} +.tst-container-body { + height: 100vh; + background: #76b852; /* fallback for old browsers */ + background: -webkit-linear-gradient(right, #5278b8, #2a66ce); + background: -moz-linear-gradient(right, #5278b8, #2a66ce); + background: -o-linear-gradient(right, #5278b8, #2a66ce); + background: linear-gradient(to left, #5278b8, #2a66ce); + font-family: Roboto, Raleway, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.tst-list-example .body-container { + margin: 0; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background-color: #333; +} + +.tst-list-example ul { + padding: 0; + list-style-type: none; +} + +.tst-list-example li { + font-size: 25px; + width: 8em; + height: 2em; + color: orange; + border-left: 0.08em solid; + position: relative; + margin-top: 0.8em; + cursor: pointer; +} + +.tst-list-example li::before, +.tst-list-example li::after { + content: ''; + position: absolute; + width: inherit; + border-left: inherit; + z-index: -1; +} + +.tst-list-example li::before { + height: 80%; + top: 10%; + left: calc(-0.15em - 0.08em * 2); + filter: brightness(0.8); +} + +.tst-list-example li::after { + height: 60%; + top: 20%; + left: calc(-0.15em * 2 - 0.08em * 3); + filter: brightness(0.6); +} + +.tst-list-example li span { + position: relative; + height: 120%; + top: -10%; + box-sizing: border-box; + border: 0.08em solid; + background-color: #333; + display: flex; + align-items: center; + justify-content: center; + font-family: sans-serif; + text-transform: capitalize; + transform: translateX(calc(-0.15em * 3 - 0.08em * 2)); + transition: 0.3s; +} + +.tst-list-example li:hover span { + transform: translateX(0.15em); +} diff --git a/server/controllers/cookieController.ts b/server/controllers/cookieController.ts index 8d1e80a05..882965cce 100644 --- a/server/controllers/cookieController.ts +++ b/server/controllers/cookieController.ts @@ -1,35 +1,35 @@ -import { CookieController } from '../interfaces'; - - -const cookieController: CookieController = { - // setSSIDCookie - store the user id from database in cookie - setSSIDCookie: (req, res, next) => { - // set cookie with key 'ssid' and value to user's id - res.cookie('ssid', res.locals.id, { - httpOnly: true, - sameSite: 'none', - secure: true, - //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 - - }); - - res.cookie('username', res.locals.username, { - httpOnly: true, - sameSite: 'none', - secure: true, - }); - return next(); - }, - - deleteCookies: (req, res, next) => { - - res.clearCookie('ssid'); - res.clearCookie('username'); - res.clearCookie('connect.sid'); - return next(); - } - - -}; - -export default cookieController; +import { CookieController } from '../interfaces'; + + +const cookieController: CookieController = { + // setSSIDCookie - store the user id from database in cookie + setSSIDCookie: (req, res, next) => { + // set cookie with key 'ssid' and value to user's id + res.cookie('ssid', res.locals.id, { + httpOnly: true, + sameSite: 'none', + secure: true, + //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 + + }); + + res.cookie('username', res.locals.username, { + httpOnly: true, + sameSite: 'none', + secure: true, + }); + return next(); + }, + + deleteCookies: (req, res, next) => { + + res.clearCookie('ssid'); + res.clearCookie('username'); + res.clearCookie('connect.sid'); + return next(); + } + + +}; + +export default cookieController; diff --git a/server/controllers/marketplaceController.ts b/server/controllers/marketplaceController.ts index 91e49be84..d9c85eb1d 100644 --- a/server/controllers/marketplaceController.ts +++ b/server/controllers/marketplaceController.ts @@ -1,152 +1,152 @@ -import Project from '../graphQL/resolvers/query'; -import { MarketplaceController } from '../interfaces'; -import { Projects, Users } from '../models/reactypeModels'; -import mongoose from 'mongoose'; - -// array of objects, objects inside -type Projects = { project: {} }[]; - -const marketplaceController: MarketplaceController = { - /** - * Middleware function that finds and returns all published projects from the database - * @return sends the entire project document to frontend - */ - getPublishedProjects: (req, res, next) => { - Projects.find({ published: true }, (err, projects) => {//removed the typing for now for projects: since its the mongodb doc - if (err) { - return next({ - log: `Error in marketplaceController.getPublishedProjects: ${err}`, - message: { - err: 'Error in marketplaceController.getPublishedProjects, check server logs for details' - } - }); - } - // returns the entire project document as an array - // need to convert each project document to an object - const convertedProjects = projects.map((project) => { - return project.toObject({ minimize: false }); - }); - res.locals.publishedProjects = convertedProjects; - return next(); - }); - }, - - /** - * - * Middleware function that publishes (and saves) a project to the database - * @return sends the updated entire project document to the frontend - */ - publishProject: async (req, res, next) => { - const { _id, project, comments, name } = req.body; - const username = req.cookies.username; - const userId = req.cookies.ssid; - const createdAt = Date.now(); - - try{ - if (mongoose.isValidObjectId(_id)) { - const noPub = {...project} - delete noPub.published; - delete noPub._id; - const publishedProject = await Projects.findOneAndUpdate - ( // looks in projects collection for project by Mongo id - { _id }, - // update or insert the project - { project: noPub, createdAt, published: true, comments, name, userId, username }, - // Options: - // upsert: true - if none found, inserts new project, otherwise updates it - // new: true - returns updated document not the original one - { upsert: true, new: true } - ); - res.locals.publishedProject = publishedProject; - return next(); - }else{ - - const noId = {...project}; - delete noId._id; //removing the empty string _id from project - delete noId.published; - const publishedProject = await Projects.create( { project: noId, createdAt, published: true, comments, name, userId, username }) - res.locals.publishedProject = publishedProject.toObject({ minimize: false }); - return next(); - } - } - catch { - - // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors - return next({ - log: 'Error in marketplaceController.publishProject', - message: { - err: 'Error in marketplaceController.publishProject, check server logs for details' - } - }) - } - }, - - /** - * - * Middleware function that marks a project as unpublished in the database - * @return sends the updated project to the frontend - */ - unpublishProject: (req, res, next) => { - const { _id } = req.body; - const userId = req.cookies.ssid; - //check if req.cookies.ssid matches userId - - try { - Projects.findOneAndUpdate({ _id, userId }, {published: false}, { new: true }, (err, result) => { - if (err || result === null) { - return next({ - log: `Error in marketplaceController.unpublishProject: ${err || null}`, - message: { - err: 'Error in marketplaceController.unpublishProject, check server logs for details' - } - }); - } - res.locals.unpublishedProject = result.toObject({ minimize: false }); - return next(); - }); - } - catch { - // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors - return next({ - log: `Error in marketplaceController.unpublishProject`, - message: { - err: 'Error in marketplaceController.unpublishProject, userId of project does not match cookies.ssid' - } - }) - - } - }, - - /** - * Middleware function that clones and saves project to user's library - * - */ - cloneProject: async (req, res, next) => { - // pulls cookies from request - const userId = req.cookies.ssid; - const username = req.cookies.username; - try { // trying to find project, update its userId and username to a new project, then save it - const originalProject = await Projects.findOne({ _id: req.params.docId }).exec(); - const updatedProject = originalProject.toObject({ minimize: false }); // minimize false makes sure Mongoose / MongoDB does not remove nested properties with values of empty objects {} - updatedProject.userId = userId; - updatedProject.project.forked = true; - updatedProject.published = false; - updatedProject.forked = `Forked from ${updatedProject.username}`; // add forked tag with current project owner username - updatedProject.username = username; // then switch to the cloning username - delete updatedProject._id; // removes the old project id from the object - updatedProject.createdAt = Date.now(); - const clonedProject = await Projects.create(updatedProject); - res.locals.clonedProject = clonedProject.toObject({ minimize: false }); // need to convert back to an object to send to frontend, again make sure minimize is false - return next(); - } - catch (err) { - return next({ - log: `Error in marketplaceController.cloneProject: ${err}`, - message: { - err: 'Error in marketplaceController.cloneProject, check server logs for details' - } - }); - } - }, -}; -export default marketplaceController; +import Project from '../graphQL/resolvers/query'; +import { MarketplaceController } from '../interfaces'; +import { Projects, Users } from '../models/reactypeModels'; +import mongoose from 'mongoose'; + +// array of objects, objects inside +type Projects = { project: {} }[]; + +const marketplaceController: MarketplaceController = { + /** + * Middleware function that finds and returns all published projects from the database + * @return sends the entire project document to frontend + */ + getPublishedProjects: (req, res, next) => { + Projects.find({ published: true }, (err, projects) => {//removed the typing for now for projects: since its the mongodb doc + if (err) { + return next({ + log: `Error in marketplaceController.getPublishedProjects: ${err}`, + message: { + err: 'Error in marketplaceController.getPublishedProjects, check server logs for details' + } + }); + } + // returns the entire project document as an array + // need to convert each project document to an object + const convertedProjects = projects.map((project) => { + return project.toObject({ minimize: false }); + }); + res.locals.publishedProjects = convertedProjects; + return next(); + }); + }, + + /** + * + * Middleware function that publishes (and saves) a project to the database + * @return sends the updated entire project document to the frontend + */ + publishProject: async (req, res, next) => { + const { _id, project, comments, name } = req.body; + const username = req.cookies.username; + const userId = req.cookies.ssid; + const createdAt = Date.now(); + + try{ + if (mongoose.isValidObjectId(_id)) { + const noPub = {...project} + delete noPub.published; + delete noPub._id; + const publishedProject = await Projects.findOneAndUpdate + ( // looks in projects collection for project by Mongo id + { _id }, + // update or insert the project + { project: noPub, createdAt, published: true, comments, name, userId, username }, + // Options: + // upsert: true - if none found, inserts new project, otherwise updates it + // new: true - returns updated document not the original one + { upsert: true, new: true } + ); + res.locals.publishedProject = publishedProject; + return next(); + }else{ + + const noId = {...project}; + delete noId._id; //removing the empty string _id from project + delete noId.published; + const publishedProject = await Projects.create( { project: noId, createdAt, published: true, comments, name, userId, username }) + res.locals.publishedProject = publishedProject.toObject({ minimize: false }); + return next(); + } + } + catch { + + // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors + return next({ + log: 'Error in marketplaceController.publishProject', + message: { + err: 'Error in marketplaceController.publishProject, check server logs for details' + } + }) + } + }, + + /** + * + * Middleware function that marks a project as unpublished in the database + * @return sends the updated project to the frontend + */ + unpublishProject: (req, res, next) => { + const { _id } = req.body; + const userId = req.cookies.ssid; + //check if req.cookies.ssid matches userId + + try { + Projects.findOneAndUpdate({ _id, userId }, {published: false}, { new: true }, (err, result) => { + if (err || result === null) { + return next({ + log: `Error in marketplaceController.unpublishProject: ${err || null}`, + message: { + err: 'Error in marketplaceController.unpublishProject, check server logs for details' + } + }); + } + res.locals.unpublishedProject = result.toObject({ minimize: false }); + return next(); + }); + } + catch { + // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors + return next({ + log: `Error in marketplaceController.unpublishProject`, + message: { + err: 'Error in marketplaceController.unpublishProject, userId of project does not match cookies.ssid' + } + }) + + } + }, + + /** + * Middleware function that clones and saves project to user's library + * + */ + cloneProject: async (req, res, next) => { + // pulls cookies from request + const userId = req.cookies.ssid; + const username = req.cookies.username; + try { // trying to find project, update its userId and username to a new project, then save it + const originalProject = await Projects.findOne({ _id: req.params.docId }).exec(); + const updatedProject = originalProject.toObject({ minimize: false }); // minimize false makes sure Mongoose / MongoDB does not remove nested properties with values of empty objects {} + updatedProject.userId = userId; + updatedProject.project.forked = true; + updatedProject.published = false; + updatedProject.forked = `Forked from ${updatedProject.username}`; // add forked tag with current project owner username + updatedProject.username = username; // then switch to the cloning username + delete updatedProject._id; // removes the old project id from the object + updatedProject.createdAt = Date.now(); + const clonedProject = await Projects.create(updatedProject); + res.locals.clonedProject = clonedProject.toObject({ minimize: false }); // need to convert back to an object to send to frontend, again make sure minimize is false + return next(); + } + catch (err) { + return next({ + log: `Error in marketplaceController.cloneProject: ${err}`, + message: { + err: 'Error in marketplaceController.cloneProject, check server logs for details' + } + }); + } + }, +}; +export default marketplaceController; diff --git a/server/controllers/projectController.ts b/server/controllers/projectController.ts index 0dd7d5bbf..d5177a154 100644 --- a/server/controllers/projectController.ts +++ b/server/controllers/projectController.ts @@ -1,102 +1,102 @@ -import { ProjectController } from '../interfaces'; -import { Projects } from '../models/reactypeModels'; -import { State} from '../../app/src/interfaces/Interfaces' - -// array of objects, objects inside -type Projects = { project: {} }[]; - -const projectController: ProjectController = { - // saveProject saves current workspace to database - saveProject: (req, res, next) => { - - // pull project name and project itself from body - const { name, project, comments } = req.body; - const username = req.cookies.username; - const userId = req.cookies.ssid; - //deleted published from project - const noPub = {...project}; - delete noPub.published; - // create createdBy field for the document - const createdAt = Date.now(); - // pull ssid from cookies for user id - Projects.findOneAndUpdate( - // looks in projects collection for project by user and name - { name, userId, username}, - // update or insert the project - { project: noPub, createdAt, published: false, comments }, - // Options: - // upsert: true - if none found, inserts new project, if found, updates project - // new: true - returns updated document not the original one - { upsert: true, new: true }, - (err, result) => { - if (err) { - return next({ - log: `Error in projectController.saveProject: ${err}`, - message: { - err: 'Error in projectController.saveProject, check server logs for details' - } - }); - } - res.locals.savedProject = result; - return next(); - } - ); - }, - - // gets all of current user's projects - getProjects: (req, res, next) => { - const userId = req.cookies.ssid - Projects.find({ userId }, (err, projects: Array<{_id: string; published: boolean; project: object }>) => { - if (err) { - return next({ - log: `Error in projectController.getProjects: ${err}`, - message: { - err: 'Error in projectController.getProjects, check server logs for details' - } - }); - } - // so it returns each project like it is in state, not the whole object in DB - res.locals.projects = projects.map((elem: {_id: string; name: string; published: boolean; project: object } ) =>({ - _id: elem._id, - name: elem.name, - published: elem.published, - ...elem.project - })); - return next(); - }); - }, - - - // delete project from database - deleteProject: async (req, res, next) => { - // pull project name and userId from req.body - const { _id } = req.body; - const userId = req.cookies.ssid; - // try { - // const response = await Projects.findOneAndDelete({ _id: _id, username: userId }); - // res.locals.deleted = response; - // return next() - // } catch (err) { - // return next({ - // log: `Error in projectController.deleteProject: ${err}`, - // message: { - // err: 'Error in projectController.deleteProject, check server logs for details' - // } - // }); - // } - // @Denton, rewrote the above syntax for async await, would be good to test this further - Projects.findOneAndDelete({ _id, userId }, null, (err, deleted) => { - if (err) { - return next({ - log: `Error in projectController.deleteProject: ${err}`, - message: { - err: 'Error in projectController.deleteProject, check server logs for details' - } - }); - } - res.locals.deleted = deleted; - return next(); - }); - } -}; -export default projectController; +import { ProjectController } from '../interfaces'; +import { Projects } from '../models/reactypeModels'; +import { State} from '../../app/src/interfaces/Interfaces' + +// array of objects, objects inside +type Projects = { project: {} }[]; + +const projectController: ProjectController = { + // saveProject saves current workspace to database + saveProject: (req, res, next) => { + + // pull project name and project itself from body + const { name, project, comments } = req.body; + const username = req.cookies.username; + const userId = req.cookies.ssid; + //deleted published from project + const noPub = {...project}; + delete noPub.published; + // create createdBy field for the document + const createdAt = Date.now(); + // pull ssid from cookies for user id + Projects.findOneAndUpdate( + // looks in projects collection for project by user and name + { name, userId, username}, + // update or insert the project + { project: noPub, createdAt, published: false, comments }, + // Options: + // upsert: true - if none found, inserts new project, if found, updates project + // new: true - returns updated document not the original one + { upsert: true, new: true }, + (err, result) => { + if (err) { + return next({ + log: `Error in projectController.saveProject: ${err}`, + message: { + err: 'Error in projectController.saveProject, check server logs for details' + } + }); + } + res.locals.savedProject = result; + return next(); + } + ); + }, + + // gets all of current user's projects + getProjects: (req, res, next) => { + const userId = req.cookies.ssid + Projects.find({ userId }, (err, projects: Array<{_id: string; published: boolean; project: object }>) => { + if (err) { + return next({ + log: `Error in projectController.getProjects: ${err}`, + message: { + err: 'Error in projectController.getProjects, check server logs for details' + } + }); + } + // so it returns each project like it is in state, not the whole object in DB + res.locals.projects = projects.map((elem: {_id: string; name: string; published: boolean; project: object } ) =>({ + _id: elem._id, + name: elem.name, + published: elem.published, + ...elem.project + })); + return next(); + }); + }, + + + // delete project from database + deleteProject: async (req, res, next) => { + // pull project name and userId from req.body + const { _id } = req.body; + const userId = req.cookies.ssid; + // try { + // const response = await Projects.findOneAndDelete({ _id: _id, username: userId }); + // res.locals.deleted = response; + // return next() + // } catch (err) { + // return next({ + // log: `Error in projectController.deleteProject: ${err}`, + // message: { + // err: 'Error in projectController.deleteProject, check server logs for details' + // } + // }); + // } + // @Denton, rewrote the above syntax for async await, would be good to test this further + Projects.findOneAndDelete({ _id, userId }, null, (err, deleted) => { + if (err) { + return next({ + log: `Error in projectController.deleteProject: ${err}`, + message: { + err: 'Error in projectController.deleteProject, check server logs for details' + } + }); + } + res.locals.deleted = deleted; + return next(); + }); + } +}; +export default projectController; diff --git a/server/controllers/sessionController.ts b/server/controllers/sessionController.ts index 81853c1af..f79a2d9c2 100644 --- a/server/controllers/sessionController.ts +++ b/server/controllers/sessionController.ts @@ -1,204 +1,204 @@ -import fetch from 'node-fetch'; -import { Sessions } from '../models/reactypeModels'; -import dotenv from 'dotenv'; -import { SessionController, SessionCookie } from '../interfaces'; - -dotenv.config(); -// here we are cheching that the user making the request is login in and has a valid cookieId -const sessionController: SessionController = { - isLoggedIn: async (req, res, next) => { - // if (process.env.NODE_ENV === 'test') { - // // Skip authentication checks in test environment - // return next(); - // } else { - try { - let cookieId; - if (req.cookies) { - // if the request cookies exist then it assigns it to cookieId - cookieId = req.cookies.ssid; - } else { - // else it creates a new cookieId for the user based on the userId - cookieId = req.body.userId; - } - - // find session from request session ID in mongodb - const session = await Sessions.findOne({ cookieId }); - if (!session) { - res.locals.loggedIn = false; - return next(); - } - res.locals.loggedIn = true; - return next(); - } catch (err) { - return next({ - log: `Error in sessionController.isLoggedIn: ${err}`, - message: { - err: 'Error in sessionController.isLoggedIn, check server logs for details' - } - }); - } - }, - // startSession - create and save a new session into the database - startSession: (req, res, next) => { - // first check if user is logged in already - Sessions.findOne({ cookieId: res.locals.id || req.user.id }, (err, ses) => { - if (err) { - return next({ - log: `Error in sessionController.startSession find session: ${err}`, - message: { - err: 'Error in sessionController.startSession find session, check server logs for details' - } - }); - // if session doesn't exist, create a session - // if valid user logged in/signed up, res.locals.id should be user's id generated from mongodb, which we will set as this session's cookieId - } - if (!ses) { - Sessions.create( - //checking if logged in via the login form (res.locals.id) or oauth(req.user.id) - { cookieId: res.locals.id || req.user.id }, - (error, session: SessionCookie) => { - if (error) { - return next({ - log: `Error in sessionController.startSession create session: ${error}`, - message: { - err: 'Error in sessionController.startSession create session, check server logs for details' - } - }); - } - res.locals.ssid = session.cookieId; - return next(); - } - ); - // if session exists, move onto next middleware - } else { - res.locals.ssid = ses.cookieId; - return next(); - } - }); - }, - - endSession: (req, res, next) => { - //finding then deleting the session - Sessions.findOneAndDelete( - { cookieId: req.cookies.ssid }, - null, - (err, deleted) => { - if (err) { - return next({ - log: `Error in sessionController.endSession: ${err}`, - message: { - err: 'Error in sessionController.endSession, check server logs for details' - } - }); - } - res.locals.deleted = deleted; - return next(); - } - ); - } - - //don't think this code is used DW17 - // gitHubResponse: (req, res, next) => { - // const { code } = req.query; - // if (!code) { - // console.log('code not found'); - // return next({ - // log: 'Undefined or no code received from github.com', - // message: 'Undefined or no code received from github.com', - // status: 400 - // }); - // } - // fetch( - // `https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}&client_secret=${process.env.GITHUB_SECRET}&code=${code}`, - // { - // method: 'POST', - // headers: { - // accept: 'application/json', - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ - // client_id: process.env.GITHUB_ID, - // client_secret: process.env.GITHUB_SECRET, - // code: code - // }) - // } - // ) - // .then((res) => res.json()) - // .then((token) => { - // res.locals.token = token['access_token']; - // return next(); - // }) - // .catch((err) => { - // res.status(500).json({ message: `${err.message} in gitHubResponse` }); - // }); - // }, - - // gitHubSendToken: (req, res, next) => { - // const { token } = res.locals; - // fetch(`https://api.github.com/user/public_emails`, { - // method: 'GET', - // headers: { - // Accept: 'application/vnd.github.v3+json', - // Authorization: `token ${token}` - // } - // }) - // .then((res) => res.json()) - // .then((data) => { - // res.locals.githubEmail = data[0]['email']; - // res.locals.signUpType = 'oauth'; - // console.log( - // 'github email:', - // res.locals.githubEmail, - // 'signup type:', - // res.locals.signUpType - // ); - // return next(); - // }) - // .catch((err) => { - // if (err.message === `Cannot read property 'email' of undefined`) { - // return res - // .status(400) - // .json({ message: `${err.message} in gitHubSendToken` }); - // } else { - // return res - // .status(500) - // .json({ message: `${err.message} in gitHubSendToken` }); - // } - // }); - // }, - - // // creates a session when logging in with github - // githubSession: (req, res, next) => { - // // req.user is passed in from passport js -> serializeuser/deserializeuser - // const cookieId = req.user.id; - // Sessions.findOne({ cookieId }, (err, session: SessionCookie) => { - // if (err) { - // return next({ - // log: `Error in sessionController.githubSession find session: ${err}`, - // message: { - // err: `Error in sessionController.githubSession find session, check server logs for details` - // } - // }); - // } else if (!session) { - // Sessions.create({ cookieId }, (err, session: SessionCookie) => { - // if (err) { - // return next({ - // log: `Error in sessionController.githubSession create session: ${err}`, - // message: { - // err: `Error in sessionController.githubSession create session, check server logs for details` - // } - // }); - // } else { - // res.locals.id = session.cookieId; - // return next(); - // } - // }); - // } else { - // res.locals.id = session.cookieId; - // return next(); - // } - // }); - // } -}; - -export default sessionController; +import fetch from 'node-fetch'; +import { Sessions } from '../models/reactypeModels'; +import dotenv from 'dotenv'; +import { SessionController, SessionCookie } from '../interfaces'; + +dotenv.config(); +// here we are cheching that the user making the request is login in and has a valid cookieId +const sessionController: SessionController = { + isLoggedIn: async (req, res, next) => { + // if (process.env.NODE_ENV === 'test') { + // // Skip authentication checks in test environment + // return next(); + // } else { + try { + let cookieId; + if (req.cookies) { + // if the request cookies exist then it assigns it to cookieId + cookieId = req.cookies.ssid; + } else { + // else it creates a new cookieId for the user based on the userId + cookieId = req.body.userId; + } + + // find session from request session ID in mongodb + const session = await Sessions.findOne({ cookieId }); + if (!session) { + res.locals.loggedIn = false; + return next(); + } + res.locals.loggedIn = true; + return next(); + } catch (err) { + return next({ + log: `Error in sessionController.isLoggedIn: ${err}`, + message: { + err: 'Error in sessionController.isLoggedIn, check server logs for details' + } + }); + } + }, + // startSession - create and save a new session into the database + startSession: (req, res, next) => { + // first check if user is logged in already + Sessions.findOne({ cookieId: res.locals.id || req.user.id }, (err, ses) => { + if (err) { + return next({ + log: `Error in sessionController.startSession find session: ${err}`, + message: { + err: 'Error in sessionController.startSession find session, check server logs for details' + } + }); + // if session doesn't exist, create a session + // if valid user logged in/signed up, res.locals.id should be user's id generated from mongodb, which we will set as this session's cookieId + } + if (!ses) { + Sessions.create( + //checking if logged in via the login form (res.locals.id) or oauth(req.user.id) + { cookieId: res.locals.id || req.user.id }, + (error, session: SessionCookie) => { + if (error) { + return next({ + log: `Error in sessionController.startSession create session: ${error}`, + message: { + err: 'Error in sessionController.startSession create session, check server logs for details' + } + }); + } + res.locals.ssid = session.cookieId; + return next(); + } + ); + // if session exists, move onto next middleware + } else { + res.locals.ssid = ses.cookieId; + return next(); + } + }); + }, + + endSession: (req, res, next) => { + //finding then deleting the session + Sessions.findOneAndDelete( + { cookieId: req.cookies.ssid }, + null, + (err, deleted) => { + if (err) { + return next({ + log: `Error in sessionController.endSession: ${err}`, + message: { + err: 'Error in sessionController.endSession, check server logs for details' + } + }); + } + res.locals.deleted = deleted; + return next(); + } + ); + } + + //don't think this code is used DW17 + // gitHubResponse: (req, res, next) => { + // const { code } = req.query; + // if (!code) { + // console.log('code not found'); + // return next({ + // log: 'Undefined or no code received from github.com', + // message: 'Undefined or no code received from github.com', + // status: 400 + // }); + // } + // fetch( + // `https://github.com/login/oauth/access_token?client_id=${process.env.GITHUB_ID}&client_secret=${process.env.GITHUB_SECRET}&code=${code}`, + // { + // method: 'POST', + // headers: { + // accept: 'application/json', + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ + // client_id: process.env.GITHUB_ID, + // client_secret: process.env.GITHUB_SECRET, + // code: code + // }) + // } + // ) + // .then((res) => res.json()) + // .then((token) => { + // res.locals.token = token['access_token']; + // return next(); + // }) + // .catch((err) => { + // res.status(500).json({ message: `${err.message} in gitHubResponse` }); + // }); + // }, + + // gitHubSendToken: (req, res, next) => { + // const { token } = res.locals; + // fetch(`https://api.github.com/user/public_emails`, { + // method: 'GET', + // headers: { + // Accept: 'application/vnd.github.v3+json', + // Authorization: `token ${token}` + // } + // }) + // .then((res) => res.json()) + // .then((data) => { + // res.locals.githubEmail = data[0]['email']; + // res.locals.signUpType = 'oauth'; + // console.log( + // 'github email:', + // res.locals.githubEmail, + // 'signup type:', + // res.locals.signUpType + // ); + // return next(); + // }) + // .catch((err) => { + // if (err.message === `Cannot read property 'email' of undefined`) { + // return res + // .status(400) + // .json({ message: `${err.message} in gitHubSendToken` }); + // } else { + // return res + // .status(500) + // .json({ message: `${err.message} in gitHubSendToken` }); + // } + // }); + // }, + + // // creates a session when logging in with github + // githubSession: (req, res, next) => { + // // req.user is passed in from passport js -> serializeuser/deserializeuser + // const cookieId = req.user.id; + // Sessions.findOne({ cookieId }, (err, session: SessionCookie) => { + // if (err) { + // return next({ + // log: `Error in sessionController.githubSession find session: ${err}`, + // message: { + // err: `Error in sessionController.githubSession find session, check server logs for details` + // } + // }); + // } else if (!session) { + // Sessions.create({ cookieId }, (err, session: SessionCookie) => { + // if (err) { + // return next({ + // log: `Error in sessionController.githubSession create session: ${err}`, + // message: { + // err: `Error in sessionController.githubSession create session, check server logs for details` + // } + // }); + // } else { + // res.locals.id = session.cookieId; + // return next(); + // } + // }); + // } else { + // res.locals.id = session.cookieId; + // return next(); + // } + // }); + // } +}; + +export default sessionController; diff --git a/server/graphQL/resolvers/mutation.ts b/server/graphQL/resolvers/mutation.ts index 94db6c44d..63896ae1d 100644 --- a/server/graphQL/resolvers/mutation.ts +++ b/server/graphQL/resolvers/mutation.ts @@ -1,177 +1,177 @@ -const { ApolloServerErrorCode } = require('@apollo/server/errors'); - -import { Projects, Users, Comments } from '../../models/reactypeModels'; - -/* - * resolvers are functions that handles graphQL requests. This file defines the logic for graphQL mutation requests - * Link to Apollo Mutations: - * https://www.apollographql.com/docs/apollo-server/data/resolvers/#defining-a-resolver - */ - -const Project = { - addLike: async (parent, { projId, likes }) => { - const filter = { _id: projId }; - const update = { likes }; - const options = { new: true }; - const resp = await Projects.findOneAndUpdate(filter, update, options); - if (resp) { - return { - name: resp.name, - id: resp._id, - userId: resp.userId, - likes: resp.likes, - published: resp.published, - createdAt: resp.createdAt - }; - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project is not found. Please try another project ID', - { - argumentName: 'projId' - } - ); - }, - - makeCopy: async (parent, { projId, userId, username }) => { - const filter = { _id: projId }; - const target = await Projects.findOne(filter); - - if (!target) { - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project is not found. Please try another project ID', - { - argumentName: 'projId' - } - ); - } - - // check if user account exists - const user = await Users.findOne({ _id: userId }); - - if (user) { - // make a copy with the passed in userId - const copy = { - name: target.name, - project: target.project, - userId, - username - }; - - // Make a copy of the requested project - const resp = await Projects.create(copy); - if (resp) { - return { - name: resp.name, - id: resp._id, - userId: resp.userId, - username: resp.username, - likes: resp.likes, - published: resp.published - }; - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT('Internal Server Error'); - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'User is not found. Please try another user ID', - { - argumentName: 'userId' - } - ); - }, - - deleteProject: async (parent, { projId }) => { - const filter = { _id: projId }; - const options = { strict: true }; - const resp = await Projects.findOneAndDelete(filter, options); - - if (resp) { - return { - name: resp.name, - id: resp._id, - userId: resp.userId, - likes: resp.likes, - published: resp.published, - createdAt: resp.createdAt - }; - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project is not found. Please try another project ID', - { - argumentName: 'projId' - } - ); - }, - - publishProject: async (parent, { projId, published }) => { - const filter = { _id: projId }; - const update = { published }; - const options = { new: true }; - const resp = await Projects.findOneAndUpdate(filter, update, options); - if (resp) { - return { - name: resp.name, - id: resp._id, - userId: resp.userId, - likes: resp.likes, - published: resp.published, - createdAt: resp.createdAt - }; - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project is not found. Please try another project ID', - { - argumentName: 'projId' - } - ); - }, - - addComment: async (parent, { projId, comment, username }) => { - const filter = { _id: projId }; - const options = { new: true }; - - // data for the new Comments document - const commentDocument = { - projectId: projId, - text: comment, - username - }; - // creating the new Comments document - const newCommentDoc = await Comments.create(commentDocument); - - // target Projects document to add comment _id to - const targetProject = await Projects.findOne(filter); - // pushing the new Comments documents _id into the targetProject comments array - targetProject.comments.push(newCommentDoc._id); - // updating the target Projects document in the database - const updatedProj = await Projects.findOneAndUpdate( - filter, - targetProject, - options - ); - if (updatedProj) { - return { - name: updatedProj.name, - id: updatedProj._id, - userId: updatedProj.userId, - likes: updatedProj.likes, - published: updatedProj.published, - createdAt: updatedProj.createdAt, - comments: updatedProj.comments - }; - } - - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project cannot be found. Please try another project ID', - { - argumentName: 'projId' - } - ); - } -}; - -export default Project; +const { ApolloServerErrorCode } = require('@apollo/server/errors'); + +import { Projects, Users, Comments } from '../../models/reactypeModels'; + +/* + * resolvers are functions that handles graphQL requests. This file defines the logic for graphQL mutation requests + * Link to Apollo Mutations: + * https://www.apollographql.com/docs/apollo-server/data/resolvers/#defining-a-resolver + */ + +const Project = { + addLike: async (parent, { projId, likes }) => { + const filter = { _id: projId }; + const update = { likes }; + const options = { new: true }; + const resp = await Projects.findOneAndUpdate(filter, update, options); + if (resp) { + return { + name: resp.name, + id: resp._id, + userId: resp.userId, + likes: resp.likes, + published: resp.published, + createdAt: resp.createdAt + }; + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project is not found. Please try another project ID', + { + argumentName: 'projId' + } + ); + }, + + makeCopy: async (parent, { projId, userId, username }) => { + const filter = { _id: projId }; + const target = await Projects.findOne(filter); + + if (!target) { + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project is not found. Please try another project ID', + { + argumentName: 'projId' + } + ); + } + + // check if user account exists + const user = await Users.findOne({ _id: userId }); + + if (user) { + // make a copy with the passed in userId + const copy = { + name: target.name, + project: target.project, + userId, + username + }; + + // Make a copy of the requested project + const resp = await Projects.create(copy); + if (resp) { + return { + name: resp.name, + id: resp._id, + userId: resp.userId, + username: resp.username, + likes: resp.likes, + published: resp.published + }; + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT('Internal Server Error'); + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'User is not found. Please try another user ID', + { + argumentName: 'userId' + } + ); + }, + + deleteProject: async (parent, { projId }) => { + const filter = { _id: projId }; + const options = { strict: true }; + const resp = await Projects.findOneAndDelete(filter, options); + + if (resp) { + return { + name: resp.name, + id: resp._id, + userId: resp.userId, + likes: resp.likes, + published: resp.published, + createdAt: resp.createdAt + }; + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project is not found. Please try another project ID', + { + argumentName: 'projId' + } + ); + }, + + publishProject: async (parent, { projId, published }) => { + const filter = { _id: projId }; + const update = { published }; + const options = { new: true }; + const resp = await Projects.findOneAndUpdate(filter, update, options); + if (resp) { + return { + name: resp.name, + id: resp._id, + userId: resp.userId, + likes: resp.likes, + published: resp.published, + createdAt: resp.createdAt + }; + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project is not found. Please try another project ID', + { + argumentName: 'projId' + } + ); + }, + + addComment: async (parent, { projId, comment, username }) => { + const filter = { _id: projId }; + const options = { new: true }; + + // data for the new Comments document + const commentDocument = { + projectId: projId, + text: comment, + username + }; + // creating the new Comments document + const newCommentDoc = await Comments.create(commentDocument); + + // target Projects document to add comment _id to + const targetProject = await Projects.findOne(filter); + // pushing the new Comments documents _id into the targetProject comments array + targetProject.comments.push(newCommentDoc._id); + // updating the target Projects document in the database + const updatedProj = await Projects.findOneAndUpdate( + filter, + targetProject, + options + ); + if (updatedProj) { + return { + name: updatedProj.name, + id: updatedProj._id, + userId: updatedProj.userId, + likes: updatedProj.likes, + published: updatedProj.published, + createdAt: updatedProj.createdAt, + comments: updatedProj.comments + }; + } + + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project cannot be found. Please try another project ID', + { + argumentName: 'projId' + } + ); + } +}; + +export default Project; diff --git a/server/graphQL/resolvers/query.ts b/server/graphQL/resolvers/query.ts index 3e8ff9e2a..b42213951 100644 --- a/server/graphQL/resolvers/query.ts +++ b/server/graphQL/resolvers/query.ts @@ -1,74 +1,74 @@ -// const { UserInputError } = require('apollo-server-express');//v3 syntax -const { ApolloServerErrorCode } = require('@apollo/server/errors'); -// import { ApolloServerErrorCode } from '@apollo/server/errors'; // v4 syntax -// const ApolloServerErrorCode = require('@apollo/server/errors') -//now using ApolloServerErrorCode.BAD_USER_INPUT in place of UserInputError - -import { Projects, Comments } from '../../models/reactypeModels'; -// Link to Apollo Query Types: -// https://www.apollographql.com/docs/apollo-server/data/resolvers/#defining-a-resolver - -const Project = { - getProject: async (parent, { projId }) => { - const resp = await Projects.findOne({ _id: projId }); - if (resp) { - return { - name: resp.name, - id: resp._id, - userId: resp.userId, - username: resp.username, - likes: resp.likes, - published: resp.published, - createdAt: resp.createdAt, - comments: resp.comments // here we should retrive the comments from the Comments collection and returns the array of comments here - }; - } - - // resp is null if nothing is found based on the project ID - throw new ApolloServerErrorCode.BAD_USER_INPUT( - 'Project is not found. Please try another project ID', - { - argumentName: 'projId' - } - ); - }, - - getAllProjects: async (parent, { userId }) => { - let resp = await Projects.find({}); - if (userId) { - // use loosely equal for the callback because there are some discrepancy between the type of userId from the db vs from the mutation query - resp = resp.filter((proj) => proj.userId == userId); - // if resp = [] after the filtering, this means the userId doesnt exisit in the database, throw error as follow - if (resp.length === 0) { - throw new ApolloServerErrorCode.BAD_USER_INPUT( - `Project for userId: "${userId}". Please try another id`, - { - argumentName: 'userId' - } - ); - } - } - - const comCollection = await Comments.find({}); - - if (resp) { - return resp.map((proj) => ({ - name: proj.name, - id: proj._id, - userId: proj.userId, - username: proj.username, - likes: proj.likes, - published: proj.published, - createdAt: proj.createdAt, - comments: comCollection.filter( - (commentDoc) => - commentDoc.projectId.toString() === proj._id.toString() - ) // here we should filter the comments for each proj and return it here - })); - } - // resp is null, return error message - throw new ApolloServerErrorCode.BAD_USER_INPUT('Internal Server Error'); - } -}; - -export default Project; +// const { UserInputError } = require('apollo-server-express');//v3 syntax +const { ApolloServerErrorCode } = require('@apollo/server/errors'); +// import { ApolloServerErrorCode } from '@apollo/server/errors'; // v4 syntax +// const ApolloServerErrorCode = require('@apollo/server/errors') +//now using ApolloServerErrorCode.BAD_USER_INPUT in place of UserInputError + +import { Projects, Comments } from '../../models/reactypeModels'; +// Link to Apollo Query Types: +// https://www.apollographql.com/docs/apollo-server/data/resolvers/#defining-a-resolver + +const Project = { + getProject: async (parent, { projId }) => { + const resp = await Projects.findOne({ _id: projId }); + if (resp) { + return { + name: resp.name, + id: resp._id, + userId: resp.userId, + username: resp.username, + likes: resp.likes, + published: resp.published, + createdAt: resp.createdAt, + comments: resp.comments // here we should retrive the comments from the Comments collection and returns the array of comments here + }; + } + + // resp is null if nothing is found based on the project ID + throw new ApolloServerErrorCode.BAD_USER_INPUT( + 'Project is not found. Please try another project ID', + { + argumentName: 'projId' + } + ); + }, + + getAllProjects: async (parent, { userId }) => { + let resp = await Projects.find({}); + if (userId) { + // use loosely equal for the callback because there are some discrepancy between the type of userId from the db vs from the mutation query + resp = resp.filter((proj) => proj.userId == userId); + // if resp = [] after the filtering, this means the userId doesnt exisit in the database, throw error as follow + if (resp.length === 0) { + throw new ApolloServerErrorCode.BAD_USER_INPUT( + `Project for userId: "${userId}". Please try another id`, + { + argumentName: 'userId' + } + ); + } + } + + const comCollection = await Comments.find({}); + + if (resp) { + return resp.map((proj) => ({ + name: proj.name, + id: proj._id, + userId: proj.userId, + username: proj.username, + likes: proj.likes, + published: proj.published, + createdAt: proj.createdAt, + comments: comCollection.filter( + (commentDoc) => + commentDoc.projectId.toString() === proj._id.toString() + ) // here we should filter the comments for each proj and return it here + })); + } + // resp is null, return error message + throw new ApolloServerErrorCode.BAD_USER_INPUT('Internal Server Error'); + } +}; + +export default Project; diff --git a/server/graphQL/schema/typeDefs.ts b/server/graphQL/schema/typeDefs.ts index a54b6b761..e70d13f7a 100644 --- a/server/graphQL/schema/typeDefs.ts +++ b/server/graphQL/schema/typeDefs.ts @@ -1,46 +1,46 @@ -// const { gql } = require('apollo-server-express'); -const { gql } = require('@apollo/client'); - -// Link to defining a schema in Apollo: -// https://www.apollographql.com/docs/apollo-server/schema/schema/ -// The schema specifies which queries and mutations are available for clients -// to execute against your data graph. - -// NOTE: Project type does not return the detail of the project's components, -// but info needed for the dashboard - -// line 15, returns type Project from line 29 -const Project = gql` - type Mutation { - addLike(projId: ID!, likes: Int!): Project - makeCopy(projId: ID!, userId: ID!, username: String!): Project - deleteProject(projId: ID!): Project - publishProject(projId: ID!, published: Boolean!): Project - addComment(projId: ID!, comment: String!, username: String!): Project - } - - type Comment { - id: ID! - username: String! - text: String! - projectId: ID! - } - - type Project { - name: String! - likes: Int - published: Boolean! - id: ID! - userId: ID! - username: String! - createdAt: String - comments: [Comment!] - } - - type Query { - getProject(projId: ID!): Project - getAllProjects(userId: ID): [Project] - } -`; - -export default Project; +// const { gql } = require('apollo-server-express'); +const { gql } = require('@apollo/client'); + +// Link to defining a schema in Apollo: +// https://www.apollographql.com/docs/apollo-server/schema/schema/ +// The schema specifies which queries and mutations are available for clients +// to execute against your data graph. + +// NOTE: Project type does not return the detail of the project's components, +// but info needed for the dashboard + +// line 15, returns type Project from line 29 +const Project = gql` + type Mutation { + addLike(projId: ID!, likes: Int!): Project + makeCopy(projId: ID!, userId: ID!, username: String!): Project + deleteProject(projId: ID!): Project + publishProject(projId: ID!, published: Boolean!): Project + addComment(projId: ID!, comment: String!, username: String!): Project + } + + type Comment { + id: ID! + username: String! + text: String! + projectId: ID! + } + + type Project { + name: String! + likes: Int + published: Boolean! + id: ID! + userId: ID! + username: String! + createdAt: String + comments: [Comment!] + } + + type Query { + getProject(projId: ID!): Project + getAllProjects(userId: ID): [Project] + } +`; + +export default Project; diff --git a/server/models/Oauth-model.ts b/server/models/Oauth-model.ts index 289344180..b4ce0c9fc 100644 --- a/server/models/Oauth-model.ts +++ b/server/models/Oauth-model.ts @@ -1,18 +1,18 @@ -import mongoose, { Document } from 'mongoose'; - -interface UserDocument extends Document { - username?: string; - githubId?: string; - googleId?: string; -} - -const userSchema = new mongoose.Schema({ - username: { type: String }, - githubId: { type: String, unique: true }, - googleId: { type: String, unique: true } -}); - -const User = mongoose.model('OauthUsers', userSchema); - - -export default User; +import mongoose, { Document } from 'mongoose'; + +interface UserDocument extends Document { + username?: string; + githubId?: string; + googleId?: string; +} + +const userSchema = new mongoose.Schema({ + username: { type: String }, + githubId: { type: String, unique: true }, + googleId: { type: String, unique: true } +}); + +const User = mongoose.model('OauthUsers', userSchema); + + +export default User; diff --git a/server/models/reactypeModels.ts b/server/models/reactypeModels.ts index ad2d10ee6..2f70255d6 100644 --- a/server/models/reactypeModels.ts +++ b/server/models/reactypeModels.ts @@ -1,105 +1,105 @@ -/* - @desc: defines Schemas for the app: sessionSchema (cookieId, created_at), userSchema - (username, password, email), projectSchema (name, userId, project, created_at) - - @export: Users, Sessions, Projects (3 schemas) - - @important: URI to database is hidden in config.js file which is not available to future - team. we recommend that your team will create a mongoDB database to test in - dev mode. the real database is deployed in heroku - */ -import mongoose from 'mongoose'; -import dotenv from 'dotenv'; -import bcrypt from 'bcryptjs'; -import { UserDocument } from '../interfaces'; -dotenv.config(); -const Schema = mongoose.Schema; - -const isTest = process.env.NODE_ENV === 'test'; - -const mongoURI = process.env.MONGO_DB; -const URI = - process.env.NODE_ENV === 'production' ? mongoURI : process.env.MONGO_DB; - -const SALT_WORK_FACTOR = 10; -// connect to mongo db -if (!isTest) { - mongoose - .connect(URI, { - // options for the connect method to parse the URI - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - // stop deprecation warning for findOneAndUpdate and findOneAndDelete queries - useFindAndModify: false, - // sets the name of the DB that our collections are part of - dbName: 'reactype' - }) - .then(() => console.log('Connected to Mongo DB.')) - .catch((err) => console.log(err)); -} - -const userSchema = new Schema({ - username: { type: String, required: true, unique: true }, - email: { type: String, required: false, unique: true }, - password: { type: String, required: true } -}); - -// mongoose middleware that will run before the save to -// collection happens (user gets put into database) - -// cannot use arrow function here as context of 'this' is important -userSchema.pre('save', function cb(next) { - // within this context, 'this' refers to the document (new user) about to be saved, - // in our case, it should have properties username, password, and projects array - bcrypt.hash(this.password, SALT_WORK_FACTOR, (err, hash) => { - if (err) { - return next({ - log: `bcrypt password hashing error: ${err}`, - message: { err: 'bcrypt hash error: check server logs for details.' } - }); - } - this.password = hash; - return next(); - }); -}); - -const commentsSchema = new Schema({ - username: { type: String, required: true }, - text: { type: String, required: true }, - projectId: { type: Schema.Types.ObjectId, required: true }, - createdAt: { type: Date, default: Date.now } -}); - -const sessionSchema = new Schema({ - cookieId: { type: String, required: true, unique: true }, - createdAt: { type: Date, default: Date.now } -}); - -const projectSchema = new Schema( - { - name: { type: String, required: true }, - forked: { type: String }, - likes: { type: Number, default: 0 }, - published: { type: Boolean, default: false }, - project: { type: Object, required: true }, - userId: { - type: Schema.Types.ObjectId, - ref: 'Users' - }, - username: { type: String, required: true }, - createdAt: { type: Date, default: Date.now }, - comments: [ - { - type: Schema.Types.ObjectId, - ref: 'Comments' - } - ] - }, - { minimize: false } //changed to false -dw -); - -export const Users = mongoose.model('Users', userSchema); -export const Comments = mongoose.model('Comments', commentsSchema); -export const Sessions = mongoose.model('Sessions', sessionSchema); -export const Projects = mongoose.model('Projects', projectSchema); +/* + @desc: defines Schemas for the app: sessionSchema (cookieId, created_at), userSchema + (username, password, email), projectSchema (name, userId, project, created_at) + + @export: Users, Sessions, Projects (3 schemas) + + @important: URI to database is hidden in config.js file which is not available to future + team. we recommend that your team will create a mongoDB database to test in + dev mode. the real database is deployed in heroku + */ +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; +import bcrypt from 'bcryptjs'; +import { UserDocument } from '../interfaces'; +dotenv.config(); +const Schema = mongoose.Schema; + +const isTest = process.env.NODE_ENV === 'test'; + +const mongoURI = process.env.MONGO_DB; +const URI = + process.env.NODE_ENV === 'production' ? mongoURI : process.env.MONGO_DB; + +const SALT_WORK_FACTOR = 10; +// connect to mongo db +if (!isTest) { + mongoose + .connect(URI, { + // options for the connect method to parse the URI + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true, + // stop deprecation warning for findOneAndUpdate and findOneAndDelete queries + useFindAndModify: false, + // sets the name of the DB that our collections are part of + dbName: 'reactype' + }) + .then(() => console.log('Connected to Mongo DB.')) + .catch((err) => console.log(err)); +} + +const userSchema = new Schema({ + username: { type: String, required: true, unique: true }, + email: { type: String, required: false, unique: true }, + password: { type: String, required: true } +}); + +// mongoose middleware that will run before the save to +// collection happens (user gets put into database) + +// cannot use arrow function here as context of 'this' is important +userSchema.pre('save', function cb(next) { + // within this context, 'this' refers to the document (new user) about to be saved, + // in our case, it should have properties username, password, and projects array + bcrypt.hash(this.password, SALT_WORK_FACTOR, (err, hash) => { + if (err) { + return next({ + log: `bcrypt password hashing error: ${err}`, + message: { err: 'bcrypt hash error: check server logs for details.' } + }); + } + this.password = hash; + return next(); + }); +}); + +const commentsSchema = new Schema({ + username: { type: String, required: true }, + text: { type: String, required: true }, + projectId: { type: Schema.Types.ObjectId, required: true }, + createdAt: { type: Date, default: Date.now } +}); + +const sessionSchema = new Schema({ + cookieId: { type: String, required: true, unique: true }, + createdAt: { type: Date, default: Date.now } +}); + +const projectSchema = new Schema( + { + name: { type: String, required: true }, + forked: { type: String }, + likes: { type: Number, default: 0 }, + published: { type: Boolean, default: false }, + project: { type: Object, required: true }, + userId: { + type: Schema.Types.ObjectId, + ref: 'Users' + }, + username: { type: String, required: true }, + createdAt: { type: Date, default: Date.now }, + comments: [ + { + type: Schema.Types.ObjectId, + ref: 'Comments' + } + ] + }, + { minimize: false } //changed to false -dw +); + +export const Users = mongoose.model('Users', userSchema); +export const Comments = mongoose.model('Comments', commentsSchema); +export const Sessions = mongoose.model('Sessions', sessionSchema); +export const Projects = mongoose.model('Projects', projectSchema); diff --git a/server/routers/auth.ts b/server/routers/auth.ts index 4b02c6894..e5f132632 100644 --- a/server/routers/auth.ts +++ b/server/routers/auth.ts @@ -1,76 +1,76 @@ -import express from 'express'; -const passport = require('passport'); -import config from '../../config'; -import { Request } from 'express'; -import sessionController from '../controllers/sessionController'; - -// trying to add interface -interface UserReq extends Request { - user: { - id: string; - username: string; - googleId: string; - }; -} - -const { API_BASE_URL2 } = config; -const router = express.Router(); - -router.get( - '/github', - passport.authenticate('github', { - scope: ['profile'] - }) -); - -router.get( - '/github/callback', - passport.authenticate('github'), - sessionController.startSession, - (req: UserReq, res) => { - console.log('github authenticate function is being run'); - console.log(req.user.id); - res.cookie('ssid', req.user.id, { - httpOnly: true, - sameSite: 'none', - secure: true - }); - - res.cookie('username', req.user.username, { - httpOnly: true, - sameSite: 'none', - secure: true - }); - return res.redirect(API_BASE_URL2); - } -); - -router.get( - '/google', - passport.authenticate('google', { - scope: ['profile'] - }) -); - -router.get( - '/google/callback', - passport.authenticate('google'), - sessionController.startSession, - (req: UserReq, res) => { - console.log('google authenicate function being run'); - res.cookie('ssid', req.user.id, { - httpOnly: true, - sameSite: 'none', - secure: true - }); - - res.cookie('username', req.user.username, { - httpOnly: true, - sameSite: 'none', - secure: true - }); - return res.redirect(API_BASE_URL2); - } -); - -export default router; +import express from 'express'; +const passport = require('passport'); +import config from '../../config'; +import { Request } from 'express'; +import sessionController from '../controllers/sessionController'; + +// trying to add interface +interface UserReq extends Request { + user: { + id: string; + username: string; + googleId: string; + }; +} + +const { API_BASE_URL2 } = config; +const router = express.Router(); + +router.get( + '/github', + passport.authenticate('github', { + scope: ['profile'] + }) +); + +router.get( + '/github/callback', + passport.authenticate('github'), + sessionController.startSession, + (req: UserReq, res) => { + console.log('github authenticate function is being run'); + console.log(req.user.id); + res.cookie('ssid', req.user.id, { + httpOnly: true, + sameSite: 'none', + secure: true + }); + + res.cookie('username', req.user.username, { + httpOnly: true, + sameSite: 'none', + secure: true + }); + return res.redirect(API_BASE_URL2); + } +); + +router.get( + '/google', + passport.authenticate('google', { + scope: ['profile'] + }) +); + +router.get( + '/google/callback', + passport.authenticate('google'), + sessionController.startSession, + (req: UserReq, res) => { + console.log('google authenicate function being run'); + res.cookie('ssid', req.user.id, { + httpOnly: true, + sameSite: 'none', + secure: true + }); + + res.cookie('username', req.user.username, { + httpOnly: true, + sameSite: 'none', + secure: true + }); + return res.redirect(API_BASE_URL2); + } +); + +export default router; diff --git a/server/routers/passport-setup.ts b/server/routers/passport-setup.ts index a55b7d9f7..fd9ee5673 100644 --- a/server/routers/passport-setup.ts +++ b/server/routers/passport-setup.ts @@ -1,96 +1,96 @@ -const passport = require('passport'); -const passportGithub2 = require('passport-github2'); -import user from '../models/Oauth-model'; -import GoogleStrategy from 'passport-google-oauth20'; -import config from '../../config'; - -const GitHubStrategy = passportGithub2.Strategy; -const { API_BASE_URL } = config; - -passport.serializeUser((user, done) => { - done(null, user.id); -}); - -passport.deserializeUser((id, done) => { - user.findById(id).then((user) => { - done(null, user); - }); -}); - -passport.use( - new GitHubStrategy( - { - clientID: process.env.GITHUB_CLIENT, - clientSecret: process.env.GITHUB_SECRET, - callbackURL: `${API_BASE_URL}/auth/github/callback`, - proxy: true - }, - function (accessToken, refreshToken, profile, done) { - user - .findOne({ - githubId: profile.id - }) - .then((currentUser) => { - if (currentUser) { - console.log('user is: ', currentUser); - return done(null, currentUser); - } else { - const initials = profile.displayName.match(/\b\w/g).join(''); - const nums = profile.id.slice(0, 5); - user - .create({ - username: initials + nums + '(Github)', - githubId: profile.id - }) - .then((data) => { - console.log('user added successfully: ', data); - return done(null, data); - }) - .catch((data) => - console.log('issue with adding user to database', data) - ); - } - }); - } - ) -); - -passport.use( - new GoogleStrategy( - { - clientID: process.env.GOOGLE_CLIENT, - clientSecret: process.env.GOOGLE_SECRET, - callbackURL: `${API_BASE_URL}/auth/google/callback`, - proxy: true - }, - function (accessToken, refreshToken, profile, done) { - user - .findOne({ - googleId: profile.id - }) - .then((currentUser) => { - if (currentUser) { - console.log('user is: ', currentUser); - return done(null, currentUser); - } else { - const initials = profile.displayName.match(/\b\w/g).join(''); - const nums = profile.id.slice(0, 5); - user - .create({ - username: initials + nums + '(Google)', - googleId: profile.id - }) - .then((data) => { - console.log('user added successfully: ', data); - return done(null, data); - }) - .catch((data) => - console.log('issue with adding user to database', data) - ); - } - }); - } - ) -); - -export default passport; +const passport = require('passport'); +const passportGithub2 = require('passport-github2'); +import user from '../models/Oauth-model'; +import GoogleStrategy from 'passport-google-oauth20'; +import config from '../../config'; + +const GitHubStrategy = passportGithub2.Strategy; +const { API_BASE_URL } = config; + +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser((id, done) => { + user.findById(id).then((user) => { + done(null, user); + }); +}); + +passport.use( + new GitHubStrategy( + { + clientID: process.env.GITHUB_CLIENT, + clientSecret: process.env.GITHUB_SECRET, + callbackURL: `${API_BASE_URL}/auth/github/callback`, + proxy: true + }, + function (accessToken, refreshToken, profile, done) { + user + .findOne({ + githubId: profile.id + }) + .then((currentUser) => { + if (currentUser) { + console.log('user is: ', currentUser); + return done(null, currentUser); + } else { + const initials = profile.displayName.match(/\b\w/g).join(''); + const nums = profile.id.slice(0, 5); + user + .create({ + username: initials + nums + '(Github)', + githubId: profile.id + }) + .then((data) => { + console.log('user added successfully: ', data); + return done(null, data); + }) + .catch((data) => + console.log('issue with adding user to database', data) + ); + } + }); + } + ) +); + +passport.use( + new GoogleStrategy( + { + clientID: process.env.GOOGLE_CLIENT, + clientSecret: process.env.GOOGLE_SECRET, + callbackURL: `${API_BASE_URL}/auth/google/callback`, + proxy: true + }, + function (accessToken, refreshToken, profile, done) { + user + .findOne({ + googleId: profile.id + }) + .then((currentUser) => { + if (currentUser) { + console.log('user is: ', currentUser); + return done(null, currentUser); + } else { + const initials = profile.displayName.match(/\b\w/g).join(''); + const nums = profile.id.slice(0, 5); + user + .create({ + username: initials + nums + '(Google)', + googleId: profile.id + }) + .then((data) => { + console.log('user added successfully: ', data); + return done(null, data); + }) + .catch((data) => + console.log('issue with adding user to database', data) + ); + } + }); + } + ) +); + +export default passport; diff --git a/server/tsconfig.json b/server/tsconfig.json index caf5d2c7f..f6417be49 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,20 +1,20 @@ -{ - // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases - "extends": "ts-node/node16/tsconfig.json", - - "compilerOptions": { - "noImplicitAny": false, - "noImplicitThis": false, - "strictNullChecks": false, - "allowSyntheticDefaultImports": true, - "strictFunctionTypes": false, - "esModuleInterop": true, - "noEmitOnError": false - }, - // Most ts-node options can be specified here using their programmatic names. - "ts-node": { - // It is faster to skip typechecking. - // Remove if you want ts-node to do typechecking. - "transpileOnly": true - } -} +{ + // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases + "extends": "ts-node/node16/tsconfig.json", + + "compilerOptions": { + "noImplicitAny": false, + "noImplicitThis": false, + "strictNullChecks": false, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "esModuleInterop": true, + "noEmitOnError": false + }, + // Most ts-node options can be specified here using their programmatic names. + "ts-node": { + // It is faster to skip typechecking. + // Remove if you want ts-node to do typechecking. + "transpileOnly": true + } +} diff --git a/src/custom-aws-exports.js b/src/custom-aws-exports.js index dfd06d4ba..45364cc02 100644 --- a/src/custom-aws-exports.js +++ b/src/custom-aws-exports.js @@ -1,48 +1,48 @@ -/* eslint-disable */ - -const awsmobile = { - aws_project_region: import.meta.env.REACT_APP_AWS_PROJECT_REGION, - aws_cognito_identity_pool_id: import.meta.env - .REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID, - aws_cognito_region: import.meta.env.REACT_APP_AWS_COGNITO_REGION, - aws_user_pools_id: import.meta.env.REACT_APP_AWS_USER_POOLS_ID, - aws_user_pools_web_client_id: import.meta.env - .REACT_APP_AWS_USER_POOLS_WEB_CLIENT_ID, - oauth: {}, - aws_cognito_username_attributes: import.meta.env - .REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES - ? import.meta.env.REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES.split(',') - : [], - aws_cognito_social_providers: import.meta.env - .REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS - ? import.meta.env.REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS.split(',') - : [], - aws_cognito_signup_attributes: import.meta.env - .REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES - ? import.meta.env.REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES.split(',') - : [], - aws_cognito_mfa_configuration: import.meta.env - .REACT_APP_AWS_COGNITO_MFA_CONFIGURATION, - aws_cognito_mfa_types: import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES - ? import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES.split(',') - : [], - aws_cognito_password_protection_settings: { - passwordPolicyMinLength: import.meta.env - .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYMINLENGTH, - passwordPolicyCharacters: import.meta.env - .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS - ? import.meta.env.REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS.split( - ',' - ) - : [] - }, - aws_cognito_verification_mechanisms: import.meta.env - .REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS - ? import.meta.env.REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS.split(',') - : [], - aws_user_files_s3_bucket: import.meta.env.REACT_APP_AWS_USER_FILES_S3_BUCKET, - aws_user_files_s3_bucket_region: import.meta.env - .REACT_APP_AWS_USER_FILES_S3_BUCKET_REGION -}; - -export default awsmobile; +/* eslint-disable */ + +const awsmobile = { + aws_project_region: import.meta.env.REACT_APP_AWS_PROJECT_REGION, + aws_cognito_identity_pool_id: import.meta.env + .REACT_APP_AWS_COGNITO_IDENTITY_POOL_ID, + aws_cognito_region: import.meta.env.REACT_APP_AWS_COGNITO_REGION, + aws_user_pools_id: import.meta.env.REACT_APP_AWS_USER_POOLS_ID, + aws_user_pools_web_client_id: import.meta.env + .REACT_APP_AWS_USER_POOLS_WEB_CLIENT_ID, + oauth: {}, + aws_cognito_username_attributes: import.meta.env + .REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES + ? import.meta.env.REACT_APP_AWS_COGNITO_USERNAME_ATTRIBUTES.split(',') + : [], + aws_cognito_social_providers: import.meta.env + .REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS + ? import.meta.env.REACT_APP_AWS_COGNITO_SOCIAL_PROVIDERS.split(',') + : [], + aws_cognito_signup_attributes: import.meta.env + .REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES + ? import.meta.env.REACT_APP_AWS_COGNITO_SIGNUP_ATTRIBUTES.split(',') + : [], + aws_cognito_mfa_configuration: import.meta.env + .REACT_APP_AWS_COGNITO_MFA_CONFIGURATION, + aws_cognito_mfa_types: import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES + ? import.meta.env.REACT_APP_AWS_COGNITO_MFA_TYPES.split(',') + : [], + aws_cognito_password_protection_settings: { + passwordPolicyMinLength: import.meta.env + .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYMINLENGTH, + passwordPolicyCharacters: import.meta.env + .REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS + ? import.meta.env.REACT_APP_AWS_COGNITO_PASSWORD_PROTECTION_SETTINGS_PASSWORDPOLICYCHARACTERS.split( + ',' + ) + : [] + }, + aws_cognito_verification_mechanisms: import.meta.env + .REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS + ? import.meta.env.REACT_APP_AWS_COGNITO_VERIFICATION_MECHANISMS.split(',') + : [], + aws_user_files_s3_bucket: import.meta.env.REACT_APP_AWS_USER_FILES_S3_BUCKET, + aws_user_files_s3_bucket_region: import.meta.env + .REACT_APP_AWS_USER_FILES_S3_BUCKET_REGION +}; + +export default awsmobile; diff --git a/tsconfig.json b/tsconfig.json index a9416b059..c3c94164c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,38 +1,38 @@ -{ - "compilerOptions": { - "target": "ESNext", - "lib": ["dom", "dom.iterable", "esnext"], - "types": ["vite/client", "vite-plugin-svgr/client"], - "allowJs": false, - "skipLibCheck": false, - "esModuleInterop": false, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "outDir": "./app/dist/", - "sourceMap": true, - "noImplicitAny": false, - "noImplicitThis": false, - "strictNullChecks": false, - "allowSyntheticDefaultImports": true, - "strictFunctionTypes": false, - "typeRoots": [ - "node_modules/@types", - "node_modules/@types/node", - "@aws-amplify/core" - ], - "noEmitOnError": false - }, - // only compile ts files in this directory - "include": [ - "app/src/**/*", - "app/src/interfaces/declarations.d.ts", - "server/**/*" - ] -} +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "types": ["vite/client", "vite-plugin-svgr/client"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "outDir": "./app/dist/", + "sourceMap": true, + "noImplicitAny": false, + "noImplicitThis": false, + "strictNullChecks": false, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "typeRoots": [ + "node_modules/@types", + "node_modules/@types/node", + "@aws-amplify/core" + ], + "noEmitOnError": false + }, + // only compile ts files in this directory + "include": [ + "app/src/**/*", + "app/src/interfaces/declarations.d.ts", + "server/**/*" + ] +} diff --git a/tslint.json b/tslint.json index 6290f91bd..6a4ebd683 100644 --- a/tslint.json +++ b/tslint.json @@ -1,18 +1,18 @@ -{ - "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], - "tslint.autoFixOnSave": true, - "linterOptions": { - "exclude": ["config/**/*.js", "node_modules/**/*.ts"] - }, - "rules": { - "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], - "jsx-boolean-value": false, - "jsx-no-lambda": false, - "jsx-no-multiline-js": false, - "object-literal-sort-keys": false, - "member-ordering": false, - "no-console": false, - "ordered-imports": false, - "comment-format": false - } -} +{ + "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], + "tslint.autoFixOnSave": true, + "linterOptions": { + "exclude": ["config/**/*.js", "node_modules/**/*.ts"] + }, + "rules": { + "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], + "jsx-boolean-value": false, + "jsx-no-lambda": false, + "jsx-no-multiline-js": false, + "object-literal-sort-keys": false, + "member-ordering": false, + "no-console": false, + "ordered-imports": false, + "comment-format": false + } +} diff --git a/vite.config.ts b/vite.config.ts index 881a7b7be..f2ab4c6c1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,11 +9,15 @@ export default defineConfig({ build: { outDir: 'build' }, - assetsInclude: ['**/*.PNG'], + optimizeDeps: { + exclude: [ 'electron', 'fs' ] + }, + assetsInclude: ['**/*.png'], server: { port: 8080 }, plugins: [ react(), svgr({ + include: '**/*.svg', svgrOptions: { icon: true // ...svgr options (https://react-svgr.com/docs/options/) diff --git a/webpack.config.js b/webpack.config.js index 843d00f17..1d2b0ee95 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,103 +1,103 @@ -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const path = require('path'); -const Dotenv = require('dotenv-webpack'); -const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); -const BundleAnalyzerPlugin = - require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -module.exports = { - optimization: { - minimize: true, - minimizer: [new TerserPlugin()] - }, - - // since JS can be written for both server / browser, the "target" specifies what environment webpack should write for - target: 'web', // Our app can run without electron - // The entry is where webpack begins assembling the dependency tree - entry: ['./app/src/index.tsx'], // The entry point of our app; these entry points can be named and we can also have multiple if we'd like to split the webpack bundle into smaller files to improve script loading speed between multiple pages of our app - // the output is only created on npm run-prod-build - output: { - path: path.resolve(__dirname, 'app/dist'), // Where all the output files get dropped after webpack is done with them - filename: 'bundle.js' // The name of the webpack bundle that's generated - }, - // If multiple files share the same name but have different extensions, - // webpack will resolve the one with the extension listed first in the array and skip the rest. - resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx'] - }, - plugins: [ - new Dotenv(), - new webpack.DefinePlugin({ - 'import.meta.env.NODE_ENV': JSON.stringify('development') - }) - // new BundleAnalyzerPlugin(), - ], - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'babel-loader', - exclude: /node_modules/ - }, - { - // loads .html files in the app/src directory - test: /\.(html)$/, - include: [path.resolve(__dirname, 'app/src')], - use: { - // exports html as string - loader: 'html-loader', - // specifies how image strings in URL should be processed in the HTML doc - // this will require each each local image specified in the src attribute - options: { - attributes: { - list: [ - { - tag: 'img', - attribute: 'data-src', - type: 'src' - } - ] - } - } - } - }, - // loads .js/jsx files - { - test: /\.jsx?$/, - include: [path.resolve(__dirname, 'app/src')], - loader: 'babel-loader', - // the resolver helps webpack locate the absolute path of imported modules - resolve: { - extensions: ['.js', '.jsx', '.json'] - } - }, - // loads .css files - // Mini CSS extract plugin extracts CSS into seperate files - // creates CSS file per JS file that conatains CSS - // less duplicate complication than extract-text-webpack-plugin - // this loader is only be using used for CSS but could also be used for sass - { - test: /\.css$/, - include: [path.resolve(__dirname, 'app/src')], - use: [ - MiniCssExtractPlugin.loader, - { - loader: 'css-loader' - } - ], - resolve: { - extensions: ['.css'] - } - }, - // loads common image formats - // resolves import/require on a file into a url and emits the file into the output directory - // url loader converts file into base 64 encoded string that can be passed inline into the file rather than be imported from a seperate file - // you can set limits for the file size at which this inline encoding happens, but there is no limit set currently - { - test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/i, - use: 'url-loader' - } - ] - } -}; +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const path = require('path'); +const Dotenv = require('dotenv-webpack'); +const webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); +const BundleAnalyzerPlugin = + require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +module.exports = { + optimization: { + minimize: true, + minimizer: [new TerserPlugin()] + }, + + // since JS can be written for both server / browser, the "target" specifies what environment webpack should write for + target: 'web', // Our app can run without electron + // The entry is where webpack begins assembling the dependency tree + entry: ['./app/src/index.tsx'], // The entry point of our app; these entry points can be named and we can also have multiple if we'd like to split the webpack bundle into smaller files to improve script loading speed between multiple pages of our app + // the output is only created on npm run-prod-build + output: { + path: path.resolve(__dirname, 'app/dist'), // Where all the output files get dropped after webpack is done with them + filename: 'bundle.js' // The name of the webpack bundle that's generated + }, + // If multiple files share the same name but have different extensions, + // webpack will resolve the one with the extension listed first in the array and skip the rest. + resolve: { + extensions: ['.ts', '.tsx', '.js', '.jsx'] + }, + plugins: [ + new Dotenv(), + new webpack.DefinePlugin({ + 'import.meta.env.NODE_ENV': JSON.stringify('development') + }) + // new BundleAnalyzerPlugin(), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'babel-loader', + exclude: /node_modules/ + }, + { + // loads .html files in the app/src directory + test: /\.(html)$/, + include: [path.resolve(__dirname, 'app/src')], + use: { + // exports html as string + loader: 'html-loader', + // specifies how image strings in URL should be processed in the HTML doc + // this will require each each local image specified in the src attribute + options: { + attributes: { + list: [ + { + tag: 'img', + attribute: 'data-src', + type: 'src' + } + ] + } + } + } + }, + // loads .js/jsx files + { + test: /\.jsx?$/, + include: [path.resolve(__dirname, 'app/src')], + loader: 'babel-loader', + // the resolver helps webpack locate the absolute path of imported modules + resolve: { + extensions: ['.js', '.jsx', '.json'] + } + }, + // loads .css files + // Mini CSS extract plugin extracts CSS into seperate files + // creates CSS file per JS file that conatains CSS + // less duplicate complication than extract-text-webpack-plugin + // this loader is only be using used for CSS but could also be used for sass + { + test: /\.css$/, + include: [path.resolve(__dirname, 'app/src')], + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader' + } + ], + resolve: { + extensions: ['.css'] + } + }, + // loads common image formats + // resolves import/require on a file into a url and emits the file into the output directory + // url loader converts file into base 64 encoded string that can be passed inline into the file rather than be imported from a seperate file + // you can set limits for the file size at which this inline encoding happens, but there is no limit set currently + { + test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/i, + use: 'url-loader' + } + ] + } +}; diff --git a/webpack.development.js b/webpack.development.js index 4b6bd645c..91213c77e 100644 --- a/webpack.development.js +++ b/webpack.development.js @@ -1,49 +1,49 @@ -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import MiniCssExtractPlugin from 'mini-css-extract-plugin'; -import merge from 'webpack-merge'; -import baseConfig from './webpack.config.js'; -import path from 'path'; -import createNonce from './app/src/utils/createNonce.ts'; -import { DEV_PORT } from './config.js'; - -// merges webpack.config.js with development specific configs -const config = merge(baseConfig, { - // sets import.meta.env.NODE_ENV to 'development; - mode: 'development', - devtool: 'inline-source-map', - devServer: { - headers: { 'Access-Control-Allow-Origin': '*' }, - historyApiFallback: true, - host: 'localhost', - port: '8080', - hot: true, // Hot-reload this server if changes are detected - compress: true, // Compress (gzip) files that are served - proxy: { - '/demoRender': { - target: `http://localhost:${DEV_PORT}/` - }, - '/user-styles': { - target: `http://localhost:${DEV_PORT}/` - }, - '/auth/**': { - target: 'http://localhost:5656/' - }, - '/**': { - target: 'http://localhost:5656/' - } - } - }, - plugins: [ - // miniCssExtractPlugin is included here because it's used as a loader in wepack.config.js - new MiniCssExtractPlugin(), - // simplifies creation of HTML files that serve webpack bundles - // creates a index.html file in the dist folder using index.html as a template - new HtmlWebpackPlugin({ - template: path.resolve(__dirname, 'app/src/public/index.ejs'), - filename: 'index.html', - nonce: createNonce() - }) - ] -}); - -export default config; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import merge from 'webpack-merge'; +import baseConfig from './webpack.config.js'; +import path from 'path'; +import createNonce from './app/src/utils/createNonce.ts'; +import { DEV_PORT } from './config.js'; + +// merges webpack.config.js with development specific configs +const config = merge(baseConfig, { + // sets import.meta.env.NODE_ENV to 'development; + mode: 'development', + devtool: 'inline-source-map', + devServer: { + headers: { 'Access-Control-Allow-Origin': '*' }, + historyApiFallback: true, + host: 'localhost', + port: '8080', + hot: true, // Hot-reload this server if changes are detected + compress: true, // Compress (gzip) files that are served + proxy: { + '/demoRender': { + target: `http://localhost:${DEV_PORT}/` + }, + '/user-styles': { + target: `http://localhost:${DEV_PORT}/` + }, + '/auth/**': { + target: 'http://localhost:5656/' + }, + '/**': { + target: 'http://localhost:5656/' + } + } + }, + plugins: [ + // miniCssExtractPlugin is included here because it's used as a loader in wepack.config.js + new MiniCssExtractPlugin(), + // simplifies creation of HTML files that serve webpack bundles + // creates a index.html file in the dist folder using index.html as a template + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'app/src/public/index.ejs'), + filename: 'index.html', + nonce: createNonce() + }) + ] +}); + +export default config; diff --git a/webpack.production.js b/webpack.production.js index fa23ecbe5..49435e1ae 100644 --- a/webpack.production.js +++ b/webpack.production.js @@ -1,23 +1,23 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const merge = require('webpack-merge'); -const base = require('./webpack.config'); -const path = require('path'); -const nonce = require('./app/src/utils/createNonce.ts')(); - -// merges webpack.config.js with production specific configs -module.exports = merge(base, { - // sets import.meta.env.NODE_ENV to 'production' - mode: 'production', - plugins: [ - // miniCssExtractPlugin is included here because it's used as a loader in wepack.config.js - new MiniCssExtractPlugin(), - // simplifies creation of HTML files that serve webpack bundles - // creates a index.html file in the dist folder using index.html as a template - new HtmlWebpackPlugin({ - template: path.resolve(__dirname, 'app/src/public/index-prod.ejs'), - filename: 'index-prod.html', - nonce: nonce - }) - ] -}); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const merge = require('webpack-merge'); +const base = require('./webpack.config'); +const path = require('path'); +const nonce = require('./app/src/utils/createNonce.ts')(); + +// merges webpack.config.js with production specific configs +module.exports = merge(base, { + // sets import.meta.env.NODE_ENV to 'production' + mode: 'production', + plugins: [ + // miniCssExtractPlugin is included here because it's used as a loader in wepack.config.js + new MiniCssExtractPlugin(), + // simplifies creation of HTML files that serve webpack bundles + // creates a index.html file in the dist folder using index.html as a template + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'app/src/public/index-prod.ejs'), + filename: 'index-prod.html', + nonce: nonce + }) + ] +});