Skip to content

Commit

Permalink
Add Custom Character Folder Image Feature Using Global Module Assets (#…
Browse files Browse the repository at this point in the history
…697)

# PR Checklist
- [ ] Have you checked if it works normally in all models? *Ignore this
if it doesn't use models.*
- [x] Have you checked if it works normally in all web, local, and node
hosted versions? If it doesn't, have you blocked it in those versions?
- [x] Have you added type definitions?

# Description
This PR adds a new feature that allows users to add custom images to
character folders. Instead of uploading images directly, this feature
utilizes the existing image pool from **the global module**. Users can
either select from existing images in the global module or upload new
images to the module.

The users can use a context menu on a folder to update its image.

## Why Use the Global Module?

The global module was chosen for several reasons:  
1. It allows the reuse of existing functions and code, minimizing the
need for new implementations.
2. Users can easily select existing images without re-uploading them.  
3. The global module provides reliable support for image upload and
deletion.
4. Images uploaded via the global module are accessible across all
platforms where the shared data is available.

## Note

To retrieve the list of assets from the global module, I used the
following code:

```typescript
let assetPaths: { [key: string]: { path: string } } = {};

const moduleAssets = getModuleAssets();
if (moduleAssets.length > 0) {
  for (const asset of moduleAssets) {
    const assetPath = await getFileSrc(asset[1]);
    assetPaths[asset[0].toLocaleLowerCase()] = {
      path: assetPath,
    };
  }
}
```

I reused the `parseAdditionalAssets` code from
`/src/ts/parser.svelte.ts` because it seemed simple and practical for
this feature. I thought about refactoring it into a shared function but
decided to keep it simple for now. I’m not sure if this is the best
approach, but it can always be changed later if needed.

---

If you are already working on a similar feature, find any issues with
this code, or feel this PR does not align with the project's direction,
I fully understand if this PR is not accepted.

Thank you!

---

One last note: With *transparent background images* and the existing
folder color functionality, users can differentiate categories even when
using the same image by applying different colors!
  • Loading branch information
