├── .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 | logo 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 | landing page 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 | 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 |
9 |
14 | 15 | 16 |
17 |
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 |
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 | screenshot 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 |
27 | 28 | 29 | 30 | 31 |
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 |
61 | ); 62 | 63 | const AuthNav = ({ dispatch, pathname }) => { 64 | return ( 65 |
66 | {pathname !== '/app' && ( 67 | 68 |
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 | 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 |
61 | 75 |
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 | 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 | 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 | visual-item 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 | 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 |
9 |
10 |

Forgot Password

11 |

12 | Submit the form to reset your password 13 |

14 |
15 | setEmail(e.target.value)} 19 | placeholder="jon@westeros.com" 20 | /> 21 |
22 |
23 |
30 |
31 |
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 |
36 | 37 | 39 | github 40 | 41 | 42 | 43 | 44 | 45 | 47 | linkedIn 48 | 49 | 50 |
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 | blackHairGirl 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 | loading 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 |
42 |
43 |
44 |
45 | Welcome back! 46 |
47 |
48 |
49 | setEmail(e.target.value)} 54 | required 55 | /> 56 |
57 |
58 |
59 | setPassword(e.target.value)} 64 | required 65 | /> 66 |
67 |
68 | 71 |
72 |
73 | 77 | Forgot Password? 78 | 79 |
80 | 81 | {span && ( 82 | 83 | )} 84 | 85 |
86 |
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 | loading 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 |
65 |
66 |

Sign Up

67 | {span && } 68 |
69 | setName(e.target.value)} 76 | /> 77 |
78 |
79 | setEmail(e.target.value)} 86 | /> 87 |
88 |
89 | setPassword(e.target.value)} 96 | /> 97 |
98 |
99 | { 104 | setIsPasswordHidden(!isPasswordHidden); 105 | }} 106 | /> 107 | 108 |
109 |
110 | 113 |
114 |

Already a member?  Sign In

115 | 116 |
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 |
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 | --------------------------------------------------------------------------------