├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── launch.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── .npmignore │ ├── build.gradle │ ├── capacitor.build.gradle │ ├── proguard-rules.pro │ ├── release │ │ └── output.json │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── getcapacitor │ │ │ └── myapp │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── capacitor.config.json │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── com │ │ │ │ └── nerdiccoder │ │ │ │ └── blockphotos │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-land-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-ldpi │ │ │ └── splash.png │ │ │ ├── drawable-land-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-ldpi │ │ │ └── splash.png │ │ │ ├── drawable-port-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ └── launch_splash.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── icon.png │ │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── config.xml │ │ │ └── file_paths.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── getcapacitor │ │ └── myapp │ │ └── ExampleUnitTest.java ├── build.gradle ├── capacitor.settings.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── capacitor.config.json ├── electron ├── capacitor.config.json ├── icons │ ├── mac │ │ ├── icon.hqx │ │ ├── icon.icns │ │ └── icon.ico │ ├── png │ │ ├── 1024x1024.png │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 24x24.png │ │ ├── 256x256.png │ │ ├── 32x32.png │ │ ├── 48x48.png │ │ ├── 512x512.png │ │ ├── 64x64.png │ │ └── 96x96.png │ └── win │ │ └── 1024.png.ico └── main.js ├── ios ├── .gitignore ├── App │ ├── App.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── App.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── App │ │ ├── App.entitlements │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── AppIcon-20x20@1x.png │ │ │ │ ├── AppIcon-20x20@2x-1.png │ │ │ │ ├── AppIcon-20x20@2x.png │ │ │ │ ├── AppIcon-20x20@3x.png │ │ │ │ ├── AppIcon-29x29@1x.png │ │ │ │ ├── AppIcon-29x29@2x-1.png │ │ │ │ ├── AppIcon-29x29@2x.png │ │ │ │ ├── AppIcon-29x29@3x.png │ │ │ │ ├── AppIcon-40x40@1x.png │ │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ │ ├── AppIcon-40x40@2x.png │ │ │ │ ├── AppIcon-40x40@3x.png │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ ├── AppIcon-60x60@2x.png │ │ │ │ ├── AppIcon-60x60@3x.png │ │ │ │ ├── AppIcon-76x76@1x.png │ │ │ │ ├── AppIcon-76x76@2x.png │ │ │ │ ├── AppIcon-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Splash.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ └── splash-2732x2732.png │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── capacitor.config.json │ │ └── config.xml │ ├── Podfile │ └── Podfile.lock └── capacitor-cordova-ios-plugins │ ├── CordovaPlugins.podspec │ ├── CordovaPluginsResources.podspec │ ├── CordovaPluginsStatic.podspec │ ├── resources │ └── .gitkeep │ └── sources │ └── .gitkeep ├── launch.json ├── nightwatch.json ├── package-lock.json ├── package.json ├── resources ├── btc-qrcode.png ├── etherium-qrcode.png ├── iota-qrcode.png └── xrp-qrcode.png ├── src ├── _headers ├── _redirects ├── assets │ ├── blockstack-19.1.0.min.js │ ├── blockstack-19.2.1.min.js │ ├── caman.full.min.js │ ├── icon │ │ ├── android-chrome-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-256x256.png │ │ ├── android-chrome-36x36.png │ │ ├── android-chrome-384x384.png │ │ ├── android-chrome-48x48.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-72x72.png │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-1024x1024.png │ │ ├── favicon-16x16.png │ │ ├── favicon-194x194.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── icon.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── safari-pinned-tab.svg │ └── placeholder-image.jpg ├── browserconfig.xml ├── components.d.ts ├── components │ ├── block-img.spec.ts │ ├── block-img.tsx │ ├── filter-popover.tsx │ └── select-album.tsx ├── global │ ├── app.css │ └── app.ts ├── index.html ├── interfaces.d.ts ├── manifest.json ├── models │ ├── photo-metadata.ts │ └── photo-type.ts ├── pages │ ├── app-albums │ │ ├── app-albums.e2e.ts │ │ ├── app-albums.spec.ts │ │ └── app-albums.tsx │ ├── app-photo │ │ ├── app-photo.css │ │ ├── app-photo.e2e.ts │ │ ├── app-photo.spec.ts │ │ └── app-photo.tsx │ ├── app-photos │ │ ├── app-photos.css │ │ ├── app-photos.e2e.ts │ │ ├── app-photos.spec.ts │ │ └── app-photos.tsx │ ├── app-root │ │ ├── app-root.css │ │ ├── app-root.e2e.ts │ │ ├── app-root.spec.ts │ │ └── app-root.tsx │ ├── app-settings │ │ ├── app-settings.e2e.ts │ │ ├── app-settings.spec.ts │ │ └── app-settings.tsx │ └── app-signin │ │ ├── app-signin.css │ │ ├── app-signin.e2e.ts │ │ ├── app-signin.spec.ts │ │ └── app-signin.tsx ├── redirect.html ├── services │ ├── albums-service.ts │ ├── analytics-service.ts │ ├── cache-service.spec.ts │ ├── cache-service.ts │ ├── photos-service.spec.ts │ ├── photos-service.ts │ ├── presenting-service.ts │ ├── settings-service.ts │ ├── storage-service.ts │ └── upload-service.ts ├── site.webmanifest └── sw.js ├── stencil.config.ts ├── tests └── Signin.js ├── tsconfig.json ├── tslint.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.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/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | /cypress 9 | /www 10 | 11 | # production 12 | /build 13 | /electron/app 14 | /out 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | android/.idea/ 28 | reports/ 29 | 30 | dist/ 31 | !www/favicon.ico 32 | www/ 33 | 34 | *~ 35 | *.sw[mnpcod] 36 | *.log 37 | *.lock 38 | *.tmp 39 | *.tmp.* 40 | log.txt 41 | *.sublime-project 42 | *.sublime-workspace 43 | 44 | .stencil/ 45 | .idea/ 46 | .vscode/ 47 | .sass-cache/ 48 | .versions/ 49 | node_modules/ 50 | $RECYCLE.BIN/ 51 | 52 | .DS_Store 53 | Thumbs.db 54 | UserInterfaceState.xcuserstate 55 | .env 56 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | *.xml 3 | *.svg 4 | *.png 5 | *_headers 6 | *_redirects 7 | *.ico 8 | *blockstack.js 9 | *components.d.ts 10 | *index.html 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "iOS Web", 7 | "type": "chrome", 8 | "request": "attach", 9 | "port": 9222, 10 | "url": "https://79.133.3.164:9876/*", 11 | "webRoot": "${workspaceRoot}/src" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v4.2.0 (05/06/2019) 4 | 5 | - [**enhancement**] Updating blockstack.js library to version 19.2.1 6 | - [**bug**] Fix deletion of photos from the photo viewer getting stuck on black page 7 | 8 | ## v4.1.0 (01/06/2019) 9 | 10 | - [**enhancement**] New setting for clearing photos cache 11 | - [**enhancement**] Improve compression from iOS 12 | 13 | ## v4.0.0 (31/05/2019) 14 | 15 | - [**enhancement**] #48 Automatically sync photos between devices 16 | - [**enhancement**] #35 Photos can now be downloaded from Block Photos 17 | - [**enhancement**] #50 Compressed versions of photos for faster loading times 18 | - [**enhancement**] Redesign of menu buttons and loaders 19 | 20 | ## v3.0.0 (09/04/2019) 21 | 22 | - [**enhancement**] #26 Photo viewer Slide between pictures with multi-touch 23 | - [**enhancement**] #25 Slide between photos in the Photo viewer with the keyboard arrow keys 24 | - [**enhancement**] #38 Zoom in on photos in the photo viewer 25 | - [**enhancement**] Updating blockstack.js library to version 19.1.0 26 | 27 | ## v2.0.0 (10/01/2019) 28 | 29 | - [**enhancement**] Possibility to create photo albums [#41](https://github.com/nerdic-coder/block-photos/issues/41) 30 | - [**enhancement**] Add upload photos progress bar [#44](https://github.com/nerdic-coder/block-photos/issues/44) 31 | - [**enhancement**] Settings page to turn on/off anonymous tracking data 32 | - [**enhancement**] New side-menu layout 33 | 34 | ## v1.3.0 (10/01/2019) 35 | 36 | - [**bug**] Fix security issue with blockstack by updating blockstack.js library to version 18.2.1. 37 | 38 | ## v1.2.0 (04/12/2018) 39 | 40 | - [**enhancement**] Default text when pictures list is empty [#45](https://github.com/nerdic-coder/block-photos/issues/45) 41 | - [**bug**] Improve the Android PWA icons [#37](https://github.com/nerdic-coder/block-photos/issues/37) 42 | - [**bug**] CSS transform: rotate not rendering correctly on iOS [#34](https://github.com/nerdic-coder/block-photos/issues/34) 43 | - [**enhancement**] Add more details to profile page [#32](https://github.com/nerdic-coder/block-photos/issues/32) 44 | - [**enhancement**] Add the possibility to delete several pictures at once [#31](https://github.com/nerdic-coder/block-photos/issues/31) 45 | - [**enhancement**] Add similar styling to the app as the landing page [#30](https://github.com/nerdic-coder/block-photos/issues/30) 46 | - [**enhancement**] Upload photos in web app version [#29](https://github.com/nerdic-coder/block-photos/issues/29) 47 | - [**closed**] Fix 'Duplicate Code' issue in Profile and Picture pages with uploadFiles method [#27](https://github.com/nerdic-coder/block-photos/issues/27) 48 | - [**closed**] Right click menu in pictures list [#24](https://github.com/nerdic-coder/block-photos/issues/24) 49 | - [**closed**] Icon and logo for the Block Photos app [#23](https://github.com/nerdic-coder/block-photos/issues/23) 50 | - [**closed**] Create unit tests for pages [#22](https://github.com/nerdic-coder/block-photos/issues/22) 51 | - [**closed**] Create unit tests for PictureService class [#21](https://github.com/nerdic-coder/block-photos/issues/21) 52 | - [**closed**] Create blockstack mock for unit tests [#20](https://github.com/nerdic-coder/block-photos/issues/20) 53 | - [**closed**] Create electron mock for unit tests [#19](https://github.com/nerdic-coder/block-photos/issues/19) 54 | - [**closed**] Write unit tests for component BlockImg [#18](https://github.com/nerdic-coder/block-photos/issues/18) 55 | - [**closed**] Add ESLint checks on project code [#17](https://github.com/nerdic-coder/block-photos/issues/17) 56 | - [**closed**] Empty cache on logout [#16](https://github.com/nerdic-coder/block-photos/issues/16) 57 | - [**enhancement**] Add support for running Block Photos on iOS and Android [#15](https://github.com/nerdic-coder/block-photos/issues/15) 58 | - [**closed**] Slide between pictures in the picture view [#13](https://github.com/nerdic-coder/block-photos/issues/13) 59 | - [**closed**] Drag and drop uploads of pictures [#11](https://github.com/nerdic-coder/block-photos/issues/11) 60 | - [**closed**] Refresh button on the pictures list [#10](https://github.com/nerdic-coder/block-photos/issues/10) 61 | - [**enhancement**] Have the pictures list in an endless scroll [#9](https://github.com/nerdic-coder/block-photos/issues/9) 62 | - [**closed**] Show loading spinner on various actions [#8](https://github.com/nerdic-coder/block-photos/issues/8) 63 | - [**enhancement**] Add instructions on login page [#7](https://github.com/nerdic-coder/block-photos/issues/7) 64 | - [**enhancement**] Add a button to rotate the images [#6](https://github.com/nerdic-coder/block-photos/issues/6) 65 | - [**closed**] Error handling at login [#5](https://github.com/nerdic-coder/block-photos/issues/5) 66 | - [**closed**] Error handling when the picutres list can't be loaded [#4](https://github.com/nerdic-coder/block-photos/issues/4) 67 | - [**closed**] Error handling when image uploading goes wrong [#3](https://github.com/nerdic-coder/block-photos/issues/3) 68 | - [**closed**] Add a file size error message [#2](https://github.com/nerdic-coder/block-photos/issues/2) 69 | - [**closed**] Switch cache to Web SQL storage [#1](https://github.com/nerdic-coder/block-photos/issues/1) 70 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at johan@nerdic-coder.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution to Block Photos 2 | 3 | To contribute you can either report issues like bugs or good new features. 4 | If you are a developer that think you can solve any of our issues, fork this repo, follow the instructions below to develop and test your changes. 5 | Then create a pull request to this repository develop branch. Good luck! 6 | 7 | Installation: 8 | 9 | ```bash 10 | git clone https://github.com/nerdic-coder/block-photos.git block-photos 11 | cd block-photos 12 | npm install 13 | ``` 14 | 15 | Starting Test server: 16 | 17 | ```bash 18 | npm run serve 19 | ``` 20 | 21 | Starting Electron app: 22 | 23 | ```bash 24 | npm start 25 | ``` 26 | 27 | Build and package Electron app: 28 | 29 | ```bash 30 | npm run package 31 | ``` 32 | 33 | Build and package for web app distribution: 34 | 35 | ```bash 36 | npm run build:web 37 | ``` 38 | 39 | Build for Android app distribution: 40 | 41 | ```bash 42 | npm run build:android 43 | ``` 44 | 45 | Build for iOS app distribution: 46 | 47 | ```bash 48 | npm run build:ios 49 | ``` 50 | 51 | Test Lint: 52 | 53 | ```bash 54 | npm run lint 55 | ``` 56 | 57 | Run unit tests: 58 | 59 | ```bash 60 | npm test 61 | ``` 62 | 63 | Run e2e tests: 64 | 65 | 1. Start the apps dev server: 66 | 67 | ```bash 68 | npm run serve 69 | ``` 70 | 71 | 2. Start selenium 72 | 73 | ```bash 74 | npm run selenium 75 | ``` 76 | 77 | 3. Run the tests 78 | 79 | ```bash 80 | npm run e2e 81 | ``` 82 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | environment { 4 | CI = 'true' 5 | } 6 | stages { 7 | stage('Install') { 8 | steps { 9 | bat 'npm install' 10 | } 11 | } 12 | stage('Lint') { 13 | steps { 14 | bat 'npm run lint' 15 | } 16 | } 17 | stage('Test') { 18 | steps { 19 | bat 'npm run test' 20 | } 21 | } 22 | stage('Build') { 23 | steps { 24 | bat 'npm run build' 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Johan Axelsson 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 | # Block Photos - Your photo album on the blockchain! 2 | 3 |

4 | 5 |

