This commit is contained in:
Bryan Ashby 2023-02-20 16:12:07 -07:00
commit 30cb21f092
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
13 changed files with 382 additions and 47 deletions

Binary file not shown.

View File

@ -498,6 +498,9 @@
width: 35 width: 35
itemFormat: "|00|03{subject}|00 {statusIndicator}" itemFormat: "|00|03{subject}|00 {statusIndicator}"
focusItemFormat: "|00|19|15{subject!styleUpper}|00 {statusIndicator}" focusItemFormat: "|00|19|15{subject!styleUpper}|00 {statusIndicator}"
itemFormat: "|00{statusIndicator} |00|03{subject}"
focusItemFormat: "|00{statusIndicator} |00|19|15{subject}"
textOverflow: "..."
} }
MT2: { MT2: {
height: 15 height: 15

View File

@ -303,6 +303,9 @@ exports.getModule = class ActivityPubActorSearch extends MenuModule {
async.series( async.series(
[ [
callback => { callback => {
if (this.viewControllers.view) {
this.viewControllers.view.setFocus(false);
}
return this.displayArtAndPrepViewController( return this.displayArtAndPrepViewController(
'main', 'main',
FormIds.main, FormIds.main,

View File

@ -59,7 +59,7 @@ function FullMenuView(options) {
} }
for (let i = 0; i < this.dimens.height; i++) { for (let i = 0; i < this.dimens.height; i++) {
const text = `${strUtil.pad(this.fillChar, width, this.fillChar, 'left')}`; const text = strUtil.pad('', width, this.fillChar);
this.client.term.write( this.client.term.write(
`${ansi.goto( `${ansi.goto(
this.position.row + i, this.position.row + i,
@ -77,6 +77,7 @@ function FullMenuView(options) {
this.autoAdjustHeightIfEnabled(); this.autoAdjustHeightIfEnabled();
this.pages = []; // reset this.pages = []; // reset
this.currentPage = 0; // reset currentPage when pages reset
// Calculate number of items visible per column // Calculate number of items visible per column
this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1)); this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1));
@ -240,14 +241,25 @@ function FullMenuView(options) {
sgr = index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR(); sgr = index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR();
} }
let renderLength = strUtil.renderStringLength(text); const renderLength = strUtil.renderStringLength(text);
if (this.hasTextOverflow() && item.col + renderLength > this.dimens.width) {
text = let relativeColumn = item.col - this.position.col;
strUtil.renderSubstr( if (relativeColumn < 0) {
text, relativeColumn = 0;
0, this.client.log.warn(
this.dimens.width - (item.col + this.textOverflow.length) { itemCol: item.col, positionColumn: this.position.col },
) + this.textOverflow; 'Invalid item column detected in full menu'
);
}
if (relativeColumn + renderLength > this.dimens.width) {
if (this.hasTextOverflow()) {
text = strUtil.renderTruncate(text, {
length:
this.dimens.width - (relativeColumn + this.textOverflow.length),
omission: this.textOverflow,
});
}
} }
let padLength = Math.min(item.fixedLength + 1, this.dimens.width); let padLength = Math.min(item.fixedLength + 1, this.dimens.width);
@ -270,7 +282,21 @@ FullMenuView.prototype.redraw = function () {
this.cachePositions(); this.cachePositions();
// In case we get in a bad state, try to recover
if (this.currentPage < 0) {
this.currentPage = 0;
}
if (this.items.length) { if (this.items.length) {
if (
this.currentPage > this.pages.length ||
!_.isObject(this.pages[this.currentPage])
) {
this.client.log.warn(
{ currentPage: this.currentPage, pagesLength: this.pages.length },
'Invalid state! in full menu redraw'
);
} else {
for ( for (
let i = this.pages[this.currentPage].start; let i = this.pages[this.currentPage].start;
i <= this.pages[this.currentPage].end; i <= this.pages[this.currentPage].end;
@ -280,6 +306,7 @@ FullMenuView.prototype.redraw = function () {
this.drawItem(i); this.drawItem(i);
} }
} }
}
}; };
FullMenuView.prototype.setHeight = function (height) { FullMenuView.prototype.setHeight = function (height) {
@ -358,6 +385,10 @@ FullMenuView.prototype.setItems = function (items) {
this.oldDimens = Object.assign({}, this.dimens); this.oldDimens = Object.assign({}, this.dimens);
} }
// Reset the page on new items
this.currentPage = 0;
this.focusedItemIndex = 0;
FullMenuView.super_.prototype.setItems.call(this, items); FullMenuView.super_.prototype.setItems.call(this, items);
this.positionCacheExpired = true; this.positionCacheExpired = true;
@ -377,6 +408,15 @@ FullMenuView.prototype.focusNext = function () {
this.clearPage(); this.clearPage();
this.focusedItemIndex = 0; this.focusedItemIndex = 0;
this.currentPage = 0; this.currentPage = 0;
} else {
if (
this.currentPage > this.pages.length ||
!_.isObject(this.pages[this.currentPage])
) {
this.client.log.warn(
{ currentPage: this.currentPage, pagesLength: this.pages.length },
'Invalid state in focusNext for full menu view'
);
} else { } else {
this.focusedItemIndex++; this.focusedItemIndex++;
if (this.focusedItemIndex > this.pages[this.currentPage].end) { if (this.focusedItemIndex > this.pages[this.currentPage].end) {
@ -384,6 +424,7 @@ FullMenuView.prototype.focusNext = function () {
this.currentPage++; this.currentPage++;
} }
} }
}
this.redraw(); this.redraw();
@ -397,11 +438,21 @@ FullMenuView.prototype.focusPrevious = function () {
this.currentPage = this.pages.length - 1; this.currentPage = this.pages.length - 1;
} else { } else {
this.focusedItemIndex--; this.focusedItemIndex--;
if (
this.currentPage > this.pages.length ||
!_.isObject(this.pages[this.currentPage])
) {
this.client.log.warn(
{ currentPage: this.currentPage, pagesLength: this.pages.length },
'Bad focus state, ignoring call to focusPrevious.'
);
} else {
if (this.focusedItemIndex < this.pages[this.currentPage].start) { if (this.focusedItemIndex < this.pages[this.currentPage].start) {
this.clearPage(); this.clearPage();
this.currentPage--; this.currentPage--;
} }
} }
}
this.redraw(); this.redraw();

View File

@ -250,6 +250,7 @@ function buildNewConfig() {
'new_user.in.hjson', 'new_user.in.hjson',
'doors.in.hjson', 'doors.in.hjson',
'file_base.in.hjson', 'file_base.in.hjson',
'activitypub.in.hjson',
]; ];
let includeFiles = []; let includeFiles = [];

View File

@ -145,29 +145,29 @@ function pad(s, len, padChar, justify, stringSGR, padSGR, useRenderLen = true) {
padSGR = padSGR || ''; padSGR = padSGR || '';
const renderLen = useRenderLen ? renderStringLength(s) : s.length; const renderLen = useRenderLen ? renderStringLength(s) : s.length;
const padlen = len >= renderLen ? len - renderLen : 0; const padLen = len > renderLen ? len - renderLen : 0;
switch (justify) { switch (justify) {
case 'L': case 'L':
case 'left': case 'left':
s = `${stringSGR}${s}${padSGR}${Array(padlen).join(padChar)}`; s = `${stringSGR}${s}${padSGR}${padChar.repeat(padLen)}`;
break; break;
case 'C': case 'C':
case 'center': case 'center':
case 'both': case 'both':
{ {
const right = Math.ceil(padlen / 2); const right = Math.ceil(padLen / 2);
const left = padlen - right; const left = padLen - right;
s = `${padSGR}${Array(left + 1).join( s = `${padSGR}${Array(left + 1).join(
padChar padChar
)}${stringSGR}${s}${padSGR}${Array(right + 1).join(padChar)}`; )}${stringSGR}${s}${padSGR}${padChar.repeat(right)}`;
} }
break; break;
case 'R': case 'R':
case 'right': case 'right':
s = `${padSGR}${Array(padlen).join(padChar)}${stringSGR}${s}`; s = `${padSGR}${padChar.repeat(padLen)}${stringSGR}${s}`;
break; break;
default: default:

View File

@ -98,10 +98,12 @@ function VerticalMenuView(options) {
sgr = index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR(); sgr = index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR();
} }
if (this.hasTextOverflow()) {
text = strUtil.renderTruncate(text, { text = strUtil.renderTruncate(text, {
length: this.dimens.width, length: this.dimens.width,
omission: this.truncateOmission, omission: this.textOverflow,
}); });
}
text = `${sgr}${strUtil.pad( text = `${sgr}${strUtil.pad(
`${text}${this.styleSGR1}`, `${text}${this.styleSGR1}`,
@ -143,18 +145,16 @@ VerticalMenuView.prototype.redraw = function () {
// erase old items // erase old items
// :TODO: optimize this: only needed if a item is removed or new max width < old. // :TODO: optimize this: only needed if a item is removed or new max width < old.
if (this.oldDimens) { if (this.oldDimens) {
const blank = new Array(Math.max(this.oldDimens.width, this.dimens.width)).join( const blank = ' '.repeat(Math.max(this.oldDimens.width, this.dimens.width));
' ' let row = this.position.row;
);
let seq = ansi.goto(this.position.row, this.position.col) + this.getSGR() + blank;
let row = this.position.row + 1;
const endRow = row + this.oldDimens.height - 2; const endRow = row + this.oldDimens.height - 2;
while (row <= endRow) { while (row <= endRow) {
seq += ansi.goto(row, this.position.col) + blank; this.client.term.write(
ansi.goto(row, this.position.col) + this.getSGR() + blank
);
row += 1; row += 1;
} }
this.client.term.write(seq);
delete this.oldDimens; delete this.oldDimens;
} }
@ -247,6 +247,7 @@ VerticalMenuView.prototype.setItems = function (items) {
if (this.items && this.items.length) { if (this.items && this.items.length) {
this.oldDimens = Object.assign({}, this.dimens); this.oldDimens = Object.assign({}, this.dimens);
} }
this.focusedItemIndex = 0;
VerticalMenuView.super_.prototype.setItems.call(this, items); VerticalMenuView.super_.prototype.setItems.call(this, items);
@ -406,6 +407,12 @@ VerticalMenuView.prototype.focusLast = function () {
return VerticalMenuView.super_.prototype.focusLast.call(this); return VerticalMenuView.super_.prototype.focusLast.call(this);
}; };
VerticalMenuView.prototype.setTextOverflow = function (overflow) {
VerticalMenuView.super_.prototype.setTextOverflow.call(this, overflow);
this.positionCacheExpired = true;
};
VerticalMenuView.prototype.setFocusItems = function (items) { VerticalMenuView.prototype.setFocusItems = function (items) {
VerticalMenuView.super_.prototype.setFocusItems.call(this, items); VerticalMenuView.super_.prototype.setFocusItems.call(this, items);

View File

@ -50,7 +50,6 @@ function View(options) {
this.textStyle = options.textStyle || 'normal'; this.textStyle = options.textStyle || 'normal';
this.focusTextStyle = options.focusTextStyle || this.textStyle; this.focusTextStyle = options.focusTextStyle || this.textStyle;
this.offsetsApplied = false; this.offsetsApplied = false;
this.truncateOmission = options.truncateOmission || '';
if (options.id) { if (options.id) {
this.setId(options.id); this.setId(options.id);
@ -274,12 +273,6 @@ View.prototype.setPropertyValue = function (propName, value) {
this.validate = value; this.validate = value;
} }
break; break;
case 'truncateOmission':
if (_.isString(value)) {
this.truncateOmission = value;
}
break;
} }
if (/styleSGR[0-9]{1,2}/.test(propName)) { if (/styleSGR[0-9]{1,2}/.test(propName)) {

View File

@ -29,9 +29,9 @@ Items can be selected on a menu via the cursor keys, Page Up, Page Down, Home, a
| `justify` | Sets the justification of each item in the list. Options: left (default), right, center | | `justify` | Sets the justification of each item in the list. Options: left (default), right, center |
| `itemFormat` | Sets the format for a list entry. See **Entry Formatting** in [MCI](../mci.md) | | `itemFormat` | Sets the format for a list entry. See **Entry Formatting** in [MCI](../mci.md) |
| `fillChar` | Specifies a character to fill extra space in the menu with. Defaults to an empty space | | `fillChar` | Specifies a character to fill extra space in the menu with. Defaults to an empty space |
| `textOverflow` | If an entry cannot be displayed due to `width`, set overflow characters. See **Text Overflow** below |
| `items` | List of items to show in the menu. See **Items** below. | `items` | List of items to show in the menu. See **Items** below.
| `focusItemFormat` | Sets the format for a focused list entry. See **Entry Formatting** in [MCI](../mci.md) | | `focusItemFormat` | Sets the format for a focused list entry. See **Entry Formatting** in [MCI](../mci.md) |
| `truncateOmission` | Sets the omission characters for truncated text if used. Defaults to an empty string. Commonly set to "..." |
### Hot Keys ### Hot Keys
@ -70,6 +70,15 @@ If the list is for display only (there is no form action associated with it) you
["First item", "Second item", "Third Item"] ["First item", "Second item", "Third Item"]
``` ```
### Text Overflow
The `textOverflow` option is used to specify what happens when a text string is too long to fit in the `width` defined. If an entry is too long to display in the width specified
> :information_source: If `textOverflow` is not specified at all, a menu can become wider than the `width` if needed to display a single column.
> :information_source: Setting `textOverflow` to an empty string `textOverflow: ""` will cause the item to be truncated if necessary without any characters displayed
> :information_source: Otherwise, setting `textOverflow` to one or more characters will truncate the value if necessary and display those characters at the end. i.e. `textOverflow: ...`
## Example ## Example

View File

@ -0,0 +1,264 @@
{
"menus": {
"activityPubMenu": {
"desc": "Menu for all ActivityPub actions",
"art": "activitypub_menu",
"prompt": "menuCommand",
"submit": [
{
"value": {
"command": "S"
},
"action": "@menu:activityPubActorSearch"
},
{
"value": {
"command": "C"
},
"action": "@menu:activityPubUserConfig"
},
{
"value": {
"command": "M"
},
"action": "@menu:activityPubFollowingManager"
},
{
"value": {
"command": "Q"
},
"action": "@menu:mainMenu"
},
]
},
"activityPubActorSearch": {
"desc": "Viewing ActivityPub",
"module": "activitypub/actor_search",
"config": {
"cls": true,
"art": {
"main": "activitypub_actor_search_main",
"view": "activitypub_actor_view"
}
},
"form": {
"0": {
"mci": {
"ET1": {
"focus": true,
"maxLength": 70,
"argName": "searchQuery",
"submit": true
}
},
"submit": {
"*": [
{
"value": {
"searchQuery": null
},
"action": "@method:search"
}
]
},
"actionKeys": [
{
"keys": [
"escape"
],
"action": "@systemMethod:prevMenu"
}
]
},
"1": {
"mci": {},
"actionKeys": [
{
"keys": [
"escape",
"q",
"shift + q"
],
"action": "@method:backKeyPressed"
},
{
"keys": [
"space"
],
"action": "@method:toggleFollowKeyPressed"
}
]
}
}
},
"activityPubUserConfig": {
"desc": "ActivityPub Config",
"module": "./activitypub/user_config",
"config": {
"art": {
"main": "activitypub_user_config_main",
"images": "activitypub_user_config_images"
}
},
"form": {
"0": {
"mci": {
"TM1": {
"focus": true,
"items": [
"yes",
"no"
],
"argName": "enabled"
},
"TM2": {
"items": [
"yes",
"no"
],
"argName": "manuallyApproveFollowers"
},
"TM3": {
"items": [
"yes",
"no"
],
"argName": "hideSocialGraph"
},
"TM4": {
"items": [
"yes",
"no"
],
"argName": "showRealName"
},
"TL5": {
"argName": "image"
},
"TL6": {
"argName": "icon"
},
"BT7": {
"text": "manage images",
"argName": "manageImages",
"submit": true,
"justify": "center"
},
"TM8": {
"items": [
"save",
"cancel"
],
"submit": true,
"argName": "saveOrCancel"
}
},
"submit": {
"*": [
{
// :TODO: we need a way to just want the argName *for the submitting item* and drop others
"value": {
"manageImages": null
},
"action": "@method:mainSubmit"
}
]
},
"actionKeys": "@reference:common.escToPrev"
},
"1": {
"mci": {
"ML1": {
"argName": "imageUrl",
"mode": "edit",
"scrollMode": "start",
"focus": true,
"tabSwitchesView": true
},
"ML2": {
"argName": "iconUrl",
"mode": "edit",
"scrollMode": "start",
"tabSwitchesView": true
},
"TM3": {
"items": [
"save",
"cancel"
],
"submit": true,
"argName": "imagesSaveOrCancel"
}
},
"submit": {
"*": [
{
"value": {
"imagesSaveOrCancel": null
},
"action": "@method:imagesSubmit"
}
]
},
"actionKeys": [
{
"keys": [
"escape"
],
"action": "@method:backToMain"
}
]
}
}
},
"activityPubFollowingManager": {
"desc": "Social Manager",
"module": "./activitypub/social_manager",
"config": {
"art": {
"main": "activitypub_social_manager"
}
},
"form": {
"0": {
"mci": {
"VM1": {},
"MT2": {
"mode": "preview",
"acceptsFocus": false,
"acceptsInput": false
},
"TM3": {
"focus": true,
"items": [
"following",
"followers"
]
}
},
"actionKeys": [
{
"keys": [
"space"
],
"action": "@method:spaceKeyPressed"
},
{
"keys": [
"down arrow",
"up arrow"
],
"action": "@method:listKeyPressed"
},
{
"keys": [
"escape"
],
"action": "@systemMethod:prevMenu"
}
]
}
}
}
}
}

View File

@ -113,6 +113,10 @@
value: { command: "M" } value: { command: "M" }
action: @menu:messageBaseMainMenu action: @menu:messageBaseMainMenu
} }
{
value: { command: "A" }
action: @menu:activityPubMenu
}
{ {
value: { command: "E" } value: { command: "E" }
action: @menu:privateMailMenu action: @menu:privateMailMenu