Skip to content

Commit

Permalink
feat(Assets): New asset search UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Kruptein authored Dec 29, 2024
1 parent 7127683 commit 30fecb2
Show file tree
Hide file tree
Showing 69 changed files with 1,946 additions and 1,155 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ tech changes will usually be stripped from release notes for the public
- Notes can now be popped out to a separate window
- NoteManager:
- Added a button to clear the current search
- In-Game Assets UI:
- Option to search through assets
- Option to add folder shortcuts per campaign
- These allow quicker navigation to frequently used folders
- A "All assets" shortcut is always available
- [server] Assets:
- limits:
- Added limits to the total size of assets a user can upload and the size of a single asset
Expand All @@ -44,6 +49,11 @@ tech changes will usually be stripped from release notes for the public
- The images shown in the asset manager will now use the thumbnail of the asset if available
- This should reduce load times and improve general performance
- This also applies to the preview when hovering over assets in the in-game assets sidebar
- Remove initiated from the context menu now removes the entire selection
- Context menu retains selection unless an item not in the current selection is clicked
- In-game assets:
- Sidebar is removed and replaced with a new Assets dialog similar to notes
- The new UI has almost full compatibility with the assets in the dashboard
- Notes:
- Add filtering option 'All' to note manager to show both global and local notes
- Note popouts for clients without edit access now show 'view source' instead of 'edit'
Expand Down
6 changes: 3 additions & 3 deletions client/src/apiTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AssetId } from "./assetManager/models";
import type { AssetId } from "./assets/models";
import type { GlobalId } from "./core/id";
import type { LayerName } from "./game/models/floor";
import type { Role } from "./game/models/role";
Expand All @@ -23,8 +23,8 @@ export interface ApiAsset {
id: AssetId;
name: string;
owner: string;
fileHash?: string;
children?: ApiAsset[];
fileHash: string | null;
children: ApiAsset[] | null;
shares: ApiAssetShare[];
}
export interface ApiAssetShare {
Expand Down
108 changes: 0 additions & 108 deletions client/src/assetManager/AssetContext.vue

This file was deleted.

11 changes: 0 additions & 11 deletions client/src/assetManager/context.ts

This file was deleted.

12 changes: 10 additions & 2 deletions client/src/assetManager/emits.ts → client/src/assets/emits.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
ApiAssetCreateFolder,
ApiAssetCreateShare,
ApiAssetFolder,
ApiAssetInodeMove,
ApiAssetRemoveShare,
ApiAssetRename,
Expand All @@ -15,12 +16,19 @@ function wrapSocket<T>(event: string): (data: T) => void {
};
}

export const sendFolderGet = wrapSocket<AssetId | undefined>("Folder.Get");
export const sendFolderGetByPath = wrapSocket<string>("Folder.GetByPath");
function wrapSocketWithAck<T, Y>(event: string): (data: T) => Promise<Y> {
return async (data: T): Promise<Y> => {
return (await socket.emitWithAck(event, data)) as Y;
};
}

export const getFolder = wrapSocketWithAck<AssetId | undefined, ApiAssetFolder>("Folder.Get");
export const getFolderByPath = wrapSocketWithAck<string, ApiAssetFolder>("Folder.GetByPath");
export const sendInodeMove = wrapSocket<ApiAssetInodeMove>("Inode.Move");
export const sendAssetRename = wrapSocket<ApiAssetRename>("Asset.Rename");
export const sendAssetRemove = wrapSocket<AssetId>("Asset.Remove");
export const sendCreateFolder = wrapSocket<ApiAssetCreateFolder>("Folder.Create");
export const sendRemoveShare = wrapSocket<ApiAssetRemoveShare>("Asset.Share.Remove");
export const sendEditShareRight = wrapSocket<ApiAssetCreateShare>("Asset.Share.Edit");
export const sendCreateShare = wrapSocket<ApiAssetCreateShare>("Asset.Share.Create");
export const getFolderPath = wrapSocketWithAck<AssetId, { id: AssetId; name: string }[]>("Asset.FolderPath");
31 changes: 3 additions & 28 deletions client/src/assetManager/events.ts → client/src/assets/events.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
import { useToast } from "vue-toastification";

import type { ApiAssetAdd, ApiAssetCreateShare, ApiAssetFolder, ApiAssetRemoveShare } from "../apiTypes";
import { baseAdjust } from "../core/http";
import { router } from "../router";
import type { ApiAssetAdd, ApiAssetCreateShare, ApiAssetRemoveShare } from "../apiTypes";
import { coreStore } from "../store/core";

import { sendFolderGet } from "./emits";
import type { AssetId } from "./models";
import { socket } from "./socket";
import { assetState } from "./state";

import { assetSystem } from ".";

let disConnected = false;

const toast = useToast();

socket.on("connect", () => {
console.log("[Assets] connected");
if (disConnected) sendFolderGet(assetState.currentFolder.value);
});

socket.on("disconnect", () => {
console.log("[Assets] disconnected");
disConnected = true;
});

