diff --git a/libraries/webcomponents-vue/package-lock.json b/libraries/webcomponents-vue/package-lock.json index cf943278a1..a4282f6b2c 100644 --- a/libraries/webcomponents-vue/package-lock.json +++ b/libraries/webcomponents-vue/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.6", "dependencies": { "@datagrok-libraries/compute-utils": "../compute-utils", - "@datagrok-libraries/dock-spawn-dg": "../webcomponents-vue", + "@datagrok-libraries/dock-spawn-dg": "../dock-spawn-dg", "@datagrok-libraries/webcomponents": "../webcomponents", "@vueuse/core": "^10.11.1", "@vueuse/rxjs": "^11.0.3", @@ -95,6 +95,26 @@ "webpack-cli": "^5.1.4" } }, + "../dock-spawn-dg": { + "name": "@datagrok-libraries/dock-spawn-dg", + "version": "0.0.2", + "license": "MIT", + "dependencies": { + "rxjs": "^6.6.7" + }, + "devDependencies": { + "@types/node": "latest", + "@types/wu": "latest", + "@typescript-eslint/eslint-plugin": "latest", + "@typescript-eslint/parser": "latest", + "eslint": "latest", + "eslint-config-google": "latest", + "ts-loader": "latest", + "typescript": "latest", + "webpack": "latest", + "webpack-cli": "latest" + } + }, "../webcomponents": { "name": "@datagrok-libraries/webcomponents", "version": "0.0.6", @@ -165,7 +185,7 @@ "link": true }, "node_modules/@datagrok-libraries/dock-spawn-dg": { - "resolved": "", + "resolved": "../dock-spawn-dg", "link": true }, "node_modules/@datagrok-libraries/webcomponents": { diff --git a/libraries/webcomponents-vue/package.json b/libraries/webcomponents-vue/package.json index 260e56fdcd..ccfcf31476 100644 --- a/libraries/webcomponents-vue/package.json +++ b/libraries/webcomponents-vue/package.json @@ -9,7 +9,7 @@ "description": "Datagrok web components vue wrappers", "dependencies": { "@datagrok-libraries/compute-utils": "../compute-utils", - "@datagrok-libraries/dock-spawn-dg": "../webcomponents-vue", + "@datagrok-libraries/dock-spawn-dg": "../dock-spawn-dg", "@datagrok-libraries/webcomponents": "../webcomponents", "@vueuse/core": "^10.11.1", "@vueuse/rxjs": "^11.0.3", diff --git a/libraries/webcomponents-vue/src/InputForm/InputForm.tsx b/libraries/webcomponents-vue/src/InputForm/InputForm.tsx index abeb159d63..d248ecab04 100644 --- a/libraries/webcomponents-vue/src/InputForm/InputForm.tsx +++ b/libraries/webcomponents-vue/src/InputForm/InputForm.tsx @@ -26,6 +26,10 @@ export const InputForm = Vue.defineComponent({ type: Object as Vue.PropType, required: true, }, + skipInit:{ + type: Boolean, + default: true, + }, validationStates: { type: Object as Vue.PropType>, }, @@ -41,14 +45,17 @@ export const InputForm = Vue.defineComponent({ }, emits: { formReplaced: (a: DG.InputForm | undefined) => a, + inputChanged: (a: DG.EventData) => a, + validationChanged: (a: boolean) => a, actionRequested: (actionUuid: string) => actionUuid, consistencyReset: (ioName: string) => ioName, }, setup(props, {emit}) { - const currentCall = Vue.computed(() => props.funcCall); + const currentCall = Vue.computed(() => Vue.markRaw(props.funcCall)); const validationStates = Vue.computed(() => props.validationStates); const consistencyStates = Vue.computed(() => props.consistencyStates); const isReadonly = Vue.computed(() => props.isReadonly); + const skipInit = Vue.computed(() => props.skipInit) const states = Vue.reactive({ meta: {} as Record, @@ -106,9 +113,13 @@ export const InputForm = Vue.defineComponent({ currentForm.value = event.detail; }; - return () => - ; + return () => + ) => emit('inputChanged', ev)} + onValidationChanged={(ev: boolean) => emit('validationChanged', ev)}> + ; }, }); diff --git a/libraries/webcomponents-vue/src/RibbonMenu/RibbonMenu.tsx b/libraries/webcomponents-vue/src/RibbonMenu/RibbonMenu.tsx index 903ad85793..cc4ab4c3ce 100644 --- a/libraries/webcomponents-vue/src/RibbonMenu/RibbonMenu.tsx +++ b/libraries/webcomponents-vue/src/RibbonMenu/RibbonMenu.tsx @@ -21,7 +21,7 @@ export const RibbonMenu = Vue.defineComponent({ setup(props, {slots}) { const elements = Vue.reactive(new Map); - const currentView = Vue.shallowRef(props.view); + const currentView = Vue.computed(() => Vue.markRaw(props.view)); Vue.watch(elements, () => { const elementsArray = [...elements.values()]; diff --git a/libraries/webcomponents-vue/src/RibbonPanel/RibbonPanel.tsx b/libraries/webcomponents-vue/src/RibbonPanel/RibbonPanel.tsx index b1a682c29e..a81db4b35e 100644 --- a/libraries/webcomponents-vue/src/RibbonPanel/RibbonPanel.tsx +++ b/libraries/webcomponents-vue/src/RibbonPanel/RibbonPanel.tsx @@ -16,23 +16,11 @@ export const RibbonPanel = Vue.defineComponent({ }>, setup(props, {slots}) { const elements = Vue.reactive(new Map); - - const currentView = Vue.shallowRef(props.view); + const currentView = Vue.computed(() => Vue.markRaw(props.view)); Vue.watch(elements, () => { - const elementsArray = [...elements.values()]; - const filteredPanels = currentView.value - .getRibbonPanels() - .filter((panel) => !panel.some((ribbonItem) => elementsArray.includes(ribbonItem.children[0] as HTMLElement))); - currentView.value.setRibbonPanels(filteredPanels); - - currentView.value.setRibbonPanels([ - currentView.value.getRibbonPanels().flat(), - elementsArray, - ]); - // Workaround for ui.comboPopup elements. It doesn't work if it is not a direct child of '.d4-ribbon-item' - elementsArray.forEach((elem) => { + elements.forEach((elem) => { const content = ((elem.firstChild?.nodeType !== Node.TEXT_NODE) ? elem.firstChild: elem.firstChild.nextSibling) as HTMLElement | null; if (content && content.tagName.toLowerCase().includes('dg-combo-popup')) { @@ -40,6 +28,20 @@ export const RibbonPanel = Vue.defineComponent({ elem.parentElement?.classList.remove('d4-ribbon-item'); } }); + + const elementsArray = [...elements.values()]; + const panels = currentView.value.getRibbonPanels(); + const existingPanelIdx = panels.findIndex((panel) => + panel.some((ribbonItem) => elementsArray.includes(ribbonItem.children[0] as HTMLElement))); + + const panel = [...elements.values()]; + + if (existingPanelIdx >= 0) + panels.splice(existingPanelIdx, 1, panel); + else + panels.push(panel) + + currentView.value.setRibbonPanels(panels); }); const addElement = (el: Element | null | any, idx: number) => { diff --git a/libraries/webcomponents-vue/src/Viewer/Viewer.tsx b/libraries/webcomponents-vue/src/Viewer/Viewer.tsx index dab3ba24c6..b007b725c3 100644 --- a/libraries/webcomponents-vue/src/Viewer/Viewer.tsx +++ b/libraries/webcomponents-vue/src/Viewer/Viewer.tsx @@ -24,7 +24,7 @@ export const Viewer = Vue.defineComponent({ viewerChanged: (v: DG.Viewer | undefined) => v, }, setup(props, {emit}) { - const currentDf = Vue.computed(() => props.dataFrame); + const currentDf = Vue.computed(() => props.dataFrame ? Vue.markRaw(props.dataFrame) : undefined); const viewerChangedCb = (event: any) => { emit('viewerChanged', event.detail); }; diff --git a/libraries/webcomponents/src/InputForm/InputForm.ts b/libraries/webcomponents/src/InputForm/InputForm.ts index 00ee1f5ddf..ece6fcea21 100644 --- a/libraries/webcomponents/src/InputForm/InputForm.ts +++ b/libraries/webcomponents/src/InputForm/InputForm.ts @@ -2,12 +2,31 @@ import * as grok from 'datagrok-api/grok'; import * as ui from 'datagrok-api/ui'; import * as DG from 'datagrok-api/dg'; +import {Subject} from 'rxjs'; +import { distinctUntilChanged, map, mapTo, startWith, switchMap, takeUntil} from 'rxjs/operators'; export class InputForm extends HTMLElement { private formInst?: DG.InputForm; + private skipDefaultInit = true; + private formChanges$ = new Subject(); + + // TODO + private destroyed$ = new Subject(); constructor() { super(); + + this.formChanges$.pipe( + switchMap((form) => form.onInputChanged), + takeUntil(this.destroyed$) + ).subscribe((ev) => this.dispatchEvent(new CustomEvent('input-changed', {detail: ev}))); + + this.formChanges$.pipe( + switchMap((form) => form.onValidationCompleted.pipe(mapTo(form), startWith(form))), + map((form) => form.isValid), + distinctUntilChanged(), + takeUntil(this.destroyed$) + ).subscribe((ev) => this.dispatchEvent(new CustomEvent('validation-changed', {detail: ev}))); } connectedCallback() { @@ -25,13 +44,22 @@ export class InputForm extends HTMLElement { this.replaceFunc(fc); } + set skipInit(val: boolean) { + this.skipDefaultInit = val; + } + + get skipInit() { + return this.skipDefaultInit; + } + private async replaceFunc(funcCall?: DG.FuncCall) { if (!funcCall) this.formInst = undefined; else { - this.formInst = await DG.InputForm.forFuncCall(funcCall, {twoWayBinding: true, skipDefaultInit: true} as any); + this.formInst = await DG.InputForm.forFuncCall(funcCall, {twoWayBinding: true, skipDefaultInit: this.skipDefaultInit} as any); this.appendChild(this.formInst.root); } + this.formChanges$.next(this.formInst); this.dispatchEvent(new CustomEvent('form-replaced', {detail: this.formInst})); } } diff --git a/libraries/webcomponents/src/Viewer/Viewer.ts b/libraries/webcomponents/src/Viewer/Viewer.ts index 8bb6314101..537705619e 100644 --- a/libraries/webcomponents/src/Viewer/Viewer.ts +++ b/libraries/webcomponents/src/Viewer/Viewer.ts @@ -14,6 +14,7 @@ export class Viewer extends HTMLElement { private viewer$ = new BehaviorSubject | undefined>(undefined); + // TODO private destroyed$ = new Subject(); constructor() { diff --git a/libraries/webcomponents/tsconfig.tsbuildinfo b/libraries/webcomponents/tsconfig.tsbuildinfo index 00452ac429..fc56a96939 100644 --- a/libraries/webcomponents/tsconfig.tsbuildinfo +++ b/libraries/webcomponents/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./index.ts"],"version":"5.7.2"} \ No newline at end of file +{"root":["./index.ts"],"version":"5.7.3"} \ No newline at end of file diff --git a/packages/Compute2/src/apps/RFVApp.tsx b/packages/Compute2/src/apps/RFVApp.tsx index 8fcac362f8..b432c989e1 100644 --- a/packages/Compute2/src/apps/RFVApp.tsx +++ b/packages/Compute2/src/apps/RFVApp.tsx @@ -45,6 +45,7 @@ export const RFVApp = Vue.defineComponent({ onUpdate:funcCall={(chosenCall) => currentFuncCall.value = chosenCall} onRunClicked={runFunc} historyEnabled={true} + autorun={isRunningOnInput.value} view={currentView.value} /> ); diff --git a/packages/Compute2/src/components/RFV/RichFunctionView.tsx b/packages/Compute2/src/components/RFV/RichFunctionView.tsx index aabf6c6bbc..c0fe47a968 100644 --- a/packages/Compute2/src/components/RFV/RichFunctionView.tsx +++ b/packages/Compute2/src/components/RFV/RichFunctionView.tsx @@ -137,16 +137,20 @@ export const RichFunctionView = Vue.defineComponent({ isReadonly: { type: Boolean, }, - historyEnabled: { + autorun: { type: Boolean, default: false, }, - viewersHook: { - type: Function as Vue.PropType, + historyEnabled: { + type: Boolean, + default: false, }, showStepNavigation: { type: Boolean, }, + viewersHook: { + type: Function as Vue.PropType, + }, view: { type: DG.ViewBase, required: true, @@ -167,7 +171,8 @@ export const RichFunctionView = Vue.defineComponent({ const {layoutDatabase} = useLayoutDb(LAYOUT_DB_NAME, STORE_NAME); const currentCall = Vue.computed(() => props.funcCall); - const currentView = Vue.shallowRef(props.view); + const currentView = Vue.computed(() => Vue.markRaw(props.view)); + const currentUuid = Vue.computed(() => props.uuid); const tabToPropertiesMap = Vue.computed(() => tabToProperties(currentCall.value.func)); @@ -381,8 +386,10 @@ export const RichFunctionView = Vue.defineComponent({ const isOutputOutdated = Vue.computed(() => props.callState?.isOutputOutdated); const isRunning = Vue.computed(() => props.callState?.isRunning); const isRunnable = Vue.computed(() => props.callState?.isRunnable); + const isReadonly = Vue.computed(() => props.isReadonly); const validationState = Vue.computed(() => props.validationStates); + const consistencyState = Vue.computed(() => props.consistencyStates); const currentFunc = Vue.computed(() => currentCall.value.func); @@ -520,7 +527,7 @@ export const RichFunctionView = Vue.defineComponent({ /> } emit('actionRequested', actionUuid)} onConsistencyReset={(ioName) => emit('consistencyReset', ioName)} - isReadonly={props.isReadonly} + onInputChanged={(ev) => console.log(ev)} + onValidationChanged={(ev) => console.log(ev)} + isReadonly={isReadonly.value} />, [[ifOverlapping, isRunning.value, 'Recalculating...']]) }
diff --git a/packages/Compute2/src/components/TreeWizard/TreeWizard.tsx b/packages/Compute2/src/components/TreeWizard/TreeWizard.tsx index d9a5f64aa3..1e288e2ac9 100644 --- a/packages/Compute2/src/components/TreeWizard/TreeWizard.tsx +++ b/packages/Compute2/src/components/TreeWizard/TreeWizard.tsx @@ -124,7 +124,7 @@ export const TreeWizard = Vue.defineComponent({ chosenStepUuid.value = nextData.state.uuid; }; - const currentView = Vue.shallowRef(props.view); + const currentView = Vue.computed(() => Vue.markRaw(props.view)); const setViewName = (name: string = '') => { if (props.view)