6 | 7 | [![github](https://img.shields.io/github/release/nerdic-coder/block-photos.svg)](https://github.com/nerdic-coder/block-photos/releases/) 8 | [![github](https://img.shields.io/github/downloads/nerdic-coder/block-photos/total.svg)](https://github.com/nerdic-coder/block-photos/releases/) 9 | [![github](https://img.shields.io/github/license/nerdic-coder/block-photos.svg)](https://github.com/nerdic-coder/block-photos) 10 | [![github](https://img.shields.io/github/repo-size/nerdic-coder/block-photos.svg)](https://github.com/nerdic-coder/block-photos) 11 | [![snyk](https://img.shields.io/snyk/vulnerabilities/github/nerdic-coder/block-photos.svg)](https://github.com/nerdic-coder/block-photos) 12 | [![CodeFactor](https://www.codefactor.io/repository/github/nerdic-coder/block-photos/badge/master)](https://www.codefactor.io/repository/github/nerdic-coder/block-photos/overview/master) 13 | [![website-up-down-green-red](https://img.shields.io/website-up-down-green-red/https/app.block-photos.com.svg?label=my-website)](https://app.block-photos.com/) 14 | [![twitter](https://img.shields.io/twitter/follow/Block_Photos.svg?label=Follow&style=social)](https://twitter.com/Block_Photos) 15 | 16 | ## Project links 17 | 18 | Visit the website here: [block-photos.com](https://block-photos.com/) 19 | 20 | Test the web app here: [app.block-photos.com](https://app.block-photos.com/) 21 | 22 | Follow Block Photos on Twitter: [@Block_Photos](https://twitter.com/Block_Photos) 23 | 24 | Read my introduction article about this project: [Building a decentralized Photos app on Blockstack with React](https://nerdic-coder.com/2018/06/22/building-a-decentralized-photos-app-on-blockstack-with-react/) 25 | 26 | ## Donations 27 | 28 | If you like this project please donate to preferred cryptocurrency below: 29 | 30 | ### Coinbase commerce 31 | 32 | [Click here to donate with Coinbase commerce](https://commerce.coinbase.com/checkout/9d35f08b-bd51-40b0-a502-b88250cffc6b) 33 | 34 | ### Bitcoin 35 | 36 | 3JkxUQ763fbf1cYVAitqpWUMeY4QLaBU8M 37 | 38 |

39 | 40 |

41 | 42 | ### Etherium 43 | 44 | 0x6e54183E7b22dA87fb48e0d79ADaDa4f665A4D5a 45 | 46 |

47 | 48 |

49 | 50 | ### XRP 51 | 52 | rGaGGSbeF8HmmrLnTg8gaT92Wa77qg3Y8k 53 | 54 |

55 | 56 |

57 | 58 | ### IOTA 59 | 60 | HTPB9JVTMCDHEDMCIPOJELELVLNWLGMHVGZJUCKOYNBZGN9EVCDKFEIUJKINPCBWZI9BKAVIHRVRNWPFC9CQXJYQSZ 61 | 62 |

63 | 64 |

65 | 66 | ### NEO 67 | 68 | ALNbr6NSuEkWE7ASNB9iB5VcpZmhz1FaAg 69 | 70 | ## Developing the project 71 | 72 | To test this project do the following... 73 | 74 | Installation: 75 | 76 | ```bash 77 | git clone https://github.com/nerdic-coder/block-photos.git block-photos 78 | cd block-photos 79 | npm install 80 | ``` 81 | 82 | Starting Test server: 83 | 84 | ```bash 85 | npm run serve 86 | ``` 87 | 88 | Starting Electron app: 89 | 90 | ```bash 91 | npm start 92 | ``` 93 | 94 | Build and package Electron app: 95 | 96 | ```bash 97 | npm run package 98 | ``` 99 | 100 | Build and package for web app distribution: 101 | 102 | ```bash 103 | npm run build:web 104 | ``` 105 | 106 | Build for Android app distribution: 107 | 108 | ```bash 109 | npm run build:android 110 | ``` 111 | 112 | Build for iOS app distribution: 113 | 114 | ```bash 115 | npm run build:ios 116 | ``` 117 | 118 | Test ESLint: 119 | 120 | ```bash 121 | npm run lint 122 | ``` 123 | 124 | Run unit tests: 125 | 126 | ```bash 127 | npm test 128 | ``` 129 | 130 | Run e2e tests: 131 | 132 | 1. Start the apps dev server: 133 | 134 | ```bash 135 | npm run serve 136 | ``` 137 | 138 | 2. Start selenium 139 | 140 | ```bash 141 | npm run selenium 142 | ``` 143 | 144 | 3. Run the tests 145 | 146 | ```bash 147 | npm run e2e 148 | ``` 149 | 150 | ## Uglify 151 | 152 | Uglify javascript files with: https://www.npmjs.com/package/uglify-es 153 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # NPM renames .gitignore to .npmignore 2 | # In order to prevent that, we remove the initial "." 3 | # And the CLI then renames it 4 | 5 | # Using Android gitignore template: https://github.com/github/gitignore/blob/master/Android.gitignore 6 | 7 | # Built application files 8 | *.apk 9 | *.ap_ 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # IntelliJ 42 | *.iml 43 | .idea/workspace.xml 44 | .idea/tasks.xml 45 | .idea/gradle.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | 49 | # Keystore files 50 | # Uncomment the following line if you do not want to check your keystore files in. 51 | #*.jks 52 | 53 | # External native build folder generated in Android Studio 2.2 and later 54 | .externalNativeBuild 55 | 56 | # Google Services (e.g. APIs or Firebase) 57 | google-services.json 58 | 59 | # Freeline 60 | freeline.py 61 | freeline/ 62 | freeline_project_description.json 63 | 64 | # fastlane 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | fastlane/readme.md 70 | 71 | # Cordova plugins for Capacitor 72 | capacitor-cordova-android-plugins 73 | 74 | # Copied web assets 75 | app/src/main/assets/public 76 | -------------------------------------------------------------------------------- /android/app/.npmignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.nerdiccoder.blockphotos" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 9 10 | versionName "4.2" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | lintOptions { 20 | // Or, if you prefer, you can continue to check for errors in release builds, 21 | // but continue the build even when errors are found: 22 | abortOnError false 23 | } 24 | } 25 | 26 | repositories { 27 | maven { 28 | url "https://dl.bintray.com/ionic-team/capacitor" 29 | } 30 | flatDir{ 31 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(include: ['*.jar'], dir: 'libs') 37 | implementation 'com.android.support:appcompat-v7:28.0.0' 38 | implementation project(':capacitor-android') 39 | testImplementation 'junit:junit:4.12' 40 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 41 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 42 | } 43 | 44 | apply from: 'capacitor.build.gradle' 45 | 46 | try { 47 | def servicesJSON = file('google-services.json') 48 | if (servicesJSON.text) { 49 | apply plugin: 'com.google.gms.google-services' 50 | } 51 | } catch(Exception e) { 52 | logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work") 53 | } 54 | -------------------------------------------------------------------------------- /android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | dependencies { 4 | 5 | 6 | } 7 | 8 | 9 | if (hasProperty('postBuildExtras')) { 10 | postBuildExtras() 11 | } 12 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/app/release/output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputType": { "type": "APK" }, 4 | "apkInfo": { 5 | "type": "MAIN", 6 | "splits": [], 7 | "versionCode": 8, 8 | "versionName": "4.1", 9 | "enabled": true, 10 | "outputFile": "app-release.apk", 11 | "fullName": "release", 12 | "baseName": "release" 13 | }, 14 | "path": "app-release.apk", 15 | "properties": {} 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.nerdiccoder.blockphotos", 3 | "appName": "Block Photos", 4 | "bundledWebRuntime": false, 5 | "webDir": "www" 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/nerdiccoder/blockphotos/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.nerdiccoder.blockphotos; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.getcapacitor.BridgeActivity; 6 | import com.getcapacitor.Plugin; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class MainActivity extends BridgeActivity { 11 | @Override 12 | public void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | 15 | // Initializes the Bridge 16 | this.init(savedInstanceState, new ArrayList>() {{ 17 | // Additional plugins you've installed go here 18 | // Ex: add(TotallyAwesomePlugin.class); 19 | }}); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-ldpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-ldpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-ldpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-ldpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-hdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-mdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/app/src/main/res/mipmap-xxxhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Block Photos 4 | Block Photos 5 | com.nerdiccoder.blockphotos 6 | com.nerdiccoder.blockphotos.fileprovider 7 | blockphotosapp 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 18 | 19 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.2' 11 | classpath 'com.google.gms:google-services:4.2.0' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 30 13:14:22 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' 6 | -------------------------------------------------------------------------------- /capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.nerdiccoder.blockphotos", 3 | "appName": "Block Photos", 4 | "bundledWebRuntime": false, 5 | "webDir": "www" 6 | } 7 | -------------------------------------------------------------------------------- /electron/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.nerdiccoder.blockphotos", 3 | "appName": "Block Photos", 4 | "bundledWebRuntime": false, 5 | "webDir": "www" 6 | } 7 | -------------------------------------------------------------------------------- /electron/icons/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/mac/icon.icns -------------------------------------------------------------------------------- /electron/icons/mac/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/mac/icon.ico -------------------------------------------------------------------------------- /electron/icons/png/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/1024x1024.png -------------------------------------------------------------------------------- /electron/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/128x128.png -------------------------------------------------------------------------------- /electron/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/16x16.png -------------------------------------------------------------------------------- /electron/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/24x24.png -------------------------------------------------------------------------------- /electron/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/256x256.png -------------------------------------------------------------------------------- /electron/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/32x32.png -------------------------------------------------------------------------------- /electron/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/48x48.png -------------------------------------------------------------------------------- /electron/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/512x512.png -------------------------------------------------------------------------------- /electron/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/64x64.png -------------------------------------------------------------------------------- /electron/icons/png/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/png/96x96.png -------------------------------------------------------------------------------- /electron/icons/win/1024.png.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/electron/icons/win/1024.png.ico -------------------------------------------------------------------------------- /electron/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const { app, BrowserWindow, protocol, dialog, shell } = require('electron'); 3 | const path = require('path'); 4 | const isDev = require('electron-is-dev'); 5 | 6 | let BASE_URL = `file://${__dirname}/app/index.html`; 7 | let SCHEME = 'blockphotosapp'; 8 | if (isDev) { 9 | // BASE_URL = 'http://localhost:9876'; 10 | } 11 | 12 | protocol.registerStandardSchemes([SCHEME]); 13 | app.setAsDefaultProtocolClient(SCHEME); 14 | 15 | // Keep a global reference of the window object, if you don't, the window will 16 | // be closed automatically when the JavaScript object is garbage collected. 17 | let mainWindow; 18 | let currentAuthResponse = ''; 19 | 20 | const gotTheLock = app.requestSingleInstanceLock(); 21 | 22 | if (!gotTheLock) { 23 | app.quit(); 24 | return; 25 | } else { 26 | app.on('second-instance', (event, commandLine) => { 27 | if (mainWindow && commandLine[1]) { 28 | if (mainWindow.isMinimized()) { 29 | mainWindow.restore(); 30 | } 31 | 32 | var request = commandLine[1].split(':'); 33 | 34 | if ( 35 | request[1] && 36 | currentAuthResponse !== request[1] && 37 | request[1].includes('localhost') === false 38 | ) { 39 | currentAuthResponse = request[1]; 40 | 41 | secondWindow = new BrowserWindow({ 42 | width: 500, 43 | height: 810, 44 | icon: path.join(__dirname, 'icons/png/64x64.png') 45 | }); 46 | 47 | secondWindow.focus(); 48 | secondWindow.loadURL(BASE_URL + '?authResponse=' + request[1]); 49 | mainWindow.close(); 50 | 51 | mainWindow = secondWindow; 52 | 53 | return true; 54 | } else { 55 | shell.openExternal(commandLine[1]); 56 | return true; 57 | } 58 | } 59 | shell.openExternal(commandLine[1]); 60 | // dialog.showMessageBox({ 61 | // message: 'Authentication failed, please try again!' + commandLine[1], 62 | // buttons: ['OK'] 63 | // }); 64 | }); 65 | } 66 | 67 | // Handle creating/removing shortcuts on Windows when installing/uninstalling. 68 | if (require('electron-squirrel-startup')) { 69 | app.quit(); 70 | } 71 | 72 | const createWindow = () => { 73 | // Create the browser window. 74 | mainWindow = new BrowserWindow({ 75 | width: 500, 76 | height: 810, 77 | icon: path.join(__dirname, 'icons/png/64x64.png') 78 | }); 79 | 80 | // and load the index.html of the app. 81 | mainWindow.loadURL(BASE_URL); 82 | 83 | // Open the DevTools. 84 | // mainWindow.webContents.openDevTools(); 85 | 86 | // Emitted when the window is closed. 87 | mainWindow.on('closed', () => { 88 | // Dereference the window object, usually you would store windows 89 | // in an array if your app supports multi windows, this is the time 90 | // when you should delete the corresponding element. 91 | mainWindow = null; 92 | }); 93 | }; 94 | 95 | // This method will be called when Electron has finished 96 | // initialization and is ready to create browser windows. 97 | // Some APIs can only be used after this event occurs. 98 | app.on('ready', createWindow); 99 | 100 | // Quit when all windows are closed. 101 | app.on('window-all-closed', function() { 102 | // On OS X it is common for applications and their menu bar 103 | // to stay active until the user quits explicitly with Cmd + Q 104 | if (process.platform !== 'darwin') { 105 | app.quit(); 106 | } 107 | }); 108 | 109 | app.on('activate', function() { 110 | // On OS X it's common to re-create a window in the app when the 111 | // dock icon is clicked and there are no other windows open. 112 | if (mainWindow === null) { 113 | createWindow(); 114 | } 115 | }); 116 | 117 | app.on('open-url', function(event, url) { 118 | // On OS X it's common to re-create a window in the app when the 119 | // dock icon is clicked and there are no other windows open. 120 | if (mainWindow) { 121 | if (mainWindow.isMinimized()) { 122 | mainWindow.restore(); 123 | } 124 | 125 | var request = url.split(':'); 126 | 127 | if (request[1] && currentAuthResponse !== request[1]) { 128 | currentAuthResponse = request[1]; 129 | 130 | mainWindow.focus(); 131 | mainWindow.loadURL(BASE_URL + '?authResponse=' + request[1]); 132 | return; 133 | } 134 | } 135 | dialog.showMessageBox({ 136 | message: 'Authentication failed, please try again!', 137 | buttons: ['OK'] 138 | }); 139 | }); 140 | 141 | // In this file you can include the rest of your app's specific main process 142 | // code. You can also put them in separate files and require them here. 143 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | # NPM renames .gitignore to .npmignore 2 | # In order to prevent that, we remove the initial "." 3 | # And the CLI then renames it 4 | 5 | App/build 6 | App/Pods 7 | App/public 8 | xcuserdata 9 | 10 | -------------------------------------------------------------------------------- /ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/App/App/App.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 11 | // Override point for customization after application launch. 12 | return true 13 | } 14 | 15 | func applicationWillResignActive(_ application: UIApplication) { 16 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 17 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 18 | } 19 | 20 | func applicationDidEnterBackground(_ application: UIApplication) { 21 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 22 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 23 | } 24 | 25 | func applicationWillEnterForeground(_ application: UIApplication) { 26 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 27 | } 28 | 29 | func applicationDidBecomeActive(_ application: UIApplication) { 30 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 31 | } 32 | 33 | func applicationWillTerminate(_ application: UIApplication) { 34 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 35 | } 36 | 37 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 38 | // Called when the app was launched with a url. Feel free to add additional processing here, 39 | // but if you want the App API to support tracking app url opens, make sure to keep this call 40 | return CAPBridge.handleOpenUrl(url, options) 41 | } 42 | 43 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { 44 | // Called when the app was launched with an activity, including Universal Links. 45 | // Feel free to add additional processing here, but if you want the App API to support 46 | // tracking app url opens, make sure to keep this call 47 | return CAPBridge.handleContinueActivity(userActivity, restorationHandler) 48 | } 49 | 50 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 51 | super.touchesBegan(touches, with: event) 52 | 53 | let statusBarRect = UIApplication.shared.statusBarFrame 54 | guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return } 55 | 56 | if statusBarRect.contains(touchPoint) { 57 | NotificationCenter.default.post(CAPBridge.statusBarTappedNotification) 58 | } 59 | } 60 | 61 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 62 | NotificationCenter.default.post(name: Notification.Name(CAPNotifications.DidRegisterForRemoteNotificationsWithDeviceToken.name()), object: deviceToken) 63 | } 64 | 65 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 66 | NotificationCenter.default.post(name: Notification.Name(CAPNotifications.DidFailToRegisterForRemoteNotificationsWithError.name()), object: error) 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon-29x29@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AppIcon-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon-20x20@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "AppIcon-20x20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "AppIcon-29x29@1x.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "AppIcon-29x29@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "AppIcon-40x40@1x.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "AppIcon-40x40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "AppIcon-76x76@1x.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "AppIcon-76x76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "AppIcon-83.5x83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "AppIcon-512@2x.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Block Photos 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.2 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLSchemes 27 | 28 | prefs 29 | 30 | 31 | 32 | CFBundleURLSchemes 33 | 34 | blockphotosapp 35 | 36 | 37 | 38 | CFBundleVersion 39 | 3 40 | LSRequiresIPhoneOS 41 | 42 | NSAppTransportSecurity 43 | 44 | NSAllowsArbitraryLoads 45 | 46 | 47 | NSCameraUsageDescription 48 | To Take Photos and Video 49 | NSLocationAlwaysUsageDescription 50 | Always allow Geolocation? 51 | NSLocationWhenInUseUsageDescription 52 | Allow Geolocation? 53 | NSMicrophoneUsageDescription 54 | To Record Audio With Video 55 | NSPhotoLibraryAddUsageDescription 56 | Store camera photos to camera 57 | NSPhotoLibraryUsageDescription 58 | To Pick Photos from Library 59 | UILaunchStoryboardName 60 | LaunchScreen 61 | UIMainStoryboardFile 62 | Main 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UIStatusBarStyle 68 | UIStatusBarStyleLightContent 69 | UISupportedInterfaceOrientations 70 | 71 | UIInterfaceOrientationPortrait 72 | UIInterfaceOrientationLandscapeLeft 73 | UIInterfaceOrientationLandscapeRight 74 | 75 | UISupportedInterfaceOrientations~ipad 76 | 77 | UIInterfaceOrientationPortrait 78 | UIInterfaceOrientationPortraitUpsideDown 79 | UIInterfaceOrientationLandscapeLeft 80 | UIInterfaceOrientationLandscapeRight 81 | 82 | UIViewControllerBasedStatusBarAppearance 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /ios/App/App/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.nerdiccoder.blockphotos", 3 | "appName": "Block Photos", 4 | "bundledWebRuntime": false, 5 | "webDir": "www" 6 | } 7 | -------------------------------------------------------------------------------- /ios/App/App/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ios/App/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | use_frameworks! 3 | 4 | target 'App' do 5 | # Add your Pods here 6 | 7 | # Automatic Capacitor Pod dependencies, do not delete 8 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 9 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 10 | 11 | # Do not delete 12 | end 13 | -------------------------------------------------------------------------------- /ios/App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Capacitor (1.0.0-beta.10): 3 | - CapacitorCordova (= 1.0.0-beta.10) 4 | - CapacitorCordova (1.0.0-beta.10) 5 | 6 | DEPENDENCIES: 7 | - "Capacitor (from `../../node_modules/@capacitor/ios`)" 8 | - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" 9 | 10 | EXTERNAL SOURCES: 11 | Capacitor: 12 | :path: "../../node_modules/@capacitor/ios" 13 | CapacitorCordova: 14 | :path: "../../node_modules/@capacitor/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Capacitor: 9aa60986d00166b45b3216daf90f49f8df6593fa 18 | CapacitorCordova: aab520b872cabe0d9f30a03dc385c5dce8d2c82b 19 | 20 | PODFILE CHECKSUM: 144b255a8aae841ea14f3ee0bfb55be44afd1167 21 | 22 | COCOAPODS: 1.5.3 23 | -------------------------------------------------------------------------------- /ios/capacitor-cordova-ios-plugins/CordovaPlugins.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'CordovaPlugins' 4 | s.version = '1.0.0-beta.11' 5 | s.summary = 'Autogenerated spec' 6 | s.license = 'Unknown' 7 | s.homepage = 'https://example.com' 8 | s.authors = { 'Capacitor Generator' => 'hi@example.com' } 9 | s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '1.0.0-beta.11' } 10 | s.source_files = 'sources/**/*.{swift,h,m,c,cc,mm,cpp}' 11 | s.ios.deployment_target = '11.0' 12 | s.dependency 'CapacitorCordova' 13 | s.swift_version = '4.0' 14 | 15 | end -------------------------------------------------------------------------------- /ios/capacitor-cordova-ios-plugins/CordovaPluginsResources.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CordovaPluginsResources' 3 | s.version = '0.0.105' 4 | s.summary = 'Resources for Cordova plugins' 5 | s.social_media_url = 'http://twitter.com/getcapacitor' 6 | s.license = 'MIT' 7 | s.homepage = 'https://capacitor.ionicframework.com/' 8 | s.authors = { 'Ionic Team' => 'hi@ionicframework.com' } 9 | s.source = { :git => 'https://github.com/ionic-team/capacitor.git', :tag => s.version.to_s } 10 | s.resources = ['resources/*'] 11 | end -------------------------------------------------------------------------------- /ios/capacitor-cordova-ios-plugins/CordovaPluginsStatic.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'CordovaPluginsStatic' 4 | s.version = '1.0.0-beta.11' 5 | s.summary = 'Autogenerated spec' 6 | s.license = 'Unknown' 7 | s.homepage = 'https://example.com' 8 | s.authors = { 'Capacitor Generator' => 'hi@example.com' } 9 | s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '1.0.0-beta.11' } 10 | s.source_files = 'sourcesstatic/**/*.{swift,h,m,c,cc,mm,cpp}' 11 | s.ios.deployment_target = '11.0' 12 | s.dependency 'CapacitorCordova' 13 | s.swift_version = '4.0' 14 | s.static_framework = true 15 | end -------------------------------------------------------------------------------- /ios/capacitor-cordova-ios-plugins/resources/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ios/capacitor-cordova-ios-plugins/sources/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "iOS Web", 6 | "type": "chrome", 7 | "request": "attach", 8 | "port": 9000, 9 | "url": "https://app.block-photos.com/*", 10 | "webRoot": "${workspaceRoot}/src" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /nightwatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_folders" : ["tests"], 3 | "output_folder" : "reports", 4 | "custom_commands_path" : "", 5 | "custom_assertions_path" : "", 6 | "page_objects_path" : "", 7 | "globals_path" : "", 8 | 9 | "selenium" : { 10 | "start_process" : false, 11 | "server_path" : "", 12 | "log_path" : "", 13 | "port" : 4444, 14 | "cli_args" : { 15 | "webdriver.chrome.driver" : "", 16 | "webdriver.gecko.driver" : "", 17 | "webdriver.edge.driver" : "" 18 | } 19 | }, 20 | 21 | "test_settings" : { 22 | "default" : { 23 | "launch_url" : "https://localhost:9876", 24 | "selenium_port" : 4444, 25 | "selenium_host" : "localhost", 26 | "silent": true, 27 | "screenshots" : { 28 | "enabled" : false, 29 | "path" : "" 30 | }, 31 | "desiredCapabilities": { 32 | "browserName": "chrome", 33 | "marionette": true 34 | } 35 | }, 36 | "chrome" : { 37 | "desiredCapabilities": { 38 | "browserName": "chrome", 39 | "chromeOptions" : { 40 | "args" : ["--no-sandbox --user-data-dir=E:\\workspaces\\block-photos\\tests\\session"] 41 | } 42 | } 43 | }, 44 | 45 | "edge" : { 46 | "desiredCapabilities": { 47 | "browserName": "MicrosoftEdge" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "block-photos", 3 | "version": "4.2.0", 4 | "private": false, 5 | "description": "The app for storing your photos privatly in the cloud!", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nerdic-coder/block-photos" 9 | }, 10 | "license": "MIT", 11 | "author": "Johan Axelsson", 12 | "main": "electron/main.js", 13 | "files": [ 14 | "dist/" 15 | ], 16 | "scripts": { 17 | "build": "stencil build", 18 | "build:android": "npm run build && npx cap copy android", 19 | "build:ios": "npm run build && npx cap copy ios", 20 | "build:web": "npm run build", 21 | "electron": "electron-forge start", 22 | "electron-forge": "electron-forge", 23 | "make": "npm run build && npx cap copy electron && electron-forge make", 24 | "package": "npm run build && npx cap copy electron && electron-forge package", 25 | "publish": "npm run build && npx cap copy electron && electron-forge publish", 26 | "serve": "stencil build --dev --watch --serve", 27 | "start": "npm run build && npx cap copy electron && npm run electron", 28 | "test": "stencil test --spec --e2e", 29 | "test.watch": "stencil test --spec --e2e --watch", 30 | "e2e": "nightwatch", 31 | "selenium": "selenium-standalone install && selenium-standalone start", 32 | "format": "npx prettier --write src/**" 33 | }, 34 | "config": { 35 | "forge": { 36 | "packagerConfig": { 37 | "name": "Block Photos", 38 | "icon": "electron/icons/mac/icon", 39 | "ignore": [ 40 | ".vscode", 41 | "coverage", 42 | "android", 43 | "ios", 44 | "public", 45 | "www" 46 | ], 47 | "prune": true, 48 | "asar": true 49 | }, 50 | "makers": [ 51 | { 52 | "name": "@electron-forge/maker-dmg" 53 | }, 54 | { 55 | "name": "@electron-forge/maker-squirrel", 56 | "config": { 57 | "name": "block_photos", 58 | "iconUrl": "https://raw.githubusercontent.com/nerdic-coder/block-photos/master/electron/icons/mac/icon.ico", 59 | "setupIcon": "electron/icons/mac/icon.ico" 60 | } 61 | }, 62 | { 63 | "name": "@electron-forge/maker-zip" 64 | }, 65 | { 66 | "name": "@electron-forge/maker-deb", 67 | "config": {} 68 | }, 69 | { 70 | "name": "@electron-forge/maker-rpm", 71 | "config": { 72 | "options": { 73 | "maintainer": "Johan Axelsson", 74 | "homepage": "https://block-photos.com/" 75 | } 76 | } 77 | } 78 | ], 79 | "publishers": [ 80 | { 81 | "name": "@electron-forge/publisher-github", 82 | "config": { 83 | "repository": { 84 | "owner": "nerdic-coder", 85 | "name": "block-photos" 86 | }, 87 | "prerelease": true 88 | } 89 | } 90 | ], 91 | "github_repository": { 92 | "owner": "nerdic-coder", 93 | "name": "block-photos" 94 | } 95 | } 96 | }, 97 | "dependencies": { 98 | "@capacitor/core": "1.0.0", 99 | "@ionic/core": "4.4.2", 100 | "@sentry/browser": "5.4.0", 101 | "blueimp-load-image": "2.21.0", 102 | "browser-image-compression": "1.0.5", 103 | "compressorjs": "1.0.5", 104 | "cors": "2.8.5", 105 | "electron-is-dev": "1.1.0", 106 | "electron-squirrel-startup": "^1.0.0", 107 | "is-electron": "^2.2.0", 108 | "js-file-downloader": "^1.1.0", 109 | "jszip": "3.2.1", 110 | "localforage": "1.7.3", 111 | "uuid": "3.3.2" 112 | }, 113 | "devDependencies": { 114 | "@capacitor/android": "1.0.0", 115 | "@capacitor/cli": "1.0.0", 116 | "@capacitor/ios": "1.0.0", 117 | "@electron-forge/cli": "6.0.0-beta.39", 118 | "@electron-forge/maker-appx": "6.0.0-beta.39", 119 | "@electron-forge/maker-deb": "6.0.0-beta.39", 120 | "@electron-forge/maker-dmg": "6.0.0-beta.39", 121 | "@electron-forge/maker-pkg": "6.0.0-beta.39", 122 | "@electron-forge/maker-rpm": "6.0.0-beta.39", 123 | "@electron-forge/maker-squirrel": "6.0.0-beta.39", 124 | "@electron-forge/maker-zip": "6.0.0-beta.39", 125 | "@electron-forge/publisher-github": "6.0.0-beta.39", 126 | "@stencil/core": "0.18.1", 127 | "@types/jest": "24.0.13", 128 | "@types/puppeteer": "1.12.4", 129 | "blockstack": "19.2.1", 130 | "electron": "9.4.0", 131 | "husky": "2.4.0", 132 | "jest": "24.8.0", 133 | "jest-cli": "24.8.0", 134 | "nightwatch": "1.1.11", 135 | "prettier": "1.17.1", 136 | "pretty-quick": "1.11.0", 137 | "puppeteer": "1.17.0", 138 | "rollup-plugin-node-builtins": "2.1.2", 139 | "rollup-plugin-node-globals": "1.4.0", 140 | "selenium-standalone": "^6.16.0", 141 | "tslint": "5.17.0", 142 | "tslint-ionic-rules": "0.0.21", 143 | "workbox-build": "3.4.1" 144 | }, 145 | "browserslist": [ 146 | ">0.2%", 147 | "not dead", 148 | "not ie <= 11", 149 | "not op_mini all" 150 | ], 151 | "productName": "block-photos", 152 | "husky": { 153 | "hooks": { 154 | "pre-commit": "pretty-quick --staged" 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /resources/btc-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/resources/btc-qrcode.png -------------------------------------------------------------------------------- /resources/etherium-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/resources/etherium-qrcode.png -------------------------------------------------------------------------------- /resources/iota-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/resources/iota-qrcode.png -------------------------------------------------------------------------------- /resources/xrp-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/resources/xrp-qrcode.png -------------------------------------------------------------------------------- /src/_headers: -------------------------------------------------------------------------------- 1 | ## A manifest.json: 2 | /manifest.json 3 | # Headers for that path: 4 | Access-Control-Allow-Origin: * 5 | 6 | ## A service-worker.js: 7 | /*service-worker.js 8 | # Headers for that path: 9 | Access-Control-Allow-Origin: * 10 | Content-Type: application/javascript; charset=UTF-8 11 | 12 | ## A precache-manifest: 13 | /*precache-manifest* 14 | # Headers for that path: 15 | Access-Control-Allow-Origin: * 16 | Content-Type: application/javascript; charset=UTF-8 17 | -------------------------------------------------------------------------------- /src/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-144x144.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-256x256.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-36x36.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-384x384.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-48x48.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-72x72.png -------------------------------------------------------------------------------- /src/assets/icon/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/assets/icon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/favicon-1024x1024.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-194x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/favicon-194x194.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/icon.png -------------------------------------------------------------------------------- /src/assets/icon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/mstile-144x144.png -------------------------------------------------------------------------------- /src/assets/icon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/icon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/mstile-310x150.png -------------------------------------------------------------------------------- /src/assets/icon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/mstile-310x310.png -------------------------------------------------------------------------------- /src/assets/icon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/icon/mstile-70x70.png -------------------------------------------------------------------------------- /src/assets/icon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/placeholder-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/assets/placeholder-image.jpg -------------------------------------------------------------------------------- /src/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #603cba 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * This is an autogenerated file created by the Stencil compiler. 4 | * It contains typing information for all components that exist in this project. 5 | */ 6 | 7 | 8 | import '@stencil/core'; 9 | 10 | import '@ionic/core'; 11 | import 'ionicons'; 12 | import { 13 | PhotoType, 14 | } from './models/photo-type'; 15 | 16 | 17 | export namespace Components { 18 | 19 | interface BlockImg { 20 | 'photoId': string; 21 | 'phototType': PhotoType; 22 | 'refresh': number; 23 | 'rotate': boolean; 24 | } 25 | interface BlockImgAttributes extends StencilHTMLAttributes { 26 | 'photoId'?: string; 27 | 'phototType'?: PhotoType; 28 | 'refresh'?: number; 29 | 'rotate'?: boolean; 30 | } 31 | 32 | interface FilterPopover { 33 | 'selectedPhotos': any[]; 34 | } 35 | interface FilterPopoverAttributes extends StencilHTMLAttributes { 36 | 'selectedPhotos'?: any[]; 37 | } 38 | 39 | interface SelectAlbum { 40 | 'endCallback': any; 41 | 'selectedPhotos': any[]; 42 | 'startCallback': any; 43 | } 44 | interface SelectAlbumAttributes extends StencilHTMLAttributes { 45 | 'endCallback'?: any; 46 | 'selectedPhotos'?: any[]; 47 | 'startCallback'?: any; 48 | } 49 | 50 | interface AppAlbums {} 51 | interface AppAlbumsAttributes extends StencilHTMLAttributes {} 52 | 53 | interface AppPhoto { 54 | 'albumId': string; 55 | 'photoId': string; 56 | 'updateCallback': any; 57 | } 58 | interface AppPhotoAttributes extends StencilHTMLAttributes { 59 | 'albumId'?: string; 60 | 'photoId'?: string; 61 | 'updateCallback'?: any; 62 | } 63 | 64 | interface AppPhotos { 65 | 'albumId': string; 66 | 'photoId': string; 67 | } 68 | interface AppPhotosAttributes extends StencilHTMLAttributes { 69 | 'albumId'?: string; 70 | 'photoId'?: string; 71 | } 72 | 73 | interface AppRoot {} 74 | interface AppRootAttributes extends StencilHTMLAttributes {} 75 | 76 | interface AppSettings {} 77 | interface AppSettingsAttributes extends StencilHTMLAttributes {} 78 | 79 | interface AppSignin {} 80 | interface AppSigninAttributes extends StencilHTMLAttributes {} 81 | } 82 | 83 | declare global { 84 | interface StencilElementInterfaces { 85 | 'BlockImg': Components.BlockImg; 86 | 'FilterPopover': Components.FilterPopover; 87 | 'SelectAlbum': Components.SelectAlbum; 88 | 'AppAlbums': Components.AppAlbums; 89 | 'AppPhoto': Components.AppPhoto; 90 | 'AppPhotos': Components.AppPhotos; 91 | 'AppRoot': Components.AppRoot; 92 | 'AppSettings': Components.AppSettings; 93 | 'AppSignin': Components.AppSignin; 94 | } 95 | 96 | interface StencilIntrinsicElements { 97 | 'block-img': Components.BlockImgAttributes; 98 | 'filter-popover': Components.FilterPopoverAttributes; 99 | 'select-album': Components.SelectAlbumAttributes; 100 | 'app-albums': Components.AppAlbumsAttributes; 101 | 'app-photo': Components.AppPhotoAttributes; 102 | 'app-photos': Components.AppPhotosAttributes; 103 | 'app-root': Components.AppRootAttributes; 104 | 'app-settings': Components.AppSettingsAttributes; 105 | 'app-signin': Components.AppSigninAttributes; 106 | } 107 | 108 | 109 | interface HTMLBlockImgElement extends Components.BlockImg, HTMLStencilElement {} 110 | var HTMLBlockImgElement: { 111 | prototype: HTMLBlockImgElement; 112 | new (): HTMLBlockImgElement; 113 | }; 114 | 115 | interface HTMLFilterPopoverElement extends Components.FilterPopover, HTMLStencilElement {} 116 | var HTMLFilterPopoverElement: { 117 | prototype: HTMLFilterPopoverElement; 118 | new (): HTMLFilterPopoverElement; 119 | }; 120 | 121 | interface HTMLSelectAlbumElement extends Components.SelectAlbum, HTMLStencilElement {} 122 | var HTMLSelectAlbumElement: { 123 | prototype: HTMLSelectAlbumElement; 124 | new (): HTMLSelectAlbumElement; 125 | }; 126 | 127 | interface HTMLAppAlbumsElement extends Components.AppAlbums, HTMLStencilElement {} 128 | var HTMLAppAlbumsElement: { 129 | prototype: HTMLAppAlbumsElement; 130 | new (): HTMLAppAlbumsElement; 131 | }; 132 | 133 | interface HTMLAppPhotoElement extends Components.AppPhoto, HTMLStencilElement {} 134 | var HTMLAppPhotoElement: { 135 | prototype: HTMLAppPhotoElement; 136 | new (): HTMLAppPhotoElement; 137 | }; 138 | 139 | interface HTMLAppPhotosElement extends Components.AppPhotos, HTMLStencilElement {} 140 | var HTMLAppPhotosElement: { 141 | prototype: HTMLAppPhotosElement; 142 | new (): HTMLAppPhotosElement; 143 | }; 144 | 145 | interface HTMLAppRootElement extends Components.AppRoot, HTMLStencilElement {} 146 | var HTMLAppRootElement: { 147 | prototype: HTMLAppRootElement; 148 | new (): HTMLAppRootElement; 149 | }; 150 | 151 | interface HTMLAppSettingsElement extends Components.AppSettings, HTMLStencilElement {} 152 | var HTMLAppSettingsElement: { 153 | prototype: HTMLAppSettingsElement; 154 | new (): HTMLAppSettingsElement; 155 | }; 156 | 157 | interface HTMLAppSigninElement extends Components.AppSignin, HTMLStencilElement {} 158 | var HTMLAppSigninElement: { 159 | prototype: HTMLAppSigninElement; 160 | new (): HTMLAppSigninElement; 161 | }; 162 | 163 | interface HTMLElementTagNameMap { 164 | 'block-img': HTMLBlockImgElement 165 | 'filter-popover': HTMLFilterPopoverElement 166 | 'select-album': HTMLSelectAlbumElement 167 | 'app-albums': HTMLAppAlbumsElement 168 | 'app-photo': HTMLAppPhotoElement 169 | 'app-photos': HTMLAppPhotosElement 170 | 'app-root': HTMLAppRootElement 171 | 'app-settings': HTMLAppSettingsElement 172 | 'app-signin': HTMLAppSigninElement 173 | } 174 | 175 | interface ElementTagNameMap { 176 | 'block-img': HTMLBlockImgElement; 177 | 'filter-popover': HTMLFilterPopoverElement; 178 | 'select-album': HTMLSelectAlbumElement; 179 | 'app-albums': HTMLAppAlbumsElement; 180 | 'app-photo': HTMLAppPhotoElement; 181 | 'app-photos': HTMLAppPhotosElement; 182 | 'app-root': HTMLAppRootElement; 183 | 'app-settings': HTMLAppSettingsElement; 184 | 'app-signin': HTMLAppSigninElement; 185 | } 186 | 187 | 188 | export namespace JSX { 189 | export interface Element {} 190 | export interface IntrinsicElements extends StencilIntrinsicElements { 191 | [tagName: string]: any; 192 | } 193 | } 194 | export interface HTMLAttributes extends StencilHTMLAttributes {} 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/components/block-img.spec.ts: -------------------------------------------------------------------------------- 1 | // import BlockImg from './block-img'; 2 | 3 | import blockstack from 'blockstack'; 4 | 5 | jest.mock('blockstack'); 6 | 7 | it('renders without crashing', () => { 8 | const mockResponse = 'base64mumbojumbo'; 9 | blockstack.getFile.mockReturnValue(Promise.resolve(mockResponse)); 10 | 11 | // const div = document.createElement('div'); 12 | // ReactDOM.render( 13 | // 14 | // , div); 15 | // ReactDOM.unmountComponentAtNode(div); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/block-img.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop, State, Watch } from '@stencil/core'; 2 | import loadImage from 'blueimp-load-image'; 3 | 4 | import PhotosService from '../services/photos-service'; 5 | import { PhotoType } from '../models/photo-type'; 6 | 7 | @Component({ 8 | tag: 'block-img' 9 | }) 10 | export class BlockImg { 11 | @Prop() photoId: string; 12 | @Prop() rotate: boolean; 13 | @Prop() refresh: number; 14 | @Prop() phototType: PhotoType = PhotoType.Download; 15 | 16 | @State() source: string; 17 | @State() isLoaded: boolean; 18 | @State() rotation: number; 19 | 20 | constructor() { 21 | this.rotation = 1; 22 | } 23 | 24 | componentWillLoad(): void { 25 | this.getPhoto(); 26 | } 27 | 28 | @Watch('photoId') 29 | photoIdDidUpdate(newValue: string, oldValue: string): void { 30 | if (newValue !== oldValue) { 31 | this.getPhoto(); 32 | } 33 | } 34 | 35 | @Watch('refresh') 36 | refreshDidUpdate(newValue: number, oldValue: number): void { 37 | if (newValue !== oldValue) { 38 | this.getPhoto(newValue); 39 | } 40 | } 41 | 42 | async getPhoto(newRotation?: number): Promise { 43 | const { photoId, rotate } = this; 44 | let rotation = 1; 45 | 46 | if (photoId === null) { 47 | this.source = 48 | 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='; 49 | return; 50 | } 51 | 52 | const metadata: PhotoMetadata = await PhotosService.getPhotoMetaData( 53 | photoId 54 | ); 55 | const photo = await PhotosService.loadPhoto( 56 | metadata, 57 | this.phototType, 58 | true 59 | ); 60 | let base64; 61 | if (photo) { 62 | base64 = photo.base64; 63 | } 64 | if (newRotation) { 65 | rotation = newRotation; 66 | } else if ( 67 | metadata && 68 | metadata.stats && 69 | metadata.stats.exifdata && 70 | metadata.stats.exifdata.tags.Orientation 71 | ) { 72 | rotation = metadata.stats.exifdata.tags.Orientation; 73 | // Handle correct orientation for iOS 74 | if ( 75 | this.iOS() && 76 | photo.phototType === PhotoType.Download && 77 | metadata.stats.exifdata.tags.OriginalOrientation 78 | ) { 79 | const originalOrientation = 80 | metadata.stats.exifdata.tags.OriginalOrientation; 81 | // If the orientation is unchanged don't rotate at all with CSS, iOS handles it automatic 82 | if (rotation === originalOrientation) { 83 | rotation = 1; 84 | } else if (rotation === 1 && originalOrientation === 6) { 85 | rotation = 8; 86 | } else if (rotation === 1) { 87 | rotation = originalOrientation; 88 | } else if (rotation === 3 && originalOrientation === 6) { 89 | rotation = 6; 90 | } else if (rotation === 8 && originalOrientation === 6) { 91 | rotation = 3; 92 | } else if (rotation === 3 && originalOrientation === 8) { 93 | rotation = 6; 94 | } else if (rotation === 6 && originalOrientation === 8) { 95 | rotation = 3; 96 | } else if (rotation === 8 && originalOrientation === 3) { 97 | rotation = 6; 98 | } else if (rotation === 6 && originalOrientation === 3) { 99 | rotation = 8; 100 | } 101 | } 102 | } 103 | 104 | // Set photo orientation from exif 105 | if (rotate) { 106 | const imageOptions = { 107 | orientation: rotation 108 | }; 109 | loadImage( 110 | base64, 111 | processedPhoto => { 112 | this.handleProcessedPhoto(processedPhoto); 113 | }, 114 | imageOptions 115 | ); 116 | } else { 117 | this.rotation = rotation; 118 | this.source = base64; 119 | } 120 | } 121 | 122 | handleProcessedPhoto(processedPhoto: any): void { 123 | if (processedPhoto.type === 'error') { 124 | // TODO: show error message 125 | } else if (processedPhoto.tagName === 'CANVAS') { 126 | this.source = processedPhoto.toDataURL(); 127 | } else { 128 | this.source = processedPhoto.src; 129 | } 130 | } 131 | 132 | iOS(): boolean { 133 | const iDevices = [ 134 | 'iPad Simulator', 135 | 'iPhone Simulator', 136 | 'iPod Simulator', 137 | 'iPad', 138 | 'iPhone', 139 | 'iPod' 140 | ]; 141 | 142 | if (navigator.platform) { 143 | while (iDevices.length) { 144 | if (navigator.platform === iDevices.pop()) { 145 | return true; 146 | } 147 | } 148 | } 149 | 150 | return false; 151 | } 152 | 153 | preventDrag(event: any): boolean { 154 | event.preventDefault(); 155 | return false; 156 | } 157 | 158 | photoLoaded() { 159 | this.isLoaded = true; 160 | } 161 | 162 | render() { 163 | const { isLoaded, source, rotation } = this; 164 | 165 | return [ 166 | this.preventDrag(event)} 172 | onIonImgDidLoad={() => this.photoLoaded()} 173 | />, 174 | 179 | ]; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/components/filter-popover.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop } from '@stencil/core'; 2 | import { RangeChangeEventDetail, RangeValue } from '@ionic/core'; 3 | 4 | declare var Caman; 5 | // import PresentingService from '../services/presenting-service'; 6 | // import PhotosService from '../services/photos-service'; 7 | 8 | @Component({ 9 | tag: 'filter-popover' 10 | }) 11 | export class FilterPopover { 12 | @Prop() selectedPhotos: any[] = []; 13 | 14 | private brightness: RangeValue = 0; 15 | private contrast: RangeValue = 0; 16 | private saturation: RangeValue = 0; 17 | 18 | async closePopover() { 19 | const popoverController = document.querySelector('ion-popover-controller'); 20 | await popoverController.componentOnReady(); 21 | popoverController.dismiss(); 22 | this.selectedPhotos = null; 23 | } 24 | 25 | setBrightness(event: CustomEvent) { 26 | console.log(event.detail.value); 27 | this.brightness = event.detail.value; 28 | this.applyFilters(); 29 | } 30 | 31 | setSaturation(event: CustomEvent) { 32 | console.log(event.detail.value); 33 | this.saturation = event.detail.value; 34 | this.applyFilters(); 35 | } 36 | 37 | setContrast(event: CustomEvent) { 38 | console.log(event.detail.value); 39 | this.contrast = event.detail.value; 40 | this.applyFilters(); 41 | } 42 | 43 | applyFilters() { 44 | const brightness = this.brightness; 45 | const contrast = this.contrast; 46 | const saturation = this.saturation; 47 | // const photoId = this.selectedPhotos[0]; 48 | 49 | Caman('#img-' + this.selectedPhotos[0], function() { 50 | // this.greyscale(); 51 | this.revert(false); 52 | this.brightness(brightness); 53 | this.contrast(contrast); 54 | this.saturation(saturation); 55 | this.render(async () => { 56 | // const result = await PhotosService.updatePhoto(photoId, this.toBase64()); 57 | // console.log('Caman result ', result); 58 | }); 59 | }); 60 | } 61 | 62 | render() { 63 | return ( 64 | 65 | 66 | Filters 67 | 68 | 69 | 70 | Brightness 71 | 72 | 73 | this.setBrightness(event)} 75 | min={-100} 76 | max={100} 77 | color="secondary" 78 | pin={true} 79 | > 80 | -100 81 | 100 82 | 83 | 84 | 85 | 86 | Contrast 87 | 88 | 89 | this.setContrast(event)} 91 | min={-100} 92 | max={100} 93 | color="secondary" 94 | pin={true} 95 | > 96 | -100 97 | 100 98 | 99 | 100 | 101 | 102 | Saturation 103 | 104 | 105 | this.setSaturation(event)} 107 | min={-100} 108 | max={100} 109 | color="secondary" 110 | pin={true} 111 | > 112 | -100 113 | 100 114 | 115 | 116 | 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/select-album.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop, State } from '@stencil/core'; 2 | 3 | import AlbumsService from '../services/albums-service'; 4 | import PresentingService from '../services/presenting-service'; 5 | import PhotosService from '../services/photos-service'; 6 | 7 | @Component({ 8 | tag: 'select-album' 9 | }) 10 | export class SelectAlbum { 11 | private present: PresentingService; 12 | 13 | @State() albums: any[] = []; 14 | @State() isLoaded: boolean; 15 | 16 | @Prop() selectedPhotos: any[] = []; 17 | @Prop() startCallback: any; 18 | @Prop() endCallback: any; 19 | 20 | constructor() { 21 | this.present = new PresentingService(); 22 | } 23 | 24 | componentWillLoad() { 25 | this.isLoaded = false; 26 | } 27 | 28 | async componentDidLoad() { 29 | this.loadAlbums(false); 30 | } 31 | 32 | async loadAlbums(sync?: boolean) { 33 | try { 34 | const albumsResponse = await AlbumsService.getAlbums(sync); 35 | this.albums = albumsResponse.albums; 36 | 37 | this.isLoaded = true; 38 | 39 | this.handleAlbumErrors(albumsResponse); 40 | } catch (error) { 41 | this.isLoaded = true; 42 | } 43 | } 44 | 45 | private handleAlbumErrors(albumsResponse: any) { 46 | if (albumsResponse.errorsList && albumsResponse.errorsList.length > 0) { 47 | for (const error of albumsResponse.errorsList) { 48 | if (error.errorCode === 'err_cache') { 49 | this.present.toast('Failed to load local albums. Please try again!'); 50 | } else if (error.errorCode) { 51 | this.present.toast( 52 | 'Could not load albums from blockstack. Please try again or create some albums if you have none!' 53 | ); 54 | } 55 | } 56 | } 57 | } 58 | 59 | async addPhotosToAlbum(event: any, album: any) { 60 | event.preventDefault(); 61 | await this.closePopover(); 62 | if (this.startCallback) { 63 | this.startCallback(); 64 | } 65 | const result: boolean = await PhotosService.addPhotosToAlbum( 66 | album.albumId, 67 | this.selectedPhotos 68 | ); 69 | if (result) { 70 | this.present.toast('Photo(s) added to album "' + album.albumName + '".'); 71 | } else { 72 | this.present.toast( 73 | 'Failed to add photo(s) to album "' + album.albumName + '".' 74 | ); 75 | } 76 | if (this.endCallback) { 77 | this.endCallback(); 78 | } 79 | } 80 | 81 | async closePopover() { 82 | const popoverController = document.querySelector('ion-popover-controller'); 83 | await popoverController.componentOnReady(); 84 | popoverController.dismiss(); 85 | this.selectedPhotos = null; 86 | } 87 | 88 | render() { 89 | if (this.isLoaded) { 90 | return ( 91 | 92 | 93 | Add to album 94 | 95 | {this.albums.map(album => ( 96 | this.addPhotosToAlbum(event, album)} 100 | > 101 | {album.albumName} 102 | 103 | ))} 104 | 105 | ); 106 | } else { 107 | return ; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/global/app.css: -------------------------------------------------------------------------------- 1 | /* Document Level Styles */ 2 | 3 | /* 4 | The imports below are needed to include our light dom css for global styles such as fonts and colors. 5 | You can comment out any of these imports if you do not need that css. For example, if you have your own 6 | global font family css then you can comment out the typography.css import. 7 | */ 8 | 9 | /** Core CSS required for ionic components to work property */ 10 | @import '~@ionic/core/css/core.css'; 11 | 12 | /** Basic CSS for apps built with Ionic */ 13 | @import '~@ionic/core/css/normalize.css'; 14 | @import '~@ionic/core/css/structure.css'; 15 | @import '~@ionic/core/css/typography.css'; 16 | @import '~@ionic/core/css/display.css'; 17 | 18 | /** Optional CSS utils that can be commented out */ 19 | @import '~@ionic/core/css/padding.css'; 20 | @import '~@ionic/core/css/float-elements.css'; 21 | @import '~@ionic/core/css/text-alignment.css'; 22 | @import '~@ionic/core/css/text-transformation.css'; 23 | @import '~@ionic/core/css/flex-utils.css'; 24 | 25 | /* 26 | The CSS Variables below can be used to theme your app. 27 | For more info on CSS variables check out: 28 | https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables 29 | 30 | More info about color theming using Ionic: 31 | https://beta.ionicframework.com/docs/theming/color-generator 32 | */ 33 | body { 34 | margin: 0; 35 | padding: 0; 36 | font-family: sans-serif; 37 | } 38 | 39 | :root { 40 | --ion-color-primary: #220631; 41 | --ion-color-primary-rgb: 34, 6, 49; 42 | --ion-color-primary-contrast: #ffffff; 43 | --ion-color-primary-contrast-rgb: 255, 255, 255; 44 | --ion-color-primary-shade: #1e052b; 45 | --ion-color-primary-tint: #381f46; 46 | 47 | --ion-color-secondary: #663399; 48 | --ion-color-secondary-rgb: 12, 209, 232; 49 | --ion-color-secondary-contrast: #ffffff; 50 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 51 | --ion-color-secondary-shade: #9249db; 52 | --ion-color-secondary-tint: rgb(129, 40, 218); 53 | 54 | --ion-color-tertiary: #7044ff; 55 | --ion-color-tertiary-rgb: 112, 68, 255; 56 | --ion-color-tertiary-contrast: #ffffff; 57 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 58 | --ion-color-tertiary-shade: #633ce0; 59 | --ion-color-tertiary-tint: #7e57ff; 60 | 61 | --ion-color-success: #10dc60; 62 | --ion-color-success-rgb: 16, 220, 96; 63 | --ion-color-success-contrast: #ffffff; 64 | --ion-color-success-contrast-rgb: 255, 255, 255; 65 | --ion-color-success-shade: #0ec254; 66 | --ion-color-success-tint: #28e070; 67 | 68 | --ion-color-warning: #ffce00; 69 | --ion-color-warning-rgb: 255, 206, 0; 70 | --ion-color-warning-contrast: #ffffff; 71 | --ion-color-warning-contrast-rgb: 255, 255, 255; 72 | --ion-color-warning-shade: #e0b500; 73 | --ion-color-warning-tint: #ffd31a; 74 | 75 | --ion-color-danger: #8a0000; 76 | --ion-color-danger-rgb: 245, 61, 61; 77 | --ion-color-danger-contrast: #ffffff; 78 | --ion-color-danger-contrast-rgb: 255, 255, 255; 79 | --ion-color-danger-shade: #d33939; 80 | --ion-color-danger-tint: #f25454; 81 | 82 | --ion-color-dark: #000; 83 | --ion-color-dark-rgb: 34, 34, 34; 84 | --ion-color-dark-contrast: #ffffff; 85 | --ion-color-dark-contrast-rgb: 255, 255, 255; 86 | --ion-color-dark-shade: #1e2023; 87 | --ion-color-dark-tint: #383a3e; 88 | 89 | --ion-color-medium: #cbcbcb; 90 | --ion-color-medium-rgb: 203, 203, 203; 91 | --ion-color-medium-contrast: #000000; 92 | --ion-color-medium-contrast-rgb: 0, 0, 0; 93 | --ion-color-medium-shade: #b3b3b3; 94 | --ion-color-medium-tint: #d0d0d0; 95 | 96 | --ion-color-light: #f4f5f8; 97 | --ion-color-light-rgb: 244, 244, 244; 98 | --ion-color-light-contrast: #000000; 99 | --ion-color-light-contrast-rgb: 0, 0, 0; 100 | --ion-color-light-shade: #d7d8da; 101 | --ion-color-light-tint: #f5f6f9; 102 | } 103 | 104 | ion-spinner { 105 | display: block; 106 | margin: auto; 107 | } 108 | 109 | .pointer { 110 | cursor: pointer; 111 | } 112 | 113 | .square, 114 | .full { 115 | width: 100%; 116 | height: 100%; 117 | } 118 | 119 | .square:after { 120 | content: ''; 121 | display: block; 122 | padding-bottom: 100%; 123 | } 124 | 125 | .square ion-img { 126 | position: absolute; 127 | object-fit: cover; 128 | width: 100%; 129 | height: 100%; 130 | padding: 2px; 131 | user-select: none; 132 | -moz-user-select: none; 133 | -webkit-user-drag: none; 134 | -webkit-user-select: none; 135 | -ms-user-select: none; 136 | } 137 | 138 | .selected .square ion-img { 139 | opacity: 0.7; 140 | } 141 | 142 | .selected .square { 143 | background-color: var(--ion-color-secondary); 144 | } 145 | 146 | .floatInput { 147 | position: absolute; 148 | top: 6px; 149 | left: 6px; 150 | } 151 | 152 | .floatIcon { 153 | position: absolute; 154 | top: 0; 155 | left: 4px; 156 | } 157 | 158 | .square ion-spinner, 159 | ion-slide ion-spinner { 160 | position: absolute; 161 | left: 45%; 162 | top: 45%; 163 | object-fit: cover; 164 | margin: auto; 165 | padding: 2px; 166 | } 167 | 168 | .square .rotation-6 { 169 | transform: rotate(90deg); 170 | } 171 | 172 | .square .rotation-3 { 173 | transform: rotate(180deg); 174 | } 175 | 176 | .square .rotation-8 { 177 | transform: rotate(270deg); 178 | } 179 | 180 | .hidden { 181 | visibility: hidden; 182 | } 183 | 184 | #file-upload { 185 | display: none; 186 | } 187 | 188 | .signin, 189 | .profile { 190 | --background: linear-gradient( 191 | 135deg, 192 | rgba(60, 8, 118, 0.8) 0%, 193 | rgba(250, 0, 118, 0.8) 100% 194 | ); 195 | } 196 | 197 | .unselectable { 198 | -webkit-touch-callout: none; 199 | -webkit-user-select: none; 200 | -khtml-user-select: none; 201 | -moz-user-select: none; 202 | -ms-user-select: none; 203 | user-select: none; 204 | } 205 | 206 | .router-modal, 207 | .router-modal .modal-wrapper { 208 | width: 100%; 209 | height: 100%; 210 | border: 0 solid #000; 211 | } 212 | 213 | ion-content { 214 | --padding-end: env(safe-area-inset-right); 215 | --padding-start: env(safe-area-inset-left); 216 | --padding-bottom: env(safe-area-inset-bottom); 217 | } 218 | 219 | ion-card ion-item { 220 | --min-height: 19px; 221 | } 222 | 223 | .loadingToolbar { 224 | display: none; 225 | } 226 | 227 | .loadingToolbar.show { 228 | display: block; 229 | } 230 | -------------------------------------------------------------------------------- /src/global/app.ts: -------------------------------------------------------------------------------- 1 | import '@ionic/core'; 2 | 3 | // import { setupConfig } from '@ionic/core'; 4 | 5 | // setupConfig({ 6 | // mode: 'ios' 7 | // }); 8 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Block Photos 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/interfaces.d.ts -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "block-photos", 3 | "short_name": "block-photos", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "icons": [ 7 | { 8 | "src": "https://app.block-photos.com/assets/icon/icon.png", 9 | "sizes": "512x512", 10 | "type": "image/png" 11 | } 12 | ], 13 | "background_color": "#488aff", 14 | "theme_color": "#488aff" 15 | } 16 | -------------------------------------------------------------------------------- /src/models/photo-metadata.ts: -------------------------------------------------------------------------------- 1 | // The metadata stored for a photo 2 | interface PhotoMetadata { 3 | id: string; 4 | filename: string; 5 | stats: any; 6 | type: string; 7 | size: number; 8 | uploadedDate: Date; 9 | albums: string[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/photo-type.ts: -------------------------------------------------------------------------------- 1 | // The iamge format to be displayed or fetched 2 | export enum PhotoType { 3 | // The original high res image 4 | Download, 5 | // For fullscreen viewing in app 6 | Viewer, 7 | // For lists of photos 8 | Thumbnail 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/app-albums/app-albums.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-profile', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | await page.setContent(''); 7 | 8 | const element = await page.find('app-profile'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('displays the specified name', async () => { 13 | const page = await newE2EPage({ url: '/profile/joseph' }); 14 | 15 | const element = await page.find('app-profile ion-content p'); 16 | expect(element.textContent).toContain('My name is Joseph.'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/app-albums/app-albums.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppProfile } from './app-profile'; 2 | 3 | describe('app-profile', () => { 4 | it('builds', () => { 5 | expect(new AppProfile()).toBeTruthy(); 6 | }); 7 | 8 | describe('normalization', () => { 9 | it('returns a blank string if the name is undefined', () => { 10 | const component = new AppProfile(); 11 | expect(component.formattedName()).toEqual(''); 12 | }); 13 | 14 | it('capitalizes the first letter', () => { 15 | const component = new AppProfile(); 16 | component.name = 'quincy'; 17 | expect(component.formattedName()).toEqual('Quincy'); 18 | }); 19 | 20 | it('lower-cases the following letters', () => { 21 | const component = new AppProfile(); 22 | component.name = 'JOSEPH'; 23 | expect(component.formattedName()).toEqual('Joseph'); 24 | }); 25 | 26 | it('handles single letter names', () => { 27 | const component = new AppProfile(); 28 | component.name = 'q'; 29 | expect(component.formattedName()).toEqual('Q'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/pages/app-photo/app-photo.css: -------------------------------------------------------------------------------- 1 | app-photo ion-slides { 2 | height: 100%; 3 | } 4 | 5 | app-photo .swiper-slide img { 6 | width: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/app-photo/app-photo.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-photo', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | await page.setContent(''); 7 | 8 | const element = await page.find('app-photo'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('displays the specified name', async () => { 13 | const page = await newE2EPage({ url: '/photos/joseph' }); 14 | 15 | const element = await page.find('app-photo ion-content p'); 16 | expect(element.textContent).toContain('My name is Joseph.'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/app-photo/app-photo.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPhoto } from './app-photo'; 2 | 3 | describe('app-photo', () => { 4 | it('builds', () => { 5 | expect(new AppPhoto()).toBeTruthy(); 6 | }); 7 | 8 | describe('normalization', () => { 9 | it('returns a blank string if the name is undefined', () => { 10 | const component = new AppPhoto(); 11 | expect(component.formattedName()).toEqual(''); 12 | }); 13 | 14 | it('capitalizes the first letter', () => { 15 | const component = new AppPhoto(); 16 | component.name = 'quincy'; 17 | expect(component.formattedName()).toEqual('Quincy'); 18 | }); 19 | 20 | it('lower-cases the following letters', () => { 21 | const component = new AppPhoto(); 22 | component.name = 'JOSEPH'; 23 | expect(component.formattedName()).toEqual('Joseph'); 24 | }); 25 | 26 | it('handles single letter names', () => { 27 | const component = new AppPhoto(); 28 | component.name = 'q'; 29 | expect(component.formattedName()).toEqual('Q'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/pages/app-photos/app-photos.css: -------------------------------------------------------------------------------- 1 | .upload-grid, 2 | .upload-grid ion-row { 3 | height: 100%; 4 | max-height: 100%; 5 | } 6 | 7 | .cloud-icon { 8 | width: 88px; 9 | height: 88px; 10 | } 11 | 12 | @media only screen and (max-width: 600px) { 13 | ion-col h3 { 14 | font-size: 14px; 15 | } 16 | 17 | .cloud-icon { 18 | width: 28px; 19 | height: 28px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/app-photos/app-photos.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-photos', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | await page.setContent(''); 7 | 8 | const element = await page.find('app-photos'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('displays the specified name', async () => { 13 | const page = await newE2EPage({ url: '/photos/joseph' }); 14 | 15 | const element = await page.find('app-photos ion-content p'); 16 | expect(element.textContent).toContain('My name is Joseph.'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/app-photos/app-photos.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPhotos } from './app-photos'; 2 | 3 | describe('app-photos', () => { 4 | it('builds', () => { 5 | expect(new AppPhotos()).toBeTruthy(); 6 | }); 7 | 8 | describe('normalization', () => { 9 | it('returns a blank string if the name is undefined', () => { 10 | const component = new AppPhotos(); 11 | expect(component.formattedName()).toEqual(''); 12 | }); 13 | 14 | it('capitalizes the first letter', () => { 15 | const component = new AppPhotos(); 16 | component.name = 'quincy'; 17 | expect(component.formattedName()).toEqual('Quincy'); 18 | }); 19 | 20 | it('lower-cases the following letters', () => { 21 | const component = new AppPhotos(); 22 | component.name = 'JOSEPH'; 23 | expect(component.formattedName()).toEqual('Joseph'); 24 | }); 25 | 26 | it('handles single letter names', () => { 27 | const component = new AppPhotos(); 28 | component.name = 'q'; 29 | expect(component.formattedName()).toEqual('Q'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/pages/app-root/app-root.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerdic-coder/block-photos/a6d76f1b3d0a32cf0d6c458fad255462cb367f6e/src/pages/app-root/app-root.css -------------------------------------------------------------------------------- /src/pages/app-root/app-root.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-root', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage({ url: '/' }); 6 | 7 | const element = await page.find('app-root'); 8 | expect(element).toHaveClass('hydrated'); 9 | }); 10 | 11 | it('renders an ion-app', async () => { 12 | const page = await newE2EPage({ url: '/' }); 13 | 14 | const element = await page.find('app-root > ion-app'); 15 | expect(element).toHaveClass('hydrated'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/pages/app-root/app-root.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppRoot } from './app-root'; 2 | 3 | describe('app-root', () => { 4 | it('builds', () => { 5 | expect(new AppRoot()).toBeTruthy(); 6 | }); 7 | 8 | describe('onSWUpdate', () => { 9 | let appRoot: AppRoot; 10 | let mockToast; 11 | beforeEach(() => { 12 | appRoot = new AppRoot(); 13 | mockToast = { 14 | present: jest.fn().mockReturnValue(Promise.resolve()), 15 | onWillDismiss: jest.fn().mockReturnValue(Promise.resolve()) 16 | }; 17 | appRoot.toastCtrl = { 18 | create: jest.fn().mockReturnValue(Promise.resolve(mockToast)) 19 | } as any; 20 | window.location.reload = jest.fn(); 21 | }); 22 | 23 | it('creates a new toast', () => { 24 | appRoot.onSWUpdate(); 25 | expect(appRoot.toastCtrl.create).toHaveBeenCalledTimes(1); 26 | expect(appRoot.toastCtrl.create).toHaveBeenCalledWith({ 27 | message: 'New version available', 28 | showCloseButton: true, 29 | closeButtonText: 'Reload' 30 | }); 31 | }); 32 | 33 | it('presents the toast', async () => { 34 | await appRoot.onSWUpdate(); 35 | expect(mockToast.present).toHaveReturnedTimes(1); 36 | }); 37 | 38 | it('reloads the app', async () => { 39 | await appRoot.onSWUpdate(); 40 | expect(window.location.reload).toHaveReturnedTimes(1); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/pages/app-root/app-root.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Listen, Prop, State } from '@stencil/core'; 2 | import { Plugins } from '@capacitor/core'; 3 | import * as Sentry from '@sentry/browser'; 4 | import localForage from 'localforage'; 5 | 6 | import AnalyticsService from '../../services/analytics-service'; 7 | import CacheService from '../../services/cache-service'; 8 | import SettingsService from '../../services/settings-service'; 9 | 10 | declare var blockstack; 11 | 12 | @Component({ 13 | tag: 'app-root', 14 | styleUrl: 'app-root.css' 15 | }) 16 | export class AppRoot { 17 | @Prop({ connect: 'ion-toast-controller' }) 18 | toastCtrl: HTMLIonToastControllerElement; 19 | 20 | @State() isAuthenticated: boolean; 21 | 22 | /** 23 | * Handle service worker updates correctly. 24 | * This code will show a toast letting the 25 | * user of the PWA know that there is a 26 | * new version available. When they click the 27 | * reload button it then reloads the page 28 | * so that the new service worker can take over 29 | * and serve the fresh content 30 | */ 31 | @Listen('window:swUpdate') 32 | async onSWUpdate() { 33 | const toast = await this.toastCtrl.create({ 34 | message: 'New version available', 35 | showCloseButton: true, 36 | closeButtonText: 'Reload' 37 | }); 38 | await toast.present(); 39 | await toast.onWillDismiss(); 40 | window.location.reload(); 41 | } 42 | 43 | async componentWillLoad() { 44 | localForage.config({ 45 | name: 'BlockPhotos', 46 | version: 1.0, 47 | storeName: 'blockphotos', // Should be alphanumeric, with underscores. 48 | description: 'Block Photos Cache' 49 | }); 50 | try { 51 | if (await SettingsService.getAnalyticsSetting(true)) { 52 | Sentry.init({ 53 | dsn: 'https://2b0b525209b646f49e438cff86c3e117@sentry.io/1331915', 54 | release: 'block-photos@4.2' 55 | }); 56 | } 57 | } catch (error) { 58 | Sentry.init({ 59 | dsn: 'https://2b0b525209b646f49e438cff86c3e117@sentry.io/1331915', 60 | release: 'block-photos@4.2' 61 | }); 62 | } 63 | 64 | this.initCapacitor(); 65 | 66 | const userSession = new blockstack.UserSession(); 67 | this.isAuthenticated = userSession.isUserSignedIn(); 68 | } 69 | 70 | async componentDidLoad() { 71 | const router: any = document.querySelector('ion-router'); 72 | await router.componentOnReady(); 73 | router.addEventListener('ionRouteDidChange', () => { 74 | const userSession = new blockstack.UserSession(); 75 | this.isAuthenticated = userSession.isUserSignedIn(); 76 | }); 77 | } 78 | 79 | async initCapacitor() { 80 | const { Device } = Plugins; 81 | 82 | const device = await Device.getInfo(); 83 | if (device.platform !== 'web') { 84 | const { App, StatusBar } = Plugins; 85 | StatusBar.setBackgroundColor({ color: '#220631' }); 86 | 87 | App.addListener('appUrlOpen', data => { 88 | if (data.url) { 89 | const authResponse = data.url.split(':')[1]; 90 | if (authResponse) { 91 | window.location.href = 92 | window.location.href + '?authResponse=' + authResponse; 93 | } 94 | } 95 | }); 96 | } 97 | } 98 | 99 | async handleSignOut() { 100 | // Clear all the users cache in localStorage 101 | CacheService.clear(); 102 | // End users Blockstack session 103 | const userSession = new blockstack.UserSession(); 104 | userSession.signUserOut(); 105 | 106 | AnalyticsService.logEvent('logged-out'); 107 | } 108 | 109 | render() { 110 | return [ 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Menu 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Photos 133 | 134 | 135 | 136 | 137 | 138 | Albums 139 | 140 | 141 | 142 | 143 | 144 | Settings 145 | 146 | 147 | 148 | this.handleSignOut()}> 149 | 150 | Logout 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | ]; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/pages/app-settings/app-settings.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-profile', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | await page.setContent(''); 7 | 8 | const element = await page.find('app-profile'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('displays the specified name', async () => { 13 | const page = await newE2EPage({ url: '/profile/joseph' }); 14 | 15 | const element = await page.find('app-profile ion-content p'); 16 | expect(element.textContent).toContain('My name is Joseph.'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/app-settings/app-settings.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppProfile } from './app-profile'; 2 | 3 | describe('app-profile', () => { 4 | it('builds', () => { 5 | expect(new AppProfile()).toBeTruthy(); 6 | }); 7 | 8 | describe('normalization', () => { 9 | it('returns a blank string if the name is undefined', () => { 10 | const component = new AppProfile(); 11 | expect(component.formattedName()).toEqual(''); 12 | }); 13 | 14 | it('capitalizes the first letter', () => { 15 | const component = new AppProfile(); 16 | component.name = 'quincy'; 17 | expect(component.formattedName()).toEqual('Quincy'); 18 | }); 19 | 20 | it('lower-cases the following letters', () => { 21 | const component = new AppProfile(); 22 | component.name = 'JOSEPH'; 23 | expect(component.formattedName()).toEqual('Joseph'); 24 | }); 25 | 26 | it('handles single letter names', () => { 27 | const component = new AppProfile(); 28 | component.name = 'q'; 29 | expect(component.formattedName()).toEqual('Q'); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/pages/app-settings/app-settings.tsx: -------------------------------------------------------------------------------- 1 | import { Component, State } from '@stencil/core'; 2 | import PresentingService from '../../services/presenting-service'; 3 | import AnalyticsService from '../../services/analytics-service'; 4 | import SettingsService from '../../services/settings-service'; 5 | import CacheService from '../../services/cache-service'; 6 | 7 | @Component({ 8 | tag: 'app-settings' 9 | }) 10 | export class AppSettings { 11 | private present: PresentingService; 12 | 13 | @State() allowAnalytics: boolean; 14 | 15 | constructor() { 16 | this.present = new PresentingService(); 17 | } 18 | 19 | async componentWillLoad() { 20 | this.allowAnalytics = await SettingsService.getAnalyticsSetting(); 21 | AnalyticsService.logEvent('settings-page'); 22 | } 23 | 24 | visitBlockstackProfile(event: any): void { 25 | if (event) { 26 | event.preventDefault(); 27 | } 28 | 29 | this.present.openLink('https://browser.blockstack.org/profiles', '_blank'); 30 | 31 | AnalyticsService.logEvent('blockstack-profile-link'); 32 | } 33 | 34 | reportIssue(event: any): void { 35 | if (event) { 36 | event.preventDefault(); 37 | } 38 | 39 | this.present.openLink( 40 | 'https://github.com/nerdic-coder/block-photos/issues/new', 41 | '_blank' 42 | ); 43 | 44 | AnalyticsService.logEvent('report-issue-link'); 45 | } 46 | 47 | donateCrypto(event: any): void { 48 | if (event) { 49 | event.preventDefault(); 50 | } 51 | 52 | this.present.openLink( 53 | 'https://commerce.coinbase.com/checkout/9d35f08b-bd51-40b0-a502-b88250cffc6b', 54 | '_blank' 55 | ); 56 | 57 | AnalyticsService.logEvent('donate-crypto-link'); 58 | } 59 | 60 | upvoteProductHunt(event: any): void { 61 | if (event) { 62 | event.preventDefault(); 63 | } 64 | 65 | this.present.openLink( 66 | 'https://www.producthunt.com/posts/block-photos', 67 | '_blank' 68 | ); 69 | 70 | AnalyticsService.logEvent('product-hunt-link'); 71 | } 72 | 73 | twitter(event: any): void { 74 | if (event) { 75 | event.preventDefault(); 76 | } 77 | 78 | this.present.openLink('https://twitter.com/Block_Photos', '_blank'); 79 | 80 | AnalyticsService.logEvent('twitter-link'); 81 | } 82 | 83 | github(event: any): void { 84 | if (event) { 85 | event.preventDefault(); 86 | } 87 | 88 | this.present.openLink( 89 | 'https://github.com/nerdic-coder/block-photos', 90 | '_blank' 91 | ); 92 | 93 | AnalyticsService.logEvent('github-link'); 94 | } 95 | 96 | sendEmail(event: any): void { 97 | if (event) { 98 | event.preventDefault(); 99 | } 100 | this.present.openLink( 101 | 'mailto:johan@block-photos.com?subject=Block Photos Feedback' 102 | ); 103 | 104 | AnalyticsService.logEvent('send-email-link'); 105 | } 106 | 107 | changeAnalyticsSetting() { 108 | SettingsService.setAnalyticsSetting(!this.allowAnalytics); 109 | } 110 | 111 | async clearCache(event) { 112 | if (event) { 113 | event.preventDefault(); 114 | } 115 | const actionSheetController = document.querySelector( 116 | 'ion-action-sheet-controller' 117 | ); 118 | await actionSheetController.componentOnReady(); 119 | 120 | const buttons = [ 121 | { 122 | text: 'Clear cache', 123 | role: 'destructive', 124 | icon: 'reverse-camera', 125 | handler: () => { 126 | CacheService.clear(); 127 | } 128 | } 129 | ]; 130 | 131 | buttons.push({ 132 | text: 'Cancel', 133 | icon: 'close', 134 | role: 'cancel', 135 | handler: null 136 | }); 137 | 138 | const actionSheet = await actionSheetController.create({ 139 | header: 'Are you sure you want to clear the cache?', 140 | buttons 141 | }); 142 | await actionSheet.present(); 143 | } 144 | 145 | render() { 146 | return [ 147 | 148 | 149 | Settings 150 | 151 | 152 | 153 | Menu 154 | 155 | 156 | 157 | 158 | , 159 | 160 | 161 | 162 | 163 | Share Analytics Data 164 | this.changeAnalyticsSetting()} 169 | /> 170 | 171 | 172 |

173 | Help Block Photos improve the app by automatically share daily 174 | diagnostic and usage data. None of your photos or personal 175 | information will be tracked. 176 |

177 |
178 |
179 | 180 | this.clearCache(event)} detail={false}> 181 | 182 | Clear photos cache 183 | 184 | 185 |

186 | Photos starting to take up too much space? Empty the photos cache 187 | here to clear up some space on your device. 188 |

189 |
190 |
191 | 192 | 193 | this.visitBlockstackProfile(event)} 197 | > 198 | 199 | Go to profile on Blockstack 200 | 201 | 202 | this.sendEmail(event)}> 203 | 204 | Email johan@block-photos.com 205 | 206 | 207 | this.reportIssue(event)}> 208 | 209 | Report issue 210 | 211 | 212 | 213 | this.donateCrypto(event)}> 214 | 215 | Donate with Crypto 216 | 217 | this.upvoteProductHunt(event)} 221 | > 222 | 223 | Upvote on Product Hunt 224 | 225 | this.twitter(event)}> 226 | 227 | Follow us on Twitter 228 | 229 | this.github(event)}> 230 | 231 | Read our source code 232 | 233 | 234 |

