Modules/UniversalDashboard.Designer/UniversalDashboard.Designer.psm1

$IndexJs = Get-ChildItem "$PSScriptRoot\index.*.bundle.js"
$DesignerAssetId = [UniversalDashboard.Services.AssetService]::Instance.RegisterScript($IndexJs.FullName)

Get-ChildItem "$PSScriptRoot\*.bundle.js" | ForEach-Object {
    [UniversalDashboard.Services.AssetService]::Instance.RegisterScript($_.FullName)
}

function Start-UDPageDesigner {
    param(
        [Parameter()]
        $Port = 80,
        [Parameter()]
        [Switch]$Wait
    )

    End {
        $EndpointInitialization = New-UDEndpointInitialization -Variable @('DesignerAssetId') -Function @("Update-UDLayout", "Add-UDDesignElement", "New-ControlProperties", "Import-UDDesignerPage", 'New-UDDesignerSurface', 'Remove-UDDesignElement', "New-UDMUInput")

        $Categories = @(
            @{
                name = "General"
                components = @(
                    @{ Icon = "puzzle_piece"; Name = "Element"; Component = "New-UDElement" }
                    @{ Icon = "code"; Name = "HTML"; Component = "New-UDHtml" }
                )
            }
            @{
                name = "Materialize"
                components = @(
                    @{ Icon = "square"; Name = "Button"; Component = "New-UDButton" }
                    @{ Icon = "window_maximize"; Name = "Card"; Component = "New-UDCard" }
                    @{ Icon = "check_square_o"; Name = "Checkbox"; Component = "New-UDCheckbox" }
                    @{ Icon = "th"; Name = "Grid"; Component = "New-UDGrid" }
                    @{ Icon = "header"; Name = "Header"; Component = "New-UDHeading" }
                    @{ Icon = "fighter_jet"; Name = "Icon"; Component = "New-UDIcon" }
                    @{ Icon = "window_restore"; Name = "IFrame"; Component = "New-UDIFrame" }
                    @{ Icon = "file_picture_o"; Name = "Image"; Component = "New-UDImage" }
                    @{ Icon = "file_text_o"; Name = "Paragraph"; Component = "New-UDParagraph" }
                    @{ Icon = "spinner"; Name = "Preloader"; Component = "New-UDPreloader" }
                    @{ Icon = "circle_o"; Name = "Radio"; Component = "New-UDRadio" }
                    @{ Icon = "plus_square_o"; Name = "Select"; Component = "New-UDSelect" }
                    @{ Icon = "toggle_on"; Name = "Switch"; Component = "New-UDSwitch" }
                    @{ Icon = "table"; Name = "Table"; Component = "New-UDTable" }
                    @{ Icon = "text_height"; Name = "Textbox"; Component = "New-UDTextbox" }
                )
            }
            @{
                name = "Material UI"
                components = @(
                    @{ Icon = "user_circle"; Name = "Avatar"; Component = "New-UDMuAvatar" }
                    @{ Icon = "square"; Name = "Button"; Component = "New-UDMuButton" }
                    @{ Icon = "window_maximize"; Name = "Card"; Component = "New-UDMuCard" }
                    @{ Icon = "circle"; Name = "Chip"; Component = "New-UDMuChip" }
                    @{ Icon = "square_o"; Name = "Paper"; Component = "New-UDMuPaper" }
                    @{ Icon = "italic"; Name = "Typography"; Component = "New-UDMuTypography" }
                )
            }
            @{
                name = "ChartJS"
                components = @(
                    @{ Icon = "bar_chart"; Name = "Chart"; Component = "New-UDChart" }
                    @{ Icon = "line_chart"; Name = "Monitor"; Component = "New-UDMonitor" }
                )
            }
            @{
                name = "Nivo"
                components = @(
                    @{ Icon = "bar_chart"; Name = "Chart"; Component = "New-UDNivoChart" }
                )
            }
        )

        $db = New-UDDashboard -Title "Page Designer" -EndpointInitialization $EndpointInitialization -Content {
            New-UDElement -Tag div -Id "container" -Endpoint {

                $Page = Import-UDDesignerPage

                New-UDDesignerSurface -Id 'grid' -Categories $Categories -LayoutJson $Page.LayoutJson -Code $Session:PageContent -Content {
                    Invoke-Expression $Page.Controls
                } -OnLayoutChanged {
                    Update-UDLayout -Layout $EventData
                } -OnDeleteElement {
                    Remove-UDDesignElement -Id $EventData
                } -OnClear {
                    $Session:PageContent = "`$Layout = '[]'
New-UDPage -Name 'Test' -Content {
    New-UDGridLayout -LayoutJson `$Layout -Content {
 
    }
}"


                    Set-UDElement -Id 'grid' -Attributes @{
                        code = $Session:PageContent
                    }
                } -OnAddComponent {
                    param($Component)
                    New-ControlProperties -ControlName $Component
                }
            }
        }

        Start-UDDashboard -Dashboard $db -Port $Port -Wait:$Wait
    }
}

function Import-UDDesignerPage {
    param(
    )

    End {
        if ($Session:PageContent -eq $null) {
            $Session:PageContent = "`$Layout = '[]'
New-UDPage -Name 'Test' -Content {
    New-UDGridLayout -LayoutJson `$Layout -Content {
 
    }
}"

        }

        $PageContent = $Session:PageContent

        $ScriptBlock = [ScriptBlock]::Create($PageContent)
        $LayoutAssignment = $ScriptBlock.Ast.Find({$args[0] -is [System.Management.Automation.Language.AssignmentStatementAst] -and $args[0].Left.ToString() -eq '$Layout'}, $true)
        $LayoutJson = $LayoutAssignment.Right.ToString().Trim("'")

        $CommandAst = $ScriptBlock.Ast.Find({$args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].GetCommandName() -eq 'New-UDGridLayout'}, $true)
        $ContentScriptBlock = $CommandAst.CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ScriptBlockExpressionAst] }
    
        @{
            LayoutJson = $LayoutJson
            Controls =  $ContentScriptBlock.ScriptBlock.EndBlock.ToString()
        }
    }
}

function Update-UDLayout {
    param(
        [string]$Layout
    )

    $PageContent = $Session:PageContent

    $ScriptBlock = [ScriptBlock]::Create($PageContent)
    $LayoutAssignment = $ScriptBlock.Ast.Find({$args[0] -is [System.Management.Automation.Language.AssignmentStatementAst] -and $args[0].Left.ToString() -eq '$Layout'}, $true)

    $PageContent = $PageContent.Remove($LayoutAssignment.Right.Extent.StartOffset, $LayoutAssignment.Right.Extent.EndOffset - $LayoutAssignment.Right.Extent.StartOffset)
    $PageContent = $PageContent.Insert($LayoutAssignment.Right.Extent.StartOffset, "'$Layout'")
    
    $Session:PageContent = $PageContent.TrimEnd()

    Set-UDElement -Id 'grid' -Attributes @{
        code = $PageContent
    }
}

function Remove-UDDesignElement {
    param(
        [string]$Id
    )

    $PageContent = $Session:PageContent

    $ScriptBlock = [ScriptBlock]::Create($PageContent)

    $CommandAst = $ScriptBlock.Ast.Find({$args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].GetCommandName() -eq 'New-UDGridLayout'}, $true)
    $ContentScriptBlock = $CommandAst.CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ScriptBlockExpressionAst] }

    $Statement = $null
    $ContentScriptBlock.ScriptBlock.EndBlock.Statements | ForEach-Object {
        $CommandElements = $_.PipelineElements.CommandElements 
        for($i = 0; $i -lt $CommandElements.Length; $i++) {
            if ($CommandElements[$i].ParameterName -eq 'Id') {
                if ($CommandElements[$i+1].Value -eq $id) {
                    $Statement = $_
                    break
                }
            }
        }
    }

    if ($Statement -ne $null) {
        $SBString = $ScriptBlock.ToString() 
        $SBString = $SBString.Remove($Statement.Extent.StartOffset, $Statement.Extent.EndOffset - $Statement.Extent.StartOffset)

        $Session:PageContent = $SBString.TrimEnd()

        Set-UDElement -Id 'grid' -Attributes @{
            code = $PageContent
        }
    }
}
function Add-UDDesignElement {
    param(
        [string]$Element
    )

    $PageContent = $Session:PageContent

    $ScriptBlock = [ScriptBlock]::Create($PageContent)
    $CommandAst = $ScriptBlock.Ast.Find({$args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].GetCommandName() -eq 'New-UDGridLayout'}, $true)
    $ContentScriptBlock = $CommandAst.CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ScriptBlockExpressionAst] }
    
    $LastBracket = $ContentScriptBlock.Extent.EndOffset - 1
    $PageContent = $PageContent.Insert($LastBracket, "`t" + $Element + [System.Environment]::NewLine + "`t")
    
    $Session:PageContent = $PageContent.TrimEnd()

    Set-UDElement -Id 'grid' -Attributes @{
        code = $PageContent
    }
}

function New-ControlProperties {
    param(
        $ControlName 
    )

    $CommandInfo = Get-Command $ControlName

    New-UDTabContainer -Tabs {
        foreach($parameterSet in $CommandInfo.ParameterSets.GetEnumerator()) {
            New-UDTab -Text $parameterSet.Name -Content {
                
                $Properties = @()
                $CommonParameters = @("Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", "ErrorVariable", "WarningVariable", "InformationVariable", "OutVariable", "OutBuffer", "PipelineVariable")
                foreach($parameter in ($parameterSet.Parameters | Sort-Object -Property Name)) {
                    if ($CommonParameters -contains $parameter.Name) {
                        continue
                    }

                    $Attribute = $Parameter.Attributes | Where-Object ParameterSetName -eq $parameterSet.Name
                    if ($null -eq $Attribute) {
                        $Attribute = $Parameter.Attributes | Where-Object ParameterSetName -eq '__AllParameterSets'
                    }

                    $type = ""
                    if ($Parameter.ParameterType -eq [System.Management.Automation.SwitchParameter]) {
                        $type = "checkbox"
                    } elseif ($Parameter.ParameterType -eq [UniversalDashboard.Models.DashboardColor]) {
                        $type = "color"
                    } elseif ($Parameter.ParameterType -eq [ScriptBlock] -or $Parameter.Name -eq 'Endpoint') {
                        $type = "code"
                    } else {
                        $type = "textbox"
                    }
                    
                    $Properties += @{
                        type = $type
                        name = $parameter.Name
                        id = $parameter.Name
                        set = $false
                        mandatory = $Attribute.Mandatory
                    }
                }
                
                New-UDMuInput -Properties $Properties -Endpoint {
                    $props = $EventData | ConvertFrom-Json

                    $arguments = @{}

                    $IdSet = $false
                    foreach($parameter in ($props | Where-Object {$_.Set})) {
                        if ($parameter.Value -ne $null) {
                            $value = $parameter.Value 

                            if ($Value -is [string] -and $Value.Contains(",")) {
                                $value = $Value.Split(",")
                            }

                            if ($Parameter.Type -eq 'Code') {
                                $Value = [ScriptBlock]::Create($Value)
                            }

                            $arguments[$parameter.Name] = $Value

                            if ($Parameter.Name -eq 'Id') {
                                $IdSet = $true
                            }
                        }
                    }

                    if (-not $IdSet) {
                        $arguments["Id"] = (New-Guid).ToString()
                    }
                    
                    Add-UDElement -ParentId "grid" -Content {
                        . $ArgumentList[0] @arguments
                    }

                    $argumentString = ""
                    foreach($parameter in ($props | Where-Object {$_.Set})) {
                        $name = $parameter.Name
                        $value = $arguments[$name]
                        
                        if ($parameter.Type -eq 'Checkbox') {
                            $argumentString += " -$($name):`$$value"
                        } elseif ($parameter.Type -eq  'Code') {
                            $argumentString += " -$name {$value}"
                        } else {
                            $argumentString += " -$Name `"$value`""
                        }
                    }

                    if (-not $IdSet) {
                        $argumentString += " -Id `"$($arguments["Id"])`""
                    }

                    $CommandString = "$($ArgumentList[0])$argumentString"
                    Add-UDDesignElement -Element $CommandString | Out-Null

                } -ArgumentList @($ControlName, $parameterSet.Name)
            }
        }
    }

}

function New-UDMUInput {
    param(
        [Hashtable[]]$Properties,
        [Object]$Endpoint,
        [object[]]$ArgumentList
    )

    End {

        if ($null -ne $Endpoint -and $Endpoint -is ([ScriptBlock])) {
            $Endpoint = New-UDEndpoint -Endpoint $Endpoint -ArgumentList $ArgumentList
        }
        elseif ($null -ne $Endpoint -and $Endpoint -isnot ([UniversalDashboard.Models.Endpoint])) {
            throw "Endpoint must be a ScriptBlock of UDEndpoint"
        }

        @{
            type = "muudinput"
            isPlugin = $true
            assetId = $DesignerAssetId

            properties = $Properties
            endpoint = $Endpoint.Name
        }
    }
}
function New-UDDesignerSurface {
    param(
        [Parameter()]
        [string]$Id = (New-Guid).ToString(),
        [Parameter()]
        [int]$RowHeight = 30,
        [Parameter()]
        [scriptblock]$Content,
        [Parameter()]
        [Hashtable[]]$Layout,
        [Parameter()]
        [string]$LayoutJson,
        [Parameter()]
        [int]$LargeColumns = 12,
        [Parameter()]
        [int]$MediumColumns = 10,
        [Parameter()]
        [int]$SmallColumns = 6,
        [Parameter()]
        [int]$ExtraSmallColumns = 4,
        [Parameter()]
        [int]$ExtraExtraSmallColumns = 2,
        [Parameter()]
        [int]$LargeBreakpoint = 1200,
        [Parameter()]
        [int]$MediumBreakpoint = 996,
        [Parameter()]
        [int]$SmallBreakpoint = 768,
        [Parameter()]
        [int]$ExtraSmallBreakpoint = 480,
        [Parameter()]
        [int]$ExtraExtraSmallBreakpoint = 0,
        [Parameter()]
        [switch]$Draggable,
        [Parameter()]
        [switch]$Resizable,
        [Parameter()]
        [switch]$Persist,
        [Parameter()]
        [object]$OnLayoutChanged,
        [Parameter()]
        [object]$OnDeleteElement,
        [Parameter()]
        [object]$OnAddComponent,
        [Parameter()]
        [object]$OnClear,
        [Parameter()]
        [string]$Code,
        [Parameter()]
        $Categories
    )

    End {
        $Breakpoints = @{
            lg = $LargeBreakpoint
            md = $MediumBreakpoint
            sm = $SmallBreakpoint
            xs = $ExtraSmallBreakpoint
            xxs = $ExtraExtraSmallBreakpoint
        }

        $Columns  = @{
            lg = $LargeColumns
            md = $MediumColumns
            sm = $SmallColumns
            xs = $ExtraSmallColumns
            xxs = $ExtraExtraSmallColumns
        }

        if ($null -ne $OnLayoutChanged -and $OnLayoutChanged -is ([ScriptBlock])) {
            $OnLayoutChanged = New-UDEndpoint -Endpoint $OnLayoutChanged
        }
        elseif ($null -ne $OnLayoutChanged -and $OnLayoutChanged -isnot ([UniversalDashboard.Models.Endpoint])) {
            throw "OnLayoutChanged must be a ScriptBlock of UDEndpoint"
        }
        
        if ($null -ne $OnDeleteElement -and $OnDeleteElement -is ([ScriptBlock])) {
            $OnDeleteElement = New-UDEndpoint -Endpoint $OnDeleteElement
        }
        elseif ($null -ne $OnDeleteElement -and $OnDeleteElement -isnot ([UniversalDashboard.Models.Endpoint])) {
            throw "OnDeleteElement must be a ScriptBlock of UDEndpoint"
        }

        if ($null -ne $OnClear -and $OnClear -is ([ScriptBlock])) {
            $OnClear = New-UDEndpoint -Endpoint $OnClear
        }
        elseif ($null -ne $OnClear -and $OnClear -isnot ([UniversalDashboard.Models.Endpoint])) {
            throw "OnClear must be a ScriptBlock of UDEndpoint"
        }

        if ($null -ne $OnAddComponent -and $OnAddComponent -is ([ScriptBlock])) {
            $OnAddComponent = New-UDEndpoint -Endpoint $OnAddComponent
        }
        elseif ($null -ne $OnAddComponent -and $OnAddComponent -isnot ([UniversalDashboard.Models.Endpoint])) {
            throw "OnAddComponent must be a ScriptBlock of UDEndpoint"
        }

        @{
            type = "designer-surface"
            isPlugin = $true
            assetId = $DesignerAssetId

            id = $Id
            className = "layout"
            rowHeight = $RowHeight
            content = $Content.Invoke()
            layout = $Layout
            layoutJson = $LayoutJson
            cols = $Columns
            breakpoints = $Breakpoints
            isDraggable = $Draggable.IsPresent
            isResizable = $Resizable.IsPresent
            persist = $Persist.IsPresent
            endpoint = $OnLayoutChanged.Name
            onDeleteElement = $OnDeleteElement.Name
            onClear = $OnClear.Name
            code = $Code
            categories = $Categories
            onAddComponent = $OnAddComponent.Name
        }
    }
}