Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to Patternflow will be documented in this file, newest first
### Firmware

- **No more stray letters on the panel's own screens.** A portrait line holds ten characters, and Adafruit GFX wraps an eleventh onto the next line by itself: the KNOB MAP's "TURN = SHOW" put a lone "W" in front of "K3 = EXIT", the install screen's "web console" did the same, and the default host name "patternflow" lost its "w" on the hotspot and UPDATE screens. The labels are shorter now ("TURN=SHOW", "console"), and names that can be longer than a line are broken into lines of ten by `drawCenteredFit` — "pattern" / "flow-a1b2", "pattern" / "flow.local" — keeping every line where it was.
- **A deck sent from the community's dock keeps its order.** The console's one-click install from a link (`/patterns?src=`) took only `.pfm` and `.json` from a build's file list, so the `catalog.txt` every deck build carries was left behind and the deck landed in alphabetical order. It now takes exactly what a dropped zip does (`catalog.txt` and `.pfs` too) - one rule, `installable()`, for every way in.
- **A firmware upload survives a Wi-Fi hitch.** The `/update` connection now waits up to two minutes for a stalled upload instead of five seconds, and the page gives up after five minutes with a message instead of waiting forever. Checked on a panel with a 12 s stall halfway through a flash. `PUT /update` takes a raw image, for `curl -T`. From Simone Majocchi ([#450](https://github.com/engmung/Patternflow/pull/450)).
- **Two 64×64 modules chained make a 128×64 panel.** `PANEL_GEOMETRY` in `config.h` picks the stock 128×64, one 64×64, or two 64×64 daisy-chained (the `firmware64x2` PlatformIO env). The driver is told the module and the chain, and everything else sees the canvas; the stock build is unchanged. From Simone Majocchi ([#446](https://github.com/engmung/Patternflow/pull/446)).
- **Performance v0.4.0 carries the clock and a Weather face**, and its show catalogue holds 100 sequences instead of 48. An MQTT subscriber following a fast sequence stream now drains the broker and applies the latest value per knob each frame instead of falling one message per frame behind (a jump of more than 16 clicks in one frame is capped), and a publisher no longer replays its own retained snapshot when it reconnects. From Simone Majocchi ([#445](https://github.com/engmung/Patternflow/pull/445), [#448](https://github.com/engmung/Patternflow/pull/448), [#449](https://github.com/engmung/Patternflow/pull/449)).
Expand All @@ -21,6 +22,8 @@ All notable changes to Patternflow will be documented in this file, newest first

### Web

- **A guide you can play along with, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). The first page is the first hour, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. The second, [/guide/make](https://patternflow.work/guide/make), is 05 Community and 06 Pattern Lab: the community's real screens, and a board that plays your own Pattern Lab draft (read, never written) while the real Lab opens in a window beside it. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says where (`guide_stuck.yml`).
- **The community's Performances note says .pfs**, which is what the Director saves, instead of "Save-JSON".
- **A moderator can take a pattern or deck off the wall without deleting it.** Somebody else's public pattern or deck now offers moderators *Make private*, with an optional reason: the softer removal, for things like the same pattern posted a fifth time. Nothing about the work changes and its author keeps it — they can still open it, edit it, take it into the lab — it just stops being shown to anyone else. On a pattern the reason is posted underneath as the moderator's comment, where the author can answer it; either way the author is told in their alerts. While the take-down stands the author cannot make it public again (a take-down its subject can undo in one click is a request); *Restore to public* puts it back exactly as it was. A moderator still cannot publish what an author made private themselves. A deck slot left empty by a take-down reads "made private by a moderator", not "by its author". Moderators can now comment on private patterns and keep their alerts about them, so the thread under a take-down works both ways. Migration `0025` adds `hidden_at` to patterns and decks; `check:hidemod` drives it all through the real routes.
- **One press of Publish is one post.** A double-click on *Publish to the wall*, or a mouse switch that bounces, could put the same pattern on the wall twice in the same second ([#455](https://github.com/engmung/Patternflow/issues/455)). The button's disabled state was React state, a render too late for the second click, and after a successful publish it came back enabled while the page changed. The publish, thread, reply, deck, header, performance and report forms now refuse a second press in the same tick (`useSubmitLatch`), and a published pattern keeps its button down until the page changes; a thread whose files failed to attach offers *Open the thread* instead of posting it again. Behind them the server answers an identical submission from the same account within a minute with the post it already made, looked up and inserted in one synchronous transaction so two requests arriving together cannot both get through. `check:publish` fires two identical publishes into the route at the same instant; a version that awaits between the lookup and the insert fails it.

Expand Down
32 changes: 19 additions & 13 deletions firmware/patternflow/console/patterns.html
Original file line number Diff line number Diff line change
Expand Up @@ -777,31 +777,34 @@ <h2>Installed</h2>
// on — writes "\" on Windows, and a pack made that way arrives with its
// whole path stuck to the filename: the junk filter stops matching, the
// duplicate check stops matching, and the queue shows "a\b\wave.pfm".
// What goes onto the device, by file name — one rule for every way in: a
// drop, a zip, a link.
// catalog.txt rides along: it is the pack's running order (deck order).
// .pfs is a Director show table — installed to /show alongside the pack.
// performance.json is that show's editable source (Director / site), not
// a module sidecar: the device has no use for it, so it stays behind.
function installable(n){
if(/^performance\.json$/i.test(n)||/\.perf\.json$/i.test(n))return false;
return /\.(pfm|json|pfs)$/i.test(n)||/^catalog\.txt$/i.test(n)
}

function expandFiles(fileList){
var list=Array.prototype.slice.call(fileList||[]);
// catalog.txt rides along: it is the pack's running order (deck order).
// .pfs is a Director show table — installed to /show alongside the pack.
// performance.json is that show's editable source (Director / site), not
// a module sidecar: the device has no use for it, so it stays behind.
var keep=function(n){
if(/^performance\.json$/i.test(n)||/\.perf\.json$/i.test(n))return false;
return /\.(pfm|json|pfs)$/i.test(n)||/^catalog\.txt$/i.test(n)
};
if(!list.some(function(f){return /\.zip$/i.test(f.name)}))
return Promise.resolve(list.filter(function(f){return keep(f.name)}));
return Promise.resolve(list.filter(function(f){return installable(f.name)}));

say('unpacking zip…');
return loadFflate().then(function(){
var out=[],seen={},total=0;
var push=function(f){if(!seen[f.name]){seen[f.name]=1;out.push(f)}};
var chain=Promise.resolve();
list.forEach(function(f){
if(!/\.zip$/i.test(f.name)){if(keep(f.name))push(f);return}
if(!/\.zip$/i.test(f.name)){if(installable(f.name))push(f);return}
chain=chain.then(function(){return f.arrayBuffer()}).then(function(buf){
var unzipped=fflate.unzipSync(new Uint8Array(buf));
Object.keys(unzipped).forEach(function(name){
var base=name.split(/[\\/]/).pop();
if(!base||base.charAt(0)==='.'||!keep(base))return;
if(!base||base.charAt(0)==='.'||!installable(base))return;
total+=unzipped[name].length;
if(total>ZIP_MAX_BYTES)
throw new Error('zip holds more than '+(ZIP_MAX_BYTES>>20)+' MB of patterns');
Expand Down Expand Up @@ -884,8 +887,11 @@ <h2>Installed</h2>
fetch(src+sep+'list=1').then(function(r){
if(!r.ok)throw 0;return r.json();
}).then(function(d){
var names=(d.files||[]).filter(function(n){return /\.(pfm|json)$/.test(n)});
if(!names.length)throw 0;
// The same rule as a dropped zip. It used to be .pfm/.json only, which
// left a deck's catalog.txt behind: a deck sent from the community's
// dock arrived in alphabetical order instead of the order it was built in.
var names=(d.files||[]).filter(installable);
if(!names.some(function(n){return /\.pfm$/i.test(n)}))throw 0;
holdBatch(true);
items=names.map(function(n){return {f:null,name:n,st:'get',pct:0,tries:0}});
retryBtn.style.display='none';
Expand Down
Loading
Loading