Repository navigation
Conversation
Custom exercises (issue DuarteSantos8#11) had no way to carry a photo or GIF — by design, since the shipped dataset's media is not ours to redistribute (NOTICE.md). A picture you supply yourself is a different thing: it is stored as a data: URL right on the exercise, riding along inside the same PUT /api/data blob everything else in the store already goes through — no upload endpoint, no server-side storage, no new dependency. imgSrc/gifSrc pass a data: URL through untouched instead of prefixing it with the dataset's base path. A GIF is used as both the still and the animation (no first frame to pull out of it without a library); a plain photo is downscaled on a canvas so a phone photo does not blow the 5 MB PUT /api/data body cap on its own. Per side (issues DuarteSantos8#31/DuarteSantos8#32/DuarteSantos8#33) was dropped when switching an exercise to Time, because it counts reps and a timed hold has none to split. But "per side" does not have to mean splitting: for a hold it means doing the whole thing once per side, so the flag now survives the switch and buildWorkSets doubles the planned sets instead of dividing the duration — 2 sets of 30s becomes 2 left + 2 right, each still 30s. The rows are plain, tagged with `side: 'L'|'R'`, not the L/R sub-row pair reps mode uses (isSideSet) — a hold has one duration to log per row, not two independent values. exLine reads "3 × 0:45 · per side" rather than trying to spell out a split that was never how a hold works. 1589 tests pass (1584 before + 5 new: buildSets doubling/history-carry for a timed per-side hold, exLine's new "per side" wording, imgSrc/ gifSrc's data: URL passthrough).
…video or guide — in the editor, and wherever an exercise's picture shows - The editor gets a "Photo, GIF or video" row and a link field. A picked file is re-encoded on the device (photos: WebP where the browser really writes it, else JPEG, at most 1600 px, plus a 480 px poster — EXIF and GPS gone by construction; GIFs lose their comment and metadata blocks; MP4/MOV have their metadata boxes and non-picture, non-sound tracks zeroed in place), hashed, and kept in the local store before the form is saved. The name of the file is never kept. Refusals say why: type, size, length, a photo too large to decode, a file this browser cannot read; a video that may not play everywhere says so - Media.jsx sends every custom exercise to CustomMedia.jsx, so the detail sheet, the config sheet, the workout card, the picker, the library, a past workout, the routine editor and the muscle explorer all show it with no change of their own. Lists load posters only. Videos up to 15 s loop muted like the catalogue's GIFs (not in the list layout, not under reduced motion); longer ones play with sound and controls on a tap. A file that is not here shows the tile, and a tap asks again - A link is a card that opens it in a new context without opener or referrer; nothing fetches it, no thumbnail, no embed. It is cleaned on save and again on every open - "Create your own exercise" no longer says "no animation" Based on PR #295 by horusglez (the picture row of the editor: the draft's thumb, Add/Change and Remove). Based on PR #246 by Vaibhav159 (the link field, cleanUrl and the link card).
|
Thanks @horusglez. Custom exercises can carry a photo, GIF or short video in v1.3.9, and that work is credited "Based on PR #295 by horusglez / #246 by Vaibhav159". I built it differently from your version: a I didn't take per side on timed holds. It's a good idea, but doubling the rows to 2n needs a progression fix too: Released in v1.3.9: https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.9 |
What
Two small, additive things I was missing running my own instance:
1. A photo or GIF on a custom exercise. Issue #11's custom exercises have no
animation by design — the shipped dataset's media isn't ours to redistribute
(NOTICE.md) — but a picture you supply is a different thing. It's stored as a
data:URL right on the exercise, riding along inside the samePUT /api/datablob everything else in the store already goes through: no upload endpoint, no
server-side storage, no new dependency.
imgSrc/gifSrcpass adata:URLthrough untouched instead of prefixing it with the dataset's base path. A GIF
is used as both the still and the animation (no first frame to pull out of it
without a library); a plain photo is downscaled on a canvas so a phone photo
doesn't blow the 5 MB body cap (
api/server.jsMAX_BODY) on its own.2. "Per side" on a timed hold. Issues #31/#32/#33 added per-side reps, but
sidewas dropped when switching to Time — a hold has no rep count to split."Per side" doesn't have to mean splitting, though: for a hold it means doing
the whole thing once on each side. The flag now survives the switch to Time
and
buildWorkSetsdoubles the planned sets instead of dividing the duration —2 sets of 30s becomes 2 left + 2 right, each still 30s. These are plain rows
tagged
side: 'L'|'R', not the L/R sub-row pair reps mode uses (isSideSet) —a hold has one duration to log per row, not two independent values to track
side by side.
exLinereads3 × 0:45 · per siderather than trying to spellout a split that was never how a hold worked.
Why bundled together
Both are small, both touch the same exercise-config sheet, and both are purely
additive (existing configs / existing per-side reps behavior are unchanged) —
happy to split into two PRs if you'd rather review them separately.
Testing
npm run build— clean.npm test— 1589 pass (1584 on main + 5 new:buildSetsdoubling andhistory-carry for a timed per-side hold,
exLine's new "per side" wording,imgSrc/gifSrc'sdata:URL passthrough).browser available in the environment I built this in) — logic is covered by
the tests above, but the UI itself (the photo picker, the doubled rows
during a workout) has not been eyeballed. Flagging that explicitly per
CONTRIBUTING.md's "test the flow you touched" — happy to do a follow-up pass
once I can.
Open questions for you
differently than what I picked (4 MB raw, ~1.5 MB encoded, 640px max
dimension for a downscaled photo)?
rather it stayed a single row with an
L/Rlabel and no duration change?I went with doubling because that's the actual training protocol (hold
it once per side), but I know it's a bigger behavioral surface than the
photo/GIF change.
🤖 Generated with Claude Code