Add about dialog
This commit is contained in:
parent
e6c0611c00
commit
49fefda7b4
7 changed files with 169 additions and 53 deletions
|
@ -1,4 +1,5 @@
|
|||
1.4.2:
|
||||
- Add About dialog
|
||||
- Redesign dark checkers
|
||||
|
||||
1.4.1:
|
||||
|
|
BIN
game/asset/image/icon.png
Normal file
BIN
game/asset/image/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
198
game/game.go
198
game/game.go
|
@ -81,6 +81,8 @@ var (
|
|||
|
||||
imgProfileBirthday1 *ebiten.Image
|
||||
|
||||
imgIcon *ebiten.Image
|
||||
|
||||
fontMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
|
@ -137,6 +139,8 @@ var (
|
|||
|
||||
displayFrame *etk.Frame
|
||||
connectFrame *etk.Frame
|
||||
registerFrame *etk.Frame
|
||||
resetFrame *etk.Frame
|
||||
createGameFrame *etk.Frame
|
||||
joinGameFrame *etk.Frame
|
||||
historyFrame *etk.Frame
|
||||
|
@ -244,6 +248,8 @@ func loadImageAssets(width int) {
|
|||
imgCubes64 = resizeDice(imgCubes.SubImage(image.Rect(size*2, size*1, size*3, size*2)), 0.6)
|
||||
|
||||
imgProfileBirthday1 = ebiten.NewImageFromImage(loadImage("asset/image/profile_birthday_1.png"))
|
||||
|
||||
imgIcon = ebiten.NewImageFromImage(loadImage("asset/image/icon.png"))
|
||||
}
|
||||
|
||||
func loadAudioAssets() {
|
||||
|
@ -578,7 +584,8 @@ type Game struct {
|
|||
|
||||
tutorialFrame *etk.Frame
|
||||
|
||||
quitDialog *etk.Grid
|
||||
aboutDialog *etk.Grid
|
||||
quitDialog *etk.Grid
|
||||
|
||||
bufferFontSize int
|
||||
|
||||
|
@ -744,6 +751,81 @@ func (g *Game) initialize() {
|
|||
return false
|
||||
})}
|
||||
|
||||
var aboutGrid *etk.Grid
|
||||
{
|
||||
aboutLabel := gotext.Get("About")
|
||||
bounds := etk.BoundString(etk.FontFace(etk.Style.TextFont, etk.Scale(largeFontSize)), aboutLabel)
|
||||
aboutButton := etk.NewButton(aboutLabel, g.showAboutDialog)
|
||||
aboutGrid = etk.NewGrid()
|
||||
aboutGrid.SetRowSizes(-1, bounds.Dy()*2)
|
||||
aboutGrid.SetColumnSizes(-1, bounds.Dx()+etk.Scale(50))
|
||||
aboutGrid.AddChildAt(etk.NewText("Boxcars "+AppVersion), 0, 1, 1, 1)
|
||||
aboutGrid.AddChildAt(aboutButton, 1, 1, 1, 1)
|
||||
}
|
||||
|
||||
{
|
||||
g.aboutDialog = etk.NewGrid()
|
||||
d := g.aboutDialog
|
||||
d.SetRowSizes(etk.Scale(baseButtonHeight), -1, -1, -1, -1, etk.Scale(baseButtonHeight))
|
||||
d.SetColumnSizes(etk.Scale(10), etk.Scale(172), -1, etk.Scale(128), etk.Scale(10))
|
||||
d.SetBackground(color.RGBA{40, 24, 9, 255})
|
||||
|
||||
var y int
|
||||
{
|
||||
label := resizeText("Boxcars")
|
||||
label.SetHorizontal(etk.AlignCenter)
|
||||
label.SetVertical(etk.AlignCenter)
|
||||
label.SetFont(etk.Style.TextFont, largeFontSize)
|
||||
d.AddChildAt(label, 1, y, 3, 1)
|
||||
y++
|
||||
}
|
||||
|
||||
{
|
||||
label := resizeText(fmt.Sprintf(gotext.Get("Created by %s"), "Trevor Slocum"))
|
||||
label.SetVertical(etk.AlignCenter)
|
||||
iconSprite := etk.NewSprite(imgIcon)
|
||||
iconSprite.SetHorizontal(etk.AlignEnd)
|
||||
iconSprite.SetVertical(etk.AlignStart)
|
||||
d.AddChildAt(label, 1, y, 2, 1)
|
||||
d.AddChildAt(iconSprite, 3, y, 1, 3)
|
||||
d.AddChildAt(etk.NewBox(), 4, y, 1, 1)
|
||||
y++
|
||||
}
|
||||
|
||||
{
|
||||
ll := resizeText(gotext.Get("Source:"))
|
||||
ll.SetVertical(etk.AlignCenter)
|
||||
rl := resizeText("bgammon.org/code")
|
||||
rl.SetVertical(etk.AlignCenter)
|
||||
d.AddChildAt(ll, 1, y, 1, 1)
|
||||
d.AddChildAt(rl, 2, y, 1, 1)
|
||||
y++
|
||||
}
|
||||
|
||||
{
|
||||
ll := resizeText(gotext.Get("Donate:"))
|
||||
ll.SetVertical(etk.AlignCenter)
|
||||
rl := resizeText("bgammon.org/donate")
|
||||
rl.SetVertical(etk.AlignCenter)
|
||||
d.AddChildAt(ll, 1, y, 1, 1)
|
||||
d.AddChildAt(rl, 2, y, 1, 1)
|
||||
y++
|
||||
}
|
||||
|
||||
{
|
||||
ll := resizeText(gotext.Get("Contact:"))
|
||||
ll.SetVertical(etk.AlignCenter)
|
||||
rl := resizeText("bgammon.org/community")
|
||||
rl.SetVertical(etk.AlignCenter)
|
||||
d.AddChildAt(ll, 1, y, 1, 1)
|
||||
d.AddChildAt(rl, 2, y, 2, 1)
|
||||
y++
|
||||
}
|
||||
|
||||
d.AddChildAt(etk.NewButton(gotext.Get("Return"), func() error { g.aboutDialog.SetVisible(false); return nil }), 0, 5, 5, 1)
|
||||
d.SetVisible(false)
|
||||
}
|
||||
|
||||
{
|
||||
headerLabel := newCenteredText(gotext.Get("Register"))
|
||||
emailLabel := newCenteredText(gotext.Get("Email"))
|
||||
|
@ -782,10 +864,6 @@ func (g *Game) initialize() {
|
|||
|
||||
infoLabel := etk.NewText(gotext.Get("Please enter a valid email address, or it will not be possible to reset your password."))
|
||||
|
||||
footerLabel := etk.NewText("Boxcars " + AppVersion)
|
||||
footerLabel.SetHorizontal(etk.AlignEnd)
|
||||
footerLabel.SetVertical(etk.AlignEnd)
|
||||
|
||||
grid := etk.NewGrid()
|
||||
grid.SetColumnPadding(int(g.board.horizontalBorderSize / 2))
|
||||
grid.SetRowPadding(yPadding)
|
||||
|
@ -813,8 +891,12 @@ func (g *Game) initialize() {
|
|||
grid.AddChildAt(subGrid, 1, y, 3, 1)
|
||||
}
|
||||
grid.AddChildAt(infoLabel, 1, y+1, 3, 1)
|
||||
grid.AddChildAt(footerLabel, 1, y+2, 3, 1)
|
||||
grid.AddChildAt(aboutGrid, 1, y+2, 3, 1)
|
||||
registerGrid = grid
|
||||
|
||||
registerFrame = etk.NewFrame(registerGrid)
|
||||
registerFrame.SetPositionChildren(true)
|
||||
registerFrame.AddChild(etk.NewFrame(g.aboutDialog))
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -840,10 +922,6 @@ func (g *Game) initialize() {
|
|||
|
||||
g.resetInfo = etk.NewText("")
|
||||
|
||||
footerLabel := etk.NewText("Boxcars " + AppVersion)
|
||||
footerLabel.SetHorizontal(etk.AlignEnd)
|
||||
footerLabel.SetVertical(etk.AlignEnd)
|
||||
|
||||
grid := etk.NewGrid()
|
||||
grid.SetColumnPadding(int(g.board.horizontalBorderSize / 2))
|
||||
grid.SetRowPadding(yPadding)
|
||||
|
@ -867,8 +945,12 @@ func (g *Game) initialize() {
|
|||
grid.AddChildAt(subGrid, 1, y, 3, 1)
|
||||
}
|
||||
grid.AddChildAt(g.resetInfo, 1, y+1, 3, 1)
|
||||
grid.AddChildAt(footerLabel, 1, y+2, 3, 1)
|
||||
grid.AddChildAt(aboutGrid, 1, y+2, 3, 1)
|
||||
resetGrid = grid
|
||||
|
||||
resetFrame = etk.NewFrame(resetGrid)
|
||||
resetFrame.SetPositionChildren(true)
|
||||
resetFrame.AddChild(etk.NewFrame(g.aboutDialog))
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -883,10 +965,6 @@ func (g *Game) initialize() {
|
|||
|
||||
infoLabel := etk.NewText(gotext.Get("To log in as a guest, enter a username (if you want) and do not enter a password."))
|
||||
|
||||
footerLabel := etk.NewText("Boxcars " + AppVersion)
|
||||
footerLabel.SetHorizontal(etk.AlignEnd)
|
||||
footerLabel.SetVertical(etk.AlignEnd)
|
||||
|
||||
g.connectUsername = &Input{etk.NewInput("", func(text string) (handled bool) {
|
||||
g.selectConnect()
|
||||
return false
|
||||
|
@ -948,7 +1026,7 @@ func (g *Game) initialize() {
|
|||
grid.AddChildAt(subGrid, 1, g.connectGridY+1, 3, 1)
|
||||
}
|
||||
grid.AddChildAt(infoLabel, 1, g.connectGridY+2, 3, 1)
|
||||
grid.AddChildAt(footerLabel, 1, g.connectGridY+3, 3, 1)
|
||||
grid.AddChildAt(aboutGrid, 1, g.connectGridY+3, 3, 1)
|
||||
connectGrid = grid
|
||||
|
||||
{
|
||||
|
@ -967,6 +1045,7 @@ func (g *Game) initialize() {
|
|||
|
||||
connectFrame = etk.NewFrame(connectGrid)
|
||||
connectFrame.SetPositionChildren(true)
|
||||
connectFrame.AddChild(etk.NewFrame(g.aboutDialog))
|
||||
connectFrame.AddChild(etk.NewFrame(g.quitDialog))
|
||||
}
|
||||
|
||||
|
@ -983,11 +1062,11 @@ func (g *Game) initialize() {
|
|||
})}
|
||||
centerInput(g.lobby.createGameName)
|
||||
|
||||
g.lobby.createGamePoints = &Input{etk.NewInput("", func(text string) (handled bool) {
|
||||
g.lobby.createGamePoints = &NumericInput{etk.NewInput("", func(text string) (handled bool) {
|
||||
g.lobby.confirmCreateGame()
|
||||
return false
|
||||
})}
|
||||
centerInput(g.lobby.createGamePoints)
|
||||
centerNumericInput(g.lobby.createGamePoints)
|
||||
|
||||
g.lobby.createGamePassword = &Input{etk.NewInput("", func(text string) (handled bool) {
|
||||
g.lobby.confirmCreateGame()
|
||||
|
@ -1194,6 +1273,7 @@ func (g *Game) initialize() {
|
|||
{
|
||||
g.lobby.historyPageDialog = etk.NewGrid()
|
||||
g.lobby.historyPageDialogInput = &NumericInput{etk.NewInput("", g.confirmHistoryPage)}
|
||||
centerNumericInput(g.lobby.historyPageDialogInput)
|
||||
label := resizeText(gotext.Get("Go to page:"))
|
||||
label.SetHorizontal(etk.AlignCenter)
|
||||
label.SetVertical(etk.AlignCenter)
|
||||
|
@ -1904,22 +1984,25 @@ func (g *Game) ConnectLocal(conn net.Conn) {
|
|||
}
|
||||
|
||||
func (g *Game) selectRegister() error {
|
||||
g.closeDialogs()
|
||||
g.showRegister = true
|
||||
g.registerUsername.SetText(g.connectUsername.Text())
|
||||
g.registerPassword.SetText(g.connectPassword.Text())
|
||||
g.setRoot(registerGrid)
|
||||
g.setRoot(registerFrame)
|
||||
etk.SetFocus(g.registerEmail)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) selectReset() error {
|
||||
g.closeDialogs()
|
||||
g.showReset = true
|
||||
g.setRoot(resetGrid)
|
||||
g.setRoot(resetFrame)
|
||||
etk.SetFocus(g.resetEmail)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) selectCancel() error {
|
||||
g.closeDialogs()
|
||||
g.showRegister = false
|
||||
g.showReset = false
|
||||
g.setRoot(connectFrame)
|
||||
|
@ -1928,6 +2011,7 @@ func (g *Game) selectCancel() error {
|
|||
}
|
||||
|
||||
func (g *Game) selectConfirmRegister() error {
|
||||
g.closeDialogs()
|
||||
go hideKeyboard()
|
||||
g.Email = g.registerEmail.Text()
|
||||
g.Username = g.registerUsername.Text()
|
||||
|
@ -1941,6 +2025,7 @@ func (g *Game) selectConfirmRegister() error {
|
|||
}
|
||||
|
||||
func (g *Game) selectConfirmReset() error {
|
||||
g.closeDialogs()
|
||||
go hideKeyboard()
|
||||
if g.resetInProgress {
|
||||
return nil
|
||||
|
@ -1963,6 +2048,7 @@ func (g *Game) selectConfirmReset() error {
|
|||
}
|
||||
|
||||
func (g *Game) selectConnect() error {
|
||||
g.closeDialogs()
|
||||
go hideKeyboard()
|
||||
g.Username = g.connectUsername.Text()
|
||||
g.Password = g.connectPassword.Text()
|
||||
|
@ -1973,6 +2059,16 @@ func (g *Game) selectConnect() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) showAboutDialog() error {
|
||||
g.aboutDialog.SetVisible(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) closeDialogs() {
|
||||
g.aboutDialog.SetVisible(false)
|
||||
g.quitDialog.SetVisible(false)
|
||||
}
|
||||
|
||||
func (g *Game) searchMatches(username string) {
|
||||
go hideKeyboard()
|
||||
loadingText := newCenteredText(gotext.Get("Loading..."))
|
||||
|
@ -2096,7 +2192,11 @@ func (g *Game) handleInput(keys []ebiten.Key) error {
|
|||
}
|
||||
}
|
||||
case ebiten.KeyEnter, ebiten.KeyKPEnter:
|
||||
if g.showRegister {
|
||||
if g.aboutDialog.Visible() {
|
||||
g.aboutDialog.SetVisible(false)
|
||||
g.ignoreEnter = true
|
||||
return nil
|
||||
} else if g.showRegister {
|
||||
g.selectConfirmRegister()
|
||||
return nil
|
||||
} else if g.showReset {
|
||||
|
@ -2110,7 +2210,10 @@ func (g *Game) handleInput(keys []ebiten.Key) error {
|
|||
return nil
|
||||
}
|
||||
case ebiten.KeyEscape:
|
||||
if g.showRegister || g.showReset {
|
||||
if g.aboutDialog.Visible() {
|
||||
g.aboutDialog.SetVisible(false)
|
||||
return nil
|
||||
} else if g.showRegister || g.showReset {
|
||||
g.selectCancel()
|
||||
return nil
|
||||
} else {
|
||||
|
@ -2274,7 +2377,7 @@ func (g *Game) Update() error {
|
|||
defer g.Unlock()
|
||||
|
||||
if g.ignoreEnter {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyEnter) {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyEnter) || ebiten.IsKeyPressed(ebiten.KeyKPEnter) {
|
||||
return nil
|
||||
}
|
||||
g.ignoreEnter = false
|
||||
|
@ -2343,6 +2446,8 @@ func (g *Game) Update() error {
|
|||
err := g.handleInput(g.pressedKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if g.ignoreEnter {
|
||||
return nil
|
||||
}
|
||||
|
||||
if mobileDevice {
|
||||
|
@ -2356,34 +2461,10 @@ func (g *Game) Update() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !g.loggedIn {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !viewBoard {
|
||||
if g.lobby.showCreateGame || g.lobby.showJoinGame {
|
||||
if g.lobby.showCreateGame {
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
p := image.Point{cx, cy}
|
||||
if p.In(g.lobby.createGameName.Rect()) {
|
||||
etk.SetFocus(g.lobby.createGameName)
|
||||
} else if p.In(g.lobby.createGamePoints.Rect()) {
|
||||
etk.SetFocus(g.lobby.createGamePoints)
|
||||
} else if p.In(g.lobby.createGamePassword.Rect()) {
|
||||
etk.SetFocus(g.lobby.createGamePassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if g.lobby.showCreateGame {
|
||||
pointsText := g.lobby.createGamePoints.Text()
|
||||
strippedText := strings.Join(anyNumbers.FindAllString(pointsText, -1), "")
|
||||
if pointsText != strippedText {
|
||||
g.lobby.createGamePoints.SetText(strippedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if viewBoard {
|
||||
g.board.Update()
|
||||
}
|
||||
return nil
|
||||
|
@ -2502,6 +2583,20 @@ func (g *Game) layoutConnect() {
|
|||
resetGrid.SetRowSizes(headerHeight, fieldHeight, etk.Scale(baseButtonHeight), infoHeight)
|
||||
}
|
||||
|
||||
{
|
||||
dialogWidth := etk.Scale(800)
|
||||
if dialogWidth > game.screenW {
|
||||
dialogWidth = game.screenW
|
||||
}
|
||||
dialogHeight := etk.Scale(baseButtonHeight)*2 + etk.Scale(250)
|
||||
if dialogHeight > game.screenH {
|
||||
dialogHeight = game.screenH
|
||||
}
|
||||
|
||||
x, y := game.screenW/2-dialogWidth/2, game.screenH/2-dialogHeight+int(g.board.verticalBorderSize)
|
||||
g.aboutDialog.SetRect(image.Rect(x, y, x+dialogWidth, y+dialogHeight))
|
||||
}
|
||||
|
||||
{
|
||||
dialogWidth := etk.Scale(400)
|
||||
if dialogWidth > game.screenW {
|
||||
|
@ -2939,6 +3034,11 @@ func centerInput(input *Input) {
|
|||
input.SetPadding(etk.Scale(5))
|
||||
}
|
||||
|
||||
func centerNumericInput(input *NumericInput) {
|
||||
input.SetVertical(etk.AlignCenter)
|
||||
input.SetPadding(etk.Scale(5))
|
||||
}
|
||||
|
||||
func saveReplay(id int, content []byte) error {
|
||||
if id <= 0 {
|
||||
return nil
|
||||
|
|
|
@ -61,7 +61,7 @@ type lobby struct {
|
|||
|
||||
showCreateGame bool
|
||||
createGameName *Input
|
||||
createGamePoints *Input
|
||||
createGamePoints *NumericInput
|
||||
createGamePassword *Input
|
||||
createGameAceyCheckbox *etk.Checkbox
|
||||
createGameTabulaCheckbox *etk.Checkbox
|
||||
|
|
|
@ -25,6 +25,9 @@ msgstr ""
|
|||
msgid "%s wins!"
|
||||
msgstr ""
|
||||
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
|
||||
msgid "Accept"
|
||||
msgstr ""
|
||||
|
||||
|
@ -76,6 +79,9 @@ msgstr ""
|
|||
msgid "Connecting..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Contact:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
|
@ -91,12 +97,18 @@ msgstr ""
|
|||
msgid "Create new match"
|
||||
msgstr ""
|
||||
|
||||
msgid "Created by %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Donate:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Double"
|
||||
msgstr ""
|
||||
|
||||
|
@ -340,6 +352,9 @@ msgstr ""
|
|||
msgid "Sound"
|
||||
msgstr ""
|
||||
|
||||
msgid "Source:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed"
|
||||
msgstr ""
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ toolchain go1.23.0
|
|||
require (
|
||||
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240918192230-0a4b5e9b0923
|
||||
code.rocket9labs.com/tslocum/bgammon-bei-bot v0.0.0-20240917031657-2648772e515e
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240912051307-7b35245a5adc
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240919024136-b9ddff24b583
|
||||
code.rocket9labs.com/tslocum/gotext v0.0.0-20240728181248-46f419ff143b
|
||||
code.rocket9labs.com/tslocum/tabula v0.0.0-20240820025521-91065b63f1be
|
||||
github.com/coder/websocket v1.8.12
|
||||
|
|
4
go.sum
4
go.sum
|
@ -4,8 +4,8 @@ code.rocket9labs.com/tslocum/bgammon v0.0.0-20240918192230-0a4b5e9b0923 h1:pOFWM
|
|||
code.rocket9labs.com/tslocum/bgammon v0.0.0-20240918192230-0a4b5e9b0923/go.mod h1:fW6aZ6va+Wh8Hw/r7fkIAHUmo/Mwkm0kR2llEX2G3Uc=
|
||||
code.rocket9labs.com/tslocum/bgammon-bei-bot v0.0.0-20240917031657-2648772e515e h1:5DSsHaB/xrD57Gmu/1YaWMRFLiTVvVmcZRa9/EJZuZo=
|
||||
code.rocket9labs.com/tslocum/bgammon-bei-bot v0.0.0-20240917031657-2648772e515e/go.mod h1:mZA8T+w1qkH5tzFfDQb87wCms+qlulyT9Q8deuK1ApQ=
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240912051307-7b35245a5adc h1:/CVCpTW/FjMJSa4OYwUP4UJPtnp2HGT2Q/I/5Pj7VXE=
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240912051307-7b35245a5adc/go.mod h1:SnxDzE2sOpFBidRavJ795+IJ0bK4PeJ8EfvxF5hoU6E=
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240919024136-b9ddff24b583 h1:5yYDNs+vnkXyp5cuxew78nlfvtsmxX9RGN5T2F7T0q0=
|
||||
code.rocket9labs.com/tslocum/etk v0.0.0-20240919024136-b9ddff24b583/go.mod h1:yCH2sQkwEsOXZjXwcO8ls5RzTc9aeLdSOWtsAhCq+0M=
|
||||
code.rocket9labs.com/tslocum/gotext v0.0.0-20240728181248-46f419ff143b h1:KSdR7VPoftY5Bt+osGRBbtHlCWxhe1QbOuf58CG7ieI=
|
||||
code.rocket9labs.com/tslocum/gotext v0.0.0-20240728181248-46f419ff143b/go.mod h1:ZkYZ/IF/ebzhUL2bNp4ALROsuH9iCztUWvUJBWsHXRU=
|
||||
code.rocket9labs.com/tslocum/tabula v0.0.0-20240820025521-91065b63f1be h1:awgpl3vS+d7vciw7X5z+joGSPZHB1OOTpkPKW3mPGDs=
|
||||
|
|
Loading…
Reference in a new issue