├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .prettierrc
├── .travis.yml
├── Andrew-Douglas-Hope.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── Visual-Music-GitHub-PR-Guide.pdf
├── package-lock.json
├── package.json
├── project.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── pull_request_template.md
├── server
├── package.json
└── server.js
└── src
├── assets
├── ErrorScreenAssets
│ ├── electric-guitar.svg
│ └── go-back.svg
├── LandingPageAssets
│ ├── bg-image2.gif
│ ├── section-2
│ │ └── blackHairAndBubbles.png
│ └── section-3-icons
│ │ ├── github.svg
│ │ └── linkedIn.svg
├── LoadingAssets
│ ├── LoadingBars.svg
│ ├── LoadingCircle.svg
│ ├── Rolling.svg
│ └── jsSpinners
│ │ ├── LoadingSpinner
│ │ ├── LoadingSpinner.js
│ │ └── LoadingSpinner.module.scss
│ │ └── ProgressSpinner
│ │ ├── CircularIndicator.js
│ │ ├── ProgressSpinner.js
│ │ └── ProgressSpinner.module.scss
├── LogoSVG.svg
├── NavigationAssets
│ └── left-curve-arrow.svg
├── PlayerBarAssets
│ ├── download-icon.svg
│ ├── download-ready-icon.svg
│ ├── pause-icon.svg
│ ├── play-icon.svg
│ ├── progress-bar.svg
│ ├── snapshot-icon-white-dark.svg
│ ├── song-icon.svg
│ └── volume-icon.svg
├── PromptAssets
│ └── close.svg
├── SharingAssets
│ ├── facebook.svg
│ ├── instagram.svg
│ ├── pinterest.svg
│ ├── share-icon.svg
│ └── twitter.svg
├── SuccessScreenAssets
│ ├── back-btn-Icon-success.svg
│ └── electric-guitar-success.svg
├── VisualPanelAssets
│ ├── ItemImg1.png
│ ├── ItemImg2.png
│ ├── ItemImg3.png
│ └── ItemImg4.png
└── mockupImages
│ ├── visual-music-logo
│ ├── Logo Background(1).jpg
│ ├── Logo Background(1).png
│ ├── Logo Background.jpg
│ ├── Logo Background.pdf
│ ├── Logo Background.png
│ ├── Logo Background.svg
│ ├── Logo Black Text(1).jpg
│ ├── Logo Black Text(1).png
│ ├── Logo Black Text.jpg
│ ├── Logo Black Text.pdf
│ ├── Logo Black Text.png
│ ├── Logo Black Text.svg
│ ├── Logo White Text(1).jpg
│ ├── Logo White Text(1).png
│ ├── Logo White Text.jpg
│ ├── Logo White Text.pdf
│ ├── Logo White Text.png
│ └── Logo White Text.svg
│ └── visual-music-mockups
│ ├── Choose Visual Theme.png
│ ├── Close Panel.png
│ ├── End Of Song.png
│ ├── Facebook.png
│ ├── Format Not Supported.png
│ ├── Instagram.png
│ ├── Landing Page.png
│ ├── Log In.png
│ ├── Pinterest.png
│ ├── Play Song.png
│ ├── Screenshot.png
│ ├── Share Visual.png
│ ├── Sign Up.png
│ ├── Theme Preview(1).png
│ ├── Theme Preview.png
│ ├── Twitter.png
│ ├── Upload Song.png
│ ├── error-sharing.png
│ ├── landing-page.png
│ ├── prototype-visual-music.webm
│ └── success-sharing.png
├── components
├── Error
│ ├── Error.js
│ └── Error.module.scss
├── HamburgerToggle
│ ├── HamburgerToggle.js
│ └── HamburgerToggle.module.scss
├── PlayerBar
│ ├── DownloadButton
│ │ └── DownloadButton.js
│ ├── PlayerBar.js
│ ├── PlayerBar.module.scss
│ ├── ScreenshotButton
│ │ └── ScreenshotButton.js
│ └── UploadButton
│ │ └── UploadButton.js
├── ScreenshotModal
│ ├── BackToPlayer.js
│ ├── BackToPlayer.module.scss
│ ├── ScreenshotModal.js
│ ├── ScreenshotModal.module.scss
│ └── SocialIcons
│ │ ├── SocialIcon
│ │ ├── FacebookButton.js
│ │ ├── InstagramButton.js
│ │ ├── PinterestButton.js
│ │ ├── SocialIcon.module.scss
│ │ └── TwitterButton.js
│ │ ├── SocialIcons.js
│ │ └── SocialIcons.module.scss
├── Success
│ ├── Success.js
│ └── Success.module.scss
├── TopNav
│ ├── TopNav.js
│ └── TopNav.module.scss
├── UploadSong
│ ├── UploadSong.jsx
│ └── UploadSong.module.scss
├── VisualPanel
│ ├── VisualPanel.js
│ └── VisualPanel.module.scss
├── Visualizer
│ ├── Visualizer.component.jsx
│ └── Visualizer.module.scss
└── units
│ ├── Button
│ ├── Button.js
│ └── Button.module.scss
│ ├── FormInput
│ ├── formInput.component.jsx
│ └── formInput.module.scss
│ ├── Prompt
│ ├── Prompt.jsx
│ └── Prompt.module.scss
│ ├── Span
│ └── Span.js
│ └── VisualItem
│ ├── VisualItem.js
│ └── VisualItem.module.scss
├── firebase
└── config.js
├── globalScss
├── index.scss
└── variables.scss
├── index.js
├── pages
├── App
│ ├── App.js
│ ├── App.module.scss
│ └── AppBrowser.js
├── AppRouter.js
├── ForgotPassword
│ ├── ForgotPassword.js
│ └── ForgotPasswordPage
│ │ ├── ForgotPasswordPage.component.jsx
│ │ └── ForgotPasswordPage.module.scss
├── LandingPage
│ ├── ContactComponents
│ │ ├── FormComponents
│ │ │ ├── ContactForm.js
│ │ │ └── ContactForm.module.scss
│ │ └── Icons
│ │ │ ├── ContactFormIcons.js
│ │ │ └── ContactFormIcons.module.scss
│ ├── LandingPage.js
│ └── LandingPage.module.scss
├── Login
│ ├── Login.js
│ └── LoginPage
│ │ ├── Login.module.scss
│ │ └── LoginPage.js
├── Register
│ ├── Register.js
│ └── RegisterPage
│ │ ├── Register.module.scss
│ │ └── RegisterPage.jsx
└── ResetPassword
│ ├── resetPasswordPage.component.jsx
│ └── resetPasswordPage.module.scss
├── store
├── actions
│ ├── authActions.js
│ ├── chatActions.js
│ ├── downloadActions.js
│ ├── fullSizeActions.js
│ ├── screenshotActions.js
│ └── songActions.js
├── reducers
│ ├── authReducer
│ │ └── authReducer.js
│ ├── chatReducer
│ │ └── chatReducer.js
│ ├── downloadReducer
│ │ └── downloadReducer.js
│ ├── fullSizeReducer
│ │ └── fullSizeReducer.js
│ ├── initialState.js
│ ├── rootReducer.js
│ ├── screenshotReducer
│ │ └── screenshotReducer.js
│ └── songReducer
│ │ └── songReducer.js
└── store.js
├── utils
├── ShowElementsOnFullSize.js
├── importAllFiles.js
└── timeFormat.js
└── vendor
├── draws
└── drawOne.js
└── sketch.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /package-lock.json
6 | /server/node_modules
7 | /server/package-lock.json
8 | /.pnp
9 | /.idea
10 | .pnp.js
11 | .env.local
12 |
13 | # testing
14 | /coverage
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
30 | .vscode
31 | .vscode/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "semi": true,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
4 | cache:
5 | directories:
6 | - node_modules
7 | script:
8 | - npm run build
9 | deploy:
10 | provider: pages
11 | skip_cleanup: true
12 | github_token: $github_token
13 | local_dir: build
14 | on:
15 | branch: master
--------------------------------------------------------------------------------
/Andrew-Douglas-Hope.md:
--------------------------------------------------------------------------------
1 | ## What are ways of visualizing music?
2 |
3 | I'm going to take your original question title entirely at face value. First off, though, let’s remind ourselves what (were it not for the music industry) music is *really* about: [community](https://www.pinterest.fr/cantillate/diy-music-and-dance-images/).
4 |
5 | Here an ad-hoc (certainly not exhaustive) list of 'ways of visualizing music':
6 |
7 | * notations (semiotics - visualisations of pitch, tempo, rhythm, or -in the case of, say- dance, movement): [Music and Dance Notations (Semiotics)](https://www.pinterest.fr/cantillate/music-and-dance-notations-semiotics/)
8 | * music instrument modelling (including actions such as fingering, chords and ornament): [STATIC World Music Instruments and Models](https://www.pinterest.fr/cantillate/static-world-music-instruments-and-models/)
9 | * music theory (tool) modelling: [STATIC Music Theory Visualization Examples](https://www.pinterest.fr/cantillate/static-music-theory-examples/)
10 | * simple categorisation (from the body of your question): tree structures representing such as genre, instrument families, theory and other hierarchies: [Music Classification Systems](https://www.pinterest.fr/cantillate/music-classification-systems/)
11 | * representation of the underlying physical and mathematical properties (a sort of ‘own bellybutton gazing’): [Music, Physics and Mathematics](https://www.pinterest.fr/cantillate/music-physics-and-mathematics/)
12 | * performance structure: [Music Performance Insights](https://www.pinterest.fr/cantillate/music-performance-insights/)
13 | * cultural (modal) framework:
14 | [STATIC Modal Theory](https://www.pinterest.fr/cantillate/static-modal-theory/)
15 | [STATIC Interval Circles (Chromatic, 5ths etc)](https://www.pinterest.fr/cantillate/static-music-theory-circles/)
16 | [STATIC Rhythm Theory Examples](https://www.pinterest.fr/cantillate/static-rhythm-theory-examples/)
17 | * hybrid visualisations (such as for example, in esoterics: coincidental mappings between, for example, the western 12-tone system and astrological signs): [STATIC Musical Esoterica Examples](https://www.pinterest.fr/cantillate/static-musical-esoterica-examples/)
18 | * use of colour: [Music and Color / Colour](https://www.pinterest.fr/cantillate/music-and-color-colour/)
19 | * purely cosmetic visualisations (the 'useless fractal visualizations' you mention above)
20 | * games: [Music Theory Games](https://www.pinterest.fr/cantillate/music-theory-games/)
21 |
22 | Note the frequent use of the word ‘static’. “Useful” music visualisation is -in a dynamic sense- a more or less greenfield application area.
23 |
24 | Given the vast range of temperaments, intonations, notes or tones per octave, tunings, sound channels (such as number of strings, rows or tubes) and timbres represented by world music instruments, however, dynamic music visualisation represents a HUGE field of study.
25 |
26 | Nevertheless, you can see some videos and screenshots of a proof of concept for a world music aggregator platform linking musical notation with DYNAMIC theory tools and instrument models.
27 |
28 | The aims are to:
29 |
30 | * strengthen musical diversity by providing open-source tool support for person-to-person (now P2P) music learning, worldwide.
31 | * model any world music instrument -by family- directly in the browser, each derived from a generic base.
32 | * link world music instruments and associated theory tools across their entire configuration spectrum, allowing direct visual comparison of the impact of configuration changes.
33 | * ultimately, drive all visualisations directly from source, be that remote (P2P) musician, notation, audio recording, midi or whatever
34 | * the ability to incorporate/represent elements of individual style
35 |
36 | Providing music sources -especially music exchange formats such as MusicXML- can keep pace, such a system may have the potential to display multiple, dynamic, interactive, highly configurable and completely synchronised views in a groundbreaking collaborative, visual and immersive learning environment.
37 |
38 | This system in particular is to be the subject of a crowdfunding campaign bearing the hashtag #WorldMusicInstrumentsAndTheory. It will build on the open source (‘code-reuse-tolerant’) conventions widespread in the data visualisation sector.
39 |
40 | Coming back to the body of your question, many of music’s deeper structures are represented by datasets and patterns, so are directly accessible to data visualization techniques.
41 |
42 | It’s worth keeping in mind, though, that many seasoned traditional / folk virtuosos will have a repertoire of thousands of tunes and an intuitive familiarity with the underlying cultural / modal framework, all this gained *simply by doing*.
43 |
44 | Music and dance have, indeed, held communities together over millennia. Perhaps artificial intelligence (automated learning) will ease or hasten the path to musical virtuosity, but in the meantime I feel it is essential -for our own wellbeing- to restore music’s original, inclusive and social context.
45 |
46 | Moreover, as folk tune or melody history quickly reveals, 1:1 (face-to-face, ear or assimilation) learning is more or less a guarantee of musical diversity. Given community, music *lives*. In a digital age, we may just need to find new ways of tapping into this stream.
47 |
48 | Above all, it is imperative we find a healthy, collaborative counterweight to the factory music monoculture and the catastrophic lifestyle excesses of it’s role models.
49 |
50 | Diversity is all. First step? Visualisation.
51 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 | ## Our Pledge
3 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
4 | Our Standards
5 |
6 | Examples of behavior that contributes to creating a positive environment include:
7 |
8 | * Using welcoming and inclusive language
9 | * Being respectful of differing viewpoints and experiences
10 | * Gracefully accepting constructive criticism
11 | * Focusing on what is best for the community
12 | * Showing empathy towards other community members
13 |
14 | Examples of unacceptable behavior by participants include:
15 |
16 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
17 | * Trolling, insulting/derogatory comments, and personal or political attacks
18 | * Public or private harassment
19 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
20 | * Other conduct which could reasonably be considered inappropriate in a professional setting
21 |
22 | ## Our Responsibilities
23 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
24 |
25 | Project maintainers 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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
26 |
27 | ## Scope
28 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
29 | Enforcement
30 |
31 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project leader at lucierabahi@protonmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
32 |
33 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
34 | Attribution
35 |
36 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4
37 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 |
2 | [lucierabahi](https://github.com/lucierabahi)
3 |
4 | [amaduasko](https://github.com/amaduasko)
5 |
6 | [justinwlin](https://github.com/justinwlin)
7 |
8 | [Vectormike40](https://github.com/Vectormike40)
9 |
10 | [nottoobright](https://github.com/nottoobright)
11 |
12 | [YogiHa](https://github.com/YogiHa)
13 |
14 | [abneha04](https://github.com/abneha04)
15 |
16 | [gmagnenat](https://github.com/gmagnenat)
17 |
18 | [ruth-naza](https://github.com/ruth-naza)
19 |
20 | [Nilkamal](https://github.com/Nilkamal)
21 |
22 | [mubarakshow](https://github.com/mubarakshow)
23 |
24 | [ArnasDickus](https://github.com/ArnasDickus)
25 |
26 | [kpence](https://github.com/kpence)
27 |
28 | [ryanboris](https://github.com/ryanboris)
29 |
30 | [tomneo2004](https://github.com/tomneo2004)
31 |
32 | [JimBratsos](https://github.com/JimBratsos)
33 |
34 | -[@codinggilm](https://github.com/codinggilm)
35 |
36 | [Rahul](https://github.com/kohli6010)
37 |
38 | [Antonio Franceschi](https://github.com/francofle)
39 |
40 | [Kiran Nyayapati](https://github.com/kiran-nyayapati)
41 |
42 | [Andronikus](https://github.com/Andronikus)
43 |
44 | [Matteo Angelotti](https://github.com/matteo1976)
45 |
46 | [Michael Donal](https://github.com/michaelbretagne)
47 |
48 | [Bo Zhao](https://github.com/zbc)
49 |
50 | [Karanveer](https://github.com/kkaranveer6)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Zero To Mastery
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Visual Music WebApp
4 |
5 | An app that converts your favorite music pieces into visual expressions.
6 |
7 | Visual Music is a collaboration project managed by a group of dedicated fellow students. Aimed at all music and arts lovers, it provides the users with a real time visual transcription of a streamed music piece.
8 |
9 | If the visual transcription can be at first of random assignment of colors and shapes, it shouldn't remain so, as **one of the main interest is to transcribe the emotional effects of the music piece**. Therefore, questions will araise as perception is highly cultural and language induced. It might be interesting in a second time to create various themes based on those considerations for the visual transcription.
10 |
11 |
12 |
13 | ## Applications
14 |
15 | The applications could be :
16 |
17 | - the possibility to screen the visual expression as background for parties, concerts and stages,
18 | - accessibility, by giving a visual transcription of music pieces, making them accessible to the hearing impaired,
19 | - creation of original objects, with the possibility of a screen capture going with a downloadable HD print file, the possibility of printing, framing and offering the painting of a song to friends and family,
20 | - tool or interesting experiment for musicologists, linguists, scientists, etc.
21 |
22 | ## Technology/Stack:
23 |
24 | - [Figma](https://www.figma.com/): to design the UI
25 | - [Storybook](https://storybook.js.org/): to test and export the UI components for React
26 | - [React](https://reactjs.org/): to build the UI
27 | - [p5](https://p5js.org/): for visualization
28 | - using [react-p5-wrapper](https://www.npmjs.com/package/react-p5-wrapper))
29 | - [Firebase](https://firebase.google.com/)
30 | - [Travis CI](https://travis-ci.org/): to build and test the deployed app
31 |
32 | **Idea:** We want to analyse the data from an uploaded mp3 song, and process it through p5.js. We are not building a player, and not building a simple visualizer: we want to think on how to transcribe the music in an artistic way.
33 |
34 | This means that a dive into visual music theory is necessary. Understanding music theory would allow us to build features such as offering different themes/options based on:
35 |
36 | - the **notes**
37 | - color of tone = amount of overtones
38 | - frequency/pitch/hertz
39 | - volume/amplitude/decibels
40 | - the **character** of the music, which can be specified by 3 components:
41 | - tone of the music piece (major, minor)
42 | - the tempo / character
43 | - the bar / measure
44 | - the **rhythm** which can generally be identified from the repetition of different components:
45 | - notes
46 | - duration of notes
47 | - sequencing of notes
48 | --> This is ultimately the musical expression of the score: a score contains both the notes to play, the duration they must have and of course in what order and how fast play them
49 |
50 | ## Useful Links
51 |
52 | [Zero to Mastery - Guidelines on open source](https://github.com/zero-to-mastery/start-here-guidelines)
53 | [CONTRIBUTING.md](https://github.com/zero-to-mastery/visual-music/blob/master/CONTRIBUTING.md)
54 | [GitHub and PR Guide](https://github.com/zero-to-mastery/visual-music/blob/master/Visual-Music-GitHub-PR-Guide.pdf)
55 | [Wiki](https://github.com/zero-to-mastery/visual-music/wiki)
56 | [Application Mock-ups](https://github.com/zero-to-mastery/visual-music/wiki/Application-Mock-ups)
57 |
58 | ## Functionalities
59 |
60 | ### Version 1.0
61 |
62 | - Landing page
63 |
64 | - parallax effect ->
65 | Features
66 | About us
67 | Contact form
68 | Social media (links)
69 | Login/Register/Reset password
70 |
71 | - Sign up screen
72 | - Login screen
73 | - Reset password screen
74 | - Onboarding user screen
75 | - Uploading song screen
76 | - Visual themes panel
77 | - Preview visual screen
78 | - Player page
79 | - Snapshot screen
80 | - Share on social media screen
81 | - Download visual screen
82 |
83 | ### Features
84 |
85 | - Upload mp3 song
86 | - Export/download HD print file
87 | - Share screenshots on social media
88 | - Various built-in themes for user to choose from
89 |
90 | ## Release & Launch
91 |
92 | Visual Music is being built with the intention of releasing and maintaining the project in the real world. Therefore providing contributors with a great opportunity to expand on and learn new skills, being part of a project that can be included on resumes and showed off to friends, family and potential employers.
93 |
94 | A survey for team members is on the Trello board to discuss where and how the app should be released.
95 |
96 | - ZTM Discord
97 | - LinkedIn
98 | - ...
99 |
--------------------------------------------------------------------------------
/Visual-Music-GitHub-PR-Guide.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/Visual-Music-GitHub-PR-Guide.pdf
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "visual-music-p5-react-wrapper",
3 | "homepage": "https://zero-to-mastery.github.io/visual-music/",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "detect-file-type": "^0.2.7",
8 | "dotenv": "^8.2.0",
9 | "emailjs-com": "^2.4.0",
10 | "firebase": "^7.8.2",
11 | "node-sass": "^4.13.1",
12 | "p5": "^0.9.0",
13 | "react": "^16.11.0",
14 | "react-dom": "^16.11.0",
15 | "react-firebase-file-uploader": "^2.4.3",
16 | "react-measure": "^2.3.0",
17 | "react-p5-wrapper": "^2.0.0",
18 | "react-redux": "^7.1.3",
19 | "react-redux-firebase": "^2.4.1",
20 | "react-reveal": "^1.2.2",
21 | "react-router-dom": "^5.1.2",
22 | "react-scripts": "^3.4.0",
23 | "react-simple-soundcloud-widget": "0.0.5",
24 | "react-spring": "^8.0.27",
25 | "redux": "^4.0.4",
26 | "redux-firestore": "^0.9.0",
27 | "redux-thunk": "^2.3.0",
28 | "socket.io-client": "^2.3.0"
29 | },
30 | "scripts": {
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "test": "react-scripts test",
34 | "format": "prettier --write \"**/*.{js,css, scss}\"",
35 | "eject": "react-scripts eject",
36 | "predeploy": "npm run build",
37 | "deploy": "gh-pages -d build"
38 | },
39 | "eslintConfig": {
40 | "extends": "react-app"
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | },
54 | "devDependencies": {
55 | "gh-pages": "^2.1.1",
56 | "prettier": "^1.19.1"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "repoName": "visual-music",
3 | "tagline": "An app that converts your favorite music pieces into visual expressions.",
4 | "color": "#e8c123",
5 | "logo": "https://raw.githubusercontent.com/zero-to-mastery/visual-music/master/visual-music-logo/Logo%20Background(1).png",
6 | "channelName": "visual-music",
7 | "desc": [
8 | "Visual Music webapp provides the users with a real time visual transcription of a streamed music piece. \nIf the visual transcription can be at first of random assignment of colors and shapes, it shouldn't remain so, as one of the main interest is to transcribe the emotional effects of the music piece.",
9 | "__**The applications could be:**__ \n:small_orange_diamond: Screening the visual expression, \n:small_orange_diamond: Hearing impaired accessibility, \n:small_orange_diamond: Downloadable artistic pieces, \n:small_orange_diamond: Tools for visualization theory \nFor more indepth use case [see here](https://github.com/zero-to-mastery/visual-music#applications)",
10 | "__**Stack:**__ \n:small_blue_diamond: Figma \n:small_blue_diamond: Storybook \n:small_blue_diamond: React \n:small_blue_diamond: p5 \n:small_blue_diamond: react-p5-wrapper \n:small_blue_diamond: Firebase",
11 | "Whilst working on various features, including data visualization and digital signal processing, you will gain valuable experience in collaborating and working with version control and source code management (Github), which are extremely valuable skills."
12 | ],
13 | "team": [
14 | ["arnas", "Project Lead"],
15 | ["nab", "Design Lead"]
16 | ],
17 | "links": [
18 | ["README.md", "https://github.com/zero-to-mastery/visual-music/blob/master/README.md"],
19 | ["CONTRIBUTING.md", "https://github.com/zero-to-mastery/visual-music/blob/master/CONTRIBUTING.md"],
20 | ["GitHub & PR Guide", "https://github.com/zero-to-mastery/visual-music/blob/master/Visual-Music-GitHub-PR-Guide.pdf"],
21 | ["Wiki", "https://github.com/zero-to-mastery/visual-music/wiki"]
22 | ],
23 | "poi": [
24 | ["README.md", "https://github.com/zero-to-mastery/visual-music/blob/master/README.md"],
25 | ["CONTRIBUTING.md", "https://github.com/zero-to-mastery/visual-music/blob/master/CONTRIBUTING.md"],
26 | ["GitHub & PR Guide", "https://github.com/zero-to-mastery/visual-music/blob/master/Visual-Music-GitHub-PR-Guide.pdf"],
27 | ["Wiki", "https://github.com/zero-to-mastery/visual-music/wiki"]
28 | ],
29 | "tasks": {
30 | "title": "Trello Tasks",
31 | "desc": "We use Trello to manage the tasks within this project, [here](https://trello.com/invite/b/tdMghU6g/909dd5caa6897b3564179cff590f799f/visual-music-webapp) is your invitation. Further information on how to use Trello and claim a task can be found in the [CONTRIBUTING.md](https://github.com/zero-to-mastery/visual-music/blob/master/CONTRIBUTING.md) file on Github."
32 | },
33 | "welcomeMsg":"Please take a moment to read through the pinned messages. Here you will find important notes and how to get stated with the project!"
34 | }
35 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
21 |
22 |
31 | Visual Music | React Wrapper for p5
32 |
33 |
34 | You need to enable JavaScript to run this app.
35 |
36 |
46 |
47 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Display Trello task [link](linktoTrello)
2 |
3 | ### Features/Libraries
4 | What Features libraries added.
5 | - Added new library [detect-file-type](https://www.npmjs.com/package/detect-file-type)
6 | To detect file format. It will check file format base on [file signature](https://en.wikipedia.org/wiki/List_of_file_signatures)
7 | - Added feature that allows user to save screenshot.
8 |
9 | ### Notes
10 | - react-firebase-file-uploader: is not acting as I except. file uploader invoked callback of my `handleFileSelected`
11 | function at first time when file selected, but not subsequent file selection. The work around is to rerender file uploader.
12 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon server.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "express": "^4.17.1",
14 | "socket.io": "^2.3.0"
15 | },
16 | "devDependencies": {
17 | "nodemon": "^2.0.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const http = require('http');
3 | const socketIO = require('socket.io');
4 |
5 | const app = express();
6 | const server = http.createServer(app);
7 | const io = socketIO(server);
8 |
9 | let onlineUsers = [];
10 |
11 | io.on('connection', socket => {
12 | socket.on('userLogin', ({ uid, userName }) => {
13 | onlineUsers.push({ uid, userName });
14 | io.sockets.emit('onlineUsers', onlineUsers);
15 | });
16 |
17 | socket.on('userDisconnect', uid => {
18 | onlineUsers = onlineUsers.filter(user => user.uid != uid);
19 | io.sockets.emit('onlineUsers', onlineUsers);
20 | });
21 | });
22 |
23 | server.listen(process.env.PORT || 3001, () => {
24 | console.log(`app is running on port ${process.env.PORT || 3001}`);
25 | });
26 |
--------------------------------------------------------------------------------
/src/assets/ErrorScreenAssets/electric-guitar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/ErrorScreenAssets/go-back.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/LandingPageAssets/bg-image2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/LandingPageAssets/bg-image2.gif
--------------------------------------------------------------------------------
/src/assets/LandingPageAssets/section-2/blackHairAndBubbles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/LandingPageAssets/section-2/blackHairAndBubbles.png
--------------------------------------------------------------------------------
/src/assets/LandingPageAssets/section-3-icons/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/LandingPageAssets/section-3-icons/linkedIn.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/LoadingBars.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/LoadingCircle.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
32 |
50 |
51 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/Rolling.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/jsSpinners/LoadingSpinner/LoadingSpinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './LoadingSpinner.module.scss';
3 |
4 | const LoadingSpinner = props => {
5 | return Loading...
;
6 | };
7 |
8 | export default LoadingSpinner;
9 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/jsSpinners/LoadingSpinner/LoadingSpinner.module.scss:
--------------------------------------------------------------------------------
1 | .loader,
2 | .loader:before,
3 | .loader:after {
4 | background: #ffffff;
5 | -webkit-animation: load1 1s infinite ease-in-out;
6 | animation: load1 1s infinite ease-in-out;
7 | width: 1em;
8 | height: 4em;
9 | }
10 |
11 | .loader {
12 | color: #ffffff;
13 | text-indent: -9999em;
14 | margin: 88px auto;
15 | position: relative;
16 | font-size: 11px;
17 | -webkit-transform: translateZ(0);
18 | -ms-transform: translateZ(0);
19 | transform: translateZ(0);
20 | -webkit-animation-delay: -0.16s;
21 | animation-delay: -0.16s;
22 | }
23 |
24 | .loader:before,
25 | .loader:after {
26 | position: absolute;
27 | top: 0;
28 | content: '';
29 | }
30 |
31 | .loader:before {
32 | left: -1.5em;
33 | -webkit-animation-delay: -0.32s;
34 | animation-delay: -0.32s;
35 | }
36 |
37 | .loader:after {
38 | left: 1.5em;
39 | }
40 |
41 | @-webkit-keyframes load1 {
42 |
43 | 0%,
44 | 80%,
45 | 100% {
46 | box-shadow: 0 0;
47 | height: 4em;
48 | }
49 |
50 | 40% {
51 | box-shadow: 0 -2em;
52 | height: 5em;
53 | }
54 | }
55 |
56 | @keyframes load1 {
57 |
58 | 0%,
59 | 80%,
60 | 100% {
61 | box-shadow: 0 0;
62 | height: 4em;
63 | }
64 |
65 | 40% {
66 | box-shadow: 0 -2em;
67 | height: 5em;
68 | }
69 | }
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/jsSpinners/ProgressSpinner/CircularIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './ProgressSpinner.module.scss';
3 |
4 | const CircularIndicator = ({ value }) => {
5 | const radius = 95;
6 | const maxProgress = radius * Math.PI * 2;
7 | const currentProgress = maxProgress - (maxProgress * value) / 100;
8 | return (
9 | <>
10 |
11 |
19 |
32 |
33 | {`${value}%`}
34 | >
35 | );
36 | };
37 |
38 | export default CircularIndicator;
39 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/jsSpinners/ProgressSpinner/ProgressSpinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularIndicator from './CircularIndicator';
3 | import { ReactComponent as LoadingCircle } from '../../LoadingCircle.svg';
4 | import classes from './ProgressSpinner.module.scss';
5 |
6 | const ProgressSpinner = ({ percentage }) => {
7 | return (
8 |
18 | );
19 | };
20 |
21 | export default ProgressSpinner;
22 |
--------------------------------------------------------------------------------
/src/assets/LoadingAssets/jsSpinners/ProgressSpinner/ProgressSpinner.module.scss:
--------------------------------------------------------------------------------
1 | .notAnimated,
2 | .animated {
3 | display: flex;
4 | justify-content: space-around;
5 | }
6 |
7 | .animated {
8 | animation: uploaded 1s ease-in-out;
9 | animation-fill-mode: forwards;
10 | }
11 |
12 | @keyframes uploaded {
13 | from {
14 | opacity: 1;
15 | }
16 |
17 | to {
18 | opacity: 0;
19 | transform: scale(2.5);
20 | }
21 | }
22 |
23 | .circle {
24 | position: absolute;
25 | width: 200px;
26 | height: 200px;
27 | animation: animate 3s linear infinite;
28 | }
29 |
30 | @keyframes animate {
31 | from {
32 | transform: rotate(0deg);
33 | }
34 |
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
40 | .value {
41 | position: absolute;
42 | color: #ccc;
43 | font-size: 2rem;
44 | align-self: center;
45 | }
--------------------------------------------------------------------------------
/src/assets/NavigationAssets/left-curve-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/download-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/download-ready-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/pause-icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
13 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/play-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/progress-bar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/snapshot-icon-white-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/song-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/PlayerBarAssets/volume-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/PromptAssets/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/SharingAssets/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/SharingAssets/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/SharingAssets/pinterest.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/SharingAssets/share-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/SharingAssets/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/SuccessScreenAssets/back-btn-Icon-success.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/SuccessScreenAssets/electric-guitar-success.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/assets/VisualPanelAssets/ItemImg1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/VisualPanelAssets/ItemImg1.png
--------------------------------------------------------------------------------
/src/assets/VisualPanelAssets/ItemImg2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/VisualPanelAssets/ItemImg2.png
--------------------------------------------------------------------------------
/src/assets/VisualPanelAssets/ItemImg3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/VisualPanelAssets/ItemImg3.png
--------------------------------------------------------------------------------
/src/assets/VisualPanelAssets/ItemImg4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/VisualPanelAssets/ItemImg4.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Background(1).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Background(1).jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Background(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Background(1).png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Background.jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Background.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Black Text(1).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Black Text(1).jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Black Text(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Black Text(1).png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Black Text.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Black Text.jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo Black Text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo Black Text.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo White Text(1).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo White Text(1).jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo White Text(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo White Text(1).png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo White Text.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo White Text.jpg
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-logo/Logo White Text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-logo/Logo White Text.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Choose Visual Theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Choose Visual Theme.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Close Panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Close Panel.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/End Of Song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/End Of Song.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Facebook.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Format Not Supported.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Format Not Supported.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Instagram.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Landing Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Landing Page.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Log In.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Log In.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Pinterest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Pinterest.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Play Song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Play Song.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Screenshot.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Share Visual.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Share Visual.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Sign Up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Sign Up.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Theme Preview(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Theme Preview(1).png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Theme Preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Theme Preview.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Twitter.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/Upload Song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/Upload Song.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/error-sharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/error-sharing.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/landing-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/landing-page.png
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/prototype-visual-music.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/prototype-visual-music.webm
--------------------------------------------------------------------------------
/src/assets/mockupImages/visual-music-mockups/success-sharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zero-to-mastery/visual-music/22841caf109536301ff06b5d8adccdb164eeb78d/src/assets/mockupImages/visual-music-mockups/success-sharing.png
--------------------------------------------------------------------------------
/src/components/Error/Error.js:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Error screen component.
3 |
4 | TODO:
5 | 1. Add functionality to go back icon.
6 | ************************************************************/
7 |
8 | import React from 'react';
9 | import { useDispatch } from 'react-redux';
10 |
11 | // Icons.
12 | import { ReactComponent as GuitarIcon } from '../../assets/ErrorScreenAssets/electric-guitar.svg';
13 | import { ReactComponent as GoBackIcon } from '../../assets/ErrorScreenAssets/go-back.svg';
14 |
15 | import classes from './Error.module.scss';
16 | import { shareScreenshotEnd } from '../../store/actions/screenshotActions';
17 |
18 | const Error = ({ screenshotError }) => {
19 | const dispatch = useDispatch();
20 |
21 | const handleGoBack = () => {
22 | dispatch(shareScreenshotEnd());
23 | };
24 |
25 | return (
26 |
33 |
34 |
35 |
36 |
Oops!
37 | Image couldn’t be shared
38 |
39 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Error;
49 |
--------------------------------------------------------------------------------
/src/components/Error/Error.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .errorScreenShow,
4 | .errorScreenHide {
5 | position: absolute;
6 | top: 0;
7 | width: 100vw;
8 | height: 100%;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | background-color: $grey;
13 | z-index: 20;
14 | }
15 |
16 | .errorScreenShow {
17 | display: flex;
18 | }
19 |
20 | .errorScreenHide {
21 | display: none;
22 | }
23 |
24 | .errorModal {
25 | width: 45em;
26 | height: 60em;
27 | border-radius: 5px;
28 | display: flex;
29 | flex-direction: column;
30 | align-items: center;
31 | justify-content: center;
32 | background-color: $red;
33 | font-size: 10px;
34 |
35 | .guitarIcon {
36 | margin-bottom: 5em;
37 | }
38 |
39 | .errorMessage {
40 | text-align: center;
41 | color: $white;
42 |
43 | h1 {
44 | font-weight: bolder;
45 | font-size: 2.5em;
46 | letter-spacing: 3.5px;
47 | }
48 |
49 | h2 {
50 | font-weight: lighter;
51 | font-size: 1.6em;
52 | letter-spacing: 1.5px;
53 | }
54 | }
55 |
56 | .goBackIcon {
57 | margin-top: 3em;
58 | cursor: pointer;
59 | }
60 | }
61 |
62 | @media only screen and (max-width: 480px) {
63 | .errorModal {
64 | font-size: 10px;
65 | width: 30em;
66 | height: 45em;
67 |
68 | .guitarIcon {
69 | margin-bottom: 3em;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/components/HamburgerToggle/HamburgerToggle.js:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Component represent a hambergur sytle of toggle
3 |
4 | State:
5 | - toggle: Indicate whether hambergur is on/off
6 |
7 | Props:
8 | - initToggle: Set this property true to set initial toggle
9 | state to be on otherwse set to false to set initial toggle
10 | state to be off
11 |
12 | - onClick: callback which is called when hamburger
13 | chicked by user and toggle state changed
14 |
15 | Current features:
16 | - A hambergur style.
17 | - Transition to X when toggle is on.
18 |
19 | ************************************************************/
20 | import React from 'react';
21 | import classes from './HamburgerToggle.module.scss';
22 |
23 | class HamburgerToggle extends React.Component {
24 | constructor() {
25 | super();
26 |
27 | this.state = {
28 | toggle: false
29 | };
30 | }
31 |
32 | componentDidMount() {
33 | const { initToggle } = this.props;
34 | this.setState({ toggle: initToggle });
35 | }
36 |
37 | toggle = () => {
38 | const { onClick } = this.props;
39 |
40 | const newToggleState = !this.state.toggle;
41 |
42 | this.setState({ toggle: newToggleState });
43 | if (onClick) {
44 | onClick(newToggleState);
45 | }
46 | };
47 |
48 | render() {
49 | const { toggle } = this.state;
50 |
51 | return (
52 | {
55 | this.toggle();
56 | }}
57 | >
58 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | }
70 | }
71 |
72 | export default HamburgerToggle;
73 |
--------------------------------------------------------------------------------
/src/components/HamburgerToggle/HamburgerToggle.module.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .hamOverlay{
4 | position: absolute;
5 | background-color: #0D4E67;
6 | border-radius: 4px;
7 | width: 100%;
8 | height: 100%;
9 |
10 | cursor: pointer;
11 | }
12 |
13 | $wrapperHOffset: 10%;
14 | $wrapperVOffset: 20%;
15 | .hamWrapper{
16 | position: absolute;
17 | top: $wrapperVOffset;
18 | bottom: $wrapperVOffset;
19 | left: $wrapperHOffset;
20 | right: $wrapperHOffset;
21 |
22 | & span{
23 | display: block;
24 | position: absolute;
25 | background: #EEEEEE;
26 | border-radius: 2px;
27 | width: 100%;
28 | height: 20%;
29 | left: 0;
30 | right: 0;
31 |
32 | transition: all .25s ease-in-out;
33 | }
34 |
35 | & span:first-child{
36 | top: 0;
37 | }
38 |
39 | & span:nth-child(2){
40 | top: 40%;
41 | }
42 |
43 | & span:nth-child(3){
44 | top: 40%;
45 | opacity: 0;
46 | }
47 |
48 | & span:last-child{
49 | bottom: 0;
50 | }
51 |
52 | &.toggle{
53 |
54 | & span:first-child{
55 | transform: scale(0);
56 | opacity: 0;
57 | }
58 |
59 | & span:last-child{
60 | transform: scale(0);
61 | opacity: 0;
62 | }
63 |
64 | & span:nth-child(2){
65 | transform: rotate(45deg);
66 | }
67 |
68 | & span:nth-child(3){
69 | opacity: 1;
70 | transform: rotate(-45deg);
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/components/PlayerBar/DownloadButton/DownloadButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { ReactComponent as DownloadIcon } from '../../../assets/PlayerBarAssets/download-icon.svg';
4 | import { ReactComponent as DownloadIconReady } from '../../../assets/PlayerBarAssets/download-ready-icon.svg';
5 | import { downloadVisualStart } from '../../../store/actions/downloadActions';
6 |
7 | export default function DownloadButton({ isSongEnded, isPlaying }) {
8 | const dispatch = useDispatch();
9 |
10 | const onDownloadVisual = () => {
11 | dispatch(downloadVisualStart());
12 | };
13 | return (
14 | <>
15 | {isSongEnded && !isPlaying ? (
16 |
17 | ) : (
18 |
19 | )}
20 | >
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/PlayerBar/PlayerBar.module.scss:
--------------------------------------------------------------------------------
1 | .playerBar {
2 | background-color: #0d4e67;
3 | display: flex;
4 | flex-direction: row;
5 | width: 100vw;
6 | align-items: center;
7 | justify-content: flex-start;
8 | padding: 0 15px;
9 | height: 64px;
10 | font-size: 12px;
11 | max-width: 100%;
12 | opacity: 1;
13 | position: relative;
14 | }
15 | .hideBar {
16 | opacity: 0;
17 | }
18 |
19 | .playButton {
20 | cursor: pointer;
21 | }
22 |
23 | .icon,
24 | .playButton,
25 | .volumeButton,
26 | .snapshotButton,
27 | .downloadButton {
28 | display: flex;
29 | align-items: center;
30 | }
31 |
32 | .volumeButton:hover .volumeSlider {
33 | left: 0.6rem;
34 | opacity: 1;
35 | }
36 |
37 | .volumeSlider {
38 | left: 0;
39 | transition: all 0.3s ease-in-out;
40 | opacity: 0;
41 | flex-grow: 1;
42 | background-color: rgba(255, 255, 255, 0.25);
43 | cursor: pointer;
44 | position: relative;
45 | border-radius: 5px;
46 | margin: 0 16px;
47 | }
48 |
49 | .nowPlaying {
50 | display: flex;
51 | flex-direction: row;
52 | align-items: center;
53 | width: 20%;
54 | }
55 |
56 | .songTitle {
57 | font-family: Biryani, sans-serif;
58 | font-weight: 600;
59 | color: #ffffff;
60 | margin-left: 7px;
61 | white-space: pre;
62 | overflow: hidden;
63 | text-overflow: ellipsis;
64 | }
65 |
66 | .playerControls {
67 | display: flex;
68 | flex-direction: row;
69 | align-items: center;
70 | width: 55%;
71 | justify-content: space-between;
72 | padding: 0 24px;
73 |
74 | .volume {
75 | .volumeButton {
76 | cursor: pointer;
77 | }
78 | }
79 | }
80 |
81 | .controls {
82 | color: #ffffff;
83 | display: flex;
84 | flex-grow: 1;
85 | justify-content: space-between;
86 | align-items: center;
87 | margin: 0 24px;
88 | height: 100%;
89 |
90 | .progressTime {
91 | font-family: Ubuntu, sans-serif;
92 | font-weight: 400;
93 | font-size: 14px;
94 | line-height: 16px;
95 | }
96 |
97 | .slider {
98 | flex-grow: 1;
99 | background-color: rgba(255, 255, 255, 0.25);
100 | position: relative;
101 | border-radius: 5px;
102 | height: 10px;
103 | margin: 0 16px;
104 | overflow: hidden;
105 |
106 | .progress {
107 | background-color: rgba(255, 255, 255, 0.8);
108 | border-radius: inherit;
109 | position: absolute;
110 | pointer-events: none;
111 | height: 100%;
112 | }
113 |
114 | .rangeInput {
115 | width: 100%;
116 | opacity: 0;
117 | cursor: pointer;
118 | }
119 | }
120 | }
121 |
122 | .playerBarRight {
123 | display: flex;
124 | flex-direction: row;
125 | align-items: center;
126 | justify-content: space-around;
127 | width: 25%;
128 |
129 | .download {
130 | display: flex;
131 | flex-direction: row;
132 | align-items: center;
133 | justify-content: space-around;
134 | width: 25%;
135 | margin-left: auto;
136 | margin-right: 10%;
137 |
138 | .snapshotButton,
139 | .downloadButton {
140 | cursor: pointer;
141 |
142 | svg:active {
143 | fill: #ccc;
144 | }
145 | }
146 | }
147 | }
148 |
149 | .uploadButton {
150 | font-family: Biryani, sans-serif;
151 | background-color: transparent;
152 | padding: 0.5em 1em;
153 | border: 0.05em solid #ffffff;
154 | border-radius: 5px;
155 | color: #ffffff;
156 | cursor: pointer;
157 | font-weight: 300;
158 | display: flex;
159 | align-items: stretch;
160 |
161 | &:hover {
162 | background-color: #ffffff;
163 | color: #0d4e67;
164 | }
165 | }
166 |
167 | .songContainer {
168 | overflow: hidden;
169 | position: relative;
170 | cursor: pointer;
171 | display: flex;
172 | align-items: stretch;
173 |
174 | & [type='file'] {
175 | cursor: pointer;
176 | display: block;
177 | height: 100%;
178 | width: 100%;
179 | left: 0;
180 | top: 0;
181 | outline: 0;
182 | opacity: 0;
183 | position: absolute;
184 | z-index: -1;
185 | }
186 | }
187 |
188 | .setSizeIcon {
189 | cursor: pointer;
190 | transition: all 0.3s ease-in-out;
191 | height: 2.2rem;
192 | width: 2.2rem;
193 | margin-left: 0.7rem;
194 | }
195 |
196 | .fullSizeIcon:hover {
197 | transform: scale(0.7);
198 | }
199 |
200 | .normalSizeIcon:hover {
201 | transform: scale(1.3);
202 | }
203 | @media only screen and (max-width: 900px) {
204 | .playerBarRight {
205 | width: 30%;
206 |
207 | .download {
208 | width: 50%;
209 | }
210 | }
211 |
212 | .uploadButton {
213 | width: 80%;
214 | text-align: center;
215 | }
216 |
217 | .player-controls {
218 | width: 50%;
219 | }
220 | }
221 |
222 | @media screen and (max-width: 700px) {
223 | .icon,
224 | .volumeButton,
225 | .playerBarRight .download {
226 | display: none;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/components/PlayerBar/ScreenshotButton/ScreenshotButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { takeScreenshot } from '../../../store/actions/screenshotActions';
4 | import { ReactComponent as SnapshotIcon } from '../../../assets/PlayerBarAssets/snapshot-icon-white-dark.svg';
5 |
6 | const ScreenshotButton = () => {
7 | const dispatch = useDispatch();
8 |
9 | const takeScreenshotHandler = () => {
10 | dispatch(takeScreenshot());
11 | };
12 | return ;
13 | };
14 |
15 | export default ScreenshotButton;
16 |
--------------------------------------------------------------------------------
/src/components/PlayerBar/UploadButton/UploadButton.js:
--------------------------------------------------------------------------------
1 | // ToDo - create some desigen to the button
2 |
3 | import React from 'react';
4 | import { useDispatch } from 'react-redux';
5 | import { clearSong } from '../../../store/actions/songActions';
6 |
7 | export default function UploadButton({ classes }) {
8 | const dispatch = useDispatch();
9 |
10 | return (
11 |
12 |
13 | Upload New Song
14 | {
18 | dispatch(clearSong());
19 | }}
20 | />
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/BackToPlayer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { clearScreenshot } from '../../store/actions/screenshotActions';
4 | import { ReactComponent as LeftArrow } from '../../assets/NavigationAssets/left-curve-arrow.svg';
5 | import classes from './BackToPlayer.module.scss';
6 |
7 | const BackToPlayer = ({ hideSocialIcons }) => {
8 | const dispatch = useDispatch();
9 |
10 | const backToPlayerHandler = () => {
11 | hideSocialIcons();
12 | dispatch(clearScreenshot());
13 | };
14 | return (
15 |
16 |
Back to Player
17 |
18 |
19 | );
20 | };
21 |
22 | export default BackToPlayer;
23 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/BackToPlayer.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .backToPlayer {
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | font-size: 1rem;
8 | font-weight: 900;
9 | color: $white;
10 | cursor: pointer;
11 |
12 | &:active {
13 | color: $light-grey;
14 |
15 | svg {
16 | path {
17 | fill: $light-grey;
18 | }
19 | }
20 | }
21 |
22 | .backArrowIcon {
23 | width: 2.5rem;
24 | height: 2.5rem;
25 | margin-left: 1rem;
26 | padding: 0.5rem;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/ScreenshotModal.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ReactComponent as ShareIcon } from '../../assets/SharingAssets/share-icon.svg';
3 | import DefaultImg from '../../assets/LandingPageAssets/bg-image2.gif';
4 | import BackToPlayer from './BackToPlayer';
5 | import SocialIcons from './SocialIcons/SocialIcons';
6 | import classes from './ScreenshotModal.module.scss';
7 |
8 | const ScreenshotModal = ({ screenshotUrl }) => {
9 | const [showSocialIcons, setShowSocialIcons] = useState(false);
10 |
11 | const showSocialIconsHandler = () => {
12 | setShowSocialIcons(true);
13 | };
14 |
15 | const hideSocialIconsHandler = () => {
16 | setShowSocialIcons(false);
17 | };
18 |
19 | return (
20 |
21 |
26 |
27 | {showSocialIcons ? (
28 |
32 | ) : (
33 |
37 |
Share Visual
38 |
39 |
40 | )}
41 |
42 | {!showSocialIcons && (
43 |
44 | )}
45 |
46 |
47 | );
48 | };
49 |
50 | export default ScreenshotModal;
51 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/ScreenshotModal.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .showModal,
4 | .hideModal {
5 | position: absolute;
6 | top: 0;
7 | left: 0;
8 | bottom: 0;
9 | right: 0;
10 | margin: auto;
11 | background-color: $grey;
12 | display: flex;
13 | justify-content: center;
14 | flex-direction: column;
15 | z-index: 10;
16 | transition: all 0.3s linear;
17 |
18 | .image {
19 | width: 60%;
20 | height: 60%;
21 | align-self: center;
22 | }
23 |
24 | .options {
25 | display: flex;
26 | flex-direction: column;
27 | height: 10%;
28 | margin: 2rem;
29 | align-self: center;
30 |
31 | .shareOption {
32 | display: flex;
33 | justify-content: space-between;
34 | align-items: center;
35 | font-size: 1rem;
36 | font-weight: 900;
37 | color: $white;
38 | cursor: pointer;
39 |
40 | &:active {
41 | color: $light-grey;
42 |
43 | svg {
44 | fill: $light-grey;
45 | }
46 | }
47 |
48 | .shareIcon {
49 | width: 2.5rem;
50 | height: 2.5rem;
51 | margin-left: 1rem;
52 | }
53 | }
54 | }
55 | }
56 |
57 | .showModal {
58 | width: 100%;
59 | height: 100%;
60 | opacity: 1;
61 | }
62 |
63 | .hideModal {
64 | width: 0%;
65 | height: 0%;
66 | opacity: 0;
67 | }
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcon/FacebookButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | shareScreenshotSuccess,
5 | shareScreenshotError
6 | } from '../../../../store/actions/screenshotActions';
7 |
8 | import { ReactComponent as Facebook } from '../../../../assets/SharingAssets/facebook.svg';
9 | import classes from './SocialIcon.module.scss';
10 |
11 | const FacebookButton = ({ url }) => {
12 | const dispatch = useDispatch();
13 |
14 | const facebookHandler = () => {
15 | // Todo: Share on Facebook and get response (success or error)
16 |
17 | let response = false;
18 | if (response) {
19 | dispatch(shareScreenshotSuccess());
20 | } else {
21 | dispatch(shareScreenshotError());
22 | }
23 | };
24 |
25 | return (
26 |
27 | );
28 | };
29 |
30 | export default FacebookButton;
31 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcon/InstagramButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | shareScreenshotSuccess,
5 | shareScreenshotError
6 | } from '../../../../store/actions/screenshotActions';
7 |
8 | import { ReactComponent as Instagram } from '../../../../assets/SharingAssets/instagram.svg';
9 | import classes from './SocialIcon.module.scss';
10 |
11 | const InstagramButton = ({ url }) => {
12 | const dispatch = useDispatch();
13 |
14 | const instagramHandler = () => {
15 | // Todo: Share on Instagram and get response (success or error)
16 |
17 | let response = true;
18 | if (response) {
19 | dispatch(shareScreenshotSuccess());
20 | } else {
21 | dispatch(shareScreenshotError());
22 | }
23 | };
24 |
25 | return (
26 |
27 | );
28 | };
29 |
30 | export default InstagramButton;
31 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcon/PinterestButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | shareScreenshotSuccess,
5 | shareScreenshotError
6 | } from '../../../../store/actions/screenshotActions';
7 |
8 | import { ReactComponent as Pinterest } from '../../../../assets/SharingAssets/pinterest.svg';
9 | import classes from './SocialIcon.module.scss';
10 |
11 | const PinterestButton = ({ url }) => {
12 | const dispatch = useDispatch();
13 |
14 | const pinterestHandler = () => {
15 | // Todo: Share on Pinterest and get response (success or error)
16 |
17 | let response = false;
18 | if (response) {
19 | dispatch(shareScreenshotSuccess());
20 | } else {
21 | dispatch(shareScreenshotError());
22 | }
23 | };
24 |
25 | return (
26 |
27 | );
28 | };
29 |
30 | export default PinterestButton;
31 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcon/SocialIcon.module.scss:
--------------------------------------------------------------------------------
1 | .socialIcon {
2 | margin: 0.5rem;
3 | padding: 0.5rem;
4 | background-color: white;
5 | border-radius: 50%;
6 | cursor: pointer;
7 | transition: all .1s;
8 |
9 | &:active {
10 | background-color: #ccc;
11 | }
12 |
13 | &:hover {
14 | transform: scale(1.1)
15 | }
16 | }
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcon/TwitterButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | shareScreenshotSuccess,
5 | shareScreenshotError
6 | } from '../../../../store/actions/screenshotActions';
7 |
8 | import { ReactComponent as Twitter } from '../../../../assets/SharingAssets/twitter.svg';
9 | import classes from './SocialIcon.module.scss';
10 |
11 | const TwitterButton = ({ url }) => {
12 | const dispatch = useDispatch();
13 |
14 | const twitterHandler = () => {
15 | // Todo: Share on Twitter and get response (success or error)
16 |
17 | let response = false;
18 | if (response) {
19 | dispatch(shareScreenshotSuccess());
20 | } else {
21 | dispatch(shareScreenshotError());
22 | }
23 | };
24 |
25 | return ;
26 | };
27 |
28 | export default TwitterButton;
29 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcons.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import BackToPlayer from '../BackToPlayer';
3 | import FacebookButton from './SocialIcon/FacebookButton';
4 | import PinterestButton from './SocialIcon/PinterestButton';
5 | import InstagramButton from './SocialIcon/InstagramButton';
6 | import TwitterButton from './SocialIcon/TwitterButton';
7 | import Error from '../../Error/Error';
8 | import Success from '../../Success/Success';
9 | import classes from './SocialIcons.module.scss';
10 |
11 | const SocialIcons = ({ hideSocialIcons, url }) => {
12 | const [modal, setModal] = useState(null);
13 |
14 | const error = () => {
15 | console.log('clicked');
16 | setModal( );
17 | };
18 |
19 | const success = () => {
20 | console.log('clicked');
21 | setModal( );
22 | };
23 |
24 | return (
25 | <>
26 |
32 |
33 |
34 |
35 | {modal}
36 | >
37 | );
38 | };
39 |
40 | export default SocialIcons;
41 |
--------------------------------------------------------------------------------
/src/components/ScreenshotModal/SocialIcons/SocialIcons.module.scss:
--------------------------------------------------------------------------------
1 | .socialIconsContainer {
2 | display: flex;
3 | justify-content: space-evenly;
4 |
5 | svg {
6 | width: 2.5rem;
7 | height: 2.5rem;
8 | fill: #0B4E67,
9 | }
10 | }
11 |
12 | .backToPlayer {
13 | position: absolute;
14 | right: 4rem;
15 | bottom: 3rem;
16 | }
--------------------------------------------------------------------------------
/src/components/Success/Success.js:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Success screen component.
3 | ************************************************************/
4 | import React from 'react';
5 | import { useDispatch } from 'react-redux';
6 | import { shareScreenshotEnd } from '../../store/actions/screenshotActions';
7 |
8 | import { ReactComponent as GuitarIcon } from '../../assets/SuccessScreenAssets/electric-guitar-success.svg';
9 | import { ReactComponent as BackIcon } from '../../assets/SuccessScreenAssets/back-btn-Icon-success.svg';
10 |
11 | import classes from './Success.module.scss';
12 |
13 | const Success = ({ screenshotSuccess }) => {
14 | const dispatch = useDispatch();
15 |
16 | const handleGoBack = () => {
17 | dispatch(shareScreenshotEnd());
18 | };
19 | return (
20 |
25 |
26 |
27 |
28 |
29 |
Success!
30 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default Success;
41 |
--------------------------------------------------------------------------------
/src/components/Success/Success.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .overlayShow,
4 | .overlayHide {
5 | position: absolute;
6 | top: 0;
7 | width: 100vw;
8 | height: 100vh;
9 | background-color: $grey;
10 | justify-content: center;
11 | align-items: center;
12 | z-index: 20;
13 | }
14 |
15 | .overlayShow {
16 | display: flex;
17 | }
18 |
19 | .overlayHide {
20 | display: none;
21 | }
22 |
23 | $border-radius: 5px;
24 |
25 | .successOverlay {
26 | position: relative;
27 | width: 45em;
28 | height: 60em;
29 | background-color: $green;
30 | border-radius: $border-radius;
31 |
32 | .gradient {
33 | position: absolute;
34 | width: 100%;
35 | height: 100%;
36 | border-radius: $border-radius;
37 | background: $green;
38 | background: -moz-linear-gradient($green-gradient);
39 | background: -webkit-linear-gradient($green-gradient);
40 | background: linear-gradient($green-gradient);
41 | z-index: 2;
42 | }
43 |
44 | .content {
45 | position: absolute;
46 | width: 100%;
47 | height: 100%;
48 | display: flex;
49 | flex-direction: column;
50 | align-items: center;
51 | justify-content: center;
52 | z-index: 3;
53 |
54 | .guitarIcon {
55 | margin-bottom: 5em;
56 | }
57 |
58 | .title {
59 | font-weight: normal;
60 | font-size: 2.5em;
61 | letter-spacing: 3.5px;
62 | color: $white;
63 | text-align: center;
64 | }
65 |
66 | .backIcon {
67 | margin-top: 3em;
68 | cursor: pointer;
69 | }
70 | }
71 |
72 |
73 | }
--------------------------------------------------------------------------------
/src/components/TopNav/TopNav.js:
--------------------------------------------------------------------------------
1 | /***********************
2 |
3 | Top Navigation Component.
4 |
5 | Current features:
6 | 1. Log In button.
7 | 2. Sign Up button. --Log Out button as ame as Sign Up for now
8 |
9 | TODO: - Black background on scroll.
10 |
11 | ************************/
12 |
13 | import React from 'react';
14 | import { Link, withRouter } from 'react-router-dom';
15 | import classes from './TopNav.module.scss';
16 | import logo from '../../assets/LogoSVG.svg';
17 | import { useSelector, useDispatch } from 'react-redux';
18 | import { logOut, cleanError } from '../../store/actions/authActions';
19 | import Button from '../units/Button/Button';
20 | import ShowElementsOnFullSize from '../../utils/ShowElementsOnFullSize';
21 |
22 | const UnAuthNav = ({ dispatch, pathname }) => (
23 |
24 | {
25 | pathname === '/login'?
26 |
27 | dispatch(cleanError())}
29 | text="Home"
30 | btnClass="logIn"
31 | />
32 |
33 | :
34 |
35 | dispatch(cleanError())}
37 | text="Log In"
38 | btnClass="logIn"
39 | />
40 |
41 | }
42 | {
43 | pathname === '/register'?
44 |
45 | dispatch(cleanError())}
47 | text="Home"
48 | btnClass="signUp"
49 | />
50 |
51 | :
52 |
53 | dispatch(cleanError())}
55 | text="Sign Up"
56 | btnClass="signUp"
57 | />
58 |
59 | }
60 |
61 | );
62 |
63 | const AuthNav = ({ dispatch, pathname }) => {
64 | return (
65 |
66 | {pathname !== '/app' && (
67 |
68 |
69 |
70 | )}
71 | dispatch(logOut())}
75 | />
76 |
77 | );
78 | };
79 |
80 | function TopNav(props) {
81 | const uid = useSelector(state => state.firebase.auth.uid);
82 | const dispatch = useDispatch();
83 | const { isFullSize, isElementsShowed } = useSelector(
84 | state => state.fullSize
85 | );
86 |
87 | return (
88 | <>
89 |
94 |
95 |
96 |
97 | {uid ? (
98 |
102 | ) : (
103 |
107 | )}
108 |
109 | {isFullSize && }
110 | >
111 | );
112 | }
113 |
114 | export default withRouter(TopNav);
115 |
--------------------------------------------------------------------------------
/src/components/TopNav/TopNav.module.scss:
--------------------------------------------------------------------------------
1 | .topNav {
2 | position: fixed;
3 | top: 0;
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | width: 100%;
8 | padding: 25px 3%;
9 | z-index: 1;
10 | opacity: 1;
11 | }
12 |
13 | .hideNav {
14 | opacity: 0;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/UploadSong/UploadSong.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .boxStyle {
4 | display: flex;
5 | width: 100vw;
6 | align-items: center;
7 | justify-content: center;
8 | height: 100vh;
9 | background: $black;
10 | }
11 |
12 | .uploadFunctionalityStyle {
13 | display: flex;
14 | width: 45%;
15 | height: 45%;
16 | padding: 0.5em 1em;
17 | align-items: center;
18 | justify-content: center;
19 | flex-direction: column;
20 | border: dashed 1px $white;
21 | border-radius: 0.4rem;
22 | border-width: 0.1rem;
23 | opacity: 0.5;
24 | }
25 |
26 | .iconStyle {
27 | background: none;
28 | border: none;
29 | outline: none;
30 | display: flex;
31 | cursor: pointer;
32 | margin-bottom: 2rem;
33 | }
34 |
35 | .uploadText {
36 | display: flex;
37 | font-weight: 900;
38 | font-size: 1.8rem;
39 | line-height: 4rem;
40 | text-align: center;
41 | color: $light-grey;
42 | }
43 |
44 | .fileFormatStyle {
45 | display: flex;
46 | font-weight: 900;
47 | font-size: 1.1rem;
48 | text-align: center;
49 | color: $light-grey;
50 | }
51 |
52 | .unsupportBox{
53 | width: 100%;
54 | height: 100%;
55 | display: flex;
56 | justify-content: center;
57 | align-items: center;
58 | background-color: transparent;
59 | }
60 |
61 | .uploadBtn{
62 | cursor: pointer;
63 | }
64 |
65 | @media screen and (max-width: 700px) {
66 | .fileFormatStyle {
67 | font-size: 0.7rem;
68 | margin-top: 2rem;
69 | }
70 |
71 | .uploadText {
72 | font-size: 1rem;
73 | line-height: 2rem;
74 | }
75 | }
--------------------------------------------------------------------------------
/src/components/VisualPanel/VisualPanel.js:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Main component for visualization list
3 |
4 | Props:
5 | - visualList: Type of array. An array contain all visualization data that need to be displayed
6 | - onItemClick: Type of function. A callback function.
7 |
8 | Callback:
9 | - onItemClick: If provided in props. Function take 2 argument and is called
10 | when an item is being clicked.
11 | 1.first argument: index of data in data array which was provided
12 | 2.second argument: data in data array which was provided
13 |
14 | Current features:
15 | - Display visulization list
16 |
17 | ************************************************************/
18 | import React from 'react';
19 | import VisualItem from '../units/VisualItem/VisualItem';
20 | import classes from './VisualPanel.module.scss';
21 |
22 | //data for testing purpose
23 | const listData = [
24 | {
25 | id: 1,
26 | title: 'visual1',
27 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg1.png')
28 | },
29 | {
30 | id: 2,
31 | title: 'visual2',
32 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg2.png')
33 | },
34 | {
35 | id: 3,
36 | title: 'visual3',
37 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg3.png')
38 | },
39 | {
40 | id: 4,
41 | title: 'visual4',
42 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg4.png')
43 | },
44 | {
45 | id: 5,
46 | title: 'visual5',
47 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg1.png')
48 | },
49 | {
50 | id: 6,
51 | title: 'visual6',
52 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg2.png')
53 | },
54 | {
55 | id: 7,
56 | title: 'visual7',
57 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg3.png')
58 | },
59 | {
60 | id: 8,
61 | title: 'visual8',
62 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg4.png')
63 | },
64 | {
65 | id: 9,
66 | title: 'visual9',
67 | imageSrc: require('../../assets/VisualPanelAssets/ItemImg1.png')
68 | }
69 | ];
70 |
71 | const VisualPanel = ({ visualList, onItemClick }) => {
72 | //for test only since no data are given at moment
73 | visualList = visualList ? visualList : listData;
74 |
75 | const onVisualItemClick = index => {
76 | const item = visualList[index];
77 |
78 | if (onItemClick) {
79 | onItemClick(index, item);
80 | }
81 | };
82 |
83 | return (
84 |
85 |
Select Visualization
86 |
87 | {visualList &&
88 | visualList.map((item, index) => {
89 | return (
90 | onVisualItemClick(index)}
95 | />
96 | );
97 | })}
98 |
99 |
100 | );
101 | };
102 |
103 | export default VisualPanel;
104 |
--------------------------------------------------------------------------------
/src/components/VisualPanel/VisualPanel.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .panelWrapper{
3 | // width: 300px;
4 | // height: 756px;
5 | // margin: 30%;
6 | display: flex;
7 | flex-direction: column;
8 | height: 100%;
9 |
10 | .title{
11 | font-size: 1.3rem;
12 | line-height: 2em;
13 | color: #0D4E67;
14 | border-bottom: 2px solid #0D4E67;
15 | margin: 5% 5% 0 5%;
16 | padding: 0 2%;
17 | }
18 |
19 | .visualList{
20 | display: flex;
21 | flex-direction: column;
22 | overflow: scroll;
23 | margin: 5% 0 0% 0;
24 | padding: 0 5% 0 5%;
25 | }
26 |
27 | @media only screen and (max-width:500px){
28 | .title{
29 | font-size: 1rem;
30 | }
31 | }
32 |
33 | @media only screen and (max-width:350px){
34 | .title{
35 | font-size: 0.8rem;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/components/Visualizer/Visualizer.component.jsx:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Main component for music visualization.
3 | Uses react-p5-wrapper to load state values to the p5 canvas.
4 |
5 | Current loaded sketch file is defined in line 18.
6 | It is then passed as props to P5Wrapper component
7 | along with other needed values such as volume, uploaded file, etc.
8 |
9 | Current features:
10 | 1. Display ellipses based on sound's amplitude.
11 |
12 | TODO:
13 |
14 | ************************************************************/
15 |
16 | import React, { useState } from 'react';
17 | import { useDispatch, useSelector } from 'react-redux';
18 | import classes from './Visualizer.module.scss';
19 | import P5Wrapper from 'react-p5-wrapper';
20 | import sketch from '../../vendor/sketch';
21 | import Measure from 'react-measure';
22 |
23 | const Visualizer = React.memo(props => {
24 | const [canvasWidth, setCanvasWidth] = useState(100);
25 | const [canvasHeight, setCanvasHeight] = useState(100);
26 | const isFullSize = useSelector(state => state.fullSize.isFullSize);
27 |
28 | const songCurrentTime = useSelector(state => state.song.currentTime);
29 | const isPlayPressed = useSelector(state => state.song.isPlayPressed);
30 | const songUrl = useSelector(state => state.song.url);
31 | const songBLob = useSelector(state => state.song.blob);
32 |
33 | const onResize = content => {
34 | const { width, height, left, top } = content;
35 | const cWidth = width - left;
36 | const cHeight = height - top;
37 | setCanvasWidth(cWidth);
38 | setCanvasHeight(cHeight);
39 | };
40 |
41 |
42 | const {
43 | volume,
44 | takeScreenshot,
45 | downloadVisual,
46 | } = props;
47 |
48 | return (
49 | {
52 | onResize(content.offset);
53 | }}
54 | >
55 | {({ measureRef }) => (
56 |
76 | )}
77 |
78 | );
79 | });
80 |
81 | export default Visualizer;
82 |
--------------------------------------------------------------------------------
/src/components/Visualizer/Visualizer.module.scss:
--------------------------------------------------------------------------------
1 | .visualizer {
2 | position: absolute;
3 | width: 90%;
4 | height: 86%;
5 | left: 5%;
6 | right: 5%;
7 | top: 7%;
8 | bottom: 7%;
9 | display: flex;
10 | justify-content: flex-end;
11 | align-items: center;
12 | }
13 |
14 | .fullSize {
15 | width: 100%;
16 | margin: 0;
17 | padding: 0;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/units/Button/Button.js:
--------------------------------------------------------------------------------
1 | /***********************
2 |
3 | Reusable button component, accepts text (string), and btnClass (className) as props.
4 | for styles pass either one of the existing classes or write a new one in Button.module.scss.
5 |
6 | TODO: - Add onClick functionality
7 | TODO: - Implement hover animation using react-spring (as we're already using it for parallax)
8 |
9 | ************************/
10 |
11 | import React from 'react';
12 | import classes from './Button.module.scss';
13 |
14 | const Button = ({ text, btnClass, ...otherProps }) => (
15 |
16 | {text}
17 |
18 | );
19 |
20 | export default Button;
21 |
--------------------------------------------------------------------------------
/src/components/units/Button/Button.module.scss:
--------------------------------------------------------------------------------
1 | /* Get started button styles */
2 | button {
3 | border: none;
4 |
5 | &:hover {
6 | cursor: pointer;
7 | }
8 |
9 | &:focus {
10 | outline: none;
11 | }
12 | }
13 |
14 | .getStarted {
15 | display: block;
16 | width: auto;
17 | margin: 8% auto;
18 | padding: 15px 45px;
19 | border: 0.8px solid;
20 | border-radius: 8px;
21 | font-size: 1.1rem;
22 | background-color: #FFFFFF;
23 | color: #0D4E67;
24 | transition: 0.4s;
25 |
26 | &:hover {
27 | background-color: transparent;
28 | color: #FFFFFF;
29 | border: 0.8px solid;
30 | }
31 | }
32 |
33 | /* Sign up button styles plus hover animation */
34 | .signUp {
35 | padding: 10px 30px;
36 | border: 0.5px solid;
37 | border-radius: 5px;
38 | color: #FFFFFF;
39 | font-size: 1rem;
40 |
41 | background: linear-gradient(to right, white 50%, transparent 50%);
42 | background-size: 200% 100%;
43 | background-position: right bottom;
44 | transition: all 0.4s ease-out;
45 |
46 | &:hover {
47 | background-position: left bottom;
48 | color: #000000;
49 | border: 0.5px solid white;
50 | }
51 | }
52 |
53 | /* Log in button styles plus hover animation */
54 | .logIn {
55 | padding: 10px 30px;
56 | background-color: transparent;
57 | color: #FFFFFF;
58 | font-size: 1rem;
59 |
60 | transform: translateZ(0);
61 | backface-visibility: hidden;
62 | position: relative;
63 | overflow: hidden;
64 |
65 | &:before {
66 | content: "";
67 | position: absolute;
68 | left: 20%;
69 | right: 100%;
70 | bottom: 0;
71 | background: #FFFFFF;
72 | height: 2px;
73 | transition: right 0.4s ease-out;
74 | }
75 |
76 | &:hover {
77 | &:before {
78 | right: 20%;
79 | }
80 | }
81 | }
82 |
83 | // .log-in:hover:before {
84 |
85 | // }
86 |
87 | /* App button styles plus hover animation */
88 | .app {
89 | padding: 10px 30px;
90 | background-color: transparent;
91 | color: #FFFFFF;
92 | font-size: 1rem;
93 |
94 | transform: translateZ(0);
95 | backface-visibility: hidden;
96 | position: relative;
97 | overflow: hidden;
98 |
99 | &:before {
100 | content: "";
101 | position: absolute;
102 | left: 20%;
103 | right: 100%;
104 | bottom: 0;
105 | background: #FFFFFF;
106 | height: 2px;
107 | transition: right 0.4s ease-out;
108 | }
109 |
110 | &:hover {
111 | &:before {
112 | right: 20%;
113 | }
114 | }
115 | }
116 |
117 | /* Scrol button styles */
118 | .scroll {
119 | display: block;
120 | margin: 13% auto;
121 | padding: 10px 30px;
122 | background-color: transparent;
123 | color: #FFFFFF;
124 | font-size: 1.3rem;
125 | }
126 |
127 | @media only screen and (min-width: 420px) {
128 | .getStarted {
129 | margin: 0 auto;
130 | }
131 |
132 | .scroll {
133 | margin: 5% auto;
134 | }
135 | }
--------------------------------------------------------------------------------
/src/components/units/FormInput/formInput.component.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * FormInput component
3 | *
4 | * Features:
5 | * - Allow user input
6 | * - Customizable validation for input
7 | * - Show error message when validation fail
8 | * - Input field is highlighted when error is presented
9 | *
10 | * Validation process:
11 | * Component take a property name "validators" and this property
12 | * accept null or an array of validator which define validation.
13 | *
14 | * When input is unfocused the method "validate" is called asynchronously and then
15 | * method go throught each validators to see if validation is fail.
16 | *
17 | * If one of validators is fail then corespond error message is setted to "errorMsg"
18 | * state, Thus error is shown.
19 | *
20 | * Defination of validator:
21 | * Each validator is an object which define two required keys and corespond values
22 | *
23 | * 1) validate: value is a function which take input value. The function must
24 | * return true indicate validation passed otherwise false indicate validation fail
25 | *
26 | * 2) failMsg: string that will be displayed when validation fail.
27 | *
28 | * Look into "minValueValidator" below for example how to create validator.
29 | *
30 | * const minValueValidator = {
31 | validate:(value)=>{
32 | return value.length > 1? true:false;
33 | },
34 | failMsg:'Input value is too short'
35 | }
36 | */
37 | import React, {useState} from 'react';
38 | import classes from './formInput.module.scss';
39 |
40 | const FormInput = ({
41 | labelText,
42 | placeholder,
43 | onChange,
44 | inline = false,//Make lable and input at same line when true
45 | validators = null,//Custom validator array
46 | fontSize = 'large',//font size of input. large, medium or small
47 | ...otherInputProps
48 | }) => {
49 | const [errorMsg, setErrorMsg] = useState('');
50 | const [inputValue, setInputValue] = useState('');
51 | const [onFocus, setOnFocus] = useState(false);
52 |
53 | //onChange handler
54 | const onChangeHandler = (evt)=>{
55 | setInputValue(evt.target.value);
56 | if(onChange) onChange(evt);
57 | }
58 |
59 | //validate method, called when input unfocus
60 | const validate = async (value)=>{
61 | if(!value || !validators){
62 | setErrorMsg('');
63 | return;
64 | }
65 |
66 | let validator = validators.find((val)=>{
67 | return !val.validate(value);
68 | });
69 |
70 | validator? setErrorMsg(validator.failMsg):setErrorMsg('');
71 | }
72 |
73 | return (
74 |
75 |
76 |
77 | {labelText}
78 |
79 |
setOnFocus(true)}
85 | onBlur={(e)=>{
86 | setOnFocus(false);
87 | validate(e.target.value);
88 | }}
89 | value={inputValue}
90 | {...otherInputProps}
91 | />
92 |
93 | {
94 | //show error message if we have error message
95 | //and input is not Focused
96 | errorMsg && !onFocus?
97 |
98 | {errorMsg}
99 |
100 | :
101 | null
102 | }
103 |
104 | );
105 | };
106 |
107 | export default FormInput;
108 |
--------------------------------------------------------------------------------
/src/components/units/FormInput/formInput.module.scss:
--------------------------------------------------------------------------------
1 | // .forgotPasswordEmailInput,
2 | // .forgotPasswordEmailLabel {
3 | // width: 100%;
4 | // font-size: 1rem;
5 | // }
6 |
7 | // .forgotPasswordEmailLabel {
8 | // margin-top: 12px;
9 | // }
10 |
11 | // .forgotPasswordEmailInput {
12 | // border: none;
13 | // border-radius: 5px;
14 | // padding: 0 6px;
15 | // line-height: 1.5rem;
16 | // color: rgb(12, 58, 80)
17 | // }
18 |
19 |
20 | .formInputOverlay{
21 | display: flex;
22 | flex-direction: column;
23 | width: 100%;
24 |
25 | .errorMessage{
26 | font-size: 1rem;
27 | color: red;
28 | text-align: left;
29 | }
30 | }
31 |
32 | .inputGroup{
33 | display: flex;
34 | flex-direction: column;
35 |
36 | input[type='password'] {
37 | letter-spacing: 0.3em;
38 | }
39 |
40 | .labelOverlay{
41 | color: black;
42 | font-family: Biryani;
43 | font-style: normal;
44 | font-weight: 600;
45 | font-size: .875rem;
46 | line-height: 160%;
47 | letter-spacing: 0.1em;
48 | }
49 |
50 | $largeFont: 2rem;
51 | $mediumFont: 1.5rem;
52 | $smallFont: 0.3rem;
53 | .inputField{
54 | outline: none;
55 | background: #ffffff;
56 | box-sizing: border-box;
57 | border-radius: 4px;
58 | font-weight: 400;
59 | font-size: $largeFont;
60 |
61 | &.errorStyle{
62 | border: 2px solid red;
63 | }
64 |
65 | &.large{
66 | font-size: $largeFont;
67 | }
68 |
69 | &.medium{
70 | font-size: $mediumFont;
71 | }
72 |
73 | &.small{
74 | font-size: 0.3rem;
75 | }
76 | }
77 |
78 | &.inlineStyle{
79 | flex-direction: row;
80 |
81 | .labelOverlay{
82 | display: flex;
83 | justify-content: center;
84 | align-items: center;
85 | margin-right: 0.5%;
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/components/units/Prompt/Prompt.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ReactComponent as CloseBtn } from '../../../assets/PromptAssets/close.svg';
3 |
4 | import classes from './Prompt.module.scss';
5 |
6 | const Prompt = ({ title, message, onClosed }) => {
7 | return (
8 |
9 |
10 |
11 | {title &&
{title}
}
12 | {message && (
13 |
{message}
14 | )}
15 |
16 |
17 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Prompt;
28 |
--------------------------------------------------------------------------------
/src/components/units/Prompt/Prompt.module.scss:
--------------------------------------------------------------------------------
1 | .overlay{
2 | position: absolute;
3 | width: 100%;
4 | height: 100%;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background: #050516;
9 | }
10 |
11 | .promptOverlay{
12 | width: 50%;
13 | // height: 45%;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: space-between;
17 | align-items: center;
18 | background-color: #0D4E67;
19 | border-radius: 4px;
20 |
21 | .content{
22 | width: 80%;
23 | height: 60%;
24 | display: flex;
25 | flex-direction: column;
26 | justify-content: center;
27 | align-items: flex-start;
28 | background-color: #FFFFFF;
29 | border: 2px solid #E6E6E6;
30 | box-sizing: border-box;
31 | border-radius: 4px;
32 | padding: 6% 5%;
33 | margin-top: 6%;
34 | margin-bottom: 6%;
35 |
36 | .title{
37 | text-align: left;
38 | font-style: normal;
39 | font-weight: normal;
40 | font-size: 2rem;
41 | line-height: 140%;
42 | letter-spacing: 0.15em;
43 | color: #0D4E67;
44 |
45 | padding-bottom: 3%;
46 | }
47 |
48 | .message{
49 | text-align: left;
50 | font-style: normal;
51 | font-weight: 600;
52 | font-size: 1.7rem;
53 | line-height: 160%;
54 | letter-spacing: 0.1em;
55 | color: #0D4E67;
56 | }
57 | }
58 |
59 | .closeButtonArea{
60 | width: 100%;
61 | text-align: right;
62 | padding-right: 3.5%;
63 | margin-bottom: 1%;
64 |
65 | .closeButton{
66 | cursor: pointer;
67 | }
68 | }
69 | }
70 |
71 | @media only screen and (max-width:980px){
72 | .promptOverlay{
73 | width: 80%;
74 | height: 75%;
75 |
76 |
77 | }
78 | }
--------------------------------------------------------------------------------
/src/components/units/Span/Span.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Span = ({ content, ...otherProps }) => (
4 | {content}
5 | );
6 |
7 | export default Span;
8 |
--------------------------------------------------------------------------------
/src/components/units/VisualItem/VisualItem.js:
--------------------------------------------------------------------------------
1 | /************************************************************
2 | Component represent item in visualization list.
3 |
4 | Params:
5 | - title: Type of string. Title for item which displayed at bottom of item
6 | - imagePath: Type of string. the image srouce path that will be used t o display item's image
7 | - onClick: Type of function. Take no arguments. A callback when item is being clicked
8 |
9 | Current features:
10 | - Display an item in visualization list.
11 | - A callback when item is being clicked
12 |
13 | ************************************************************/
14 | import React from 'react';
15 | import classes from './VisualItem.module.scss';
16 |
17 | const VisualItem = ({ title, imagePath, onClick }) => {
18 | const imgSrc = imagePath ? imagePath : '';
19 |
20 | return (
21 |
22 |
23 |
24 |
29 |
30 |
{title}
31 |
32 |
33 | );
34 | };
35 |
36 | export default VisualItem;
37 |
--------------------------------------------------------------------------------
/src/components/units/VisualItem/VisualItem.module.scss:
--------------------------------------------------------------------------------
1 |
2 | .overlay{
3 | padding: 0 0 5% 0;
4 | }
5 |
6 | .itemWrapper{
7 |
8 | cursor: pointer;
9 |
10 | .imageWrapper{
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | background: #FFFFFF;
15 | box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.1);
16 | border-radius: 2px;
17 | padding: 3%;
18 |
19 | .image{
20 | width: 100%;
21 | box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.1);
22 | border-radius: 5px;
23 | object-fit: cover;
24 | }
25 | }
26 |
27 | .title{
28 | font-weight: bold;
29 | font-size: 1.3rem;
30 | line-height: 25px;
31 | text-align: center;
32 | color: #0D4E67;
33 | padding: 5% 0;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/firebase/config.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app';
2 | import 'firebase/firestore';
3 | import 'firebase/auth';
4 | import 'firebase/storage';
5 |
6 | const firebaseConfig = {
7 | apiKey: process.env.REACT_APP_apiKey,
8 | authDomain: process.env.REACT_APP_authDomain,
9 | databaseURL: process.env.REACT_APP_databaseURL,
10 | projectId: process.env.REACT_APP_projectId,
11 | storageBucket: process.env.REACT_APP_storageBucket,
12 | messagingSenderId: process.env.REACT_APP_messagingSenderId,
13 | appId: process.env.appId
14 | };
15 |
16 | firebase.initializeApp(firebaseConfig);
17 |
18 | export default firebase;
19 |
--------------------------------------------------------------------------------
/src/globalScss/index.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | font-size: 62.5%;
10 | font-family: 'Biryani', sans-serif;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | overflow-x: hidden;
14 | }
15 |
--------------------------------------------------------------------------------
/src/globalScss/variables.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Variable usage instructions:
3 |
4 | @import './variables.scss';
5 | color: $white;
6 |
7 |
8 | Add variables here.
9 | */
10 | $white: #eee;
11 |
12 | $light-grey: #c0c0c0;
13 | $grey: #595757;
14 |
15 | $black: #050516;
16 |
17 | $green: #008000;
18 | $light-green: rgba(41, 172, 109, 1);
19 | $dark-green: rgba(35, 79, 58, 1);
20 | $green-gradient: (180deg, $light-green 1%, $dark-green 100%);
21 |
22 | $red: #fb3600;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import { Provider } from 'react-redux';
5 | import { store } from './store/store';
6 |
7 | import AppRouter from './pages/AppRouter';
8 | import { BrowserRouter as Router } from 'react-router-dom';
9 |
10 | import './globalScss/index.scss';
11 |
12 | // this line for making .env vars accessible in the whole app, https://stackoverflow.com/questions/49579028/adding-an-env-file-to-react-project
13 | require('dotenv').config();
14 |
15 | const ROUTER = (
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | store.firebaseAuthIsReady.then(() => {
24 | ReactDOM.render(ROUTER, document.getElementById('root'));
25 | });
26 |
--------------------------------------------------------------------------------
/src/pages/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import Visualizer from '../../components/Visualizer/Visualizer.component';
4 | import PlayerBar from '../../components/PlayerBar/PlayerBar';
5 | import classes from './App.module.scss';
6 |
7 | import HamburgerToggle from '../../components/HamburgerToggle/HamburgerToggle';
8 | import VisualPanel from '../../components/VisualPanel/VisualPanel';
9 | import ScreenshotModal from '../../components/ScreenshotModal/ScreenshotModal';
10 | import Error from '../../components/Error/Error';
11 | import Success from '../../components/Success/Success';
12 | import ShowElementsOnFullSize from '../../utils/ShowElementsOnFullSize';
13 | import { setCurrentTime, setDuration, setPlayPressed } from '../../store/actions/songActions';
14 |
15 | export default function App() {
16 | // Local States
17 | const [isPlaying, setIsPlaying] = useState(false);
18 | const [volume, setVolume] = useState(0.5);
19 | const [isTogglePanel, setisTogglePanel] = useState(false);
20 | const [isSongEnded, setisSongEnded] = useState(false);
21 | // Redux States
22 | const { downloadState } = useSelector(state => state.download);
23 | const {
24 | screenshotUrl,
25 | takeScreenshot,
26 | screenshotSuccess,
27 | screenshotError
28 | } = useSelector(state => state.screenshot);
29 | const { isFullSize, isElementsShowed } = useSelector(
30 | state => state.fullSize
31 | );
32 |
33 | const songCurrentTime = useSelector(state => state.song.currentTime);
34 | const isPlayPressed = useSelector(state => state.song.isPlayPressed);
35 |
36 | const dispatch = useDispatch();
37 |
38 | // Update state when a new song is uploaded
39 | useEffect(() => {
40 | setisSongEnded(false);
41 | setIsPlaying(false);
42 | }, []);
43 |
44 | const onSongEnd = () => {
45 | setIsPlaying(false);
46 | playPressedHandler(false);
47 | setisSongEnded(true);
48 | dispatch(setCurrentTime(0));
49 | };
50 |
51 | const playPressedHandler = (isPlayPressed) => {
52 | dispatch(setPlayPressed(isPlayPressed));
53 | }
54 |
55 | const onTimeUpdateHandler = e => {
56 | const currentTime = parseInt(e.target.currentTime);
57 | if(!isSongEnded && currentTime !== songCurrentTime){
58 | dispatch(setCurrentTime(currentTime));
59 | }
60 | }
61 |
62 | return (
63 | <>
64 |
65 |
66 |
67 |
71 |
78 | setisTogglePanel(toggleState) }
81 | />
82 |
83 |
88 |
89 |
97 |
98 |
99 |
100 |
101 |
dispatch(setDuration(parseInt(e.currentTarget.duration)))}
105 | onPlay={() => setIsPlaying(true)}
106 | onTimeUpdate={onTimeUpdateHandler}
107 | >
108 |
109 |
110 |
setVolume(e.target.value)}
113 | onPlayPress={() => playPressedHandler(!isPlayPressed)}
114 | isPlaying={isPlaying}
115 | isSongEnded={isSongEnded}
116 | onCueTimeChange={e => setCurrentTime(e.target.value)}
117 | />
118 |
119 |
120 |
121 |
122 |
123 |
124 | {/* attach when fullSize event listeners to hamburger and visual-panel for show while hover on */}
125 | {isFullSize && (
126 | <>
127 | {' '}
128 | {' '}
129 | >
130 | )}
131 | >
132 | );
133 | }
--------------------------------------------------------------------------------
/src/pages/App/App.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../globalScss/variables.scss';
2 |
3 | .pageContainer {
4 | position: relative;
5 |
6 | /*
7 | visualContainer's responsiveness is base on
8 | min-height
9 | max-height
10 | height
11 | */
12 | min-height: 92vh;
13 | max-height: 92vh;
14 | height: 100vh;
15 |
16 | display: flex;
17 | flex-direction: row;
18 | justify-content: flex-start;
19 | }
20 |
21 | .visualContainer {
22 | $animSpeed: 0.25s;
23 | $hamburgerSize: 2.5rem;
24 | //Modify this value to change panel width
25 | $panelWidth: 30%;
26 | $visualMusicWidth: 100%;
27 |
28 | position: relative;
29 | height: 100%;
30 |
31 | .visualPanel {
32 | z-index: 2;
33 | position: absolute;
34 | width: $panelWidth;
35 | height: 100%;
36 | left: -$panelWidth;
37 | bottom: 0;
38 | background-color: $white;
39 |
40 | transition: all $animSpeed ease-in-out;
41 | opacity: 1;
42 |
43 | &.slideIn {
44 | left: 0;
45 | bottom: 0;
46 | }
47 | }
48 |
49 | .hideVisualPanel {
50 | opacity: 0;
51 | }
52 |
53 | .visualmusic {
54 | position: relative;
55 | width: $visualMusicWidth;
56 | height: 100%;
57 | right: 0;
58 | left: 0;
59 | background: $grey;
60 |
61 | transition: all $animSpeed ease-in-out;
62 |
63 | .hamburger {
64 | position: absolute;
65 | z-index: 1;
66 | top: 1%;
67 | left: 0;
68 | width: $hamburgerSize;
69 | height: $hamburgerSize;
70 | opacity: 1;
71 | }
72 |
73 | .hideHamburger {
74 | opacity: 0;
75 | }
76 |
77 | &.shrink {
78 | width: 100% - $panelWidth;
79 | left: $panelWidth;
80 | }
81 | }
82 | }
83 |
84 | .bar {
85 | // position: absolute;
86 | bottom: 0;
87 | left: 0;
88 | width: 100%;
89 | max-width: 100%;
90 | }
91 |
92 | .upload-page {
93 | position: absolute;
94 | bottom: 0;
95 | left: 0;
96 | width: 100%;
97 | max-width: 100%;
98 | }
99 |
--------------------------------------------------------------------------------
/src/pages/App/AppBrowser.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { initChat, disconnect } from '../../store/actions/chatActions';
4 | import App from './App';
5 | import UploadSong from '../../components/UploadSong/UploadSong';
6 | import { withRouter, Redirect } from 'react-router-dom';
7 |
8 | // this is the {/app} route Browser
9 |
10 | function AppBrowser() {
11 | // uid and song are gonna be the trigers for re-render the the content this route will have.
12 | // in case uid and song are exists, user gonna transfer to the visualizer
13 | // in case there is user but no song, upload song mockup will be the rendered component,
14 | // and in case that there is no user (or user log out ...) the route will reDirect to landing page.
15 | const uid = useSelector(state => state.firebase.auth.uid);
16 | const userName = useSelector(state => state.firebase.profile.name);
17 | const songURl = useSelector(state => state.song.url);
18 | const dispatch = useDispatch();
19 |
20 | useEffect(() => {
21 | if (userName) {
22 | dispatch(initChat({ uid, userName }));
23 | // the window listener is to include also if user close tab while on '/app' route that not catch in a regualr return () || componentWillUnmount
24 | window.addEventListener('beforeunload', () =>
25 | dispatch(disconnect(uid))
26 | );
27 | }
28 | return () => {
29 | dispatch(disconnect(uid));
30 | window.removeEventListener('beforeunload', () =>
31 | dispatch(disconnect(uid))
32 | );
33 | };
34 | }, [userName, dispatch, uid]);
35 |
36 | return (
37 |
38 | {uid ? (
39 | <> {songURl ?
:
} >
40 | ) : (
41 |
42 | )}
43 |
44 | );
45 | }
46 |
47 | export default withRouter(AppBrowser);
48 |
--------------------------------------------------------------------------------
/src/pages/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 | import TopNav from '../components/TopNav/TopNav';
4 | import AppBrowser from './App/AppBrowser';
5 | import LandingPage from './LandingPage/LandingPage';
6 | import Login from './Login/Login';
7 | import ForgotPassword from './ForgotPassword/ForgotPassword';
8 | import Register from './Register/Register';
9 | import ResetPassword from './ResetPassword/resetPasswordPage.component';
10 |
11 | export default function AppRouter() {
12 | return (
13 | <>
14 | {' '}
15 |
16 | {' '}
17 | {' '}
18 | {' '}
19 | {' '}
20 | {' '}
21 | {' '}
22 | {' '}
23 | >
24 | );
25 | }
26 |
27 | /**
28 | * For simplicity and the sake of a working router prototype, I kept the names of the paths simple for the time being - these can easily be changed and adapted going forward. Essentially, any new routers going forward can easily be added and all other work can occur in tandem without hindering or blocking the current routing system until new routes are ready to be integrated.
29 | */
30 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { forgotPassword } from '../../store/actions/authActions';
4 | import { withRouter } from 'react-router-dom';
5 | import ForgotPasswordPage from './ForgotPasswordPage/ForgotPasswordPage.component';
6 |
7 | function ForgotPassword() {
8 | const dispatch = useDispatch();
9 | const error = useSelector(state => state.authError);
10 |
11 | const [email, setEmail] = useState('');
12 |
13 | const onFormSubmit = event => {
14 | event.preventDefault();
15 | dispatch(forgotPassword({ email }));
16 | };
17 |
18 | if (error) {
19 | console.log(error);
20 | }
21 |
22 | return (
23 |
24 | );
25 | }
26 |
27 | export default withRouter(ForgotPassword);
28 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword/ForgotPasswordPage/ForgotPasswordPage.component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '../../../components/units/Button/Button';
3 | import FormInput from '../../../components/units/FormInput/formInput.component';
4 | import classes from './ForgotPasswordPage.module.scss';
5 |
6 | function ForgotPasswordPage({ setEmail, onFormSubmit }) {
7 | return (
8 |
32 | );
33 | }
34 |
35 | export default ForgotPasswordPage;
36 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword/ForgotPasswordPage/ForgotPasswordPage.module.scss:
--------------------------------------------------------------------------------
1 | .forgotPasswordDiv {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | height: 100vh;
6 | width: 100vw;
7 | background-color: rgb(5, 5, 22);
8 | color: white;
9 |
10 | .forgotPasswordForm {
11 | display: flex;
12 | flex-direction: column;
13 | padding: 40px;
14 | width: 500px;
15 | background-color: rgb(13, 78, 103);
16 | border-radius: 10px;
17 |
18 | .pageTitle {
19 | font-size: 1.5rem;
20 | padding-bottom: 0;
21 | }
22 |
23 | .subTitle {
24 | font-size: 0.8rem;
25 | margin-bottom: 20px;
26 | }
27 |
28 | .formGroup {
29 | margin-bottom: 30px;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/LandingPage/ContactComponents/FormComponents/ContactForm.module.scss:
--------------------------------------------------------------------------------
1 | .contactForm {
2 | margin-left: auto;
3 | display: flex;
4 | flex-direction: column;
5 | color: white;
6 | label {
7 | width: 40%;
8 | margin: 0 0 2% 0;
9 | font-size: 1.5em;
10 | }
11 | .errorLabel {
12 | background-color: rgba(147, 112, 219, 0.8);
13 | padding-top: 3%;
14 | width: 100%;
15 | font-size: 1.2rem;
16 | line-height: 160%;
17 | letter-spacing: 0.1em;
18 | text-align: center;
19 | padding-bottom: 5%;
20 | }
21 | .formInput {
22 | width: 100%;
23 | line-height: 2.5;
24 | margin-top: 1%;
25 | border: none;
26 | border-radius: 2rem;
27 | }
28 | .validationError {
29 | color: red;
30 | font-size: 1.2em;
31 | }
32 | #message {
33 | overflow: hidden;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/LandingPage/ContactComponents/Icons/ContactFormIcons.js:
--------------------------------------------------------------------------------
1 | /*************************
2 |
3 | Component for social icons in Section Three.
4 |
5 | Current features:
6 | 1. Social media icons for Github and LinkedIn.
7 |
8 | **********************
9 | Implement icons hover animations using react-spring (already installed for parallax). /** paralax deleted
10 | Responsive to screen size. //*** screen is responsive
11 | *****************
12 |
13 | TODO: - Implement click functionality for the icons.
14 |
15 | ************************/
16 |
17 | import React from 'react';
18 | import classes from './ContactFormIcons.module.scss';
19 |
20 | import importAll from '../../../../utils/importAllFiles'
21 |
22 |
23 |
24 | // Import all icons from assets folder
25 | const icons = importAll(
26 | require.context(
27 | './../../../../assets/LandingPageAssets/section-3-icons/',
28 | false,
29 | /\.(svg)$/
30 | )
31 | );
32 | const [linkedIn, github] = icons;
33 |
34 | const SectionThreeIcons = () => (
35 |
51 | );
52 |
53 | export default SectionThreeIcons;
54 |
--------------------------------------------------------------------------------
/src/pages/LandingPage/ContactComponents/Icons/ContactFormIcons.module.scss:
--------------------------------------------------------------------------------
1 | .socialIcons {
2 | position: relative;
3 | }
4 |
5 | .socialIcon {
6 | width: 5.5vh;
7 | transition: all .3s;
8 |
9 | &:hover {
10 | cursor: pointer;
11 | filter: drop-shadow(1px 4px 5px #ffffff93);
12 | transform: translateY(-10%);
13 | }
14 | }
15 |
16 | #github {
17 | position: absolute;
18 | left: 0;
19 | z-index: 99;
20 | }
21 |
22 | #linkedIn {
23 | position: absolute;
24 | left: 15%;
25 | z-index: 99;
26 | }
--------------------------------------------------------------------------------
/src/pages/LandingPage/LandingPage.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react';
2 | import classes from './LandingPage.module.scss';
3 |
4 | import blackHairGirl from '../../assets/LandingPageAssets/section-2/blackHairAndBubbles.png';
5 | import SectionThreeIcons from './ContactComponents/Icons/ContactFormIcons';
6 | import ContactForm from './ContactComponents/FormComponents/ContactForm';
7 | import {Rotate, Roll, Fade} from'react-reveal';
8 |
9 |
10 | export default class LandingPage extends PureComponent{
11 | render(){
12 | return(
13 |
14 |
15 |
16 |
17 |
18 |
19 | Discover and Share
20 |
Music Visualization
21 |
22 |
23 |
24 | An app that converts your favorite music pieces into visual
25 | expresions.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Best Experience
38 |
39 |
40 |
Collection of visual themes to transcribe the emotional and
41 |
rythmic effects of an uploaded mp3 song.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
Contact
60 |
61 |
62 |
63 |
64 |
65 |
Would you like to write us?
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 | }
--------------------------------------------------------------------------------
/src/pages/LandingPage/LandingPage.module.scss:
--------------------------------------------------------------------------------
1 | $small: 320px;
2 | $medium: 480px;
3 | $medium-large: 640px;
4 | $large: 900px;
5 | $x-large: 1200px;
6 |
7 |
8 |
9 | body{
10 | margin: 0 auto;
11 | background-image: url('../../assets/LandingPageAssets/bg-image2.gif');
12 | background-position: center;
13 | background-size: cover;
14 | background-repeat: no-repeat;
15 | background-attachment: fixed;
16 | color: #FFFFFF;
17 | background-color: rgba(0, 0, 0, 0.6);
18 | background-blend-mode: overlay;
19 | }
20 |
21 |
22 | .container{
23 | display: flex;
24 | flex-flow: row wrap;
25 | width: 100vw;
26 |
27 |
28 | .myText{
29 | padding-left: 10vw;
30 | padding-right: 20px;;
31 | .heading {
32 | font-size: 1.8rem;
33 | font-weight: 400;
34 | border-bottom: 1.5px solid #FFFFFF;
35 |
36 | @media only screen and (min-width: $small) {
37 | font-size: 1.5rem;
38 | }
39 | @media only screen and (min-width: $medium-large) {
40 | font-size: 2.4rem;
41 | }
42 | @media only screen and (min-width: $large) {
43 | font-size: 2.8rem;
44 | }
45 | @media only screen and (min-width: $x-large) {
46 | font-size: 3.2rem;
47 | }
48 | }
49 | .tagline {
50 | padding-top: 10px ;
51 | font-size: 1.5rem;
52 | font-weight: 300;
53 | @media only screen and (min-width: $small) {
54 | font-size: 0.8rem;
55 |
56 | }
57 | @media only screen and (min-width: $medium-large) {
58 | font-size: 1rem;
59 | }
60 | @media only screen and (min-width: $large) {
61 | font-size: 1.2rem;
62 | }
63 | @media only screen and (min-width: $x-large) {
64 | font-size: 1.5rem;
65 | }
66 | }
67 | }
68 |
69 | .item{
70 | height: 500px;
71 | flex:1 1 100%;
72 | min-width: 400px;
73 | }
74 |
75 | .item1{
76 | display: flex;
77 | flex-flow: row wrap;
78 | width: 100vw;
79 | height: 100vh;
80 | -webkit-flex-flow:row nowrap;
81 | -moz-flex-flow:row nowrap;
82 | -ms-flex-flow: row nowrap;
83 | -o-flex-flow:row nowrap;
84 | .int1item1{
85 | flex: 0 1 60%;
86 | align-self: center;
87 | }
88 | }
89 | .item2{
90 | height: 500px;
91 | display: flex;
92 | flex-flow: row wrap;
93 | margin-bottom: 100px;
94 |
95 | .int1Item2{
96 | height: 20vh;
97 | min-width: 200px;
98 | flex: 1 1 40%;
99 | padding-top:5%;
100 |
101 | h2{
102 | width: 70%;
103 | }
104 | }
105 | .int2Item2{
106 | padding:45px;
107 | margin:auto;
108 | text-align: center;
109 | max-width: 600px;
110 | min-width: 400px;
111 | flex: 1 1 60%;
112 | }
113 | }
114 | .item3{
115 | height: 500px;
116 | display: flex;
117 | flex-flow: row wrap;
118 | margin: auto;
119 | .int1Item3{
120 | min-width: 200px;
121 | max-width: 400px;
122 | flex: 1 1 30%;
123 | margin-left: 15%;
124 | margin-bottom: 8rem;
125 |
126 | h1{
127 | margin-bottom: 15px;
128 | }
129 | }
130 | .int2Item3{
131 |
132 | padding-left: 10%;
133 | min-width: 400px;
134 | flex: 1 1 55%;
135 | }
136 | }
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/src/pages/Login/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { logIn, cleanError } from '../../store/actions/authActions';
4 | import { withRouter, Redirect } from 'react-router-dom';
5 | import LoginPage from './LoginPage/LoginPage';
6 |
7 | function Login() {
8 | // useSelector Redux hook re-mount every time the `state` in redux-store modified,
9 | // at first visit in the page, `uid` and `authError` are both equal null.
10 | // after submiting a login request, one of them should not be null anymore...
11 | const uid = useSelector(state => state.firebase.auth.uid);
12 | const error = useSelector(state => state.authError);
13 |
14 | // useSispatch its the second redux hook, allow actions dispatching from react component.
15 | // we will dispatch { logIn } action imported from the store.
16 |
17 | const dispatch = useDispatch();
18 |
19 | const [email, setEmail] = useState('');
20 | const [password, setPassword] = useState('');
21 | const [span, setSpan] = useState(null);
22 |
23 | const onFormSubmit = event => {
24 | event.preventDefault();
25 | dispatch(cleanError());
26 | setSpan(
27 |
31 | );
32 | dispatch(logIn({ email, password }));
33 | };
34 |
35 | // in case { uid } is not null anymore we want the user redirect to app
36 | if (uid) return ;
37 |
38 | if (error) {
39 | if (span !== error) setSpan(error);
40 | }
41 |
42 | return (
43 |
49 | );
50 | }
51 |
52 | export default withRouter(Login);
53 |
--------------------------------------------------------------------------------
/src/pages/Login/LoginPage/Login.module.scss:
--------------------------------------------------------------------------------
1 | @mixin formLabel {
2 | font-family: Biryani;
3 | font-style: normal;
4 | font-weight: 600;
5 | font-size: 1rem;
6 | line-height: 160%;
7 | letter-spacing: 0.1em;
8 | color: #ffffff;
9 | }
10 |
11 | @mixin formInputField {
12 | outline: none;
13 | background: #ffffff;
14 | border: 2px solid #e6e6e6;
15 | box-sizing: border-box;
16 | border-radius: 4px;
17 | font-weight: 600;
18 | font-size: 2rem;
19 | }
20 |
21 | @mixin formGroup {
22 | display: flex;
23 | flex-direction: column;
24 | width: 100%;
25 | }
26 |
27 | .overlay {
28 | width: 100vw;
29 | height: 100vh;
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | background: #595757;
34 |
35 | .loginOverlay {
36 | width: 32%;
37 | background: #0d4e67;
38 | border-radius: 4px;
39 | }
40 |
41 | @media only screen and (max-width: 1178px) {
42 | .loginOverlay {
43 | width: 50%;
44 | }
45 | }
46 |
47 | @media only screen and (max-width: 775px) {
48 | .loginOverlay {
49 | width: 70%;
50 | }
51 | }
52 |
53 | @media only screen and (max-width: 600px) {
54 | .loginOverlay {
55 | width: 80%;
56 | }
57 | }
58 | }
59 |
60 | .loginForm {
61 | display: flex;
62 | flex-direction: column;
63 | justify-content: space-between;
64 | align-items: flex-start;
65 | margin: 7% 10% 10%;
66 | margin-bottom: 8%;
67 |
68 | .titleGroup {
69 | @include formGroup;
70 | font-family: Biryani;
71 | font-style: normal;
72 | font-weight: normal;
73 | font-size: 2rem;
74 | line-height: 140%;
75 | letter-spacing: 0.15em;
76 | color: #ffffff;
77 | padding-bottom: 6%;
78 | }
79 |
80 | .errorLabel {
81 | padding-top: 3%;
82 | width: 100%;
83 | font-size: 1.2rem;
84 | line-height: 160%;
85 | letter-spacing: 0.1em;
86 | color: red;
87 | text-align: center;
88 | padding-bottom: 5%;
89 | }
90 |
91 | .emailGroup {
92 | @include formGroup;
93 | padding-bottom: 5%;
94 |
95 | .emailLabel {
96 | @include formLabel;
97 | }
98 |
99 | .emailField {
100 | @include formInputField;
101 | }
102 | }
103 |
104 | .passwordGroup {
105 | @include formGroup;
106 | padding-bottom: 6%;
107 |
108 | .passwordLabel {
109 | @include formLabel;
110 | }
111 |
112 | .passwordField {
113 | @include formInputField;
114 | }
115 | }
116 |
117 | .loginButtonGroup {
118 | @include formGroup;
119 | justify-content: center;
120 | align-items: center;
121 | padding-bottom: 4%;
122 |
123 | .loginButton {
124 | outline: none;
125 | width: 120px;
126 | height: 39px;
127 | background: #ffffff;
128 | border-radius: 10px;
129 |
130 | font-family: Biryani;
131 | font-style: normal;
132 | font-weight: 600;
133 | font-size: 1rem;
134 | line-height: 100%;
135 | letter-spacing: 0.1em;
136 | color: #0d4e67;
137 |
138 | cursor: pointer;
139 | }
140 | }
141 |
142 | .forgotPasswordLinksGroup {
143 | margin-top: 5px;
144 | display: flex;
145 | justify-content: center;
146 | width: 100%;
147 |
148 | .forgotPasswordLink {
149 | font-size: 14px;
150 | background: transparent;
151 | display: inline-block;
152 | color: white;
153 | text-decoration: none;
154 |
155 | &::after {
156 | content: '';
157 | display: block;
158 | width: 0;
159 | height: 1px;
160 | background: white;
161 | transition: width 0.3s;
162 | }
163 |
164 | &:hover::after {
165 | width: 100%;
166 | }
167 | }
168 | }
169 |
170 | @media only screen and (max-width: 473px) {
171 | .titleGroup {
172 | font-size: 1.5rem;
173 | }
174 | }
175 |
176 | @media only screen and (max-width: 353px) {
177 | .titleGroup {
178 | font-size: 1.2rem;
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/pages/Login/LoginPage/LoginPage.js:
--------------------------------------------------------------------------------
1 | /*
2 | Main component for Login.
3 |
4 | Allows users to input email address and password and click
5 | Login button to submit the form
6 |
7 | Current features:
8 | 1. Input user email address
9 | 2. Input user password
10 | 3. Do not allow email address and password filed to be blank
11 | 4. Validate email address and password field.
12 | */
13 | import React from 'react';
14 | import classes from './Login.module.scss';
15 | import { Link } from 'react-router-dom';
16 | import Span from '../../../components/units/Span/Span';
17 | import FormInput from '../../../components/units/FormInput/formInput.component';
18 |
19 | const emailValidators = [
20 | {
21 | validate: value => {
22 | return (
23 | value.includes('@') && (value.match(/@/g) || []).length === 1
24 | );
25 | },
26 | failMsg: 'Email is not valid'
27 | }
28 | ];
29 |
30 | const passwordValidators = [
31 | {
32 | validate: value => {
33 | return value.length >= 6 ? true : false;
34 | },
35 | failMsg: 'Password is too short'
36 | }
37 | ];
38 |
39 | function LoginPage({ setEmail, setPassword, onFormSubmit, span }) {
40 | return (
41 |
87 | );
88 | }
89 |
90 | export default LoginPage;
91 |
--------------------------------------------------------------------------------
/src/pages/Register/Register.js:
--------------------------------------------------------------------------------
1 | // for notes, visit Login.js
2 |
3 | import React, { useState } from 'react';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { register, cleanError } from '../../store/actions/authActions';
6 | import { withRouter, Redirect } from 'react-router-dom';
7 | import RegisterPage from './RegisterPage/RegisterPage';
8 |
9 | function Register() {
10 | const uid = useSelector(state => state.firebase.auth.uid);
11 | const error = useSelector(state => state.authError);
12 |
13 | const dispatch = useDispatch();
14 |
15 | const [name, setName] = useState('');
16 | const [email, setEmail] = useState('');
17 | const [password, setPassword] = useState('');
18 | const [span, setSpan] = useState(null);
19 |
20 | const onFormSubmit = event => {
21 | event.preventDefault();
22 | dispatch(cleanError());
23 | setSpan(
24 |
28 | );
29 | dispatch(register({ name, email, password }));
30 | };
31 |
32 | if (uid) return ;
33 |
34 | if (error) {
35 | if (span !== error) setSpan(error);
36 | }
37 |
38 | return (
39 |
46 | );
47 | }
48 |
49 | export default withRouter(Register);
50 |
--------------------------------------------------------------------------------
/src/pages/Register/RegisterPage/Register.module.scss:
--------------------------------------------------------------------------------
1 | .signup {
2 | color: #222;
3 | width: 100vw;
4 | height: 100vh;
5 | background-color: transparent;
6 | display: flex;
7 |
8 | .colone {
9 | width: 60%;
10 | color: #fff;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: center;
15 | text-align: left;
16 |
17 | .container {
18 | width: 85%;
19 | padding: 0 .5rem;
20 |
21 | .heading {
22 | font-size: 4rem;
23 | font-weight: 400;
24 | }
25 |
26 | .subheading {
27 | padding-right: 10rem;
28 | font-size: 1.5rem;
29 | font-weight: 400;
30 | }
31 |
32 | @media screen and (max-width: 992px) {
33 | .heading {
34 | font-size: 2.75rem;
35 | }
36 |
37 | .subheading {
38 | font-size: 1.2rem;
39 | padding-right: 0;
40 | }
41 | }
42 | }
43 | }
44 |
45 | .coltwo {
46 | width: 40%;
47 | background-color: #fafafa;
48 | display: flex;
49 | flex-direction: column;
50 | align-items: center;
51 | justify-content: center;
52 |
53 | .signupform {
54 | border: 2px solid #C43388;
55 | height: 90%;
56 | width: 80%;
57 | text-align: center;
58 | display: flex;
59 | flex-direction: column;
60 | align-items: center;
61 | justify-content: center;
62 |
63 | .signupheading {
64 | position: absolute;
65 | top: 10%;
66 | font-size: 2.25rem;
67 | font-weight: 600;
68 | letter-spacing: .1rem;
69 | }
70 |
71 | .name,
72 | .email,
73 | .password {
74 | text-align: left;
75 | width: 90%;
76 | display: flex;
77 | margin-top: 3.33%;
78 | justify-content: flex-start;
79 | }
80 |
81 | .errorLabel {
82 | padding: .75rem 1rem;
83 | width: 90%;
84 | background-color: rgba(red, .3);
85 | color: darken(red, 10);
86 | display: flex;
87 | justify-content: center;
88 | align-items: center;
89 | font-size: .8rem;
90 | border-radius: 4px;
91 | }
92 |
93 | .showpassword {
94 | width: 30%;
95 | display: flex;
96 | justify-content: center;
97 | align-items: center;
98 | margin-top: 3%;
99 |
100 | input {
101 | width: auto;
102 | }
103 |
104 | label {
105 | width: 90%;
106 | font-size: .75rem;
107 | letter-spacing: 0.1em;
108 | color: #111;
109 | padding: 2%;
110 | }
111 | }
112 |
113 | .custombutton {
114 | // background-color: greenyellow;
115 | width: 90%;
116 | display: flex;
117 | justify-content: center;
118 |
119 | button {
120 | text-align: center;
121 | letter-spacing: 0.1em;
122 | margin-top: 7.5%;
123 | margin-bottom: 5%;
124 | color: #fafafa;
125 | background: #C43388;
126 | border-radius: 20px;
127 | width: 35%;
128 | font-size: 1rem;
129 | padding: 2% 2%;
130 | transition: background .2s ease-out;
131 |
132 | &:hover {
133 | background: darken(#C43388, 10);
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | @media screen and (max-width: 992px) {
141 | .colone {
142 | width: 50%;
143 | }
144 |
145 | .coltwo {
146 | width: 50%;
147 | }
148 | }
149 |
150 | @media screen and (max-width: 768px) {
151 | .colone {
152 | display: none;
153 | }
154 |
155 | .coltwo {
156 | width: 100%;
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/pages/Register/RegisterPage/RegisterPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import classes from './Register.module.scss';
4 | import Span from '../../../components/units/Span/Span';
5 | import FormInput from '../../../components/units/FormInput/formInput.component';
6 | /*
7 | Main component for Signup.
8 |
9 | Allows users to input name, email address and password and click
10 | signup button to submit the form
11 |
12 | Current features:
13 | 1. Input user name
14 | 2. Input user email address
15 | 3. Input user password
16 | 4. Do not allow email address and password filed to be blank
17 | 5. Validate name, email address and password field.
18 | */
19 |
20 | const nameValidators=[
21 | {
22 | validate:(value)=>{
23 | return value.length >= 2?true:false;
24 | },
25 | failMsg:'Name is too short'
26 | },
27 | {
28 | validate:(value)=>{
29 | return value.length <= 15?true:false;
30 | },
31 | failMsg:'Name is too long'
32 | }
33 | ];
34 |
35 | const emailValidators=[
36 | {
37 | validate:(value)=>{
38 | return value.includes('@')&&((value.match(/@/g) || []).length===1);
39 | },
40 | failMsg:'Email is not valid'
41 | }
42 | ];
43 |
44 | const passwordValidators=[
45 | {
46 | validate:(value)=>{
47 | return value.length >= 6?true:false;
48 | },
49 | failMsg:'Password is too short'
50 | }
51 | ];
52 |
53 | function RegisterPage({ setName, setEmail, setPassword, onFormSubmit, span }) {
54 | const [isPasswordHidden, setIsPasswordHidden] = React.useState(true);
55 |
56 | return (
57 |
58 |
59 |
60 |
Visualize music in a new way!
61 | The app that converts your favourite music pieces into visual expressions
62 |
63 |
64 |
117 |
118 | );
119 | }
120 |
121 | export default RegisterPage;
122 |
--------------------------------------------------------------------------------
/src/pages/ResetPassword/resetPasswordPage.component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './resetPasswordPage.module.scss';
3 | import FormInput from '../../components/units/FormInput/formInput.component';
4 | import Button from '../../components/units/Button/Button';
5 |
6 | class ResetPassword extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | newPassword: '',
11 | verifyPassword: ''
12 | };
13 | }
14 |
15 | onInputChange = event => {
16 | const { name, value } = event.target;
17 | this.setState({
18 | [name]: value
19 | });
20 | };
21 |
22 | // TODO: Implement submit functionality.
23 | // Added fetch function for test purposes
24 | onFormSubmit = async event => {
25 | event.preventDefault();
26 | const { newPassword, verifyPassword } = this.state;
27 | console.log(
28 | `newPassword: ${newPassword}, verifyPassword: ${verifyPassword}`
29 | );
30 |
31 | // boilerplate fetch for test purposes
32 | // will console.log the names of a users array provided by jsonplaceholder.typicode.com
33 | const response = await fetch(
34 | 'https://jsonplaceholder.typicode.com/users'
35 | );
36 | const data = await response.json();
37 | data.map(user => console.log(user.name));
38 | };
39 |
40 | render() {
41 | return (
42 |
43 |
44 |
Reset Password
45 |
46 | Complete the form below to reset your password.
47 |
48 |
49 |
55 |
61 |
62 |
63 |
69 |
70 |
71 |
72 | );
73 | }
74 | }
75 |
76 | export default ResetPassword;
77 |
--------------------------------------------------------------------------------
/src/pages/ResetPassword/resetPasswordPage.module.scss:
--------------------------------------------------------------------------------
1 | @mixin centerContent {
2 | display: flex;
3 | justify-content: center;
4 | }
5 |
6 | .resetPassword {
7 | @include centerContent;
8 | align-items: center;
9 | height: 100vh;
10 | width: 100vw;
11 | background-color: rgb(5, 5, 22);
12 | color: white;
13 |
14 | .resetPasswordForm {
15 | padding: 40px;
16 | width: 500px;
17 | background-color: rgb(13, 78, 103);
18 | border-radius: 10px;
19 | }
20 | .pageTitle {
21 | font-size: 1.5rem;
22 | padding-bottom: 0;
23 | }
24 |
25 | .subTitle {
26 | font-size: 0.8rem;
27 | margin-bottom: 20px;
28 | }
29 |
30 | .formGroup {
31 | margin-bottom: 30px;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/store/actions/authActions.js:
--------------------------------------------------------------------------------
1 | export const register = newUser => {
2 | return (dispatch, getState, { getFirebase, getFirestore }) => {
3 | const firebase = getFirebase();
4 | const firestore = getFirestore();
5 | firebase
6 | .auth()
7 | .createUserWithEmailAndPassword(newUser.email, newUser.password)
8 | .then(resp => {
9 | return firestore
10 | .collection('users')
11 | .doc(resp.user.uid)
12 | .set({
13 | name: newUser.name,
14 | email: newUser.email
15 | });
16 | })
17 | .then(() => {
18 | dispatch({ type: 'ERROR_CLEARED' });
19 | })
20 | .catch(err => {
21 | dispatch({
22 | type: 'AUTH_ERROR',
23 | err
24 | });
25 | });
26 | };
27 | };
28 |
29 | export const logIn = credentials => {
30 | return (dispatch, getState, { getFirebase }) => {
31 | const firebase = getFirebase();
32 | firebase
33 | .auth()
34 | .signInWithEmailAndPassword(credentials.email, credentials.password)
35 | .then(() => {
36 | dispatch({ type: 'ERROR_CLEARED' });
37 | })
38 | .catch(err => {
39 | dispatch({
40 | type: 'AUTH_ERROR',
41 | err
42 | });
43 | });
44 | };
45 | };
46 |
47 | export const logOut = () => {
48 | return (dispatch, getState, { getFirebase }) => {
49 | const firebase = getFirebase();
50 | firebase
51 | .auth()
52 | .signOut()
53 | .then(() => {
54 | dispatch({ type: 'SIGNOUT' });
55 | });
56 | };
57 | };
58 |
59 | export const forgotPassword = ({ email }) => {
60 | return (dispatch, getState, { getFirebase }) => {
61 | const firebase = getFirebase();
62 | firebase
63 | .auth()
64 | .sendPasswordResetEmail(email)
65 | .then(() => {
66 | // Todo - Customize account mail.
67 | // https://support.google.com/firebase/answer/7000714
68 | alert('email sent successfully');
69 | dispatch({ type: 'FORGOT_PASSWORD' });
70 | })
71 | .catch(err => {
72 | dispatch({
73 | type: 'AUTH_ERROR',
74 | err
75 | });
76 | });
77 | };
78 | };
79 |
80 | export const cleanError = () => {
81 | return (dispatch, getState) => {
82 | dispatch({
83 | type: 'ERROR_CLEARED'
84 | });
85 | };
86 | };
87 |
--------------------------------------------------------------------------------
/src/store/actions/chatActions.js:
--------------------------------------------------------------------------------
1 | import socketIOClient from 'socket.io-client';
2 | const socket = socketIOClient('localhost:3001');
3 |
4 | export const initChat = ({ uid, userName }) => {
5 | socket.emit('userLogin', { uid, userName });
6 | // user is subscribe to listen to "onlineUsers" socket until he log out, every time the socket update, new [onlineUsers] will dispatch
7 | return dispatch => {
8 | socket.on('onlineUsers', onlineUsers => {
9 | dispatch({ type: 'CHAT_INIT', onlineUsers });
10 | });
11 | };
12 | };
13 |
14 | export const disconnect = uid => {
15 | socket.emit('userDisconnect', uid);
16 | socket.off('onlineUsers');
17 | return dispatch => {
18 | dispatch({ type: 'CHAT_LOG_OUT' });
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/store/actions/downloadActions.js:
--------------------------------------------------------------------------------
1 | export const downloadVisualStart = () => {
2 | return (dispatch, getState) => {
3 | try {
4 | dispatch({
5 | type: 'DOWNLOAD_START'
6 | });
7 | } catch (err) {
8 | dispatch({
9 | type: 'DOWNLOAD_ERR',
10 | err
11 | });
12 | }
13 | };
14 | };
15 |
16 | export const downloadVisualEnd = () => {
17 | return (dispatch, getState) => {
18 | try {
19 | dispatch({
20 | type: 'DOWNLOAD_END'
21 | });
22 | } catch (err) {
23 | dispatch({
24 | type: 'DOWNLOAD_ERR',
25 | err
26 | });
27 | }
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/src/store/actions/fullSizeActions.js:
--------------------------------------------------------------------------------
1 | export const setIsFullSize = boolean => {
2 | return (dispatch, getState) =>
3 | dispatch({ type: 'FULL_SIZE', payload: boolean });
4 | };
5 |
6 | export const setIsElementsShowed = boolean => {
7 | return (dispatch, getState) =>
8 | dispatch({ type: 'ELEMENTS_SHOWED', payload: boolean });
9 | };
10 |
--------------------------------------------------------------------------------
/src/store/actions/screenshotActions.js:
--------------------------------------------------------------------------------
1 | export const takeScreenshot = () => {
2 | return (dispatch, getState) => {
3 | try {
4 | dispatch({
5 | type: 'TAKE_SCREENSHOT'
6 | });
7 | } catch (err) {
8 | dispatch({
9 | type: 'TAKE_SCREENSHOT_ERR',
10 | err
11 | });
12 | }
13 | };
14 | };
15 |
16 | export const getScreenshotUrl = screenshotUrl => {
17 | return (dispatch, getState) => {
18 | try {
19 | dispatch({
20 | type: 'GET_SCREENSHOT_URL',
21 | screenshotUrl
22 | });
23 | } catch (err) {
24 | dispatch({
25 | type: 'GET_SCREENSHOT_URL_ERR',
26 | err
27 | });
28 | }
29 | };
30 | };
31 |
32 | export const shareScreenshotSuccess = () => {
33 | return (dispatch, getState) => {
34 | try {
35 | dispatch({
36 | type: 'SHARE_SCREENSHOT_SUCCESS'
37 | });
38 | } catch (err) {
39 | dispatch({
40 | type: 'SHARE_SCREENSHOT_SUCCESS_ERR',
41 | err
42 | });
43 | }
44 | };
45 | };
46 |
47 | export const shareScreenshotError = () => {
48 | return (dispatch, getState) => {
49 | try {
50 | dispatch({
51 | type: 'SHARE_SCREENSHOT_ERROR'
52 | });
53 | } catch (err) {
54 | dispatch({
55 | type: 'SHARE_SCREENSHOT_ERROR_ERR',
56 | err
57 | });
58 | }
59 | };
60 | };
61 |
62 | export const shareScreenshotEnd = () => {
63 | return (dispatch, getState) => {
64 | try {
65 | dispatch({
66 | type: 'SHARE_SCREENSHOT_END'
67 | });
68 | } catch (err) {
69 | dispatch({
70 | type: 'SHARE_SCREENSHOT_END_ERR',
71 | err
72 | });
73 | }
74 | };
75 | };
76 |
77 | export const clearScreenshot = () => {
78 | return (dispatch, getState) => {
79 | try {
80 | dispatch({
81 | type: 'CLEAR_SCREENSHOT'
82 | });
83 | } catch (err) {
84 | dispatch({
85 | type: 'CLEAR_SCREENSHOT_URL_ERR',
86 | err
87 | });
88 | }
89 | };
90 | };
91 |
--------------------------------------------------------------------------------
/src/store/actions/songActions.js:
--------------------------------------------------------------------------------
1 | export const setSong = song => {
2 | return (dispatch, getState) => {
3 | try {
4 | dispatch({
5 | type: 'SONG_SETTED',
6 | url: song.url,
7 | name: song.filename
8 | });
9 | } catch (err) {
10 | dispatch({
11 | type: 'SONG_ERR',
12 | err
13 | });
14 | }
15 | };
16 | };
17 |
18 | export const clearSong = () => {
19 | return (dispatch, getState) => {
20 | try {
21 | dispatch({
22 | type: 'SONG_CLEARED'
23 | });
24 | } catch (err) {
25 | dispatch({
26 | type: 'SONG_ERR',
27 | err
28 | });
29 | }
30 | };
31 | };
32 |
33 | export const storeBlob = blob => {
34 | return (dispatch, getState) => {
35 | try {
36 | dispatch({
37 | type: 'BLOB_STORED',
38 | blob
39 | });
40 | } catch (err) {
41 | dispatch({
42 | type: 'SONG_ERR',
43 | err
44 | });
45 | }
46 | };
47 | };
48 |
49 | export const setCurrentTime = currentTime => {
50 | return (dispatch, getState) => {
51 | try{
52 | dispatch({type: 'SET_CURRENT_TIME', currentTime})
53 | }catch(err){
54 | dispatch({
55 | type: 'SONG_ERR',
56 | err
57 | });
58 | }
59 |
60 | }
61 | }
62 |
63 | export const setDuration = duration => {
64 | return (dispatch, getState) => {
65 | try{
66 | dispatch({type: 'SET_DURATION', duration})
67 | }catch(err){
68 | dispatch({
69 | type: 'SONG_ERR',
70 | err
71 | });
72 | }
73 |
74 | }
75 | }
76 |
77 | export const setPlayPressed = isPlayPressed => {
78 | return (dispatch, getState) => {
79 | try{
80 | dispatch({type: 'PLAY_PRESSED', isPlayPressed})
81 | }catch(err){
82 | dispatch({
83 | type: 'SONG_ERR',
84 | err
85 | });
86 | }
87 |
88 | }
89 | }
--------------------------------------------------------------------------------
/src/store/reducers/authReducer/authReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const authReducer = (state = initialState.authError, action) => {
4 | switch (action.type) {
5 | case 'ERROR_CLEARED':
6 | // the state here it's only the error, the user is registerd in firebaseReducer at the rootReducer. cause user loged/registered, error = null
7 | return null;
8 |
9 | case 'AUTH_ERROR':
10 | return action.err.message;
11 |
12 | default:
13 | return state;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/store/reducers/chatReducer/chatReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const chatReducer = (state = initialState.chat, action) => {
4 | switch (action.type) {
5 | case 'CHAT_INIT':
6 | return { ...state, onlineUsers: action.onlineUsers };
7 | case 'CHAT_LOG_OUT':
8 | return { room: 'global', messages: [], onlineUsers: null };
9 | default:
10 | return state;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/store/reducers/downloadReducer/downloadReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const downloadReducer = (state = initialState.downloadState, action) => {
4 | switch (action.type) {
5 | case 'DOWNLOAD_START':
6 | return true;
7 |
8 | case 'SONG_CLEARED':
9 | case 'DOWNLOAD_END':
10 | return false;
11 |
12 | case 'DOWNLOAD_ERROR':
13 | console.log(action.err);
14 | break;
15 |
16 | default:
17 | return state;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/store/reducers/fullSizeReducer/fullSizeReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const fullSizeReducer = (state = initialState.fullSize, action) => {
4 | switch (action.type) {
5 | case 'FULL_SIZE':
6 | return {
7 | ...state,
8 | isFullSize: action.payload
9 | };
10 | case 'ELEMENTS_SHOWED':
11 | return {
12 | ...state,
13 | isElementsShowed: action.payload
14 | };
15 | default:
16 | return state;
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/src/store/reducers/initialState.js:
--------------------------------------------------------------------------------
1 | export default {
2 | authError: null,
3 | song: {
4 | url: null,
5 | name: null,
6 | blob: null,
7 | duration: 0,
8 | currentTime: 0,
9 | isPlayPressed: false
10 | },
11 | downloadState: false,
12 | screenshot: {
13 | takeScreenshot: false,
14 | screenshotUrl: '',
15 | screenshotSuccess: false,
16 | screenshotError: false
17 | },
18 | fullSize: {
19 | isFullSize: false,
20 | isElementsShowed: true
21 | },
22 | chat: {
23 | room: 'global',
24 | messages: [],
25 | onlineUsers: null
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/src/store/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { firebaseReducer } from 'react-redux-firebase';
3 | import { authReducer } from './authReducer/authReducer';
4 | import { songReducer } from './songReducer/songReducer';
5 | import { downloadReducer } from './downloadReducer/downloadReducer';
6 | import { screenshotReducer } from './screenshotReducer/screenshotReducer';
7 | import { fullSizeReducer } from './fullSizeReducer/fullSizeReducer';
8 | import { chatReducer } from './chatReducer/chatReducer';
9 |
10 | // the root reducer will become the state at the whole app.
11 | // { state.firebase/state.authError } for example, are ways to reach the content the state holds.
12 |
13 | const rootReducer = combineReducers({
14 | authError: authReducer,
15 | firebase: firebaseReducer,
16 | song: songReducer,
17 | download: downloadReducer,
18 | screenshot: screenshotReducer,
19 | fullSize: fullSizeReducer,
20 | chat: chatReducer
21 | });
22 |
23 | export default rootReducer;
24 |
--------------------------------------------------------------------------------
/src/store/reducers/screenshotReducer/screenshotReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const screenshotReducer = (state = initialState.screenshot, action) => {
4 | switch (action.type) {
5 | case 'TAKE_SCREENSHOT':
6 | return {
7 | ...state,
8 | takeScreenshot: true
9 | };
10 |
11 | case 'TAKE_SCREENSHOT_ERR':
12 | console.log(action.err);
13 | break;
14 |
15 | case 'GET_SCREENSHOT_URL':
16 | return {
17 | ...state,
18 | screenshotUrl: action.screenshotUrl
19 | };
20 |
21 | case 'GET_SCREENSHOT_URL_ERR':
22 | console.log(action.err);
23 | break;
24 |
25 | case 'SHARE_SCREENSHOT_SUCCESS':
26 | return {
27 | ...state,
28 | screenshotSuccess: true
29 | };
30 |
31 | case 'SHARE_SCREENSHOT_SUCCESS_ERR':
32 | console.log(action.err);
33 | break;
34 |
35 | case 'SHARE_SCREENSHOT_ERROR':
36 | return {
37 | ...state,
38 | screenshotError: true
39 | };
40 |
41 | case 'SHARE_SCREENSHOT_ERROR_ERR':
42 | console.log(action.err);
43 | break;
44 |
45 | case 'SHARE_SCREENSHOT_END':
46 | return {
47 | ...state,
48 | screenshotSuccess: false,
49 | screenshotError: false
50 | };
51 |
52 | case 'SHARE_SCREENSHOT_END_ERR':
53 | console.log(action.err);
54 | break;
55 |
56 | case 'CLEAR_SCREENSHOT':
57 | return {
58 | ...state,
59 | screenshotUrl: '',
60 | takeScreenshot: false,
61 | screenshotSuccess: false,
62 | screenshotError: false
63 | };
64 |
65 | case 'CLEAR_SCREENSHOT_ERR':
66 | console.log(action.err);
67 | break;
68 |
69 | default:
70 | return state;
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/src/store/reducers/songReducer/songReducer.js:
--------------------------------------------------------------------------------
1 | import initialState from '../initialState';
2 |
3 | export const songReducer = (state = initialState.song, action) => {
4 | switch (action.type) {
5 | case 'SONG_SETTED':
6 | // for more info- https://trello.com/c/AAphnJN4/116-discussion-cors-error
7 | return {
8 | ...state,
9 | url: `https://cors-anywhere.herokuapp.com/${action.url}`,
10 | name: action.name
11 | };
12 |
13 | case 'SONG_CLEARED':
14 | return initialState.song;
15 |
16 | case 'SONG_ERROR':
17 | console.log(action.err);
18 | break;
19 |
20 | case 'BLOB_STORED':
21 | return {
22 | ...state,
23 | blob: action.blob
24 | };
25 | case 'SET_CURRENT_TIME':
26 | return {
27 | ...state,
28 | currentTime: action.currentTime
29 | }
30 | case 'SET_DURATION':
31 | return {
32 | ...state,
33 | duration: action.duration
34 | }
35 | case 'PLAY_PRESSED':
36 | return {
37 | ...state,
38 | isPlayPressed: action.isPlayPressed
39 | }
40 | default:
41 | return state;
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from './reducers/rootReducer';
4 |
5 | import { reactReduxFirebase, getFirebase } from 'react-redux-firebase';
6 | import { reduxFirestore, getFirestore } from 'redux-firestore';
7 | import FBConfig from '../firebase/config';
8 |
9 | export const store = createStore(
10 | rootReducer,
11 | compose(
12 | applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore })),
13 | reactReduxFirebase(FBConfig, {
14 | attachAuthIsReady: true,
15 | userProfile: 'users',
16 | useFirestoreForProfile: true
17 | }),
18 | reduxFirestore(FBConfig)
19 | )
20 | );
21 |
--------------------------------------------------------------------------------
/src/utils/ShowElementsOnFullSize.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { setIsElementsShowed } from '../store/actions/fullSizeActions';
4 |
5 | export default function ShowElementsOnFullSize({ elemID }) {
6 | const dispatch = useDispatch();
7 | useEffect(() => {
8 | const elem = document.getElementById(elemID);
9 | if (elem) {
10 | elem.addEventListener(
11 | 'mouseenter',
12 | () => dispatch(setIsElementsShowed(true)),
13 | false
14 | );
15 | elem.addEventListener(
16 | 'mouseleave',
17 | () => dispatch(setIsElementsShowed(false)),
18 | false
19 | );
20 | }
21 |
22 | return () => {
23 | elem.removeEventListener(
24 | 'mouseenter',
25 | () => dispatch(setIsElementsShowed(true)),
26 | false
27 | );
28 | elem.removeEventListener(
29 | 'mouseleave',
30 | () => dispatch(setIsElementsShowed(false)),
31 | false
32 | );
33 | };
34 | });
35 | return null;
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/importAllFiles.js:
--------------------------------------------------------------------------------
1 | export default function importAll(files) {
2 | return files.keys().map(files);
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/timeFormat.js:
--------------------------------------------------------------------------------
1 | export const getTimeFormatedMMSS = dur => {
2 | return (
3 | Math.floor(dur / 60) + ':' + ('0' + Math.floor(dur % 60)).slice(-2)
4 | );
5 | };
--------------------------------------------------------------------------------
/src/vendor/draws/drawOne.js:
--------------------------------------------------------------------------------
1 | export const drawOne = ({ p, fft, canvas, amplitude }) => {
2 | // WAVES
3 | const h = 100;
4 | const spectrum = fft.analyze();
5 |
6 | let scaledSpectrum = splitOctaves(spectrum, 12, p);
7 | const spectrumLength = scaledSpectrum.length;
8 |
9 | p.background(253, 155, 74, 1);
10 |
11 | // copy before clearing the background
12 | p.copy(canvas, 0, 0, p.width, p.height, 0, 1, p.width, p.height);
13 |
14 | // draw shape
15 | p.beginShape();
16 |
17 | // one at the far corner
18 | p.curveVertex(0, h);
19 |
20 | for (let i = 0; i < spectrumLength; i++) {
21 | let point = smoothPoint(scaledSpectrum, i, 2);
22 | let x = p.map(i, 0, spectrumLength - 1, 0, p.width);
23 | let y = p.map(point, 0, 255, h, 0);
24 | p.curveVertex(x, y);
25 | }
26 |
27 | // one last point at the end
28 | p.curveVertex(p.width, h);
29 |
30 | p.endShape();
31 |
32 | // ELLIPSE 1
33 | let songVolume = amplitude.getLevel();
34 | let ellipseDiameter = p.map(songVolume, 0, 1, 40, 500); // you need the map() in order to get a big enough ellipse
35 |
36 | if (songVolume < 0.1) {
37 | p.fill(15, 167, 151);
38 | } else if (songVolume < 0.2) {
39 | p.fill(196, 51, 136);
40 | } else {
41 | p.fill(253, 155, 74);
42 | }
43 | p.ellipse(p.width / 2, p.height / 2, ellipseDiameter, ellipseDiameter);
44 |
45 | // ELLIPSE 2
46 | let ellipseDiameter2 = p.map(songVolume, 0, 1, 60, 600);
47 | if (songVolume < 0.01) {
48 | p.fill(196, 51, 136);
49 | } else if (songVolume < 0.06) {
50 | p.fill(253, 155, 74);
51 | } else {
52 | p.fill(15, 167, 151);
53 | }
54 | p.ellipse(p.width / 4, p.height / 4, ellipseDiameter2, ellipseDiameter2);
55 |
56 | /**
57 | * Divides an fft array into octaves with each
58 | * divided by three, or by a specified "slicesPerOctave".
59 | *
60 | * There are 10 octaves in the range 20 - 20,000 Hz,
61 | * so this will result in 10 * slicesPerOctave + 1
62 | *
63 | * @method splitOctaves
64 | * @param {Array} spectrum Array of fft.analyze() values
65 | * @param {Number} [slicesPerOctave] defaults to thirds
66 | * @return {Array} scaledSpectrum array of the spectrum reorganized by division
67 | * of octaves
68 | */
69 | };
70 |
71 | const splitOctaves = (spectrum, slicesPerOctave, p) => {
72 | let scaledSpectrum = [];
73 | const spectrumLength = spectrum.length;
74 |
75 | // default to thirds
76 | let n = slicesPerOctave || 3;
77 | let nthRootOfTwo = Math.pow(2, 1 / n);
78 |
79 | // the last N bins get their own
80 | const lowestBin = slicesPerOctave;
81 |
82 | let binIndex = spectrumLength - 1;
83 | let i = binIndex;
84 |
85 | while (i > lowestBin) {
86 | let nextBinIndex = p.round(binIndex / nthRootOfTwo);
87 |
88 | if (nextBinIndex === 1) return;
89 |
90 | let total = 0;
91 | let numBins = 0;
92 |
93 | // add up all of the values for the frequencies
94 | for (i = binIndex; i > nextBinIndex; i--) {
95 | total += spectrum[i];
96 | numBins++;
97 | }
98 |
99 | // divide total sum by number of bins
100 | let energy = total / numBins;
101 | scaledSpectrum.push(energy);
102 |
103 | // keep the loop going
104 | binIndex = nextBinIndex;
105 | }
106 |
107 | // add the lowest bins at the end
108 | for (let j = i; j > 0; j--) {
109 | scaledSpectrum.push(spectrum[j]);
110 | }
111 |
112 | // reverse so that array has same order as original array (low to high frequencies)
113 | scaledSpectrum.reverse();
114 |
115 | return scaledSpectrum;
116 | };
117 |
118 | // average a point in an array with its neighbors
119 | const smoothPoint = (spectrum, index, numberOfNeighbors) => {
120 | // default to 2 neighbors on either side
121 | let neighbors = numberOfNeighbors || 2;
122 | let spectrumLength = spectrum.length;
123 |
124 | let val = 0;
125 |
126 | // start below the index
127 | let indexMinusNeighbors = index - neighbors;
128 | let smoothedPoints = 0;
129 |
130 | for (
131 | let i = indexMinusNeighbors;
132 | i < index + neighbors && i < spectrumLength;
133 | i++
134 | ) {
135 | // if there is a point at spectrum[i], tally it
136 | if (typeof spectrum[i] !== 'undefined') {
137 | val += spectrum[i];
138 | smoothedPoints++;
139 | }
140 | }
141 |
142 | val = val / smoothedPoints;
143 |
144 | return val;
145 | };
146 |
--------------------------------------------------------------------------------