Compare commits

...

12 Commits

Author SHA1 Message Date
Faton Ramadani
6a655090c9 fix(frontend): add missinng migration code 2024-04-12 16:23:08 +02:00
Faton Ramadani
40a64a05d5 fix(frontend): Correctly handle undefined actions 2024-04-12 16:16:43 +02:00
Faton Ramadani
071c3cf04a Merge branch 'fix-aggrid' of https://github.com/windmill-labs/windmill into fix-aggrid 2024-04-12 16:14:17 +02:00
Faton Ramadani
c037117708 fix(frontend): Correctly handle undefined actions 2024-04-12 16:14:13 +02:00
Faton Ramadani
ade9734936 Merge branch 'main' into fix-aggrid 2024-04-12 16:09:23 +02:00
Faton Ramadani
6e87e089ac fix(frontend): Correctly handle undefined actions 2024-04-12 16:08:51 +02:00
Ruben Fiszel
62efbe11e7 chore(main): release 1.306.0 (#3542)
* chore(main): release 1.306.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2024-04-12 16:03:18 +02:00
Faton Ramadani
7eca53f9e7 fix(frontend): Correctly handle undefined actions (#3545) 2024-04-12 15:58:12 +02:00
Faton Ramadani
e9e6614486 feat(frontend): add maplock + properly display marker title (#3544) 2024-04-12 13:50:26 +02:00
Faton Ramadani
06723629ca fix(frontend): Fix AgGrid action selection (#3543) 2024-04-12 13:05:24 +02:00
Faton Ramadani
48ad095633 feat(frontend): Ag grid actions (#3535)
* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): implement cellRenderer

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): Fix actions width

* feat(frontend): Fix interactions

* feat(frontend): Fix styling

* feat(frontend): Correctly handleAgGrid EE

* feat(frontend): Fix select

* feat(frontend): Fix select

* feat(frontend): Fix how row is passed

* feat(frontend): Fix update

* feat(frontend): Correctly implement cache

* feat(frontend): Correctly implement cache

* feat(frontend): simplify how refreshActions work

* feat(frontend): improve comparaison

* feat(frontend): simplify refreshactins

* feat(frontend): clean up

* feat(frontend): clean up

* feat(frontend): add inputs output + fix row eval

* feat(frontend): add config to wrap actions

* feat(frontend): add config to wrap actions

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-04-12 11:45:24 +02:00
Faton Ramadani
0afd68d474 fix(frontend): Fix s3 uploader (#3539)
* fix(frontend): Correctly handle multiple files

* fix(frontend): Correctly handle multiple files

* fix(frontend): improve comparaison

* feat(frontend): fix drag and drop
2024-04-12 11:45:11 +02:00
38 changed files with 847 additions and 198 deletions

View File

@@ -1,5 +1,20 @@
# Changelog
## [1.306.0](https://github.com/windmill-labs/windmill/compare/v1.305.3...v1.306.0) (2024-04-12)
### Features
* **frontend:** add maplock + properly display marker title ([#3544](https://github.com/windmill-labs/windmill/issues/3544)) ([e9e6614](https://github.com/windmill-labs/windmill/commit/e9e66144860f2a390b58db1f5c0e2d09b46a777f))
* **frontend:** Ag grid actions ([#3535](https://github.com/windmill-labs/windmill/issues/3535)) ([48ad095](https://github.com/windmill-labs/windmill/commit/48ad095633f3e83f16eccc15b1de534c4804d807))
### Bug Fixes
* **frontend:** Correctly handle undefined actions ([#3545](https://github.com/windmill-labs/windmill/issues/3545)) ([7eca53f](https://github.com/windmill-labs/windmill/commit/7eca53f9e7f0640bd43fc37abbe6eb624d77430d))
* **frontend:** Fix AgGrid action selection ([#3543](https://github.com/windmill-labs/windmill/issues/3543)) ([0672362](https://github.com/windmill-labs/windmill/commit/06723629caed3e152a9161ec3f1fee10710b4971))
* **frontend:** Fix s3 uploader ([#3539](https://github.com/windmill-labs/windmill/issues/3539)) ([0afd68d](https://github.com/windmill-labs/windmill/commit/0afd68d474b65ce1d1026bdabb081d775e0dcaa3))
## [1.305.3](https://github.com/windmill-labs/windmill/compare/v1.305.2...v1.305.3) (2024-04-12)

40
backend/Cargo.lock generated
View File

@@ -100,9 +100,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
version = "0.2.16"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "android-tzdata"
@@ -9707,7 +9707,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"axum",
@@ -9744,7 +9744,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"argon2",
@@ -9821,7 +9821,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"base64 0.21.7",
"chrono",
@@ -9839,7 +9839,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"chrono",
"serde",
@@ -9852,7 +9852,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"aws-config",
@@ -9888,7 +9888,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"regex",
"rsmq_async",
@@ -9903,7 +9903,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"serde",
"serde_json",
@@ -9911,7 +9911,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9922,7 +9922,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"gosyn",
@@ -9934,7 +9934,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9945,7 +9945,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"itertools 0.12.1",
@@ -9956,7 +9956,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -9973,7 +9973,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9984,7 +9984,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10002,7 +10002,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"getrandom 0.2.14",
@@ -10020,7 +10020,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10053,7 +10053,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"serde",
"wasm-bindgen",
@@ -10064,7 +10064,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.305.3"
version = "1.306.0"
dependencies = [
"anyhow",
"async-recursion",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.305.3"
version = "1.306.0"
authors.workspace = true
edition.workspace = true
@@ -24,7 +24,7 @@ members = [
]
[workspace.package]
version = "1.305.3"
version = "1.306.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.305.3
version: 1.306.0
title: Windmill API
contact:

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.305.3";
export const VERSION = "v1.306.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -31,7 +31,7 @@ addEventListener("error", (event) => {
}
});
export const VERSION = "v1.305.3";
export const VERSION = "v1.306.0";
let command: any = new Command()
.name("wmill")

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.305.3",
"version": "1.306.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.305.3",
"version": "1.306.0",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.305.3",
"version": "1.306.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -117,3 +117,24 @@
}
}
}
.grid-cell-centered {
display: flex;
align-items: center;
justify-content: center;
}
.grid-cell-centered .svelte-select {
height: 32px !important;
}
.grid-cell-centered .selected-item {
margin-top: -4px;
}
.ol-overlaycontainer-stopevent {
@apply flex flex-col justify-start items-end;
}
.ol-control button {
@apply w-7 h-7 center-center bg-surface border text-secondary
rounded mt-1 mr-1 shadow duration-200 hover:bg-surface-hover focus:bg-surface-hover;
}

View File

@@ -2,7 +2,6 @@
import { getContext } from 'svelte'
import { initCss } from '../../utils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
import { useGeographic } from 'ol/proj'
@@ -10,10 +9,12 @@
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer'
import { Point } from 'ol/geom'
import { defaults as defaultControls } from 'ol/control'
import { findGridItem, initOutput } from '../../editor/appUtils'
import { findGridItem, initConfig, initOutput } from '../../editor/appUtils'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import { Style, Circle, Fill, Stroke, Text } from 'ol/style'
interface Marker {
lon: number
lat: number
@@ -32,10 +33,16 @@
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'mapcomponent'> | undefined = undefined
export let render: boolean
export let extraKey: string | undefined = undefined
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
getContext<AppViewerContext>('AppViewerContext')
const resolvedConfig = initConfig(
components['mapcomponent'].initialData.configuration,
configuration
)
let outputs = initOutput($worldStore, id, {
mapRegion: {
topLeft: { lat: 0, lon: 0 },
@@ -46,21 +53,15 @@
let map: Map | undefined = undefined
let mapElement: HTMLDivElement | undefined = undefined
let longitude: number | undefined = undefined
let latitude: number | undefined = undefined
let zoom: number | undefined = undefined
// If string, it's a JSON file read as text
let markers: Marker[] | string | undefined = undefined
$: if (map && longitude && latitude) {
map.getView().setCenter([longitude, latitude])
$: if (map && resolvedConfig.longitude && resolvedConfig.latitude) {
map.getView().setCenter([resolvedConfig.longitude, resolvedConfig.latitude])
}
$: if (map && zoom) {
map.getView().setZoom(zoom)
$: if (map && resolvedConfig.zoom) {
map.getView().setZoom(resolvedConfig.zoom)
}
$: if (map && markers) {
$: if (map && resolvedConfig.markers) {
updateMarkers()
}
@@ -81,11 +82,11 @@
function getMarkerArray(): Marker[] | undefined {
let array: Marker[] | undefined = undefined
try {
if (typeof markers === 'string') {
const json = JSON.parse(markers)
if (typeof resolvedConfig.markers === 'string') {
const json = JSON.parse(resolvedConfig.markers)
array = Array.isArray(json) ? json : [json]
} else {
array = markers
array = resolvedConfig.markers
}
return array?.filter((m) => !isNaN(+m.lat) && !isNaN(+m.lon))
} catch (error) {
@@ -97,28 +98,41 @@
function createMarkerLayers() {
const markerArray = getMarkerArray()
return markerArray?.map((m) => {
const feature = new Feature({
geometry: new Point([+m.lon, +m.lat]),
name: m.title
})
feature.setStyle(
new Style({
image: new Circle({
radius: m.radius ?? 7,
fill: new Fill({ color: m.color ?? '#dc2626' }),
stroke: new Stroke({ width: m.strokeWidth ?? 3, color: m.strokeColor ?? '#fca5a5' })
}),
text: new Text({
text: m.title,
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff',
width: 2
}),
offsetY: -15
})
})
)
return new VectorLayer({
properties: {
name: LAYER_NAME.MARKER
},
source: new VectorSource({
features: [
new Feature({
geometry: new Point([+m.lon, +m.lat]),
name: m.title
})
]
}),
style: {
'circle-radius': m.radius ?? 7,
'circle-fill-color': m.color ?? '#dc2626',
'circle-stroke-width': m.strokeWidth ?? 3,
'circle-stroke-color': m.strokeColor ?? '#fca5a5'
}
features: [feature]
})
})
})
}
function updateMarkers() {
const layers = getLayersByName('MARKER')
if (layers?.length) {
@@ -143,8 +157,8 @@
...(createMarkerLayers() || [])
],
view: new View({
center: [longitude ?? 0, latitude ?? 0],
zoom: zoom ?? 2
center: [resolvedConfig.longitude ?? 0, resolvedConfig.latitude ?? 0],
zoom: resolvedConfig.zoom ?? 2
}),
controls: defaultControls({
attribution: false
@@ -153,10 +167,28 @@
updateRegionOutput()
}
let previousLock: boolean | undefined = undefined
function updateInteractions(map: Map) {
if (previousLock === resolvedConfig.lock) {
return
}
previousLock = resolvedConfig.lock
map.getInteractions().forEach((i) => {
i.setActive(!resolvedConfig.lock)
})
map.changed()
}
$: map && resolvedConfig && updateInteractions(map)
let css = initCss($app.css?.mapcomponent, customCss)
function updateRegionOutput() {
if (map) {
if (map && !resolvedConfig.lock) {
let extent = map.getView().calculateExtent(map.getSize())
const [left, bottom, right, top] = extent
@@ -197,10 +229,16 @@
}
</script>
<InputValue key="longitude" {id} input={configuration.longitude} bind:value={longitude} />
<InputValue key="latitude" {id} input={configuration.latitude} bind:value={latitude} />
<InputValue key="zoom" {id} input={configuration.zoom} bind:value={zoom} />
<InputValue key="markers" {id} input={configuration.markers} bind:value={markers} />
{#each Object.entries(components['mapcomponent'].initialData.configuration) as [key, initialConfig] (key)}
<ResolveConfig
{id}
{extraKey}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
{initialConfig}
/>
{/each}
<InitializeComponent {id} />
@@ -236,13 +274,3 @@
{/if}
</div>
{/if}
<style global lang="postcss">
.ol-overlaycontainer-stopevent {
@apply flex flex-col justify-start items-end;
}
.ol-control button {
@apply w-7 h-7 center-center bg-surface border text-secondary
rounded mt-1 mr-1 shadow duration-200 hover:bg-surface-hover focus:bg-surface-hover;
}
</style>

View File

@@ -1,23 +1,33 @@
<script lang="ts">
import { GridApi, createGrid } from 'ag-grid-community'
import { isObject, sendUserToast } from '$lib/utils'
import { getContext } from 'svelte'
import { SvelteComponent, getContext, onDestroy } from 'svelte'
import type { AppInput } from '../../../inputType'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
ListContext,
ListInputs,
RichConfigurations
} from '../../../types'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
import { initConfig, initOutput } from '$lib/components/apps/editor/appUtils'
import { components } from '$lib/components/apps/editor/component'
import { components, type TableAction } from '$lib/components/apps/editor/component'
import Alert from '$lib/components/common/alert/Alert.svelte'
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
import { deepEqual } from 'fast-equals'
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css'
import { Loader2 } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import { initCss } from '$lib/components/apps/utils'
import ResolveStyle from '../../helpers/ResolveStyle.svelte'
import AppAggridTableActions from './AppAggridTableActions.svelte'
// import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css'
export let id: string
@@ -26,9 +36,14 @@
export let initializing: boolean | undefined = undefined
export let render: boolean
export let customCss: ComponentCustomCSS<'aggridcomponent'> | undefined = undefined
export let actions: TableAction[] | undefined = undefined
const { app, worldStore, selectedComponent, componentControl, darkMode } =
getContext<AppViewerContext>('AppViewerContext')
const context = getContext<AppViewerContext>('AppViewerContext')
const iterContext = getContext<ListContext>('ListWrapperContext')
const listInputs: ListInputs | undefined = getContext<ListInputs>('ListInputs')
const { app, worldStore, selectedComponent, componentControl, darkMode } = context
const rowHeights = {
normal: 40,
@@ -78,6 +93,7 @@
page: 0,
newChange: { row: 0, column: '', value: undefined },
ready: undefined as boolean | undefined,
inputs: {},
filters: {},
displayedRowCount: 0
})
@@ -93,12 +109,21 @@
selectedRowIndex = rowIndex
outputs?.selectedRowIndex.set(rowIndex)
}
if (!deepEqual(outputs?.selectedRow?.peak(), data)) {
outputs?.selectedRow.set(data)
}
if (iterContext && listInputs) {
listInputs.set(id, { selectedRow: data, selectedRowIndex: selectedRowIndex })
}
}
}
onDestroy(() => {
listInputs?.remove(id)
})
function toggleRows(rows: any[]) {
if (rows.length === 0) {
outputs?.selectedRows.set([])
@@ -136,25 +161,122 @@
}
let extraConfig = resolvedConfig.extraConfig
let api: GridApi<any> | undefined = undefined
let eGui: HTMLDivElement
let state: any = undefined
$: loaded && eGui && mountGrid()
let state: any = undefined
const cachedDivs = new Map<
number,
{
div: HTMLDivElement
svelteComponent: SvelteComponent
actions: TableAction[]
}
>()
function refreshActions(actions: TableAction[]) {
if (!deepEqual(actions, lastActions)) {
lastActions = [...actions]
cachedDivs.forEach((cachedDiv) => {
cachedDiv.svelteComponent.$destroy()
cachedDiv.div.remove()
})
cachedDivs.clear()
updateOptions()
}
}
let lastActions: TableAction[] | undefined = undefined
$: actions && refreshActions(actions)
let inputs = {}
function actionRenderer(params) {
const { rowIndex, data: row } = params
if (rowIndex === -1 || actions == undefined || actions?.length == 0) {
return null
}
if (cachedDivs.has(rowIndex)) {
return cachedDivs.get(rowIndex)?.div
}
const div = document.createElement('div')
div.classList.add('flex', 'flex-row', 'items-center', 'w-full', 'h-full')
const svelteComponent = new AppAggridTableActions({
target: div,
props: {
id: id,
actions,
rowIndex,
row,
render,
wrapActions: resolvedConfig.wrapActions,
onSet: (id, value) => {
if (!inputs[id]) {
inputs[id] = { [rowIndex]: value }
} else {
inputs[id] = { ...inputs[id], [rowIndex]: value }
}
outputs?.inputs.set(inputs, true)
},
onRemove: (id) => {
if (inputs?.[id] == undefined) {
return
}
delete inputs[id][rowIndex]
inputs[id] = { ...inputs[id] }
if (Object.keys(inputs?.[id] ?? {}).length == 0) {
delete inputs[id]
inputs = { ...inputs }
}
outputs?.inputs.set(inputs, true)
}
},
context: new Map([['AppViewerContext', context]])
})
cachedDivs.set(rowIndex, {
div,
actions,
svelteComponent
})
return div
}
function mountGrid() {
if (eGui) {
try {
let columnDefs =
Array.isArray(resolvedConfig?.columnDefs) && resolvedConfig.columnDefs.every(isObject)
? [...resolvedConfig?.columnDefs] // Clone to avoid direct mutation
: []
// Add the action column if actions are defined
if (actions && actions.length > 0) {
columnDefs.push({
headerName: 'Actions',
cellRenderer: actionRenderer,
autoHeight: true,
cellStyle: { textAlign: 'center' },
cellClass: 'grid-cell-centered',
...(!resolvedConfig?.wrapActions ? { minWidth: 130 * actions?.length } : {})
})
}
createGrid(
eGui,
{
rowData: value,
columnDefs:
Array.isArray(resolvedConfig?.columnDefs) && resolvedConfig.columnDefs.every(isObject)
? resolvedConfig?.columnDefs
: [],
columnDefs: columnDefs,
pagination: resolvedConfig?.pagination,
paginationAutoPageSize: resolvedConfig?.pagination,
defaultColDef: {
@@ -219,7 +341,6 @@
}
$: resolvedConfig && updateOptions()
$: value && updateValue()
$: if (!deepEqual(extraConfig, resolvedConfig.extraConfig)) {
@@ -258,12 +379,26 @@
function updateOptions() {
try {
const columnDefs =
Array.isArray(resolvedConfig?.columnDefs) && resolvedConfig.columnDefs.every(isObject)
? [...resolvedConfig?.columnDefs] // Clone to avoid direct mutation
: []
// Add the action column if actions are defined
if (actions && actions.length > 0) {
columnDefs.push({
headerName: 'Actions',
cellRenderer: actionRenderer,
autoHeight: true,
cellStyle: { textAlign: 'center' },
cellClass: 'grid-cell-centered',
...(!resolvedConfig?.wrapActions ? { minWidth: 130 * actions?.length } : {})
})
}
api?.updateGridOptions({
rowData: value,
columnDefs:
Array.isArray(resolvedConfig?.columnDefs) && resolvedConfig.columnDefs.every(isObject)
? resolvedConfig?.columnDefs
: undefined,
columnDefs: columnDefs,
pagination: resolvedConfig?.pagination,
paginationAutoPageSize: resolvedConfig?.pagination,
defaultColDef: {

View File

@@ -0,0 +1,240 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import type { AppViewerContext } from '../../../types'
import type { TableAction } from '$lib/components/apps/editor/component'
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css'
import AppButton from '../../buttons/AppButton.svelte'
import { AppCheckbox, AppSelect } from '../..'
import { twMerge } from 'tailwind-merge'
import { Popup } from '$lib/components/common'
import { Plug2 } from 'lucide-svelte'
import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte'
import { connectOutput } from '$lib/components/apps/editor/appUtils'
import RowWrapper from '../../layout/RowWrapper.svelte'
export let id: string
export let render: boolean
export let actions: TableAction[] = []
export let rowIndex: number
export let row: { original: Record<string, any> }
export let onSet: (id: string, value: any) => void
export let onRemove: (id: string) => void
export let wrapActions: boolean | undefined = undefined
const dispatch = createEventDispatcher()
const { selectedComponent, hoverStore, mode, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
</script>
<RowWrapper value={row} index={rowIndex} {onSet} {onRemove}>
<div
class={twMerge(
'flex flex-row justify-center items-center gap-4 h-full px-4 py-1',
!wrapActions ? 'flex-wrap' : ''
)}
>
{#each actions as action, actionIndex}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore missing-declaration -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
on:mouseover|stopPropagation={() => {
if (action.id !== $hoverStore) {
$hoverStore = action.id
}
}}
on:mouseout|stopPropagation={() => {
if ($hoverStore !== undefined) {
$hoverStore = undefined
}
}}
on:pointerdown|stopPropagation={() => {
$selectedComponent = [action.id]
}}
class={twMerge(
($selectedComponent?.includes(action.id) || $hoverStore === action.id) &&
$mode !== 'preview'
? 'outline outline-indigo-500 outline-1 outline-offset-1 relative z-50'
: 'relative'
)}
>
{#if $mode !== 'preview'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
title={`Id: ${action.id}`}
class={twMerge(
'px-2 text-2xs font-bold absolute shadow -top-2 -left-4 border z-50 rounded-sm w-8 !h-5 flex justify-center items-center',
'bg-indigo-500/90 border-indigo-600 text-white',
$selectedComponent?.includes(action.id) || $hoverStore === action.id
? 'opacity-100'
: 'opacity-0'
)}
on:click|stopPropagation={() => {
$selectedComponent = [action.id]
}}
>
{action.id}
</div>
{#if $connectingInput.opened}
<div class="absolute z-50 left-8 -top-[10px]">
<Popup
floatingConfig={{
strategy: 'absolute',
placement: 'bottom-start'
}}
>
<svelte:fragment slot="button">
<button
class="bg-red-500/70 border border-red-600 px-1 py-0.5"
title="Outputs"
aria-label="Open output"><Plug2 size={12} /></button
>
</svelte:fragment>
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) =>
connectOutput(connectingInput, 'buttoncomponent', action.id, detail)}
componentId={action.id}
/>
</Popup>
</div>
{/if}
{/if}
{#if rowIndex === 0}
{@const controls = {
left: () => {
if (actionIndex === 0) {
$selectedComponent = [id]
return true
} else if (actionIndex > 0) {
$selectedComponent = [actions[actionIndex - 1].id]
return true
}
return false
},
right: () => {
if (actionIndex === actions.length - 1) {
return id
} else if (actionIndex < actions.length - 1) {
$selectedComponent = [actions[actionIndex + 1].id]
return true
}
return false
}
}}
{#if action.type == 'buttoncomponent'}
<AppButton
noInitialize
extraKey={'idx' + rowIndex}
{render}
noWFull
preclickAction={async () => {
dispatch('toggleRow')
}}
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
extraQueryParams={{
row
}}
componentInput={action.componentInput}
verticalAlignment="center"
{controls}
/>
{:else if action.type == 'checkboxcomponent'}
<AppCheckbox
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
onToggle={action.onToggle}
preclickAction={async () => {
dispatch('toggleRow')
}}
verticalAlignment="center"
{controls}
/>
{:else if action.type == 'selectcomponent'}
<div class="w-40">
<AppSelect
noDefault
noInitialize
extraKey={'idx' + rowIndex}
{render}
--font-size="10px"
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
onSelect={action.onSelect}
preclickAction={async () => {
dispatch('toggleRow')
}}
{controls}
/>
</div>
{/if}
{:else if action.type == 'buttoncomponent'}
<AppButton
noInitialize
extraKey={'idx' + rowIndex}
{render}
preclickAction={async () => {
dispatch('toggleRow')
}}
noWFull
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
extraQueryParams={{
row
}}
componentInput={action.componentInput}
/>
{:else if action.type == 'checkboxcomponent'}
<AppCheckbox
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
onToggle={action.onToggle}
preclickAction={async () => {
dispatch('toggleRow')
}}
/>
{:else if action.type == 'selectcomponent'}
<div class="w-40">
<AppSelect
noDefault
noInitialize
extraKey={'idx' + rowIndex}
{render}
--font-size="10px"
id={action.id}
customCss={action.customCss}
configuration={action.configuration}
recomputeIds={action.recomputeIds}
onSelect={action.onSelect}
preclickAction={async () => {
dispatch('toggleRow')
}}
/>
</div>
{/if}
</div>
{/each}
</div>
</RowWrapper>

View File

@@ -9,6 +9,7 @@
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css'
import { Loader2 } from 'lucide-svelte'
import type { TableAction } from '$lib/components/apps/editor/component'
export let id: string
export let license: string
@@ -17,6 +18,7 @@
export let initializing: boolean | undefined = undefined
export let render: boolean
export let customCss: ComponentCustomCSS<'aggridcomponent'> | undefined = undefined
export let actions: TableAction[] = []
let loaded = false
async function load() {
@@ -31,7 +33,15 @@
</script>
{#if loaded}
<AppAggridTable {id} {componentInput} {configuration} {initializing} {render} {customCss} />
<AppAggridTable
{id}
{componentInput}
{configuration}
{initializing}
{render}
{customCss}
{actions}
/>
{:else}
<Loader2 class="animate-spin" />
{/if}

View File

@@ -651,7 +651,6 @@
<AppSelect
noDefault
noInitialize
--font-size="10px"
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}

View File

@@ -135,6 +135,7 @@
textStyle={css?.text?.style ?? ''}
on:change={(e) => {
preclickAction?.()
value = e.detail
if (recomputeIds) {
recomputeIds.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb()))

View File

@@ -184,6 +184,12 @@
if (c.type === 'tablecomponent') {
r.push(...c.actionButtons.map((x) => ({ input: x.componentInput, id: x.id })))
}
if (
(c.type === 'aggridcomponent' || c.type === 'aggridcomponentee') &&
Array.isArray(c.actions)
) {
r.push(...c.actions.map((x) => ({ input: x.componentInput, id: x.id })))
}
if (c.type === 'menucomponent') {
r.push(...c.menuItems.map((x) => ({ input: x.componentInput, id: x.id })))
}

View File

@@ -29,6 +29,17 @@
{/each}
</div>
</div>
{:else if gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee'}
<div>
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
<div class="ml-4 mt-4">
{#if Array.isArray(gridItem.data.actions)}
{#each gridItem.data.actions as actionButton (actionButton.id)}
<AppComponentInput bind:component={actionButton.data} {resourceOnly} />
{/each}
{/if}
</div>
</div>
{:else}
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
{/if}

View File

@@ -33,6 +33,13 @@
return { item: { data: tableAction, id: tableAction.id }, parent: x.data.id }
}
}
} else if (x?.data?.type === 'aggridcomponent' || x?.data?.type === 'aggridcomponentee') {
if (x?.data?.actions) {
const tableAction = x.data.actions.find((x) => x.id === id)
if (tableAction) {
return { item: { data: tableAction, id: tableAction.id }, parent: x.data.id }
}
}
}
})
.find((x) => x)
@@ -81,6 +88,15 @@
(x) => x.id !== tableActionSettings?.item.id
)
}
if (
(parent.data.type === 'aggridcomponent' || parent.data.type === 'aggridcomponentee') &&
Array.isArray(parent.data.actions)
) {
parent.data.actions = parent.data.actions.filter(
(x) => x.id !== tableActionSettings?.item.id
)
}
}
}}
/>

View File

@@ -72,6 +72,11 @@ export function dfs(
return [item.id, id]
} else if (item.data.type == 'menucomponent' && item.data.menuItems.find((x) => id == x.id)) {
return [item.id, id]
} else if (
(item.data.type == 'aggridcomponent' || item.data.type == 'aggridcomponentee') &&
item.data.actions?.find((x) => id == x.id)
) {
return [item.id, id]
} else {
for (let i = 0; i < (item.data.numberOfSubgrids ?? 0); i++) {
const res = dfs(subgrids[`${item.id}-${i}`], id, subgrids)
@@ -165,6 +170,12 @@ export function allsubIds(app: App, parentId: string): string[] {
if (item.data.type === 'tablecomponent') {
subIds.push(...item.data.actionButtons?.map((x) => x.id))
}
if (
item.data.type === 'aggridcomponent' ||
(item.data.type === 'aggridcomponentee' && Array.isArray(item.data.actions))
) {
subIds.push(...item.data.actions?.map((x) => x.id))
}
if (item.data.type === 'menucomponent') {
subIds.push(...item.data.menuItems?.map((x) => x.id))
}
@@ -182,6 +193,8 @@ export function getNextGridItemId(app: App): string {
const allIds = allItems(app.grid, app.subgrids).flatMap((x) => {
if (x.data.type === 'tablecomponent') {
return [x.id, ...x.data.actionButtons.map((x) => x.id)]
} else if (x.data.type === 'aggridcomponent' || x.data.type === 'aggridcomponentee') {
return [x.id, ...x.data.actions.map((x) => x.id)]
} else if (x.data.type === 'menucomponent') {
return [x.id, ...x.data.menuItems.map((x) => x.id)]
} else {
@@ -329,6 +342,7 @@ export function appComponentFromType<T extends keyof typeof components>(
customCss: ccomponents[type].customCss as any,
recomputeIds: init.recomputeIds ? [] : undefined,
actionButtons: init.actionButtons ? [] : undefined,
actions: [],
menuItems: init.menuItems ? [] : undefined,
numberOfSubgrids: init.numberOfSubgrids,
horizontalAlignment: init.horizontalAlignment,
@@ -419,6 +433,16 @@ export function copyComponent(
id: x.id.replace(`${item.id}_`, `${id}_`)
})) ?? []
}
} else if (item.data.type === 'aggridcomponent' || item.data.type === 'aggridcomponentee') {
return {
...item.data,
id,
actionButtons:
item.data.actions.map((x) => ({
...x,
id: x.id.replace(`${item.id}_`, `${id}_`)
})) ?? []
}
} else if (item.data.type === 'menucomponent') {
return {
...item.data,
@@ -462,6 +486,10 @@ export function getAllSubgridsAndComponentIds(
components.push(...component.actionButtons?.map((x) => x.id))
}
if (component.type === 'aggridcomponent' || component.type === 'aggridcomponentee') {
components.push(...component.actions?.map((x) => x.id))
}
if (component.type === 'menucomponent') {
components.push(...component.menuItems?.map((x) => x.id))
}
@@ -489,6 +517,11 @@ export function getAllGridItems(app: App): GridItem[] {
.map((x) => {
if (x?.data?.type === 'tablecomponent') {
return [x, ...x?.data?.actionButtons?.map((x) => ({ data: x, id: x.id }))]
} else if (
(x?.data?.type === 'aggridcomponent' || x?.data?.type === 'aggridcomponentee') &&
Array.isArray(x?.data?.actions)
) {
return [x, ...x?.data?.actions?.map((x) => ({ data: x, id: x.id }))]
} else if (x?.data?.type === 'menucomponent') {
return [x, ...x?.data?.menuItems?.map((x) => ({ data: x, id: x.id }))]
}

View File

@@ -351,6 +351,7 @@
bind:initializing
componentInput={component.componentInput}
customCss={component.customCss}
actions={component.actions ?? []}
{render}
/>
{:else if component.type === 'aggridcomponentee'}
@@ -361,6 +362,7 @@
bind:initializing
componentInput={component.componentInput}
customCss={component.customCss}
actions={component.actions ?? []}
{render}
/>
{:else if component.type === 'textcomponent'}

View File

@@ -62,6 +62,11 @@
if (left.data.type === 'tablecomponent' && left.data.actionButtons.length >= 1) {
$selectedComponent = [left.data.actionButtons[left.data.actionButtons.length - 1].id]
} else if (
(left.data.type === 'aggridcomponent' || left.data.type === 'aggridcomponentee') &&
left.data.actions.length >= 1
) {
$selectedComponent = [left.data.actions[left.data.actions.length - 1].id]
} else {
$selectedComponent = [left.id]
}

View File

@@ -137,12 +137,19 @@ export type AgChartsComponentEe = BaseComponent<'agchartscomponentee'> & {
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
export type TableAction = BaseAppComponent &
(ButtonComponent | CheckboxComponent | SelectComponent) &
GridItem
export type TableComponent = BaseComponent<'tablecomponent'> & {
actionButtons: (BaseAppComponent & ButtonComponent & GridItem)[]
actionButtons: TableAction[]
}
export type AggridComponent = BaseComponent<'aggridcomponent'> & {
actions: TableAction[]
}
export type AggridComponent = BaseComponent<'aggridcomponent'>
export type AggridComponentEe = BaseComponent<'aggridcomponentee'> & {
license: string
actions: TableAction[]
}
export type DisplayComponent = BaseComponent<'displaycomponent'>
export type LogComponent = BaseComponent<'logcomponent'>
@@ -348,6 +355,7 @@ export interface InitialAppComponent extends Partial<Aligned> {
numberOfSubgrids?: number
recomputeIds?: boolean
actionButtons?: boolean
actions?: boolean
menuItems?: boolean
tabs?: string[]
panes?: number[]
@@ -674,6 +682,13 @@ const aggridcomponentconst = {
value: 'normal',
selectOptions: ['normal', 'compact', 'comfortable'],
tooltip: 'Change the row height'
},
wrapActions: {
type: 'static',
fieldType: 'boolean',
value: false,
tooltip:
'When true, actions will wrap to the next line. Otherwise, the column will grow to fit the actions.'
}
},
componentInput: {
@@ -2843,7 +2858,13 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
title: 'London'
}
]
} as StaticAppInput
} as StaticAppInput,
lock: {
fieldType: 'boolean',
type: 'static',
value: false,
tooltip: 'Lock the map to prevent user interaction'
}
}
}
},

View File

@@ -90,6 +90,14 @@
}
}
if (item?.data.type == 'aggridcomponent' || item?.data.type == 'aggridcomponentee') {
for (let c of item.data.actions) {
let old = c.id
c.id = c.id.replace(id + '_', newId + '_')
propagateRename(old, c.id)
}
}
if (item?.data.type === 'menucomponent') {
for (let c of item.data.menuItems) {
let old = c.id
@@ -111,6 +119,12 @@
}
}
if (data.type == 'aggridcomponent' || data.type == 'aggridcomponentee') {
for (let c of data.actions) {
renameComponent(from, to, c)
}
}
if (data.type === 'menucomponent') {
for (let c of data.menuItems) {
renameComponent(from, to, c)

View File

@@ -13,4 +13,11 @@
{/each}
</div>
{/if}
{#if (gridItem.data.type === 'aggridcomponent' || gridItem.data.type === 'aggridcomponentee') && gridItem.data.actions?.length > 0}
<div class="ml-2 border-l">
{#each gridItem.data.actions as action, index}
<Output id={action.id} first={index === 0} label="Table action" />
{/each}
</div>
{/if}
</div>

View File

@@ -34,6 +34,20 @@
{/each}
{/if}
{#if (gridItem?.data?.type === 'aggridcomponent' || gridItem?.data?.type === 'aggridcomponentee') && Array.isArray(gridItem.data.actions)}
{#each gridItem.data.actions as actionButton, index (index)}
{#if actionButton?.id === $selectedComponentInEditor || actionButton?.id + '_transformer' === $selectedComponentInEditor}
<InlineScriptEditorPanel
on:createScriptFromInlineScript
componentType={actionButton.type}
id={actionButton.id}
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
bind:componentInput={actionButton.componentInput}
/>
{/if}
{/each}
{/if}
{#if gridItem?.data?.type === 'menucomponent'}
{#each gridItem.data.menuItems as actionButton, index (index)}
{#if actionButton?.id === $selectedComponentInEditor || actionButton?.id + '_transformer' === $selectedComponentInEditor}

View File

@@ -66,6 +66,20 @@ function processGridItemRunnable(gridItem: GridItem, list: AppScriptsList): AppS
})
}
if (component.type === 'aggridcomponent' || component.type === 'aggridcomponentee') {
component.actions?.forEach((actionButton) => {
if (actionButton.componentInput?.type !== 'runnable') {
return
}
processRunnable(
actionButton.componentInput.runnable,
actionButton.componentInput.transformer,
actionButton.id,
list
)
})
}
if (component.type === 'menucomponent') {
component.menuItems?.forEach((menuItem) => {
if (menuItem.componentInput?.type !== 'runnable') {

View File

@@ -335,6 +335,7 @@
/>
{:else if componentSettings.item.data.type === 'aggridcomponentee'}
<GridAgGridLicenseKey bind:license={componentSettings.item.data.license} />
<TableActions id={component.id} bind:components={componentSettings.item.data.actions} />
{:else if componentSettings.item.data.type === 'agchartscomponentee'}
<GridAgChartsLicenseKe bind:license={componentSettings.item.data.license} />
{:else if componentSettings.item.data.type === 'steppercomponent'}
@@ -364,6 +365,8 @@
bind:panes={componentSettings.item.data.panes}
bind:component={componentSettings.item.data}
/>
{:else if componentSettings.item.data.type === 'aggridcomponent'}
<TableActions id={component.id} bind:components={componentSettings.item.data.actions} />
{:else if componentSettings.item.data.type === 'tablecomponent' && Array.isArray(componentSettings.item.data.actionButtons)}
<TableActions id={component.id} bind:components={componentSettings.item.data.actionButtons} />
{:else if componentSettings.item.data.type === 'menucomponent' && Array.isArray(componentSettings.item.data.menuItems)}

View File

@@ -3,21 +3,34 @@
import Button from '$lib/components/common/button/Button.svelte'
import { getNextId } from '$lib/components/flows/idUtils'
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import { getContext, onMount } from 'svelte'
import type { AppViewerContext, BaseAppComponent } from '../../types'
import { appComponentFromType } from '../appUtils'
import type { ButtonComponent, CheckboxComponent, SelectComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
import { Inspect, List, ToggleRightIcon, Trash } from 'lucide-svelte'
export let components: (BaseAppComponent &
(ButtonComponent | CheckboxComponent | SelectComponent))[]
export let components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
| undefined
// Migration code:
onMount(() => {
if (components === undefined) {
components = []
}
})
export let id: string
const { selectedComponent, app, errorByComponent } =
getContext<AppViewerContext>('AppViewerContext')
function addComponent(typ: 'buttoncomponent' | 'checkboxcomponent' | 'selectcomponent') {
if (!components) {
return
}
const actionId = getNextId(components.map((x) => x.id.split('_')[1]))
const newComponent = {
@@ -29,6 +42,9 @@
}
function deleteComponent(cid: string) {
if (!components) {
return
}
components = components.filter((x) => x.id !== cid)
delete $errorByComponent[cid]
@@ -38,77 +54,79 @@
}
</script>
<PanelSection title={`Table Actions`}>
{#if components.length == 0}
<span class="text-xs text-tertiary">No action buttons</span>
{/if}
{#each components as component}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={classNames(
'w-full text-xs font-bold gap-1 truncate py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
'bg-surface hover:bg-surface-hover focus:border-primary text-secondary',
$selectedComponent?.includes(component.id) ? 'outline outline-blue-500 bg-red-400' : ''
)}
on:click={() => {
$selectedComponent = [component.id]
}}
on:keypress
>
<Badge color="dark-indigo">
{component.id}
</Badge>
{#if components}
<PanelSection title={`Table Actions`}>
{#if components.length == 0}
<span class="text-xs text-tertiary">No action buttons</span>
{/if}
{#each components as component}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={classNames(
'w-full text-xs font-bold gap-1 truncate py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
'bg-surface hover:bg-surface-hover focus:border-primary text-secondary',
$selectedComponent?.includes(component.id) ? 'outline outline-blue-500 bg-red-400' : ''
)}
on:click={() => {
$selectedComponent = [component.id]
}}
on:keypress
>
<Badge color="dark-indigo">
{component.id}
</Badge>
<div>
{#if component.type == 'buttoncomponent'}
Button
{:else if component.type == 'selectcomponent'}
Select
{:else if component.type == 'checkboxcomponent'}
Toggle
{/if}
</div>
<div>
<Button
variant="border"
color="red"
on:click={() => deleteComponent(component.id)}
startIcon={{ icon: Trash }}
iconOnly
/>
<div>
{#if component.type == 'buttoncomponent'}
Button
{:else if component.type == 'selectcomponent'}
Select
{:else if component.type == 'checkboxcomponent'}
Toggle
{/if}
</div>
<div>
<Button
variant="border"
color="red"
on:click={() => deleteComponent(component.id)}
startIcon={{ icon: Trash }}
iconOnly
/>
</div>
</div>
{/each}
<div class="w-full flex gap-2">
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('buttoncomponent')}
title="Add Button"
>
+ <Inspect size={14} />
</Button>
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('checkboxcomponent')}
title="Add Toggle"
>
+ <ToggleRightIcon size={14} />
</Button>
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('selectcomponent')}
title="Add Select"
>
+ <List size={14} />
</Button>
</div>
{/each}
<div class="w-full flex gap-2">
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('buttoncomponent')}
title="Add Button"
>
+ <Inspect size={14} />
</Button>
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('checkboxcomponent')}
title="Add Toggle"
>
+ <ToggleRightIcon size={14} />
</Button>
<Button
btnClasses="gap-1 flex items-center text-sm text-tertiary"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('selectcomponent')}
title="Add Select"
>
+ <List size={14} />
</Button>
</div>
</PanelSection>
</PanelSection>
{/if}

View File

@@ -291,6 +291,16 @@ export function getAllScriptNames(app: App): string[] {
})
}
if (gridItem.data.type === 'aggridcomponent' || gridItem.data.type === 'aggridcomponentee') {
gridItem.data.actions?.forEach((actionButton) => {
if (actionButton.componentInput?.type === 'runnable') {
if (actionButton.componentInput.runnable?.type === 'runnableByName') {
acc.push(actionButton.componentInput.runnable.name)
}
}
})
}
if (gridItem.data.type === 'menucomponent') {
gridItem.data.menuItems.forEach((menuItem) => {
if (menuItem.componentInput?.type === 'runnable') {

View File

@@ -77,6 +77,22 @@
})
}
function handleDragOver(event: DragEvent) {
event.preventDefault()
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy'
}
}
function handleDrop(event: DragEvent) {
event.preventDefault()
if (event.dataTransfer) {
if (event.dataTransfer.files && event.dataTransfer.files.length) {
onChange(event.dataTransfer.files)
}
}
}
function removeFile(index: number) {
if (!files) return
files.splice(index, 1)
@@ -114,6 +130,8 @@
duration-200 rounded-lg p-1`,
c
)}
on:dragover={handleDragOver}
on:drop={handleDrop}
{style}
>
{#if !hideIcon && !files}

View File

@@ -44,7 +44,8 @@
}
}
let xhr: XMLHttpRequest | undefined = undefined
let activeUploads: { xhr: XMLHttpRequest; fileName: string }[] = []
async function uploadFileToS3(fileToUpload: File, fileToUploadKey: string) {
if (fileToUpload === undefined || fileToUploadKey === undefined) {
return
@@ -72,6 +73,7 @@
path: path,
file: fileToUpload
}
$fileUploads = [...$fileUploads, uploadData]
// // Use a custom TransformStream to track upload progress
@@ -119,7 +121,9 @@
// }
// )
xhr = new XMLHttpRequest()
let xhr = new XMLHttpRequest()
activeUploads.push({ xhr, fileName: fileToUpload.name })
const response = (await new Promise((resolve, reject) => {
xhr?.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
@@ -144,7 +148,8 @@
reject(response)
}
}
xhr = undefined
activeUploads = activeUploads.filter((x) => x.fileName !== fileToUpload.name)
})
xhr?.open(
'POST',
@@ -190,11 +195,21 @@
sendUserToast('File deleted!')
}
onDestroy(() => {
if (xhr) {
xhr?.abort
xhr = undefined
function clearRequests() {
activeUploads.forEach(({ xhr }) => xhr.abort())
activeUploads = []
}
function abortUpload(fileName: string) {
const upload = activeUploads.find((x) => x.fileName === fileName)
if (upload) {
upload.xhr.abort()
activeUploads = activeUploads.filter((x) => x.fileName !== fileName)
}
}
onDestroy(() => {
clearRequests()
})
</script>
@@ -232,10 +247,7 @@
return
}
if (xhr) {
xhr.abort()
xhr = undefined
}
abortUpload(fileUpload.name)
$fileUploads = $fileUploads.filter(
(_fileUpload) => _fileUpload.name !== fileUpload.name
@@ -260,10 +272,7 @@
return
}
if (xhr) {
xhr.abort()
xhr = undefined
}
clearRequests()
$fileUploads = $fileUploads.filter(
(_fileUpload) => _fileUpload.name !== fileUpload.name
@@ -306,10 +315,7 @@
if (fileUpload.path) {
deleteFile(fileUpload.path)
}
if (xhr) {
xhr.abort()
xhr = undefined
}
abortUpload(fileUpload.name)
}}
startIcon={{
icon: Trash

View File

@@ -25,6 +25,8 @@
<div class="font-bold text-md">Contained buttons</div>
<div class="grid grid-cols-2 gap-2 md:grid-cols-4 lg:grid-cols-6">
<Button>Lorem</Button>
<Button disabled>Lorem</Button>
<Button color="dark" loading>Lorem</Button>
<Button
color="gray"

View File

@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.305.3"
wmill_pg = ">=1.305.3"
wmill = ">=1.306.0"
wmill_pg = ">=1.306.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.305.3
version: 1.306.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.305.3"
version = "1.306.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.305.3"
version = "1.306.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.305.3",
"version": "1.306.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {

View File

@@ -1 +1 @@
1.305.3
1.306.0