runbox7 icon indicating copy to clipboard operation
runbox7 copied to clipboard

feat(a11y-table): Define a standalone table component

Open shadowbas opened this issue 1 year ago • 11 comments

Add table to the app component.

  • [x] Render massive amounts of data (tested with a million records)
  • [x] Advanced (range) select features
  • [x] Keyboard friendly with Tab and Enter.
  • [x] Toggle preview with UI button
  • [x] Use arrow keys to move up and down a message.
  • [x] ~~Use j and k to scroll up and down in messages.~~ Dropped the support for j and k scroll.
  • [x] Allow passing templates to render the cells (allows for more rich components).
  • [x] Make the canvastable inert and not actually render to screen.
  • [x] Drag and drop to folders.
  • [x] Bulk actions on selected items.
  • [x] Select row when navigating on fragment.
  • [x] Show to in sent folder instead of from
  • [x] Remove canvastable code as much as possible.
  • [x] Add the flag icon on flagged messages.
  • [x] Add aria-label to attachment icon.
  • [x] Add aria-label to flagged messages.
  • [x] Fix issue where ctrl+tab causes ctrl flag to stay true.
  • [x] Better styling for narrow screen (break out of the table layout by using different display, considering flex)
  • [x] Should only get the doc data when the item is in view (causing massive amount of requests)
  • [x] Resize-able columns.
  • [x] Fix the width of columns on initial render. This prevents jumping of columns during scroll.
  • [x] Set sane defaults for initial columns widths.
  • [x] Sort on column heading click.
  • [x] Fix to table when index-sync-off in sent folder
  • [x] Double click on column resize separator resets width
  • [x] Figure out how to reset column widths on switching routes
  • [x] Fix bug where last selected item is removed when no longer in view (selects first item)
  • [x] Could we add a more visible/intuitive column divider indicator in the column header?
  • [x] There doesn't seem to be a folder column in search results, which would be useful to have.
  • [x] Fix issue where new rows causes weird scroll jump upward
  • [x] Add answered column
  • [x] Fix issue where columns are very narrow on initial load
  • [x] Reduce font-size in the message list
  • [x] Fix issue where bulk action buttons are no longer visible
  • [x] Remove Direction.None from sort button cycle
  • [x] Fix threaded view
  • [x] Fix tests to work with html table
  • [x] Remove console. calls
  • [x] Autosquash and force push
  • [x] Would be nice to have a sticky table head
  • [x] Remove commented out code
  • [x] Only perform column width reset when resize is vertical
  • [ ] Re add updating index https://github.com/runbox/runbox7/pull/1634/files/bf4ca21fcaea8660712ec9615b82a3da644b9eee#r2163563214
  • [x] Remove the $ postfix for rxjs stream variables

shadowbas avatar Nov 29 '24 12:11 shadowbas

Initial thoughts

  • I'd like to keep the abstraction for the messagedisplay and column lists - for column lists especially we have a plan to allow users to rearrange them, so they should ideally not be defined in the template - canvastable is the only place messagedisplay is used, so it can be improved if needed for this feature
  • messagedisplay for example already has the ability to return selectedMessageIds for each possible type of display, so I don't think it makes sense to redo that (confusingly ;)
  • there's still a fair amount of this.canvastable.rows references in app.component.ts that presumably still need changing

castaway avatar Dec 09 '24 11:12 castaway

