migrate
This commit is contained in:
9
public/scripts/extensions/assets/character.html
Normal file
9
public/scripts/extensions/assets/character.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="characterAsset">
|
||||
<div class="characterAssetName">{{name}}</div>
|
||||
<img class="characterAssetImage" alt="{{name}}" src="{{url}}" />
|
||||
<div class="characterAssetDescription" title="{{description}}">{{description}}</div>
|
||||
<div class="characterAssetButtons flex-container">
|
||||
<div class="characterAssetDownloadButton right_menu_button fa-fw fa-solid fa-download" title="Download"></div>
|
||||
<div class="characterAssetCheckMark right_menu_button fa-fw fa-solid fa-check" title="Installed"></div>
|
||||
</div>
|
||||
</div>
|
472
public/scripts/extensions/assets/index.js
Normal file
472
public/scripts/extensions/assets/index.js
Normal file
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
TODO:
|
||||
*/
|
||||
//const DEBUG_TONY_SAMA_FORK_MODE = true
|
||||
|
||||
import { DOMPurify } from '../../../lib.js';
|
||||
import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js';
|
||||
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
|
||||
import { executeSlashCommands } from '../../slash-commands.js';
|
||||
import { accountStorage } from '../../util/AccountStorage.js';
|
||||
import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
|
||||
import { t } from '../../i18n.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'assets';
|
||||
const DEBUG_PREFIX = '<Assets module> ';
|
||||
let previewAudio = null;
|
||||
let ASSETS_JSON_URL = 'https://raw.githubusercontent.com/ChuQuadrant/ChuQuadrant-Content/main/index.json';
|
||||
|
||||
|
||||
// DBG
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
// ASSETS_JSON_URL = "https://raw.githubusercontent.com/Tony-sama/ChuQuadrant-Content/main/index.json"
|
||||
let availableAssets = {};
|
||||
let currentAssets = {};
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
function filterAssets() {
|
||||
const searchValue = String($('#assets_search').val()).toLowerCase().trim();
|
||||
const typeValue = String($('#assets_type_select').val());
|
||||
|
||||
if (typeValue === '') {
|
||||
$('#assets_menu .assets-list-div').show();
|
||||
$('#assets_menu .assets-list-div h3').show();
|
||||
} else {
|
||||
$('#assets_menu .assets-list-div h3').hide();
|
||||
$('#assets_menu .assets-list-div').hide();
|
||||
$(`#assets_menu .assets-list-div[data-type="${typeValue}"]`).show();
|
||||
}
|
||||
|
||||
if (searchValue === '') {
|
||||
$('#assets_menu .asset-block').show();
|
||||
} else {
|
||||
$('#assets_menu .asset-block').hide();
|
||||
$('#assets_menu .asset-block').filter(function () {
|
||||
return $(this).text().toLowerCase().includes(searchValue);
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
const KNOWN_TYPES = {
|
||||
'extension': 'Extensions',
|
||||
'character': 'Characters',
|
||||
'ambient': 'Ambient sounds',
|
||||
'bgm': 'Background music',
|
||||
'blip': 'Blip sounds',
|
||||
};
|
||||
|
||||
async function downloadAssetsList(url) {
|
||||
updateCurrentAssets().then(async function () {
|
||||
fetch(url, { cache: 'no-cache' })
|
||||
.then(response => response.json())
|
||||
.then(async function(json) {
|
||||
|
||||
availableAssets = {};
|
||||
$('#assets_menu').empty();
|
||||
|
||||
console.debug(DEBUG_PREFIX, 'Received assets dictionary', json);
|
||||
|
||||
for (const i of json) {
|
||||
//console.log(DEBUG_PREFIX,i)
|
||||
if (availableAssets[i['type']] === undefined)
|
||||
availableAssets[i['type']] = [];
|
||||
|
||||
availableAssets[i['type']].push(i);
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets);
|
||||
// First extensions, then everything else
|
||||
const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0);
|
||||
|
||||
$('#assets_type_select').empty();
|
||||
$('#assets_search').val('');
|
||||
$('#assets_type_select').append($('<option />', { value: '', text: t`All` }));
|
||||
|
||||
for (const type of assetTypes) {
|
||||
const option = $('<option />', { value: type, text: t([KNOWN_TYPES[type] || type]) });
|
||||
$('#assets_type_select').append(option);
|
||||
}
|
||||
|
||||
if (assetTypes.includes('extension')) {
|
||||
$('#assets_type_select').val('extension');
|
||||
}
|
||||
|
||||
$('#assets_type_select').off('change').on('change', filterAssets);
|
||||
$('#assets_search').off('input').on('input', filterAssets);
|
||||
|
||||
for (const assetType of assetTypes) {
|
||||
let assetTypeMenu = $('<div />', { id: 'assets_audio_ambient_div', class: 'assets-list-div' });
|
||||
assetTypeMenu.attr('data-type', assetType);
|
||||
assetTypeMenu.append(`<h3>${KNOWN_TYPES[assetType] || assetType}</h3>`).hide();
|
||||
|
||||
if (assetType == 'extension') {
|
||||
assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation'));
|
||||
}
|
||||
|
||||
for (const i in availableAssets[assetType].sort((a, b) => a?.name && b?.name && a['name'].localeCompare(b['name']))) {
|
||||
const asset = availableAssets[assetType][i];
|
||||
const elemId = `assets_install_${assetType}_${i}`;
|
||||
let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
|
||||
const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
|
||||
element.append(label);
|
||||
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
// asset["url"] = asset["url"].replace("https://github.com/ChuQuadrant/","https://github.com/Tony-sama/"); // DBG
|
||||
|
||||
console.debug(DEBUG_PREFIX, 'Checking asset', asset['id'], asset['url']);
|
||||
|
||||
const assetInstall = async function () {
|
||||
element.off('click');
|
||||
label.removeClass('fa-download');
|
||||
this.classList.add('asset-download-button-loading');
|
||||
await installAsset(asset['url'], assetType, asset['id']);
|
||||
label.addClass('fa-check');
|
||||
this.classList.remove('asset-download-button-loading');
|
||||
element.on('click', assetDelete);
|
||||
element.on('mouseenter', function () {
|
||||
label.removeClass('fa-check');
|
||||
label.addClass('fa-trash');
|
||||
label.addClass('redOverlayGlow');
|
||||
}).on('mouseleave', function () {
|
||||
label.addClass('fa-check');
|
||||
label.removeClass('fa-trash');
|
||||
label.removeClass('redOverlayGlow');
|
||||
});
|
||||
};
|
||||
|
||||
const assetDelete = async function () {
|
||||
if (assetType === 'character') {
|
||||
toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
|
||||
await executeSlashCommands(`/go ${asset['id']}`);
|
||||
return;
|
||||
}
|
||||
element.off('click');
|
||||
await deleteAsset(assetType, asset['id']);
|
||||
label.removeClass('fa-check');
|
||||
label.removeClass('redOverlayGlow');
|
||||
label.removeClass('fa-trash');
|
||||
label.addClass('fa-download');
|
||||
element.off('mouseenter').off('mouseleave');
|
||||
element.on('click', assetInstall);
|
||||
};
|
||||
|
||||
if (isAssetInstalled(assetType, asset['id'])) {
|
||||
console.debug(DEBUG_PREFIX, 'installed, checked');
|
||||
label.toggleClass('fa-download');
|
||||
label.toggleClass('fa-check');
|
||||
element.on('click', assetDelete);
|
||||
element.on('mouseenter', function () {
|
||||
label.removeClass('fa-check');
|
||||
label.addClass('fa-trash');
|
||||
label.addClass('redOverlayGlow');
|
||||
}).on('mouseleave', function () {
|
||||
label.addClass('fa-check');
|
||||
label.removeClass('fa-trash');
|
||||
label.removeClass('redOverlayGlow');
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX, 'not installed, unchecked');
|
||||
element.prop('checked', false);
|
||||
element.on('click', assetInstall);
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX, 'Created element for ', asset['id']);
|
||||
|
||||
const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
|
||||
const description = DOMPurify.sanitize(asset['description'] || '');
|
||||
const url = isValidUrl(asset['url']) ? asset['url'] : '';
|
||||
const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`;
|
||||
const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
const toolTag = assetType === 'extension' && asset['tool'];
|
||||
|
||||
const assetBlock = $('<i></i>')
|
||||
.append(element)
|
||||
.append(`<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span class="asset-name flex-container alignitemscenter">
|
||||
<b>${displayName}</b>
|
||||
<a class="asset_preview" href="${url}" target="_blank" title="${title}">
|
||||
<i class="fa-solid fa-sm ${previewIcon}"></i>
|
||||
</a>` +
|
||||
(toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' +
|
||||
t`Tool` + '</span>' : '') +
|
||||
`</span>
|
||||
<small class="asset-description">
|
||||
${description}
|
||||
</small>
|
||||
</div>`);
|
||||
|
||||
assetBlock.find('.tag').on('click', function (e) {
|
||||
const a = document.createElement('a');
|
||||
a.href = 'https://docs.ChuQuadrant.app/for-contributors/function-calling/';
|
||||
a.target = '_blank';
|
||||
a.click();
|
||||
});
|
||||
|
||||
if (assetType === 'character') {
|
||||
if (asset.highlight) {
|
||||
assetBlock.find('.asset-name').append('<i class="fa-solid fa-sm fa-trophy"></i>');
|
||||
}
|
||||
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
|
||||
}
|
||||
|
||||
assetBlock.addClass('asset-block');
|
||||
|
||||
assetTypeMenu.append(assetBlock);
|
||||
}
|
||||
assetTypeMenu.appendTo('#assets_menu');
|
||||
assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
|
||||
}
|
||||
|
||||
filterAssets();
|
||||
$('#assets_filters').show();
|
||||
$('#assets_menu').show();
|
||||
})
|
||||
.catch((error) => {
|
||||
// Info hint if the user maybe... likely accidently was trying to install an extension and we wanna help guide them? uwu :3
|
||||
const installButton = $('#third_party_extension_button');
|
||||
flashHighlight(installButton, 10_000);
|
||||
toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 });
|
||||
|
||||
// Error logged after, to appear on top
|
||||
console.error(error);
|
||||
toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list');
|
||||
$('#assets-connect-button').addClass('fa-plug-circle-exclamation');
|
||||
$('#assets-connect-button').addClass('redOverlayGlow');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function previewAsset(e) {
|
||||
const href = $(this).attr('href');
|
||||
const audioExtensions = ['.mp3', '.ogg', '.wav'];
|
||||
|
||||
if (audioExtensions.some(ext => href.endsWith(ext))) {
|
||||
e.preventDefault();
|
||||
|
||||
if (previewAudio) {
|
||||
previewAudio.pause();
|
||||
|
||||
if (previewAudio.src === href) {
|
||||
previewAudio = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
previewAudio = new Audio(href);
|
||||
previewAudio.play();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function isAssetInstalled(assetType, filename) {
|
||||
let assetList = currentAssets[assetType];
|
||||
|
||||
if (assetType == 'extension') {
|
||||
const thirdPartyMarker = 'third-party/';
|
||||
assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, ''));
|
||||
}
|
||||
|
||||
if (assetType == 'character') {
|
||||
assetList = getContext().characters.map(x => x.avatar);
|
||||
}
|
||||
|
||||
for (const i of assetList) {
|
||||
//console.debug(DEBUG_PREFIX,i,filename)
|
||||
if (i.includes(filename))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function installAsset(url, assetType, filename) {
|
||||
console.debug(DEBUG_PREFIX, 'Downloading ', url);
|
||||
const category = assetType;
|
||||
try {
|
||||
if (category === 'extension') {
|
||||
console.debug(DEBUG_PREFIX, 'Installing extension ', url);
|
||||
await installExtension(url);
|
||||
console.debug(DEBUG_PREFIX, 'Extension installed.');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = { url, category, filename };
|
||||
const result = await fetch('/api/assets/download', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, 'Download success.');
|
||||
if (category === 'character') {
|
||||
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
||||
const blob = await result.blob();
|
||||
const file = new File([blob], filename, { type: blob.type });
|
||||
await processDroppedFiles([file], true);
|
||||
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAsset(assetType, filename) {
|
||||
console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename);
|
||||
const category = assetType;
|
||||
try {
|
||||
if (category === 'extension') {
|
||||
console.debug(DEBUG_PREFIX, 'Deleting extension ', filename);
|
||||
await deleteExtension(filename);
|
||||
console.debug(DEBUG_PREFIX, 'Extension deleted.');
|
||||
}
|
||||
|
||||
const body = { category, filename };
|
||||
const result = await fetch('/api/assets/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, 'Deletion success.');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function openCharacterBrowser(forceDefault) {
|
||||
const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val());
|
||||
const fetchResult = await fetch(url, { cache: 'no-cache' });
|
||||
const json = await fetchResult.json();
|
||||
const characters = json.filter(x => x.type === 'character');
|
||||
|
||||
if (!characters.length) {
|
||||
toastr.error('No characters found in the assets list', 'Character browser');
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'market', {}));
|
||||
|
||||
for (const character of characters.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
const listElement = template.find(character.highlight ? '.contestWinnersList' : '.featuredCharactersList');
|
||||
const characterElement = $(await renderExtensionTemplateAsync(MODULE_NAME, 'character', character));
|
||||
const downloadButton = characterElement.find('.characterAssetDownloadButton');
|
||||
const checkMark = characterElement.find('.characterAssetCheckMark');
|
||||
const isInstalled = isAssetInstalled('character', character.id);
|
||||
|
||||
downloadButton.toggle(!isInstalled).on('click', async () => {
|
||||
downloadButton.toggleClass('fa-download fa-spinner fa-spin');
|
||||
await installAsset(character.url, 'character', character.id);
|
||||
downloadButton.hide();
|
||||
checkMark.show();
|
||||
});
|
||||
|
||||
checkMark.toggle(isInstalled);
|
||||
|
||||
listElement.append(characterElement);
|
||||
}
|
||||
|
||||
callGenericPopup(template, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, allowVerticalScrolling: true, allowHorizontalScrolling: false });
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// API Calls //
|
||||
//#############################//
|
||||
|
||||
async function updateCurrentAssets() {
|
||||
console.debug(DEBUG_PREFIX, 'Checking installed assets...');
|
||||
try {
|
||||
const result = await fetch('/api/assets/get', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
currentAssets = result.ok ? (await result.json()) : {};
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
console.debug(DEBUG_PREFIX, 'Current assets found:', currentAssets);
|
||||
}
|
||||
|
||||
|
||||
//#############################//
|
||||
// Extension load //
|
||||
//#############################//
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
jQuery(async () => {
|
||||
// This is an example of loading HTML from a file
|
||||
const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {});
|
||||
const windowHtml = $(windowTemplate);
|
||||
|
||||
const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
|
||||
assetsJsonUrl.val(ASSETS_JSON_URL);
|
||||
|
||||
const charactersButton = windowHtml.find('#assets-characters-button');
|
||||
charactersButton.on('click', async function () {
|
||||
openCharacterBrowser(false);
|
||||
});
|
||||
|
||||
const installHintButton = windowHtml.find('.assets-install-hint-link');
|
||||
installHintButton.on('click', async function () {
|
||||
const installButton = $('#third_party_extension_button');
|
||||
flashHighlight(installButton, 5000);
|
||||
toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
|
||||
});
|
||||
|
||||
const connectButton = windowHtml.find('#assets-connect-button');
|
||||
connectButton.on('click', async function () {
|
||||
const url = DOMPurify.sanitize(String(assetsJsonUrl.val()));
|
||||
const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`;
|
||||
const skipConfirm = accountStorage.getItem(rememberKey) === 'true';
|
||||
|
||||
const confirmation = skipConfirm || await Popup.show.confirm(t`Loading Asset List`, '<span>' + t`Are you sure you want to connect to the following url?` + `</span><var>${url}</var>`, {
|
||||
customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],
|
||||
onClose: popup => {
|
||||
if (popup.result) {
|
||||
const rememberValue = popup.inputResults.get('assets-remember');
|
||||
accountStorage.setItem(rememberKey, String(rememberValue));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (confirmation) {
|
||||
try {
|
||||
console.debug(DEBUG_PREFIX, 'Confimation, loading assets...');
|
||||
downloadAssetsList(url);
|
||||
connectButton.removeClass('fa-plug-circle-exclamation');
|
||||
connectButton.removeClass('redOverlayGlow');
|
||||
connectButton.addClass('fa-plug-circle-check');
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
toastr.error(`Cannot get assets list from ${url}`);
|
||||
connectButton.removeClass('fa-plug-circle-check');
|
||||
connectButton.addClass('fa-plug-circle-exclamation');
|
||||
connectButton.removeClass('redOverlayGlow');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX, 'Connection refused by user');
|
||||
}
|
||||
});
|
||||
|
||||
windowHtml.find('#assets_filters').hide();
|
||||
$('#assets_container').append(windowHtml);
|
||||
|
||||
eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
|
||||
openCharacterBrowser(forceDefault);
|
||||
});
|
||||
});
|
4
public/scripts/extensions/assets/installation.html
Normal file
4
public/scripts/extensions/assets/installation.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="assets-list-git">
|
||||
<span data-i18n="extension_install_1">To download extensions from this page, you need to have </span><a href="https://git-scm.com/downloads" target="_blank">Git</a><span data-i18n="extension_install_2"> installed.</span><br>
|
||||
<span data-i18n="extension_install_3">Click the </span><i class="fa-solid fa-sm fa-arrow-up-right-from-square"></i><span data-i18n="extension_install_4"> icon to visit the Extension's repo for tips on how to use it.</span>
|
||||
</div>
|
11
public/scripts/extensions/assets/manifest.json
Normal file
11
public/scripts/extensions/assets/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Assets",
|
||||
"loading_order": 15,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Keij#6799",
|
||||
"version": "0.1.0",
|
||||
"homePage": "https://github.com/ChuQuadrant/ChuQuadrant"
|
||||
}
|
19
public/scripts/extensions/assets/market.html
Normal file
19
public/scripts/extensions/assets/market.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="flex-container flexFlowColumn padding5">
|
||||
<div class="contestWinners flex-container flexFlowColumn">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" data-i18n="[title]These characters are the winners of character design contests and have outstandable quality." title="These characters are the winners of character design contests and have outstandable quality.">
|
||||
<span data-i18n="Contest Winners">Contest Winners</span>
|
||||
<i class="fa-solid fa-star"></i>
|
||||
</h3>
|
||||
<div class="contestWinnersList characterAssetList">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="featuredCharacters flex-container flexFlowColumn">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" data-i18n="[title]These characters are the finalists of character design contests and have remarkable quality." title="These characters are the finalists of character design contests and have remarkable quality.">
|
||||
<span data-i18n="Featured Characters">Featured Characters</span>
|
||||
<i class="fa-solid fa-thumbs-up"></i>
|
||||
</h3>
|
||||
<div class="featuredCharactersList characterAssetList">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
175
public/scripts/extensions/assets/style.css
Normal file
175
public/scripts/extensions/assets/style.css
Normal file
@@ -0,0 +1,175 @@
|
||||
#assets-json-url-field {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
#assets-connect-button {
|
||||
width: 15%;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.assets-url-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.assets-install-hint-link {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.assets-connect-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.assets-list-git {
|
||||
font-size: calc(var(--mainFontSize) * 0.8);
|
||||
opacity: 0.8;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.assets-list-div h3 {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.assets-list-div i a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.assets-list-div > i {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding: 5px;
|
||||
font-style: normal;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.assets-list-div i span:first-of-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.asset-download-button {
|
||||
position: relative;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.asset-download-button:active {
|
||||
background: #007a63;
|
||||
}
|
||||
|
||||
.asset-download-button-text {
|
||||
font: bold 20px "Quicksand", san-serif;
|
||||
color: #ffffff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.asset-download-button-loading .asset-download-button-text {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.asset-download-button-loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
border: 4px solid transparent;
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: asset-download-button-loading-spinner 1s ease infinite;
|
||||
}
|
||||
|
||||
.asset-name .avatar {
|
||||
--imgSize: 30px !important;
|
||||
flex: unset;
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
}
|
||||
|
||||
.asset-name .avatar img {
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-position: center center;
|
||||
}
|
||||
|
||||
@keyframes asset-download-button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
.characterAssetList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.characterAsset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
background-color: var(--black30a);
|
||||
border-radius: 10px;
|
||||
width: 17%;
|
||||
min-width: 150px;
|
||||
margin: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.characterAssetName {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.characterAssetImage {
|
||||
max-height: 140px;
|
||||
object-fit: scale-down;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.characterAssetDescription {
|
||||
font-size: 0.75em;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.characterAssetButtons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.asset-name .tag {
|
||||
gap: 5px;
|
||||
align-items: baseline;
|
||||
font-size: calc(var(--mainFontSize)* 0.8);
|
||||
cursor: pointer;
|
||||
opacity: 0.9;
|
||||
margin-left: 2px;
|
||||
}
|
46
public/scripts/extensions/assets/window.html
Normal file
46
public/scripts/extensions/assets/window.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<div id="assets_ui">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Download Extensions & Assets">Download Extensions & Assets</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>
|
||||
<span data-i18n="Load a custom asset list or select">
|
||||
Load a custom asset list or select
|
||||
</span>
|
||||
<a class="assets-install-hint-link" data-i18n="Install extension">Install Extension</a>
|
||||
<span data-i18n="to install 3rd party extensions.">
|
||||
to install 3rd party extensions.
|
||||
</span>
|
||||
</small>
|
||||
<div class="assets-url-block m-b-1 m-t-1">
|
||||
<label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label>
|
||||
<small data-i18n="[title]load_asset_list_desc" title="Load a list of extensions & assets based on an asset list file.
|
||||
|
||||
The default Asset URL in this field points to the list of offical first party extensions and assets.
|
||||
If you have a custom asset list, you can insert it here.
|
||||
|
||||
To install a single 3rd party extension, use the "Install Extensions" button on the top right.">
|
||||
<span data-i18n="Load an asset list">Load an asset list</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p"></div>
|
||||
</small>
|
||||
<div class="assets-connect-div">
|
||||
<input id="assets-json-url-field" class="text_pole widthUnset flex1">
|
||||
<i id="assets-connect-button" class="menu_button fa-solid fa-plug-circle-exclamation fa-xl redOverlayGlow" title="Load Asset List" data-i18n="[title]Load Asset List"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="assets_filters" class="flex-container">
|
||||
<select id="assets_type_select" class="text_pole flex1">
|
||||
</select>
|
||||
<input id="assets_search" class="text_pole flex1" data-i18n="[placeholder]Search" placeholder="Search" type="search">
|
||||
<div id="assets-characters-button" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-image-portrait"></i>
|
||||
<span data-i18n="Characters">Characters</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer-content" id="assets_menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user