Have got adjusting processor arguments working
This commit is contained in:
parent
488942db2e
commit
f9a65c8ca9
|
|
@ -24,9 +24,17 @@ const processorUIs = {
|
||||||
"shadow": {
|
"shadow": {
|
||||||
label: "Shadow",
|
label: "Shadow",
|
||||||
template: Handlebars.compile(`
|
template: Handlebars.compile(`
|
||||||
<div class="mb-3">
|
<div class="row mb-3 align-items-center">
|
||||||
<label for="{{id}}_width" class="form-label">Colour</label>
|
<label for="{{id}}_color" class="col-sm col-form-label">Colour</label>
|
||||||
<input name="width" class="form-control" id="{{id}}_{{props.color}}" type="color" value="{{color}}" {{{submit_on id 'change'}}}>
|
<div class="col-sm">
|
||||||
|
<input name="color" class="form-control" id="{{id}}_color" type="color" value="{{props.color}}" {{{submit_on id 'change'}}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="{{id}}_offset_y" class="col-sm col-form-label">Offset Y</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input name="offset_y" class="form-control" id="{{id}}_{{props.color}}" type="number" value="{{props.offset_y}}" {{{submit_on id 'blur'}}}>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
|
|
@ -67,10 +75,19 @@ export default class UploadEditController extends Controller {
|
||||||
async removeProcessor(ev) {
|
async removeProcessor(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
let id = ev.params.id;
|
let id = ev.params.id;
|
||||||
console.log(ev.params);
|
|
||||||
await this._removeProcessor(id);
|
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() {
|
_rebuildProcessList() {
|
||||||
let el = this.processListTarget;
|
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) {
|
async _removeProcessor(processorID) {
|
||||||
await this._doReturningState(async () => {
|
await this._doReturningState(async () => {
|
||||||
return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors/${processorID}`, {
|
return (await fetch(`/sites/${this.siteIdValue}/imageedit/${this._state.session.guid}/processors/${processorID}`, {
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ Starting weiro without any arguments will start the server.
|
||||||
siteGroup.Get("/uploads/:uploadID/edit", uh.Edit)
|
siteGroup.Get("/uploads/:uploadID/edit", uh.Edit)
|
||||||
|
|
||||||
siteGroup.Post("/imageedit", ieh.Create)
|
siteGroup.Post("/imageedit", ieh.Create)
|
||||||
|
siteGroup.Patch("/imageedit/:sessionID", ieh.PatchSession)
|
||||||
siteGroup.Post("/imageedit/:sessionID/processors", ieh.AddProcessor)
|
siteGroup.Post("/imageedit/:sessionID/processors", ieh.AddProcessor)
|
||||||
siteGroup.Delete("/imageedit/:sessionID/processors/:processorID", ieh.DeleteProcessor)
|
siteGroup.Delete("/imageedit/:sessionID/processors/:processorID", ieh.DeleteProcessor)
|
||||||
siteGroup.Get("/imageedit/:sessionID/preview/:versionID", ieh.Preview)
|
siteGroup.Get("/imageedit/:sessionID/preview/:versionID", ieh.Preview)
|
||||||
|
|
|
||||||
|
|
@ -113,3 +113,32 @@ func (ieh ImageEditHandlers) DeleteProcessor(c fiber.Ctx) error {
|
||||||
PreviewURL: res.PreviewURL(),
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ type imageProcessor struct {
|
||||||
|
|
||||||
type shadowProcessorArgs struct {
|
type shadowProcessorArgs struct {
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
OffsetY int `json:"offset_y"`
|
OffsetY int `json:"offset_y,string"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var processors = map[string]imageProcessor{
|
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) {
|
processImage: func(ctx context.Context, srcImg image.Image, params any) (image.Image, error) {
|
||||||
p := params.(*shadowProcessorArgs)
|
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)
|
composit := imaging.OverlayCenter(shadow, srcImg, 1.0)
|
||||||
return composit, nil
|
return composit, nil
|
||||||
},
|
},
|
||||||
|
|
@ -135,3 +140,39 @@ type imageImageSource struct {
|
||||||
func (i imageImageSource) image() (image.Image, error) {
|
func (i imageImageSource) image() (image.Image, error) {
|
||||||
return i.img, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,35 @@ func (s *Service) DeleteProcessor(ctx context.Context, sessionID, processorID st
|
||||||
return session, nil
|
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) {
|
func (s *Service) loadAndVerifySession(ctx context.Context, sessionID string) (*models.ImageEditSession, error) {
|
||||||
site, user, err := s.fetchSiteAndUser(ctx)
|
site, user, err := s.fetchSiteAndUser(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<main class="flex-grow-1 flex-shrink-1">
|
<main class="flex-grow-1 flex-shrink-1"
|
||||||
|
data-controller="edit-upload"
|
||||||
|
data-edit-upload-site-id-value="{{ .site.ID }}"
|
||||||
|
data-edit-upload-upload-id-value="{{ .upload.Upload.ID }}"
|
||||||
|
>
|
||||||
<div class="flex-grow-1 flex-shrink-1 d-flex flex-column">
|
<div class="flex-grow-1 flex-shrink-1 d-flex flex-column">
|
||||||
<div class="row flex-grow-1 flex-shrink-1 m-0"
|
<div class="row flex-grow-1 flex-shrink-1 m-0">
|
||||||
data-controller="edit-upload"
|
|
||||||
data-edit-upload-site-id-value="{{ .site.ID }}"
|
|
||||||
data-edit-upload-upload-id-value="{{ .upload.Upload.ID }}"
|
|
||||||
>
|
|
||||||
<figure class="col-md-9 p-3">
|
<figure class="col-md-9 p-3">
|
||||||
<img data-edit-upload-target="preview" src="{{ .upload.URL }}" alt="{{ .upload.Upload.Alt }}" class="img-fluid">
|
<img data-edit-upload-target="preview" src="{{ .upload.URL }}" alt="{{ .upload.Upload.Alt }}" class="img-fluid" style="max-height: 80vw;">
|
||||||
</figure>
|
</figure>
|
||||||
<div class="col-md-3 p-3">
|
<div class="col-md-3 p-3">
|
||||||
<div data-edit-upload-target="processList"></div>
|
<div data-edit-upload-target="processList"></div>
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
Actions go here
|
<button class="btn btn-primary" data-action="edit-upload#saveUpload">Save</button>
|
||||||
|
<button class="btn btn-secondary" data-action="edit-upload#saveNewUpload">Save as Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
Loading…
Reference in a new issue