diff --git a/assets/js/controllers/edit_upload.js b/assets/js/controllers/edit_upload.js index f1d472a..f575bea 100644 --- a/assets/js/controllers/edit_upload.js +++ b/assets/js/controllers/edit_upload.js @@ -24,9 +24,17 @@ const processorUIs = { "shadow": { label: "Shadow", template: Handlebars.compile(` -
- - +
+ +
+ +
+
+
+ +
+ +
`), }, @@ -67,10 +75,19 @@ export default class UploadEditController extends Controller { async removeProcessor(ev) { ev.preventDefault(); let id = ev.params.id; - console.log(ev.params); await this._removeProcessor(id); } + async updateProcessor(ev) { + ev.preventDefault(); + let id = ev.params.id; + + let paramParentEl = ev.target.closest('[data-role="processor-params"]'); + let params = Object.fromEntries(new FormData(paramParentEl).entries()); + + await this._updateProcessor(id, params); + } + _rebuildProcessList() { let el = this.processListTarget; @@ -135,6 +152,25 @@ export default class UploadEditController extends Controller { } } + async _updateProcessor(processorID, params) { + await this._doReturningState(async () => { + return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}`, { + method: 'PATCH', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + processor: { + id: processorID, + props: params, + } + }) + })).json(); + }) + } + + async _removeProcessor(processorID) { await this._doReturningState(async () => { return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors/${processorID}`, { diff --git a/cmds/server.go b/cmds/server.go index 515f7a5..7a1445a 100644 --- a/cmds/server.go +++ b/cmds/server.go @@ -153,6 +153,7 @@ Starting weiro without any arguments will start the server. siteGroup.Get("/uploads/:uploadID/edit", uh.Edit) siteGroup.Post("/imageedit", ieh.Create) + siteGroup.Patch("/imageedit/:sessionID", ieh.PatchSession) siteGroup.Post("/imageedit/:sessionID/processors", ieh.AddProcessor) siteGroup.Delete("/imageedit/:sessionID/processors/:processorID", ieh.DeleteProcessor) siteGroup.Get("/imageedit/:sessionID/preview/:versionID", ieh.Preview) diff --git a/handlers/imageedit.go b/handlers/imageedit.go index 8026c53..ced7f75 100644 --- a/handlers/imageedit.go +++ b/handlers/imageedit.go @@ -113,3 +113,32 @@ func (ieh ImageEditHandlers) DeleteProcessor(c fiber.Ctx) error { PreviewURL: res.PreviewURL(), }) } + +func (ieh ImageEditHandlers) PatchSession(c fiber.Ctx) error { + var req struct { + UpdateProc *imgedit.UpdateProcessorReq `json:"processor"` + } + + sessionID := c.Params("sessionID") + if sessionID == "" { + return fiber.ErrBadRequest + } + + if err := c.Bind().Body(&req); err != nil { + return err + } + log.Printf("Got request: %v", *req.UpdateProc) + + if req.UpdateProc != nil { + res, err := ieh.ImageEditService.UpdateProcessor(c.Context(), sessionID, *req.UpdateProc) + if err != nil { + return err + } + return c.Status(http.StatusOK).JSON(sessionResponse{ + Session: res, + PreviewURL: res.PreviewURL(), + }) + } + + return fiber.ErrBadRequest +} diff --git a/services/imgedit/processing.go b/services/imgedit/processing.go index 378ead5..c1f99bf 100644 --- a/services/imgedit/processing.go +++ b/services/imgedit/processing.go @@ -21,7 +21,7 @@ type imageProcessor struct { type shadowProcessorArgs struct { Color string `json:"color"` - OffsetY int `json:"offset_y"` + OffsetY int `json:"offset_y,string"` } var processors = map[string]imageProcessor{ @@ -35,7 +35,12 @@ var processors = map[string]imageProcessor{ processImage: func(ctx context.Context, srcImg image.Image, params any) (image.Image, error) { p := params.(*shadowProcessorArgs) - shadow := makeBoxShadow(srcImg, color.Black, 4, 10, p.OffsetY) + shadowColor, err := parseHexColor(p.Color) + if err != nil { + return nil, fmt.Errorf("invalid shadow color: %w", err) + } + + shadow := makeBoxShadow(srcImg, shadowColor, 4, 10, p.OffsetY) composit := imaging.OverlayCenter(shadow, srcImg, 1.0) return composit, nil }, @@ -135,3 +140,39 @@ type imageImageSource struct { func (i imageImageSource) image() (image.Image, error) { return i.img, nil } + +func parseHexColor(s string) (color.Color, error) { + // Remove leading hash if present + if len(s) > 0 && s[0] == '#' { + s = s[1:] + } + + // Parse based on length + var r, g, b, a uint8 + switch len(s) { + case 6: + // RGB format + var rgb uint32 + if _, err := fmt.Sscanf(s, "%06x", &rgb); err != nil { + return nil, fmt.Errorf("invalid hex color format: %w", err) + } + r = uint8((rgb >> 16) & 0xFF) + g = uint8((rgb >> 8) & 0xFF) + b = uint8(rgb & 0xFF) + a = 0xFF + case 8: + // RGBA format + var rgba uint32 + if _, err := fmt.Sscanf(s, "%08x", &rgba); err != nil { + return nil, fmt.Errorf("invalid hex color format: %w", err) + } + r = uint8((rgba >> 24) & 0xFF) + g = uint8((rgba >> 16) & 0xFF) + b = uint8((rgba >> 8) & 0xFF) + a = uint8(rgba & 0xFF) + default: + return nil, fmt.Errorf("invalid hex color length: expected 6 or 8 characters, got %d", len(s)) + } + + return color.RGBA{R: r, G: g, B: b, A: a}, nil +} diff --git a/services/imgedit/service.go b/services/imgedit/service.go index d9f3ba4..c53a37e 100644 --- a/services/imgedit/service.go +++ b/services/imgedit/service.go @@ -146,6 +146,35 @@ func (s *Service) DeleteProcessor(ctx context.Context, sessionID, processorID st return session, nil } +type UpdateProcessorReq struct { + ID string `json:"id"` + Props json.RawMessage `json:"props"` +} + +func (s *Service) UpdateProcessor(ctx context.Context, sessionID string, req UpdateProcessorReq) (*models.ImageEditSession, error) { + session, err := s.loadAndVerifySession(ctx, sessionID) + if err != nil { + return nil, err + } + + for i, p := range session.Processors { + if p.ID == req.ID { + session.Processors[i].Props = req.Props + break + } + } + + session.RecalcVersionIDs() + if err := s.sessionStore.save(session); err != nil { + return nil, err + } + if _, err := s.reprocess(ctx, session); err != nil { + return nil, err + } + + return session, nil +} + func (s *Service) loadAndVerifySession(ctx context.Context, sessionID string) (*models.ImageEditSession, error) { site, user, err := s.fetchSiteAndUser(ctx) if err != nil { diff --git a/views/uploads/edit.html b/views/uploads/edit.html index 5c8cc2d..21b41bb 100644 --- a/views/uploads/edit.html +++ b/views/uploads/edit.html @@ -1,12 +1,12 @@ -
+
-
+
- {{ .upload.Upload.Alt }} + {{ .upload.Upload.Alt }}
@@ -25,6 +25,7 @@
- Actions go here + +
\ No newline at end of file