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/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/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/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/left/ElementsContainer.tsx b/app/src/components/left/ElementsContainer.tsx index d7ab77c9e..7674b9d37 100644 --- a/app/src/components/left/ElementsContainer.tsx +++ b/app/src/components/left/ElementsContainer.tsx @@ -68,10 +68,12 @@ const ElementsContainer = (props): JSX.Element => { > {' '} - { // moved ComponentDrag to DragDropPanel - /*
+ { + // moved ComponentDrag to DragDropPanel + /*
-
*/} +
*/ + } ); }; 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/right/ComponentPanelItem.tsx b/app/src/components/right/ComponentPanelItem.tsx index a8d8bf086..bc11936b2 100644 --- a/app/src/components/right/ComponentPanelItem.tsx +++ b/app/src/components/right/ComponentPanelItem.tsx @@ -76,9 +76,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 &&
} @@ -101,16 +101,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 c416c24bf..030f0d472 100644 --- a/app/src/components/right/ComponentPanelRoutingItem.tsx +++ b/app/src/components/right/ComponentPanelRoutingItem.tsx @@ -1,128 +1,128 @@ -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'; - -/** - * `ComponentPanelRoutingItem` represents a routing item in a component panel, specifically for Next.js mode. - * It facilitates the creation of navigational links between pages by providing a drag-and-drop interface. - * Users can select from a list of root components (pages) to set up navigation routes within the application. - * - * This component fetches root components from the Redux store, presents them in a dropdown for user selection, - * and enables dragging these as route links to be dropped into a design canvas. - * - * @returns {JSX.Element} A grid item that contains a dropdown of navigable components and supports drag-and-drop. - */ -const ComponentPanelRoutingItem: React.FC<{}> = (): JSX.Element => { - 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'; + +/** + * `ComponentPanelRoutingItem` represents a routing item in a component panel, specifically for Next.js mode. + * It facilitates the creation of navigational links between pages by providing a drag-and-drop interface. + * Users can select from a list of root components (pages) to set up navigation routes within the application. + * + * This component fetches root components from the Redux store, presents them in a dropdown for user selection, + * and enables dragging these as route links to be dropped into a design canvas. + * + * @returns {JSX.Element} A grid item that contains a dropdown of navigable components and supports drag-and-drop. + */ +const ComponentPanelRoutingItem: React.FC<{}> = (): JSX.Element => { + 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/top/NavBarButtons.tsx b/app/src/components/top/NavBarButtons.tsx index 5b7f56e2c..d6e3ee807 100644 --- a/app/src/components/top/NavBarButtons.tsx +++ b/app/src/components/top/NavBarButtons.tsx @@ -14,7 +14,6 @@ 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'; @@ -106,7 +105,6 @@ const StyledMenuItem = withStyles((theme) => ({ */ const navbarDropDown = (props): JSX.Element => { const dispatch = useDispatch(); - const [modal, setModal] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null); const classes = useStyles(); @@ -121,6 +119,8 @@ const navbarDropDown = (props): JSX.Element => { const closeModal = () => setModal(''); const handleClick = (event) => { setAnchorEl(event.currentTarget); + + props.setDropDownMenu(true) }; const clearWorkspace = () => { @@ -204,7 +204,7 @@ const navbarDropDown = (props): JSX.Element => { return (
- + - {/* */} + + +

ReacType Tutorial

@@ -166,4 +167,5 @@ const Tutorial: React.FC = (): JSX.Element => { ); }; -export default withRouter(Tutorial); + +export default Tutorial; diff --git a/app/src/tutorial/TutorialPage.tsx b/app/src/tutorial/TutorialPage.tsx index 215dc5eea..cd1281e43 100644 --- a/app/src/tutorial/TutorialPage.tsx +++ b/app/src/tutorial/TutorialPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState , useEffect } from 'react'; import makeStyles from '@mui/styles/makeStyles'; import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; import Pages from './Pages'; @@ -227,4 +227,4 @@ const TutorialPage: React.FC = (props): JSX.Element => { ); }; -export default withRouter(TutorialPage); +export default TutorialPage; 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/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/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/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..13365f17b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,11 +9,12 @@ export default defineConfig({ build: { outDir: 'build' }, - assetsInclude: ['**/*.PNG'], + 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 + }) + ] +});