@vuu-ui/vuu-shell 0.7.3 → 0.7.4-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.js +1067 -2
- package/cjs/index.js.map +2 -2
- package/esm/index.js +1072 -2
- package/esm/index.js.map +2 -2
- package/index.css +208 -1
- package/index.css.map +1 -1
- package/package.json +4 -4
package/esm/index.js
CHANGED
|
@@ -1,4 +1,1074 @@
|
|
|
1
|
-
import Ue,{useEffect as Ie,useState as Be}from"react";import Ae from"classnames";import{Fragment as Oe,jsx as $e,jsxs as Q}from"react/jsx-runtime";var vo=({connectionStatus:e,className:t,element:o="span",...r})=>{let[s,a]=Be("vuuConnectingStatus");Ie(()=>{switch(e){case"connected":case"reconnected":a("vuuActiveStatus");break;case"connecting":a("vuuConnectingStatus");break;case"disconnected":a("vuuDisconnectedStatus");break;default:break}},[e]);let l=Ue.createElement(o,{...r,className:Ae("vuuStatus vuuIcon",s,t)});return $e(Oe,{children:Q("div",{className:"vuuStatus-container salt-theme",children:[l,Q("div",{className:"vuuStatus-text",children:["Status: ",e.toUpperCase()]})]})})};import{Dropdown as Je}from"@heswell/salt-lab";import{useCallback as _e}from"react";import qe from"classnames";import{jsx as Ye}from"react/jsx-runtime";var Ge="vuuDensitySwitch",ze=["high","medium","low","touch"],Ke="high",bo=({className:e,defaultDensity:t=Ke,onDensityChange:o})=>{let r=_e((a,l)=>{o(l)},[o]),s=qe(Ge,e);return Ye(Je,{className:s,source:ze,defaultSelected:t,onSelectionChange:r})};import j,{Suspense as je,useEffect as ee}from"react";import{registerComponent as et}from"@vuu-ui/vuu-layout";import We from"react";import{Fragment as Qe,jsx as X,jsxs as Xe}from"react/jsx-runtime";var H=class extends We.Component{constructor(t){super(t),this.state={errorMessage:null}}static getDerivedStateFromError(t){return{errorMessage:t.message}}componentDidCatch(t,o){console.log(t,o)}render(){return this.state.errorMessage?Xe(Qe,{children:[X("h1",{children:"Something went wrong."}),X("p",{children:this.state.errorMessage})]}):this.props.children}};import{jsx as Ze}from"react/jsx-runtime";var Z=()=>Ze("div",{className:"hwLoader",children:"loading"});import{jsx as F}from"react/jsx-runtime";var R=new Map,tt=e=>(ee(()=>()=>{R.delete(e)},[e]),R.has(e)||R.set(e,j.lazy(()=>import(e))),R.get(e));function ot({url:e,css:t,params:o,...r}){console.log("Feature render",{css:t,url:e,props:r}),ee(()=>(console.log("%cFeature mount","color: green;"),()=>{console.log("%cFeature unmount","color:red;")}),[]),t&&import(t).then(a=>{console.log("%cInject Styles","color: blue;font-weight: bold"),document.adoptedStyleSheets=[...document.adoptedStyleSheets,a.default]});let s=tt(e);return F(H,{children:F(je,{fallback:F(Z,{}),children:F(s,{...r,...o})})})}var te=j.memo(ot);te.displayName="Feature";et("Feature",te,"view");import{useState as oe}from"react";import{Button as rt}from"@salt-ds/core";import{FormField as re,Input as ne}from"@heswell/salt-lab";import{jsx as M,jsxs as nt}from"react/jsx-runtime";var se="vuuLoginPanel",jo=({onSubmit:e})=>{let[t,o]=oe(""),[r,s]=oe(""),a=()=>{e(t,r)},l=(m,u)=>{o(u)},c=(m,u)=>{s(u)},i=t.trim()!==""&&r.trim()!=="";return nt("div",{className:se,children:[M(re,{label:"Username",style:{width:200},children:M(ne,{value:t,id:"text-username",onChange:l})}),M(re,{label:"Password",style:{width:200},children:M(ne,{type:"password",value:r,id:"text-password",onChange:c})}),M(rt,{className:`${se}-login`,disabled:!i,onClick:a,variant:"cta",children:"Login"})]})};import{getCookieValue as ae}from"@vuu-ui/vuu-utils";var rr=()=>{let e=ae("vuu-username"),t=ae("vuu-auth-token");return[e,t]},st=(e="login.html")=>{window.location.href=e},ie=e=>{document.cookie="vuu-username= ; expires = Thu, 01 Jan 1970 00:00:00 GMT",document.cookie="vuu-auth-token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT",st(e)};import{useCallback as P,useEffect as at,useMemo as it,useRef as O,useState as le}from"react";import ue from"classnames";import{useIdMemo as lt}from"@salt-ds/core";import{Button as ce}from"@salt-ds/core";import{isErrorResponse as ut,RemoteDataSource as ct}from"@vuu-ui/vuu-data";import{buildColumnMap as mt,isValidNumber as me,shallowEquals as dt}from"@vuu-ui/vuu-utils";import{jsx as L,jsxs as $}from"react/jsx-runtime";var x="vuuSessionEditingForm",de=(e,t)=>{let o=e.find(r=>r.name===t);if(o)return o;throw Error(`SessionEditingForm, no field '${t}' found`)},pe=e=>{let{dataset:{field:t},value:o}=e.target;if(t===void 0)throw Error("SessionEditingForm, form field has no field name");return[t,o]},C={uninitialised:0,unchanged:1,changed:2,invalid:3};function fe(e,t,o=!1){switch(t){case"int":case"long":{let r=parseInt(e,10);if(me(r))return r;if(o)throw Error("SessionEditingForm getTypedValue");return}case"double":{let r=parseFloat(e);return me(r)?r:void 0}case"boolean":return e==="true";default:return e}}var pt=(e,t)=>{if(e)return e;if(t)return new ct({bufferSize:0,table:t.table,columns:t.columns.map(o=>o.name)});throw Error("SessionEditingForm: either a DataSource or a TableSchema must be provided")},Cr=({className:e,config:{fields:t,key:o},dataSource:r,id:s,onClose:a,schema:l,...c})=>{let[i,m]=le(),[u,g]=le(""),d=O(null),p=O(),h=O(C.uninitialised),v=it(()=>{let n=S=>{if(y){let T={};for(let D of v.columns)T[D]=S[y[D]];h.current===C.uninitialised&&(h.current=C.unchanged,p.current=T),m(T)}},f=pt(r,l),y=mt(f.columns);return f.subscribe({range:{from:0,to:5}},S=>{S.type==="viewport-update"&&S.rows&&(h.current===C.uninitialised?n(S.rows[0]):console.log("what do we do with server updates"))}),f},[r,l]),E=lt(s),N=P(n=>{let[f,y]=pe(n),{type:S}=de(t,f),T=fe(y,S);m((D={})=>{let W={...D,[f]:T},ke=dt(W,p.current);return h.current=ke?C.unchanged:T!==void 0?C.changed:C.invalid,W})},[t]),k=P(n=>{let[f,y]=pe(n),{type:S}=de(t,f);console.log("BLUR",{keyField:o});let T=i==null?void 0:i[o],D=fe(y,S,!0);typeof T=="string"&&v.menuRpcCall({rowKey:T,field:f,value:D,type:"VP_EDIT_CELL_RPC"})},[v,t,o,i]),b=P(async()=>{let n=await v.menuRpcCall({type:"VP_EDIT_SUBMIT_FORM_RPC"});ut(n)&&g(n.error)},[v]),U=P(n=>{n.key==="Enter"&&h.current===C.changed&&b()},[b]),I=P(()=>{a()},[a]),B=n=>{var y;let f=String((y=i==null?void 0:i[n.name])!=null?y:"");return n.readonly||n.name===o?L("div",{className:`${x}-fieldValue vuuReadOnly`,children:f}):L("input",{className:`${x}-fieldValue`,"data-field":n.name,onBlur:k,onChange:N,type:"text",value:f,id:`${E}-input-${n.name}`})};at(()=>{if(d.current){let n=d.current.querySelector("input");n&&setTimeout(()=>{n.focus(),console.log("select item"),n.select()},100)}},[]);let A=h.current===C.changed;return $("div",{...c,className:ue(x,e),children:[u?L("div",{className:`${x}-errorBanner`,"data-icon":"error",title:u,children:"Error, edit(s) not saved"}):void 0,L("div",{className:`${x}-content`,ref:d,onKeyDown:U,children:t.map(n=>{var f;return $("div",{className:`${x}-field`,children:[L("label",{className:ue(`${x}-fieldLabel`,{[`${x}-required`]:n.required}),htmlFor:`${E}-input-${n.name}`,children:(f=n==null?void 0:n.label)!=null?f:n.description}),B(n)]},n.name)})}),$("div",{className:`${x}-buttonbar salt-theme salt-density-high`,children:[L(ce,{type:"submit",variant:"cta",disabled:!A,onClick:b,children:"Submit"}),L(ce,{variant:"secondary",onClick:I,children:"Cancel"})]})]})};import{connectToServer as qt}from"@vuu-ui/vuu-data";import Gt from"classnames";import{useCallback as z,useEffect as zt,useRef as K,useState as Kt}from"react";import{createContext as ft,useContext as gt}from"react";import{jsx as J}from"react/jsx-runtime";var ht={},_=ft(ht),yt=({children:e,context:t,inheritedContext:o})=>{let r={...o,...t};return J(_.Provider,{value:r,children:e})},ge=({children:e,value:t})=>J(_.Consumer,{children:o=>J(yt,{context:t,inheritedContext:o,children:e})}),Nr=()=>gt(_);import{useCallback as q,useEffect as vt,useState as St}from"react";var he=(e,t,o="latest")=>new Promise((r,s)=>{console.log(`load local config at ${e} for user ${t.username}, id ${o}`);let a=localStorage.getItem(e);if(a){let l=JSON.parse(a);r(l)}else s()}),ye=(e,t,o)=>new Promise((r,s)=>{try{localStorage.setItem(e,JSON.stringify(o)),r(void 0)}catch{s()}});var ve=(e,t,o="latest")=>new Promise((r,s)=>{fetch(`${e}/${t.username}/${o}`,{}).then(a=>{a.ok?r(a.json()):s(void 0)}).catch(()=>{s(void 0)})}),Se=(e,t,o)=>new Promise((r,s)=>{fetch(`${e}/${t.username}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}).then(a=>{a.ok?r(void 0):s()})});var Te=({saveLocation:e,saveUrl:t="api/vui",user:o,defaultLayout:r})=>{let[s,a]=St(r),l=e==="remote",c=l?ve:he,i=l?Se:ye,m=p=>{a(p)},u=q(async(p="latest")=>{try{let h=await c(t,o,p);m(h)}catch{m(r)}},[r,c,t,o]);vt(()=>{u()},[u]);let g=q(p=>{i(t,o,p)},[i,t,o]),d=q(p=>{u(p)},[u]);return[s,g,d]};import{DockLayout as Yt,DraggableLayout as Re,Drawer as Wt,Flexbox as Qt,LayoutProvider as Xt,View as Zt}from"@vuu-ui/vuu-layout";import{useCallback as Ot}from"react";import{Button as Nt}from"@salt-ds/core";import{DropdownBase as Ht}from"@heswell/salt-lab";import{UserSolidIcon as Rt}from"@salt-ds/icons";import{formatDate as Tt}from"@vuu-ui/vuu-utils";import{List as xt,ListItem as Ct}from"@heswell/salt-lab";import{Button as Lt}from"@salt-ds/core";import{ExportIcon as wt}from"@salt-ds/icons";import{forwardRef as Dt,useCallback as Ce,useEffect as Et,useState as bt}from"react";var xe=async e=>await fetch(`api/vui/${e.username}`,{}).then(o=>o.ok?o.json():null).catch(()=>{console.log("error getting history")});import{jsx as V,jsxs as Le}from"react/jsx-runtime";var Mt=({lastUpdate:e},{lastUpdate:t})=>t===e?0:t<e?-1:1,Pt=e=>V(Ct,{...e}),we=Dt(function({loginUrl:t,onNavigate:o,user:r,layoutId:s="latest"},a){let[l,c]=bt([]);Et(()=>{async function g(){let p=(await xe(r)).filter(h=>h.id!=="latest").sort(Mt).map(({id:h,lastUpdate:v})=>({lastUpdate:v,id:h,label:`Saved at ${Tt(new Date(v),"kk:mm:ss")}`}));console.log({sortedHistory:p}),c(p)}g()},[r]);let i=Ce((g,d)=>{d&&o(d.id)},[o]),m=Ce(()=>{ie(t)},[t]),u=l.length===0?null:s==="latest"?l[0]:l.find(g=>g.id===s);return Le("div",{className:"vuuUserPanel",ref:a,children:[V(xt,{ListItem:Pt,className:"vuuUserPanel-history",onSelect:i,selected:u,source:l}),V("div",{className:"vuuUserPanel-buttonBar",children:Le(Lt,{"aria-label":"logout",onClick:m,children:[V(wt,{})," Logout"]})})]})});import{jsx as G,jsxs as Ft}from"react/jsx-runtime";var De=({layoutId:e,loginUrl:t,onNavigate:o,user:r})=>Ft(Ht,{className:"vuuUserProfile",placement:"bottom-end",children:[G(Nt,{variant:"secondary",children:G(Rt,{})}),G(we,{layoutId:e,loginUrl:t,onNavigate:a=>{o(a)},user:r})]});import{ToggleButton as Ee,ToggleButtonGroup as Vt}from"@heswell/salt-lab";import kt from"classnames";import{useControlled as Ut}from"@salt-ds/core";import{useCallback as It}from"react";import{jsx as Me,jsxs as At}from"react/jsx-runtime";var Bt="vuuThemeSwitch",be=["light","dark"],Pe=({className:e,defaultMode:t,mode:o,onChange:r,...s})=>{let[a,l]=Ut({controlled:o,default:t!=null?t:"light",name:"ThemeSwitch",state:"mode"}),c=be.indexOf(a),i=It((u,g)=>{let d=be[g];l(d),r(d)},[r,l]),m=kt(Bt,e);return At(Vt,{className:m,...s,onChange:i,selectedIndex:c,children:[Me(Ee,{"aria-label":"alert",tooltipText:"Light Theme","data-icon":"light"}),Me(Ee,{"aria-label":"home",tooltipText:"Dark Theme","data-icon":"dark"})]})};import $t from"classnames";import{jsx as Ne,jsxs as _t}from"react/jsx-runtime";var Jt="vuuAppHeader",He=({className:e,layoutId:t,loginUrl:o,onNavigate:r,onSwitchTheme:s,themeMode:a="light",user:l,...c})=>{let i=$t(Jt,e),m=Ot(u=>s==null?void 0:s(u),[s]);return _t("header",{className:i,...c,children:[Ne(Pe,{defaultMode:a,onChange:m}),Ne(De,{layoutId:t,loginUrl:o,onNavigate:r,user:l})]})};import{logger as jt}from"@vuu-ui/vuu-utils";import{jsx as w,jsxs as Fe}from"react/jsx-runtime";var{error:Y}=jt("Shell"),eo={type:"View",props:{style:{height:"calc(100% - 6px)"}},children:[{props:{className:"vuuShell-warningPlaceholder"},type:"Placeholder"}]},os=({children:e,className:t,defaultLayout:o=eo,leftSidePanel:r,loginUrl:s,saveLocation:a="remote",saveUrl:l,serverUrl:c,user:i,...m})=>{let u=K(null),g=K(null),[d,p]=Kt(!1),h=K("latest"),[v,E,N]=Te({defaultLayout:o,saveLocation:a,user:i}),k=z(n=>{try{E(n)}catch{Y==null||Y("Failed to save layout")}},[E]),b=z(n=>{u.current&&(u.current.dataset.mode=n)},[]),U=n=>{var y;let f=n.target;(y=g.current)!=null&&y.contains(f)||p(!d)},I=z(n=>{h.current=n,N(n)},[N]);zt(()=>{c&&i.token&&qt({authToken:i.token,url:c,username:i.username})},[c,i.token,i.username]);let B=()=>{let n=[];return r&&n.push(w(Wt,{onClick:U,open:d,position:"left",inline:!0,peekaboo:!0,sizeOpen:200,toggleButton:"end",children:w(Zt,{className:"vuuShell-palette",id:"vw-app-palette",ref:g,style:{height:"100%"},children:r},"app-palette")},"left-panel")),n},A=Gt("vuuShell",t,"salt-theme","salt-density-high");return Fe(ge,{value:void 0,children:[w(Xt,{layout:v,onLayoutChange:k,children:w(Re,{className:A,"data-mode":"light",ref:u,...m,children:Fe(Qt,{className:"App",style:{flexDirection:"column",height:"100%",width:"100%"},children:[w(He,{layoutId:h.current,loginUrl:s,user:i,onNavigate:I,onSwitchTheme:b}),w(Yt,{style:{flex:1},children:B().concat(w(Re,{dropTarget:!0,style:{width:"100%",height:"100%"}},"main-content"))})]})})}),e]})};import{createContext as to,isValidElement as oo,cloneElement as ro,useContext as no}from"react";import so from"classnames";import{jsx as mo}from"react/jsx-runtime";var ao="medium",io="salt-theme",lo="light",Ve=to({density:"high",theme:"salt-theme",themeMode:"light"}),uo=(e,t,o,r)=>{var s;return oo(e)?ro(e,{className:so((s=e.props)==null?void 0:s.className,t,`salt-density-${r}`),"data-mode":o}):(console.warn(`
|
|
1
|
+
// src/connection-status/ConnectionStatusIcon.tsx
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var ConnectionStatusIcon = ({ connectionStatus, className, element = "span", ...props }) => {
|
|
6
|
+
const [classBase6, setClassBase] = useState("vuuConnectingStatus");
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
switch (connectionStatus) {
|
|
9
|
+
case "connected":
|
|
10
|
+
case "reconnected":
|
|
11
|
+
setClassBase("vuuActiveStatus");
|
|
12
|
+
break;
|
|
13
|
+
case "connecting":
|
|
14
|
+
setClassBase("vuuConnectingStatus");
|
|
15
|
+
break;
|
|
16
|
+
case "disconnected":
|
|
17
|
+
setClassBase("vuuDisconnectedStatus");
|
|
18
|
+
break;
|
|
19
|
+
default:
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}, [connectionStatus]);
|
|
23
|
+
const statusIcon = React.createElement(
|
|
24
|
+
element,
|
|
25
|
+
{
|
|
26
|
+
...props,
|
|
27
|
+
className: cx("vuuStatus vuuIcon", classBase6, className)
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "vuuStatus-container salt-theme", children: [
|
|
31
|
+
statusIcon,
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "vuuStatus-text", children: [
|
|
33
|
+
"Status: ",
|
|
34
|
+
connectionStatus.toUpperCase()
|
|
35
|
+
] })
|
|
36
|
+
] }) });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/density-switch/DensitySwitch.tsx
|
|
40
|
+
import { Dropdown } from "@heswell/salt-lab";
|
|
41
|
+
import { useCallback } from "react";
|
|
42
|
+
import cx2 from "classnames";
|
|
43
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
44
|
+
var classBase = "vuuDensitySwitch";
|
|
45
|
+
var densities = ["high", "medium", "low", "touch"];
|
|
46
|
+
var DEFAULT_DENSITY = "high";
|
|
47
|
+
var DensitySwitch = ({
|
|
48
|
+
className: classNameProp,
|
|
49
|
+
defaultDensity = DEFAULT_DENSITY,
|
|
50
|
+
onDensityChange
|
|
51
|
+
}) => {
|
|
52
|
+
const handleSelectionChange = useCallback((_event, selectedItem) => {
|
|
53
|
+
onDensityChange(selectedItem);
|
|
54
|
+
}, [onDensityChange]);
|
|
55
|
+
const className = cx2(classBase, classNameProp);
|
|
56
|
+
return /* @__PURE__ */ jsx2(
|
|
57
|
+
Dropdown,
|
|
58
|
+
{
|
|
59
|
+
className,
|
|
60
|
+
source: densities,
|
|
61
|
+
defaultSelected: defaultDensity,
|
|
62
|
+
onSelectionChange: handleSelectionChange
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/feature/Feature.tsx
|
|
68
|
+
import React3, { Suspense, useEffect as useEffect2 } from "react";
|
|
69
|
+
import { registerComponent } from "@vuu-ui/vuu-layout";
|
|
70
|
+
|
|
71
|
+
// src/feature/ErrorBoundary.jsx
|
|
72
|
+
import React2 from "react";
|
|
73
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
74
|
+
var ErrorBoundary = class extends React2.Component {
|
|
75
|
+
constructor(props) {
|
|
76
|
+
super(props);
|
|
77
|
+
this.state = { errorMessage: null };
|
|
78
|
+
}
|
|
79
|
+
static getDerivedStateFromError(error2) {
|
|
80
|
+
return { errorMessage: error2.message };
|
|
81
|
+
}
|
|
82
|
+
componentDidCatch(error2, errorInfo) {
|
|
83
|
+
console.log(error2, errorInfo);
|
|
84
|
+
}
|
|
85
|
+
render() {
|
|
86
|
+
if (this.state.errorMessage) {
|
|
87
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
88
|
+
/* @__PURE__ */ jsx3("h1", { children: "Something went wrong." }),
|
|
89
|
+
/* @__PURE__ */ jsx3("p", { children: this.state.errorMessage })
|
|
90
|
+
] });
|
|
91
|
+
}
|
|
92
|
+
return this.props.children;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/feature/Loader.tsx
|
|
97
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
98
|
+
var Loader = () => /* @__PURE__ */ jsx4("div", { className: "hwLoader", children: "loading" });
|
|
99
|
+
|
|
100
|
+
// src/feature/Feature.tsx
|
|
101
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
102
|
+
var componentsMap = /* @__PURE__ */ new Map();
|
|
103
|
+
var useCachedFeature = (url) => {
|
|
104
|
+
useEffect2(
|
|
105
|
+
() => () => {
|
|
106
|
+
componentsMap.delete(url);
|
|
107
|
+
},
|
|
108
|
+
[url]
|
|
109
|
+
);
|
|
110
|
+
if (!componentsMap.has(url)) {
|
|
111
|
+
componentsMap.set(
|
|
112
|
+
url,
|
|
113
|
+
React3.lazy(() => import(
|
|
114
|
+
/* @vite-ignore */
|
|
115
|
+
url
|
|
116
|
+
))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return componentsMap.get(url);
|
|
120
|
+
};
|
|
121
|
+
function RawFeature({
|
|
122
|
+
url,
|
|
123
|
+
css,
|
|
124
|
+
params,
|
|
125
|
+
...props
|
|
126
|
+
}) {
|
|
127
|
+
console.log("Feature render", { css, url, props });
|
|
128
|
+
useEffect2(() => {
|
|
129
|
+
console.log("%cFeature mount", "color: green;");
|
|
130
|
+
return () => {
|
|
131
|
+
console.log("%cFeature unmount", "color:red;");
|
|
132
|
+
};
|
|
133
|
+
}, []);
|
|
134
|
+
if (css) {
|
|
135
|
+
import(
|
|
136
|
+
/* @vite-ignore */
|
|
137
|
+
css
|
|
138
|
+
).then(
|
|
139
|
+
(cssModule) => {
|
|
140
|
+
console.log("%cInject Styles", "color: blue;font-weight: bold");
|
|
141
|
+
document.adoptedStyleSheets = [
|
|
142
|
+
...document.adoptedStyleSheets,
|
|
143
|
+
cssModule.default
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const LazyFeature = useCachedFeature(url);
|
|
149
|
+
return /* @__PURE__ */ jsx5(ErrorBoundary, { children: /* @__PURE__ */ jsx5(Suspense, { fallback: /* @__PURE__ */ jsx5(Loader, {}), children: /* @__PURE__ */ jsx5(LazyFeature, { ...props, ...params }) }) });
|
|
150
|
+
}
|
|
151
|
+
var Feature = React3.memo(RawFeature);
|
|
152
|
+
Feature.displayName = "Feature";
|
|
153
|
+
registerComponent("Feature", Feature, "view");
|
|
154
|
+
|
|
155
|
+
// src/login/LoginPanel.tsx
|
|
156
|
+
import { useState as useState2 } from "react";
|
|
157
|
+
import { Button } from "@salt-ds/core";
|
|
158
|
+
import { FormField, Input } from "@heswell/salt-lab";
|
|
159
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
160
|
+
var classBase2 = "vuuLoginPanel";
|
|
161
|
+
var LoginPanel = ({ onSubmit }) => {
|
|
162
|
+
const [username, setUserName] = useState2("");
|
|
163
|
+
const [password, setPassword] = useState2("");
|
|
164
|
+
const login = () => {
|
|
165
|
+
onSubmit(username, password);
|
|
166
|
+
};
|
|
167
|
+
const handleUsername = (_event, value) => {
|
|
168
|
+
setUserName(value);
|
|
169
|
+
};
|
|
170
|
+
const handlePassword = (_event, value) => {
|
|
171
|
+
setPassword(value);
|
|
172
|
+
};
|
|
173
|
+
const dataIsValid = username.trim() !== "" && password.trim() !== "";
|
|
174
|
+
return /* @__PURE__ */ jsxs3("div", { className: classBase2, children: [
|
|
175
|
+
/* @__PURE__ */ jsx6(FormField, { label: "Username", style: { width: 200 }, children: /* @__PURE__ */ jsx6(Input, { value: username, id: "text-username", onChange: handleUsername }) }),
|
|
176
|
+
/* @__PURE__ */ jsx6(FormField, { label: "Password", style: { width: 200 }, children: /* @__PURE__ */ jsx6(
|
|
177
|
+
Input,
|
|
178
|
+
{
|
|
179
|
+
type: "password",
|
|
180
|
+
value: password,
|
|
181
|
+
id: "text-password",
|
|
182
|
+
onChange: handlePassword
|
|
183
|
+
}
|
|
184
|
+
) }),
|
|
185
|
+
/* @__PURE__ */ jsx6(
|
|
186
|
+
Button,
|
|
187
|
+
{
|
|
188
|
+
className: `${classBase2}-login`,
|
|
189
|
+
disabled: !dataIsValid,
|
|
190
|
+
onClick: login,
|
|
191
|
+
variant: "cta",
|
|
192
|
+
children: "Login"
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
] });
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/login/login-utils.ts
|
|
199
|
+
import { getCookieValue } from "@vuu-ui/vuu-utils";
|
|
200
|
+
var getAuthDetailsFromCookies = () => {
|
|
201
|
+
const username = getCookieValue("vuu-username");
|
|
202
|
+
const token = getCookieValue("vuu-auth-token");
|
|
203
|
+
return [username, token];
|
|
204
|
+
};
|
|
205
|
+
var redirectToLogin = (loginUrl = "login.html") => {
|
|
206
|
+
window.location.href = loginUrl;
|
|
207
|
+
};
|
|
208
|
+
var logout = (loginUrl) => {
|
|
209
|
+
document.cookie = "vuu-username= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
210
|
+
document.cookie = "vuu-auth-token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
211
|
+
redirectToLogin(loginUrl);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/session-editing-form/SessionEditingForm.tsx
|
|
215
|
+
import {
|
|
216
|
+
useCallback as useCallback2,
|
|
217
|
+
useEffect as useEffect3,
|
|
218
|
+
useMemo,
|
|
219
|
+
useRef,
|
|
220
|
+
useState as useState3
|
|
221
|
+
} from "react";
|
|
222
|
+
import cx3 from "classnames";
|
|
223
|
+
import { useIdMemo } from "@salt-ds/core";
|
|
224
|
+
import { Button as Button2 } from "@salt-ds/core";
|
|
225
|
+
import {
|
|
226
|
+
isErrorResponse,
|
|
227
|
+
RemoteDataSource
|
|
228
|
+
} from "@vuu-ui/vuu-data";
|
|
229
|
+
import {
|
|
230
|
+
buildColumnMap,
|
|
231
|
+
isValidNumber,
|
|
232
|
+
shallowEquals
|
|
233
|
+
} from "@vuu-ui/vuu-utils";
|
|
234
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
235
|
+
var classBase3 = "vuuSessionEditingForm";
|
|
236
|
+
var getField = (fields, name) => {
|
|
237
|
+
const field = fields.find((f) => f.name === name);
|
|
238
|
+
if (field) {
|
|
239
|
+
return field;
|
|
240
|
+
} else {
|
|
241
|
+
throw Error(`SessionEditingForm, no field '${name}' found`);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var getFieldNameAndValue = (evt) => {
|
|
245
|
+
const {
|
|
246
|
+
dataset: { field },
|
|
247
|
+
value
|
|
248
|
+
} = evt.target;
|
|
249
|
+
if (field === void 0) {
|
|
250
|
+
throw Error("SessionEditingForm, form field has no field name");
|
|
251
|
+
}
|
|
252
|
+
return [field, value];
|
|
253
|
+
};
|
|
254
|
+
var Status = {
|
|
255
|
+
uninitialised: 0,
|
|
256
|
+
unchanged: 1,
|
|
257
|
+
changed: 2,
|
|
258
|
+
invalid: 3
|
|
259
|
+
};
|
|
260
|
+
function getTypedValue(value, type, throwIfUndefined = false) {
|
|
261
|
+
switch (type) {
|
|
262
|
+
case "int":
|
|
263
|
+
case "long": {
|
|
264
|
+
const typedValue = parseInt(value, 10);
|
|
265
|
+
if (isValidNumber(typedValue)) {
|
|
266
|
+
return typedValue;
|
|
267
|
+
} else if (throwIfUndefined) {
|
|
268
|
+
throw Error("SessionEditingForm getTypedValue");
|
|
269
|
+
} else {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
case "double": {
|
|
274
|
+
const typedValue = parseFloat(value);
|
|
275
|
+
if (isValidNumber(typedValue)) {
|
|
276
|
+
return typedValue;
|
|
277
|
+
}
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
case "boolean":
|
|
281
|
+
return value === "true" ? true : false;
|
|
282
|
+
default:
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
var getDataSource = (dataSource, schema) => {
|
|
287
|
+
if (dataSource) {
|
|
288
|
+
return dataSource;
|
|
289
|
+
} else if (schema) {
|
|
290
|
+
return new RemoteDataSource({
|
|
291
|
+
bufferSize: 0,
|
|
292
|
+
table: schema.table,
|
|
293
|
+
columns: schema.columns.map((col) => col.name)
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
throw Error(
|
|
297
|
+
"SessionEditingForm: either a DataSource or a TableSchema must be provided"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var SessionEditingForm = ({
|
|
302
|
+
className,
|
|
303
|
+
config: { fields, key: keyField },
|
|
304
|
+
dataSource: dataSourceProp,
|
|
305
|
+
id: idProp,
|
|
306
|
+
onClose,
|
|
307
|
+
schema,
|
|
308
|
+
...htmlAttributes
|
|
309
|
+
}) => {
|
|
310
|
+
const [values, setValues] = useState3();
|
|
311
|
+
const [errorMessage, setErrorMessage] = useState3("");
|
|
312
|
+
const formContentRef = useRef(null);
|
|
313
|
+
const initialDataRef = useRef();
|
|
314
|
+
const dataStatusRef = useRef(Status.uninitialised);
|
|
315
|
+
const dataSource = useMemo(() => {
|
|
316
|
+
const applyServerData = (data) => {
|
|
317
|
+
if (columnMap) {
|
|
318
|
+
const values2 = {};
|
|
319
|
+
for (const column of dataSource.columns) {
|
|
320
|
+
values2[column] = data[columnMap[column]];
|
|
321
|
+
}
|
|
322
|
+
if (dataStatusRef.current === Status.uninitialised) {
|
|
323
|
+
dataStatusRef.current = Status.unchanged;
|
|
324
|
+
initialDataRef.current = values2;
|
|
325
|
+
}
|
|
326
|
+
setValues(values2);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const ds = getDataSource(dataSourceProp, schema);
|
|
330
|
+
const columnMap = buildColumnMap(ds.columns);
|
|
331
|
+
ds.subscribe({ range: { from: 0, to: 5 } }, (message) => {
|
|
332
|
+
if (message.type === "viewport-update" && message.rows) {
|
|
333
|
+
if (dataStatusRef.current === Status.uninitialised) {
|
|
334
|
+
applyServerData(message.rows[0]);
|
|
335
|
+
} else {
|
|
336
|
+
console.log("what do we do with server updates");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
return ds;
|
|
341
|
+
}, [dataSourceProp, schema]);
|
|
342
|
+
const id = useIdMemo(idProp);
|
|
343
|
+
const handleChange = useCallback2(
|
|
344
|
+
(evt) => {
|
|
345
|
+
const [field, value] = getFieldNameAndValue(evt);
|
|
346
|
+
const { type } = getField(fields, field);
|
|
347
|
+
const typedValue = getTypedValue(value, type);
|
|
348
|
+
setValues((values2 = {}) => {
|
|
349
|
+
const newValues = {
|
|
350
|
+
...values2,
|
|
351
|
+
[field]: typedValue
|
|
352
|
+
};
|
|
353
|
+
const notUpdated = shallowEquals(newValues, initialDataRef.current);
|
|
354
|
+
dataStatusRef.current = notUpdated ? Status.unchanged : typedValue !== void 0 ? Status.changed : Status.invalid;
|
|
355
|
+
return newValues;
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
[fields]
|
|
359
|
+
);
|
|
360
|
+
const handleBlur = useCallback2(
|
|
361
|
+
(evt) => {
|
|
362
|
+
const [field, value] = getFieldNameAndValue(evt);
|
|
363
|
+
const { type } = getField(fields, field);
|
|
364
|
+
console.log("BLUR", {
|
|
365
|
+
keyField
|
|
366
|
+
});
|
|
367
|
+
const rowKey = values == null ? void 0 : values[keyField];
|
|
368
|
+
const typedValue = getTypedValue(value, type, true);
|
|
369
|
+
if (typeof rowKey === "string") {
|
|
370
|
+
dataSource.menuRpcCall({
|
|
371
|
+
rowKey,
|
|
372
|
+
field,
|
|
373
|
+
value: typedValue,
|
|
374
|
+
type: "VP_EDIT_CELL_RPC"
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
[dataSource, fields, keyField, values]
|
|
379
|
+
);
|
|
380
|
+
const handleSubmit = useCallback2(async () => {
|
|
381
|
+
const response = await dataSource.menuRpcCall({
|
|
382
|
+
type: "VP_EDIT_SUBMIT_FORM_RPC"
|
|
383
|
+
});
|
|
384
|
+
if (isErrorResponse(response)) {
|
|
385
|
+
setErrorMessage(response.error);
|
|
386
|
+
}
|
|
387
|
+
}, [dataSource]);
|
|
388
|
+
const handleKeyDown = useCallback2(
|
|
389
|
+
(evt) => {
|
|
390
|
+
if (evt.key === "Enter" && dataStatusRef.current === Status.changed) {
|
|
391
|
+
handleSubmit();
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
[handleSubmit]
|
|
395
|
+
);
|
|
396
|
+
const handleCancel = useCallback2(() => {
|
|
397
|
+
onClose();
|
|
398
|
+
}, [onClose]);
|
|
399
|
+
const getFormControl = (field) => {
|
|
400
|
+
var _a;
|
|
401
|
+
const value = String((_a = values == null ? void 0 : values[field.name]) != null ? _a : "");
|
|
402
|
+
if (field.readonly || field.name === keyField) {
|
|
403
|
+
return /* @__PURE__ */ jsx7("div", { className: `${classBase3}-fieldValue vuuReadOnly`, children: value });
|
|
404
|
+
} else {
|
|
405
|
+
return /* @__PURE__ */ jsx7(
|
|
406
|
+
"input",
|
|
407
|
+
{
|
|
408
|
+
className: `${classBase3}-fieldValue`,
|
|
409
|
+
"data-field": field.name,
|
|
410
|
+
onBlur: handleBlur,
|
|
411
|
+
onChange: handleChange,
|
|
412
|
+
type: "text",
|
|
413
|
+
value,
|
|
414
|
+
id: `${id}-input-${field.name}`
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
useEffect3(() => {
|
|
420
|
+
if (formContentRef.current) {
|
|
421
|
+
const firstInput = formContentRef.current.querySelector(
|
|
422
|
+
"input"
|
|
423
|
+
);
|
|
424
|
+
if (firstInput) {
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
firstInput.focus();
|
|
427
|
+
console.log("select item");
|
|
428
|
+
firstInput.select();
|
|
429
|
+
}, 100);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}, []);
|
|
433
|
+
const isDirty = dataStatusRef.current === Status.changed;
|
|
434
|
+
return /* @__PURE__ */ jsxs4("div", { ...htmlAttributes, className: cx3(classBase3, className), children: [
|
|
435
|
+
errorMessage ? /* @__PURE__ */ jsx7(
|
|
436
|
+
"div",
|
|
437
|
+
{
|
|
438
|
+
className: `${classBase3}-errorBanner`,
|
|
439
|
+
"data-icon": "error",
|
|
440
|
+
title: errorMessage,
|
|
441
|
+
children: "Error, edit(s) not saved"
|
|
442
|
+
}
|
|
443
|
+
) : void 0,
|
|
444
|
+
/* @__PURE__ */ jsx7(
|
|
445
|
+
"div",
|
|
446
|
+
{
|
|
447
|
+
className: `${classBase3}-content`,
|
|
448
|
+
ref: formContentRef,
|
|
449
|
+
onKeyDown: handleKeyDown,
|
|
450
|
+
children: fields.map((field) => {
|
|
451
|
+
var _a;
|
|
452
|
+
return /* @__PURE__ */ jsxs4("div", { className: `${classBase3}-field`, children: [
|
|
453
|
+
/* @__PURE__ */ jsx7(
|
|
454
|
+
"label",
|
|
455
|
+
{
|
|
456
|
+
className: cx3(`${classBase3}-fieldLabel`, {
|
|
457
|
+
[`${classBase3}-required`]: field.required
|
|
458
|
+
}),
|
|
459
|
+
htmlFor: `${id}-input-${field.name}`,
|
|
460
|
+
children: (_a = field == null ? void 0 : field.label) != null ? _a : field.description
|
|
461
|
+
}
|
|
462
|
+
),
|
|
463
|
+
getFormControl(field)
|
|
464
|
+
] }, field.name);
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
),
|
|
468
|
+
/* @__PURE__ */ jsxs4("div", { className: `${classBase3}-buttonbar salt-theme salt-density-high`, children: [
|
|
469
|
+
/* @__PURE__ */ jsx7(
|
|
470
|
+
Button2,
|
|
471
|
+
{
|
|
472
|
+
type: "submit",
|
|
473
|
+
variant: "cta",
|
|
474
|
+
disabled: !isDirty,
|
|
475
|
+
onClick: handleSubmit,
|
|
476
|
+
children: "Submit"
|
|
477
|
+
}
|
|
478
|
+
),
|
|
479
|
+
/* @__PURE__ */ jsx7(Button2, { variant: "secondary", onClick: handleCancel, children: "Cancel" })
|
|
480
|
+
] })
|
|
481
|
+
] });
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// src/shell.tsx
|
|
485
|
+
import { connectToServer } from "@vuu-ui/vuu-data";
|
|
486
|
+
import cx6 from "classnames";
|
|
487
|
+
import {
|
|
488
|
+
useCallback as useCallback7,
|
|
489
|
+
useEffect as useEffect6,
|
|
490
|
+
useRef as useRef2,
|
|
491
|
+
useState as useState6
|
|
492
|
+
} from "react";
|
|
493
|
+
|
|
494
|
+
// src/ShellContextProvider.tsx
|
|
495
|
+
import { createContext, useContext } from "react";
|
|
496
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
497
|
+
var defaultConfig = {};
|
|
498
|
+
var ShellContext = createContext(defaultConfig);
|
|
499
|
+
var Provider = ({
|
|
500
|
+
children,
|
|
501
|
+
context,
|
|
502
|
+
inheritedContext
|
|
503
|
+
}) => {
|
|
504
|
+
const mergedContext = {
|
|
505
|
+
...inheritedContext,
|
|
506
|
+
...context
|
|
507
|
+
};
|
|
508
|
+
return /* @__PURE__ */ jsx8(ShellContext.Provider, { value: mergedContext, children });
|
|
509
|
+
};
|
|
510
|
+
var ShellContextProvider = ({
|
|
511
|
+
children,
|
|
512
|
+
value
|
|
513
|
+
}) => {
|
|
514
|
+
return /* @__PURE__ */ jsx8(ShellContext.Consumer, { children: (context) => /* @__PURE__ */ jsx8(Provider, { context: value, inheritedContext: context, children }) });
|
|
515
|
+
};
|
|
516
|
+
var useShellContext = () => {
|
|
517
|
+
return useContext(ShellContext);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// src/layout-config/use-layout-config.ts
|
|
521
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState4 } from "react";
|
|
522
|
+
|
|
523
|
+
// src/layout-config/local-config.ts
|
|
524
|
+
var loadLocalConfig = (saveUrl, user, id = "latest") => new Promise((resolve, reject) => {
|
|
525
|
+
console.log(
|
|
526
|
+
`load local config at ${saveUrl} for user ${user.username}, id ${id}`
|
|
527
|
+
);
|
|
528
|
+
const data = localStorage.getItem(saveUrl);
|
|
529
|
+
if (data) {
|
|
530
|
+
const layout = JSON.parse(data);
|
|
531
|
+
resolve(layout);
|
|
532
|
+
} else {
|
|
533
|
+
reject();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
var saveLocalConfig = (saveUrl, user, data) => new Promise((resolve, reject) => {
|
|
537
|
+
try {
|
|
538
|
+
localStorage.setItem(saveUrl, JSON.stringify(data));
|
|
539
|
+
resolve(void 0);
|
|
540
|
+
} catch {
|
|
541
|
+
reject();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// src/layout-config/remote-config.ts
|
|
546
|
+
var loadRemoteConfig = (saveUrl, user, id = "latest") => new Promise((resolve, reject) => {
|
|
547
|
+
fetch(`${saveUrl}/${user.username}/${id}`, {}).then((response) => {
|
|
548
|
+
if (response.ok) {
|
|
549
|
+
resolve(response.json());
|
|
550
|
+
} else {
|
|
551
|
+
reject(void 0);
|
|
552
|
+
}
|
|
553
|
+
}).catch(() => {
|
|
554
|
+
reject(void 0);
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
var saveRemoteConfig = (saveUrl, user, data) => new Promise((resolve, reject) => {
|
|
558
|
+
fetch(`${saveUrl}/${user.username}`, {
|
|
559
|
+
method: "POST",
|
|
560
|
+
headers: {
|
|
561
|
+
"Content-Type": "application/json"
|
|
562
|
+
},
|
|
563
|
+
body: JSON.stringify(data)
|
|
564
|
+
}).then((response) => {
|
|
565
|
+
if (response.ok) {
|
|
566
|
+
resolve(void 0);
|
|
567
|
+
} else {
|
|
568
|
+
reject();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// src/layout-config/use-layout-config.ts
|
|
574
|
+
var useLayoutConfig = ({
|
|
575
|
+
saveLocation,
|
|
576
|
+
saveUrl = "api/vui",
|
|
577
|
+
user,
|
|
578
|
+
defaultLayout
|
|
579
|
+
}) => {
|
|
580
|
+
const [layout, _setLayout] = useState4(defaultLayout);
|
|
581
|
+
const usingRemote = saveLocation === "remote";
|
|
582
|
+
const loadConfig = usingRemote ? loadRemoteConfig : loadLocalConfig;
|
|
583
|
+
const saveConfig = usingRemote ? saveRemoteConfig : saveLocalConfig;
|
|
584
|
+
const setLayout = (layout2) => {
|
|
585
|
+
_setLayout(layout2);
|
|
586
|
+
};
|
|
587
|
+
const load = useCallback3(
|
|
588
|
+
async (id = "latest") => {
|
|
589
|
+
try {
|
|
590
|
+
const layout2 = await loadConfig(saveUrl, user, id);
|
|
591
|
+
setLayout(layout2);
|
|
592
|
+
} catch {
|
|
593
|
+
setLayout(defaultLayout);
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
[defaultLayout, loadConfig, saveUrl, user]
|
|
597
|
+
);
|
|
598
|
+
useEffect4(() => {
|
|
599
|
+
load();
|
|
600
|
+
}, [load]);
|
|
601
|
+
const saveData = useCallback3(
|
|
602
|
+
(data) => {
|
|
603
|
+
saveConfig(saveUrl, user, data);
|
|
604
|
+
},
|
|
605
|
+
[saveConfig, saveUrl, user]
|
|
606
|
+
);
|
|
607
|
+
const loadLayoutById = useCallback3(
|
|
608
|
+
(id) => {
|
|
609
|
+
load(id);
|
|
610
|
+
},
|
|
611
|
+
[load]
|
|
612
|
+
);
|
|
613
|
+
return [layout, saveData, loadLayoutById];
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/shell.tsx
|
|
617
|
+
import {
|
|
618
|
+
DockLayout,
|
|
619
|
+
DraggableLayout,
|
|
620
|
+
Drawer,
|
|
621
|
+
Flexbox,
|
|
622
|
+
LayoutProvider,
|
|
623
|
+
View
|
|
624
|
+
} from "@vuu-ui/vuu-layout";
|
|
625
|
+
|
|
626
|
+
// src/app-header/AppHeader.tsx
|
|
627
|
+
import { useCallback as useCallback6 } from "react";
|
|
628
|
+
|
|
629
|
+
// src/user-profile/UserProfile.tsx
|
|
630
|
+
import { Button as Button4 } from "@salt-ds/core";
|
|
631
|
+
import { DropdownBase } from "@heswell/salt-lab";
|
|
632
|
+
import { UserSolidIcon } from "@salt-ds/icons";
|
|
633
|
+
|
|
634
|
+
// src/user-profile/UserPanel.tsx
|
|
635
|
+
import { formatDate } from "@vuu-ui/vuu-utils";
|
|
636
|
+
import { List, ListItem } from "@heswell/salt-lab";
|
|
637
|
+
import { Button as Button3 } from "@salt-ds/core";
|
|
638
|
+
import { ExportIcon } from "@salt-ds/icons";
|
|
639
|
+
import {
|
|
640
|
+
forwardRef,
|
|
641
|
+
useCallback as useCallback4,
|
|
642
|
+
useEffect as useEffect5,
|
|
643
|
+
useState as useState5
|
|
644
|
+
} from "react";
|
|
645
|
+
|
|
646
|
+
// src/get-layout-history.ts
|
|
647
|
+
var getLayoutHistory = async (user) => {
|
|
648
|
+
const history = await fetch(`api/vui/${user.username}`, {}).then((response) => {
|
|
649
|
+
return response.ok ? response.json() : null;
|
|
650
|
+
}).catch(() => {
|
|
651
|
+
console.log("error getting history");
|
|
652
|
+
});
|
|
653
|
+
return history;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// src/user-profile/UserPanel.tsx
|
|
657
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
658
|
+
var byLastUpdate = ({ lastUpdate: l1 }, { lastUpdate: l2 }) => {
|
|
659
|
+
return l2 === l1 ? 0 : l2 < l1 ? -1 : 1;
|
|
660
|
+
};
|
|
661
|
+
var HistoryListItem = (props) => {
|
|
662
|
+
return /* @__PURE__ */ jsx9(ListItem, { ...props });
|
|
663
|
+
};
|
|
664
|
+
var UserPanel = forwardRef(function UserPanel2({ loginUrl, onNavigate, user, layoutId = "latest" }, forwardedRef) {
|
|
665
|
+
const [history, setHistory] = useState5([]);
|
|
666
|
+
useEffect5(() => {
|
|
667
|
+
async function getHistory() {
|
|
668
|
+
const history2 = await getLayoutHistory(user);
|
|
669
|
+
const sortedHistory = history2.filter((item) => item.id !== "latest").sort(byLastUpdate).map(({ id, lastUpdate }) => ({
|
|
670
|
+
lastUpdate,
|
|
671
|
+
id,
|
|
672
|
+
label: `Saved at ${formatDate(new Date(lastUpdate), "kk:mm:ss")}`
|
|
673
|
+
}));
|
|
674
|
+
console.log({ sortedHistory });
|
|
675
|
+
setHistory(sortedHistory);
|
|
676
|
+
}
|
|
677
|
+
getHistory();
|
|
678
|
+
}, [user]);
|
|
679
|
+
const handleHisorySelected = useCallback4(
|
|
680
|
+
(evt, selected2) => {
|
|
681
|
+
if (selected2) {
|
|
682
|
+
onNavigate(selected2.id);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
[onNavigate]
|
|
686
|
+
);
|
|
687
|
+
const handleLogout = useCallback4(() => {
|
|
688
|
+
logout(loginUrl);
|
|
689
|
+
}, [loginUrl]);
|
|
690
|
+
const selected = history.length === 0 ? null : layoutId === "latest" ? history[0] : history.find((i) => i.id === layoutId);
|
|
691
|
+
return /* @__PURE__ */ jsxs5("div", { className: "vuuUserPanel", ref: forwardedRef, children: [
|
|
692
|
+
/* @__PURE__ */ jsx9(
|
|
693
|
+
List,
|
|
694
|
+
{
|
|
695
|
+
ListItem: HistoryListItem,
|
|
696
|
+
className: "vuuUserPanel-history",
|
|
697
|
+
onSelect: handleHisorySelected,
|
|
698
|
+
selected,
|
|
699
|
+
source: history
|
|
700
|
+
}
|
|
701
|
+
),
|
|
702
|
+
/* @__PURE__ */ jsx9("div", { className: "vuuUserPanel-buttonBar", children: /* @__PURE__ */ jsxs5(Button3, { "aria-label": "logout", onClick: handleLogout, children: [
|
|
703
|
+
/* @__PURE__ */ jsx9(ExportIcon, {}),
|
|
704
|
+
" Logout"
|
|
705
|
+
] }) })
|
|
706
|
+
] });
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// src/user-profile/UserProfile.tsx
|
|
710
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
711
|
+
var UserProfile = ({
|
|
712
|
+
layoutId,
|
|
713
|
+
loginUrl,
|
|
714
|
+
onNavigate,
|
|
715
|
+
user
|
|
716
|
+
}) => {
|
|
717
|
+
const handleNavigate = (id) => {
|
|
718
|
+
onNavigate(id);
|
|
719
|
+
};
|
|
720
|
+
return /* @__PURE__ */ jsxs6(DropdownBase, { className: "vuuUserProfile", placement: "bottom-end", children: [
|
|
721
|
+
/* @__PURE__ */ jsx10(Button4, { variant: "secondary", children: /* @__PURE__ */ jsx10(UserSolidIcon, {}) }),
|
|
722
|
+
/* @__PURE__ */ jsx10(
|
|
723
|
+
UserPanel,
|
|
724
|
+
{
|
|
725
|
+
layoutId,
|
|
726
|
+
loginUrl,
|
|
727
|
+
onNavigate: handleNavigate,
|
|
728
|
+
user
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
] });
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// src/theme-switch/ThemeSwitch.tsx
|
|
735
|
+
import {
|
|
736
|
+
ToggleButton,
|
|
737
|
+
ToggleButtonGroup
|
|
738
|
+
} from "@heswell/salt-lab";
|
|
739
|
+
import cx4 from "classnames";
|
|
740
|
+
import { useControlled } from "@salt-ds/core";
|
|
741
|
+
import { useCallback as useCallback5 } from "react";
|
|
742
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
743
|
+
var classBase4 = "vuuThemeSwitch";
|
|
744
|
+
var modes = ["light", "dark"];
|
|
745
|
+
var ThemeSwitch = ({
|
|
746
|
+
className: classNameProp,
|
|
747
|
+
defaultMode: defaultModeProp,
|
|
748
|
+
mode: modeProp,
|
|
749
|
+
onChange,
|
|
750
|
+
...htmlAttributes
|
|
751
|
+
}) => {
|
|
752
|
+
const [mode, setMode] = useControlled({
|
|
753
|
+
controlled: modeProp,
|
|
754
|
+
default: defaultModeProp != null ? defaultModeProp : "light",
|
|
755
|
+
name: "ThemeSwitch",
|
|
756
|
+
state: "mode"
|
|
757
|
+
});
|
|
758
|
+
const selectedIndex = modes.indexOf(mode);
|
|
759
|
+
const handleChangeSecondary = useCallback5(
|
|
760
|
+
(_evt, index) => {
|
|
761
|
+
const mode2 = modes[index];
|
|
762
|
+
setMode(mode2);
|
|
763
|
+
onChange(mode2);
|
|
764
|
+
},
|
|
765
|
+
[onChange, setMode]
|
|
766
|
+
);
|
|
767
|
+
const className = cx4(classBase4, classNameProp);
|
|
768
|
+
return /* @__PURE__ */ jsxs7(
|
|
769
|
+
ToggleButtonGroup,
|
|
770
|
+
{
|
|
771
|
+
className,
|
|
772
|
+
...htmlAttributes,
|
|
773
|
+
onChange: handleChangeSecondary,
|
|
774
|
+
selectedIndex,
|
|
775
|
+
children: [
|
|
776
|
+
/* @__PURE__ */ jsx11(
|
|
777
|
+
ToggleButton,
|
|
778
|
+
{
|
|
779
|
+
"aria-label": "alert",
|
|
780
|
+
tooltipText: "Light Theme",
|
|
781
|
+
"data-icon": "light"
|
|
782
|
+
}
|
|
783
|
+
),
|
|
784
|
+
/* @__PURE__ */ jsx11(
|
|
785
|
+
ToggleButton,
|
|
786
|
+
{
|
|
787
|
+
"aria-label": "home",
|
|
788
|
+
tooltipText: "Dark Theme",
|
|
789
|
+
"data-icon": "dark"
|
|
790
|
+
}
|
|
791
|
+
)
|
|
792
|
+
]
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/app-header/AppHeader.tsx
|
|
798
|
+
import cx5 from "classnames";
|
|
799
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
800
|
+
var classBase5 = "vuuAppHeader";
|
|
801
|
+
var AppHeader = ({
|
|
802
|
+
className: classNameProp,
|
|
803
|
+
layoutId,
|
|
804
|
+
loginUrl,
|
|
805
|
+
onNavigate,
|
|
806
|
+
onSwitchTheme,
|
|
807
|
+
themeMode = "light",
|
|
808
|
+
user,
|
|
809
|
+
...htmlAttributes
|
|
810
|
+
}) => {
|
|
811
|
+
const className = cx5(classBase5, classNameProp);
|
|
812
|
+
const handleSwitchTheme = useCallback6(
|
|
813
|
+
(mode) => onSwitchTheme == null ? void 0 : onSwitchTheme(mode),
|
|
814
|
+
[onSwitchTheme]
|
|
815
|
+
);
|
|
816
|
+
return /* @__PURE__ */ jsxs8("header", { className, ...htmlAttributes, children: [
|
|
817
|
+
/* @__PURE__ */ jsx12(ThemeSwitch, { defaultMode: themeMode, onChange: handleSwitchTheme }),
|
|
818
|
+
/* @__PURE__ */ jsx12(
|
|
819
|
+
UserProfile,
|
|
820
|
+
{
|
|
821
|
+
layoutId,
|
|
822
|
+
loginUrl,
|
|
823
|
+
onNavigate,
|
|
824
|
+
user
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
] });
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// src/shell.tsx
|
|
831
|
+
import { logger } from "@vuu-ui/vuu-utils";
|
|
832
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
833
|
+
var { error } = logger("Shell");
|
|
834
|
+
var warningLayout = {
|
|
835
|
+
type: "View",
|
|
836
|
+
props: {
|
|
837
|
+
style: { height: "calc(100% - 6px)" }
|
|
838
|
+
},
|
|
839
|
+
children: [
|
|
840
|
+
{
|
|
841
|
+
props: {
|
|
842
|
+
className: "vuuShell-warningPlaceholder"
|
|
843
|
+
},
|
|
844
|
+
type: "Placeholder"
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
};
|
|
848
|
+
var Shell = ({
|
|
849
|
+
children,
|
|
850
|
+
className: classNameProp,
|
|
851
|
+
defaultLayout = warningLayout,
|
|
852
|
+
leftSidePanel,
|
|
853
|
+
loginUrl,
|
|
854
|
+
saveLocation = "remote",
|
|
855
|
+
saveUrl,
|
|
856
|
+
serverUrl,
|
|
857
|
+
user,
|
|
858
|
+
...htmlAttributes
|
|
859
|
+
}) => {
|
|
860
|
+
const rootRef = useRef2(null);
|
|
861
|
+
const paletteView = useRef2(null);
|
|
862
|
+
const [open, setOpen] = useState6(false);
|
|
863
|
+
const layoutId = useRef2("latest");
|
|
864
|
+
const [layout, saveLayoutConfig, loadLayoutById] = useLayoutConfig({
|
|
865
|
+
defaultLayout,
|
|
866
|
+
saveLocation,
|
|
867
|
+
user
|
|
868
|
+
});
|
|
869
|
+
const handleLayoutChange = useCallback7(
|
|
870
|
+
(layout2) => {
|
|
871
|
+
try {
|
|
872
|
+
saveLayoutConfig(layout2);
|
|
873
|
+
} catch {
|
|
874
|
+
error == null ? void 0 : error("Failed to save layout");
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
[saveLayoutConfig]
|
|
878
|
+
);
|
|
879
|
+
const handleSwitchTheme = useCallback7((mode) => {
|
|
880
|
+
if (rootRef.current) {
|
|
881
|
+
rootRef.current.dataset.mode = mode;
|
|
882
|
+
}
|
|
883
|
+
}, []);
|
|
884
|
+
const handleDrawerClick = (e) => {
|
|
885
|
+
var _a;
|
|
886
|
+
const target = e.target;
|
|
887
|
+
if (!((_a = paletteView.current) == null ? void 0 : _a.contains(target))) {
|
|
888
|
+
setOpen(!open);
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
const handleNavigate = useCallback7(
|
|
892
|
+
(id) => {
|
|
893
|
+
layoutId.current = id;
|
|
894
|
+
loadLayoutById(id);
|
|
895
|
+
},
|
|
896
|
+
[loadLayoutById]
|
|
897
|
+
);
|
|
898
|
+
useEffect6(() => {
|
|
899
|
+
if (serverUrl && user.token) {
|
|
900
|
+
connectToServer({
|
|
901
|
+
authToken: user.token,
|
|
902
|
+
url: serverUrl,
|
|
903
|
+
username: user.username
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}, [serverUrl, user.token, user.username]);
|
|
907
|
+
const getDrawers = () => {
|
|
908
|
+
const drawers = [];
|
|
909
|
+
if (leftSidePanel) {
|
|
910
|
+
drawers.push(
|
|
911
|
+
/* @__PURE__ */ jsx13(
|
|
912
|
+
Drawer,
|
|
913
|
+
{
|
|
914
|
+
onClick: handleDrawerClick,
|
|
915
|
+
open,
|
|
916
|
+
position: "left",
|
|
917
|
+
inline: true,
|
|
918
|
+
peekaboo: true,
|
|
919
|
+
sizeOpen: 200,
|
|
920
|
+
toggleButton: "end",
|
|
921
|
+
children: /* @__PURE__ */ jsx13(
|
|
922
|
+
View,
|
|
923
|
+
{
|
|
924
|
+
className: "vuuShell-palette",
|
|
925
|
+
id: "vw-app-palette",
|
|
926
|
+
ref: paletteView,
|
|
927
|
+
style: { height: "100%" },
|
|
928
|
+
children: leftSidePanel
|
|
929
|
+
},
|
|
930
|
+
"app-palette"
|
|
931
|
+
)
|
|
932
|
+
},
|
|
933
|
+
"left-panel"
|
|
934
|
+
)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
return drawers;
|
|
938
|
+
};
|
|
939
|
+
const className = cx6(
|
|
940
|
+
"vuuShell",
|
|
941
|
+
classNameProp,
|
|
942
|
+
"salt-theme",
|
|
943
|
+
"salt-density-high"
|
|
944
|
+
);
|
|
945
|
+
return (
|
|
946
|
+
// ShellContext TBD
|
|
947
|
+
/* @__PURE__ */ jsxs9(ShellContextProvider, { value: void 0, children: [
|
|
948
|
+
/* @__PURE__ */ jsx13(LayoutProvider, { layout, onLayoutChange: handleLayoutChange, children: /* @__PURE__ */ jsx13(
|
|
949
|
+
DraggableLayout,
|
|
950
|
+
{
|
|
951
|
+
className,
|
|
952
|
+
"data-mode": "light",
|
|
953
|
+
ref: rootRef,
|
|
954
|
+
...htmlAttributes,
|
|
955
|
+
children: /* @__PURE__ */ jsxs9(
|
|
956
|
+
Flexbox,
|
|
957
|
+
{
|
|
958
|
+
className: "App",
|
|
959
|
+
style: { flexDirection: "column", height: "100%", width: "100%" },
|
|
960
|
+
children: [
|
|
961
|
+
/* @__PURE__ */ jsx13(
|
|
962
|
+
AppHeader,
|
|
963
|
+
{
|
|
964
|
+
layoutId: layoutId.current,
|
|
965
|
+
loginUrl,
|
|
966
|
+
user,
|
|
967
|
+
onNavigate: handleNavigate,
|
|
968
|
+
onSwitchTheme: handleSwitchTheme
|
|
969
|
+
}
|
|
970
|
+
),
|
|
971
|
+
/* @__PURE__ */ jsx13(DockLayout, { style: { flex: 1 }, children: getDrawers().concat(
|
|
972
|
+
/* @__PURE__ */ jsx13(
|
|
973
|
+
DraggableLayout,
|
|
974
|
+
{
|
|
975
|
+
dropTarget: true,
|
|
976
|
+
style: { width: "100%", height: "100%" }
|
|
977
|
+
},
|
|
978
|
+
"main-content"
|
|
979
|
+
)
|
|
980
|
+
) })
|
|
981
|
+
]
|
|
982
|
+
}
|
|
983
|
+
)
|
|
984
|
+
}
|
|
985
|
+
) }),
|
|
986
|
+
children
|
|
987
|
+
] })
|
|
988
|
+
);
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// src/theme-provider/ThemeProvider.tsx
|
|
992
|
+
import {
|
|
993
|
+
createContext as createContext2,
|
|
994
|
+
isValidElement,
|
|
995
|
+
cloneElement,
|
|
996
|
+
useContext as useContext2
|
|
997
|
+
} from "react";
|
|
998
|
+
import cx7 from "classnames";
|
|
999
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1000
|
+
var DEFAULT_DENSITY2 = "medium";
|
|
1001
|
+
var DEFAULT_THEME = "salt-theme";
|
|
1002
|
+
var DEFAULT_THEME_MODE = "light";
|
|
1003
|
+
var ThemeContext = createContext2({
|
|
1004
|
+
density: "high",
|
|
1005
|
+
theme: "salt-theme",
|
|
1006
|
+
themeMode: "light"
|
|
1007
|
+
});
|
|
1008
|
+
var createThemedChildren = (children, theme, themeMode, density) => {
|
|
1009
|
+
var _a;
|
|
1010
|
+
if (isValidElement(children)) {
|
|
1011
|
+
return cloneElement(children, {
|
|
1012
|
+
className: cx7(
|
|
1013
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1014
|
+
(_a = children.props) == null ? void 0 : _a.className,
|
|
1015
|
+
theme,
|
|
1016
|
+
`salt-density-${density}`
|
|
1017
|
+
),
|
|
1018
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1019
|
+
// @ts-expect-error
|
|
1020
|
+
"data-mode": themeMode
|
|
1021
|
+
});
|
|
1022
|
+
} else {
|
|
1023
|
+
console.warn(
|
|
1024
|
+
`
|
|
2
1025
|
ThemeProvider can only apply CSS classes for theming to a single nested child element of the ThemeProvider.
|
|
3
|
-
Wrap elements with a single container`
|
|
1026
|
+
Wrap elements with a single container`
|
|
1027
|
+
);
|
|
1028
|
+
return children;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
var ThemeProvider = ({
|
|
1032
|
+
children,
|
|
1033
|
+
theme: themeProp,
|
|
1034
|
+
themeMode: themeModeProp,
|
|
1035
|
+
density: densityProp
|
|
1036
|
+
}) => {
|
|
1037
|
+
var _a, _b, _c;
|
|
1038
|
+
const {
|
|
1039
|
+
density: inheritedDensity,
|
|
1040
|
+
themeMode: inheritedThemeMode,
|
|
1041
|
+
theme: inheritedTheme
|
|
1042
|
+
} = useContext2(ThemeContext);
|
|
1043
|
+
const density = (_a = densityProp != null ? densityProp : inheritedDensity) != null ? _a : DEFAULT_DENSITY2;
|
|
1044
|
+
const themeMode = (_b = themeModeProp != null ? themeModeProp : inheritedThemeMode) != null ? _b : DEFAULT_THEME_MODE;
|
|
1045
|
+
const theme = (_c = themeProp != null ? themeProp : inheritedTheme) != null ? _c : DEFAULT_THEME;
|
|
1046
|
+
const themedChildren = createThemedChildren(
|
|
1047
|
+
children,
|
|
1048
|
+
theme,
|
|
1049
|
+
themeMode,
|
|
1050
|
+
density
|
|
1051
|
+
);
|
|
1052
|
+
return /* @__PURE__ */ jsx14(ThemeContext.Provider, { value: { themeMode, density, theme }, children: themedChildren });
|
|
1053
|
+
};
|
|
1054
|
+
ThemeProvider.displayName = "ThemeProvider";
|
|
1055
|
+
export {
|
|
1056
|
+
ConnectionStatusIcon,
|
|
1057
|
+
DEFAULT_DENSITY2 as DEFAULT_DENSITY,
|
|
1058
|
+
DEFAULT_THEME,
|
|
1059
|
+
DEFAULT_THEME_MODE,
|
|
1060
|
+
DensitySwitch,
|
|
1061
|
+
Feature,
|
|
1062
|
+
LoginPanel,
|
|
1063
|
+
SessionEditingForm,
|
|
1064
|
+
Shell,
|
|
1065
|
+
ShellContextProvider,
|
|
1066
|
+
ThemeContext,
|
|
1067
|
+
ThemeProvider,
|
|
1068
|
+
ThemeSwitch,
|
|
1069
|
+
getAuthDetailsFromCookies,
|
|
1070
|
+
logout,
|
|
1071
|
+
redirectToLogin,
|
|
1072
|
+
useShellContext
|
|
1073
|
+
};
|
|
4
1074
|
//# sourceMappingURL=index.js.map
|