this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: parse multiple commands

basically at MVP now
the react app can now add, complete and reactively filter tasks

The filter command doesn't have a enter action, it should probably do
something. maybe I do need the default to be select, instead of filter,
and filter should _actually filter_.

Syncing is next. I'll need to look at the vcln starter template to
understand how they set up, and then maintain different databases for
each user.

What needs to be done is setting up rooms, and the backend such that
everybody gets their own lil sql database, and the syncer can identify
them. we don't really have any notion of security, which is uhhh, not
great.

After that we can look at some malleable features that lend themself to
discoverability. ideally, I don't need to have a tutorial at all.
reactivity needs to exist for almost every command. I want to save the
command, to a dirty state sql table, and then restore it on load. that
should cause all the elements to re-render the proposed change.

this actually unlocks a thought for me: executing filters, sets them as
a "context". a context makes up a view. reports are literally just
saved contexts then. this allows the tanstack table to handle virtual
ids for us!

Then, for the malleable input, all we need some kind of triple scroll
wheel that can be typed, for each of the three segments. ideally with
desired autocomplete 🤔

i think i'm reimagining the CLI for the web almost!

+118 -22
+71 -13
mast-react-vite/src/App.tsx
··· 40 40 { 41 41 id: "select", 42 42 header: ({ table }) => ( 43 - <Checkbox 44 - checked={ 45 - table.getIsAllPageRowsSelected() || 46 - (table.getIsSomePageRowsSelected() && "indeterminate") 47 - } 48 - onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} 49 - aria-label="Select all" 50 - className="translate-y-[2px]" 51 - /> 43 + <div className="flex items-center gap-2"> 44 + <p>0</p> 45 + <Checkbox 46 + checked={ 47 + table.getIsAllPageRowsSelected() || 48 + (table.getIsSomePageRowsSelected() && "indeterminate") 49 + } 50 + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} 51 + aria-label="Select all" 52 + className="translate-y-[2px]" 53 + /> 54 + </div> 52 55 ), 53 56 cell: ({ row, table }) => ( 54 57 <div className="flex items-center gap-2"> ··· 99 102 ] 100 103 101 104 function App({ ctx }) { 102 - const todos = useQuery(ctx, "SELECT * FROM todos").data; 105 + const todos = useQuery(ctx, "SELECT * FROM todos where completed = 0").data; 103 106 const [newText, setNewText] = useState(""); 104 107 const [rowSelection, setRowSelection] = useState({}); 105 108 ··· 118 121 } 119 122 120 123 const parseTodos = (e) => { 121 - if (e.target.value.trim() !== "") { 124 + // On enter execute the command 125 + if (e.key === "Enter") { 126 + console.log("enter confirmed") 122 127 try { 123 128 const parsed = commandParser.parse(e.target.value); 129 + // TODO: 130 + // We need to identify the type of the command first 131 + // Currently assumes that it is done 132 + switch (parsed.type) { 133 + case "done": 134 + if (parsed.filters && parsed.filters.length > 0) { 135 + const idFilters = parsed.filters.filter(f => f.type === "id"); 136 + 137 + // Get the row IDs from the table 138 + const rowIds = idFilters.flatMap(filter => 139 + filter.ids.map(id => todos[id - 1]?.id) 140 + ).filter(Boolean); 141 + 142 + // Execute SQL update for each row 143 + if (rowIds.length > 0) { 144 + rowIds.forEach(id => { 145 + ctx.db.exec( 146 + `UPDATE todos SET completed = 1 WHERE id = ?`, 147 + [id] 148 + ); 149 + }); 150 + } 151 + 152 + setNewText(""); 153 + setRowSelection({}); 154 + } 155 + case "add": 156 + ctx.db.exec(`INSERT INTO todos (id, description, tags, project, completed) 157 + VALUES (lower(hex(randomblob(16))), ?, ?, ?, 0)`, 158 + [ 159 + parsed.description, 160 + parsed.tags, 161 + parsed.project 162 + ]); 163 + setNewText(""); 164 + } 165 + } catch (error) { 166 + // TODO: 167 + // This is actually bad 168 + // We want to throw an error to the user here 169 + console.log("Unable to parse field"); 170 + return; 171 + } 172 + } 173 + // React to key presses for selection 174 + else if (e.target.value.trim() !== "") { 175 + try { 176 + const parsed = commandParser.parse(e.target.value); 177 + // TODO: 178 + // We want to reactively update based on command still 179 + // Right now this assumes the command is filter 124 180 125 - console.log(parsed) 126 181 if (parsed.filters && parsed.filters.length > 0) { 127 182 const idFilters = parsed.filters.filter(f => f.type === "id"); 128 - console.log(idFilters) 129 183 184 + // TODO: 185 + // We should probably have selection state react to changes on NewText 186 + // Rather than manually updating here 187 + 130 188 // Create new selection state 131 189 const newSelection = {}; 132 190 idFilters.forEach(filter => {
+24 -5
parser/command_js.peg
··· 4 4 const validParts = parts || []; 5 5 // Extract pure description (without attributes) 6 6 const description = validParts 7 - .filter(part => typeof part === 'string') 7 + .filter(part => part.type === 'text') 8 + .map(part => part.value) 8 9 .join(' ') 9 10 .trim(); 10 11 11 12 // Collect all attributes 12 13 const attributes = validParts 13 - .filter(part => typeof part === 'object') 14 + .filter(part => part.type !== 'text') 14 15 .filter(part => part !== null); 16 + 17 + const project = validParts 18 + .filter(part => part.type === 'project') 19 + .map(part => part.value) 20 + .join('') 21 + .trim(); 22 + 23 + const tags = validParts 24 + .filter(part => part.type === 'tag') 25 + .map(part => part.value); 26 + 15 27 16 28 // Store original parts for reconstruction 17 29 const originalParts = validParts.filter(part => part !== null); 18 - 30 + let validFilters; 31 + if (!!filters) { 32 + validFilters = filters.flat(); 33 + } else { 34 + validFilters = []; 35 + } 19 36 20 37 return { 21 38 type: type, 22 - filters: filters.flat() || [], 23 39 description: description || [], 24 40 attributes: attributes || [], 41 + filters: validFilters, 42 + project: project, 43 + tags: JSON.stringify(tags), 25 44 parts: parts || [], 26 45 reconstruct: function() { 27 46 const filterStr = this.filters.map(f => f.reconstruct()).join(','); ··· 71 90 72 91 Filter = IdFilter / Tag / Project / Priority / Due 73 92 74 - IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? { 93 + IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? { 75 94 const ids = [first, ...rest.map(r => r[1])].flat(); 76 95 return { 77 96 type: 'id',
+23 -4
parser/command_parser.js
··· 1219 1219 const validParts = parts || []; 1220 1220 // Extract pure description (without attributes) 1221 1221 const description = validParts 1222 - .filter(part => typeof part === 'string') 1222 + .filter(part => part.type === 'text') 1223 + .map(part => part.value) 1223 1224 .join(' ') 1224 1225 .trim(); 1225 1226 1226 1227 // Collect all attributes 1227 1228 const attributes = validParts 1228 - .filter(part => typeof part === 'object') 1229 + .filter(part => part.type !== 'text') 1229 1230 .filter(part => part !== null); 1231 + 1232 + const project = validParts 1233 + .filter(part => part.type === 'project') 1234 + .map(part => part.value) 1235 + .join('') 1236 + .trim(); 1237 + 1238 + const tags = validParts 1239 + .filter(part => part.type === 'tag') 1240 + .map(part => part.value); 1241 + 1230 1242 1231 1243 // Store original parts for reconstruction 1232 1244 const originalParts = validParts.filter(part => part !== null); 1233 - 1245 + let validFilters; 1246 + if (!!filters) { 1247 + validFilters = filters.flat(); 1248 + } else { 1249 + validFilters = []; 1250 + } 1234 1251 1235 1252 return { 1236 1253 type: type, 1237 - filters: filters.flat() || [], 1238 1254 description: description || [], 1239 1255 attributes: attributes || [], 1256 + filters: validFilters, 1257 + project: project, 1258 + tags: JSON.stringify(tags), 1240 1259 parts: parts || [], 1241 1260 reconstruct: function() { 1242 1261 const filterStr = this.filters.map(f => f.reconstruct()).join(',');