Skip to content

Commit

Permalink
Add file to upload
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmi committed Aug 6, 2023
1 parent 0c4e8f5 commit d6be060
Show file tree
Hide file tree
Showing 20 changed files with 319 additions and 95 deletions.
1 change: 1 addition & 0 deletions backend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const main = async ({ store, schedules, functions, hooks }) => {
await store.createOrUpdateBox("room", { security: "public" });
await store.createOrUpdateBox("session", { security: "public" });
await store.createOrUpdateBox("user", { security: "private" });
await store.createOrUpdateBox("files", { security: "private" });

// Add schedules
schedules["daily"] = [deleteOldSession(store)];
Expand Down
7 changes: 2 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"lodash.debounce": "^4.0.8",
"marked": "^4.0.12",
"memoizee": "^0.4.14",
"mime-types": "^2.1.35",
"nanoid": "^3.3.0",
"openvidu-browser": "^2.26.0",
"p-limit": "^4.0.0",
Expand Down
2 changes: 0 additions & 2 deletions src/gameComponents/Canvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,6 @@ const NoCanvas = ({ layers, width, height }) => {
}
}, [actions, firstImage.url]);

console.log(firstImage.url, state.status, state.error);

if (state.status === "error") {
return <Error />;
}
Expand Down
10 changes: 10 additions & 0 deletions src/gameComponents/Image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ const Template = createItemTemplate({
},
name: i18n.t("Image"),
template: {},
async mapMedia(item, fn) {
const result = await Promise.all([
fn(item.content),
fn(item.backContent),
fn(item.overlay?.content),
]);
item.content = result[0];
item.backContent = result[1];
item.overlay = { content: result[2] };
},
});

