diff --git a/.eslintrc.js b/.eslintrc.js index c02fc66..4ab840a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,25 +1,25 @@ module.exports = { - env: { - browser: true, - node: true, - commonjs: true, - es2021: true, - jest: true, + env: { + browser: true, + node: true, + commonjs: true, + es2021: true, + jest: true, + serviceworker: true, + }, + extends: ['eslint:recommended', 'plugin:react/recommended'], + overrides: [], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, }, - extends: ['eslint:recommended', 'plugin:react/recommended'], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: "module", - ecmaFeatures: { - jsx: true - } + }, + rules: {}, + settings: { + react: { + version: 'detect', }, - rules: {}, - settings: { - react: { - version: "detect" - } - }, - - }; \ No newline at end of file + }, +}; diff --git a/package-lock.json b/package-lock.json index d02fc02..cd43fc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fontsource/poppins": "^4.5.10", "@octokit/rest": "^19.0.5", "bootstrap": "^5.2.2", + "compressorjs": "^1.1.1", "eslint": "^8.27.0", "http-status-codes": "^2.2.0", "octokit": "^2.0.10", @@ -6219,6 +6220,25 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@typescript-eslint/utils": { "version": "5.43.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", @@ -7224,6 +7244,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -7785,6 +7810,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/compressorjs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.1.1.tgz", + "integrity": "sha512-SysRuUPfmUNoq+RviE0iMFVUmoX2q/x+7PkEPUmk6NGkd85hDrmvujx0Qtp8UCGA6KMe5kuodsylPQcNaLf60w==", + "dependencies": { + "blueimp-canvas-to-blob": "^3.29.0", + "is-blob": "^2.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -10650,19 +10684,6 @@ } ] }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -10870,25 +10891,6 @@ "node": ">=4" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -11476,6 +11478,17 @@ "node": ">=8" } }, + "node_modules/is-blob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", + "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -17398,6 +17411,25 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/react-dev-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-dev-utils/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -17599,6 +17631,19 @@ } } }, + "node_modules/react-scripts/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/react-scripts/node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -24847,6 +24892,21 @@ "is-glob": "^4.0.3", "semver": "^7.3.7", "tsutils": "^3.21.0" + }, + "dependencies": { + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + } } }, "@typescript-eslint/utils": { @@ -25626,6 +25686,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "blueimp-canvas-to-blob": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -26051,6 +26116,15 @@ } } }, + "compressorjs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.1.1.tgz", + "integrity": "sha512-SysRuUPfmUNoq+RviE0iMFVUmoX2q/x+7PkEPUmk6NGkd85hDrmvujx0Qtp8UCGA6KMe5kuodsylPQcNaLf60w==", + "requires": { + "blueimp-canvas-to-blob": "^3.29.0", + "is-blob": "^2.1.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -28004,16 +28078,6 @@ "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==" }, - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, "fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -28159,19 +28223,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -28587,6 +28638,11 @@ "binary-extensions": "^2.0.0" } }, + "is-blob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", + "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" + }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -32611,6 +32667,19 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -32745,6 +32814,16 @@ "source-map": "^0.7.3" } }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/package.json b/package.json index 8202e8c..6490335 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "react-scripts build", "test": "jest --", "eject": "react-scripts eject", - "auth-worker": "wrangler dev ./worker/index", + "auth-worker": "wrangler dev ./src/worker/index", "auth-worker:login": "wrangler login", "lint": "eslint src/**/*.js", "format": "npx prettier --write .", @@ -28,6 +28,7 @@ "@fontsource/poppins": "^4.5.10", "@octokit/rest": "^19.0.5", "bootstrap": "^5.2.2", + "compressorjs": "^1.1.1", "eslint": "^8.27.0", "http-status-codes": "^2.2.0", "octokit": "^2.0.10", diff --git a/src/App.js b/src/App.js index a3be9ac..ec24214 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,13 @@ -import React from "react"; -import NavigationBar from "./NavigationBar"; -import AuthDialog from "./AuthDialog"; -import UploadInterface from "./UploadInterface"; +import React from 'react'; +import NavigationBar from './NavigationBar'; +import AuthDialog from './AuthDialog'; +import UploadInterface from './UploadInterface'; function App() { const [token, setToken] = React.useState(null); const [repository, setRepository] = React.useState(null); const [show, setShow] = React.useState(true); - const isAuthorized = token && repository; + const isAuthorized = Boolean(token && repository); return ( <> @@ -19,14 +19,9 @@ function App() { }} /> - + - + ); } diff --git a/src/AuthDialog.js b/src/AuthDialog.js index 1f6ef66..9792e04 100644 --- a/src/AuthDialog.js +++ b/src/AuthDialog.js @@ -1,5 +1,5 @@ -import React, { useRef } from "react"; -import { Button, Form, Modal } from "react-bootstrap"; +import React, { useRef } from 'react'; +import { Button, Form, Modal } from 'react-bootstrap'; import PropTypes from 'prop-types'; function AuthDialog({ show, setShow, setToken, setRepository }) { @@ -23,7 +23,7 @@ function AuthDialog({ show, setShow, setToken, setRepository }) { - Repository (username/repository_name) + Repository Name @@ -43,13 +43,12 @@ function AuthDialog({ show, setShow, setToken, setRepository }) { let token = tokenEl.current; let repo = repoEl.current; - const validRepo = - /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/[a-zA-Z0-9_.-]+$/i; + const validRepo = /^[a-zA-Z0-9_.-]+$/i; - if (token.value === "") { + if (token.value === '') { token.focus(); return; - } else if (repo.value === "" || !repo.value.match(validRepo)) { + } else if (repo.value === '' || !repo.value.match(validRepo)) { repo.focus(); return; } @@ -69,7 +68,7 @@ AuthDialog.propTypes = { show: PropTypes.bool, setToken: PropTypes.func, setRepository: PropTypes.func, - setShow: PropTypes.func -} + setShow: PropTypes.func, +}; -export default AuthDialog \ No newline at end of file +export default AuthDialog; diff --git a/src/UploadInterface.js b/src/UploadInterface.js index 1edbe47..c33334c 100644 --- a/src/UploadInterface.js +++ b/src/UploadInterface.js @@ -1,23 +1,132 @@ -import React, { useRef, useState } from "react"; -import "./App.css"; -import "bootstrap/dist/css/bootstrap.min.css"; -import { FileUploader } from "react-drag-drop-files"; -import { Button, OverlayTrigger, Tooltip } from "react-bootstrap"; +import React, { useRef, useState } from 'react'; +import './App.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import { FileUploader } from 'react-drag-drop-files'; +import { Alert, Button, Image, OverlayTrigger, Tooltip } from 'react-bootstrap'; import PropTypes from 'prop-types'; +import { Octokit } from '@octokit/rest'; +import getUser from './worker/functions/get-user'; +import createRepo from './worker/functions/create-repo'; +import { + getCurrentCommit, + createBlobForFile, + createNewTree, + getPathNamesFromFile, + createNewCommit, + setBranchToCommit, +} from './worker/functions/commit-files'; +import { createOptimizedImages } from './image-optimizer'; +import loadingGif from './resource/loading-gear.gif'; -const fileTypes = ["jpg", "jpeg", "png", "bmp", "gif"]; +const fileTypes = ['jpg', 'jpeg', 'png', 'bmp', 'gif']; -function UploadInterface({ isAuthorized }) { - const [image, setImage] = useState(null); +function UploadInterface({ isAuthorized, token, repository }) { + const [images, setImages] = useState([]); + const [userMessage, setUserMessage] = useState(''); + const [errorOccurred, setErrorOccurred] = useState(false); + const [loading, setLoading] = useState(false); const imgPreviewEl = useRef(null); + async function uploadBtnClicked() { + setUserMessage('MyPhotoHub is validating user token...'); + setLoading(true); + setErrorOccurred(false); + + const octokit = new Octokit({ auth: token }); + let username, repoCreated; + try { + username = await getUser(octokit); + } catch (err) { + console.error('Invalid PAT token'); + setUserMessage('Sorry, could not find a user with the token'); + setErrorOccurred(true); + setLoading(false); + return; + } + + setUserMessage('User validated! MyPhotoHub is creating a GitHub repository...'); + repoCreated = await createRepo(octokit, username, repository); + + if (repoCreated) { + setUserMessage('GitHub repository has been created! Creating optimized images...'); + + const convertedFiles = await createOptimizedImages(images); + if (!convertedFiles) { + setUserMessage('Something went wrong while converting images...'); + setLoading(false); + return; + } + const pathNames = getPathNamesFromFile(convertedFiles); + + setUserMessage('Images optimized! Preparing image files for a new commit...'); + try { + const listOfBlob = await createBlobForFile(octokit, username, repository, convertedFiles); + + const currentCommit = await getCurrentCommit(octokit, username, repository, 'main'); + + const newTree = await createNewTree( + octokit, + username, + repository, + listOfBlob, + pathNames, + currentCommit.treeSha + ); + + setUserMessage('Now creating a commit to repo...'); + const commitMessage = 'Add my photos to repo'; + const newCommit = await createNewCommit( + octokit, + username, + repository, + commitMessage, + newTree.sha, + currentCommit.commitSha + ); + + await setBranchToCommit(octokit, username, repository, 'main', newCommit.sha); + + setUserMessage( + `Process complete. You can check out https://github.com/${username}/${repository}` + ); + setLoading(false); + } catch (err) { + setLoading(false); + setErrorOccurred(true); + setUserMessage(`Something went wrong while creating a commit`); + console.error('Error occurred while creating a commit'); + } + } else { + console.error('Error occurred while creating a repo'); + setUserMessage('Sorry, something went wrong while creating a repository'); + setErrorOccurred(true); + setLoading(false); + } + } + return (
+ {!userMessage ? ( + <> + ) : loading ? ( + + + {userMessage} + + ) : !errorOccurred ? ( + + {userMessage} + + ) : ( + + {userMessage} + + )} Selected file preview

Drag and Drop or Click to Upload Images

@@ -25,27 +134,28 @@ function UploadInterface({ isAuthorized }) { name="file" accept={fileTypes} types={fileTypes} - multiple={false} + multiple={true} classes="Drop-zone" - handleChange={(file) => { - setImage(file); + handleChange={(files) => { + setImages(files); const reader = new FileReader(); reader.onload = (e) => { imgPreviewEl.current.src = e.target.result; }; - reader.readAsDataURL(file); - imgPreviewEl.current.title = file.name; + reader.readAsDataURL(files[0]); + imgPreviewEl.current.title = files[0].name; + console.log('IMAGE', images); }} - label={"Upload or drop a file right here"} + label={'Upload or drop a file right here'} /> - {!image - ? "Select Image to Upload" + {!images + ? 'Select Image to Upload' : !isAuthorized - ? "Add Token and Repository" - : "Click to Upload"} + ? 'Add Token and Repository' + : 'Click to Upload'} } placement="bottom" @@ -54,7 +164,8 @@ function UploadInterface({ isAuthorized }) { @@ -66,7 +177,9 @@ function UploadInterface({ isAuthorized }) { } UploadInterface.propTypes = { - isAuthorized:PropTypes.bool -} + isAuthorized: PropTypes.bool, + token: PropTypes.string, + repository: PropTypes.string, +}; -export default UploadInterface \ No newline at end of file +export default UploadInterface; diff --git a/src/image-optimizer.js b/src/image-optimizer.js new file mode 100644 index 0000000..87d87c7 --- /dev/null +++ b/src/image-optimizer.js @@ -0,0 +1,57 @@ +import Compressor from 'compressorjs'; + +const compressorOptions = [ + { mimeType: 'image/jpeg', width: 375 }, + { mimeType: 'image/jpeg', width: 744 }, + { mimeType: 'image/jpeg', width: 950 }, + { mimeType: 'image/jpeg', width: 1120 }, + { mimeType: 'image/webp', width: 375 }, + { mimeType: 'image/webp', width: 744 }, + { mimeType: 'image/webp', width: 950 }, + { mimeType: 'image/webp', width: 1120 }, + { mimeType: 'image/png', width: 375 }, + { mimeType: 'image/png', width: 744 }, + { mimeType: 'image/png', width: 950 }, + { mimeType: 'image/png', width: 1120 }, + { mimeType: 'image/avif', width: 375 }, + { mimeType: 'image/avif', width: 744 }, + { mimeType: 'image/avif', width: 950 }, + { mimeType: 'image/avif', width: 1120 }, +]; + +const compressImage = (file, option) => { + return new Promise((resolve, reject) => { + new Compressor(file, { + width: option.width, + quality: 0.6, + success: (result) => { + const fileName = + file.name.replace(/\.[^/.]+$/, '') + + '_' + + option.width + + '.' + + option.mimeType.substring(option.mimeType.indexOf('/') + 1); + resolve(new File([result], fileName, { type: option.mimeType })); + }, + error: (err) => { + console.error(err.message); + reject(err); + }, + }); + }); +}; + +export const createOptimizedImages = async (originalImages) => { + if (!originalImages) { + return; + } + + let compressFilesPromises = []; + compressorOptions.forEach((option) => { + for (const image of originalImages) { + compressFilesPromises.push(compressImage(image, option)); + } + }); + + return Promise.all(compressFilesPromises); +}; diff --git a/src/resource/loading-gear.gif b/src/resource/loading-gear.gif new file mode 100644 index 0000000..7fc3834 Binary files /dev/null and b/src/resource/loading-gear.gif differ diff --git a/src/worker/functions/commit-files.js b/src/worker/functions/commit-files.js new file mode 100644 index 0000000..b329ab6 --- /dev/null +++ b/src/worker/functions/commit-files.js @@ -0,0 +1,109 @@ +const getCurrentCommit = async (octokit, username, repo, branch = 'main') => { + const { data: refData } = await octokit.request( + `GET /repos/${username}/${repo}/git/ref/heads/${branch}`, + { + owner: username, + repo: repo, + ref: `heads/${branch}`, + } + ); + + const commitSha = refData.object.sha; + const { data: commitData } = await octokit.request( + `GET /repos/${username}/${repo}/git/commits/${commitSha}`, + { + owner: username, + repo: repo, + commit_sha: commitSha, + } + ); + return { + commitSha, + treeSha: commitData.tree.sha, + }; +}; + +const createBlobForFile = async (octokit, username, repoName, files) => { + let blobDataPromises = []; + for (const file of files) { + let reader = new FileReader(); + await reader.readAsArrayBuffer(file); + const promise = new Promise((resolve, reject) => { + reader.onload = async () => { + const imageBuffer = reader.result; + const byteArray = new Uint8Array(imageBuffer); + const blob = new Blob([byteArray]); + const BlobURL = URL.createObjectURL(blob); + const blobData = octokit.request(`POST /repos/${username}/${repoName}/git/blobs`, { + owner: username, + repo: repoName, + content: BlobURL, + }); + resolve(blobData); + }; + reader.onerror = reject; + }); + blobDataPromises.push(promise); + } + return Promise.all(blobDataPromises); +}; + +const createNewTree = async (octokit, username, repoName, blobs, paths, parentTreeSha) => { + const blobData = blobs.map((blob) => blob.data); + const trees = blobData.map(({ sha }, index) => ({ + path: `images/${paths[index]}`, + mode: `100644`, + type: `blob`, + sha: sha, + })); + const { data } = await octokit.request(`POST /repos/${username}/${repoName}/git/trees`, { + owner: username, + repo: repoName, + base_tree: parentTreeSha, + tree: trees, + }); + return data; +}; + +const createNewCommit = async ( + octokit, + username, + repoName, + message, + currentTreeSha, + currentCommitSha +) => + ( + await octokit.request(`POST /repos/${username}/${repoName}/git/commits`, { + owner: username, + repo: repoName, + message: message, + tree: currentTreeSha, + parents: [currentCommitSha], + }) + ).data; + +const setBranchToCommit = (octokit, username, repoName, branch = `main`, commitSha) => + octokit.request(`PATCH /repos/${username}/${repoName}/git/refs/heads/${branch}`, { + owner: username, + repo: repoName, + ref: `heads/${branch}`, + sha: commitSha, + }); + +const getPathNamesFromFile = (convertedFiles) => { + let pathNames = []; + for (const file of convertedFiles) { + pathNames.push(file.name); + } + return pathNames; +}; + +module.exports = { + getCurrentCommit, + createBlobForFile, + createNewTree, + getPathNamesFromFile, + createNewCommit, + setBranchToCommit, +}; diff --git a/worker/functions/create-repo.js b/src/worker/functions/create-repo.js similarity index 80% rename from worker/functions/create-repo.js rename to src/worker/functions/create-repo.js index 37d5eee..bca0cc1 100644 --- a/worker/functions/create-repo.js +++ b/src/worker/functions/create-repo.js @@ -1,15 +1,14 @@ const generateUniqueName = (username) => { - return `${username}-photohub-${new Date(Date.now()).toLocaleDateString( - "en-CA" - )}`; + return `${username}-photohub-${new Date(Date.now()).toLocaleDateString('en-CA')}`; }; const createRepo = async (octokit, username, repoName) => { try { await octokit.rest.repos.createForAuthenticatedUser({ name: repoName ? repoName : generateUniqueName(username), - description: "Your repository generated using my-photohub", + description: 'Your repository generated using my-photohub', private: false, + auto_init: true, }); return true; } catch (err) { diff --git a/worker/functions/get-user.js b/src/worker/functions/get-user.js similarity index 100% rename from worker/functions/get-user.js rename to src/worker/functions/get-user.js diff --git a/worker/functions/handle-post-req.js b/src/worker/functions/handle-post-req.js similarity index 100% rename from worker/functions/handle-post-req.js rename to src/worker/functions/handle-post-req.js diff --git a/worker/functions/on-req-post.js b/src/worker/functions/on-req-post.js similarity index 100% rename from worker/functions/on-req-post.js rename to src/worker/functions/on-req-post.js diff --git a/worker/functions/response.js b/src/worker/functions/response.js similarity index 100% rename from worker/functions/response.js rename to src/worker/functions/response.js diff --git a/worker/functions/validate-input.js b/src/worker/functions/validate-input.js similarity index 100% rename from worker/functions/validate-input.js rename to src/worker/functions/validate-input.js diff --git a/src/worker/index.js b/src/worker/index.js new file mode 100644 index 0000000..23abce0 --- /dev/null +++ b/src/worker/index.js @@ -0,0 +1,28 @@ +import { onRequestPost } from './functions/on-req-post'; +import { StatusCodes } from 'http-status-codes'; +import { rawHtmlResponse, loginForm } from './functions/response'; + +const handleRequest = async (request) => { + console.log('HANDLE REQUEST'); + const result = await onRequestPost(request); + if (result === StatusCodes.CONFLICT) { + const error = '

ERROR: Repo already exists

'; + return rawHtmlResponse(loginForm(error)); + } + return new Response(result); +}; + +addEventListener('fetch', (event) => { + console.log('FETCH EVENT'); + const { request } = event; + const { url } = request; + // GET request + if (new URL(url).pathname === '/' && request.method === 'GET') { + return event.respondWith(rawHtmlResponse(loginForm())); + // POST requests + } else if (request.method === 'POST') { + return event.respondWith(handleRequest(request)); + } +}); + +export default handleRequest; diff --git a/worker/index.test.js b/src/worker/index.test.js similarity index 100% rename from worker/index.test.js rename to src/worker/index.test.js diff --git a/worker/wrangler.toml b/src/worker/wrangler.toml similarity index 100% rename from worker/wrangler.toml rename to src/worker/wrangler.toml diff --git a/worker/.eslintrc.json b/worker/.eslintrc.json deleted file mode 100644 index 62a4635..0000000 --- a/worker/.eslintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "serviceworker": true - }, - "overrides": [], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": {} -} diff --git a/worker/.gitignore b/worker/.gitignore deleted file mode 100644 index b512c09..0000000 --- a/worker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/worker/index.js b/worker/index.js deleted file mode 100644 index 871051e..0000000 --- a/worker/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { onRequestPost } from "./functions/on-req-post"; -import { StatusCodes } from "http-status-codes"; -import { rawHtmlResponse, loginForm } from "./functions/response"; - -const handleRequest = async (request) => { - const result = await onRequestPost(request); - if (result === StatusCodes.CONFLICT) { - const error = "

ERROR: Repo already exists

"; - return rawHtmlResponse(loginForm(error)); - } - return new Response(result); -}; - -addEventListener("fetch", (event) => { - const { request } = event; - const { url } = request; - // GET request - if (new URL(url).pathname === "/" && request.method === "GET") { - return event.respondWith(rawHtmlResponse(loginForm())); - // POST requests - } else if (request.method === "POST") { - return event.respondWith(handleRequest(request)); - } -});