diff --git a/package.json b/package.json index c57b6b9..d1f45eb 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,11 @@ "@emotion/css": "^11.1.3", "@emotion/react": "^11.4.1", "@fontsource/inter": "^4.5.0", + "dotenv": "^10.0.0", "File": "^0.10.2", "html2canvas": "^1.3.2", "http2": "^3.3.7", - "jquery": "3.4.1", + "jquery": "^3.6.0", "node-fetch": "^2.6.1", "open-graph": "^0.2.6", "prettier": "^2.4.0", @@ -50,6 +51,6 @@ ] }, "devDependencies": { - "cross-env": "7.0.2" + "cross-env": "^7.0.3" } } diff --git a/public/background.js b/public/background.js index ff8eec0..cca655c 100644 --- a/public/background.js +++ b/public/background.js @@ -5,6 +5,10 @@ const domains = { cookie: 'https://slate.host' } +function randomID() { + return Math.random().toString(36).substr(2, 9); +} + const getSessionID = async () => { return new Promise((resolve, reject) => { chrome.cookies.get({"url": domain, "name": "WEB_SERVICE_SESSION_KEY"}, cookie => { @@ -63,7 +67,7 @@ const getUser = async (props) => { const json = await response.json(); if (json.error) { - console.log(json); + console.log("User data: ", json); } else { const collections = json.collections; const user = json.user; @@ -91,7 +95,6 @@ const checkLink = async (props) => { } const handleSaveLink = async (props) => { - const apiKey = await getApiKey(); const response = await fetch(`${domain}/api/v2/create-link`, { method: "POST", @@ -107,12 +110,14 @@ const handleSaveLink = async (props) => { }); const json = await response.json(); - console.log('upload data: ', json) - + + console.log('Link upload: ', json) + if(json.decorator === "LINK_DUPLICATE") { chrome.tabs.sendMessage(parseInt(props.tab), { run: 'UPLOAD_DUPLICATE', - data: json.data[0] + data: json.data[0], + id: props.id }); return; } @@ -120,6 +125,7 @@ const handleSaveLink = async (props) => { if(json.decorator === "SERVER_CREATE_LINK_FAILED" || json.error === true) { chrome.tabs.sendMessage(parseInt(props.tab), { run: 'UPLOAD_FAIL', + id: props.id }); return; } @@ -127,10 +133,11 @@ const handleSaveLink = async (props) => { if(!props.background) { chrome.tabs.sendMessage(parseInt(props.tab), { run: 'UPLOAD_DONE', - data: json.data[0], + data: json.data[0], + id: props.id, tab: props.tab }); - }else{ + } else { //If background upload, dont send a message to a tab return; } @@ -151,7 +158,7 @@ handleSaveImage = async (props) => { body: JSON.stringify({ data: { url: props.url, - filename: props.url, + filename: props.url }, }), }); @@ -196,16 +203,16 @@ const checkLoginSession = async (tab) => { } } -chrome.action.onClicked.addListener(async (tab) => { - chrome.tabs.sendMessage(tab.id, { run: 'LOAD_APP', type: 'LOADER_MAIN', tabId: tab.id }); +const openApp = async (tab) => { + chrome.tabs.sendMessage(parseInt(tab.id), { run: 'LOAD_APP', type: 'LOADER_MAIN' }); await checkLoginData(tab); -}); +} + +chrome.action.onClicked.addListener(async (tab) => { openApp(tab) }); chrome.commands.onCommand.addListener(async (command, tab) => { - if(command == 'open-app') { - chrome.tabs.sendMessage(tab.id, { run: 'LOAD_APP', type: 'LOADER_MAIN' }); - await checkLoginData(tab); - } + if(command == 'open-app') openApp(tab); + if(command == 'open-slate') { chrome.tabs.create({ 'url': `${domain}/_/data&extension=true&id=${tab.id}` }); } @@ -227,9 +234,15 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { } if(request.type === "SAVE_LINK") { - chrome.tabs.sendMessage(parseInt(sender.tab.id), { run: "OPEN_LOADING" }); - let data = await handleSaveLink({ url: sender.url, tab: sender.tab.id }) - } + let uploadId = randomID(); + chrome.tabs.sendMessage(parseInt(sender.tab.id), { + run: "OPEN_LOADING", + id: uploadId, + image: sender.tab.favIconUrl, + title: sender.tab.title + }); + let data = await handleSaveLink({ id: uploadId, url: sender.url, tab: sender.tab.id }) + } if(request.type === "CHECK_LOGIN") { await checkLoginSession(sender.tab); @@ -263,3 +276,91 @@ chrome.tabs.onUpdated.addListener(async (tabId , info , tab) => { } } }); + +handleUploadImage = async (info, tab) => { + chrome.tabs.sendMessage(parseInt(tab.id), { run: 'LOAD_APP_RIGHT_CLICK', type: 'LOADER_MINI' }); + let session = await checkLoginData(tab); + + if(session.user) { + + let uploadId = randomID(); + + const filename = info.srcUrl.replace(/^.*[\\\/]/, '') + const finalTitle = filename.split("?")[0]; + + chrome.tabs.sendMessage(parseInt(tab.id), { + run: 'OPEN_LOADING', + image: info.srcUrl, + id: uploadId, + title: finalTitle, + }); + + const apiKey = await getApiKey(); + const url = 'https://uploads.slate.host/api/v2/public/upload-by-url'; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': "application/json", + Authorization: apiKey, + }, + body: JSON.stringify({ + data: { + url: info.srcUrl, + filename: finalTitle, + }, + }), + }); + + const json = await response.json(); + console.log('Image upload: ', json) + + if(json.decorator === "LINK_DUPLICATE") { + chrome.tabs.sendMessage(parseInt(tab.id), { + run: 'UPLOAD_DONE', + id: uploadId, + data: json.data, + tab: tab + }); + return; + } + + if(json.decorator === "SERVER_CREATE_LINK_FAILED" || json.error === true) { + chrome.tabs.sendMessage(parseInt(tab.id), { + run: 'UPLOAD_FAIL', + id: uploadId, + data: json, + tab: tab + }); + return; + } + + chrome.tabs.sendMessage(parseInt(tab.id), { + run: 'UPLOAD_DONE', + id: uploadId, + data: json.data, + tab: tab + }); + } + + return; +} + +handleMenuOpen = async (info, tab) => { await openApp(tab) } + +chrome.contextMenus.create({ + id: "image_slate", + title: "Save image", + contexts: ["image"], +}, () => chrome.runtime.lastError); + +chrome.contextMenus.onClicked.addListener(async (info, tab) => { + if (info.menuItemId == "parent") { + await handleMenuOpen(info, tab); + } + + if (info.menuItemId == "image_slate") { + await handleUploadImage(info, tab) + } +}); + diff --git a/public/content.js b/public/content.js index c7b6b5b..b0863e2 100644 --- a/public/content.js +++ b/public/content.js @@ -16,7 +16,7 @@ if(window.location.href.startsWith('https://slate.host')) { let isJumperOpen = false; -chrome.runtime.onMessage.addListener(function(request, sender, callback) { +chrome.runtime.onMessage.addListener(async function(request, sender, callback) { if(request.run === "LOAD_APP") { if(!isJumperOpen) { @@ -24,9 +24,18 @@ chrome.runtime.onMessage.addListener(function(request, sender, callback) { isJumperOpen = true; return; } + window.postMessage({ type: "SHOW_APP" }, "*"); return; } + if(request.run === "LOAD_APP_RIGHT_CLICK") { + if(!isJumperOpen) { + main({ type: request.type }); + isJumperOpen = true; + return; + } + } + if(request.run === "AUTH_REQ") { window.postMessage({ type: "AUTH_REQ" }, "*"); return true; @@ -38,22 +47,42 @@ chrome.runtime.onMessage.addListener(function(request, sender, callback) { } if(request.run === "OPEN_LOADING") { - window.postMessage({ type: "OPEN_LOADING" }, "*"); + window.postMessage({ + type: "OPEN_LOADING", + id: request.id, + image: request.image, + title: request.title, + }, "*"); return true; } if(request.run === "UPLOAD_DONE") { - window.postMessage({ type: "UPLOAD_DONE", data: request.data, tab: request.tab }, "*"); + window.postMessage({ + type: "UPLOAD_DONE", + id: request.id, + data: request.data, + tab: request.tab + }, "*"); return true; } if(request.run === "UPLOAD_FAIL") { - window.postMessage({ type: "UPLOAD_FAIL" }, "*"); + window.postMessage({ + type: "UPLOAD_FAIL", + id: request.id, + data: request.data, + tab: request.tab + }, "*"); return true; } if(request.run === "UPLOAD_DUPLICATE") { - window.postMessage({ type: "UPLOAD_DUPLICATE", data: request.data }, "*"); + window.postMessage({ + type: "UPLOAD_DUPLICATE", + id: request.id, + data: request.data, + tab: request.tab + }, "*"); return true; } @@ -101,7 +130,11 @@ window.addEventListener("message", async function(event) { } if(event.data.run === "OPEN_LOADING") { - chrome.runtime.sendMessage({ type: "SAVE_LINK", url: event.data.url }); + chrome.runtime.sendMessage({ + type: "SAVE_LINK", + url: event.data.url, + image: event.data.image, + }); return true; } @@ -119,9 +152,5 @@ window.addEventListener("message", async function(event) { chrome.runtime.sendMessage({ type: "SIGN_OUT" }); } - if(event.data.run === "SET_OPEN_FALSE") { - isJumperOpen = false; - } - if (event.source !== window) return; }); diff --git a/public/manifest.json b/public/manifest.json index 8117f02..f12c114 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -20,8 +20,8 @@ "matches": [""], "all_frames": false, "js": [ - "./jquery.js", - "./content.js" + "./content.js", + "./jquery.js" ], "run_at": "document_idle" }], @@ -31,7 +31,7 @@ "cookies", "commands" ], - "host_permissions": ["https://slate.host/"], + "host_permissions": ["https://slate.host/", "http://*/*", "https://*/*"], "commands": { "open-app": { "suggested_key": { @@ -65,9 +65,10 @@ "/static/*", "/fonts/*", "Inter-Regular.ttf", - "../fonts/*" + "../fonts/*", + "icon128.png" ], - "matches": [""] + "matches": ["", "http://*/*", "https://*/*"] } ] } diff --git a/src/App.js b/src/App.js index 6098402..f028e6e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,5 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; +import useState from "react-usestateref"; import Modal from "./Components/Modal"; import Toast from "./Components/Toast"; import ModalProvider from "./Contexts/ModalProvider"; @@ -7,14 +8,34 @@ import Hotkeys from "react-hot-keys"; import * as Strings from "./Common/strings"; function App() { - const [isOpened, setIsOpened] = useState(true); - const [isUploading, setIsUploading] = useState(false); + const [isOpened, setIsOpened, isOpenedRef] = useState(true); + const [isUploading, setIsUploading, isUploadingRef] = useState(false); //const [isScreenshot, setIsScreenshot] = useState(false); const [og, setOg] = useState({ image: null, title: null }); const [checkLink, setCheckLink] = useState({ uploaded: false, data: null }); + const [files, setFiles, filesRef] = useState({ all: [] }); //const [user, setUser] = useState({ signedin: false, data: null }); + async function updateUploadItem(props) { + if (filesRef.current.all) { + let arrayUpdate = [...filesRef.current.all]; + let update = arrayUpdate.map((file) => + file.id === props.id + ? { ...file, status: props.status, cid: props.cid } + : file + ); + return update; + } + } + + function removeUploadItem(props) { + let arrayRemove = [...filesRef.current.all]; + const removed = arrayRemove.filter((file) => file.id !== props.id); + setFiles({ all: removed }); + return; + } + //Disable Up Down arrows in the main window to prevent page scrolls const onKeyDownMain = (e) => { if ( @@ -30,7 +51,6 @@ function App() { if (keyName === "esc") { setIsOpened(false); window.removeEventListener("keydown", onKeyDownMain); - window.postMessage({ run: "SET_OPEN_FALSE" }, "*"); } if (keyName === "alt+b" || keyName === "enter") { @@ -55,22 +75,50 @@ function App() { } } - const messageListeners = () => { - window.addEventListener("message", function (event) { + const messageListeners = async () => { + window.addEventListener("message", async function (event) { + + /* if (event.data.type === "UPLOAD_START") { - setIsOpened(false); + //setIsOpened(false); setIsUploading(true); } + */ + + if (event.data.type === "SHOW_APP") { + setIsOpened(true); + } if (event.data.type === "CLOSE_APP") { window.removeEventListener("keydown", onKeyDownMain); setIsOpened(false); - window.postMessage({ run: "SET_OPEN_FALSE" }, "*"); } if (event.data.type === "OPEN_LOADING") { - setIsOpened(false); + setIsOpened(false) + + let newFileData = { + title: event.data.title, + image: event.data.image, + id: event.data.id, + status: "uploading", + error: false, + cid: null, + }; + + let checkIfUploading = filesRef.current.all.find((file) => { + return file.id === event.data.id; + }); + + if (!checkIfUploading) { + setFiles(() => ({ + all: [...filesRef.current.all, newFileData], + })); + } + + if (event.data.image) setOg({ image: event.data.image }); setIsUploading(true); + return; } if (event.data.type === "CHECK_LINK") { @@ -78,8 +126,47 @@ function App() { setCheckLink({ uploaded: true, data: event.data.data }); } } + + if (event.data.type === "UPLOAD_DONE") { + let data = await updateUploadItem({ + id: event.data.id, + status: 'complete', + cid: event.data.data.cid + }) + + setFiles({ all: data }); + setTimeout(() => removeUploadItem({ id: event.data.id }), 3000); + return; + } + + if (event.data.type === "UPLOAD_DUPLICATE") { + let data = await updateUploadItem({ + id: event.data.id, + status: 'duplicate', + error: false, + cid: event.data.data.cid + }) + + setFiles({ all: data }); + setTimeout(() => removeUploadItem({ id: event.data.id }), 3000); + return; + } + + if (event.data.type === "UPLOAD_FAIL") { + let data = await updateUploadItem({ + id: event.data.id, + status: 'error', + error: true, + cid: null + }) + + setFiles({ all: data }); + setTimeout(() => removeUploadItem({ id: event.data.id }), 3000); + return; + } + }); - } + } useEffect(() => { messageListeners(); @@ -111,20 +198,28 @@ function App() { return ( <> - {isOpened && ( - -
- - - -
-
- )} - - {isUploading && } + +
+ + + +
+
+ + +
+ removeUploadItem({ id: file.id })} + show={isUploadingRef.current} + /> +
+
); } diff --git a/src/Common/styles.js b/src/Common/styles.js index 2771ea4..923e27b 100644 --- a/src/Common/styles.js +++ b/src/Common/styles.js @@ -388,6 +388,30 @@ export const toast = ` color: ${Constants.system.black}; } + .loaderWindowTwo { + top: 0%; + right: 0%; + margin-top: 2em; + margin-right: 2em; + position: fixed; + z-index: 2100000000; + } + + .loaderWindowMain { + margin-bottom: 1em; + position: relative; + background: ${Constants.system.white}; + width: 320px; + height: 88px; + max-width: 320px; + max-height: 88px; + box-shadow: 0 .5rem 1rem rgba(0,0,0,.15) !important; + z-index: 2100000000; + border-radius: 10px; + border: 0; + color: ${Constants.system.black}; + } + .loaderBody { width: 100%; } diff --git a/src/Components/Modal.js b/src/Components/Modal.js index 030ad7c..7dffb4d 100644 --- a/src/Components/Modal.js +++ b/src/Components/Modal.js @@ -72,26 +72,31 @@ const Modal = (props) => { <> - - -
+ {props.show && + <> + + +
+ + + }
)} diff --git a/src/Components/Toast.js b/src/Components/Toast.js index 1218c91..8bc0f57 100644 --- a/src/Components/Toast.js +++ b/src/Components/Toast.js @@ -1,4 +1,6 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect } from "react"; +import useState from "react-usestateref"; + import { ModalContext } from "../Contexts/ModalProvider"; import ReactShadowRoot from "react-shadow-root"; @@ -11,65 +13,14 @@ import * as Strings from "../Common/strings"; const Toast = (props) => { const [favicon, setFavicon] = useState(null); - const [visable, setVisable] = useState(true); - const [upload, setUpload] = useState({ - status: "uploading", - data: null, - error: false, - tab: null, - }); - - const handleCloseModal = () => { - setVisable(false); - }; - - const toastTimer = () => { - const timer = setTimeout(() => { - handleCloseModal(); - }, 10000); - return () => clearTimeout(timer); - } - - const messageListeners = () => { - window.addEventListener("message", function (event) { - if (event.data.type === "UPLOAD_DONE") { - setUpload({ - status: "complete", - data: event.data.data.cid, - tab: event.data.tab, - }); - toastTimer(); - return; - } - - if (event.data.type === "UPLOAD_FAIL") { - setUpload({ status: "error" }); - toastTimer() - return; - } - - if (event.data.type === "UPLOAD_DUPLICATE") { - setUpload({ - status: "duplicate", - data: event.data.data.cid, - }); - toastTimer() - return; - } - }); - } - - useEffect(() => { - messageListeners() - }, []); - + const [uploads, setUploads, uploadsRef] = useState({ all: [] }); let count = 28; let title = Strings.truncateString(count, props.title); const Footer = (props) => { - let url = props.upload.data - ? Strings.getSlateFileLink(props.upload.data, props.upload.tab) + let url = props.upload.cid + ? Strings.getSlateFileLink(props.upload.cid, 100) : null; return ( <> @@ -124,32 +75,48 @@ const Toast = (props) => { checkImage(props.image); } + const _handleCloseToast = (file) => { + props.callback(file); + } + return ( {({ pageData }) => ( <> - {visable && ( -