package overlay import ( "sync/atomic" "time" "git.d3nexus.de/Dadido3/D3vugu-components/components/navigation" "github.com/vugu/vugu" "golang.org/x/exp/slices" ) // Container can be embedded in your root component and provides a way to show overlays like modals and toasts. type Container struct { modal vugu.Builder `vugu:"data"` modalTitle string `vugu:"data"` modalSignalClasses string // Additional classes that are applied to the modal menu bar. modalContainerClasses string // Additional classes that are applied to the whole modal body. toasts []ContainerToast `vugu:"data"` waitOverlayCounter atomic.Int32 // Counter for the wait/loading overlay. If > 0, the overlay will be shown. } type ContainerToast struct { body vugu.Builder `vugu:"data"` signalClasses string // Additional classes that are applied to the small bar to the left. containerClasses string // Additional classes that are applied to the whole toast. } func (c *Container) handleModalClose(event vugu.DOMEvent) { c.modal = nil } func (c *Container) SetModal(component vugu.Builder) { c.modal = component c.modalTitle = "" c.modalContainerClasses = "" c.modalSignalClasses = "d3c-color-accent" // Retrieve title from component if it implements the PageTitleGetter interface. if pageInfo, ok := component.(navigation.PageTitleGetter); ok { c.modalTitle, _, _ = pageInfo.PageTitle() } // Retrieve additional CSS classes from component. if overlayClassesGetter, ok := component.(OverlayClassesGetter); ok { c.modalSignalClasses, c.modalContainerClasses = overlayClassesGetter.OverlayClasses() } } func (c *Container) handleToastClose(event vugu.DOMEvent, toast vugu.Builder) { c.CloseToast(toast) } func (c *Container) AddToast(eventEnv vugu.EventEnv, component vugu.Builder) { toast := ContainerToast{ body: component, signalClasses: "d3c-color-accent", } // Retrieve additional classes. if overlayClassesGetter, ok := component.(OverlayClassesGetter); ok { toast.signalClasses, toast.containerClasses = overlayClassesGetter.OverlayClasses() } // Handle auto close after a given amount of time. if durationGetter, ok := component.(ToastDurationGetter); ok && durationGetter.ToastDuration() > 0 { go func(component vugu.Builder) { time.Sleep(durationGetter.ToastDuration()) eventEnv.Lock() defer eventEnv.UnlockRender() c.CloseToast(component) }(component) } c.toasts = append(c.toasts, toast) } func (c *Container) CloseToast(component vugu.Builder) { for i, toast := range c.toasts { if toast.body == component { c.toasts = slices.Delete(c.toasts, i, i+1) break } } } // WaitOverlayOpen returns true if the wait overlay is currently opened. func (c *Container) WaitOverlayActive() bool { return c.waitOverlayCounter.Load() != 0 } // WaitOverlayOpen shows the wait overlay. // Internally this is implemented as counter, so you have to ensure that there are as many WaitOverlayOpen as WaitOverlayDone calls. // // Always ensure that there is the complementary WaitOverlayDone() call, ideally with a defer. func (c *Container) WaitOverlayOpen() { c.waitOverlayCounter.Add(1) } // WaitOverlayDone will mark the current wait overlay as done. // Internally this is implemented as counter, so you have to ensure that there are as many WaitOverlayOpen as WaitOverlayDone calls. func (c *Container) WaitOverlayDone() { c.waitOverlayCounter.Add(-1) } type OverlayContainerRef struct { *Container } func (p *OverlayContainerRef) OverlayContainerSet(container *Container) { p.Container = container } type OverlayContainerSetter interface { OverlayContainerSet(*Container) } var _ OverlayContainerSetter = &OverlayContainerRef{}