delete block and close tab working

This commit is contained in:
sawka 2024-05-27 16:33:31 -07:00
parent e6d7a4e674
commit 3f45945cb4
8 changed files with 165 additions and 24 deletions
README.md
frontend/app
pkg
service/objectservice
wstore

View File

@ -34,7 +34,7 @@ Now to run the dev version of the app:
wails3 dev wails3 dev
``` ```
You should see a very poorly laid out app :) You should see the app!
Now to build a MacOS application: Now to build a MacOS application:

View File

@ -16,7 +16,7 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
const [dims, setDims] = React.useState({ width: 0, height: 0 }); const [dims, setDims] = React.useState({ width: 0, height: 0 });
function handleClose() { function handleClose() {
// TODO WOS.DeleteBlock(blockId);
} }
React.useEffect(() => { React.useEffect(() => {

View File

@ -178,6 +178,7 @@ function updateWaveObject(update: WaveObjUpdate) {
waveObjectValueCache.set(oref, wov); waveObjectValueCache.set(oref, wov);
} }
if (update.updatetype == "delete") { if (update.updatetype == "delete") {
console.log("WaveObj deleted", oref);
globalStore.set(wov.dataAtom, { value: null, loading: false }); globalStore.set(wov.dataAtom, { value: null, loading: false });
} else { } else {
if (!isValidWaveObj(update.obj)) { if (!isValidWaveObj(update.obj)) {
@ -188,6 +189,7 @@ function updateWaveObject(update: WaveObjUpdate) {
if (curValue.value != null && curValue.value.version >= update.obj.version) { if (curValue.value != null && curValue.value.version >= update.obj.version) {
return; return;
} }
console.log("WaveObj updated", oref);
globalStore.set(wov.dataAtom, { value: update.obj, loading: false }); globalStore.set(wov.dataAtom, { value: update.obj, loading: false });
} }
wov.holdTime = Date.now() + defaultHoldTime; wov.holdTime = Date.now() + defaultHoldTime;
@ -226,12 +228,14 @@ Events.On("waveobj:update", (event: any) => {
function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> { function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> {
const uiContext = globalStore.get(atoms.uiContext); const uiContext = globalStore.get(atoms.uiContext);
const startTs = Date.now();
let prtn = $Call.ByName( let prtn = $Call.ByName(
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService." + fnName, "github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService." + fnName,
uiContext, uiContext,
...args ...args
); );
prtn = prtn.then((val) => { prtn = prtn.then((val) => {
console.log("Call", fnName, Date.now() - startTs + "ms");
if (val.updates) { if (val.updates) {
updateWaveObjects(val.updates); updateWaveObjects(val.updates);
} }
@ -249,18 +253,26 @@ function getStaticObjectValue<T>(oref: string, getFn: jotai.Getter): T {
return atomVal.value; return atomVal.value;
} }
function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> { export function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> {
return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab); return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab);
} }
function SetActiveTab(tabId: string): Promise<void> { export function SetActiveTab(tabId: string): Promise<void> {
return wrapObjectServiceCall("SetActiveTab", tabId); return wrapObjectServiceCall("SetActiveTab", tabId);
} }
function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> { export function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> {
return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts); return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts);
} }
export function DeleteBlock(blockId: string): Promise<void> {
return wrapObjectServiceCall("DeleteBlock", blockId);
}
export function CloseTab(tabId: string): Promise<void> {
return wrapObjectServiceCall("CloseTab", tabId);
}
export { export {
makeORef, makeORef,
useWaveObject, useWaveObject,
@ -272,7 +284,4 @@ export {
updateWaveObjects, updateWaveObjects,
cleanWaveObjectCache, cleanWaveObjectCache,
getStaticObjectValue, getStaticObjectValue,
AddTabToWorkspace,
SetActiveTab,
CreateBlock,
}; };

View File

@ -8,7 +8,7 @@ import { atoms } from "@/store/global";
import * as WOS from "@/store/wos"; import * as WOS from "@/store/wos";
import "./tab.less"; import "./tab.less";
import { CenteredLoadingDiv } from "../element/quickelems"; import { CenteredDiv, CenteredLoadingDiv } from "../element/quickelems";
const TabContent = ({ tabId }: { tabId: string }) => { const TabContent = ({ tabId }: { tabId: string }) => {
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId)); const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
@ -16,7 +16,11 @@ const TabContent = ({ tabId }: { tabId: string }) => {
return <CenteredLoadingDiv />; return <CenteredLoadingDiv />;
} }
if (!tabData) { if (!tabData) {
return <div className="tabcontent">Tab not found</div>; return (
<div className="tabcontent">
<CenteredDiv>Tab Not Found</CenteredDiv>
</div>
);
} }
return ( return (
<div className="tabcontent"> <div className="tabcontent">

View File

@ -55,9 +55,24 @@
height: 100%; height: 100%;
border-right: 1px solid var(--border-color); border-right: 1px solid var(--border-color);
cursor: pointer; cursor: pointer;
position: relative;
&.active { &.active {
background-color: var(--highlight-bg-color); background-color: var(--highlight-bg-color);
} }
&.active:hover .tab-close {
display: block;
}
.tab-close {
position: absolute;
display: none;
padding: 5px;
right: 2px;
top: 5px;
cursor: pointer;
}
} }
.tab-add { .tab-add {

View File

@ -14,17 +14,22 @@ import "./workspace.less";
function Tab({ tabId }: { tabId: string }) { function Tab({ tabId }: { tabId: string }) {
const windowData = jotai.useAtomValue(atoms.waveWindow); const windowData = jotai.useAtomValue(atoms.waveWindow);
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId)); const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
function setActiveTab(tabId: string) { function setActiveTab() {
if (tabId == null) {
return;
}
WOS.SetActiveTab(tabId); WOS.SetActiveTab(tabId);
} }
function handleCloseTab() {
WOS.CloseTab(tabId);
}
return ( return (
<div <div
className={clsx("tab", { active: tabData != null && windowData.activetabid === tabData.oid })} className={clsx("tab", { active: tabData != null && windowData.activetabid === tabData.oid })}
onClick={() => setActiveTab(tabData?.oid)} onClick={() => setActiveTab()}
> >
<div className="tab-close" onClick={() => handleCloseTab()}>
<div>
<i className="fa fa-solid fa-xmark" />
</div>
</div>
{tabData?.name ?? "..."} {tabData?.name ?? "..."}
</div> </div>
); );
@ -115,8 +120,14 @@ function WorkspaceElem() {
<div className="workspace"> <div className="workspace">
<TabBar workspace={ws} /> <TabBar workspace={ws} />
<div className="workspace-tabcontent"> <div className="workspace-tabcontent">
<TabContent key={windowData.workspaceid} tabId={activeTabId} /> {activeTabId == "" ? (
<Widgets /> <CenteredDiv>No Active Tab</CenteredDiv>
) : (
<>
<TabContent key={windowData.workspaceid} tabId={activeTabId} />
<Widgets />
</>
)}
</div> </div>
</div> </div>
); );

View File

@ -132,3 +132,50 @@ func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wsto
rtn["blockid"] = blockData.OID rtn["blockid"] = blockData.OID
return updatesRtn(ctx, rtn) return updatesRtn(ctx, rtn)
} }
func (svc *ObjectService) DeleteBlock(uiContext wstore.UIContext, blockId string) (any, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = wstore.ContextWithUpdates(ctx)
err := wstore.DeleteBlock(ctx, uiContext.ActiveTabId, blockId)
if err != nil {
return nil, fmt.Errorf("error deleting block: %w", err)
}
blockcontroller.StopBlockController(blockId)
return updatesRtn(ctx, nil)
}
func (svc *ObjectService) CloseTab(uiContext wstore.UIContext, tabId string) (any, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = wstore.ContextWithUpdates(ctx)
window, err := wstore.DBMustGet[*wstore.Window](ctx, uiContext.WindowId)
if err != nil {
return nil, fmt.Errorf("error getting window: %w", err)
}
tab, err := wstore.DBMustGet[*wstore.Tab](ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error getting tab: %w", err)
}
for _, blockId := range tab.BlockIds {
blockcontroller.StopBlockController(blockId)
}
err = wstore.CloseTab(ctx, window.WorkspaceId, tabId)
if err != nil {
return nil, fmt.Errorf("error closing tab: %w", err)
}
if window.ActiveTabId == tabId {
ws, err := wstore.DBMustGet[*wstore.Workspace](ctx, window.WorkspaceId)
if err != nil {
return nil, fmt.Errorf("error getting workspace: %w", err)
}
var newActiveTabId string
if len(ws.TabIds) > 0 {
newActiveTabId = ws.TabIds[0]
} else {
newActiveTabId = ""
}
wstore.SetActiveTab(ctx, uiContext.WindowId, newActiveTabId)
}
return updatesRtn(ctx, nil)
}

View File

@ -159,10 +159,12 @@ func (update WaveObjUpdate) MarshalJSON() ([]byte, error) {
rtn["updatetype"] = update.UpdateType rtn["updatetype"] = update.UpdateType
rtn["otype"] = update.OType rtn["otype"] = update.OType
rtn["oid"] = update.OID rtn["oid"] = update.OID
var err error if update.Obj != nil {
rtn["obj"], err = waveobj.ToJsonMap(update.Obj) var err error
if err != nil { rtn["obj"], err = waveobj.ToJsonMap(update.Obj)
return nil, err if err != nil {
return nil, err
}
} }
return json.Marshal(rtn) return json.Marshal(rtn)
} }
@ -308,9 +310,11 @@ func SetActiveTab(ctx context.Context, windowId string, tabId string) error {
if window == nil { if window == nil {
return fmt.Errorf("window not found: %q", windowId) return fmt.Errorf("window not found: %q", windowId)
} }
tab, _ := DBGet[*Tab](tx.Context(), tabId) if tabId != "" {
if tab == nil { tab, _ := DBGet[*Tab](tx.Context(), tabId)
return fmt.Errorf("tab not found: %q", tabId) if tab == nil {
return fmt.Errorf("tab not found: %q", tabId)
}
} }
window.ActiveTabId = tabId window.ActiveTabId = tabId
DBUpdate(tx.Context(), window) DBUpdate(tx.Context(), window)
@ -340,6 +344,57 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *BlockDef, rtOpts *
}) })
} }
func findStringInSlice(slice []string, val string) int {
for idx, v := range slice {
if v == val {
return idx
}
}
return -1
}
func DeleteBlock(ctx context.Context, tabId string, blockId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
tab, _ := DBGet[*Tab](tx.Context(), tabId)
if tab == nil {
return fmt.Errorf("tab not found: %q", tabId)
}
blockIdx := findStringInSlice(tab.BlockIds, blockId)
if blockIdx == -1 {
return nil
}
tab.BlockIds = append(tab.BlockIds[:blockIdx], tab.BlockIds[blockIdx+1:]...)
DBUpdate(tx.Context(), tab)
DBDelete(tx.Context(), "block", blockId)
return nil
})
}
func CloseTab(ctx context.Context, workspaceId string, tabId string) error {
return WithTx(ctx, func(tx *TxWrap) error {
ws, _ := DBGet[*Workspace](tx.Context(), workspaceId)
if ws == nil {
return fmt.Errorf("workspace not found: %q", workspaceId)
}
tab, _ := DBGet[*Tab](tx.Context(), tabId)
if tab == nil {
return fmt.Errorf("tab not found: %q", tabId)
}
tabIdx := findStringInSlice(ws.TabIds, tabId)
if tabIdx == -1 {
return nil
}
ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...)
DBUpdate(tx.Context(), ws)
DBDelete(tx.Context(), "tab", tabId)
for _, blockId := range tab.BlockIds {
DBDelete(tx.Context(), "block", blockId)
}
return nil
})
}
func EnsureInitialData() error { func EnsureInitialData() error {
// does not need to run in a transaction since it is called on startup // does not need to run in a transaction since it is called on startup
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)