socket.on("redirect", (destination: string) => {
Expand All @@ -39,20 +32,6 @@ socket.on("Folder.Root.Set", (root: AssetId) => {
assetSystem.setRoot(root);
});

socket.on("Folder.Set", async (data: ApiAssetFolder) => {
assetSystem.clear();
assetSystem.setFolderData(data.folder.id, data.folder);
assetState.mutableReactive.sharedParent = data.sharedParent;
assetState.mutableReactive.sharedRight = data.sharedRight;
if (!assetState.readonly.modalActive) {
if (data.path) assetSystem.setPath(data.path);
const path = `/assets${assetState.currentFilePath.value}/`;
if (path !== router.currentRoute.value.path) {
await router.push({ path });
}
}
});

socket.on("Asset.Add", (data: ApiAssetAdd) => {
assetSystem.addAsset(data.asset, data.parent);
});
Expand All @@ -62,13 +41,9 @@ socket.on("Asset.Upload.Finish", (data: ApiAssetAdd) => {
assetSystem.resolveUpload(data.asset.name);
});

socket.on("Asset.Export.Finish", (uuid: string) => {
window.open(baseAdjust(`/static/temp/${uuid}.paa`));
});

socket.on("Asset.Import.Finish", (name: string) => {
socket.on("Asset.Import.Finish", async (name: string) => {
assetSystem.resolveUpload(name);
sendFolderGet(assetState.currentFolder.value);
await assetSystem.loadFolder(assetState.currentFolder.value);
});

socket.on("Asset.Share.Created", (data: ApiAssetCreateShare) => {
Expand Down
60 changes: 51 additions & 9 deletions client/src/assetManager/index.ts → client/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useToast } from "vue-toastification";

import type { ApiAsset, ApiAssetUpload } from "../apiTypes";
import { registerSystem, type System } from "../core/systems";
import type { SystemClearReason } from "../core/systems/models";
import { callbackProvider, uuidv4 } from "../core/utils";
import { router } from "../router";

import { sendAssetRemove, sendAssetRename, sendFolderGet, sendInodeMove } from "./emits";
import { sendAssetRemove, sendAssetRename, getFolder, sendInodeMove, getFolderPath, getFolderByPath } from "./emits";
import type { AssetId } from "./models";
import { socket } from "./socket";
import { assetState } from "./state";
Expand All @@ -15,12 +17,21 @@ const toast = useToast();

const { raw, mutableReactive: $ } = assetState;

class AssetSystem {
class AssetSystem implements System {
rootCallback = callbackProvider();

clear(): void {
clearLocal(): void {
$.folders = [];
$.files = [];
$.loadingFolder = false;
}

clear(reason: SystemClearReason): void {
if (reason === "logging-out") {
this.clearLocal();
$.idMap.clear();
$.folderPath = [];
}
}

clearFolderPath(): void {
Expand Down Expand Up @@ -55,7 +66,8 @@ class AssetSystem {
sendInodeMove({ inode, target: targetFolder });
}

changeDirectory(targetFolder: AssetId | "POP"): void {
async changeDirectory(targetFolder: AssetId | "POP"): Promise<void> {
$.loadingFolder = true;
if (targetFolder === "POP") {
$.folderPath.pop();
} else if (targetFolder === raw.root) {
Expand All @@ -64,10 +76,28 @@ class AssetSystem {
while (assetState.currentFolder.value !== targetFolder) $.folderPath.pop();
} else {
const asset = raw.idMap.get(targetFolder);
if (asset !== undefined) $.folderPath.push({ id: targetFolder, name: asset.name });
if (asset !== undefined) {
if (raw.root && ($.idMap.get(raw.root)?.children?.some((c) => c.id === targetFolder) ?? false)) {
$.folderPath = [{ id: targetFolder, name: asset.name }];
} else {
const path = await getFolderPath(targetFolder);
$.folderPath = path.slice(1);
}
}
}
this.clearSelected();
sendFolderGet(assetState.currentFolder.value);
await this.loadFolder(assetState.currentFolder.value);
}

async loadFolder(folder: AssetId | string | undefined): Promise<void> {
if (folder === undefined) return;

const data = typeof folder === "string" ? await getFolderByPath(folder) : await getFolder(folder);
this.clearLocal();
this.setFolderData(data.folder.id, data.folder);
if (data.path) assetSystem.setPath(data.path);
assetState.mutableReactive.sharedParent = data.sharedParent;
assetState.mutableReactive.sharedRight = data.sharedRight;
}

setFolderData(folder: AssetId, data: ApiAsset): void {
Expand All @@ -78,6 +108,7 @@ class AssetSystem {
this.addAsset(child);
}
}
$.loadingFolder = false;
}

// SELECTED
Expand Down Expand Up @@ -161,19 +192,29 @@ class AssetSystem {
await assetSystem.rootCallback.wait();
}

const limit = await socket.emitWithAck("Asset.Upload.Limit") as { single: number; total: number; used: number };
const limit = (await socket.emitWithAck("Asset.Upload.Limit")) as {
single: number;
total: number;
used: number;
};

// First check limits
let totalSize = 0;
for (const file of fls) {
totalSize += file.size;
if (limit.single > 0 && file.size > limit.single) {
toast.error(`File ${file.name} is too large. Max size is ${limit.single} bytes. Contact the server admin if you need to upload larger files.`, { timeout: 0 });
toast.error(
`File ${file.name} is too large. Max size is ${limit.single} bytes. Contact the server admin if you need to upload larger files.`,
{ timeout: 0 },
);
return [];
}
if (limit.total > 0 && totalSize > limit.total - limit.used) {
const remaining = Math.max(0, limit.total - limit.used);
toast.error(`Total size of files is too large. You have ${remaining} bytes remaining and attempted to upload ${totalSize} bytes. Contact the server admin if you need to upload larger files.`, { timeout: 0 });
toast.error(
`Total size of files is too large. You have ${remaining} bytes remaining and attempted to upload ${totalSize} bytes. Contact the server admin if you need to upload larger files.`,
{ timeout: 0 },
);
return [];
}
}
Expand Down Expand Up @@ -240,3 +281,4 @@ class AssetSystem {
export const assetSystem = new AssetSystem();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(window as any).assetSystem = assetSystem;
registerSystem("asset", assetSystem, false, assetState);
File renamed without changes.
Loading

0 comments on commit 30fecb2

Please sign in to comment.