2020-09-27 00:15:37 +03:00
|
|
|
import * as Actions from "~/common/actions";
|
2020-10-02 07:24:10 +03:00
|
|
|
import * as Store from "~/common/store";
|
2020-10-05 00:30:28 +03:00
|
|
|
import * as Constants from "~/common/constants";
|
2020-10-23 13:34:31 +03:00
|
|
|
import * as Credentials from "~/common/credentials";
|
2020-10-31 02:12:20 +03:00
|
|
|
import * as Strings from "~/common/strings";
|
2020-11-15 19:41:35 +03:00
|
|
|
import * as Validations from "~/common/validations";
|
2020-11-28 07:39:01 +03:00
|
|
|
import * as Events from "~/common/custom-events";
|
2021-06-11 22:25:58 +03:00
|
|
|
import * as Logging from "~/common/logging";
|
2021-07-07 23:50:57 +03:00
|
|
|
import * as UserBehaviors from "~/common/user-behaviors";
|
|
|
|
import * as Window from "~/common/window";
|
2020-10-02 07:24:10 +03:00
|
|
|
|
2020-10-05 00:30:28 +03:00
|
|
|
import { encode } from "blurhash";
|
2020-09-12 01:25:33 +03:00
|
|
|
|
2020-09-28 20:48:44 +03:00
|
|
|
const STAGING_DEAL_BUCKET = "stage-deal";
|
|
|
|
|
2021-07-07 23:50:57 +03:00
|
|
|
export const fileKey = ({ lastModified, name }) => `${lastModified}-${name}`;
|
|
|
|
|
2020-10-05 00:30:28 +03:00
|
|
|
const loadImage = async (src) =>
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
const img = new Image();
|
|
|
|
img.crossOrigin = "Anonymous";
|
|
|
|
img.onload = () => resolve(img);
|
|
|
|
img.onerror = (...args) => reject(args);
|
|
|
|
img.src = src;
|
|
|
|
});
|
|
|
|
|
|
|
|
const getImageData = (image) => {
|
2020-12-12 06:57:54 +03:00
|
|
|
let ratio = Math.min(100 / image.height, 100 / image.width);
|
|
|
|
image.height = image.height * ratio;
|
|
|
|
image.width = image.width * ratio;
|
2020-10-05 00:30:28 +03:00
|
|
|
const canvas = document.createElement("canvas");
|
|
|
|
canvas.width = image.width;
|
|
|
|
canvas.height = image.height;
|
|
|
|
const context = canvas.getContext("2d");
|
2020-12-12 06:57:54 +03:00
|
|
|
context.scale(ratio, ratio);
|
2020-10-05 00:30:28 +03:00
|
|
|
context.drawImage(image, 0, 0);
|
|
|
|
return context.getImageData(0, 0, image.width, image.height);
|
|
|
|
};
|
|
|
|
|
|
|
|
const encodeImageToBlurhash = async (imageUrl) => {
|
|
|
|
const image = await loadImage(imageUrl);
|
|
|
|
const imageData = getImageData(image);
|
|
|
|
return encode(imageData.data, imageData.width, imageData.height, 4, 4);
|
|
|
|
};
|
|
|
|
|
2020-10-23 13:34:31 +03:00
|
|
|
// NOTE(jim): We're speaking to a different server now.
|
|
|
|
const getCookie = (name) => {
|
|
|
|
var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
|
|
|
|
if (match) return match[2];
|
|
|
|
};
|
|
|
|
|
2021-07-07 23:50:57 +03:00
|
|
|
export const uploadLink = async ({ url, slate }) => {
|
|
|
|
let createResponse = await Actions.createLink({ url, slate });
|
|
|
|
if (Events.hasError(createResponse)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { added, skipped } = createResponse.data;
|
|
|
|
if (added) {
|
|
|
|
Events.dispatchMessage({ message: "Link added", status: "INFO" });
|
|
|
|
} else if (skipped) {
|
|
|
|
Events.dispatchMessage({
|
|
|
|
message: "You've already saved this link",
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const uploadFiles = async ({ context, files, slate, keys, numFailed = 0 }) => {
|
|
|
|
if (!files || !files.length) {
|
|
|
|
context._handleRegisterLoadingFinished({ keys });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const resolvedFiles = [];
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
const currentFileKey = fileKey(files[i]);
|
|
|
|
if (Store.checkCancelled(currentFileKey)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(jim): With so many failures, probably good to wait a few seconds.
|
|
|
|
await Window.delay(3000);
|
|
|
|
|
|
|
|
// NOTE(jim): Sends XHR request.
|
|
|
|
let response;
|
|
|
|
try {
|
|
|
|
response = await upload({
|
|
|
|
file: files[i],
|
|
|
|
context,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
Logging.error(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!response || response.error) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
resolvedFiles.push(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!resolvedFiles.length) {
|
|
|
|
context._handleRegisterLoadingFinished({ keys });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//NOTE(martina): this commented out portion is only for if parallel uploading
|
|
|
|
// let responses = await Promise.allSettled(resolvedFiles);
|
|
|
|
// let succeeded = responses
|
|
|
|
// .filter((res) => {
|
|
|
|
// return res.status === "fulfilled" && res.value && !res.value.error;
|
|
|
|
// })
|
|
|
|
// .map((res) => res.value);
|
|
|
|
|
|
|
|
let createResponse = await Actions.createFile({ files: resolvedFiles, slate });
|
|
|
|
|
|
|
|
if (Events.hasError(createResponse)) {
|
|
|
|
context._handleRegisterLoadingFinished({ keys });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { added, skipped } = createResponse.data;
|
|
|
|
|
|
|
|
let message = Strings.formatAsUploadMessage(added, skipped + numFailed, slate);
|
|
|
|
Events.dispatchMessage({ message, status: !added ? null : "INFO" });
|
|
|
|
|
|
|
|
context._handleRegisterLoadingFinished({ keys });
|
|
|
|
};
|
|
|
|
|
|
|
|
export const upload = async ({ file, context, bucketName }) => {
|
|
|
|
const currentFileKey = fileKey(file);
|
2020-08-20 08:20:18 +03:00
|
|
|
let formData = new FormData();
|
|
|
|
const HEIC2ANY = require("heic2any");
|
|
|
|
|
2020-08-20 08:29:33 +03:00
|
|
|
// NOTE(jim): You must provide a file from an type="file" input field.
|
|
|
|
if (!file) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-11-15 19:41:35 +03:00
|
|
|
const isZipFile =
|
2020-11-14 16:27:45 +03:00
|
|
|
file.type.startsWith("application/zip") || file.type.startsWith("application/x-zip-compressed");
|
2020-11-15 19:41:35 +03:00
|
|
|
const isUnityFile = await Validations.isUnityFile(file);
|
2020-11-14 16:27:45 +03:00
|
|
|
|
2020-08-20 08:20:18 +03:00
|
|
|
// TODO(jim): Put this somewhere else to handle conversion cases.
|
|
|
|
if (file.type.startsWith("image/heic")) {
|
|
|
|
const converted = await HEIC2ANY({
|
|
|
|
blob: file,
|
|
|
|
toType: "image/png",
|
|
|
|
quality: 1,
|
2020-10-02 07:24:10 +03:00
|
|
|
}); //TODO(martina): figure out how to cancel an await if upload has been cancelled
|
2020-08-20 08:20:18 +03:00
|
|
|
|
|
|
|
formData.append("data", converted);
|
|
|
|
} else {
|
|
|
|
formData.append("data", file);
|
|
|
|
}
|
|
|
|
|
2021-07-07 23:50:57 +03:00
|
|
|
if (Store.checkCancelled(currentFileKey)) {
|
2020-10-02 07:24:10 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const _privateUploadMethod = (path, file) =>
|
2020-08-20 08:20:18 +03:00
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
const XHR = new XMLHttpRequest();
|
2020-10-09 13:43:56 +03:00
|
|
|
|
2021-07-07 23:50:57 +03:00
|
|
|
window.addEventListener(`cancel-${currentFileKey}`, () => {
|
2020-10-23 13:34:31 +03:00
|
|
|
XHR.abort();
|
|
|
|
});
|
2020-10-09 13:43:56 +03:00
|
|
|
|
2020-08-20 08:20:18 +03:00
|
|
|
XHR.open("post", path, true);
|
2020-10-23 13:34:31 +03:00
|
|
|
XHR.setRequestHeader("authorization", getCookie(Credentials.session.key));
|
2020-08-20 08:20:18 +03:00
|
|
|
XHR.onerror = (event) => {
|
2021-06-11 22:25:58 +03:00
|
|
|
Logging.error(event);
|
2020-10-09 05:32:30 +03:00
|
|
|
XHR.abort();
|
2020-08-20 08:20:18 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// NOTE(jim): UPLOADS ONLY.
|
|
|
|
XHR.upload.addEventListener(
|
|
|
|
"progress",
|
|
|
|
(event) => {
|
2020-08-21 10:07:39 +03:00
|
|
|
if (!context) {
|
2020-08-20 08:29:33 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-20 08:20:18 +03:00
|
|
|
if (event.lengthComputable) {
|
2021-06-11 22:25:58 +03:00
|
|
|
Logging.log("FILE UPLOAD PROGRESS", event);
|
2020-08-21 10:07:39 +03:00
|
|
|
context.setState({
|
2020-08-20 08:20:18 +03:00
|
|
|
fileLoading: {
|
2020-08-21 10:07:39 +03:00
|
|
|
...context.state.fileLoading,
|
2021-07-07 23:50:57 +03:00
|
|
|
[currentFileKey]: {
|
2020-08-20 08:20:18 +03:00
|
|
|
name: file.name,
|
|
|
|
loaded: event.loaded,
|
|
|
|
total: event.total,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
false
|
|
|
|
);
|
|
|
|
|
2021-07-07 23:50:57 +03:00
|
|
|
window.removeEventListener(`cancel-${currentFileKey}`, () => XHR.abort());
|
2020-10-02 07:24:10 +03:00
|
|
|
|
2020-08-20 08:20:18 +03:00
|
|
|
XHR.onloadend = (event) => {
|
2021-06-11 22:25:58 +03:00
|
|
|
Logging.log("FILE UPLOAD END", event);
|
2020-08-20 08:20:18 +03:00
|
|
|
try {
|
|
|
|
return resolve(JSON.parse(event.target.response));
|
|
|
|
} catch (e) {
|
|
|
|
return resolve({
|
|
|
|
error: "SERVER_UPLOAD_ERROR",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2020-09-19 22:36:58 +03:00
|
|
|
XHR.send(formData);
|
2020-08-20 08:20:18 +03:00
|
|
|
});
|
2021-07-07 23:50:57 +03:00
|
|
|
const resources = context.props.resources;
|
|
|
|
const storageDealRoute = resources?.storageDealUpload
|
|
|
|
? `${resources.storageDealUpload}/api/deal/`
|
|
|
|
: null;
|
|
|
|
const generalRoute = resources?.upload ? `${resources.upload}/api/data/` : null;
|
|
|
|
const zipUploadRoute = resources?.uploadZip ? `${resources.uploadZip}/api/data/zip/` : null;
|
2020-10-23 13:34:31 +03:00
|
|
|
|
2020-11-14 16:27:45 +03:00
|
|
|
if (!storageDealRoute || !generalRoute || !zipUploadRoute) {
|
2020-11-28 07:39:01 +03:00
|
|
|
Events.dispatchMessage({ message: "We could not find our upload server." });
|
2020-10-27 21:54:57 +03:00
|
|
|
|
|
|
|
return {
|
|
|
|
decorator: "NO_UPLOAD_RESOURCE_URI_ATTACHED",
|
|
|
|
error: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let res;
|
2020-11-15 19:41:35 +03:00
|
|
|
if (isZipFile && isUnityFile) {
|
2020-11-14 16:27:45 +03:00
|
|
|
res = await _privateUploadMethod(`${zipUploadRoute}${file.name}`, file);
|
|
|
|
} else if (bucketName && bucketName === STAGING_DEAL_BUCKET) {
|
2020-10-23 13:34:31 +03:00
|
|
|
res = await _privateUploadMethod(`${storageDealRoute}${file.name}`, file);
|
2020-09-23 14:17:56 +03:00
|
|
|
} else {
|
2020-10-23 13:34:31 +03:00
|
|
|
res = await _privateUploadMethod(`${generalRoute}${file.name}`, file);
|
2020-09-23 14:17:56 +03:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:53:54 +03:00
|
|
|
if (!res?.data || res.error) {
|
2020-08-21 10:07:39 +03:00
|
|
|
if (context) {
|
2020-12-12 06:57:54 +03:00
|
|
|
await context.setState({
|
2020-08-20 08:29:33 +03:00
|
|
|
fileLoading: {
|
2020-08-21 10:07:39 +03:00
|
|
|
...context.state.fileLoading,
|
2020-08-20 08:29:33 +03:00
|
|
|
[`${file.lastModified}-${file.name}`]: {
|
|
|
|
name: file.name,
|
|
|
|
failed: true,
|
|
|
|
},
|
2020-08-20 08:20:18 +03:00
|
|
|
},
|
2020-08-20 08:29:33 +03:00
|
|
|
});
|
|
|
|
}
|
2020-11-28 07:39:01 +03:00
|
|
|
Events.dispatchMessage({ message: "Some of your files could not be uploaded" });
|
2020-08-20 08:29:33 +03:00
|
|
|
|
2020-10-27 21:54:57 +03:00
|
|
|
return !res ? { decorator: "NO_RESPONSE_FROM_SERVER", error: true } : res;
|
2020-08-20 08:20:18 +03:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:53:54 +03:00
|
|
|
let item = res.data.data;
|
|
|
|
if (item.data.type.startsWith("image/")) {
|
|
|
|
let url = Strings.getURLfromCID(item.cid);
|
2020-11-02 23:27:11 +03:00
|
|
|
try {
|
|
|
|
let blurhash = await encodeImageToBlurhash(url);
|
2021-03-07 23:53:54 +03:00
|
|
|
item.data.blurhash = blurhash;
|
2020-11-02 23:27:11 +03:00
|
|
|
} catch (e) {
|
2021-06-11 22:25:58 +03:00
|
|
|
Logging.error(e);
|
2020-11-02 23:27:11 +03:00
|
|
|
}
|
2020-10-05 00:30:28 +03:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:53:54 +03:00
|
|
|
return item;
|
2020-08-20 08:20:18 +03:00
|
|
|
};
|
2021-07-07 23:50:57 +03:00
|
|
|
|
|
|
|
export const formatPastedImages = ({ clipboardItems }) => {
|
|
|
|
let files = [];
|
|
|
|
let fileLoading = {};
|
|
|
|
for (let i = 0; i < clipboardItems.length; i++) {
|
|
|
|
// Note(Amine): skip content if it's not an image
|
|
|
|
if (clipboardItems[i].type.indexOf("image") === -1) continue;
|
|
|
|
const file = clipboardItems[i].getAsFile();
|
|
|
|
files.push(file);
|
|
|
|
fileLoading[`${file.lastModified}-${file.name}`] = {
|
|
|
|
name: file.name,
|
|
|
|
loaded: 0,
|
|
|
|
total: file.size,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return { fileLoading, toUpload: files };
|
|
|
|
};
|
|
|
|
|
|
|
|
export const formatDroppedFiles = async ({ dataTransfer }) => {
|
|
|
|
// NOTE(jim): If this is true, then drag and drop came from a slate object.
|
|
|
|
const data = dataTransfer.getData("slate-object-drag-data");
|
|
|
|
if (data) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const files = [];
|
|
|
|
let fileLoading = {};
|
|
|
|
if (dataTransfer.items && dataTransfer.items.length) {
|
|
|
|
for (var i = 0; i < dataTransfer.items.length; i++) {
|
|
|
|
const data = dataTransfer.items[i];
|
|
|
|
|
|
|
|
let file = null;
|
|
|
|
if (data.kind === "file") {
|
|
|
|
file = data.getAsFile();
|
|
|
|
} else if (data.kind == "string" && data.type == "text/uri-list") {
|
|
|
|
try {
|
|
|
|
const dataAsString = new Promise((resolve, reject) =>
|
|
|
|
data.getAsString((d) => resolve(d))
|
|
|
|
);
|
|
|
|
const resp = await fetch(await dataAsString);
|
|
|
|
const blob = resp.blob();
|
|
|
|
|
|
|
|
file = new File(blob, `data-${uuid()}`);
|
|
|
|
file.name = `data-${uuid()}`;
|
|
|
|
} catch (e) {
|
|
|
|
Events.dispatchMessage({
|
|
|
|
message: "File type not supported. Please try a different file",
|
|
|
|
});
|
|
|
|
|
|
|
|
return { error: true };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
files.push(file);
|
|
|
|
fileLoading[`${file.lastModified}-${file.name}`] = {
|
|
|
|
name: file.name,
|
|
|
|
loaded: 0,
|
|
|
|
total: file.size,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!files.length) {
|
|
|
|
Events.dispatchMessage({ message: "File type not supported. Please try a different file" });
|
|
|
|
}
|
|
|
|
|
|
|
|
return { fileLoading, files, numFailed: dataTransfer.items.length - files.length };
|
|
|
|
};
|
|
|
|
|
|
|
|
export const formatUploadedFiles = ({ files }) => {
|
|
|
|
let toUpload = [];
|
|
|
|
let fileLoading = {};
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
let file = files[i];
|
|
|
|
|
|
|
|
if (!file) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
toUpload.push(file);
|
|
|
|
fileLoading[fileKey(file)] = {
|
|
|
|
name: file.name,
|
|
|
|
loaded: 0,
|
|
|
|
total: file.size,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!toUpload.length) {
|
|
|
|
Events.dispatchMessage({ message: "We could not find any files to upload." });
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return { toUpload, fileLoading, numFailed: files.length - toUpload.length };
|
|
|
|
};
|