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