diff --git a/app/app.go b/app/app.go index 7d86a855..79d3ad28 100644 --- a/app/app.go +++ b/app/app.go @@ -89,23 +89,22 @@ func (a *App) GetContext() context.Context { func (a *App) Startup(ctx context.Context) { a.ctx = ctx + if err := a.loadConfig(); err != nil { + messages.SendError(a.ctx, err) + } + + go a.loadHistory(a.GetLastAddress(), nil, nil) + a.FreshenController = daemons.NewFreshen(a, "freshen", 3000, a.GetSessionDeamon("daemon-freshen")) a.ScraperController = daemons.NewScraper(a, "scraper", 7000, a.GetSessionDeamon("daemon-scraper")) a.IpfsController = daemons.NewIpfs(a, "ipfs", 10000, a.GetSessionDeamon("daemon-ipfs")) go a.startDaemons() - if startupError != nil { a.Fatal(startupError.Error()) } logger.Info("Starting freshen process...") a.Refresh(a.GetSession().LastRoute) - - if err := a.loadConfig(); err != nil { - messages.SendError(a.ctx, err) - } - - go a.loadHistory(a.GetLastAddress(), nil, nil) } func (a *App) DomReady(ctx context.Context) { @@ -172,6 +171,6 @@ type ModifyData struct { func (a *App) ModifyNoop(modData *ModifyData) error { route := a.GetSessionVal("route") - messages.Send(a.ctx, messages.Info, messages.NewInfoMessage(fmt.Sprintf("%s modify %s: %s", route, modData.Operation, modData.Address.Hex()))) + messages.Send(a.ctx, messages.Info, messages.NewInfoMessage(fmt.Sprintf("%s modify NO-OP %s: %s", route, modData.Operation, modData.Address.Hex()))) return nil } diff --git a/app/data_history.go b/app/data_history.go index 382d2ace..046d6003 100644 --- a/app/data_history.go +++ b/app/data_history.go @@ -4,7 +4,6 @@ import ( "fmt" "sort" "sync" - "sync/atomic" "github.com/TrueBlocks/trueblocks-browse/pkg/messages" "github.com/TrueBlocks/trueblocks-browse/pkg/types" @@ -15,10 +14,6 @@ import ( ) func (a *App) HistoryPage(addr string, first, pageSize int) *types.HistoryContainer { - if !a.isConfigured() { - return &types.HistoryContainer{} - } - address, ok := a.ConvertToAddress(addr) if !ok { messages.Send(a.ctx, messages.Error, messages.NewErrorMsg(fmt.Errorf("Invalid address: "+addr))) @@ -27,14 +22,6 @@ func (a *App) HistoryPage(addr string, first, pageSize int) *types.HistoryContai _, exists := a.project.HistoryMap.Load(address) if !exists { - if err := a.Thing(address, base.Max(pageSize, 1)); err != nil { - messages.Send(a.ctx, messages.Error, messages.NewErrorMsg(err, address)) - return &types.HistoryContainer{} - } - a.loadProject(nil, nil) - } - - if first == -1 { return &types.HistoryContainer{} } @@ -105,12 +92,12 @@ func (a *App) txCount(address base.Address) int { } } -var historyLock atomic.Uint32 +// var historyLock atomic.Uint32 func (a *App) loadHistory(address base.Address, wg *sync.WaitGroup, errorChan chan error) error { - // if wg != nil { - // defer wg.Done() - // } + if wg != nil { + defer wg.Done() + } if address.IsZero() { return nil @@ -121,16 +108,15 @@ func (a *App) loadHistory(address base.Address, wg *sync.WaitGroup, errorChan ch // } // defer historyLock.CompareAndSwap(1, 0) - // history, exists := a.project.HistoryMap.Load(address) - // if exists { - // if !history.NeedsUpdate(a.nameChange()) { - // return nil - // } - // } + history, exists := a.project.HistoryMap.Load(address) + if exists { + if !history.NeedsUpdate(a.nameChange()) { + return nil + } + } logger.Info("Loading history for address: ", address.Hex()) - // _ = a.HistoryPage(address.Hex(), -1, 15) - if err := a.Thing(address, 15); err != nil { + if err := a.thing(address, 15); err != nil { messages.Send(a.ctx, messages.Error, messages.NewErrorMsg(err, address)) return err } @@ -139,7 +125,7 @@ func (a *App) loadHistory(address base.Address, wg *sync.WaitGroup, errorChan ch return nil } -func (a *App) Thing(address base.Address, freq int) error { +func (a *App) thing(address base.Address, freq int) error { rCtx := a.RegisterCtx(address) opts := sdk.ExportOptions{ Addrs: []string{address.Hex()}, @@ -174,7 +160,7 @@ func (a *App) Thing(address base.Address, freq int) error { }) messages.Send(a.ctx, messages.Progress, - messages.NewProgressMsg(int64(len(summary.Items)), int64(nItems), address), + messages.NewProgressMsg(len(summary.Items), nItems, address), ) } @@ -201,18 +187,18 @@ func (a *App) Thing(address base.Address, freq int) error { } a.meta = *meta - summary, _ := a.project.HistoryMap.Load(address) - sort.Slice(summary.Items, func(i, j int) bool { - if summary.Items[i].BlockNumber == summary.Items[j].BlockNumber { - return summary.Items[i].TransactionIndex > summary.Items[j].TransactionIndex + history, _ := a.project.HistoryMap.Load(address) + sort.Slice(history.Items, func(i, j int) bool { + if history.Items[i].BlockNumber == history.Items[j].BlockNumber { + return history.Items[i].TransactionIndex > history.Items[j].TransactionIndex } - return summary.Items[i].BlockNumber > summary.Items[j].BlockNumber + return history.Items[i].BlockNumber > history.Items[j].BlockNumber }) - summary.Summarize() - a.project.HistoryMap.Store(address, summary) + history.Summarize() + a.project.HistoryMap.Store(address, history) messages.Send(a.ctx, messages.Completed, - messages.NewProgressMsg(int64(a.txCount(address)), int64(a.txCount(address)), address), + messages.NewProgressMsg(a.txCount(address), a.txCount(address), address), ) return nil } diff --git a/app/data_monitors.go b/app/data_monitors.go index 93f55da5..7e63e76d 100644 --- a/app/data_monitors.go +++ b/app/data_monitors.go @@ -9,9 +9,13 @@ import ( "github.com/TrueBlocks/trueblocks-browse/pkg/messages" "github.com/TrueBlocks/trueblocks-browse/pkg/types" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/crud" + coreTypes "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" sdk "github.com/TrueBlocks/trueblocks-sdk/v3" ) +var monitorMutex sync.Mutex + // Find: NewViews func (a *App) MonitorPage(first, pageSize int) *types.MonitorContainer { first = base.Max(0, base.Min(first, len(a.monitors.Monitors)-1)) @@ -74,3 +78,54 @@ func (a *App) loadMonitors(wg *sync.WaitGroup, errorChan chan error) error { } return nil } + +func (a *App) ModifyMonitors(modData *ModifyData) error { + if !monitorLock.CompareAndSwap(0, 1) { + return nil + } + defer monitorLock.CompareAndSwap(1, 0) + + opFromString := func(op string) crud.Operation { + m := map[string]crud.Operation{ + "delete": crud.Delete, + "undelete": crud.Undelete, + "remove": crud.Remove, + } + return m[op] + } + + op := opFromString(modData.Operation) + opts := sdk.MonitorsOptions{ + Addrs: []string{modData.Address.Hex()}, + Delete: op == crud.Delete, + Undelete: op == crud.Undelete, + Remove: op == crud.Remove, + Globals: a.globals, + } + + if _, _, err := opts.Monitors(); err != nil { + messages.SendError(a.ctx, err) + return err + } + + newArray := []coreTypes.Monitor{} + for _, mon := range a.monitors.Monitors { + if mon.Address == modData.Address { + switch op { + case crud.Delete: + mon.Deleted = true + case crud.Undelete: + mon.Deleted = false + case crud.Remove: + continue + } + } + newArray = append(newArray, mon) + } + monitorMutex.Lock() + defer monitorMutex.Unlock() + + a.monitors.Monitors = newArray + a.monitors.Summarize() + return nil +} diff --git a/app/data_project.go b/app/data_project.go index fdce6bd2..eaa4b394 100644 --- a/app/data_project.go +++ b/app/data_project.go @@ -105,7 +105,7 @@ func (a *App) Reload(address base.Address) { Operation: "reload", Address: address, }) - a.HistoryPage(address.Hex(), 0, 15) + a.loadHistory(a.GetLastAddress(), nil, nil) a.Refresh() a.loadProject(nil, nil) } diff --git a/app/menu_file.go b/app/menu_file.go index fc20bae5..2e179a4b 100644 --- a/app/menu_file.go +++ b/app/menu_file.go @@ -32,24 +32,21 @@ func (a *App) FileOpen(cd *menu.CallbackData) { save := a.FreshenController.Sleep defer func() { a.FreshenController.Sleep = save }() a.FreshenController.Sleep = 1000 + a.SetSessionVal("file", file) + + a.CancelAllContexts() + a.project = types.NewProjectContainer(file, &types.HistoryMap{}, &sync.Map{}, &sync.Map{}) newProject := types.ProjectContainer{ Filename: file, } newProject.Load() a.session = newProject.Session + var wg sync.WaitGroup for _, history := range newProject.Items { - history.Balance = a.getBalance(history.Address) - if loaded, ok := a.project.HistoryMap.Load(history.Address); ok && loaded.NItems == loaded.NTotal { - history.Items = loaded.Items - newProject.HistoryMap.Store(history.Address, history) - messages.Send(a.ctx, - messages.Completed, - messages.NewProgressMsg(int64(a.txCount(history.Address)), int64(a.txCount(history.Address)), history.Address), - ) - } else { - go a.HistoryPage(history.Address.Hex(), -1, 15) - } + wg.Add(1) + go a.loadHistory(history.Address, &wg, nil) } + wg.Wait() messages.Send(a.ctx, messages.Navigate, messages.NewNavigateMsg("/")) messages.Send(a.ctx, messages.Document, messages.NewDocumentMsg(a.project.Filename, "Opened")) diff --git a/app/proc_ctxs.go b/app/proc_ctxs.go index 96de1b86..a3034a64 100644 --- a/app/proc_ctxs.go +++ b/app/proc_ctxs.go @@ -24,7 +24,7 @@ func (a *App) CancelContext(address base.Address) { defer ctxMutex.Unlock() if ctxArrays, ok := a.renderCtxs[address]; ok { for _, ctx := range ctxArrays { - messages.Send(a.ctx, messages.Cancelled, messages.NewProgressMsg(-1, -1, address)) + messages.Send(a.ctx, messages.Cancelled, messages.NewCancelMsg(address)) ctx.Cancel() } delete(a.renderCtxs, address) diff --git a/app/session.go b/app/session.go index 79e480e7..cd8ab5ce 100644 --- a/app/session.go +++ b/app/session.go @@ -11,6 +11,8 @@ import ( func (a *App) GetSessionVal(which string) string { switch which { + case "file": + return a.GetSession().LastFile case "route": if !a.isConfigured() { return "/wizard" @@ -26,6 +28,8 @@ func (a *App) GetSessionVal(which string) string { func (a *App) SetSessionVal(which, value string) { switch which { + case "file": + a.GetSession().LastFile = value case "route": parts := strings.Split(value, "/") if len(parts) > 2 { diff --git a/app/util_explore.go b/app/util_explore.go index 97666d68..894f3393 100644 --- a/app/util_explore.go +++ b/app/util_explore.go @@ -2,12 +2,18 @@ package app import ( "fmt" + "os" "github.com/TrueBlocks/trueblocks-browse/pkg/messages" sdk "github.com/TrueBlocks/trueblocks-sdk/v3" ) func (a *App) GetExploreUrl(term string, google, dalle bool) string { + if len(term) != 42 { + google = false + dalle = false + } + opts := sdk.ExploreOptions{ Terms: []string{term}, Google: google, @@ -16,6 +22,8 @@ func (a *App) GetExploreUrl(term string, google, dalle bool) string { Globals: a.globals, } + // TODO: Expose this to the user and/or put it in trueBlocks.toml + os.Setenv("TB_DALLE_SERIES", "five-tone-postal-protozoa") if result, meta, err := opts.Explore(); err != nil { messages.Send(a.ctx, messages.Error, messages.NewErrorMsg(err)) return "" diff --git a/frontend/package.json b/frontend/package.json index c6fd6b44..f52478ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "serve": "vite preview" }, "dependencies": { + "@mantine/notifications": "^7.13.2", "@tabler/icons-react": "3.11.0", "@tanstack/react-table": "^8.19.2", "dirname": "^0.1.0", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 0af5c9ae..b0afb364 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -391913b6e357db82588bd20c70d3bd03 \ No newline at end of file +8e5135546ea2780cb1d7b9981e7e4272 \ No newline at end of file diff --git a/frontend/src/components/buttons/BaseButton.module.css b/frontend/src/components/buttons/BaseButton.module.css new file mode 100644 index 00000000..ee18fece --- /dev/null +++ b/frontend/src/components/buttons/BaseButton.module.css @@ -0,0 +1,20 @@ +.baseButton { + background-color: red !important; + width: 8rem !important; +} + +.actionButton { + background-color: green !important; +} + +.closeButton { + background-color: transparent !important; + color: white !important; + border: none !important; + font-size: 16px !important; + cursor: pointer !important; + padding: 0 !important; + position: absolute !important; + top: 10px !important; + right: 10px !important; +} diff --git a/frontend/src/components/buttons/BaseButton.tsx b/frontend/src/components/buttons/BaseButton.tsx new file mode 100644 index 00000000..a3f57401 --- /dev/null +++ b/frontend/src/components/buttons/BaseButton.tsx @@ -0,0 +1,50 @@ +import { Button, ButtonProps as MantineButtonProps, ActionIcon } from "@mantine/core"; +import { base } from "@gocode/models"; +import classes from "./BaseButton.module.css"; + +export type ButtonMouseEvent = React.MouseEvent; + +export interface ButtonProps extends MantineButtonProps { + tip?: string; + value?: string | base.Address; + onClick?: (e: ButtonMouseEvent) => void; + onClose?: (e: ButtonMouseEvent) => void; +} + +// BaseButton is a generic button that can have a loading spinner, a tip, and a +// left section. It can also be used as an action icon if it has no children. +export const BaseButton = ({ + loading = false, + tip = "", + onClick = () => {}, + onClose = () => {}, + children, + ...props +}: ButtonProps) => { + const { leftSection } = props; + const baseProps: MantineButtonProps = { ...props }; + const hasChilren: boolean = children !== undefined; + + const handleClick = (e: ButtonMouseEvent) => { + if (onClick) { + onClick(e); + } + if (onClose) { + onClose(e); + } + }; + + if (!hasChilren) { + return ( + + {leftSection} + + ); + } + + return ( + + ); +}; diff --git a/frontend/src/components/buttons/ButtonProps.tsx b/frontend/src/components/buttons/ButtonProps.tsx deleted file mode 100644 index 0719f50b..00000000 --- a/frontend/src/components/buttons/ButtonProps.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { base } from "@gocode/models"; - -export type ButtonProps = { - value: string | base.Address; - size: "xs" | "sm" | "md" | "lg" | "xl"; - noText?: boolean; - onClick?: () => void; -}; diff --git a/frontend/src/components/buttons/CloseButton.tsx b/frontend/src/components/buttons/CloseButton.tsx new file mode 100644 index 00000000..d9fe196a --- /dev/null +++ b/frontend/src/components/buttons/CloseButton.tsx @@ -0,0 +1,11 @@ +import { BaseButton, ButtonProps } from "@components"; +import classes from "./BaseButton.module.css"; + +// CloseButton is used currently only for the Help window to close it if it's open. +export const CloseButton = ({ onClose }: ButtonProps) => { + return ( + + X + + ); +}; diff --git a/frontend/src/components/buttons/CopyButton.tsx b/frontend/src/components/buttons/CopyButton.tsx index efc32751..969b473c 100644 --- a/frontend/src/components/buttons/CopyButton.tsx +++ b/frontend/src/components/buttons/CopyButton.tsx @@ -1,12 +1,25 @@ -import { ActionIcon } from "@mantine/core"; -import { IconCopy } from "@tabler/icons-react"; - -// TODO: Should use ButtonProps -export const CopyButton = ({ onClick }: { onClick: () => void }) => { - const size = "sm"; - return ( - - - - ); +import { showNotification } from "@mantine/notifications"; +import { IconCopy, IconCheck } from "@tabler/icons-react"; +import { BaseButton, ButtonProps, ButtonMouseEvent } from "@components"; + +// CopyButton copies the address of the row to the clipboard. +export const CopyButton = ({ value, onClick, ...props }: ButtonProps) => { + const handleClick = (e: ButtonMouseEvent) => { + if (onClick) { + onClick(e); + } + + const shortened = (val: string) => (val.length > 10 ? `${val.slice(0, 6)}...${val.slice(-4)}` : val); + showNotification({ + title: "Copied", + message: `Copied ${shortened(value as string)} to clipboard`, + icon: , + color: "green", + autoClose: 2000, + }); + + console.log("Copied to clipboard"); + }; + + return } />; }; diff --git a/frontend/src/components/buttons/DalleButton.tsx b/frontend/src/components/buttons/DalleButton.tsx new file mode 100644 index 00000000..86878b4e --- /dev/null +++ b/frontend/src/components/buttons/DalleButton.tsx @@ -0,0 +1,6 @@ +import { ExploreButton, ButtonProps } from "@components"; + +// DalleButton opens up the DalleDress explorer website. +export const DalleButton = ({ value, ...props }: ButtonProps) => { + return ; +}; diff --git a/frontend/src/components/buttons/DeleteButton.tsx b/frontend/src/components/buttons/DeleteButton.tsx index 04263f7b..d1a8d0df 100644 --- a/frontend/src/components/buttons/DeleteButton.tsx +++ b/frontend/src/components/buttons/DeleteButton.tsx @@ -1,80 +1,32 @@ -import { useState, useEffect } from "react"; -import { ActionIcon, Group } from "@mantine/core"; +import { Group } from "@mantine/core"; import { IconTrash, IconTrashX, IconArrowBackUp } from "@tabler/icons-react"; -import { ButtonProps } from "@components"; -import { base, app } from "@gocode/models"; -import { useViewState } from "@state"; +import { BaseButton, ButtonProps } from "@components"; +import { base } from "@gocode/models"; +import { useModifyFn } from "@hooks"; -export interface DeleteButtonProps extends Omit, "size"> { +export interface DeleteButtonProps extends ButtonProps { isDeleted: boolean; } -export const DeleteButton = ({ value, isDeleted }: DeleteButtonProps) => { - const [address, setAddress] = useState(value as unknown as base.Address); - const { fetchFn, modifyFn, pager } = useViewState(); +// DeleteButton deletes, undeletes, or removes the pointed to items. This component +// always operates on an address, but it picks up its modifyFn and fetchFn from +// the useModifyFn hook. This allows different views (MonitorsView, AbisView, etc.) +// to use delete buttons without having to know the details of how to delete items. +export const DeleteButton = ({ value, isDeleted, ...props }: DeleteButtonProps) => { + const { deleteItem, undeleteItem, removeItem } = useModifyFn(value as base.Address); - useEffect(() => { - setAddress(value as unknown as base.Address); - }, [value]); - - const handleDelete = (e: React.MouseEvent) => { - e.preventDefault(); - const op = "delete"; - const modData = app.ModifyData.createFrom({ - operation: op, - address: address, - value: "", - }); - modifyFn(modData).then(() => { - fetchFn(pager.getOffset(), pager.perPage); - }); - }; - - const handleUndelete = (e: React.MouseEvent) => { - e.preventDefault(); - const op = "undelete"; - const modData = app.ModifyData.createFrom({ - operation: op, - address: address, - value: "", - }); - modifyFn(modData).then(() => { - fetchFn(pager.getOffset(), pager.perPage); - }); - }; - - const handleRemove = (e: React.MouseEvent) => { - e.preventDefault(); - const op = "remove"; - const modData = app.ModifyData.createFrom({ - operation: op, - address: address, - value: "", - }); - modifyFn(modData).then(() => { - fetchFn(pager.getOffset(), pager.perPage); - }); - }; - - const size = "sm"; if (isDeleted) { return ( - - - - - - + } /> + } /> ); } return ( - - - + } /> ); }; diff --git a/frontend/src/components/buttons/ExploreButton.tsx b/frontend/src/components/buttons/ExploreButton.tsx index cd062bc7..c3f43b6c 100644 --- a/frontend/src/components/buttons/ExploreButton.tsx +++ b/frontend/src/components/buttons/ExploreButton.tsx @@ -1,69 +1,45 @@ import { useState, useEffect } from "react"; -import { ActionIcon, Button } from "@mantine/core"; -import { IconExternalLink, IconBrandGoogle, IconBrandOpenai } from "@tabler/icons-react"; -import { ButtonProps } from "@components"; +import { IconExternalLink, IconBrandOpenai, IconBrandGoogle } from "@tabler/icons-react"; +import { ButtonProps, BaseButton } from "@components"; import { GetExploreUrl } from "@gocode/app/App"; import { BrowserOpenURL } from "@runtime"; -export enum UrlType { - Google = "google", - Dalle = "dalle", - Regular = "regular", +export interface ExploreButtonProps extends ButtonProps { + type?: "explore" | "google" | "dalle"; } -interface ExploreButtonProps extends Omit { - urlType?: UrlType; // Add the UrlType prop -} - -export const ExploreButton = ({ value, noText, urlType = UrlType.Regular, onClick }: ExploreButtonProps) => { +// ExploreButton opens a browser window to an explorer. It's also the basis +// for the DalleButton and GoogleButton components. +export const ExploreButton = ({ type = "explore", value, ...props }: ExploreButtonProps) => { const [url, setUrl] = useState(""); const [icon, setIcon] = useState(); - const [text, setText] = useState("Explore"); + const [tip, setTip] = useState("Explore"); useEffect(() => { - const isGoogle = urlType === UrlType.Google; - const isDalle = urlType === UrlType.Dalle; - if (isGoogle) { - setIcon(); - setText("Google"); - } else if (isDalle) { - setIcon(); - setText("Dalle"); - } - GetExploreUrl(value as string, isGoogle, isDalle).then((url) => { - url = url.replace("/simple/", "/five-tone-postal-protozoa/"); - url = url.replace("http://", "https://"); + const google = type === "google"; + const dalle = type === "dalle"; + GetExploreUrl(value as string, google, dalle).then((url) => { setUrl(url); }); - }, [value, urlType]); + switch (type) { + case "explore": + setTip("Explore"); + setIcon(); + break; + case "google": + setTip("Google"); + setIcon(); + break; + case "dalle": + setTip("Dalle"); + setIcon(); + break; + } + }, [value, type]); const handleClick = () => { BrowserOpenURL(url); - if (onClick) { - onClick(); - } }; - const size = "sm"; - if (noText) { - return ( - - {icon} - - ); - } - - return ( - - ); -}; - -export const DalleButton = ({ value, noText, onClick }: ExploreButtonProps) => { - return ; -}; - -export const GoogleButton = ({ value, noText, onClick }: ExploreButtonProps) => { - return ; + return ; }; diff --git a/frontend/src/components/buttons/ExportButton.tsx b/frontend/src/components/buttons/ExportButton.tsx index 6c5249ec..638f9db4 100644 --- a/frontend/src/components/buttons/ExportButton.tsx +++ b/frontend/src/components/buttons/ExportButton.tsx @@ -1,29 +1,15 @@ -import { Button, ActionIcon } from "@mantine/core"; import { IconFileExport } from "@tabler/icons-react"; -import { ButtonProps } from "@components"; +import { BaseButton, ButtonProps } from "@components"; import { ExportToCsv } from "@gocode/app/App"; import { base } from "@gocode/models"; -export const ExportButton = ({ value, noText, onClick }: Omit) => { +// ExportButton exports an address's history to a .csv file. +export const ExportButton = ({ value, ...props }: ButtonProps) => { + const icon = ; + const handleClick = () => { ExportToCsv(value as base.Address); - if (onClick) { - onClick(); - } }; - const size = "sm"; - if (noText) { - return ( - - - - ); - } - - return ( - - ); + return ; }; diff --git a/frontend/src/components/buttons/GoogleButton.tsx b/frontend/src/components/buttons/GoogleButton.tsx new file mode 100644 index 00000000..bc71aae1 --- /dev/null +++ b/frontend/src/components/buttons/GoogleButton.tsx @@ -0,0 +1,8 @@ +import { ExploreButton, ButtonProps } from "@components"; + +// GoogleButton opens a browser window to search google for a given address, but +// it only shows results that are not blockchain explorers. Useful to study +// an address unrelated to its transactional history. +export const GoogleButton = ({ value, ...props }: ButtonProps) => { + return ; +}; diff --git a/frontend/src/components/buttons/ViewButton.tsx b/frontend/src/components/buttons/ViewButton.tsx index 32f89f95..4186c587 100644 --- a/frontend/src/components/buttons/ViewButton.tsx +++ b/frontend/src/components/buttons/ViewButton.tsx @@ -1,33 +1,19 @@ -import { Button, ActionIcon } from "@mantine/core"; import { IconLink } from "@tabler/icons-react"; -import { ButtonProps } from "@components"; +import { BaseButton, ButtonProps } from "@components"; import { SetSessionVal } from "@gocode/app/App"; import { messages } from "@gocode/models"; import { EventsEmit } from "@runtime"; -export const ViewButton = ({ value, noText, onClick }: Omit) => { +// ViewButton opens the history page for a given address. +export const ViewButton = ({ value, ...props }: ButtonProps) => { + const icon = ; + const handleClick = () => { SetSessionVal("route", `/history/${value}`); EventsEmit(messages.Message.NAVIGATE, { route: `/history/${value}`, }); - if (onClick) { - onClick(); - } }; - const size = "sm"; - if (noText) { - return ( - - - - ); - } - - return ( - - ); + return ; }; diff --git a/frontend/src/components/buttons/index.tsx b/frontend/src/components/buttons/index.tsx index 8bb4a184..87b99e94 100644 --- a/frontend/src/components/buttons/index.tsx +++ b/frontend/src/components/buttons/index.tsx @@ -1,6 +1,9 @@ -export * from "./ButtonProps"; +export * from "./BaseButton"; +export * from "./CloseButton"; +export * from "./CopyButton"; +export * from "./DalleButton"; +export * from "./DeleteButton"; export * from "./ExploreButton"; -export * from "./ViewButton"; export * from "./ExportButton"; -export * from "./DeleteButton"; -export * from "./CopyButton"; +export * from "./GoogleButton"; +export * from "./ViewButton"; diff --git a/frontend/src/components/formatters/AddressFormatter.tsx b/frontend/src/components/formatters/AddressFormatter.tsx index 623c138a..009d95c5 100644 --- a/frontend/src/components/formatters/AddressFormatter.tsx +++ b/frontend/src/components/formatters/AddressFormatter.tsx @@ -69,18 +69,20 @@ export const AddressFormatter = ({ value, value2, className, mode = EdMode.All } const line1Type: knownType = "address-line1"; const line2Type: knownType = "address-line2"; - const copyAddress = useCallback(() => { + const onCopy = useCallback(() => { ClipboardSetText(givenAddress).then(() => { setPopupOpen(false); }); }, [givenAddress]); + const onClose = useCallback(() => setPopupOpen(false), []); + const editor = isPopupOpen ? ( setPopupOpen(false)} + onCopy={onCopy} + onClose={onClose} onSubmit={(newValue: string) => { setPopupOpen(false); const modData = app.ModifyData.createFrom({ diff --git a/frontend/src/components/formatters/AddressPopup.tsx b/frontend/src/components/formatters/AddressPopup.tsx index 787fa185..7b3ead03 100644 --- a/frontend/src/components/formatters/AddressPopup.tsx +++ b/frontend/src/components/formatters/AddressPopup.tsx @@ -20,10 +20,6 @@ export const AddressPopup = forwardRef( [inputValue, onSubmit, onClose] ); - const handleButtonClick = useCallback(() => { - onClose(); - }, [onClose]); - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (ref && "current" in ref && ref.current && !ref.current.contains(event.target as Node)) { @@ -49,14 +45,14 @@ export const AddressPopup = forwardRef( autoFocus /> - - - - + + + + - + diff --git a/frontend/src/components/formatters/AppearanceFormatter.tsx b/frontend/src/components/formatters/AppearanceFormatter.tsx index 75db5f3a..72c264bc 100644 --- a/frontend/src/components/formatters/AppearanceFormatter.tsx +++ b/frontend/src/components/formatters/AppearanceFormatter.tsx @@ -5,20 +5,15 @@ import { ClipboardSetText } from "@runtime"; export const AppearanceFormatter = ({ value, value2, className }: Omit, "size">) => { const [isPopupOpen, setPopupOpen] = useState(false); - const copyHash = useCallback(() => { + const onCopy = useCallback(() => { ClipboardSetText(String(value2)).then(() => { setPopupOpen(false); }); }, [value2]); - const appPopup = ( - setPopupOpen(false)} - onClose={() => setPopupOpen(false)} - onCopy={copyHash} - /> - ); + const onClose = useCallback(() => setPopupOpen(false), []); + + const appPopup = ; const editor = isPopupOpen ? appPopup : null; return ( diff --git a/frontend/src/components/formatters/AppearancePopup.tsx b/frontend/src/components/formatters/AppearancePopup.tsx index 5a5fb8c2..62d4aace 100644 --- a/frontend/src/components/formatters/AppearancePopup.tsx +++ b/frontend/src/components/formatters/AppearancePopup.tsx @@ -1,4 +1,4 @@ -import { forwardRef, useCallback } from "react"; +import { forwardRef } from "react"; import { Group } from "@mantine/core"; import { ExploreButton, CopyButton, PopupProps } from "@components"; @@ -6,15 +6,11 @@ export interface AppearancePopupProps extends PopupProps { hash: string; } -export const AppearancePopup = forwardRef(({ hash, onClose, onCopy }) => { - const handleButtonClick = useCallback(() => { - onClose(); - }, [onClose]); - +export const AppearancePopup = forwardRef(({ hash, onCopy, onClose }, ref) => { return ( - - + + ); }); diff --git a/frontend/src/components/formatters/DalleImage.tsx b/frontend/src/components/formatters/DalleImage.tsx index 57a46bd7..abb88200 100644 --- a/frontend/src/components/formatters/DalleImage.tsx +++ b/frontend/src/components/formatters/DalleImage.tsx @@ -1,22 +1,32 @@ import { useState, useEffect } from "react"; import { Image } from "@mantine/core"; import { GetExploreUrl } from "@gocode/app/App"; +import { BrowserOpenURL } from "@runtime"; import { FormatterProps } from "./Formatter"; export interface DalleImageProps extends Omit { height?: number; } -export function DalleImage({ value, height = 200 }: DalleImageProps) { +export function DalleImage({ value, height = 125 }: DalleImageProps) { const [url, setUrl] = useState(value); useEffect(() => { GetExploreUrl(value as string, false, true).then((url) => { - url = url.replace("/simple/", "/five-tone-postal-protozoa/"); - url = url.replace("http://", "https://"); setUrl(url); }); }, [value]); - return {url}; + // Handler to open the URL using Wails' browser + const handleImageClick = () => { + if (url) { + BrowserOpenURL(url); + } + }; + + const s = + height === 125 + ? { cursor: "pointer", marginTop: -10, marginBottom: -10 } + : { cursor: "pointer", marginTop: 0, marginBottom: 0 }; + return {url}; } diff --git a/frontend/src/components/help/Help.module.css b/frontend/src/components/help/Help.module.css index dd129294..0f36f80c 100644 --- a/frontend/src/components/help/Help.module.css +++ b/frontend/src/components/help/Help.module.css @@ -8,14 +8,3 @@ background-color: #333; color: white; } - -.closeButton { - position: absolute; - top: 10px; - right: 10px; - background-color: transparent; - color: white; - border: none; - font-size: 16px; - cursor: pointer; -} diff --git a/frontend/src/components/help/Help.tsx b/frontend/src/components/help/Help.tsx index e5bd002c..675be05b 100644 --- a/frontend/src/components/help/Help.tsx +++ b/frontend/src/components/help/Help.tsx @@ -1,8 +1,9 @@ -import { useState, useEffect, CSSProperties } from "react"; -import { Title, Button } from "@mantine/core"; // Assuming you're using Mantine's Button component +import { useState, useEffect, useCallback } from "react"; +import { Title } from "@mantine/core"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { useLocation } from "wouter"; +import { CloseButton } from "@components"; import { messages } from "@gocode/models"; import { useViewName } from "@hooks"; import { EventsEmit } from "@runtime"; @@ -20,10 +21,9 @@ export function Help(): JSX.Element { const [error, setError] = useState(false); const viewName = useViewName(); - // Close button function - const onClose = () => { + const onClose = useCallback(() => { EventsEmit(messages.Message.TOGGLEHELP, {}); - }; + }, []); useEffect(() => { const baseRoute = location.split("/")[1]; @@ -50,9 +50,7 @@ export function Help(): JSX.Element { return (
- {/* Close button at the top-right corner */} - - + {viewName} @@ -60,27 +58,3 @@ export function Help(): JSX.Element {
); } - -interface CloseButtonProps { - onClose: () => void; - style?: CSSProperties; -} - -export function CloseButton({ onClose, style }: CloseButtonProps): JSX.Element { - return ( - - ); -} diff --git a/frontend/src/components/tables/EditableTable.tsx b/frontend/src/components/tables/EditableTable.tsx new file mode 100644 index 00000000..a08064ba --- /dev/null +++ b/frontend/src/components/tables/EditableTable.tsx @@ -0,0 +1,142 @@ +import { useState } from "react"; +import { useReactTable, getCoreRowModel, ColumnDef, Table } from "@tanstack/react-table"; + +type EditableTableProps = { + data: T[]; + columns: ColumnDef[]; +}; + +export const EditableTable = ({ data: initialData, columns }: EditableTableProps) => { + const [data, setData] = useState(initialData); + const [editingRowId, setEditingRowId] = useState(null); + const [editingData, setEditingData] = useState>({}); + + const handleInputChange = (e: React.ChangeEvent, field: keyof T) => { + setEditingData({ + ...editingData, + [field]: e.target.value, + }); + }; + + const toggleEdit = (rowId: number) => { + if (editingRowId === rowId) { + setData((prev) => prev.map((row, index) => (index === rowId ? { ...row, ...editingData } : row))); + setEditingRowId(null); + setEditingData({}); + } else { + setEditingRowId(rowId); + const rowData = data[rowId]; + setEditingData(rowData ?? {}); + } + }; + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( + + ); +}; + +const MyTable = ({ + table, + toggleEdit, + handleInputChange, + editingRowId, + editingData, +}: { + table: Table; + toggleEdit: (id: number) => void; + handleInputChange: (e: React.ChangeEvent, field: keyof T) => void; + editingRowId: number | null; + editingData: Partial; +}) => { + return ( + + + +
+ ); +}; + +const MyTableHeader = ({ table }: { table: Table }) => { + return ( + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : typeof header.column.columnDef.header === "function" + ? header.column.columnDef.header(header.getContext()) + : header.column.columnDef.header} + + ))} + + ))} + + ); +}; + +const MyTableBody = ({ + table, + toggleEdit, + handleInputChange, + editingRowId, + editingData, +}: { + table: Table; + toggleEdit: (id: number) => void; + handleInputChange: (e: React.ChangeEvent, field: keyof T) => void; + editingRowId: number | null; + editingData: Partial; +}) => { + return ( + + {table.getRowModel().rows.map((row, rowIndex) => ( + + {row.getVisibleCells().map((cell) => ( + + {editingRowId === rowIndex ? ( + handleInputChange(e, cell.column.id as keyof T)} + /> + ) : typeof cell.column.columnDef.cell === "function" ? ( + cell.column.columnDef.cell(cell.getContext()) + ) : ( + cell.getValue() + )} + + ))} + + + + + ))} + + ); +}; diff --git a/frontend/src/components/tables/index.tsx b/frontend/src/components/tables/index.tsx index 0040b6f5..167374fc 100644 --- a/frontend/src/components/tables/index.tsx +++ b/frontend/src/components/tables/index.tsx @@ -2,3 +2,4 @@ export * from "./FormTable"; export * from "./CustomMeta"; export * from "./DataTable"; export * from "./Paginator"; +export * from "./EditableTable"; diff --git a/frontend/src/hooks/index.tsx b/frontend/src/hooks/index.tsx index 8cca8f1c..63a36d82 100644 --- a/frontend/src/hooks/index.tsx +++ b/frontend/src/hooks/index.tsx @@ -1,3 +1,4 @@ export * from "./useKeyboardPaging"; export * from "./useViewName"; export * from "./useEnvironment"; +export * from "./useModifyFn"; diff --git a/frontend/src/hooks/useModifyFn.tsx b/frontend/src/hooks/useModifyFn.tsx new file mode 100644 index 00000000..76d04a13 --- /dev/null +++ b/frontend/src/hooks/useModifyFn.tsx @@ -0,0 +1,23 @@ +import { app, base } from "../../wailsjs/go/models"; +import { useViewState } from "../state"; + +export const useModifyFn = (address: base.Address) => { + const { fetchFn, modifyFn, pager } = useViewState(); + + const deleteItem = () => + modifyFn(app.ModifyData.createFrom({ operation: "delete", address })).then(() => + fetchFn(pager.getOffset(), pager.perPage) + ); + + const undeleteItem = () => + modifyFn(app.ModifyData.createFrom({ operation: "undelete", address })).then(() => + fetchFn(pager.getOffset(), pager.perPage) + ); + + const removeItem = () => + modifyFn(app.ModifyData.createFrom({ operation: "remove", address })).then(() => + fetchFn(pager.getOffset(), pager.perPage) + ); + + return { deleteItem, undeleteItem, removeItem }; +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 95679309..ea6bb3ca 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,15 +1,18 @@ import { StrictMode } from "react"; import { MantineProvider } from "@mantine/core"; +import { Notifications } from "@mantine/notifications"; import { createRoot } from "react-dom/client"; import "./style.css"; import App from "./App"; import "@mantine/core/styles.css"; +import "@mantine/notifications/styles.css"; const container = document.getElementById("root"); const root = createRoot(container!); root.render( + diff --git a/frontend/src/views/Abis/AbisTable.tsx b/frontend/src/views/Abis/AbisTable.tsx index a65e481d..1c038be3 100644 --- a/frontend/src/views/Abis/AbisTable.tsx +++ b/frontend/src/views/Abis/AbisTable.tsx @@ -58,8 +58,7 @@ export const tableColumns: CustomColumnDef[] = [ header: () => "Crud Buttons", cell: (info) => { const { address, isKnown } = info.row.original; - const addr = address as unknown as string; - return isKnown ? <> : ; + return isKnown ? <> : ; }, meta: { className: "small center cell" }, }), diff --git a/frontend/src/views/Abis/AbisView.tsx b/frontend/src/views/Abis/AbisView.tsx index cc2deb16..6077c320 100644 --- a/frontend/src/views/Abis/AbisView.tsx +++ b/frontend/src/views/Abis/AbisView.tsx @@ -1,5 +1,5 @@ -import { getCoreRowModel, useReactTable, Table } from "@tanstack/react-table"; -import { View, FormTable, DataTable, GroupDefinition } from "@components"; +import { getCoreRowModel, useReactTable, Table, ColumnDef } from "@tanstack/react-table"; +import { View, FormTable, DataTable, GroupDefinition, EditableTable } from "@components"; import { ModifyAbi } from "@gocode/app/App"; import { types } from "@gocode/models"; import { useAppState, ViewStateProvider } from "@state"; diff --git a/frontend/src/views/History/HistoryView.tsx b/frontend/src/views/History/HistoryView.tsx index f422c66c..b542b330 100644 --- a/frontend/src/views/History/HistoryView.tsx +++ b/frontend/src/views/History/HistoryView.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { Stack } from "@mantine/core"; +import { Group, Stack } from "@mantine/core"; import { getCoreRowModel, useReactTable, Table } from "@tanstack/react-table"; import { useParams } from "wouter"; import { @@ -53,16 +53,22 @@ function CreateHistoryForm(table: Table): GroupDefinition): GroupDefinition - - - - + + Explore + Dalle + + + Google + Export + ), }, diff --git a/frontend/src/views/Monitors/MonitorsTable.tsx b/frontend/src/views/Monitors/MonitorsTable.tsx index 358c23b8..e9997da3 100644 --- a/frontend/src/views/Monitors/MonitorsTable.tsx +++ b/frontend/src/views/Monitors/MonitorsTable.tsx @@ -1,5 +1,5 @@ import { createColumnHelper } from "@tanstack/react-table"; -import { CustomColumnDef, Formatter } from "@components"; +import { CustomColumnDef, Formatter, DeleteButton } from "@components"; import { types } from "@gocode/models"; const columnHelper = createColumnHelper(); @@ -42,4 +42,12 @@ export const tableColumns: CustomColumnDef[] = [ cell: (info) => , meta: { className: "medium cell" }, }), + columnHelper.accessor("deleted", { + header: () => "Crud Buttons", + cell: (info) => { + const { deleted, address } = info.row.original; + return ; + }, + meta: { className: "small center cell" }, + }), ]; diff --git a/frontend/src/views/Monitors/MonitorsView.tsx b/frontend/src/views/Monitors/MonitorsView.tsx index 5ac51ce8..1583b537 100644 --- a/frontend/src/views/Monitors/MonitorsView.tsx +++ b/frontend/src/views/Monitors/MonitorsView.tsx @@ -1,6 +1,6 @@ import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { View, FormTable, DataTable, GroupDefinition } from "@components"; -import { SetSessionVal, ModifyNoop } from "@gocode/app/App"; +import { SetSessionVal, ModifyMonitors } from "@gocode/app/App"; import { types, messages } from "@gocode/models"; import { Page } from "@hooks"; import { EventsEmit } from "@runtime"; @@ -31,7 +31,7 @@ export function MonitorsView() { nItems={monitors.nItems} fetchFn={fetchMonitors} onEnter={handleEnter} - modifyFn={ModifyNoop} + modifyFn={ModifyMonitors} > diff --git a/frontend/src/views/Names/NamesTable.tsx b/frontend/src/views/Names/NamesTable.tsx index 1d15a17b..65d77248 100644 --- a/frontend/src/views/Names/NamesTable.tsx +++ b/frontend/src/views/Names/NamesTable.tsx @@ -52,9 +52,7 @@ export const tableColumns: CustomColumnDef[] = [ header: () => "Crud Buttons", cell: (info) => { const { deleted, isCustom, address } = info.row.original; - const addr = address as unknown as string; - const del = deleted ? true : false; - return isCustom ? : <>; + return isCustom ? : <>; }, meta: { className: "small center cell" }, }), diff --git a/frontend/src/views/Project/ProjectTable.tsx b/frontend/src/views/Project/ProjectTable.tsx index c9663373..d58b7222 100644 --- a/frontend/src/views/Project/ProjectTable.tsx +++ b/frontend/src/views/Project/ProjectTable.tsx @@ -9,17 +9,18 @@ import { DeleteButton, DalleButton, GoogleButton, + CopyButton, } from "@components"; -import { types } from "@gocode/models"; +import { base, types } from "@gocode/models"; const columnHelper = createColumnHelper(); const baseColumns: CustomColumnDef[] = [ - // columnHelper.accessor("address", { - // header: () => "Dalle", - // cell: (info) => , - // meta: { className: "medium cell" }, - // }), + columnHelper.accessor("address", { + header: () => "Dalle", + cell: (info) => , + meta: { className: "medium cell" }, + }), columnHelper.accessor("address", { header: () => "Address", cell: (info) => , @@ -40,21 +41,29 @@ const baseColumns: CustomColumnDef[] = [ }), ]; +const defButtons = (address: base.Address) => { + return ( + + + + + + + + + ); +}; + export const withDelete: CustomColumnDef[] = [ ...baseColumns, columnHelper.accessor("address", { header: () => " ", cell: (info) => { const { address } = info.row.original; - const addr = address as unknown as string; return ( - - - - - - + {defButtons(address)} + ); }, @@ -67,15 +76,8 @@ export const withoutDelete: CustomColumnDef[] = [ columnHelper.accessor("address", { header: () => " ", cell: (info) => { - return ( - - - - - - - - ); + const { address } = info.row.original; + return {defButtons(address)}; }, meta: { className: "wide cell" }, }), diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index 089f85ef..fa99dd3f 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -73,6 +73,8 @@ export function ManifestPage(arg1:number,arg2:number):Promise; +export function ModifyMonitors(arg1:app.ModifyData):Promise; + export function ModifyName(arg1:app.ModifyData):Promise; export function ModifyNoop(arg1:app.ModifyData):Promise; @@ -111,8 +113,6 @@ export function SystemAbout(arg1:menu.CallbackData):Promise; export function SystemQuit(arg1:menu.CallbackData):Promise; -export function Thing(arg1:base.Address,arg2:number):Promise; - export function ToggleDaemon(arg1:string):Promise; export function ViewAbis(arg1:menu.CallbackData):Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index a38c38ad..3a622528 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -130,6 +130,10 @@ export function ModifyAbi(arg1) { return window['go']['app']['App']['ModifyAbi'](arg1); } +export function ModifyMonitors(arg1) { + return window['go']['app']['App']['ModifyMonitors'](arg1); +} + export function ModifyName(arg1) { return window['go']['app']['App']['ModifyName'](arg1); } @@ -206,10 +210,6 @@ export function SystemQuit(arg1) { return window['go']['app']['App']['SystemQuit'](arg1); } -export function Thing(arg1, arg2) { - return window['go']['app']['App']['Thing'](arg1, arg2); -} - export function ToggleDaemon(arg1) { return window['go']['app']['App']['ToggleDaemon'](arg1); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 6b5e07b9..25924cc5 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -106,6 +106,7 @@ export namespace config { } export class Session { chain: string; + lastFile: string; lastRoute: string; lastSub: {[key: string]: string}; lastHelp: boolean; @@ -120,6 +121,7 @@ export namespace config { constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.chain = source["chain"]; + this.lastFile = source["lastFile"]; this.lastRoute = source["lastRoute"]; this.lastSub = source["lastSub"]; this.lastHelp = source["lastHelp"]; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4f82cf61..90f24392 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -157,6 +157,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" + integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" @@ -581,6 +588,19 @@ resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.13.0.tgz#7d91e20841456ef0ec8dbd1952409439fe35ac37" integrity sha512-oQpwSi0gajH3UR1DFa9MQ+zeYy75xbc8Im9jIIepLbiJXtIcPK+yll1BMxNwPQLYU1pYI6ZgUazI2PoykVNmsg== +"@mantine/notifications@^7.13.2": + version "7.13.2" + resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.13.2.tgz#0be074470b0a91a6dc7a6834e67c301d7ebea903" + integrity sha512-14vFJtR0wjO8Won96UMLxIGmKetR0ocBxcghtuGh6+wnXt6r/ezfQKsdGkkNj6w91I+0Nl9jspcxEekE5q2tBQ== + dependencies: + "@mantine/store" "7.13.2" + react-transition-group "4.4.5" + +"@mantine/store@7.13.2": + version "7.13.2" + resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.13.2.tgz#aba23573bff42eda28716f422275929afde086c9" + integrity sha512-JcBGOqRynYiRWzw1rYdmViB/lfeYSec2EXVdSt4eJv+RPICsjjuqrIc3sNzfqJEGxcN4hGSlaeBriSh05K+vNQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1535,6 +1555,14 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + electron-to-chromium@^1.5.28: version "1.5.29" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz#aa592a3caa95d07cc26a66563accf99fa573a1ee" @@ -3461,7 +3489,7 @@ prompts@^2.4.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3566,6 +3594,16 @@ react-textarea-autosize@8.5.3: use-composed-ref "^1.3.0" use-latest "^1.2.1" +react-transition-group@4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" diff --git a/pkg/config/session.go b/pkg/config/session.go index fb469aa5..966cd1e3 100644 --- a/pkg/config/session.go +++ b/pkg/config/session.go @@ -28,6 +28,7 @@ type Window struct { // Session stores ephemeral things such as last window position, last view, and recent file type Session struct { Chain string `json:"chain"` + LastFile string `json:"lastFile"` LastRoute string `json:"lastRoute"` LastSub map[string]string `json:"lastSub"` LastHelp bool `json:"lastHelp"` @@ -39,6 +40,7 @@ type Session struct { var defaultSession = Session{ Chain: "mainnet", Daemons: Daemons{Freshen: true}, + LastFile: "Untitled.tbx", LastRoute: "/", LastSub: map[string]string{"/history": "0xf503017d7baf7fbc0fff7492b751025c6a78179b"}, LastHelp: true, @@ -67,6 +69,9 @@ func (s *Session) MustLoadSession() { if s.Chain == "" { s.Chain = "mainnet" } + if s.LastFile == "" { + s.LastFile = "Untitled.tbx" + } return } } diff --git a/pkg/messages/progress_message.go b/pkg/messages/progress_message.go index 955ab029..073ce1c9 100644 --- a/pkg/messages/progress_message.go +++ b/pkg/messages/progress_message.go @@ -4,11 +4,15 @@ import "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" type ProgressMsg struct { Address base.Address `json:"address"` - Have int64 `json:"have"` - Want int64 `json:"want"` + Have int `json:"have"` + Want int `json:"want"` } -func NewProgressMsg(have int64, want int64, addrs ...base.Address) *ProgressMsg { +func NewCancelMsg(addrs ...base.Address) *ProgressMsg { + return NewProgressMsg(-1, -1, addrs...) +} + +func NewProgressMsg(have int, want int, addrs ...base.Address) *ProgressMsg { address := base.ZeroAddr if len(addrs) > 0 { address = addrs[0]