Block Photos - Version 4.2

235 |
236 | ]; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/pages/app-signin/app-signin.css: -------------------------------------------------------------------------------- 1 | .spinner-grid, 2 | .spinner-grid ion-row { 3 | height: 100%; 4 | max-height: 100%; 5 | } 6 | 7 | .signin ion-col { 8 | max-width: 450px; 9 | } 10 | 11 | .signin h1 { 12 | color: var(--light, #ffffff); 13 | } 14 | 15 | .signin ion-img { 16 | width: 70%; 17 | margin-left: auto; 18 | margin-right: auto; 19 | } 20 | 21 | .sc-ion-card-ios-h { 22 | margin: 16px 20px; 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/app-signin/app-signin.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('app-signin', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | await page.setContent(''); 7 | 8 | const element = await page.find('app-signin'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('contains a "Profile Page" button', async () => { 13 | const page = await newE2EPage(); 14 | await page.setContent(''); 15 | 16 | const element = await page.find('app-signin ion-content ion-button'); 17 | expect(element.textContent).toEqual('Profile page'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/app-signin/app-signin.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppSignin } from './app-signin'; 2 | 3 | describe('app', () => { 4 | it('builds', () => { 5 | expect(new AppSignin()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Block Photos 5 | 15 | 16 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/services/albums-service.ts: -------------------------------------------------------------------------------- 1 | import uuidv4 from 'uuid/v4'; 2 | 3 | import StorageService from './storage-service'; 4 | 5 | export default class AlbumsService { 6 | static async getAlbums(updateCache?: boolean): Promise { 7 | let cachedAlbums = []; 8 | const errorsList = []; 9 | try { 10 | const rawCachedAlbums = await StorageService.getItem( 11 | 'albums-list.json', 12 | updateCache, 13 | updateCache 14 | ); 15 | if (rawCachedAlbums) { 16 | cachedAlbums = JSON.parse(rawCachedAlbums); 17 | } else { 18 | errorsList.push('err_list'); 19 | } 20 | } catch (error) { 21 | errorsList.push('err_list'); 22 | } 23 | 24 | return { 25 | albums: cachedAlbums, 26 | errorsList 27 | }; 28 | } 29 | 30 | static async createAlbum(albumName: string) { 31 | const albumsResponse = await AlbumsService.getAlbums(true); 32 | let albums = albumsResponse.albums; 33 | if ((!albums || albums == null) && albumsResponse.errorsList.length === 0) { 34 | albums = []; 35 | } 36 | 37 | const errorsList = []; 38 | const albumId = uuidv4(); 39 | const metadata = { 40 | albumId, 41 | albumName, 42 | createdDate: new Date(), 43 | thumbnailId: null 44 | }; 45 | try { 46 | await StorageService.setItem(albumId, '[]'); 47 | 48 | albums.unshift(metadata); 49 | } catch (error) { 50 | errorsList.push({ 51 | id: albumId, 52 | errorCode: 'err_failed' 53 | }); 54 | } 55 | 56 | await StorageService.setItem('albums-list.json', JSON.stringify(albums)); 57 | return { albums, errorsList }; 58 | } 59 | 60 | static async updateAlbumName( 61 | albumId: string, 62 | albumName: string 63 | ): Promise { 64 | // id and metadata is required 65 | if (!albumId || !albumName) { 66 | return false; 67 | } 68 | const albumsResponse = await AlbumsService.getAlbums(true); 69 | const albums = albumsResponse.albums; 70 | let albumFound = false; 71 | let index = 0; 72 | for (const album of albums) { 73 | // Current album 74 | if (album.albumId === albumId) { 75 | albums[index].albumName = albumName; 76 | albumFound = true; 77 | break; 78 | } 79 | index++; 80 | } 81 | 82 | // Don't update if album don't exist 83 | if (!albumFound) { 84 | return false; 85 | } 86 | 87 | await StorageService.setItem('albums-list.json', JSON.stringify(albums)); 88 | 89 | return albums; 90 | } 91 | 92 | static async updateAlbumThumbnail( 93 | albumId: string, 94 | thumbnailId: string 95 | ): Promise { 96 | // id and metadata is required 97 | if (!albumId || !thumbnailId) { 98 | return false; 99 | } 100 | const albumsResponse = await AlbumsService.getAlbums(true); 101 | const albums = albumsResponse.albums; 102 | let albumFound = false; 103 | let index = 0; 104 | for (const album of albums) { 105 | // Current album 106 | if (album.albumId === albumId) { 107 | albums[index].thumbnailId = thumbnailId; 108 | albumFound = true; 109 | break; 110 | } 111 | index++; 112 | } 113 | 114 | // Don't update if album don't exist 115 | if (!albumFound) { 116 | return false; 117 | } 118 | 119 | await StorageService.setItem('albums-list.json', JSON.stringify(albums)); 120 | 121 | return albums; 122 | } 123 | 124 | static async deleteAlbum(albumId: string): Promise { 125 | let returnState = false; 126 | try { 127 | StorageService.deleteItem(albumId); 128 | returnState = true; 129 | } catch (error) { 130 | returnState = false; 131 | } 132 | 133 | if (!returnState) { 134 | return false; 135 | } 136 | 137 | const albumResponse = await AlbumsService.getAlbums(true); 138 | const albums = albumResponse.albums; 139 | 140 | let index = 0; 141 | for (const photo of albums) { 142 | if (albumId === photo.albumId) { 143 | albums.splice(index, 1); 144 | await StorageService.setItem( 145 | 'albums-list.json', 146 | JSON.stringify(albums) 147 | ); 148 | return albums; 149 | } 150 | index++; 151 | } 152 | return false; 153 | } 154 | 155 | static async getAlbumMetaData(albumId: string): Promise { 156 | let response = {}; 157 | const albumsResponse = await AlbumsService.getAlbums(); 158 | const albums = albumsResponse.albums; 159 | 160 | let index = 0; 161 | for (const album of albums) { 162 | // Current album 163 | if (album.albumId === albumId) { 164 | response = albums[index]; 165 | break; 166 | } 167 | index++; 168 | } 169 | 170 | return response; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/services/analytics-service.ts: -------------------------------------------------------------------------------- 1 | import SettingsService from './settings-service'; 2 | 3 | declare var amplitude; 4 | 5 | export default class AnalyticsService { 6 | static async logEvent(event: string): Promise { 7 | if (amplitude && (await SettingsService.getAnalyticsSetting())) { 8 | amplitude.getInstance().logEvent(event); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/services/cache-service.spec.ts: -------------------------------------------------------------------------------- 1 | import CacheService from './cache-service'; 2 | import localForage from 'localforage'; 3 | 4 | jest.mock('localforage'); 5 | 6 | describe('CacheService Test Suites', () => { 7 | it('initialize without crashing', () => { 8 | new CacheService(); 9 | }); 10 | 11 | it('get photos list', async () => { 12 | const mockResponse = [ 13 | { 14 | id: 'test123.jpg', 15 | uploadedDate: '2018-07-11T21:58:39.754Z' 16 | } 17 | ]; 18 | 19 | localForage.getItem.mockReturnValue( 20 | Promise.resolve(JSON.stringify(mockResponse)) 21 | ); 22 | 23 | const cacheService = new CacheService(); 24 | const response = await cacheService.getItem('picture-list.json'); 25 | 26 | const jsonResponse = JSON.parse(response); 27 | expect(Array.isArray(jsonResponse)).toBe(true); 28 | expect(jsonResponse).toEqual(mockResponse); 29 | }); 30 | 31 | it('get empty photos list', async () => { 32 | const mockResponse = []; 33 | 34 | localForage.getItem.mockReturnValue( 35 | Promise.resolve(JSON.stringify(mockResponse)) 36 | ); 37 | 38 | const cacheService = new CacheService(); 39 | const response = await cacheService.getItem('picture-list.json'); 40 | 41 | const jsonResponse = JSON.parse(response); 42 | expect(Array.isArray(jsonResponse)).toBe(true); 43 | expect(jsonResponse).toEqual(mockResponse); 44 | }); 45 | 46 | it('get null photos list', async () => { 47 | const mockResponse = null; 48 | 49 | localForage.getItem.mockReturnValue( 50 | Promise.resolve(JSON.stringify(mockResponse)) 51 | ); 52 | 53 | const cacheService = new CacheService(); 54 | const response = await cacheService.getItem('picture-list.json'); 55 | 56 | const jsonResponse = JSON.parse(response); 57 | expect(Array.isArray(jsonResponse)).toBe(false); 58 | expect(jsonResponse).toBe(null); 59 | }); 60 | 61 | it('cache photo', async () => { 62 | const cacheService = new CacheService(); 63 | await cacheService.setItem('test1.png', [ 64 | { 65 | filename: 'test1.png', 66 | data: 'trash' 67 | } 68 | ]); 69 | 70 | // const response = await cacheService.getItem('test1.png'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/services/cache-service.ts: -------------------------------------------------------------------------------- 1 | import localForage from 'localforage'; 2 | 3 | export default class CacheService { 4 | static async getItem(itemId: string): Promise { 5 | return localForage.getItem(itemId); 6 | } 7 | 8 | static async setItem(itemId: string, itemValue: any): Promise { 9 | return localForage.setItem(itemId, itemValue); 10 | } 11 | 12 | static async deleteItem(itemId: string): Promise { 13 | return localForage.removeItem(itemId); 14 | } 15 | 16 | static async clear(): Promise { 17 | return localForage.clear(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/services/presenting-service.ts: -------------------------------------------------------------------------------- 1 | import PhotosService from './photos-service'; 2 | import isElectron from 'is-electron'; 3 | 4 | export default class PresentingService { 5 | private loadingElement: HTMLIonLoadingElement; 6 | 7 | async loading(message: string, duration?: number): Promise { 8 | const loadingController = document.querySelector('ion-loading-controller'); 9 | await loadingController.componentOnReady(); 10 | 11 | this.loadingElement = await loadingController.create({ 12 | message, 13 | spinner: 'circles', 14 | duration 15 | }); 16 | return this.loadingElement.present(); 17 | } 18 | 19 | async dismissLoading(): Promise { 20 | if (this.loadingElement) { 21 | await this.loadingElement.dismiss(); 22 | } 23 | } 24 | 25 | async presentToolbarLoader(message: string): Promise { 26 | const loadingToolbar = document.querySelector('.loadingToolbar'); 27 | 28 | if (loadingToolbar) { 29 | const loadingTitle = loadingToolbar.querySelector('ion-title'); 30 | await loadingTitle.componentOnReady(); 31 | loadingTitle.innerText = message; 32 | loadingToolbar.classList.add('show'); 33 | } 34 | } 35 | 36 | toolbarLoaderIsPresent(): boolean { 37 | const loadingToolbar = document.querySelector('.loadingToolbar'); 38 | 39 | if (loadingToolbar) { 40 | return loadingToolbar.classList.contains('show'); 41 | } else { 42 | return false; 43 | } 44 | } 45 | 46 | async dismissToolbarLoader(): Promise { 47 | const loadingToolbar = document.querySelector('.loadingToolbar'); 48 | 49 | if (loadingToolbar) { 50 | const loadingTitle = loadingToolbar.querySelector('ion-title'); 51 | await loadingTitle.componentOnReady(); 52 | loadingTitle.innerText = ''; 53 | loadingToolbar.classList.remove('show'); 54 | } 55 | } 56 | 57 | async toast(message: string): Promise { 58 | const toastController = document.querySelector('ion-toast-controller'); 59 | await toastController.componentOnReady(); 60 | 61 | const toast = await toastController.create({ 62 | message, 63 | showCloseButton: true, 64 | color: 'primary' 65 | }); 66 | return toast.present(); 67 | } 68 | 69 | async deletePhotos( 70 | ids: string[], 71 | endCallback: any, 72 | albumId?: string, 73 | startCallback?: any 74 | ): Promise { 75 | if (!ids || ids.length < 1) { 76 | return; 77 | } 78 | 79 | let header = 'Delete ' + ids.length + ' photos?'; 80 | if (ids.length === 1) { 81 | header = 'Delete ' + ids.length + ' photo?'; 82 | } 83 | const actionSheetController = document.querySelector( 84 | 'ion-action-sheet-controller' 85 | ); 86 | await actionSheetController.componentOnReady(); 87 | 88 | const buttons = [ 89 | { 90 | text: 'Delete from app', 91 | role: 'destructive', 92 | icon: 'trash', 93 | handler: () => { 94 | if (startCallback) { 95 | startCallback(); 96 | } 97 | 98 | PhotosService.deletePhotos(ids).then(async result => { 99 | if (result === true) { 100 | endCallback(); 101 | } else { 102 | this.errorAlert( 103 | 'Removal failed', 104 | 'The removal of some photos failed. Please try again in a few minutes!' 105 | ); 106 | endCallback(); 107 | } 108 | }); 109 | } 110 | } 111 | ]; 112 | 113 | if (albumId) { 114 | buttons.push({ 115 | text: 'Remove from album', 116 | role: 'destructive', 117 | icon: 'remove-circle', 118 | handler: () => { 119 | if (startCallback) { 120 | startCallback(); 121 | } 122 | PhotosService.removePhotosFromList(ids, albumId).then( 123 | async result => { 124 | if (result === true) { 125 | endCallback(); 126 | } else { 127 | this.errorAlert( 128 | 'Removal failed', 129 | 'The removal of some photos failed. Please try again in a few minutes!' 130 | ); 131 | endCallback(); 132 | } 133 | } 134 | ); 135 | } 136 | }); 137 | } 138 | 139 | buttons.push({ 140 | text: 'Cancel', 141 | icon: 'close', 142 | role: 'cancel', 143 | handler: null 144 | }); 145 | 146 | const actionSheet = await actionSheetController.create({ 147 | header, 148 | buttons 149 | }); 150 | await actionSheet.present(); 151 | } 152 | 153 | async errorAlert(header: string, message: string): Promise { 154 | const alertController = document.querySelector('ion-alert-controller'); 155 | await alertController.componentOnReady(); 156 | 157 | const alert = await alertController.create({ 158 | header, 159 | subHeader: '', 160 | message, 161 | buttons: ['OK'] 162 | }); 163 | return alert.present(); 164 | } 165 | 166 | openLink(url: string, target?: string): void { 167 | if (isElectron()) { 168 | const electron = window['require']('electron'); 169 | electron.shell.openExternal(url); 170 | } else { 171 | window.open(url, target); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/services/settings-service.ts: -------------------------------------------------------------------------------- 1 | import StorageService from './storage-service'; 2 | 3 | export default class SettingsService { 4 | static async getAnalyticsSetting(updateCache?: boolean): Promise { 5 | return ( 6 | (await StorageService.getItem('analytics-settings', updateCache)) === 7 | 'true' 8 | ); 9 | } 10 | 11 | static async setAnalyticsSetting(allowAnalytics: boolean): Promise { 12 | return StorageService.setItem( 13 | 'analytics-settings', 14 | allowAnalytics.toString() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/services/storage-service.ts: -------------------------------------------------------------------------------- 1 | import CacheService from './cache-service'; 2 | 3 | declare var blockstack; 4 | 5 | export default class StorageService { 6 | static async getItem( 7 | itemId: string, 8 | updateCache?: boolean, 9 | forceUpdateCache?: boolean 10 | ) { 11 | let item = await CacheService.getItem(itemId); 12 | if (!item || forceUpdateCache) { 13 | const userSession = new blockstack.UserSession(); 14 | item = await userSession.getFile(itemId); 15 | if (updateCache || forceUpdateCache) { 16 | CacheService.setItem(itemId, item); 17 | } 18 | } 19 | 20 | return item; 21 | } 22 | 23 | static async setItem( 24 | itemId: string, 25 | itemValue: any, 26 | cacheItem = true 27 | ): Promise { 28 | const userSession = new blockstack.UserSession(); 29 | await userSession.putFile(itemId, itemValue); 30 | if (cacheItem) { 31 | await CacheService.setItem(itemId, itemValue); 32 | } 33 | 34 | const timeStamp = Math.floor(Date.now() / 1000); 35 | userSession.putFile('block-photos-last-updated', timeStamp.toString()); 36 | CacheService.setItem('block-photos-last-checked', timeStamp.toString()); 37 | } 38 | 39 | static async deleteItem(itemId: string) { 40 | const userSession = new blockstack.UserSession(); 41 | await userSession.deleteFile(itemId); 42 | await CacheService.deleteItem(itemId); 43 | } 44 | 45 | static clear() { 46 | CacheService.clear(); 47 | } 48 | 49 | static async checkUpdatedTimestamp(): Promise { 50 | const checkedTimestamp = await CacheService.getItem( 51 | 'block-photos-last-checked' 52 | ); 53 | const timeStamp = Math.floor(Date.now() / 1000); 54 | if (!checkedTimestamp) { 55 | await CacheService.setItem( 56 | 'block-photos-last-checked', 57 | timeStamp.toString() 58 | ); 59 | return false; 60 | } else { 61 | const userSession = new blockstack.UserSession(); 62 | const updatedTimeStamp = await userSession.getFile( 63 | 'block-photos-last-updated' 64 | ); 65 | if (updatedTimeStamp > checkedTimestamp) { 66 | await CacheService.setItem( 67 | 'block-photos-last-checked', 68 | timeStamp.toString() 69 | ); 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/services/upload-service.ts: -------------------------------------------------------------------------------- 1 | import loadImage from 'blueimp-load-image'; 2 | import uuidv4 from 'uuid/v4'; 3 | import PhotosService from './photos-service'; 4 | import PresentingService from './presenting-service'; 5 | import { PhotoType } from '../models/photo-type'; 6 | import { Plugins } from '@capacitor/core'; 7 | 8 | export default class UploadService { 9 | private root: any; 10 | private present: PresentingService; 11 | private callback: any; 12 | private startedCallback: any; 13 | private dropEventBinding: any; 14 | private handleFileSelectEventBinding: any; 15 | private albumId: string; 16 | 17 | constructor(callback: any, albumId?: string, startedCallback?: any) { 18 | this.present = new PresentingService(); 19 | this.callback = callback; 20 | this.startedCallback = startedCallback; 21 | this.dropEventBinding = this.dropEvent.bind(this); 22 | this.handleFileSelectEventBinding = this.handleFileSelectEvent.bind(this); 23 | this.root = document.getElementById('photos-list'); 24 | this.albumId = albumId; 25 | } 26 | 27 | addEventListeners(fileDialog: boolean): void { 28 | this.root = document.getElementById('photos-list'); 29 | if (this.root) { 30 | this.root.addEventListener('dragover', this.dragoverEvent); 31 | this.root.addEventListener('drop', this.dropEventBinding); 32 | } 33 | if (fileDialog && document.getElementById('file-upload')) { 34 | document 35 | .getElementById('file-upload') 36 | .addEventListener('change', this.handleFileSelectEventBinding, false); 37 | } 38 | } 39 | 40 | removeEventListeners(fileDialog: boolean): void { 41 | if (this.root) { 42 | this.root.removeEventListener('dragover', this.dragoverEvent); 43 | this.root.removeEventListener('drop', this.dropEventBinding); 44 | } 45 | if (fileDialog && document.getElementById('file-upload')) { 46 | document 47 | .getElementById('file-upload') 48 | .removeEventListener('change', this.handleFileSelectEventBinding); 49 | } 50 | } 51 | 52 | dragoverEvent(event: any): void { 53 | event.stopPropagation(); 54 | event.preventDefault(); 55 | event.dataTransfer.dropEffect = 'copy'; 56 | } 57 | 58 | dropEvent(event: any): void { 59 | event.stopPropagation(); 60 | event.preventDefault(); 61 | 62 | if (event.dataTransfer.items) { 63 | const photosToUpload = []; 64 | for (let i = 0; i < event.dataTransfer.items.length; i++) { 65 | const item = { 66 | kind: event.dataTransfer.items[i].kind, 67 | file: event.dataTransfer.items[i].getAsFile() 68 | }; 69 | photosToUpload.push(item); 70 | } 71 | this.processUpload(photosToUpload, 0); 72 | 73 | event.dataTransfer.items.clear(); 74 | } 75 | } 76 | 77 | handleFileSelectEvent(event: any): void { 78 | event.stopPropagation(); 79 | event.preventDefault(); 80 | 81 | const files = event.target.files; 82 | 83 | if (files) { 84 | const photosToUpload = []; 85 | for (let i = 0; i < files.length; i++) { 86 | const item = { 87 | kind: 'file', 88 | file: files[i] 89 | }; 90 | photosToUpload.push(item); 91 | } 92 | this.processUpload(photosToUpload, 0); 93 | } 94 | } 95 | 96 | async processUpload(list: any, currentIndex: number): Promise { 97 | if (currentIndex === 0) { 98 | if (this.startedCallback && typeof this.startedCallback === 'function') { 99 | // execute the callback, passing parameters as necessary 100 | this.startedCallback(); 101 | } 102 | } 103 | this.present.presentToolbarLoader( 104 | 'Uploading photo ' + (currentIndex + 1) + ' of ' + list.length + '.' 105 | ); 106 | // If dropped items aren't files, reject them 107 | if (list[currentIndex]) { 108 | const tempFile = list[currentIndex].file; 109 | if (list[currentIndex].kind === 'file') { 110 | if (tempFile.type.indexOf('image') !== -1) { 111 | const { Device } = Plugins; 112 | const info = await Device.getInfo(); 113 | let thumbnailData = null; 114 | let viewerData = null; 115 | thumbnailData = await PhotosService.compressPhoto( 116 | tempFile, 117 | PhotoType.Thumbnail, 118 | tempFile.type 119 | ); 120 | if (info.model !== 'iPhone' && info.model !== 'iPad') { 121 | viewerData = await PhotosService.compressPhoto( 122 | tempFile, 123 | PhotoType.Viewer, 124 | tempFile.type 125 | ); 126 | } 127 | loadImage.parseMetaData( 128 | tempFile, 129 | data => { 130 | const reader = new FileReader(); 131 | 132 | let orientation = 1; 133 | if (data && data.exif) { 134 | orientation = data.exif.get('Orientation'); 135 | } 136 | 137 | // Closure to capture the file information. 138 | reader.onload = ((loadedFile, loadedList, loadedOrientation) => { 139 | return async event => { 140 | const originalData = event.target.result; 141 | if (loadedOrientation) { 142 | loadedFile.exifdata = { 143 | tags: { 144 | Orientation: loadedOrientation, 145 | OriginalOrientation: loadedOrientation 146 | } 147 | }; 148 | } 149 | const photoId: string = 150 | uuidv4() + 151 | loadedFile.name.replace('.', '').replace(' ', ''); 152 | const metadata: PhotoMetadata = { 153 | id: photoId, 154 | filename: loadedFile.name, 155 | stats: loadedFile, 156 | type: tempFile.type, 157 | size: tempFile.size, 158 | uploadedDate: new Date(), 159 | albums: [this.albumId] 160 | }; 161 | 162 | await this.uploadPhoto( 163 | metadata, 164 | originalData, 165 | thumbnailData, 166 | viewerData 167 | ); 168 | if (loadedList[currentIndex + 1]) { 169 | this.processUpload(loadedList, currentIndex + 1); 170 | } else { 171 | this.uploadFilesDone(); 172 | } 173 | }; 174 | })(tempFile, list, orientation); 175 | // Read in the image file as a data URL. 176 | reader.readAsDataURL(tempFile); 177 | }, 178 | { 179 | maxMetaDataSize: 262144, 180 | disableImageHead: false 181 | } 182 | ); 183 | } else { 184 | this.present.toast( 185 | 'The file "' + 186 | tempFile.name + 187 | '" could not be uploaded, are you sure it\'s a photo?' 188 | ); 189 | if (list[currentIndex + 1]) { 190 | this.processUpload(list, currentIndex + 1); 191 | } else { 192 | this.uploadFilesDone(); 193 | } 194 | } 195 | } else { 196 | if (tempFile && tempFile.name) { 197 | this.present.toast( 198 | 'The file "' + 199 | tempFile.name + 200 | '" could not be uploaded, are you sure it\'s a photo?' 201 | ); 202 | } else { 203 | this.present.toast( 204 | "One of the files could not be uploaded, are you sure it's a photo?" 205 | ); 206 | } 207 | if (list[currentIndex + 1]) { 208 | this.processUpload(list, currentIndex + 1); 209 | } else { 210 | this.uploadFilesDone(); 211 | } 212 | } 213 | } else { 214 | this.present.toast( 215 | "The file could not be uploaded, are you sure it's a photo?" 216 | ); 217 | if (list[currentIndex + 1]) { 218 | this.processUpload(list, currentIndex + 1); 219 | } else { 220 | this.uploadFilesDone(); 221 | } 222 | } 223 | } 224 | 225 | async uploadPhoto( 226 | metadata: PhotoMetadata, 227 | data: any, 228 | thumbnailData?: any, 229 | viewerData?: any 230 | ): Promise { 231 | if (metadata && data) { 232 | const response = await PhotosService.uploadPhoto( 233 | metadata, 234 | data, 235 | this.albumId, 236 | thumbnailData, 237 | viewerData 238 | ); 239 | if (response.errorsList && response.errorsList.length > 0) { 240 | for (const error of response.errorsList) { 241 | if (error.errorCode === 'err_filesize') { 242 | this.present.toast( 243 | 'Failed to upload "' + 244 | error.id + 245 | '", photo exceeds file size limit of 5MB.' 246 | ); 247 | } else { 248 | this.present.toast('Failed to upload "' + error.id + '".'); 249 | } 250 | } 251 | } 252 | } else { 253 | this.present.toast('Failed to upload "' + metadata.filename + '".'); 254 | } 255 | } 256 | 257 | uploadFilesDone(): void { 258 | this.present.dismissToolbarLoader(); 259 | if (this.callback && typeof this.callback === 'function') { 260 | // execute the callback, passing parameters as necessary 261 | this.callback(); 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Block Photos", 3 | "short_name": "Block Photos", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-36x36.png", 7 | "sizes": "36x36", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/android-chrome-72x72.png", 17 | "sizes": "72x72", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/android-chrome-96x96.png", 22 | "sizes": "96x96", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "/android-chrome-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "/android-chrome-192x192.png", 32 | "sizes": "192x192", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "/android-chrome-256x256.png", 37 | "sizes": "256x256", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "/android-chrome-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "/android-chrome-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ], 51 | "theme_color": "#220631", 52 | "background_color": "#ffffff", 53 | "start_url": "https://app.block-photos.com/", 54 | "display": "standalone" 55 | } 56 | -------------------------------------------------------------------------------- /src/sw.js: -------------------------------------------------------------------------------- 1 | importScripts('workbox-v3.4.1/workbox-sw.js'); 2 | 3 | /* 4 | This is our code to handle push events. 5 | */ 6 | self.addEventListener('push', event => { 7 | console.log('[Service Worker] Push Received.'); 8 | console.log(`[Service Worker] Push had this data: "${event.data.text()}"`); 9 | 10 | const title = 'Push Notification'; 11 | const options = { 12 | body: `${event.data.text()}`, 13 | icon: 'images/icon.png', 14 | badge: 'images/badge.png' 15 | }; 16 | 17 | event.waitUntil(self.registration.showNotification(title, options)); 18 | }); 19 | 20 | self.workbox.precaching.precacheAndRoute([]); 21 | -------------------------------------------------------------------------------- /stencil.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@stencil/core'; 2 | import builtins from 'rollup-plugin-node-builtins'; 3 | import globals from 'rollup-plugin-node-globals'; 4 | // https://stenciljs.com/docs/config 5 | 6 | export const config: Config = { 7 | outputTargets: [ 8 | { 9 | type: 'www', 10 | serviceWorker: { 11 | swSrc: 'src/sw.js' 12 | } 13 | } 14 | ], 15 | copy: [ 16 | { src: '_headers' }, 17 | { src: '_redirects' }, 18 | { src: 'browserconfig.xml' }, 19 | { src: 'redirect.html' }, 20 | { src: 'site.webmanifest' } 21 | ], 22 | globalScript: 'src/global/app.ts', 23 | globalStyle: 'src/global/app.css', 24 | nodeResolve: { 25 | browser: true, 26 | preferBuiltins: false 27 | }, 28 | plugins: [builtins(), globals()] 29 | }; 30 | -------------------------------------------------------------------------------- /tests/Signin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'Signin' : function (browser) { 3 | browser 4 | .url('http://localhost:9876') 5 | .waitForElementVisible('body', 1000) 6 | // .setValue('input[type=text]', 'nightwatch') 7 | // .waitForElementVisible('button[name=btnG]', 1000) 8 | // .click('button[name=btnG]') 9 | .assert.containsText('ion-title', 'Block Photos') 10 | .assert.containsText('h1', 'Welcome to Block Photos!') 11 | .assert.containsText('ion-button', 'SIGN IN WITH BLOCKSTACK') 12 | .click('ion-button') 13 | // .assert.containsText('#main', 'Night Watch') 14 | .end(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowUnreachableCode": false, 5 | "declaration": false, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2017" 10 | ], 11 | "moduleResolution": "node", 12 | "module": "esnext", 13 | "target": "es2017", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "jsx": "react", 17 | "jsxFactory": "h", 18 | "resolveJsonModule": true, 19 | "suppressImplicitAnyIndexErrors": true 20 | }, 21 | "include": [ 22 | "src" 23 | ], 24 | "exclude": [ 25 | "node_modules" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-ionic-rules", 3 | "rules": { 4 | "no-import-side-effect": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // copy manifest.json to the path: 'public/build' 4 | // this will allow for the authRequest to see the file at www.example.com/manifest.json 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | const HeadersPlugin = new CopyWebpackPlugin([{ from: 'public/', to: '.' }]); 7 | const IonicDistPlugin = new CopyWebpackPlugin([ { from: 'node_modules/@ionic/core/dist', to: 'ionic' } ]); 8 | const WorkboxPlugin = require('workbox-webpack-plugin'); 9 | 10 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 11 | 12 | let PUBLIC_PATH = './'; 13 | 14 | process.argv.forEach(function (val) { 15 | if (val.includes('target=web')) { 16 | PUBLIC_PATH = '/'; 17 | } 18 | }); 19 | 20 | const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({ 21 | template: './public/index.html', 22 | filename: 'index.html', 23 | inject: 'body', 24 | base: PUBLIC_PATH 25 | }); 26 | 27 | module.exports = { 28 | entry: ['@babel/polyfill', './src/index.js'], 29 | target: 'electron-main', 30 | output: { 31 | path: path.resolve('www'), 32 | filename: '[name].bundle.js', 33 | chunkFilename: '[name].bundle.js', 34 | publicPath: PUBLIC_PATH 35 | }, 36 | devServer: { 37 | https: false, 38 | historyApiFallback: { 39 | disableDotRule: true 40 | }, 41 | watchOptions: { aggregateTimeout: 300, poll: 1000 }, 42 | headers: { 43 | "Access-Control-Allow-Origin": "*", 44 | "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", 45 | "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization", 46 | }, 47 | port: 9876 48 | }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.js$/, 53 | loader: 'babel-loader', 54 | exclude: /\.(\/node_modules\/|\/build\/|\/electron\/|\/src\/server.js)(\?\S*)?$/ 55 | }, 56 | { test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ }, 57 | { 58 | test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/, 59 | loader: 'file-loader!url-loader', 60 | }, 61 | { test: /\.css$/, loader: 'style-loader!css-loader' } 62 | ] 63 | }, 64 | plugins: [HtmlWebpackPluginConfig, IonicDistPlugin, HeadersPlugin, 65 | new WorkboxPlugin.InjectManifest({ 66 | swSrc: './public/service-worker.js', 67 | }) 68 | ] 69 | } 70 | --------------------------------------------------------------------------------