| 1 | {{/* The verbs live in the top bar; the page is the list. |
| 2 | |
| 3 | This screen used to open with two stacked forms — a title field |
| 4 | with a Create button, then a dashed drop zone with a file input and |
| 5 | four lines of limits — and the sheets themselves began below the |
| 6 | fold of both. A list of sheets should be a list of sheets. |
| 7 | |
| 8 | Create is a button beside the account menu now, and makes a sheet |
| 9 | called "Untitled sheet" in one click; the name is fixed afterwards |
| 10 | by renaming it in the top bar of the sheet itself, which is what |
| 11 | handlers_rename.go is for. Import moved to a page of its own at |
| 12 | /sheets/import — see handleImportPage for why a page and not an |
| 13 | item in the File menu. */}} |
| 14 | {{define "head"}} |
| 15 | {{/* Drag and drop, and only that. The overlay at the foot of this file |
| 16 | is hidden until a file is dragged over the window, and every drop |
| 17 | ends as a submit of the same form /sheets/import serves — same |
| 18 | route, same CSRF check, same job. |
| 19 | |
| 20 | With scripting off it never appears and the Import link in the top |
| 21 | bar is the entire feature. That is what AGENTS.md's "every |
| 22 | drag-only affordance needs a menu equivalent" asks for: the link |
| 23 | is the floor, and the drag is a shortcut to it. */}} |
| 24 | <script defer src="{{asset "static/drop.js"}}"></script> |
| 25 | {{end}} |
| 26 | |
| 27 | {{/* Import and Create are the bar's page actions (handleSheetList builds |
| 28 | them as estilo chrome.Actions): buttons beside the account at wide |
| 29 | widths, rows in the Menu sheet below 800px. Create is a POST in both |
| 30 | places. They used to be an extra block in the rastrillo tail, whose |
| 31 | buttons were taller than the bar and grew it from 45px to 51px. */}} |
| 32 | |
| 33 | {{define "content"}} |
| 34 | <header rst-page-header> |
| 35 | <div rst-page-header-titles> |
| 36 | {{/* "All sheets", not "Your sheets". The old heading was wrong twice: |
| 37 | it repeated the brand in the bar directly above it, and it claimed |
| 38 | ownership the line underneath immediately contradicts — everyone on |
| 39 | the instance opens every sheet here, so almost none of these are |
| 40 | yours. A heading its own subtitle corrects is worse than none. */}} |
| 41 | <h1>All sheets</h1> |
| 42 | <p rst-page-header-sub>Everyone on this instance can open every sheet here.</p> |
| 43 | </div> |
| 44 | </header> |
| 45 | |
| 46 | {{if .Data.Sheets}} |
| 47 | <ul rst-list data-sheet-list> |
| 48 | {{range .Data.Sheets}} |
| 49 | <li rst-lrow> |
| 50 | {{/* The thumbnail. Built from the SHAPE of the sheet — which cells |
| 51 | exist, and what colour they are — and never from a cell's |
| 52 | contents, which is why it cannot leak one. aria-hidden, because |
| 53 | the link beside it already says which sheet this is and a |
| 54 | screen reader gains nothing from "image" twice. |
| 55 | |
| 56 | The same 26x34 slot in the same place as the Docs app's, by |
| 57 | agreement, so the two file lists read as one product. */}} |
| 58 | <span data-preview-slot>{{.Preview}}</span> |
| 59 | <div rst-row-main> |
| 60 | <a href="/sheets/{{.ID}}">{{.Title}}</a> |
| 61 | <span rst-row-sub>Last changed {{.UpdatedAt.Format "2 Jan 2006, 15:04"}}</span> |
| 62 | </div> |
| 63 | </li> |
| 64 | {{end}} |
| 65 | </ul> |
| 66 | {{else}} |
| 67 | {{/* The empty state points at the two controls in the top bar. It |
| 68 | used to say "make one above", which was true when the form was |
| 69 | directly above it and would now be a sentence pointing at a |
| 70 | header. An empty screen is the one place in a product that has |
| 71 | nothing to do but explain what to do next. */}} |
| 72 | <div rst-empty> |
| 73 | <p rst-empty-title>No sheets yet</p> |
| 74 | <p rst-empty-body>Create one from the top bar and it opens with a blank |
| 75 | grid you can type into straight away, or |
| 76 | <a href="/sheets/import">import an Excel file</a> to start from what you |
| 77 | already have.</p> |
| 78 | </div> |
| 79 | {{end}} |
| 80 | |
| 81 | {{/* The drop target for the whole page. |
| 82 | |
| 83 | This screen used to carry a dashed box you could drop a file on, |
| 84 | and taking the box away took the drop with it — so the drop comes |
| 85 | back without the box. It is `hidden` at rest, contributes nothing |
| 86 | to the layout and nothing to the tab order, and drop.js reveals it |
| 87 | only while a drag carrying files is actually over the window. An |
| 88 | affordance that appears exactly when it applies beats a permanent |
| 89 | rectangle explaining that it exists. |
| 90 | |
| 91 | It is the same form as the one on /sheets/import, deliberately: |
| 92 | drop.js fills this file input and submits this form, so a drop |
| 93 | from here is the ordinary multipart POST and there is no second |
| 94 | code path on the server. The file field is visible inside the |
| 95 | panel rather than hidden, because a refused drop ("drop one file |
| 96 | at a time") needs something to recover with that is not the back |
| 97 | button. |
| 98 | |
| 99 | `hidden` is load-bearing and is asserted in a test: rst-modal-overlay |
| 100 | sets display:flex, an author rule beats the UA's [hidden], and |
| 101 | without the reinstating rule in sheets.css this would cover the |
| 102 | list permanently. */}} |
| 103 | <form data-drop data-drop-page hidden rst-modal-overlay |
| 104 | method="post" action="/sheets/import" enctype="multipart/form-data"> |
| 105 | <div rst-modal-panel> |
| 106 | <div data-drop-page-body> |
| 107 | <p data-drop-page-title>Drop it to import</p> |
| 108 | {{/* The same limits /sheets/import states, and stated before the |
| 109 | import for the same reason: one worksheet is read, and a |
| 110 | three-sheet workbook would otherwise arrive looking whole. This |
| 111 | is also the line drop.js rewrites to refuse a drop, which is |
| 112 | why it is a sentence and not a list. */}} |
| 113 | <p rst-field-hint data-drop-hint>Excel .xlsx or .xlsm, up to 20 MB. The |
| 114 | first worksheet is imported.</p> |
| 115 | {{/* Chooser and submit, so a refused drop has something to recover |
| 116 | with, and so the panel is a working form rather than a picture |
| 117 | of one. They are hidden while a drag is actually over the window |
| 118 | — see [data-drop-page-recover] in sheets.css — because offering |
| 119 | a file picker to somebody mid-drag is a second way to do the |
| 120 | thing they are already doing. |
| 121 | |
| 122 | They carry no tabindex, and its absence is a fix rather than an |
| 123 | omission. Both used to carry tabindex="-1", to "keep them out of |
| 124 | the tab order while the overlay is up" — which is exactly backwards. |
| 125 | The overlay stays up after a REFUSED drop precisely so these two |
| 126 | controls can be used, and a negative tabindex meant a keyboard user |
| 127 | tabbed straight past the recovery form into the list behind it. |
| 128 | The other two states never needed the help: `hidden` on the form |
| 129 | takes the whole subtree out of the tab order at rest, and |
| 130 | [data-drop-page-recover] is display:none under [data-drop-over], |
| 131 | which does the same mid-drag. So it was redundant where it was |
| 132 | harmless and harmful where it was not. */}} |
| 133 | <div data-drop-page-recover> |
| 134 | <input rst-input name="file" type="file" accept=".xlsx,.xlsm" |
| 135 | aria-label="Spreadsheet to import"> |
| 136 | <button rst-btn="primary" type="submit">Import</button> |
| 137 | </div> |
| 138 | </div> |
| 139 | </div> |
| 140 | </form> |
| 141 | {{end}} |
| 142 | |