oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

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
75grid you can type into straight away, or
76<a href="/sheets/import">import an Excel file</a> to start from what you
77already 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
114first 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