make assets be stored in indexeddb
This commit is contained in:
parent
7b096fe79a
commit
05f1c889ee
@ -1,4 +1,5 @@
|
||||
import { promptUpload } from "./prompt_upload.js";
|
||||
import { AssetStore } from "./asset_store.js";
|
||||
|
||||
export class AssetEditor {
|
||||
constructor(rootEl) {
|
||||
@ -6,7 +7,8 @@ export class AssetEditor {
|
||||
this.editor = rootEl.querySelector("#asset-editor");
|
||||
this.toggleButton = rootEl.querySelector("#toggle-asset-editor-button");
|
||||
this.container = rootEl;
|
||||
this.previewedId = null;
|
||||
this.previewedName = null;
|
||||
|
||||
if (localStorage.getItem("asset-editor-expanded")) {
|
||||
this.editor.style.display = "block";
|
||||
this.container.style.flexGrow = "1";
|
||||
@ -18,61 +20,76 @@ export class AssetEditor {
|
||||
this.preview.title = rootEl.querySelector("#asset-editor-preview-title");
|
||||
this.preview.image = rootEl.querySelector("#asset-editor-preview-image");
|
||||
this.preview.snippet = rootEl.querySelector("#asset-editor-preview-snippet");
|
||||
this.assets = [];
|
||||
|
||||
rootEl.querySelector("#asset-editor-upload-button").addEventListener("click", () => {
|
||||
this.promptUpload();
|
||||
});
|
||||
|
||||
this.renderList();
|
||||
AssetStore.load("assetdb").then((store) => {
|
||||
this.store = store;
|
||||
|
||||
this.renderList();
|
||||
});
|
||||
}
|
||||
|
||||
importAssets(assets) {
|
||||
this.assets = [];
|
||||
getAssets() {
|
||||
return this.store.getAll();
|
||||
}
|
||||
|
||||
async importAssets(assets) {
|
||||
await this.clearAssets();
|
||||
|
||||
for (const asset of assets) {
|
||||
this.addAsset({ name: asset.name, bytes: asset.bytes, mime: asset.mime });
|
||||
await this.addAsset({ name: asset.name, file: asset.file });
|
||||
}
|
||||
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
async promptUpload() {
|
||||
for (const file of await promptUpload("image/*", true)) {
|
||||
const rootName = file.name;
|
||||
|
||||
let fullName = file.name;
|
||||
for (let i = 0; this.assets.some((x) => x.name === fullName); ++i) {
|
||||
for (let i = 0; await this.store.get(fullName); i++) {
|
||||
const extensionIdx = rootName.split("").findLastIndex((x) => x === ".");
|
||||
|
||||
let name = rootName;
|
||||
let extension = "";
|
||||
|
||||
if (extensionIdx !== -1) {
|
||||
name = rootName.slice(0, extensionIdx);
|
||||
extension = rootName.slice(extensionIdx);
|
||||
}
|
||||
|
||||
fullName = `${name}-${i}${extension}`;
|
||||
}
|
||||
this.addAsset({
|
||||
name: fullName,
|
||||
mime: file.type,
|
||||
bytes: await fetch(URL.createObjectURL(file)).then((x) => x.bytes()),
|
||||
});
|
||||
|
||||
await this.addAsset({ name: fullName, file });
|
||||
}
|
||||
}
|
||||
|
||||
addAsset({ name, bytes, mime }) {
|
||||
const id = Math.round(Math.random() * 1e6);
|
||||
this.assets.push({ id, bytes, name, mime });
|
||||
async clearAssets() {
|
||||
await this.store.clear();
|
||||
}
|
||||
|
||||
async addAsset({ name, file }) {
|
||||
await this.store.add(name, file);
|
||||
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
deleteAsset(id) {
|
||||
this.assets = this.assets.filter((x) => x.id !== id);
|
||||
async deleteAsset(name) {
|
||||
await this.store.delete(name);
|
||||
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
setPreview(id) {
|
||||
this.previewedId = id;
|
||||
const asset = this.assets.find((x) => x.id === id);
|
||||
async setPreview(asset) {
|
||||
this.previewedName = asset.name;
|
||||
|
||||
this.preview.title.textContent = asset.name;
|
||||
this.preview.image.src = `data:${asset.mime};base64,${asset.bytes.toBase64()}`;
|
||||
this.preview.image.src = URL.createObjectURL(asset.file);
|
||||
|
||||
const sanitizedName = asset.name.replace(/</g, "<").replace(/&/g, "&");
|
||||
this.preview.snippet.innerHTML = "lib.drawSprite(<br>" +
|
||||
@ -88,35 +105,47 @@ export class AssetEditor {
|
||||
this.preview.image.src = "";
|
||||
}
|
||||
|
||||
renderList() {
|
||||
const children = this.assets
|
||||
async renderList() {
|
||||
const assets = await this.store.getAll();
|
||||
|
||||
const children = assets
|
||||
.map((asset) => {
|
||||
const listItem = document.createElement("li");
|
||||
listItem.classList.add("asset-editor-list-item");
|
||||
if (this.previewedId === asset.id) {
|
||||
|
||||
if (this.previewedName === asset.id) {
|
||||
listItem.setAttribute("previewed", true);
|
||||
}
|
||||
|
||||
listItem.addEventListener("click", () => {
|
||||
for (const li of this.list.querySelectorAll("li[previewed]")) {
|
||||
li.removeAttribute("previewed");
|
||||
}
|
||||
|
||||
listItem.setAttribute("previewed", true);
|
||||
this.setPreview(asset.id);
|
||||
|
||||
this.setPreview(asset);
|
||||
});
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.textContent = asset.name;
|
||||
listItem.append(name);
|
||||
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.innerHTML = "×";
|
||||
deleteButton.classList.add("delete-button");
|
||||
deleteButton.addEventListener("click", (event) => {
|
||||
deleteButton.addEventListener("click", async (event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (listItem.getAttribute("previewed")) {
|
||||
this.clearPreview();
|
||||
}
|
||||
this.deleteAsset(asset.id);
|
||||
|
||||
await this.deleteAsset(asset.name);
|
||||
});
|
||||
|
||||
listItem.append(deleteButton);
|
||||
|
||||
return listItem;
|
||||
});
|
||||
|
||||
|
@ -9,17 +9,35 @@ export class AssetProvider {
|
||||
|
||||
url(name) {
|
||||
const asset = this.assets.find((x) => x.name === name);
|
||||
|
||||
if (!asset) {
|
||||
throw new Error(`Asset with name '${name}' does not exist`);
|
||||
}
|
||||
return `data:${asset.mime};base64,${asset.bytes.toBase64()}`;
|
||||
|
||||
return URL.createObjectURL(asset.file);
|
||||
}
|
||||
|
||||
getAll() {
|
||||
fullUrl(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const asset = this.assets.find((x) => x.name === name);
|
||||
|
||||
if (!asset) {
|
||||
throw new Error(`Asset with name '${name}' does not exist`);
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(asset.file);
|
||||
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = () => reject(reader.error);
|
||||
});
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const result = {};
|
||||
|
||||
for (const asset of this.assets) {
|
||||
result[asset.name] = this.url(asset.name);
|
||||
result[asset.name] = await this.fullUrl(asset.name);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,6 +1,5 @@
|
||||
export class AssetStore {
|
||||
static #isInternalConstructing = false;
|
||||
static #idb = globalThis.indexedDB;
|
||||
|
||||
constructor(db) {
|
||||
if (!AssetStore.#isInternalConstructing) {
|
||||
@ -11,28 +10,66 @@ export class AssetStore {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
async add(name, mime, bytes) {
|
||||
doTransaction(callback) {
|
||||
const transaction = this.db.transaction(["asset"], "readwrite");
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
transaction.oncomplete = () => {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onerror = () => {
|
||||
reject();
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onerror = () => reject(transaction.error);
|
||||
|
||||
const objectStore = transaction.objectStore("asset");
|
||||
|
||||
objectStore.add({ name, mime, bytes });
|
||||
callback(objectStore);
|
||||
});
|
||||
}
|
||||
|
||||
request(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
return this.doTransaction(async (objectStore) => {
|
||||
await this.request(objectStore.clear());
|
||||
});
|
||||
}
|
||||
|
||||
async get(name) {
|
||||
let result;
|
||||
|
||||
await this.doTransaction(async (objectStore) => {
|
||||
result = await this.request(objectStore.get(name));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
let result = [];
|
||||
|
||||
await this.doTransaction(async (objectStore) => {
|
||||
result = await this.request(objectStore.getAll());
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
add(name, file) {
|
||||
return this.doTransaction(async (objectStore) => {
|
||||
await this.request(objectStore.add({ name, file }));
|
||||
});
|
||||
}
|
||||
|
||||
delete(name) {
|
||||
return this.doTransaction(async (objectStore) => {
|
||||
await this.request(objectStore.delete(name));
|
||||
});
|
||||
}
|
||||
|
||||
static async load(name) {
|
||||
/* remove when not developing db */
|
||||
this.#idb.deleteDatabase(name);
|
||||
const req = this.#idb.open(name);
|
||||
const req = indexedDB.open(name);
|
||||
const db = await new Promise((resolve, reject) => {
|
||||
req.onerror = () => {
|
||||
reject(req.error);
|
||||
|
@ -13,7 +13,7 @@ export class HtmlExporter {
|
||||
let lib = await (await fetch("./src/gamelib.js")).text();
|
||||
lib = minifyJs(lib);
|
||||
|
||||
const assets = this.assetProvider.getAll();
|
||||
const assets = await this.assetProvider.getAll();
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
|
12
src/index.js
12
src/index.js
@ -14,7 +14,6 @@ import { TextCompleter } from "./text_completer.js";
|
||||
import { ConsoleInput } from "./console_input.js";
|
||||
import { downloadFile, slugify } from "./utils.js";
|
||||
import { HtmlExporter } from "./html_exporter.js";
|
||||
import { AssetStore } from "./asset_store.js";
|
||||
|
||||
const editor = ace.edit("editor");
|
||||
editor.setTheme("ace/theme/gruvbox");
|
||||
@ -108,12 +107,15 @@ loadButton.onclick = async () => {
|
||||
editor.setValue(dec.decode(code.content));
|
||||
};
|
||||
|
||||
runButton.onclick = () => {
|
||||
runButton.onclick = async () => {
|
||||
const code = editor.getValue();
|
||||
|
||||
karlkoder.lib().assetProvider.injectAssets(assetEditor.assets);
|
||||
const assets = await assetEditor.getAssets();
|
||||
karlkoder.lib().assetProvider.injectAssets(assets);
|
||||
|
||||
codeRunner.setCode(code);
|
||||
codeRunner.toggle();
|
||||
|
||||
document.querySelector("canvas").focus();
|
||||
|
||||
if (codeRunner.isRunning) {
|
||||
@ -153,7 +155,3 @@ addEventListener("keydown", (ev) => {
|
||||
});
|
||||
|
||||
toggleAssetEditorButton.addEventListener("click", () => assetEditor.toggleEditor());
|
||||
|
||||
const assetStore = await AssetStore.load("assetdb");
|
||||
|
||||
await assetStore.add("test", "image/png", []);
|
||||
|
Loading…
x
Reference in New Issue
Block a user