kwaroran authored Jan 1, 2025
2 parents 0c5aab0 + 2730e40 commit e977158
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ export const languageEnglish = {
cancel: "Cancel",
renameFolder: "Rename Folder",
changeFolderColor: "Change Folder Color",
changeFolderImage: "Change Folder Image",
fullWordMatching: "Full Word Matching",
botSettingAtStart: "Bot Menu when Launch",
triggerStart: "On chat Send",
Expand Down
1 change: 1 addition & 0 deletions src/lang/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export const languageKorean = {
"cancel": "취소",
"renameFolder": "폴더 이름 변경하기",
"changeFolderColor": "폴더 색상 변경하기",
"changeFolderImage": "폴더 이미지 변경하기",
"fullWordMatching": "단어 단위 매칭",
"botSettingAtStart": "실행 시 봇 설정으로 시작하기",
"triggerStart": "채팅 보낼 시",
Expand Down
47 changes: 41 additions & 6 deletions src/lib/SideBars/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
import SidebarAvatar from "./SidebarAvatar.svelte";
import BaseRoundedButton from "../UI/BaseRoundedButton.svelte";
import { get } from "svelte/store";
import { getCharacterIndexObject } from "src/ts/util";
import { getCharacterIndexObject, selectSingleFile } from "src/ts/util";
import { v4 } from "uuid";
import { checkCharOrder } from "src/ts/globalApi.svelte";
import { checkCharOrder, getFileSrc, saveAsset } from "src/ts/globalApi.svelte";
import { alertInput, alertSelect } from "src/ts/alert";
import SideChatList from "./SideChatList.svelte";
import { ConnectionIsHost, ConnectionOpenStore, RoomIdStore } from "src/ts/sync/multiuser";
Expand All @@ -59,7 +59,7 @@
}
type sortTypeNormal = { type:'normal',img: string, index: number, name:string }
type sortType = sortTypeNormal|{type:'folder',folder:sortTypeNormal[],id:string, name:string, color:string}
type sortType = sortTypeNormal|{type:'folder',folder:sortTypeNormal[],id:string, name:string, color:string, img?:string}
let charImages: sortType[] = $state([]);
let IconRounded = $state(false)
let openFolders:string[] = $state([])
Expand Down Expand Up @@ -109,7 +109,8 @@
type: "folder",
id: folder.id,
name: folder.name,
color: folder.color
color: folder.color,
img: folder.img,
});
}
}
Expand Down Expand Up @@ -472,10 +473,10 @@
{:else if char.type === "folder"}
{#key char.color}
{#key char.name}
<SidebarAvatar src="slot" size="56" rounded={IconRounded} bordered name={char.name} color={char.color}
<SidebarAvatar src="slot" size="56" rounded={IconRounded} bordered name={char.name} color={char.color} backgroundimg={char.img}
oncontextmenu={async (e) => {
e.preventDefault()
const sel = parseInt(await alertSelect([language.renameFolder,language.changeFolderColor,language.cancel]))
const sel = parseInt(await alertSelect([language.renameFolder,language.changeFolderColor,language.changeFolderImage,language.cancel]))
if(sel === 0){
const v = await alertInput(language.changeFolderName)
const db = DBState.db
Expand All @@ -501,6 +502,40 @@
db.characterOrder[ind] = oder
setDatabase(db)
}
else if(sel === 2) {
const sel = parseInt(await alertSelect(['Reset to Default Image', 'Select Image File']))
const db = DBState.db
const oder = db.characterOrder[ind]
if(typeof(oder) === 'string'){
return
}

switch (sel) {
case 0:
oder.imgFile = null
oder.img = ''
break;

case 1:
const folderImage = await selectSingleFile([
'png',
'jpg',
'webp',
])

if(!folderImage) {
return
}

const folderImageData = await saveAsset(folderImage.data)

oder.imgFile = folderImageData
oder.img = await getFileSrc(folderImageData)
db.characterOrder[ind] = oder
setDatabase(db)
break;
}
}
}}
onClick={() => {
if(char.type !== 'folder'){
Expand Down
11 changes: 10 additions & 1 deletion src/lib/SideBars/SidebarAvatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
onClick?: any;
bordered?: boolean;
color?: string;
backgroundimg?: string;
children?: import('svelte').Snippet;
oncontextmenu?: (event: MouseEvent & {
currentTarget: EventTarget & HTMLDivElement;
Expand All @@ -23,6 +24,7 @@
onClick = () => {},
bordered = false,
color = '',
backgroundimg = '',
children,
oncontextmenu
}: Props = $props();
Expand Down Expand Up @@ -55,8 +57,15 @@
style:width={size + "px"}
style:height={size + "px"}
style:minWidth={size + "px"}
style:background-image={backgroundimg ? `url('${backgroundimg}')` : undefined}
style:background-size={backgroundimg ? "cover" : undefined}
style:background-position={backgroundimg ? "center" : undefined}
class:rounded-md={!rounded} class:rounded-full={rounded}
>{@render children?.()}</div>
>
{#if !backgroundimg}
{@render children?.()}
{/if}
</div>
{:else}
{#await src}
<div
Expand Down
8 changes: 8 additions & 0 deletions src/ts/globalApi.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,14 @@ export function getUnpargeables(db: Database, uptype: 'basename' | 'pure' = 'bas
addUnparge(v.icon);
});
}

if(db.characterOrder){
db.characterOrder.forEach((item) => {
if (typeof item === 'object' && 'imgFile' in item) {
addUnparge(item.imgFile);
}
})
}
return unpargeable;
}

Expand Down
2 changes: 2 additions & 0 deletions src/ts/storage/database.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,8 @@ export interface folder{
data:string[]
color:string
id:string
imgFile?:string
img?:string
}


Expand Down

0 comments on commit e977158

Please sign in to comment.