Update and fix default expressions + emojis - Move "use default emojis" from its own toggle into the fallback dropdown - Add "no fallback" to the fallback dropdown - fix fallback expressions not being consistently used - Switching fallback will now reroll/reset the relevant expression - Add html attributes on the expression image
| @@ -70,6 +70,12 @@ const DEFAULT_EXPRESSIONS = [ | |||
| 70 | 'surprise', | 70 | 'surprise', |
| 71 | 'neutral', | 71 | 'neutral', |
| 72 | ]; | 72 | ]; |
| 73 | |||
| 74 | const OPTION_NO_FALLBACK = '#none'; | ||
| 75 | const OPTION_EMOJI_FALLBACK = '#emoji'; | ||
| 76 | const RESET_SPRITE_LABEL = '#reset'; | ||
| 77 | |||
| 78 | |||
| 73 | /** @enum {number} */ | 79 | /** @enum {number} */ |
| 74 | const EXPRESSION_API = { | 80 | const EXPRESSION_API = { |
| 75 | local: 0, | 81 | local: 0, |
| @@ -106,15 +112,6 @@ function getPlaceholderImage(expression, isCustom = false) { | |||
| 106 | }; | 112 | }; |
| 107 | } | 113 | } |
| 108 | 114 | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Returns the fallback expression if explicitly chosen, otherwise the default one | ||
| 112 | * @returns {string} expression name | ||
| 113 | */ | ||
| 114 | function getFallbackExpression() { | ||
| 115 | return extension_settings.expressions.fallback_expression ?? DEFAULT_FALLBACK_EXPRESSION; | ||
| 116 | } | ||
| 117 | |||
| 118 | function isVisualNovelMode() { | 115 | function isVisualNovelMode() { |
| 119 | return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId); | 116 | return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId); |
| 120 | } | 117 | } |
| @@ -127,12 +124,12 @@ async function forceUpdateVisualNovelMode() { | |||
| 127 | 124 | ||
| 128 | const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick); | 125 | const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick); |
| 129 | 126 | ||
| 130 | async function updateVisualNovelMode(name, expression) { | 127 | async function updateVisualNovelMode(spriteFolderName, expression) { |
| 131 | const vnContainer = $('#visual-novel-wrapper'); | 128 | const vnContainer = $('#visual-novel-wrapper'); |
| 132 | 129 | ||
| 133 | await visualNovelRemoveInactive(vnContainer); | 130 | await visualNovelRemoveInactive(vnContainer); |
| 134 | 131 | ||
| 135 | const setSpritePromises = await visualNovelSetCharacterSprites(vnContainer, name, expression); | 132 | const setSpritePromises = await visualNovelSetCharacterSprites(vnContainer, spriteFolderName, expression); |
| 136 | 133 | ||
| 137 | // calculate layer indices based on recent messages | 134 | // calculate layer indices based on recent messages |
| 138 | await visualNovelUpdateLayers(vnContainer); | 135 | await visualNovelUpdateLayers(vnContainer); |
| @@ -414,23 +411,6 @@ async function setImage(img, path) { | |||
| 414 | }); | 411 | }); |
| 415 | } | 412 | } |
| 416 | 413 | ||
| 417 | function onExpressionsShowDefaultInput() { | ||
| 418 | const value = $(this).prop('checked'); | ||
| 419 | extension_settings.expressions.showDefault = value; | ||
| 420 | saveSettingsDebounced(); | ||
| 421 | |||
| 422 | const existingImageSrc = $('img.expression').prop('src'); | ||
| 423 | if (existingImageSrc !== undefined) { //if we have an image in src | ||
| 424 | if (!value && existingImageSrc.includes('/img/default-expressions/')) { //and that image is from /img/ (default) | ||
| 425 | $('img.expression').prop('src', ''); //remove it | ||
| 426 | lastMessage = null; | ||
| 427 | } | ||
| 428 | if (value) { | ||
| 429 | lastMessage = null; | ||
| 430 | } | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | async function moduleWorker() { | 414 | async function moduleWorker() { |
| 435 | const context = getContext(); | 415 | const context = getContext(); |
| 436 | 416 | ||
| @@ -546,8 +526,8 @@ async function moduleWorker() { | |||
| 546 | const force = !!context.groupId; | 526 | const force = !!context.groupId; |
| 547 | 527 | ||
| 548 | // Character won't be angry on you for swiping | 528 | // Character won't be angry on you for swiping |
| 549 | if (currentLastMessage.mes == '...' && expressionsList.includes(getFallbackExpression())) { | 529 | if (currentLastMessage.mes == '...' && expressionsList.includes(extension_settings.expressions.fallback_expression)) { |
| 550 | expression = getFallbackExpression(); | 530 | expression = extension_settings.expressions.fallback_expression; |
| 551 | } | 531 | } |
| 552 | 532 | ||
| 553 | await sendExpressionCall(spriteFolderName, expression, { force: force, vnMode: vnMode }); | 533 | await sendExpressionCall(spriteFolderName, expression, { force: force, vnMode: vnMode }); |
| @@ -599,23 +579,23 @@ function getFolderNameByMessage(message) { | |||
| 599 | /** | 579 | /** |
| 600 | * Update the expression for the given character. | 580 | * Update the expression for the given character. |
| 601 | * | 581 | * |
| 602 | * @param {string} name The character name, optionally with a sprite folder override, e.g. "folder/expression". | 582 | * @param {string} spriteFolderName The character name, optionally with a sprite folder override, e.g. "folder/expression". |
| 603 | * @param {string} expression The expression label, e.g. "amusement", "joy", etc. | 583 | * @param {string} expression The expression label, e.g. "amusement", "joy", etc. |
| 604 | * @param {Object} [options] Additional options | 584 | * @param {Object} [options] Additional options |
| 605 | * @param {boolean} [options.force=false] If true, the expression will be sent even if it is the same as the current expression. | 585 | * @param {boolean} [options.force=false] If true, the expression will be sent even if it is the same as the current expression. |
| 606 | * @param {boolean} [options.vnMode=null] If true, the expression will be sent in Visual Novel mode. If null, it will be determined by the current chat mode. | 586 | * @param {boolean} [options.vnMode=null] If true, the expression will be sent in Visual Novel mode. If null, it will be determined by the current chat mode. |
| 607 | * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name. | 587 | * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name. |
| 608 | */ | 588 | */ |
| 609 | async function sendExpressionCall(name, expression, { force = false, vnMode = null, overrideSpriteFile = null } = {}) { | 589 | async function sendExpressionCall(spriteFolderName, expression, { force = false, vnMode = null, overrideSpriteFile = null } = {}) { |
| 610 | lastExpression[name.split('/')[0]] = expression; | 590 | lastExpression[spriteFolderName.split('/')[0]] = expression; |
| 611 | if (vnMode === null) { | 591 | if (vnMode === null) { |
| 612 | vnMode = isVisualNovelMode(); | 592 | vnMode = isVisualNovelMode(); |
| 613 | } | 593 | } |
| 614 | 594 | ||
| 615 | if (vnMode) { | 595 | if (vnMode) { |
| 616 | await updateVisualNovelMode(name, expression); | 596 | await updateVisualNovelMode(spriteFolderName, expression); |
| 617 | } else { | 597 | } else { |
| 618 | setExpression(name, expression, { force: force, overrideSpriteFile: overrideSpriteFile }); | 598 | setExpression(spriteFolderName, expression, { force: force, overrideSpriteFile: overrideSpriteFile }); |
| 619 | } | 599 | } |
| 620 | } | 600 | } |
| 621 | 601 | ||
| @@ -679,7 +659,7 @@ async function setSpriteSlashCommand({ type }, searchTerm) { | |||
| 679 | await validateImages(spriteFolderName); | 659 | await validateImages(spriteFolderName); |
| 680 | 660 | ||
| 681 | // Handle reset as a special term and just reset the sprite via expression call | 661 | // Handle reset as a special term and just reset the sprite via expression call |
| 682 | if (searchTerm === '#reset') { | 662 | if (searchTerm === RESET_SPRITE_LABEL) { |
| 683 | await sendExpressionCall(spriteFolderName, label, { force: true }); | 663 | await sendExpressionCall(spriteFolderName, label, { force: true }); |
| 684 | return lastExpression[spriteFolderName] ?? ''; | 664 | return lastExpression[spriteFolderName] ?? ''; |
| 685 | } | 665 | } |
| @@ -933,12 +913,12 @@ function onTextGenSettingsReady(args) { | |||
| 933 | * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification. | 913 | * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification. |
| 934 | * @param {object} [options={}] - Optional arguments. | 914 | * @param {object} [options={}] - Optional arguments. |
| 935 | * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification. | 915 | * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification. |
| 936 | * @returns {Promise<string>} - The label of the expression. | 916 | * @returns {Promise<string?>} - The label of the expression. |
| 937 | */ | 917 | */ |
| 938 | export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) { | 918 | export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) { |
| 939 | // Return if text is undefined, saving a costly fetch request | 919 | // Return if text is undefined, saving a costly fetch request |
| 940 | if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) { | 920 | if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) { |
| 941 | return getFallbackExpression(); | 921 | return extension_settings.expressions.fallback_expression; |
| 942 | } | 922 | } |
| 943 | 923 | ||
| 944 | if (extension_settings.expressions.translate && typeof window['translate'] === 'function') { | 924 | if (extension_settings.expressions.translate && typeof window['translate'] === 'function') { |
| @@ -968,7 +948,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | |||
| 968 | await waitUntilCondition(() => online_status !== 'no_connection', 3000, 250); | 948 | await waitUntilCondition(() => online_status !== 'no_connection', 3000, 250); |
| 969 | } catch (error) { | 949 | } catch (error) { |
| 970 | console.warn('No LLM connection. Using fallback expression', error); | 950 | console.warn('No LLM connection. Using fallback expression', error); |
| 971 | return getFallbackExpression(); | 951 | return extension_settings.expressions.fallback_expression; |
| 972 | } | 952 | } |
| 973 | 953 | ||
| 974 | const expressionsList = await getExpressionsList(); | 954 | const expressionsList = await getExpressionsList(); |
| @@ -981,7 +961,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | |||
| 981 | case EXPRESSION_API.webllm: { | 961 | case EXPRESSION_API.webllm: { |
| 982 | if (!isWebLlmSupported()) { | 962 | if (!isWebLlmSupported()) { |
| 983 | console.warn('WebLLM is not supported. Using fallback expression'); | 963 | console.warn('WebLLM is not supported. Using fallback expression'); |
| 984 | return getFallbackExpression(); | 964 | return extension_settings.expressions.fallback_expression; |
| 985 | } | 965 | } |
| 986 | 966 | ||
| 987 | const expressionsList = await getExpressionsList(); | 967 | const expressionsList = await getExpressionsList(); |
| @@ -1014,9 +994,9 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | |||
| 1014 | } break; | 994 | } break; |
| 1015 | } | 995 | } |
| 1016 | } catch (error) { | 996 | } catch (error) { |
| 1017 | toastr.info('Could not classify expression. Check the console or your backend for more information.'); | 997 | toastr.error('Could not classify expression. Check the console or your backend for more information.'); |
| 1018 | console.error(error); | 998 | console.error(error); |
| 1019 | return getFallbackExpression(); | 999 | return extension_settings.expressions.fallback_expression; |
| 1020 | } | 1000 | } |
| 1021 | } | 1001 | } |
| 1022 | 1002 | ||
| @@ -1231,13 +1211,20 @@ async function renderFallbackExpressionPicker() { | |||
| 1231 | const defaultPicker = $('#expression_fallback'); | 1211 | const defaultPicker = $('#expression_fallback'); |
| 1232 | defaultPicker.empty(); | 1212 | defaultPicker.empty(); |
| 1233 | 1213 | ||
| 1234 | const fallbackExpression = getFallbackExpression(); | 1214 | |
| 1215 | addOption(OPTION_NO_FALLBACK, '[ No fallback ]', !extension_settings.expressions.fallback_expression); | ||
| 1216 | addOption(OPTION_EMOJI_FALLBACK, '[ Default emojis ]', !!extension_settings.expressions.showDefault); | ||
| 1235 | 1217 | ||
| 1236 | for (const expression of expressions) { | 1218 | for (const expression of expressions) { |
| 1219 | addOption(expression, expression, expression == extension_settings.expressions.fallback_expression); | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | /** @type {(value: string, label: string, isSelected: boolean) => void} */ | ||
| 1223 | function addOption(value, label, isSelected) { | ||
| 1237 | const option = document.createElement('option'); | 1224 | const option = document.createElement('option'); |
| 1238 | option.value = expression; | 1225 | option.value = value; |
| 1239 | option.text = expression; | 1226 | option.text = label; |
| 1240 | option.selected = expression == fallbackExpression; | 1227 | option.selected = isSelected; |
| 1241 | defaultPicker.append(option); | 1228 | defaultPicker.append(option); |
| 1242 | } | 1229 | } |
| 1243 | } | 1230 | } |
| @@ -1326,12 +1313,19 @@ export async function getExpressionsList() { | |||
| 1326 | * @param {object} [options] - Options to select the sprite | 1313 | * @param {object} [options] - Options to select the sprite |
| 1327 | * @param {string} [options.prevExpressionSrc=null] - The source of the previous expression | 1314 | * @param {string} [options.prevExpressionSrc=null] - The source of the previous expression |
| 1328 | * @param {string} [options.overrideSpriteFile=null] - The file name of the sprite to select | 1315 | * @param {string} [options.overrideSpriteFile=null] - The file name of the sprite to select |
| 1329 | * @returns {ExpressionImage} - The selected sprite | 1316 | * @returns {ExpressionImage?} - The selected sprite |
| 1330 | */ | 1317 | */ |
| 1331 | function chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc = null, overrideSpriteFile = null } = {}) { | 1318 | function chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc = null, overrideSpriteFile = null } = {}) { |
| 1332 | const sprite = (spriteCache[spriteFolderName] && spriteCache[spriteFolderName].find(x => x.label === expression)); | 1319 | if (!spriteCache[spriteFolderName]) return null; |
| 1333 | if (!(sprite?.files.length > 0)) | 1320 | if (expression === RESET_SPRITE_LABEL) return null; |
| 1334 | return null; | 1321 | |
| 1322 | // Search for sprites of that expression - or fallback expression sprites if enabled | ||
| 1323 | let sprite = spriteCache[spriteFolderName].find(x => x.label === expression); | ||
| 1324 | if (!(sprite?.files.length > 0) && extension_settings.expressions.fallback_expression) { | ||
| 1325 | sprite = spriteCache[spriteFolderName].find(x => x.label === extension_settings.expressions.fallback_expression); | ||
| 1326 | console.debug('Expression', expression, 'not found. Using fallback expression', extension_settings.expressions.fallback_expression); | ||
| 1327 | } | ||
| 1328 | if (!(sprite?.files.length > 0)) return null; | ||
| 1335 | 1329 | ||
| 1336 | let spriteFile = sprite.files[0]; | 1330 | let spriteFile = sprite.files[0]; |
| 1337 | 1331 | ||
| @@ -1397,6 +1391,10 @@ async function setExpression(spriteFolderName, expression, { force = false, over | |||
| 1397 | expressionClone.attr('id', '').css({ opacity: 0 }); | 1391 | expressionClone.attr('id', '').css({ opacity: 0 }); |
| 1398 | //add new sprite path to clone src | 1392 | //add new sprite path to clone src |
| 1399 | expressionClone.attr('src', spriteFile.imageSrc); | 1393 | expressionClone.attr('src', spriteFile.imageSrc); |
| 1394 | //set relevant data tags | ||
| 1395 | expressionClone.attr('data-sprite-folder-name', spriteFolderName); | ||
| 1396 | expressionClone.attr('data-expression', expression); | ||
| 1397 | expressionClone.attr('data-sprite-filename', spriteFile.fileName); | ||
| 1400 | //add invisible clone to html | 1398 | //add invisible clone to html |
| 1401 | expressionClone.appendTo($('#expression-holder')); | 1399 | expressionClone.appendTo($('#expression-holder')); |
| 1402 | 1400 | ||
| @@ -1442,12 +1440,12 @@ async function setExpression(spriteFolderName, expression, { force = false, over | |||
| 1442 | 1440 | ||
| 1443 | expressionClone.removeClass('default'); | 1441 | expressionClone.removeClass('default'); |
| 1444 | expressionClone.off('error'); | 1442 | expressionClone.off('error'); |
| 1445 | expressionClone.on('error', function () { | 1443 | expressionClone.on('error', function (error) { |
| 1446 | console.debug('Expression image error', spriteFile.imageSrc); | 1444 | console.debug('Expression image error', spriteFile.imageSrc, error); |
| 1447 | $(this).attr('src', ''); | 1445 | $(this).attr('src', ''); |
| 1448 | $(this).off('error'); | 1446 | $(this).off('error'); |
| 1449 | if (force && extension_settings.expressions.showDefault) { | 1447 | if (force && extension_settings.expressions.showDefault) { |
| 1450 | setDefault(); | 1448 | setDefaultEmojiForImage(img, expression); |
| 1451 | } | 1449 | } |
| 1452 | }); | 1450 | }); |
| 1453 | } | 1451 | } |
| @@ -1455,30 +1453,52 @@ async function setExpression(spriteFolderName, expression, { force = false, over | |||
| 1455 | console.info('Expression set', { expression: spriteFile.expression, file: spriteFile.fileName }); | 1453 | console.info('Expression set', { expression: spriteFile.expression, file: spriteFile.fileName }); |
| 1456 | } | 1454 | } |
| 1457 | else { | 1455 | else { |
| 1458 | if (extension_settings.expressions.showDefault) { | 1456 | img.attr('data-sprite-folder-name', spriteFolderName); |
| 1459 | setDefault(); | 1457 | img.attr('data-expression', expression); |
| 1458 | |||
| 1459 | $(img).off('error'); | ||
| 1460 | |||
| 1461 | if (extension_settings.expressions.showDefault && expression !== RESET_SPRITE_LABEL) { | ||
| 1462 | setDefaultEmojiForImage(img, expression); | ||
| 1460 | } else { | 1463 | } else { |
| 1461 | setNone(); | 1464 | setNoneForImage(img, expression); |
| 1462 | } | 1465 | } |
| 1463 | console.debug('Expression unset - No sprite found', { expression: expression }); | 1466 | console.debug('Expression unset - No sprite found', { expression: expression }); |
| 1464 | } | 1467 | } |
| 1465 | 1468 | ||
| 1466 | function setDefault() { | 1469 | document.getElementById('expression-holder').style.display = ''; |
| 1467 | console.debug('setting default expression'); | 1470 | } |
| 1471 | |||
| 1472 | /** | ||
| 1473 | * Sets the default expression image for the given image element and expression | ||
| 1474 | * @param {JQuery<HTMLElement>} img - The image element to set the default expression for | ||
| 1475 | * @param {string} expression - The expression label to use for the default image | ||
| 1476 | */ | ||
| 1477 | function setDefaultEmojiForImage(img, expression) { | ||
| 1478 | if (extension_settings.expressions.custom?.includes(expression)) { | ||
| 1479 | console.debug(`Can't set default emoji for a custom expression (${expression}). setting to ${DEFAULT_FALLBACK_EXPRESSION} instead.`); | ||
| 1480 | expression = DEFAULT_FALLBACK_EXPRESSION; | ||
| 1481 | } | ||
| 1482 | |||
| 1468 | const defImgUrl = `/img/default-expressions/${expression}.png`; | 1483 | const defImgUrl = `/img/default-expressions/${expression}.png`; |
| 1469 | //console.log(defImgUrl); | ||
| 1470 | img.attr('src', defImgUrl); | 1484 | img.attr('src', defImgUrl); |
| 1485 | img.attr('data-expression', expression); | ||
| 1486 | img.attr('data-sprite-filename', null); | ||
| 1471 | img.addClass('default'); | 1487 | img.addClass('default'); |
| 1472 | } | 1488 | } |
| 1473 | function setNone() { | 1489 | |
| 1474 | console.debug('setting no expression'); | 1490 | /** |
| 1491 | * Sets the image element to display no expression by clearing its source attribute. | ||
| 1492 | * @param {JQuery<HTMLElement>} img - The image element to clear the expression for | ||
| 1493 | * @param {string} expression - The expression label to use | ||
| 1494 | */ | ||
| 1495 | function setNoneForImage(img, expression) { | ||
| 1475 | img.attr('src', ''); | 1496 | img.attr('src', ''); |
| 1497 | img.attr('data-expression', expression); | ||
| 1498 | img.attr('data-sprite-filename', null); | ||
| 1476 | img.removeClass('default'); | 1499 | img.removeClass('default'); |
| 1477 | } | 1500 | } |
| 1478 | 1501 | ||
| 1479 | document.getElementById('expression-holder').style.display = ''; | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | function onClickExpressionImage() { | 1502 | function onClickExpressionImage() { |
| 1483 | // If there is no expression image and we clicked on the placeholder, we remove the sprite by calling via the expression label | 1503 | // If there is no expression image and we clicked on the placeholder, we remove the sprite by calling via the expression label |
| 1484 | if ($(this).attr('data-expression-type') === 'failure') { | 1504 | if ($(this).attr('data-expression-type') === 'failure') { |
| @@ -1504,19 +1524,15 @@ async function onClickExpressionAddCustom() { | |||
| 1504 | 1524 | ||
| 1505 | // a-z, 0-9, dashes and underscores only | 1525 | // a-z, 0-9, dashes and underscores only |
| 1506 | if (!/^[a-z0-9-_]+$/.test(expressionName)) { | 1526 | if (!/^[a-z0-9-_]+$/.test(expressionName)) { |
| 1507 | toastr.info('Invalid custom expression name provided'); | 1527 | toastr.warning('Invalid custom expression name provided', 'Add Custom Expression'); |
| 1508 | return; | 1528 | return; |
| 1509 | } | 1529 | } |
| 1510 | |||
| 1511 | // Check if expression name already exists in default expressions | ||
| 1512 | if (DEFAULT_EXPRESSIONS.includes(expressionName)) { | 1530 | if (DEFAULT_EXPRESSIONS.includes(expressionName)) { |
| 1513 | toastr.info('Expression name already exists'); | 1531 | toastr.warning('Expression name already exists', 'Add Custom Expression'); |
| 1514 | return; | 1532 | return; |
| 1515 | } | 1533 | } |
| 1516 | |||
| 1517 | // Check if expression name already exists in custom expressions | ||
| 1518 | if (extension_settings.expressions.custom.includes(expressionName)) { | 1534 | if (extension_settings.expressions.custom.includes(expressionName)) { |
| 1519 | toastr.info('Custom expression already exists'); | 1535 | toastr.warning('Custom expression already exists', 'Add Custom Expression'); |
| 1520 | return; | 1536 | return; |
| 1521 | } | 1537 | } |
| 1522 | 1538 | ||
| @@ -1550,8 +1566,8 @@ async function onClickExpressionRemoveCustom() { | |||
| 1550 | // Remove custom expression from settings | 1566 | // Remove custom expression from settings |
| 1551 | const index = extension_settings.expressions.custom.indexOf(selectedExpression); | 1567 | const index = extension_settings.expressions.custom.indexOf(selectedExpression); |
| 1552 | extension_settings.expressions.custom.splice(index, 1); | 1568 | extension_settings.expressions.custom.splice(index, 1); |
| 1553 | if (selectedExpression == getFallbackExpression()) { | 1569 | if (selectedExpression == extension_settings.expressions.fallback_expression) { |
| 1554 | toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`); | 1570 | toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`, 'Remove Custom Expression'); |
| 1555 | extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION; | 1571 | extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION; |
| 1556 | } | 1572 | } |
| 1557 | await renderAdditionalExpressionSettings(); | 1573 | await renderAdditionalExpressionSettings(); |
| @@ -1575,12 +1591,35 @@ function onExpressionApiChanged() { | |||
| 1575 | } | 1591 | } |
| 1576 | } | 1592 | } |
| 1577 | 1593 | ||
| 1578 | function onExpressionFallbackChanged() { | 1594 | async function onExpressionFallbackChanged() { |
| 1579 | const expression = this.value; | 1595 | /** @type {HTMLSelectElement} */ |
| 1580 | if (expression) { | 1596 | const select = this; |
| 1581 | extension_settings.expressions.fallback_expression = expression; | 1597 | const selectedValue = select.value; |
| 1582 | saveSettingsDebounced(); | 1598 | |
| 1599 | switch (selectedValue) { | ||
| 1600 | case OPTION_NO_FALLBACK: | ||
| 1601 | extension_settings.expressions.fallback_expression = null; | ||
| 1602 | extension_settings.expressions.showDefault = false; | ||
| 1603 | break; | ||
| 1604 | case OPTION_EMOJI_FALLBACK: | ||
| 1605 | extension_settings.expressions.fallback_expression = null; | ||
| 1606 | extension_settings.expressions.showDefault = true; | ||
| 1607 | break; | ||
| 1608 | default: | ||
| 1609 | extension_settings.expressions.fallback_expression = selectedValue; | ||
| 1610 | extension_settings.expressions.showDefault = false; | ||
| 1611 | break; | ||
| 1583 | } | 1612 | } |
| 1613 | |||
| 1614 | const img = $('img.expression'); | ||
| 1615 | const spriteFolderName = img.attr('data-sprite-folder-name'); | ||
| 1616 | const expression = img.attr('data-expression'); | ||
| 1617 | |||
| 1618 | if (spriteFolderName && expression) { | ||
| 1619 | await sendExpressionCall(spriteFolderName, expression, { force: true }); | ||
| 1620 | } | ||
| 1621 | |||
| 1622 | saveSettingsDebounced(); | ||
| 1584 | } | 1623 | } |
| 1585 | 1624 | ||
| 1586 | async function handleFileUpload(url, formData) { | 1625 | async function handleFileUpload(url, formData) { |
| @@ -1924,6 +1963,16 @@ function migrateSettings() { | |||
| 1924 | extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT; | 1963 | extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT; |
| 1925 | saveSettingsDebounced(); | 1964 | saveSettingsDebounced(); |
| 1926 | } | 1965 | } |
| 1966 | |||
| 1967 | if (extension_settings.expressions.allowMultiple === undefined) { | ||
| 1968 | extension_settings.expressions.allowMultiple = true; | ||
| 1969 | saveSettingsDebounced(); | ||
| 1970 | } | ||
| 1971 | |||
| 1972 | if (extension_settings.expressions.showDefault && extension_settings.expressions.fallback_expression !== undefined) { | ||
| 1973 | extension_settings.expressions.showDefault = false; | ||
| 1974 | saveSettingsDebounced(); | ||
| 1975 | } | ||
| 1927 | } | 1976 | } |
| 1928 | 1977 | ||
| 1929 | (async function () { | 1978 | (async function () { |
| @@ -1950,9 +1999,7 @@ function migrateSettings() { | |||
| 1950 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings'); | 1999 | const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings'); |
| 1951 | $('#expressions_container').append(template); | 2000 | $('#expressions_container').append(template); |
| 1952 | $('#expression_override_button').on('click', onClickExpressionOverrideButton); | 2001 | $('#expression_override_button').on('click', onClickExpressionOverrideButton); |
| 1953 | $('#expressions_show_default').on('input', onExpressionsShowDefaultInput); | ||
| 1954 | $('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton); | 2002 | $('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton); |
| 1955 | $('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input'); | ||
| 1956 | $('#expression_translate').prop('checked', extension_settings.expressions.translate).on('input', function () { | 2003 | $('#expression_translate').prop('checked', extension_settings.expressions.translate).on('input', function () { |
| 1957 | extension_settings.expressions.translate = !!$(this).prop('checked'); | 2004 | extension_settings.expressions.translate = !!$(this).prop('checked'); |
| 1958 | saveSettingsDebounced(); | 2005 | saveSettingsDebounced(); |
| @@ -2080,7 +2127,7 @@ function migrateSettings() { | |||
| 2080 | if (type == 'sprite') return localEnumProviders.sprites(); | 2127 | if (type == 'sprite') return localEnumProviders.sprites(); |
| 2081 | else return [ | 2128 | else return [ |
| 2082 | ...localEnumProviders.expressions(), | 2129 | ...localEnumProviders.expressions(), |
| 2083 | new SlashCommandEnumValue('#reset', 'Resets the expression (to either default or no sprite)', enumTypes.enum, '❌'), | 2130 | new SlashCommandEnumValue(RESET_SPRITE_LABEL, 'Resets the expression (to either default or no sprite)', enumTypes.enum, '❌'), |
| 2084 | ]; | 2131 | ]; |
| 2085 | }, | 2132 | }, |
| 2086 | }), | 2133 | }), |
| @@ -10,10 +10,6 @@ | |||
| 10 | <input id="expression_translate" type="checkbox"> | 10 | <input id="expression_translate" type="checkbox"> |
| 11 | <span data-i18n="Translate text to English before classification">Translate text to English before classification</span> | 11 | <span data-i18n="Translate text to English before classification">Translate text to English before classification</span> |
| 12 | </label> | 12 | </label> |
| 13 | <label class="checkbox_label" for="expressions_show_default"> | ||
| 14 | <input id="expressions_show_default" type="checkbox"> | ||
| 15 | <span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span> | ||
| 16 | </label> | ||
| 17 | <label class="checkbox_label" for="expressions_allow_multiple" title="A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected." data-i18n="[title]A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected."> | 13 | <label class="checkbox_label" for="expressions_allow_multiple" title="A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected." data-i18n="[title]A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected."> |
| 18 | <input id="expressions_allow_multiple" type="checkbox"> | 14 | <input id="expressions_allow_multiple" type="checkbox"> |
| 19 | <span data-i18n="Allow multiple sprites per expression">Allow multiple sprites per expression</span> | 15 | <span data-i18n="Allow multiple sprites per expression">Allow multiple sprites per expression</span> |