├── .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 | [](https://github.com/nerdic-coder/block-photos/releases/)
8 | [](https://github.com/nerdic-coder/block-photos/releases/)
9 | [](https://github.com/nerdic-coder/block-photos)
10 | [](https://github.com/nerdic-coder/block-photos)
11 | [](https://github.com/nerdic-coder/block-photos)
12 | [](https://www.codefactor.io/repository/github/nerdic-coder/block-photos/overview/master)
13 | [](https://app.block-photos.com/)
14 | [](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 |
--------------------------------------------------------------------------------