Notes while testing/poking around - mostly comparing to existing prod (since we didnt write a spec..) 1. 2025-02-04 11 50 40 rmmstage02 runbox com 1ff05c40c6c9 Clicking on the Arrow seems to do nothing other than make the arrow disappear (does not redisplay sorted date in reverse) - ah! the arrow is just a display of the current direction? It looks like its actionable.. clicking on the arrow is how the current one sorts.

  1. (new change on prod) - the icons for move/trash etc for selected messages - need to disappear or better grey-out when no messages are selected
  2. Is there no "show message list and mailviewer at the same time in horizontal mode"? it seems to go full screen only and not allow me to drag it smaller - I was wrong, there is, it just somehow defaulted to full height (ignored my prefs?)
  3. open message, full height/display mailviewer - refresh page - it looks like the "sync index" prompt appears and then is covered up by the opened message - not sure if this is an issue in prod already, dont usually view it full height
  4. Drag messages to folder - missing the drag-image showing the messages being dragged
  5. This one is a bit weird - I keep having to do things twice - for example I visited the Overview, viewed a list of emails, which link to the mail view (for example https://rmmstage02.runbox.com/app/#Inbox:12813748 (user [email protected]) - clicking on this link took me to the #Inbox page, not opening the specific message. I went back to the Overview (forward, I clicked on Overview again), re-clicked on the message link, and the 2nd time it did open the individual message in the mailview
  6. Opened a message / mailview (from the Overview), the list jumps/scrolls to show that row (good!) , I closed the message (horizontal view), more of the message list was displayed - but it didnt fill the available space / load more messages: 2025-02-04 13 32 13 rmmstage02 runbox com 7537e106e09b .. If I scroll down from that I lose even more messagelist height: 2025-02-04 13 35 28 rmmstage02 runbox com 0ccbd02015f8
  7. Threaded view doesnt seem to work for me, after I chose it I got this (ongoing): 2025-02-04 13 44 09 rmmstage02 runbox com 7ea043c9ad38 .. and this in the devtools:
TypeError: Cannot read properties of undefined (reading '1')
    at lp.getRowData (searchmessagedisplay.ts:244:29)
    at app.component.ts:1500:48
    at Generator.next (<anonymous>)```

10. After testing threading I switched it off and tried unread only, which worked.. then I turned off the index, scrolled, turned unread only off/on, and its not filtering:

![2025-02-04 13 49 33 rmmstage02 runbox com 7bc055abc18b](https://github.com/user-attachments/assets/101e1789-63f0-4e7a-aeaf-7a04eff43940)


castaway avatar Feb 04 '25 11:02 castaway

Starting new comment since that one seems full: 11. Select-drag over checkboxes with mouse (works on prod) - doesnt here 12. No way of "select all on page" / "select all in folder" that I can see?

castaway avatar Feb 04 '25 16:02 castaway

Another odd issue: If I navigate away from the Inbox/message list (eg to Settings), then back via clicking on the menus at the top of the app - the message list shows up empty apart from the checkboxes.. If I swap browser tabs (eg to mattermost and back) then the list reappears ...

2025-02-05 11 11 40 rmmstage02 runbox com 6718e7cce586

castaway avatar Feb 05 '25 16:02 castaway

Some testing this afternoon:

  • The fetch of message contents according to the current message list - this should happen in both the index-sync and index-off modes
  • If I click "Stop Index Synchronisation" things go a bit screwy! (hmm not always tho.. yay) 2025-03-19-162146_1096x677_scrot

castaway avatar Mar 19 '25 16:03 castaway

Some testing this afternoon:

  • The fetch of message contents according to the current message list - this should happen in both the index-sync and index-off modes
  • If I click "Stop Index Synchronisation" things go a bit screwy! (hmm not always tho.. yay) 2025-03-19-162146_1096x677_scrot

@castaway , I am unable to reproduce this on my account. I could do a null check and drop the message when it is null.

shadowbas avatar Mar 31 '25 12:03 shadowbas

Improved default column widths: Message table

gtandersen avatar May 12 '25 08:05 gtandersen

Canvastable use review:

Summary:

  • MessageDisplay object (the one setMessageDisplay sets/updates) - needs to move somewhere else (into app.component.ts?), and all calls to it changed to use the new one

    • Most of its purpose is to track which rows are available, which are selected, which are checked and so on, while being able to cope with the 3 different variants of row storage. It looks like the checked/selected etc logic has been moved into new classes for the virtual table, so any code that expects to be able to change/select/clear the selected/checked rows etc needs to be changed to use the new models? (or the models need to use the MessageDisplay)
    • In particular, the logic is supposed to be such that when the user does an action, eg deletes a message, the existing MessageDisplay rows are updated (see eg canvastable.removeMessages), then we set hasChanges, and the message list display instantly changes. Now that the rows are extracted from the MessageDisplay, and stored as a copy in this.rows, this "instant update" cannot happen - how we do we fix this ?
  • Preferences - the existing code stores the "showContextPreview" and "columnWidths" preferences after users change them

    • this needs to happen for the new system (we are not storing column widths prefs in virtual table yet)
    • the showContextPreview value that is stored in canvastable can just move to app.component.ts
    • columnwidths should also be resized/stretched accordingly (not reset to default), if the browser window size changes
  • Keyboard events - the app.component.ts has keyboard events for scroll up/down, we need to port these to the virtual table, or decide to lose them

  • Loading new message service (non-index) data - I cant see/understand where the virtual table loads more rows, for the REST API data - it used to use the scrollLimitHit event to call messagelistservice.requestMoreData .. there are no new calls to this, how does it work now?

  • jumpToOpenMessage - this was called from outside the canvastable, so that if the message that the user selected to open, did NOT open (this is possible if there are errors loading the message), the scroll/jump to top did not happen. Does the virtual table cope with this? (not jumping if the pane does not open)

  • canvastable.topindex - replace with whatever makes the virtualtable jump to the top from outside (used after switching from Index to API based data)

  • More, see NOTES below:

Raw NOTES:

  • canvastable.showContentTextPreview, pulled out of preferences in app.component.ts:336 / 406 , stored in canvastable, displayed if there is a canvastable object, in app.component.html:336/559/656/677

    • Suggestion: store in an app.component.ts variable instead?
    • adjust all uses of it to use app. instead of canvastable
  • canvastable.rows.openedRowIndex / canvastable.scrollUp / canvastable.scrollDown - in keyboardevent listener

    • app.component.ts:272
    • I assume this has been replaced in new virtual table and can be removed?
  • canvastable.columnWidths / canvasNamedColumnWidthsBySet the saved columnwidth preferences

    • Does the new table save the column widths after the columns are re-sized? if not this should be saved using the preferences system - after a test it seems like it does not
    • fix saving code, save widths, remove canvastable columnWidths code
  • canvastable.jumpToOpenMessage - selecting fragment message and scrolling to it in the message list are separate actions in the canvastable class. JumpToMessage is also used by updateMessageListHeight (which is called by afterLoadMessage when we open one) to scroll the list to that message

    • it seems the virtual table does the "scroll to and open to fragment" and "scroll to opened message" in some other fashion, so updateMessageListHeight, afterLoadMessage and jumpToOpenMessage appear to be redundant now. As is the afterLoadMessage usage in mailviewer/singlemailviewer.component
    • does the new version cope if the message its asked to open doesnt open ? (aka the message loader cant find it) does it scroll anyway? (that would be an odd visual I think)
    • See logic in singlemailviewer - if the requested message id has some kind of issue, the mailviewer self-closes
  • canvastable.scrollLimitHit

    • app.component.ts:447 this is how the existing code decides to load another page of messages, when viewing the list in non-index mode
    • the new virtual table seems to do this, though I can't see where/how !? Very confused by this one, where does it prompt the messagelist service for more data?
  • canvastable.canvasResizedSubject

    • app.component.ts:451
    • I suspect this can go away entirely, its just stretching the columns to fit a new / resized window width - I assume the virtual table can do this?
    • tested - it does not seem to, so we should implement that.
  • canvastable.rows.removeMessages

    • app.component.ts:720,843,1147,1184,
    • this reaches into the MessageDisplay rows and removes some messages
    • it is meant to cause an instant update of the listed messages being displayed, as the virtual table code is only operating on list changes after setMessageDisplay changes them (which is after the backend update), presumably thats slower..? (especially on slow network connections)
    • we should store the MessageDisplay rows somewhere not the canvastable, I suspect only app.component needs to talk to it, so probably in there.
    • OR if the virtual table has its own storage for "which rows are displayed and their states (selected checked etc), change to use that
  • canvastable.topindex

    • app.component.ts:862
    • this should jump the message list to the first item, after deleting the index, looks like the virtual table does not do this yet.
    • it also emptys the messagedisplay (canvastable.rows) - not clear if this is necessary to keep
  • setMessageDisplay - setting canvastable.rows etc

    • app.component.ts:886 et al
    • manages which rows should be displayed (the messagedisplay) - we should store the current messagedisplay .. somewhere, and it should be used to display the list (not the extracted sublist of rows)
  • canvastable.columns

    • app.component.ts:924
    • this sets the visual look of the columns for the currently chosen messagedisplay, unused if that logic is now elsewhere? (where is it?) (Sent vs From etc)
  • canvastable.rows.filterBy

    • app.component.ts:934
    • filters visible messages by the unread/search options - change to point to whereever the MessageDisplay object ends up
  • canvastable.hasChanges

    • tons of places - I assume no longer needed, as it was picked up by the repaint loop on the canvas
    • how does external code tell the virtual table to update because its content has changed now? just by setting rows ?
  • canvastable.rows.getRowMessageId, .rows.rowSelected, etc

    • app.component.ts:964, 971-975
    • sets the urlFragment to the opened message id - change this to fetch from whereever the MessageDisplay object ends up..
    • note it checks hasChanges, so that we dont update it constantly, we need some equivalent
    • this also tells the singlemailviewer which message it is opening (line 975)
  • rowSelected

    • app.component.ts:951
    • this is the whole "which rows are selected and how" (checked, highlighted etcetc) management code for the canvastable view - it looks like the virtual table sidesteps all that and has its own row view management. This is most of the point of the existance of MessageDisplay.. so anything using that either needs to be changed to use the new models, or the new models need to be replaced with MessageDisplay
  • canvastable.rows.getCurrentRow()[2] !== '1')

    • app.component.ts:983
    • when viewing threads/conversations this only shows the "single conversation" view, when the conversation has more than one item in it (if there's only one item, it opens the email instead)
    • NB: testing on allytable - the Count should show "1" for threads with one item in, and when clicking on a thread with 1 item, it should just open the email
  • canvastable.rows.getCurrentRow()[0], 1)

    • app.component.ts:990
    • updates the search/list of displayed messages using just the conversation, from the index
    • update to use whereever we move MessageDisplay object to (or whatever the virtualtable is using for current messagelist status)
  • canvastable.rows.clearOpenedRow

    • app.component.ts:1092
    • de-highlights the greyed row in the message list, showing which email is currently open (after mailviewer close)
    • replace with however the virtualtable knows which row is "open"
  • canvastable.scrollTop

    • app.component.ts:1272
    • Another "scroll to top of list" after switching folders - I think the virtual table does this already in some other way, so move ?
  • resetColumns

    • app.component.ts:1276
    • sets the correct set of columns visible, for the current MessageDisplay style, called various places
    • adjust to set virtualtable column sets ?
  • canvastable.scrollTop

    • app.component.ts:1412
    • scrolls to top of list, after new search (NB search includes just listing all the messages)
    • I cant find anywhere that actually passes "noscroll=true" so its possible this logic is redundant
  • canvastable.horizScroll

    • app.component.ts:1442
    • Looks like is entirely redundant now
  • canvastable.autoAdjustColumnWidths

    • app.component.ts:1448
    • set column widths, mostly called after changing which columns are displayed, probably redundant now?
  • this.canvastable.rows.rows

    • app.component.ts:1493
    • use the MessageDisplay object, whereever we end up storing it? Ditto in enrichRows
    • Why do we set this.rows to the above, and then have enrichRows work on canvastable.rows ? seems a bit redundent to do both

castaway avatar Jul 16 '25 12:07 castaway

Ah just noticed this part too:

      const {data: column, direction} = this.orderSelectionModel.selected

      if (direction === Direction.None) {
         this.canvastablecontainer.sortColumn = 2;
         this.canvastablecontainer.sortDescending = true;
       } else {
         this.canvastablecontainer.sortColumn = column;
         this.canvastablecontainer.sortDescending = Direction.Descending === direction;
       }

castaway avatar Jul 17 '25 11:07 castaway

Copy this here so we don't lose track of it:

Testing on Thurs Oct 30th (screenshots in chat)

  • selected some messages in the Inbox list
  • marked them as Spam
  • checked that the backend confirmed that they should now be in the Spam folder
  • some of them disappeared from the Inbox message list and .. some did not
  • also it only fetched the contents of the message after I clicked on it to view it (which was from the inbox, after it should have been moved to Spam)

castaway avatar Nov 05 '25 10:11 castaway

More test results follow:

  1. Overall impression smooth and snappy. :)
  2. Clicking to stop and then start index sync removes the search field while in prod you can still search using the server index while the index is being downloaded.
  3. Couldn't see how to select all messages in Inbox (including non-visible). Actually it seems that all message rows in Trash are selected by default, but not in other folders. After turning index sync off, the default behavior seems to be selecting all messages in the folder. Could be related to the number of messages in the folder, which in Inbox is 47k.
  4. Selecting a page full (23) messages and dragging them from Inbox to Trash or another regular folder seems to not have any effect. Turning off index sync shows the correct result of the operation.
  5. The "Moving ..." box is very useful but its position immediately changes from the cursor position to the top left corner. In Safari the box doesn't show at all and messages can't be moved.
  6. Selecting 5 messages and clicking Mark as Read shows a "Toggling read status" message that does not disappear but the messages are not toggled. There are no visible errors in the console. Without index sync it works as expected, except that "Toggling read status" doesn't disappear.
  7. Minor detail: The green background color on target folders when moving messages could be changed to (light) blue,.

gtandersen avatar Nov 05 '25 14:11 gtandersen