Merge branch 'worktree-agent-a1dd614394ee20ff2'
This commit is contained in:
116 files changed
+6484
-9
No files matched your search
@@ -1,3 +1,200 @@
|
||||
package main
|
||||
|
||||
var messagingPages = []page{}
|
||||
const messagingCategory = "Messaging & Mail"
|
||||
|
||||
var messagingPages = []page{
|
||||
{ID: "401", Name: "ChannelList", ZhName: "频道列表", Category: messagingCategory, Demo: channelListDemo,
|
||||
Keys: "Tab 进入;上下方向键切换行;Enter / Space 选择或折叠;分组标题上的左、右方向键折叠、展开。", Code: `groups := []mujica.ChannelGroup{{ID: "ch", Title: "Channels", Channels: []mujica.Channel{
|
||||
{ID: "general", Name: "general", Unread: 3},
|
||||
{ID: "rehearsal", Name: "rehearsal", Pinned: true},
|
||||
}}}
|
||||
selected := "general"
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.ChannelList(c, &selected, groups, mujica.ChannelListOptions{}).Changed() {
|
||||
_ = selected // show that channel
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "402", Name: "ChatMessage", ZhName: "聊天消息", Category: messagingCategory, Demo: chatMessageDemo,
|
||||
Keys: "可选文字支持选择复制;插槽控件按 Tab 访问。", Code: `prev := mujica.ChatEntry{ID: "1", Author: "Uika", Time: time.Now(), Text: "Rehearsal at nine."}
|
||||
m := mujica.ChatEntry{ID: "2", Author: "Uika", Time: time.Now(), Text: "Bring the masks.", Edited: true}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.ChatMessage(c, prev, mujica.ChatMessageOptions{})
|
||||
mujica.ChatMessage(c, m, mujica.ChatMessageOptions{
|
||||
Continued: mujica.ChatContinues(prev, m),
|
||||
Reactions: func() { mujica.Tag(c, "👍 2", mujica.TagOptions{}) },
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "403", Name: "UnreadDivider", ZhName: "未读分隔线", Category: messagingCategory, Demo: unreadDividerDemo,
|
||||
Keys: "展示组件保持自然焦点序列。", Code: `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.UnreadDivider(c, 3)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "404", Name: "ReadReceipt", ZhName: "已读回执", Category: messagingCategory, Demo: readReceiptDemo,
|
||||
Keys: "展示组件保持自然焦点序列。", Code: `readers := []mujica.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.ReadReceipt(c, mujica.ReceiptRead, mujica.ReadReceiptOptions{Readers: readers})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "405", Name: "MemberList", ZhName: "成员列表", Category: messagingCategory, Demo: memberListDemo,
|
||||
Keys: "Tab 聚焦列表;上下方向键选择;Home / End 首尾;Enter 或双击打开成员。", Code: `var s mujica.MemberListState
|
||||
members := []mujica.Member{
|
||||
{ID: "1", Name: "Sakiko", Presence: mujica.PresenceOnline, Role: "Leader"},
|
||||
{ID: "2", Name: "Umiri", Presence: mujica.PresenceOffline},
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.MemberList(c, &s, members, mujica.MemberListOptions{}); r.Opened != "" {
|
||||
_ = r.Opened // show that member's profile
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "406", Name: "UserProfileCard", ZhName: "用户资料卡", Category: messagingCategory, Demo: userProfileCardDemo,
|
||||
Keys: "Tab 访问操作插槽;Enter / Space 激活按钮。", Code: `profile := mujica.UserProfile{Name: "Sakiko Togawa", Presence: mujica.PresenceBusy, Role: "Keyboard", Status: "In rehearsal"}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.UserProfileCard(c, profile, mujica.UserProfileCardOptions{Actions: func() {
|
||||
mujica.Button(c, "Message", mujica.ButtonOptions{})
|
||||
}})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "407", Name: "OnlineStatus", ZhName: "在线状态", Category: messagingCategory, Demo: onlineStatusDemo,
|
||||
Keys: "展示组件保持自然焦点序列。", Code: `view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.OnlineStatus(c, mujica.PresenceAway, mujica.OnlineStatusOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "408", Name: "StatusSetter", ZhName: "状态设置", Category: messagingCategory, Demo: statusSetterDemo,
|
||||
Keys: "Tab 访问表情、文字、期限和清除按钮;选择器用方向键移动、Enter 确认、Escape 关闭。", Code: `status := mujica.CustomStatus{Emoji: "🎻", Text: "Rehearsing"}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.StatusSetter(c, &status, mujica.StatusSetterOptions{}).Changed() {
|
||||
_ = status // publish it; clear it once status.Until passes
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "409", Name: "CallControls", ZhName: "通话控制", Category: messagingCategory, Demo: callControlsDemo,
|
||||
Keys: "Tab 进入工具栏;左右方向键移动;Enter / Space 激活。Cmd/Ctrl+D 静音,Cmd/Ctrl+E 摄像头,Cmd/Ctrl+Shift+S 共享,Cmd/Ctrl+Shift+H 挂断。", Code: `st := mujica.CallState{}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.CallControls(c, st, mujica.CallControlsOptions{
|
||||
OnMute: func(on bool) { st.Muted = on },
|
||||
OnCamera: func(off bool) { st.CameraOff = off },
|
||||
OnShare: func(on bool) { st.Sharing = on },
|
||||
OnHangUp: func() {}, // leave the call
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "410", Name: "VideoCallGrid", ZhName: "视频通话宫格", Category: messagingCategory, Demo: videoCallGridDemo,
|
||||
Keys: "Tab 进入宫格;方向键移动参与者焦点并读取状态。", Code: `people := []mujica.CallParticipant{
|
||||
{ID: "1", Name: "Sakiko", Speaking: true}, // Frame: the latest decoded frame
|
||||
{ID: "2", Name: "Uika", Muted: true},
|
||||
}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.VideoCallGrid(c, people, mujica.VideoCallGridOptions{})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "411", Name: "IncomingCallDialog", ZhName: "来电对话框", Category: messagingCategory, Demo: incomingCallDialogDemo,
|
||||
Keys: "默认焦点为拒绝;Tab 循环访问操作;Enter / Space 激活;Escape 拒绝;关闭后恢复焦点。", Code: `ringing := true
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.IncomingCallDialog(c, &ringing, mujica.IncomingCall{Caller: "Sakiko", Video: true}, mujica.IncomingCallOptions{
|
||||
OnAccept: func(video bool) {}, // join, with the camera on when video
|
||||
OnDecline: func() {},
|
||||
})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "412", Name: "EmojiPicker", ZhName: "表情选择器", Category: messagingCategory, Demo: emojiPickerDemo,
|
||||
Keys: "Tab 访问搜索、肤色、分类、表情;方向键在组内移动;Enter / Space 选择。", Code: `var recent []string
|
||||
tone := 0
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.EmojiPicker(c, mujica.EmojiPickerOptions{Recent: &recent, Tone: &tone}); r.Picked != "" {
|
||||
_ = r.Picked // insert it
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "413", Name: "PinnedMessages", ZhName: "置顶消息", Category: messagingCategory, Demo: pinnedMessagesDemo,
|
||||
Keys: "Tab 访问跳转与取消置顶;方向键移动;Enter / Space 激活。", Code: `pins := []mujica.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.PinnedMessages(c, pins, mujica.PinnedMessagesOptions{})
|
||||
if r.Unpinned != "" {
|
||||
pins = pins[:0] // unpin it
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "414", Name: "ThreadPanel", ZhName: "话题侧栏", Category: messagingCategory, Demo: threadPanelDemo,
|
||||
Keys: "Tab 访问关闭、回复和发送;Enter 发送回复;Escape 关闭面板。", Code: `open := true
|
||||
root := mujica.ChatEntry{ID: "r", Author: "Sakiko", Text: "Set list for Friday?"}
|
||||
var replies []mujica.ChatEntry
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.ThreadPanel(c, &open, root, replies, mujica.ThreadPanelOptions{}); r.Sent != "" {
|
||||
replies = append(replies, mujica.ChatEntry{ID: r.Sent, Author: "Me", Text: r.Sent, Own: true})
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "415", Name: "MailList", ZhName: "邮件列表", Category: messagingCategory, Demo: mailListDemo,
|
||||
Keys: "上下方向键、Home / End 移动选择;Enter 或双击打开;S 请求星标;Cmd/Ctrl+A 全选;Cmd/Ctrl 点击切换单项,Shift 点击选择范围。", Code: `var s mujica.ListState[string]
|
||||
mails := []mujica.MailItem{{ID: "1", From: "Sakiko", Subject: "Second act", Unread: true, Attachments: 1}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.MailList(c, &s, mails, mujica.MailListOptions{}); r.Starred != "" {
|
||||
mails[0].Starred = !mails[0].Starred
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "416", Name: "MailReader", ZhName: "邮件阅读器", Category: messagingCategory, Demo: mailReaderDemo,
|
||||
Keys: "Tab 访问链接、引用开关和附件;Enter / Space 激活;正文支持选择复制。", Code: `m := mujica.Mail{From: "Sakiko", Subject: "Second act", Body: "The score is attached.\n> Can you send it?",
|
||||
Attachments: []mujica.MailAttachment{{Name: "score.pdf", Size: 2048}}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if r := mujica.MailReader(c, m); r.Link != "" {
|
||||
c.OpenURL(r.Link)
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "417", Name: "MailComposer", ZhName: "邮件编辑器", Category: messagingCategory, Demo: mailComposerDemo,
|
||||
Keys: "Tab 访问字段与操作;Cmd/Ctrl+S 保存草稿;组件内 Cmd/Ctrl+Enter 发送;正文 Enter 换行。", Code: `draft := mujica.MailDraft{To: []string{"uika@example.com"}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
r := mujica.MailComposer(c, &draft, mujica.MailComposerOptions{
|
||||
OnSaveDraft: func(d mujica.MailDraft) {}, // store d
|
||||
})
|
||||
if r.Sent {
|
||||
draft = mujica.MailDraft{}
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "418", Name: "RecipientInput", ZhName: "收件人输入", Category: messagingCategory, Demo: recipientInputDemo,
|
||||
Keys: "Enter 或分隔符提交标签;空输入 Backspace 删除末项;上下方向键移动联系人建议,Enter 选择,Escape 收起建议。", Code: `var to []string
|
||||
contacts := []mujica.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}}
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.RecipientInput(c, &to, mujica.RecipientInputOptions{Contacts: contacts, Label: "To"})
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "419", Name: "QuotedText", ZhName: "引用文字", Category: messagingCategory, Demo: quotedTextDemo,
|
||||
Keys: "Tab 聚焦开关;Enter / Space 展开或折叠;展开内容支持选择复制。", Code: `open := false
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
mujica.QuotedText(c, "> Can you send the score?", &open)
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
{ID: "420", Name: "SnoozePicker", ZhName: "稍后提醒", Category: messagingCategory, Demo: snoozePickerDemo,
|
||||
Keys: "Tab 进入;上下方向键选择快捷项;Enter / Space 激活;自定义时间沿用 DateTimePicker 的输入、确认和取消操作。", Code: `var until time.Time
|
||||
view := func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{})
|
||||
if mujica.SnoozePicker(c, &until, mujica.SnoozePickerOptions{}).Changed() {
|
||||
_ = until // hide the mail until then
|
||||
}
|
||||
}
|
||||
ui.Render(view, 400, 300, 1)`},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func callControlsDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
call mujica.CallState
|
||||
disabled bool
|
||||
ended bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "409", func() state { return state{} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Reconnect", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
s.ended = false
|
||||
log("call joined")
|
||||
}
|
||||
mujica.CallControls(c, s.call, mujica.CallControlsOptions{
|
||||
Disabled: s.disabled || s.ended,
|
||||
OnMute: func(on bool) { s.call.Muted = on; log(fmt.Sprintf("mute %v", on)) },
|
||||
OnCamera: func(off bool) { s.call.CameraOff = off; log(fmt.Sprintf("camera off %v", off)) },
|
||||
OnShare: func(on bool) { s.call.Sharing = on; log(fmt.Sprintf("sharing %v", on)) },
|
||||
OnHangUp: func() { s.ended = true; log("call left") },
|
||||
})
|
||||
ui.Text(c, fmt.Sprintf("Muted: %v; camera off: %v; sharing: %v; ended: %v", s.call.Muted, s.call.CameraOff, s.call.Sharing, s.ended))
|
||||
}
|
||||
|
||||
func videoCallGridDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
speaking, muted, cameraOff, frame bool
|
||||
columns float64
|
||||
}
|
||||
s := ui.Local(c.Root(), "410", func() state { return state{speaking: true, frame: true, columns: 2} })
|
||||
mediaCheck(c, log, &s.speaking, "Speaking")
|
||||
mediaCheck(c, log, &s.muted, "Muted")
|
||||
mediaCheck(c, log, &s.cameraOff, "Camera off")
|
||||
mediaCheck(c, log, &s.frame, "Caller-provided frame")
|
||||
if mujica.NumberInput(c, &s.columns, mujica.NumberInputOptions{Min: 0, Max: 3, Step: 1, Label: "Columns (0 = auto)"}).Changed() {
|
||||
log(fmt.Sprintf("columns %.0f", s.columns))
|
||||
}
|
||||
people := []mujica.CallParticipant{
|
||||
{ID: "1", Name: "Sakiko", Speaking: s.speaking, Muted: s.muted, CameraOff: s.cameraOff},
|
||||
{ID: "2", Name: "Uika", Muted: true}, {ID: "3", Name: "Mutsumi", CameraOff: true}, {ID: "4", Name: "Umiri"},
|
||||
}
|
||||
if s.frame {
|
||||
people[0].Frame = mediaBitmaps[0]
|
||||
people[3].Frame = mediaBitmaps[1]
|
||||
}
|
||||
mujica.VideoCallGrid(c, people, mujica.VideoCallGridOptions{Columns: int(s.columns)})
|
||||
}
|
||||
|
||||
func incomingCallDialogDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
open, video, focusAccept, disabled bool
|
||||
result string
|
||||
}
|
||||
s := ui.Local(c.Root(), "411", func() state { return state{video: true} })
|
||||
mediaCheck(c, log, &s.video, "Video call")
|
||||
mediaCheck(c, log, &s.focusAccept, "Focus Accept")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Incoming call", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
|
||||
s.open = true
|
||||
log("incoming call")
|
||||
}
|
||||
mujica.IncomingCallDialog(c, &s.open, mujica.IncomingCall{Caller: "Sakiko Togawa", Video: s.video}, mujica.IncomingCallOptions{
|
||||
Disabled: s.disabled, FocusAccept: s.focusAccept,
|
||||
OnAccept: func(video bool) { s.result = fmt.Sprintf("Accepted, video: %v", video); log(s.result) },
|
||||
OnDecline: func() { s.result = "Declined"; log(s.result) },
|
||||
})
|
||||
ui.Text(c, s.result)
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func chatMessageDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
continued, edited, own, reply, reactions bool
|
||||
reacted bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "402", func() state { return state{continued: true, edited: true, reply: true, reactions: true} })
|
||||
mediaCheck(c, log, &s.continued, "Merge consecutive messages")
|
||||
mediaCheck(c, log, &s.edited, "Edited")
|
||||
mediaCheck(c, log, &s.own, "Own message")
|
||||
mediaCheck(c, log, &s.reply, "Reply slot")
|
||||
mediaCheck(c, log, &s.reactions, "Reaction slot")
|
||||
at := time.Date(2026, 10, 5, 21, 7, 0, 0, time.Local)
|
||||
mujica.ChatMessage(c, mujica.ChatEntry{Author: "Uika", Time: at, Text: "Rehearsal at nine.", Own: s.own}, mujica.ChatMessageOptions{})
|
||||
opts := mujica.ChatMessageOptions{Continued: s.continued}
|
||||
if s.reply {
|
||||
opts.Reply = func() {
|
||||
if mujica.QuoteReply(c, "Sakiko", "When do we start?").Clicked() {
|
||||
log("jump to quoted message")
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.reactions {
|
||||
opts.Reactions = func() {
|
||||
if mujica.Toggle(c, &s.reacted, "Like", mujica.ToggleOptions{}).Changed() {
|
||||
log(fmt.Sprintf("reaction %v", s.reacted))
|
||||
}
|
||||
}
|
||||
}
|
||||
mujica.ChatMessage(c, mujica.ChatEntry{Author: "Uika", Time: at.Add(time.Minute), Text: "Bring the masks.", Own: s.own, Edited: s.edited}, opts)
|
||||
}
|
||||
|
||||
func unreadDividerDemo(c *ui.Context, log func(string)) {
|
||||
count := ui.Local(c.Root(), "403", func() float64 { return 3 })
|
||||
if mujica.NumberInput(c, count, mujica.NumberInputOptions{Min: 0, Max: 999, Step: 1, Label: "Unread count"}).Changed() {
|
||||
log(fmt.Sprintf("unread %.0f", *count))
|
||||
}
|
||||
mujica.UnreadDivider(c, int(*count))
|
||||
inputSection(c, "Zero count", func() { mujica.UnreadDivider(c, 0) })
|
||||
}
|
||||
|
||||
func readReceiptDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
receipt mujica.ReceiptState
|
||||
readers bool
|
||||
max float64
|
||||
}
|
||||
s := ui.Local(c.Root(), "404", func() state { return state{receipt: mujica.ReceiptRead, readers: true, max: 3} })
|
||||
items := []mujica.SelectOption[mujica.ReceiptState]{{Value: mujica.ReceiptSent, Label: "Sent"}, {Value: mujica.ReceiptDelivered, Label: "Delivered"}, {Value: mujica.ReceiptRead, Label: "Read"}}
|
||||
if mujica.Select(c, &s.receipt, items, mujica.SelectOptions{Label: "Receipt"}).Changed() {
|
||||
log(fmt.Sprintf("receipt %d", s.receipt))
|
||||
}
|
||||
mediaCheck(c, log, &s.readers, "Reader avatars")
|
||||
if mujica.NumberInput(c, &s.max, mujica.NumberInputOptions{Min: 1, Max: 4, Step: 1, Label: "Max avatars"}).Changed() {
|
||||
log(fmt.Sprintf("max %.0f", s.max))
|
||||
}
|
||||
opts := mujica.ReadReceiptOptions{Max: int(s.max)}
|
||||
if s.readers {
|
||||
opts.Readers = []mujica.AvatarItem{{Name: "Uika"}, {Name: "Mutsumi"}, {Name: "Umiri"}, {Name: "Nyamu"}}
|
||||
}
|
||||
mujica.ReadReceipt(c, s.receipt, opts)
|
||||
}
|
||||
|
||||
func emojiPickerDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
recent []string
|
||||
tone int
|
||||
picked string
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "412", func() state { return state{} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if r := mujica.EmojiPicker(c, mujica.EmojiPickerOptions{Recent: &s.recent, Tone: &s.tone, Disabled: s.disabled}); r.Picked != "" {
|
||||
s.picked = r.Picked
|
||||
log("emoji " + r.Picked)
|
||||
}
|
||||
ui.Text(c, "Picked: "+s.picked)
|
||||
}
|
||||
|
||||
func pinnedMessagesDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
pins []mujica.ChatEntry
|
||||
disabled bool
|
||||
jump string
|
||||
}
|
||||
newPins := func() []mujica.ChatEntry {
|
||||
return []mujica.ChatEntry{{ID: "a", Author: "Sakiko", Text: "Rehearsal moves to nine."}, {ID: "b", Author: "Uika", Text: "Masks are in the hall."}}
|
||||
}
|
||||
s := ui.Local(c.Root(), "413", func() state { return state{pins: newPins()} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Restore pins", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
s.pins = newPins()
|
||||
log("pins restored")
|
||||
}
|
||||
r := mujica.PinnedMessages(c, s.pins, mujica.PinnedMessagesOptions{Disabled: s.disabled, Height: 180})
|
||||
if r.Jump != "" {
|
||||
s.jump = r.Jump
|
||||
log("jump " + r.Jump)
|
||||
}
|
||||
if r.Unpinned != "" {
|
||||
s.pins = slices.DeleteFunc(slices.Clone(s.pins), func(m mujica.ChatEntry) bool { return m.ID == r.Unpinned })
|
||||
log("unpin " + r.Unpinned)
|
||||
}
|
||||
ui.Text(c, "Jump target: "+s.jump)
|
||||
}
|
||||
|
||||
func threadPanelDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
open, disabled bool
|
||||
replies []mujica.ChatEntry
|
||||
}
|
||||
s := ui.Local(c.Root(), "414", func() state {
|
||||
return state{open: true, replies: []mujica.ChatEntry{{ID: "1", Author: "Uika", Text: "Overture first."}}}
|
||||
})
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.Button(c, "Open thread", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
|
||||
s.open = true
|
||||
log("thread opened")
|
||||
}
|
||||
before := s.open
|
||||
ui.Row(c).Height(420).Children(func() {
|
||||
r := mujica.ThreadPanel(c, &s.open, mujica.ChatEntry{ID: "root", Author: "Sakiko", Text: "Set list for Friday?"}, s.replies, mujica.ThreadPanelOptions{Disabled: s.disabled})
|
||||
if r.Sent != "" {
|
||||
s.replies = append(s.replies, mujica.ChatEntry{ID: fmt.Sprint(len(s.replies) + 1), Author: "Me", Text: r.Sent, Own: true})
|
||||
log("reply sent")
|
||||
}
|
||||
})
|
||||
if before && !s.open {
|
||||
log("thread closed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func mailListDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
list mujica.ListState[string]
|
||||
mails []mujica.MailItem
|
||||
disabled, empty bool
|
||||
opened string
|
||||
}
|
||||
s := ui.Local(c.Root(), "415", func() state {
|
||||
return state{mails: []mujica.MailItem{
|
||||
{ID: "1", From: "Sakiko", Subject: "Second act", Snippet: "The score is attached.", Unread: true, Attachments: 2},
|
||||
{ID: "2", From: "Uika", Subject: "Costumes", Snippet: "Fittings on Thursday.", Starred: true},
|
||||
{ID: "3", From: "Umiri", Subject: "Venue", Snippet: "The hall is booked."},
|
||||
}}
|
||||
})
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
mediaCheck(c, log, &s.empty, "Empty")
|
||||
if mujica.Button(c, "Load 100,000 mails", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
s.mails = make([]mujica.MailItem, 100000)
|
||||
for i := range s.mails {
|
||||
s.mails[i] = mujica.MailItem{ID: fmt.Sprint(i), From: "Sender", Subject: fmt.Sprintf("Mail %d", i), Unread: i%2 == 0}
|
||||
}
|
||||
log("loaded 100,000 mails")
|
||||
}
|
||||
mails := s.mails
|
||||
if s.empty {
|
||||
mails = nil
|
||||
}
|
||||
r := mujica.MailList(c, &s.list, mails, mujica.MailListOptions{Disabled: s.disabled, Height: 240})
|
||||
if r.Changed() {
|
||||
log(fmt.Sprintf("selected %d mails", s.list.Selection.Len()))
|
||||
}
|
||||
if r.Opened != "" {
|
||||
s.opened = r.Opened
|
||||
for i := range s.mails {
|
||||
if s.mails[i].ID == r.Opened {
|
||||
s.mails[i].Unread = false
|
||||
break
|
||||
}
|
||||
}
|
||||
log("open " + r.Opened)
|
||||
}
|
||||
if r.Starred != "" {
|
||||
for i := range s.mails {
|
||||
if s.mails[i].ID == r.Starred {
|
||||
s.mails[i].Starred = !s.mails[i].Starred
|
||||
break
|
||||
}
|
||||
}
|
||||
log("star " + r.Starred)
|
||||
}
|
||||
ui.Text(c, "Opened: "+s.opened)
|
||||
}
|
||||
|
||||
func mailReaderDemo(c *ui.Context, log func(string)) {
|
||||
type state struct{ markdown, attachments, disabled bool }
|
||||
s := ui.Local(c.Root(), "416", func() state { return state{attachments: true} })
|
||||
mediaCheck(c, log, &s.markdown, "Safe Markdown")
|
||||
mediaCheck(c, log, &s.attachments, "Attachments")
|
||||
mediaCheck(c, log, &s.disabled, "Disabled actions")
|
||||
m := mujica.Mail{From: "Sakiko Togawa", To: []string{"Uika Misumi"}, Cc: []string{"Umiri Yahata"}, Subject: "Second act", Date: time.Date(2026, 10, 5, 21, 7, 0, 0, time.Local), Body: "The score is attached.\n> Can you send it tonight?"}
|
||||
if s.markdown {
|
||||
m.Format = mujica.MailMarkdown
|
||||
m.Body = "**Rehearsal** at nine. [Venue](https://example.com)\n\n<script>alert(1)</script>"
|
||||
}
|
||||
if s.attachments {
|
||||
m.Attachments = []mujica.MailAttachment{{Name: "score.pdf", Size: 2048}}
|
||||
}
|
||||
ui.Column(c).Disabled(s.disabled).Children(func() {
|
||||
r := mujica.MailReader(c, m)
|
||||
if r.Link != "" {
|
||||
log("link " + r.Link)
|
||||
}
|
||||
if r.Opened >= 0 {
|
||||
log(fmt.Sprintf("open attachment %d", r.Opened))
|
||||
}
|
||||
if r.Downloaded >= 0 {
|
||||
log(fmt.Sprintf("download attachment %d", r.Downloaded))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func mailComposerDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
draft mujica.MailDraft
|
||||
disabled bool
|
||||
saved string
|
||||
}
|
||||
s := ui.Local(c.Root(), "417", func() state { return state{draft: mujica.MailDraft{To: []string{"uika@example.com"}}} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
r := mujica.MailComposer(c, &s.draft, mujica.MailComposerOptions{
|
||||
Disabled: s.disabled, Contacts: messagingContacts,
|
||||
OnSaveDraft: func(d mujica.MailDraft) { s.saved = d.Subject; log("draft saved") },
|
||||
OnAttach: func() {
|
||||
s.draft.Attachments = append(s.draft.Attachments, mujica.MailAttachment{Name: fmt.Sprintf("score-%d.pdf", len(s.draft.Attachments)+1), Size: 2048})
|
||||
log("attachment added")
|
||||
},
|
||||
})
|
||||
if r.Sent {
|
||||
log("mail sent")
|
||||
s.draft = mujica.MailDraft{}
|
||||
}
|
||||
if r.Discarded {
|
||||
log("draft discarded")
|
||||
s.draft = mujica.MailDraft{}
|
||||
}
|
||||
ui.Text(c, "Saved subject: "+s.saved)
|
||||
}
|
||||
|
||||
var messagingContacts = []mujica.Contact{{Name: "Uika Misumi", Email: "uika@example.com"}, {Name: "Umiri Yahata", Email: "umiri@example.com"}, {Name: "Sakiko Togawa", Email: "sakiko@example.com"}}
|
||||
|
||||
func recipientInputDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
to []string
|
||||
disabled, contacts bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "418", func() state { return state{contacts: true} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
mediaCheck(c, log, &s.contacts, "Contact suggestions")
|
||||
contacts := messagingContacts
|
||||
if !s.contacts {
|
||||
contacts = nil
|
||||
}
|
||||
if mujica.RecipientInput(c, &s.to, mujica.RecipientInputOptions{Contacts: contacts, Label: "Recipients", Disabled: s.disabled}).Changed() {
|
||||
log(fmt.Sprintf("recipients %d", len(s.to)))
|
||||
}
|
||||
if mujica.Button(c, "Clear recipients", mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.disabled}).Clicked() {
|
||||
s.to = nil
|
||||
log("recipients cleared")
|
||||
}
|
||||
ui.Text(c, "Type a name or email; Enter or comma commits valid addresses.")
|
||||
}
|
||||
|
||||
func quotedTextDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
open, disabled bool
|
||||
text string
|
||||
}
|
||||
s := ui.Local(c.Root(), "419", func() state { return state{text: "> Can you send the score?\n> Thanks."} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.TextArea(c, &s.text, mujica.TextAreaOptions{Label: "Quoted source", MinHeight: 80}).Changed() {
|
||||
log("quote source edited")
|
||||
}
|
||||
ui.Column(c).Disabled(s.disabled).Children(func() {
|
||||
if mujica.QuotedText(c, s.text, &s.open).Changed() {
|
||||
log(fmt.Sprintf("quote expanded %v", s.open))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func snoozePickerDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
until time.Time
|
||||
disabled bool
|
||||
morning float64
|
||||
}
|
||||
s := ui.Local(c.Root(), "420", func() state { return state{morning: 9} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.NumberInput(c, &s.morning, mujica.NumberInputOptions{Min: 1, Max: 23, Step: 1, Label: "Morning hour"}).Changed() {
|
||||
log(fmt.Sprintf("morning %.0f", s.morning))
|
||||
}
|
||||
if mujica.SnoozePicker(c, &s.until, mujica.SnoozePickerOptions{Disabled: s.disabled, MorningHour: int(s.morning)}).Changed() {
|
||||
log("snooze " + s.until.Format(time.RFC3339))
|
||||
}
|
||||
if mujica.Button(c, "Clear snooze", mujica.ButtonOptions{Variant: mujica.Secondary, Disabled: s.disabled}).Clicked() {
|
||||
s.until = time.Time{}
|
||||
log("snooze cleared")
|
||||
}
|
||||
ui.Text(c, fmt.Sprint("Until: ", s.until))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func channelListDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
selected string
|
||||
disabled bool
|
||||
revision int
|
||||
groups []mujica.ChannelGroup
|
||||
}
|
||||
s := ui.Local(c.Root(), "401", func() state {
|
||||
return state{groups: []mujica.ChannelGroup{
|
||||
{ID: "channels", Title: "Channels", Channels: []mujica.Channel{
|
||||
{ID: "general", Name: "general", Unread: 3}, {ID: "rehearsal", Name: "rehearsal", Pinned: true},
|
||||
{ID: "random", Name: "random", Unread: 120, Muted: true},
|
||||
}},
|
||||
{ID: "direct", Title: "Direct messages", Channels: []mujica.Channel{{ID: "uika", Name: "Uika", Direct: true, Presence: mujica.PresenceOnline}}},
|
||||
}}
|
||||
})
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
before := s.groups[0].Channels[0]
|
||||
mediaCheck(c, log, &s.groups[0].Channels[0].Muted, "Mute general")
|
||||
mediaCheck(c, log, &s.groups[0].Channels[0].Pinned, "Pin general")
|
||||
if before != s.groups[0].Channels[0] {
|
||||
s.revision++
|
||||
}
|
||||
if mujica.ChannelList(c, &s.selected, s.groups, mujica.ChannelListOptions{Disabled: s.disabled, Revision: s.revision}).Changed() {
|
||||
log("channel " + s.selected)
|
||||
}
|
||||
ui.Text(c, "Selected: "+s.selected)
|
||||
}
|
||||
|
||||
func memberListDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
list mujica.MemberListState
|
||||
disabled, empty bool
|
||||
members []mujica.Member
|
||||
}
|
||||
s := ui.Local(c.Root(), "405", func() state {
|
||||
return state{members: []mujica.Member{
|
||||
{ID: "1", Name: "Sakiko", Presence: mujica.PresenceOnline, Role: "Leader"},
|
||||
{ID: "2", Name: "Uika", Presence: mujica.PresenceBusy, Role: "Vocal"},
|
||||
{ID: "3", Name: "Mutsumi", Presence: mujica.PresenceAway}, {ID: "4", Name: "Umiri", Presence: mujica.PresenceOffline},
|
||||
}}
|
||||
})
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
mediaCheck(c, log, &s.empty, "Empty")
|
||||
if mujica.Button(c, "Toggle Uika presence", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
||||
s.members = slices.Clone(s.members)
|
||||
if s.members[1].Presence == mujica.PresenceOffline {
|
||||
s.members[1].Presence = mujica.PresenceOnline
|
||||
} else {
|
||||
s.members[1].Presence = mujica.PresenceOffline
|
||||
}
|
||||
log("Uika presence changed")
|
||||
}
|
||||
members := s.members
|
||||
if s.empty {
|
||||
members = nil
|
||||
}
|
||||
r := mujica.MemberList(c, &s.list, members, mujica.MemberListOptions{Disabled: s.disabled, Height: 240})
|
||||
if r.Changed() {
|
||||
log("member " + s.list.Selected)
|
||||
}
|
||||
if r.Opened != "" {
|
||||
log("profile " + r.Opened)
|
||||
}
|
||||
}
|
||||
|
||||
func userProfileCardDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
presence mujica.Presence
|
||||
actions, disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "406", func() state { return state{actions: true, presence: mujica.PresenceOnline} })
|
||||
mediaCheck(c, log, &s.actions, "Actions")
|
||||
mediaCheck(c, log, &s.disabled, "Disable actions")
|
||||
messagingPresence(c, log, &s.presence)
|
||||
opts := mujica.UserProfileCardOptions{}
|
||||
if s.actions {
|
||||
opts.Actions = func() {
|
||||
for _, action := range []string{"Message", "Call"} {
|
||||
if mujica.Button(c, action, mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
|
||||
log(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mujica.UserProfileCard(c, mujica.UserProfile{Name: "Sakiko Togawa", Presence: s.presence, Role: "Keyboard", Status: "In rehearsal", About: "Founder of the masquerade."}, opts)
|
||||
}
|
||||
|
||||
func onlineStatusDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
presence mujica.Presence
|
||||
hidden bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "407", func() state { return state{presence: mujica.PresenceOnline} })
|
||||
mediaCheck(c, log, &s.hidden, "Hide text")
|
||||
messagingPresence(c, log, &s.presence)
|
||||
mujica.OnlineStatus(c, s.presence, mujica.OnlineStatusOptions{HideText: s.hidden})
|
||||
inputSection(c, "States", func() {
|
||||
for p := mujica.PresenceOffline; p <= mujica.PresenceBusy; p++ {
|
||||
mujica.OnlineStatus(c, p, mujica.OnlineStatusOptions{})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func statusSetterDemo(c *ui.Context, log func(string)) {
|
||||
type state struct {
|
||||
status mujica.CustomStatus
|
||||
disabled bool
|
||||
}
|
||||
s := ui.Local(c.Root(), "408", func() state { return state{} })
|
||||
mediaCheck(c, log, &s.disabled, "Disabled")
|
||||
if mujica.StatusSetter(c, &s.status, mujica.StatusSetterOptions{Disabled: s.disabled}).Changed() {
|
||||
log("status changed")
|
||||
}
|
||||
ui.Text(c, fmt.Sprintf("Text: %s; expires: %v", s.status.Text, s.status.Until))
|
||||
}
|
||||
|
||||
func messagingPresence(c *ui.Context, log func(string), value *mujica.Presence) {
|
||||
items := []mujica.SelectOption[mujica.Presence]{
|
||||
{Value: mujica.PresenceOnline, Label: "Online"}, {Value: mujica.PresenceAway, Label: "Away"},
|
||||
{Value: mujica.PresenceBusy, Label: "Busy"}, {Value: mujica.PresenceOffline, Label: "Offline"},
|
||||
}
|
||||
if mujica.Select(c, value, items, mujica.SelectOptions{Label: "Presence"}).Changed() {
|
||||
log(fmt.Sprintf("presence %d", *value))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
mujica "mujicaui"
|
||||
)
|
||||
|
||||
func messagingDemoTester(t *testing.T, id string, mode mujica.Mode) (*ui.Tester, *[]string) {
|
||||
t.Helper()
|
||||
i := slices.IndexFunc(messagingPages, func(p page) bool { return p.ID == id })
|
||||
if i < 0 {
|
||||
t.Fatalf("missing page %s", id)
|
||||
}
|
||||
events := &[]string{}
|
||||
tt := ui.NewTester(func(c *ui.Context) {
|
||||
mujica.Use(c, mujica.Settings{Mode: mode})
|
||||
ui.Scroll(c).Fill().Padding(24).Gap(12).Children(func() {
|
||||
messagingPages[i].Demo(c, func(s string) { *events = append(*events, s) })
|
||||
})
|
||||
}, 900, 800)
|
||||
tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1})
|
||||
tt.Frame()
|
||||
return tt, events
|
||||
}
|
||||
|
||||
func TestMessagingDemosInteractAndLog(t *testing.T) {
|
||||
cases := []struct{ id, click, event string }{
|
||||
{"401", "random, 120 unread", "channel random"},
|
||||
{"402", "Like", "reaction true"},
|
||||
{"403", "Unread count", "unread 7"},
|
||||
{"404", "Read", "receipt 0"},
|
||||
{"405", "Sakiko", "member 1"},
|
||||
{"406", "Message", "Message"},
|
||||
{"407", "Presence", "presence 3"},
|
||||
{"408", "Status text", "status changed"},
|
||||
{"409", "Mute", "mute true"},
|
||||
{"410", "Speaking", "Speaking = false"},
|
||||
{"411", "Incoming call", "Declined"},
|
||||
{"412", "grinning face", "emoji "},
|
||||
{"413", "Unpin Uika's message", "unpin b"},
|
||||
{"414", "Reply in thread", "reply sent"},
|
||||
{"415", "Star Second act", "star 1"},
|
||||
{"416", "Download score.pdf", "download attachment 0"},
|
||||
{"417", "Attach", "attachment added"},
|
||||
{"418", "Recipients", "recipients 1"},
|
||||
{"419", "Show quoted text (2 lines)", "quote expanded true"},
|
||||
{"420", "Tomorrow", "snooze "},
|
||||
}
|
||||
if len(messagingPages) != len(cases) {
|
||||
t.Fatalf("pages=%d, cases=%d", len(messagingPages), len(cases))
|
||||
}
|
||||
for _, tc := range cases {
|
||||
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
|
||||
t.Run(tc.id+mode.String(), func(t *testing.T) {
|
||||
tt, events := messagingDemoTester(t, tc.id, mode)
|
||||
if err := tt.Click(tc.click); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
switch tc.id {
|
||||
case "403":
|
||||
tt.Command("selectAll")
|
||||
tt.Type("7")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
case "404":
|
||||
if err := tt.Click("Sent"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case "407":
|
||||
tt.Key(0, ui.KeyDown)
|
||||
tt.Key(0, ui.KeyDown)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
case "408":
|
||||
tt.Type("Rehearsing")
|
||||
case "411":
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
case "414":
|
||||
tt.Type("Second act")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
case "418":
|
||||
tt.Type("guest@example.com")
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
}
|
||||
if !slices.ContainsFunc(*events, func(s string) bool { return strings.HasPrefix(s, tc.event) }) {
|
||||
t.Fatalf("want %q, events %v", tc.event, *events)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessagingDemoCaptures(t *testing.T) {
|
||||
dir := os.Getenv("MESSAGING_DEMO_DIR")
|
||||
if dir == "" {
|
||||
t.Skip("set MESSAGING_DEMO_DIR to export demos")
|
||||
}
|
||||
for _, p := range messagingPages {
|
||||
if p.Category != "Messaging & Mail" || p.Code == "" || p.Keys == "" {
|
||||
t.Fatalf("incomplete page %s", p.ID)
|
||||
}
|
||||
for _, mode := range []mujica.Mode{mujica.Light, mujica.Dark} {
|
||||
tt, _ := messagingDemoTester(t, p.ID, mode)
|
||||
f, err := os.Create(filepath.Join(dir, fmt.Sprintf("%s-%s.png", p.ID, mode)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := png.Encode(f, tt.Image()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user