2023-08-21 06:46:25 +02:00
import {
2023-08-21 06:55:28 +02:00
eventSource ,
this _chid ,
characters ,
2023-08-21 18:16:10 +02:00
getRequestHeaders ,
2023-12-02 19:04:51 +01:00
} from '../../../script.js' ;
2024-01-08 12:15:09 +01:00
import { groups , selected _group } from '../../group-chats.js' ;
2023-12-02 19:04:51 +01:00
import { loadFileToDocument , delay } from '../../utils.js' ;
2023-08-28 06:49:20 +02:00
import { loadMovingUIState } from '../../power-user.js' ;
2023-09-07 16:02:34 +02:00
import { dragElement } from '../../RossAscends-mods.js' ;
2023-12-02 19:04:51 +01:00
import { registerSlashCommand } from '../../slash-commands.js' ;
2023-08-21 06:46:25 +02:00
2023-12-02 19:04:51 +01:00
const extensionName = 'gallery' ;
2023-08-21 06:55:28 +02:00
const extensionFolderPath = ` scripts/extensions/ ${ extensionName } / ` ;
let firstTime = true ;
2023-08-21 06:46:25 +02:00
2023-08-31 02:37:03 +02:00
// Exposed defaults for future tweaking
let thumbnailHeight = 150 ;
let paginationVisiblePages = 10 ;
let paginationMaxLinesPerPage = 2 ;
let galleryMaxRows = 3 ;
2023-08-21 06:55:28 +02:00
2023-08-21 06:46:25 +02:00
2023-08-21 06:55:28 +02:00
/ * *
2023-08-30 23:55:17 +02:00
* Retrieves a list of gallery items based on a given URL . This function calls an API endpoint
2023-08-21 06:55:28 +02:00
* to get the filenames and then constructs the item list .
2023-08-30 23:55:17 +02:00
*
2023-08-21 06:55:28 +02:00
* @ param { string } url - The base URL to retrieve the list of images .
* @ returns { Promise < Array > } - Resolves with an array of gallery item objects , rejects on error .
* /
async function getGalleryItems ( url ) {
2023-08-30 23:55:17 +02:00
const response = await fetch ( ` /listimgfiles/ ${ url } ` , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
2023-08-21 06:55:28 +02:00
} ) ;
2023-08-30 23:55:17 +02:00
const data = await response . json ( ) ;
const items = data . map ( ( file ) => ( {
src : ` user/images/ ${ url } / ${ file } ` ,
srct : ` user/images/ ${ url } / ${ file } ` ,
2023-12-02 19:04:51 +01:00
title : '' , // Optional title for each item
2023-08-30 23:55:17 +02:00
} ) ) ;
return items ;
2023-08-21 06:55:28 +02:00
}
2023-08-21 06:46:25 +02:00
2023-08-21 06:55:28 +02:00
/ * *
2023-08-21 18:16:10 +02:00
* Initializes a gallery using the provided items and sets up the drag - and - drop functionality .
2023-08-30 23:55:17 +02:00
* It uses the nanogallery2 library to display the items and also initializes
2023-08-21 18:16:10 +02:00
* event listeners to handle drag - and - drop of files onto the gallery .
2023-08-30 23:55:17 +02:00
*
2023-08-21 18:16:10 +02:00
* @ param { Array < Object > } items - An array of objects representing the items to display in the gallery .
* @ param { string } url - The URL to use when a file is dropped onto the gallery for uploading .
* @ returns { Promise < void > } - Promise representing the completion of the gallery initialization .
* /
async function initGallery ( items , url ) {
2023-12-02 19:04:51 +01:00
$ ( '#dragGallery' ) . nanogallery2 ( {
'items' : items ,
2023-08-28 06:49:20 +02:00
thumbnailWidth : 'auto' ,
2023-08-31 02:37:03 +02:00
thumbnailHeight : thumbnailHeight ,
paginationVisiblePages : paginationVisiblePages ,
paginationMaxLinesPerPage : paginationMaxLinesPerPage ,
galleryMaxRows : galleryMaxRows ,
2023-08-30 17:12:59 +02:00
galleryPaginationTopButtons : false ,
galleryNavigationOverlayButtons : true ,
2023-08-31 02:08:48 +02:00
galleryTheme : {
navigationBar : { background : 'none' , borderTop : '' , borderBottom : '' , borderRight : '' , borderLeft : '' } ,
navigationBreadcrumb : { background : '#111' , color : '#fff' , colorHover : '#ccc' , borderRadius : '4px' } ,
navigationFilter : { color : '#ddd' , background : '#111' , colorSelected : '#fff' , backgroundSelected : '#111' , borderRadius : '4px' } ,
navigationPagination : { background : '#111' , color : '#fff' , colorHover : '#ccc' , borderRadius : '4px' } ,
thumbnail : { background : '#444' , backgroundImage : 'linear-gradient(315deg, #111 0%, #445 90%)' , borderColor : '#000' , borderRadius : '0px' , labelOpacity : 1 , labelBackground : 'rgba(34, 34, 34, 0)' , titleColor : '#fff' , titleBgColor : 'transparent' , titleShadow : '' , descriptionColor : '#ccc' , descriptionBgColor : 'transparent' , descriptionShadow : '' , stackBackground : '#aaa' } ,
thumbnailIcon : { padding : '5px' , color : '#fff' , shadow : '' } ,
2023-12-02 21:06:57 +01:00
pagination : { background : '#181818' , backgroundSelected : '#666' , color : '#fff' , borderRadius : '2px' , shapeBorder : '3px solid var(--SmartThemeQuoteColor)' , shapeColor : '#444' , shapeSelectedColor : '#aaa' } ,
2023-08-31 02:08:48 +02:00
} ,
2023-12-02 19:04:51 +01:00
galleryDisplayMode : 'pagination' ,
2023-08-31 02:08:48 +02:00
fnThumbnailOpen : viewWithDragbox ,
2023-08-21 18:16:10 +02:00
} ) ;
2023-08-28 06:49:20 +02:00
eventSource . on ( 'resizeUI' , function ( elmntName ) {
2023-12-02 19:04:51 +01:00
jQuery ( '#dragGallery' ) . nanogallery2 ( 'resize' ) ;
2023-08-28 06:49:20 +02:00
} ) ;
2023-08-30 23:55:17 +02:00
const dropZone = $ ( '#dragGallery' ) ;
2023-08-30 21:33:30 +02:00
//remove any existing handlers
dropZone . off ( 'dragover' ) ;
dropZone . off ( 'dragleave' ) ;
dropZone . off ( 'drop' ) ;
2023-08-30 23:55:17 +02:00
2023-09-02 21:40:15 +02:00
// Set dropzone height to be the same as the parent
dropZone . css ( 'height' , dropZone . parent ( ) . css ( 'height' ) ) ;
2023-08-28 06:49:20 +02:00
2023-08-21 18:16:10 +02:00
// Initialize dropzone handlers
dropZone . on ( 'dragover' , function ( e ) {
e . stopPropagation ( ) ; // Ensure this event doesn't propagate
e . preventDefault ( ) ;
2023-08-30 23:55:17 +02:00
$ ( this ) . addClass ( 'dragging' ) ; // Add a CSS class to change appearance during drag-over
2023-08-21 18:16:10 +02:00
} ) ;
dropZone . on ( 'dragleave' , function ( e ) {
e . stopPropagation ( ) ; // Ensure this event doesn't propagate
$ ( this ) . removeClass ( 'dragging' ) ;
} ) ;
dropZone . on ( 'drop' , function ( e ) {
e . stopPropagation ( ) ; // Ensure this event doesn't propagate
e . preventDefault ( ) ;
$ ( this ) . removeClass ( 'dragging' ) ;
let file = e . originalEvent . dataTransfer . files [ 0 ] ;
uploadFile ( file , url ) ; // Added url parameter to know where to upload
} ) ;
2023-10-13 17:29:41 +02:00
//let images populate first
2023-12-02 20:11:06 +01:00
await delay ( 100 ) ;
2023-10-13 17:29:41 +02:00
//unset the height (which must be getting set by the gallery library at some point)
2023-12-02 19:04:51 +01:00
$ ( '#dragGallery' ) . css ( 'height' , 'unset' ) ;
2023-10-13 17:29:41 +02:00
//force a resize to make images display correctly
2023-12-02 19:04:51 +01:00
jQuery ( '#dragGallery' ) . nanogallery2 ( 'resize' ) ;
2023-08-21 18:16:10 +02:00
}
/ * *
* Displays a character gallery using the nanogallery2 library .
2023-08-30 23:55:17 +02:00
*
2023-08-21 18:16:10 +02:00
* This function takes care of :
* - Loading necessary resources for the gallery on the first invocation .
* - Preparing gallery items based on the character or group selection .
* - Handling the drag - and - drop functionality for image upload .
* - Displaying the gallery in a popup .
* - Cleaning up resources when the gallery popup is closed .
2023-08-30 23:55:17 +02:00
*
2023-08-21 18:16:10 +02:00
* @ returns { Promise < void > } - Promise representing the completion of the gallery display process .
2023-08-21 06:55:28 +02:00
* /
2023-08-21 06:46:25 +02:00
async function showCharGallery ( ) {
2023-08-21 06:55:28 +02:00
// Load necessary files if it's the first time calling the function
2023-08-21 06:46:25 +02:00
if ( firstTime ) {
2023-08-21 17:21:32 +02:00
await loadFileToDocument (
2023-08-21 06:55:28 +02:00
` ${ extensionFolderPath } nanogallery2.woff.min.css ` ,
2023-12-02 21:06:57 +01:00
'css' ,
2023-08-21 06:55:28 +02:00
) ;
2023-08-21 17:21:32 +02:00
await loadFileToDocument (
2023-08-21 06:55:28 +02:00
` ${ extensionFolderPath } jquery.nanogallery2.min.js ` ,
2023-12-02 21:06:57 +01:00
'js' ,
2023-08-21 06:55:28 +02:00
) ;
firstTime = false ;
2023-12-02 19:04:51 +01:00
toastr . info ( 'Images can also be found in the folder `user/images`' , 'Drag and drop images onto the gallery to upload them' , { timeOut : 6000 } ) ;
2023-08-21 06:46:25 +02:00
}
try {
2023-08-21 06:55:28 +02:00
let url = selected _group || this _chid ;
if ( ! selected _group && this _chid ) {
const char = characters [ this _chid ] ;
2023-12-02 19:04:51 +01:00
url = char . avatar . replace ( '.png' , '' ) ;
2023-08-21 06:46:25 +02:00
}
const items = await getGalleryItems ( url ) ;
2023-08-30 21:33:30 +02:00
// if there already is a gallery, destroy it and place this one in its place
2023-12-02 19:04:51 +01:00
if ( $ ( '#dragGallery' ) . length ) {
$ ( '#dragGallery' ) . nanogallery2 ( 'destroy' ) ;
2023-08-30 21:33:30 +02:00
initGallery ( items , url ) ;
} else {
makeMovable ( ) ;
setTimeout ( async ( ) => {
await initGallery ( items , url ) ;
} , 100 ) ;
2023-08-21 06:46:25 +02:00
}
} catch ( err ) {
2023-08-31 02:08:48 +02:00
console . trace ( ) ;
2023-08-21 06:46:25 +02:00
console . error ( err ) ;
}
}
2023-08-21 18:16:10 +02:00
/ * *
* Uploads a given file to a specified URL .
* Once the file is uploaded , it provides a success message using toastr ,
* destroys the existing gallery , fetches the latest items , and reinitializes the gallery .
2023-08-30 23:55:17 +02:00
*
2023-08-21 18:16:10 +02:00
* @ param { File } file - The file object to be uploaded .
* @ param { string } url - The URL indicating where the file should be uploaded .
* @ returns { Promise < void > } - Promise representing the completion of the file upload and gallery refresh .
* /
async function uploadFile ( file , url ) {
// Convert the file to a base64 string
const reader = new FileReader ( ) ;
reader . onloadend = async function ( ) {
const base64Data = reader . result ;
// Create the payload
const payload = {
2023-12-02 21:06:57 +01:00
image : base64Data ,
2023-08-21 18:16:10 +02:00
} ;
// Add the ch_name from the provided URL (assuming it's the character name)
payload . ch _name = url ;
try {
const headers = await getRequestHeaders ( ) ;
// Merge headers with content-type for JSON
Object . assign ( headers , {
2023-12-02 21:06:57 +01:00
'Content-Type' : 'application/json' ,
2023-08-21 18:16:10 +02:00
} ) ;
const response = await fetch ( '/uploadimage' , {
method : 'POST' ,
headers : headers ,
2023-12-02 21:06:57 +01:00
body : JSON . stringify ( payload ) ,
2023-08-21 18:16:10 +02:00
} ) ;
if ( ! response . ok ) {
throw new Error ( ` HTTP error! Status: ${ response . status } ` ) ;
}
const result = await response . json ( ) ;
toastr . success ( 'File uploaded successfully. Saved at: ' + result . path ) ;
// Refresh the gallery
2023-12-02 19:04:51 +01:00
$ ( '#dragGallery' ) . nanogallery2 ( 'destroy' ) ; // Destroy old gallery
2023-08-21 18:16:10 +02:00
const newItems = await getGalleryItems ( url ) ; // Fetch the latest items
initGallery ( newItems , url ) ; // Reinitialize the gallery with new items and pass 'url'
} catch ( error ) {
2023-12-02 19:04:51 +01:00
console . error ( 'There was an issue uploading the file:' , error ) ;
2023-08-21 18:16:10 +02:00
// Replacing alert with toastr error notification
toastr . error ( 'Failed to upload the file.' ) ;
}
2023-12-02 20:11:06 +01:00
} ;
2023-08-21 18:16:10 +02:00
reader . readAsDataURL ( file ) ;
}
2023-08-21 06:55:28 +02:00
$ ( document ) . ready ( function ( ) {
// Register an event listener
2023-12-02 19:04:51 +01:00
eventSource . on ( 'charManagementDropdown' , ( selectedOptionId ) => {
if ( selectedOptionId === 'show_char_gallery' ) {
2023-08-21 06:55:28 +02:00
showCharGallery ( ) ;
}
2023-08-21 06:46:25 +02:00
} ) ;
2023-08-21 06:55:28 +02:00
// Add an option to the dropdown
2023-12-02 19:04:51 +01:00
$ ( '#char-management-dropdown' ) . append (
$ ( '<option>' , {
id : 'show_char_gallery' ,
text : 'Show Gallery' ,
2023-12-02 21:06:57 +01:00
} ) ,
2023-08-21 06:55:28 +02:00
) ;
} ) ;
2023-08-28 06:49:20 +02:00
2023-08-30 17:12:59 +02:00
/ * *
* Creates a new draggable container based on a template .
* This function takes a template with the ID 'generic_draggable_template' and clones it .
* The cloned element has its attributes set , a new child div appended , and is made visible on the body .
* Additionally , it sets up the element to prevent dragging on its images .
* /
2023-12-02 19:04:51 +01:00
function makeMovable ( id = 'gallery' ) {
2023-08-28 06:49:20 +02:00
2023-12-02 20:11:06 +01:00
console . debug ( 'making new container from template' ) ;
2023-08-28 06:49:20 +02:00
const template = $ ( '#generic_draggable_template' ) . html ( ) ;
const newElement = $ ( template ) ;
2023-10-13 17:29:41 +02:00
newElement . css ( 'background-color' , 'var(--SmartThemeBlurTintColor)' ) ;
2023-08-30 21:33:30 +02:00
newElement . attr ( 'forChar' , id ) ;
newElement . attr ( 'id' , ` ${ id } ` ) ;
newElement . find ( '.drag-grabber' ) . attr ( 'id' , ` ${ id } header ` ) ;
2023-12-02 20:11:06 +01:00
newElement . find ( '.dragTitle' ) . text ( 'Image Gallery' ) ;
2023-08-28 06:49:20 +02:00
//add a div for the gallery
2023-12-02 19:04:51 +01:00
newElement . append ( '<div id="dragGallery"></div>' ) ;
2023-08-28 06:49:20 +02:00
// add no-scrollbar class to this element
newElement . addClass ( 'no-scrollbar' ) ;
2023-08-31 02:08:48 +02:00
// get the close button and set its id and data-related-id
const closeButton = newElement . find ( '.dragClose' ) ;
closeButton . attr ( 'id' , ` ${ id } close ` ) ;
closeButton . attr ( 'data-related-id' , ` ${ id } ` ) ;
2023-08-28 06:49:20 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#dragGallery' ) . css ( 'display' , 'block' ) ;
2023-08-28 06:49:20 +02:00
$ ( 'body' ) . append ( newElement ) ;
loadMovingUIState ( ) ;
2023-08-30 21:33:30 +02:00
$ ( ` .draggable[forChar=" ${ id } "] ` ) . css ( 'display' , 'block' ) ;
2023-08-28 06:49:20 +02:00
dragElement ( newElement ) ;
2023-08-30 21:33:30 +02:00
$ ( ` .draggable[forChar=" ${ id } "] img ` ) . on ( 'dragstart' , ( e ) => {
2023-08-28 06:49:20 +02:00
console . log ( 'saw drag on avatar!' ) ;
e . preventDefault ( ) ;
return false ;
} ) ;
2023-08-31 02:08:48 +02:00
$ ( 'body' ) . on ( 'click' , '.dragClose' , function ( ) {
const relatedId = $ ( this ) . data ( 'related-id' ) ; // Get the ID of the related draggable
$ ( ` # ${ relatedId } ` ) . remove ( ) ; // Remove the associated draggable
} ) ;
2023-08-28 06:49:20 +02:00
}
2023-08-30 17:12:59 +02:00
/ * *
* Creates a new draggable image based on a template .
2023-08-30 23:55:17 +02:00
*
2023-08-30 17:12:59 +02:00
* This function clones a provided template with the ID 'generic_draggable_template' ,
* appends the given image URL , ensures the element has a unique ID ,
* and attaches the element to the body . After appending , it also prevents
* dragging on the appended image .
2023-08-30 23:55:17 +02:00
*
2023-08-30 17:12:59 +02:00
* @ param { string } id - A base identifier for the new draggable element .
* @ param { string } url - The URL of the image to be added to the draggable element .
* /
2023-08-30 04:44:09 +02:00
function makeDragImg ( id , url ) {
// Step 1: Clone the template content
const template = document . getElementById ( 'generic_draggable_template' ) ;
if ( ! ( template instanceof HTMLTemplateElement ) ) {
console . error ( 'The element is not a <template> tag' ) ;
return ;
}
const newElement = document . importNode ( template . content , true ) ;
// Step 2: Append the given image
const imgElem = document . createElement ( 'img' ) ;
imgElem . src = url ;
2023-08-30 17:12:59 +02:00
let uniqueId = ` draggable_ ${ id } ` ;
2023-08-30 04:44:09 +02:00
const draggableElem = newElement . querySelector ( '.draggable' ) ;
if ( draggableElem ) {
draggableElem . appendChild ( imgElem ) ;
2023-08-30 17:12:59 +02:00
// Find a unique id for the draggable element
let counter = 1 ;
while ( document . getElementById ( uniqueId ) ) {
uniqueId = ` draggable_ ${ id } _ ${ counter } ` ;
counter ++ ;
}
draggableElem . id = uniqueId ;
2023-08-30 04:44:09 +02:00
// Ensure that the newly added element is displayed as block
draggableElem . style . display = 'block' ;
2023-10-13 17:29:41 +02:00
//and has no padding unlike other non-zoomed-avatar draggables
draggableElem . style . padding = '0' ;
2023-08-30 04:44:09 +02:00
2023-08-31 02:08:48 +02:00
// Add an id to the close button
// If the close button exists, set related-id
const closeButton = draggableElem . querySelector ( '.dragClose' ) ;
if ( closeButton ) {
closeButton . id = ` ${ uniqueId } close ` ;
closeButton . dataset . relatedId = uniqueId ;
}
2023-08-30 17:12:59 +02:00
// Find the .drag-grabber and set its matching unique ID
2023-08-30 04:44:09 +02:00
const dragGrabber = draggableElem . querySelector ( '.drag-grabber' ) ;
if ( dragGrabber ) {
2023-08-30 17:12:59 +02:00
dragGrabber . id = ` ${ uniqueId } header ` ; // appending _header to make it match the parent's unique ID
2023-08-30 04:44:09 +02:00
}
}
// Step 3: Attach it to the body
document . body . appendChild ( newElement ) ;
// Step 4: Call dragElement and loadMovingUIState
2023-08-30 17:12:59 +02:00
const appendedElement = document . getElementById ( uniqueId ) ;
2023-08-30 04:44:09 +02:00
if ( appendedElement ) {
var elmntName = $ ( appendedElement ) ;
loadMovingUIState ( ) ;
2023-08-30 06:11:20 +02:00
dragElement ( elmntName ) ;
2023-08-30 04:44:09 +02:00
// Prevent dragging the image
2023-08-30 17:12:59 +02:00
$ ( ` # ${ uniqueId } img ` ) . on ( 'dragstart' , ( e ) => {
2023-08-30 04:44:09 +02:00
console . log ( 'saw drag on avatar!' ) ;
e . preventDefault ( ) ;
return false ;
} ) ;
} else {
2023-12-02 19:04:51 +01:00
console . error ( 'Failed to append the template content or retrieve the appended content.' ) ;
2023-08-30 04:44:09 +02:00
}
2023-08-31 02:08:48 +02:00
$ ( 'body' ) . on ( 'click' , '.dragClose' , function ( ) {
const relatedId = $ ( this ) . data ( 'related-id' ) ; // Get the ID of the related draggable
$ ( ` # ${ relatedId } ` ) . remove ( ) ; // Remove the associated draggable
} ) ;
2023-08-30 04:44:09 +02:00
}
2023-09-12 18:23:33 +02:00
/ * *
* Sanitizes a given ID to ensure it can be used as an HTML ID .
* This function replaces spaces and non - word characters with dashes .
* It also removes any non - ASCII characters .
* @ param { string } id - The ID to be sanitized .
* @ returns { string } - The sanitized ID .
* /
2023-10-13 17:29:41 +02:00
function sanitizeHTMLId ( id ) {
2023-09-12 18:23:33 +02:00
// Replace spaces and non-word characters
id = id . replace ( /\s+/g , '-' )
2023-10-13 17:29:41 +02:00
. replace ( /[^\x00-\x7F]/g , '-' )
. replace ( /\W/g , '' ) ;
2023-09-12 18:23:33 +02:00
return id ;
}
2023-08-30 04:44:09 +02:00
2023-08-30 17:12:59 +02:00
/ * *
* Processes a list of items ( containing URLs ) and creates a draggable box for the first item .
2023-08-30 23:55:17 +02:00
*
2023-08-30 17:12:59 +02:00
* If the provided list of items is non - empty , it takes the URL of the first item ,
* derives an ID from the URL , and uses the makeDragImg function to create
* a draggable image element based on that ID and URL .
2023-08-30 23:55:17 +02:00
*
2023-08-30 17:12:59 +02:00
* @ param { Array } items - A list of items where each item has a responsiveURL method that returns a URL .
* /
function viewWithDragbox ( items ) {
2023-08-28 06:49:20 +02:00
if ( items && items . length > 0 ) {
2023-09-12 18:23:33 +02:00
const url = items [ 0 ] . responsiveURL ( ) ; // Get the URL of the clicked image/video
2023-08-30 04:44:09 +02:00
// ID should just be the last part of the URL, removing the extension
2023-09-12 18:23:33 +02:00
const id = sanitizeHTMLId ( url . substring ( url . lastIndexOf ( '/' ) + 1 , url . lastIndexOf ( '.' ) ) ) ;
2023-08-30 04:44:09 +02:00
makeDragImg ( id , url ) ;
2023-08-28 06:49:20 +02:00
}
}
2023-08-30 04:44:09 +02:00
2023-09-07 16:02:34 +02:00
// Registers a simple command for opening the char gallery.
2023-12-02 19:04:51 +01:00
registerSlashCommand ( 'show-gallery' , showGalleryCommand , [ 'sg' ] , '– shows the gallery' , true , true ) ;
2024-01-08 12:15:09 +01:00
registerSlashCommand ( 'list-gallery' , listGalleryCommand , [ 'lg' ] , '<span class="monospace">[optional char=charName] [optional group=groupName]</span> – list images in the gallery of the current char / group or a specified char / group' , true , true ) ;
2023-09-07 16:02:34 +02:00
function showGalleryCommand ( args ) {
showCharGallery ( ) ;
}
2024-01-08 12:15:09 +01:00
async function listGalleryCommand ( args ) {
try {
let url = args . char ? ? ( args . group ? groups . find ( it => it . name == args . group ) ? . id : null ) ? ? ( selected _group || this _chid ) ;
if ( ! args . char && ! args . group && ! selected _group && this _chid ) {
const char = characters [ this _chid ] ;
url = char . avatar . replace ( '.png' , '' ) ;
}
const items = await getGalleryItems ( url ) ;
return JSON . stringify ( items . map ( it => it . src ) ) ;
} catch ( err ) {
console . trace ( ) ;
console . error ( err ) ;
}
return JSON . stringify ( [ ] ) ;
}