diff --git a/internal/domains/accounts/page.go b/internal/domains/accounts/page.go index b4ca13c..3d606f2 100644 --- a/internal/domains/accounts/page.go +++ b/internal/domains/accounts/page.go @@ -12,10 +12,15 @@ import ( ) // TODO: simpify, if possible (single query ideal) -func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform, orderIndex int) error { +func (db *Store) SetOrderOfPlatformOnAccountPage( + ctx context.Context, + acctID int64, + platform Platform, + orderIndex int, +) (orderedPlatforms []Platform, prevOrderIndex int, err error) { tx, err := db.db.Begin(ctx) if err != nil { - return fmt.Errorf("failed to start transaction: %w", err) + return nil, 0, fmt.Errorf("failed to start transaction: %w", err) } defer tx.Rollback(ctx) @@ -39,7 +44,7 @@ func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int }, ) if err != nil { - return fmt.Errorf("failed to perform query to look up existing indexes: %w", err) + return nil, 0, fmt.Errorf("failed to perform query to look up existing indexes: %w", err) } indexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { @@ -47,7 +52,7 @@ func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int Order_index int }]) if err != nil { - return fmt.Errorf("failed to scan rows for query to look up existing indexes: %w", err) + return nil, 0, fmt.Errorf("failed to scan rows for query to look up existing indexes: %w", err) } // compute the implied indexes, @@ -74,9 +79,13 @@ func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int } } - prevOrderIndex := indexPerPlatform[platform] + prevOrderIndex = indexPerPlatform[platform] + orderedPlatforms = make([]Platform, len(allPlatforms)) if indexPerPlatform[platform] == orderIndex { - return nil + for i := range allPlatforms { + orderedPlatforms[i] = platformPerIndex[i] + } + return orderedPlatforms, prevOrderIndex, nil } if increased := orderIndex > prevOrderIndex; increased { @@ -110,6 +119,7 @@ func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int valuesLines[i] = fmt.Sprintf("(@account_id, @platform_%d, @order_index_%d::smallint)", i, i) args[fmt.Sprintf("platform_%d", i)] = p args[fmt.Sprintf("order_index_%d", i)] = indexPerPlatform[p] + orderedPlatforms[i] = platformPerIndex[i] } _, err = tx.Exec( @@ -148,14 +158,14 @@ func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int args, ) if err != nil { - return fmt.Errorf("failed to perform query to delete old indexes and insert new indexes: %w", err) + return nil, 0, fmt.Errorf("failed to perform query to delete old indexes and insert new indexes: %w", err) } if tx.Commit(ctx); err != nil { - return fmt.Errorf("failed to commit txn: %w", err) + return nil, 0, fmt.Errorf("failed to commit txn: %w", err) } - return nil + return orderedPlatforms, prevOrderIndex, nil } func (db *Store) GetOrderOfPlatformsOnAccountPage(ctx context.Context, acctID int64) ([]Platform, error) { diff --git a/internal/domains/accounts/store_with_context.go b/internal/domains/accounts/store_with_context.go index 6434abb..e48abc7 100644 --- a/internal/domains/accounts/store_with_context.go +++ b/internal/domains/accounts/store_with_context.go @@ -51,7 +51,7 @@ func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Accoun return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID) } -func (v_ctx *StoreWithContext) SetOrderOfPlatformOnAccountPage(acctID int64, platform Platform, orderIndex int) error { +func (v_ctx *StoreWithContext) SetOrderOfPlatformOnAccountPage(acctID int64, platform Platform, orderIndex int) ([]Platform, int, error) { return v_ctx.Store.SetOrderOfPlatformOnAccountPage(v_ctx.ctx, acctID, platform, orderIndex) } diff --git a/internal/server/api/accounts/router.go b/internal/server/api/accounts/router.go index 5dbf6fc..1241b65 100644 --- a/internal/server/api/accounts/router.go +++ b/internal/server/api/accounts/router.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -19,6 +20,7 @@ import ( type accountSubrouter struct { log *logging.Logger accts *accounts.Store + pub *sse.UpdateNotificationPublisher } func Routes( @@ -30,6 +32,7 @@ func Routes( as := &accountSubrouter{ log: logger, accts: accts, + pub: pub, } r.POST("", response.Handler(as.createAccount)) @@ -187,9 +190,13 @@ func getOrderIndexForSyncGroupListingDraftFromPath(c *gin.Context) (int, error) return orderIndex, nil } +// PUT /:acctID/platforms/:platform/order-index" +// this endpoint is called when dragging a platform tab in the accounts page. func (s *accountSubrouter) setOrderOfPlatformOnAccountPage(c *gin.Context) (response.Response, error) { acctID := auth.GetIdentity(c).Account.AccountID + // validate parameters + var orderIndex int if v, ok := c.GetPostForm("order-index"); !ok { return nil, response.BadRequest(). @@ -216,9 +223,34 @@ func (s *accountSubrouter) setOrderOfPlatformOnAccountPage(c *gin.Context) (resp platform = p } - if err := s.accts.SetOrderOfPlatformOnAccountPage(c, acctID, platform, orderIndex); err != nil { + // update the order + + platforms, prevIndex, err := s.accts.SetOrderOfPlatformOnAccountPage(c, acctID, platform, orderIndex) + if err != nil { return nil, fmt.Errorf("failed to save record: %w", err) } - return response.Status(200), nil + // emit events on all platforms updated, so the tabs can all refresh (including their logic) + + platformEvents := make([]string, max(orderIndex, prevIndex)-min(orderIndex, prevIndex)) + if orderIndex > prevIndex { + for i := range orderIndex - prevIndex { + p := platforms[prevIndex+i] + platformEvents[i] = fmt.Sprintf("accounts_%d_platforms_%s_order-index", acctID, lowerSnakeCase(p)) + } + } else if orderIndex < prevIndex { + for i := range prevIndex - orderIndex { + p := platforms[orderIndex+1+i] + platformEvents[i] = fmt.Sprintf("accounts_%d_platforms_%s_order-index", acctID, lowerSnakeCase(p)) + } + } + if err := s.pub.Push(c, acctID, platformEvents...); err != nil { + s.log.Errorf("failed to publish platform order-index events: %v", err) + } + + return response.StatusNoContent(), nil +} + +func lowerSnakeCase(s accounts.Platform) string { + return strings.ToLower(strings.Join(strings.Split(string(s), " "), "_")) } diff --git a/internal/server/sse/publisher.go b/internal/server/sse/publisher.go index 7b3903d..8ea0ab7 100644 --- a/internal/server/sse/publisher.go +++ b/internal/server/sse/publisher.go @@ -2,9 +2,11 @@ package sse import ( "context" + "errors" "fmt" "path" "strings" + "sync" "time" "github.com/gin-gonic/gin" @@ -120,6 +122,34 @@ func (p *UpdateNotificationPublisher) Publish(pathPattern string) gin.HandlerFun } } +func (p *UpdateNotificationPublisher) Push(ctx context.Context, acctID int64, eventTypes ...string) error { + var wg sync.WaitGroup + wg.Add(len(eventTypes)) + + errs := make([]error, len(eventTypes)) + for i, e := range eventTypes { + i := i + e := e + + go func() { + defer wg.Done() + if err := p.queue.Send(ctx, Event{ + AccountID: acctID, + Type: e, + Data: []byte(fmt.Sprintf(`{"eventType": %q}`, e)), + }); err != nil { + errs[i] = fmt.Errorf("failed to send sse event to listener: %w", err) + } + }() + } + + wg.Wait() + + return errors.Join(errs...) + + return nil +} + func getPathSegments(p string) []string { p = path.Clean(p) if p == "" || p == "." || p == "/" { diff --git a/internal/server/ui/router.go b/internal/server/ui/router.go index a970d58..5475ab0 100644 --- a/internal/server/ui/router.go +++ b/internal/server/ui/router.go @@ -66,7 +66,7 @@ func Routes( }, // strings - "lowerCamelCase": func(s string) string { + "lowerSnakeCase": func(s string) string { return strings.ToLower(strings.Join(strings.Split(s, " "), "_")) }, diff --git a/scripts/hx-drag.js b/scripts/hx-drag.js index 6fc5dbf..5d77842 100644 --- a/scripts/hx-drag.js +++ b/scripts/hx-drag.js @@ -114,9 +114,6 @@ const dragAttr = GetAttribute(drag, "hx-drag"); const dropAttr = GetAttribute(drop, "hx-drop"); - console.log('dragAttr:', dragAttr); - console.log('dropAttr:', dropAttr); - const dragVals = JSON.parse(GetAttribute(drag, "hx-drag") || "{}"); const dropVals = JSON.parse(GetAttribute(drop, "hx-drop") || "{}"); @@ -159,8 +156,6 @@ values = Object.assign({}, dragVals, dropVals); } - console.log('action:', action) - if (action === null) return; try { // don't let one failure cascade to the other ajax diff --git a/templates/components/accounts/{acctID.int64}/shops/list-items/{platform}.html.tmpl b/templates/components/accounts/{acctID.int64}/shops/list-items/{platform}.html.tmpl index d79eab2..90f942a 100644 --- a/templates/components/accounts/{acctID.int64}/shops/list-items/{platform}.html.tmpl +++ b/templates/components/accounts/{acctID.int64}/shops/list-items/{platform}.html.tmpl @@ -6,44 +6,48 @@ {{- end }} -{{- $platformSegment := lowerCamelCase (printf "%s" $platform) }} -{{- $path := printf "/accounts/%d/platforms/%s/order-index" $acctID $platformSegment }} +{{- $platformSegment := lowerSnakeCase (printf "%s" $platform) }} +{{- $updatePath := printf "/accounts/%d/platforms/%s/order-index" $acctID $platformSegment }}
  • will be NOT be updated by the response (204 response), but instead the following sse events */}} hx-drag="{}" - hx-trigger="sse:{{$path}}" - hx-get="/ui{{$path}}" - hx-on::sse-message="console.log('test')" - hx-on::sseMessage="console.log('test')" - hx-on::sseOpen="console.log('test')" - hx-on::sse-open="console.log('test')" - hx-on:htmx:sse-message="console.log('test')" - hx-on:htmx:sseMessage="console.log('test')" - hx-on:htmx:sseOpen="console.log('test')" - hx-on:htmx:sse-open="console.log('test')" - hx-on::htmx:sse-message="console.log('test')" - hx-on::htmx:sseMessage="console.log('test')" - hx-on::htmx:sseOpen="console.log('test')" - hx-on::htmx:sse-open="console.log('test')" - hx-on--sse-message="console.log('test')" - hx-on--sseMessage="console.log('test')" - hx-on--sseOpen="console.log('test')" - hx-on--sse-open="console.log('test')" - hx-on-htmx-sse-message="console.log('test')" - hx-on-htmx-sseMessage="console.log('test')" - hx-on-htmx-sseOpen="console.log('test')" - hx-on-htmx-sse-open="console.log('test')" - hx-on--htmx-sse-message="console.log('test')" - hx-on--htmx-sseMessage="console.log('test')" - hx-on--htmx-sseOpen="console.log('test')" - hx-on--htmx-sse-open="console.log('test')" + hx-drop='{"order-index": "{{$orderIndex}}"}' + hx-drag-action={{printf "/api/accounts/%d/platforms/%s/order-index" $acctID $platformSegment}} + + hx-trigger={{printf "sse:accounts_%d_platforms_%s_order-index" $acctID $platformSegment }} + hx-get={{printf "/ui/accounts/%d/shops/list-items/%s" $acctID $platformSegment}} + draggable="true" + data-order-index={{$orderIndex}} + _=" + on dragstart + set event.dataTransfer.effectAllowed to 'move' + event.dataTransfer.setData('platform-section', my id) + event.dataTransfer.setData('order-index', my dataset.orderIndex) + + on dragover + if event.dataTransfer.types.includes('platform-section') then + event.preventDefault() + end + + on drop + event.preventDefault() + set draggedID to event.dataTransfer.getData('platform-section') + set draggedOrderIndex to event.dataTransfer.getData('order-index') + set myOrderIndex to parseInt(my dataset.orderIndex) + set draggedSection to #{draggedID} + if draggedOrderIndex < myOrderIndex then + me.after(draggedSection) + else + me.before(draggedSection) + end + " >
    - + {{/* group-open:border-b-background group-open:border-b-[0.25rem] @@ -85,12 +92,12 @@

    {{$platform.PrettyPrint}}

    -
    - {{- $pathSegment := lowerCamelCase (printf "%s" $platform) }} - {{- - component - (printf "/accounts/%d/shops/link-sections/%s" $acctID $pathSegment) - }} +
    + {{- $pathSegment := lowerSnakeCase (printf "%s" $platform) }} + {{- component (printf "/accounts/%d/shops/link-sections/%s" $acctID $pathSegment) }}
  • diff --git a/templates/pages/accounts/{acctID.int64}/index.html.tmpl b/templates/pages/accounts/{acctID.int64}/index.html.tmpl index 7ee2cc8..4e4cbb6 100644 --- a/templates/pages/accounts/{acctID.int64}/index.html.tmpl +++ b/templates/pages/accounts/{acctID.int64}/index.html.tmpl @@ -57,11 +57,6 @@ border-b-[2px] border-sidebar-border " - - hx-trigger="{{printf "sse:accounts_%d_platforms" $acctID}}" - hx-get="{{printf "/ui/accounts/%d" $acctID}}" - hx-select="#shops-section" - hx-swap="morph" >
    @@ -70,7 +65,7 @@ -