slate/common/file-utilities.js

235 lines
7.0 KiB
JavaScript

import * as Actions from "~/common/actions";
import * as Credentials from "~/common/credentials";
import * as Strings from "~/common/strings";
import * as Validations from "~/common/validations";
import * as Events from "~/common/custom-events";
import * as Logging from "~/common/logging";
import * as Environment from "~/common/environment";
import * as Constants from "~/common/constants";
import { encode, isBlurhashValid } from "blurhash";
import { v4 as uuid } from "uuid";
export const fileKey = ({ lastModified, name }) => `${lastModified}-${name}`;
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) => {
let ratio = Math.min(100 / image.height, 100 / image.width);
image.height = image.height * ratio;
image.width = image.width * ratio;
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext("2d");
context.scale(ratio, ratio);
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);
};
// 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];
};
export const uploadLink = async ({ url, slate, uploadAbort }) => {
const abortController = new AbortController();
if (uploadAbort) uploadAbort.abort = abortController.abort.bind(abortController);
let createResponse = await Actions.createLink({ url, slate }, { signal: abortController.signal });
return createResponse;
};
export const saveCopy = async ({ file, uploadAbort }) => {
const abortController = new AbortController();
if (uploadAbort) uploadAbort.abort = abortController.abort.bind(abortController);
return await Actions.saveCopy({ files: [file] }, { signal: abortController.signal });
};
export const upload = async ({ file, onProgress, bucketName, uploadAbort }) => {
let formData = new FormData();
const HEIC2ANY = require("heic2any");
// NOTE(jim): You must provide a file from an type="file" input field.
if (!file) {
return null;
}
const isZipFile =
file.type.startsWith("application/zip") || file.type.startsWith("application/x-zip-compressed");
const isUnityFile = await Validations.isUnityFile(file);
// 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,
}); //TODO(martina): figure out how to cancel an await if upload has been cancelled
formData.append("data", converted);
} else {
formData.append("data", file);
}
const _privateUploadMethod = (path, file) =>
new Promise((resolve) => {
const XHR = new XMLHttpRequest();
if (uploadAbort) uploadAbort.abort = XHR.abort.bind(XHR);
XHR.open("post", path, true);
XHR.setRequestHeader("authorization", getCookie(Credentials.session.key));
XHR.onerror = (event) => {
Logging.error(event);
XHR.abort();
};
// NOTE(jim): UPLOADS ONLY.
XHR.upload.addEventListener(
"progress",
(event) => {
if (event.lengthComputable) {
Logging.log("FILE UPLOAD PROGRESS", event);
if (onProgress) onProgress(event);
}
},
false
);
XHR.addEventListener("abort", () => {
resolve({ aborted: true });
});
XHR.onloadend = (event) => {
Logging.log("FILE UPLOAD END", event);
try {
return resolve(JSON.parse(event.target.response));
} catch (e) {
return resolve({
error: "SERVER_UPLOAD_ERROR",
});
}
};
XHR.send(formData);
});
const storageDealRoute = `${Environment.URI_SHOVEL}/api/deal/`;
const generalRoute = `${Environment.URI_SHOVEL}/api/data/`;
const zipUploadRoute = `${Environment.URI_SHOVEL}/api/data/zip/`;
if (!storageDealRoute || !generalRoute || !zipUploadRoute) {
Events.dispatchMessage({ message: "We could not find our upload server." });
return {
decorator: "NO_UPLOAD_RESOURCE_URI_ATTACHED",
error: true,
};
}
let res;
if (isZipFile && isUnityFile) {
res = await _privateUploadMethod(`${zipUploadRoute}${file.name}`, file);
} else if (bucketName && bucketName === Constants.textile.dealsBucket) {
res = await _privateUploadMethod(`${storageDealRoute}${file.name}`, file);
} else {
res = await _privateUploadMethod(`${generalRoute}${file.name}`, file);
}
if (!res || res.error || res.aborted) {
return res;
}
let item = res.data.data;
if (Validations.isPreviewableImage(item.type)) {
let url = Strings.getURLfromCID(item.cid);
try {
let blurhash = await encodeImageToBlurhash(url);
if (isBlurhashValid(blurhash).result) {
item.blurhash = blurhash;
}
} catch (e) {
Logging.error(e);
}
}
return item;
};
export const formatPastedImages = ({ clipboardItems }) => {
let files = [];
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);
}
return { 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 = [];
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) => 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);
}
}
return { files };
};
export const formatUploadedFiles = ({ files }) => {
let toUpload = [];
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (!file) {
continue;
}
toUpload.push(file);
}
return { files: toUpload };
};