export default Template;
13 changes: 5 additions & 8 deletions src/gameComponents/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const resize = (prop) => ({ width, actualWidth, prevState }) => {
export const sizeResize = resize("size");
export const radiusResize = resize("radius");

const defaultTemplate = {
const defaultTemplate = () => ({
resizeDirections: {
w: true,
h: true,
Expand All @@ -25,15 +25,12 @@ const defaultTemplate = {
applyDefault(item) {
return item;
},
};
// eslint-disable-next-line no-unused-vars
mapMedia(item, fn) {},
});

export const createItemTemplate = (template) => {
return Object.assign(
{},
JSON.parse(JSON.stringify(defaultTemplate)),
template,
{ uid: uid() }
);
return Object.assign({}, defaultTemplate(), template, { uid: uid() });
};

export default createItemTemplate;
7 changes: 7 additions & 0 deletions src/games/testGame.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ const genGame = () => {
color: "#D00022",
size: 80,
},
{
type: "image",
content: "/game_assets/JC.jpg",
backContent: "/game_assets/Red_back.jpg",
overlay: { content: "/game_assets/overlay.png" },
width: 100,
},
],
board: {
size: 1000,
Expand Down
8 changes: 6 additions & 2 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@
"Error while loading the module, please report the error...": "Error while loading the module, please report the error...",
"Drag'n'drop a vassal module here": "Drag'n'drop a vassal module here",
"Load a Vassal module": "Load a Vassal module",
"Load a Vassal module?": "Load a Vassal module?",
"Load a Vassal module?": "Load a Vassal module? (experimental)",
"Load Vassal module": "Load Vassal module",
"Import Vassal module": "Import Vassal module",
"Secondary color": "Secondary color",
Expand All @@ -351,5 +351,9 @@
"Don't forget to save your game using the save button located in the middle of the left sidebar.\n\n": "Don't forget to save your game using the save button located in the middle of the left sidebar.\n\n",
"Have fun exploring the possibilities and enjoy the process of game creation!": "Have fun exploring the possibilities and enjoy the process of game creation!",
"Login failed. Please try again.": "Login failed. Please try again.",
"Home": "Home"
"Home": "Home",
"Die": "Die",
"Image die": "Image die",
"Include files": "Include files",
"An error occurred. Try again!": "An error occurred. Try again!"
}
10 changes: 7 additions & 3 deletions src/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@
"Next": "Suivant",
"Previous": "Précédent",
"Error while loading the module, please report the error...": "Une erreur est survenue pendant le chargement de ce module, merci de nous prévenir...",
"Drag'n'drop a vassal module here": "Glisser&Déposer un module Vassal ici",
"Drag'n'drop a vassal module here": "Glisser & Déposer un module Vassal ici",
"Load a Vassal module": "Charger un module Vassal",
"Load a Vassal module?": "Charger un module Vassal ?",
"Load a Vassal module?": "Charger un module Vassal ? (Expérimental)",
"Load Vassal module": "Charger le module Vassal",
"Import Vassal module": "Importer le module Vassal",
"Secondary color": "Couleur secondaire",
Expand All @@ -351,5 +351,9 @@
"Don't forget to save your game using the save button located in the middle of the left sidebar.\n\n": "N'oubliez pas de sauvegarder votre jeu en utilisant le bouton de sauvegarde situé au milieu de la barre latérale gauche.\n\n",
"Have fun exploring the possibilities and enjoy the process of game creation!": "Amusez-vous à explorer les possibilités !",
"Login failed. Please try again.": "Échec de l'authentification, veuillez réessayer...",
"Home": "Accueil"
"Home": "Accueil",
"Die": "",
"Image die": "Dé image",
"Include files": "Inclure les fichiers",
"An error occurred. Try again!": "Une erreur est survenue. Veuillez réessayer !"
}
24 changes: 19 additions & 5 deletions src/utils/image.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { itemTemplates } from "../gameComponents";

const imageCache = {};

export const getImageWithRetry = async (url, retry = 0) => {
console.log("called with", url, retry);
if (!url) {
return null;
}
Expand All @@ -10,15 +11,12 @@ export const getImageWithRetry = async (url, retry = 0) => {
img.onload = () => {
resolve(img);
};
img.onerror = (e) => {
console.log("onError", url, e);
img.onerror = () => {
if (retry < 3) {
console.log("call retry", retry);
getImageWithRetry(url, retry + 1)
.then(resolve)
.catch(reject);
} else {
console.log("reject");
reject(new Error(`Failed to load: <${url}>`));
}
};
Expand All @@ -35,3 +33,19 @@ export const getImage = async (url) => {
}
return imageCache[url];
};

export const uploadItemMedia = (onFile) => async (item) => {
const template = itemTemplates[item.type];
const itemCloned = JSON.parse(JSON.stringify(item));

await template.mapMedia(itemCloned, async (media) => {
if (typeof media === "object" && media.file) {
const imageURL = await onFile(media.file);
return { type: "local", content: imageURL };
} else {
return media;
}
});

return itemCloned;
};
11 changes: 11 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,14 @@ export const playAudio = (url, volume = 1) => {
console.log("Fail to play audio", e);
}
};

export const triggerFileDownload = (url, filename) => {
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();

// Clean up and remove the link
link.parentNode.removeChild(link);
};
16 changes: 16 additions & 0 deletions src/utils/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,19 @@ export const getItemElement = (id) => {
}
return elem;
};

export const availableItemVisitor = async (items, callback) => {
return await Promise.all(
items.map(async (node) => {
if (node.items) {
return {
...node,
items: await availableItemVisitor(node.items, callback),
};
} else {
// It's an element
return await callback(node);
}
})
);
};
1 change: 0 additions & 1 deletion src/utils/vassal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// import { ZipReader, BlobReader, TextWriter, BlobWriter } from "@zip.js/zip.js";
import X2JS from "x2js";
import pLimit from "p-limit";
import JSZip from "jszip";
Expand Down
119 changes: 73 additions & 46 deletions src/views/BoardView/DownloadLink.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,90 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { FiDownload } from "react-icons/fi";

const generateDownloadURI = (data) => {
return (
"data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data))
);
import { itemTemplates } from "../../gameComponents";
import { media2Url } from "../../mediaLibrary";
import { getImage } from "../../utils/image";
import { uid } from "../../utils";
import mime from "mime-types";
import { availableItemVisitor } from "../../utils/item";
import { triggerFileDownload } from "../../utils";

const imageAsBlob = async (image) => {
const response = await fetch(image.src);
return await response.blob();
};

export const DownloadLink = ({ getData = () => {} }) => {
const { t } = useTranslation();
const addImageToZipFromItem = (zip) => async (item) => {
const template = itemTemplates[item.type];
const itemCloned = JSON.parse(JSON.stringify(item));

const [downloadURI, setDownloadURI] = React.useState("");
const [date, setDate] = React.useState(Date.now());
const [genOnce, setGenOnce] = React.useState(false);
await template.mapMedia(itemCloned, async (media) => {
const url = media2Url(media);
if (url) {
const image = await getImage(url);
const blob = await imageAsBlob(image);
const extension = mime.extension(blob.type);
const filename = `${uid()}.${extension}`;

const updateSaveLink = React.useCallback(async () => {
const data = await getData();
if (data.items.length) {
setDownloadURI(generateDownloadURI(data));
setDate(Date.now());
setGenOnce(true);
zip.file(filename, blob);

return { file: filename };
}
}, [getData]);
return media;
});

return itemCloned;
};

React.useEffect(() => {
let mounted = true;
const buildZipFile = async (data, withFile = false) => {
const JSZip = (await import("jszip")).default;
const zip = new JSZip();
const addImage = addImageToZipFromItem(zip);

const cancel = setInterval(() => {
if (!mounted) return;
updateSaveLink();
}, 2000);
if (withFile) {
data.items = await Promise.all(
data.items.map(async (item) => await addImage(item))
);
data.availableItems = await availableItemVisitor(
data.availableItems,
addImage
);
}

updateSaveLink();
zip.file("content.json", JSON.stringify(data));
const base64 = await zip.generateAsync({ type: "base64" });
const url = "data:application/zip;base64," + base64;

return () => {
mounted = false;
setGenOnce(false);
clearInterval(cancel);
};
}, [updateSaveLink]);
return url;
};

export const DownloadLink = ({ getData = () => {}, withFile = false }) => {
const { t } = useTranslation();

const [generating, setGenerating] = React.useState(false);

const triggerDownload = React.useCallback(async () => {
const data = await getData();

if (data.items.length) {
setGenerating(true);
try {
const url = await buildZipFile(
JSON.parse(JSON.stringify(data)),
withFile
);
triggerFileDownload(url, `airboardgame_${Date.now()}.zip`);
} finally {
setGenerating(false);
}
}
}, [getData, withFile]);

return (
<>
{genOnce && (
<a
className="button success icon"
href={downloadURI}
download={`airboardgame_${date}.json`}
>
{t("Export")}
<FiDownload size="20" color="#f9fbfa" alt="Download" />
</a>
)}
{!genOnce && (
<button className="button" disabled>
{t("Generating export")}...
</button>
)}
</>
<button className="button success icon" onClick={triggerDownload}>
{!generating ? t("Export") : t("Generating export")}
<FiDownload size="20" color="#f9fbfa" alt="Download" />
</button>
);
};

Expand Down
Loading

1 comment on commit d6be060

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.