Документация Engee

Плагины Stipple

Плагины Stipple — это пакеты Julia, которые добавляют новые функции в приложение Stipple, такие как скачивание файлов, отображение Markdown, форматирование LaTeX и многое другое. На этой странице перечислены доступные плагины с примерами их использования в приложениях.

StippleDownloads

StippleDownloads — это плагин для Stipple, позволяющий скачивать динамически создаваемые файлы. Обработчики событий гарантируют, что копию файла получит только запрашивающий клиент.

Поддерживаются текстовые и двоичные файлы, а имена файлов можно выбирать произвольным образом.

Примеры

Скачивание содержимого переменной в виде необработанных данных или BSON

using Stipple, Stipple.ReactiveTools
using StippleUI, StippleDownloads
using BSON

@app begin
    @in data = randn(1000)

    @event download_raw begin
        download_binary(__model__, data, "raw")
    end
    @event download_bson begin
        # При вызове @save для `data` выдается ошибка, так как это реактивная переменная. Необходимо создать копию.
        data_var = data
        io = IOBuffer()
        BSON.@save io data_var
        seekstart(io)
        download_binary(__model__, take!(io), "data.bson")
    end

end

function ui()
    row([
            cell(btn(class="q-ml-lg", "Raw", icon="download", @on(:click, :download_raw, :addclient), color="primary", nocaps=true))
            cell(btn(class="q-ml-lg", "BSON", icon="download", @on(:click, :download_bson, :addclient), color="secondary", nocaps=true))
        ])

end

@page("/", ui)

Скачивание DataFrame в виде листа Excel

using Stipple, Stipple.ReactiveTools
using StippleUI
using StippleDownloads

using DataFrames
using XLSX

import Stipple.opts
import StippleUI.Tables.table

function df_to_xlsx(df)
    io = IOBuffer()
    XLSX.writetable(io, df)
    take!(io)
end

@app begin
    @out table = DataTable(DataFrame(:a => rand(1:10, 5), :b => rand(1:10, 5)))
    @in text = "The quick brown fox jumped over the ..."

    @event download_text begin
        download_text(__model__, :text)
    end

    @event download_df begin
        try
            download_binary(__model__, df_to_xlsx(table.data), "file.xlsx"; client = event["_client"])
        catch ex
            println(ex)
        end
    end
end

function ui()
    row(cell(class = "st-module", [

        row([
            cell(textfield(class = "q-pr-md", "Download text", :text, placeholder = "no output yet ...", :outlined, :filled, type = "textarea"))
            cell(table(class = "q-pl-md", :table))
        ])

        row([
            cell(col = 1, "Without client info")
            cell(btn("Text File", icon = "download", @on(:click, :download_text), color = "primary", nocaps = true))
            cell(col = 1, "With client info")
            cell(btn(class = "q-ml-lg", "Excel File", icon = "download", @on(:click, :download_df, :addclient), color = "primary", nocaps = true))
        ])
    ]))
end

@page("/", ui)

up(open_browser = true)

Чтобы увидеть разницу между вызовами с информацией о клиенте и без нее, продублируйте вкладку приложения и нажмите кнопку Download Text (Скачать текст).

Будут скачаны два идентичных файла, так как при дублировании вкладки создается синхронизированная копия приложения. Чтобы файл получал только запрашивающий клиент, следует ограничить загрузку с помощью event["_client"] и добавить для активирующей кнопки дополнительное свойство :addclient, в которое будет включаться эта информация.

StippleMarkdown

Отображение текста Markdown в приложениях Genie

Этот пакет содержит два новых компонента Stipple: markdowntext и markdowncard. В качестве аргумента можно передать либо строку текста Markdown, либо символ, который ссылается на переменную с текстом Markdown.

using GenieFramework
@genietools
using StippleMarkdown

@app begin
    @out txt = "**hello** world"
end
@deps StippleMarkdown

ui() = [ markdowntext(:txt), markdowntext("## Hello World!"), markdowncard(:txt), markdowncard("## Hello World!\n This is a Markdown card")]

@page("/", ui)

StippleLatex

StippleLatex использует Vue-Katex для реализации форматирования LaTeX в Stipple. Есть три возможных варианта добавления содержимого LaTeX на веб-страницу:

  • Элемент span LaTeX:

    latex(<LaTeX formula>, <formatting options>)

  • HTML-элемент с содержимым LaTeX через строковый макрос с необязательным модификатором, который может принимать значение auto или display:

    span(latex"<LaTeX formula>") span(latex"<LaTeX formula>"display)

  • HTML-элемент с содержимым LaTeX через макрос @latex с дополнительными параметрами:

    span(@latex(raw"<LaTeX formula>") span(@latex(raw"<LaTeX formula>", display = true)

Все аргументы также поддерживают символы для привязки к полям модели. Вот приложение, демонстрирующее возможные варианты использования.

using Stipple, Stipple.ReactiveTools
using StippleUI
using StippleLatex

## определяем небольшой генератор формул
function nestlist(f, a; init = nothing)
    T = eltype(a)
    list = T[]
    el = init
    for (i, x) in enumerate(a)
        el = i == 1 && init === nothing ? x : f(el, x)
        push!(list, el)
    end
    list
end

formula = nestlist(*, ["", raw"\sin", "^2", " x", " +", raw" \sqrt{", "a", "^2", " +", " b", "^2"])
formula[contains.(formula, "sqrt")] .*= "}"

## настраиваем приложение
@app begin
    @in x = 0
    @in formula_1 = raw"\int_{a}^{b} f(x) \, dx = F(x)\Biggr|^b_a"
    @in formula_2 = raw""
    @private p = @task 1 + 1

    @onchange isready begin
        if !istaskstarted(p) || istaskdone(p)
            p = @task begin
                println("Task started")
                while x <= 100
                    sleep(1)
                    x += 1
                    pos = x < 6 ? 1 : (x - 5) % (length(formula) + 5) + 1
                    formula_2 = formula[min(pos, length(formula))]
                end
            end
            schedule(p)
        end
    end
end

function ui()
    [
        row(cell(class = "st-module", [
            cell(h1(latex("\\LaTeX") * "-Demo"))
            cell(h2(latex"a^2 + b^2 = c^2"))
        ]))

        row(cell(class = "st-module", [
            textfield("Enter your LaTeX-Forumla", :formula_1,)
            cell(class = "q-pa-md", latex":formula_1"display)
            row([
                cell(class = "q-pa-md bg-red-1", raw"""cell(latex"\cos^2x"display)""")
                cell(class = "q-pa-md bg-green-1", latex"\cos^2x"display)
            ])
            row([
                cell(class = "q-pa-md bg-red-1", raw"""cell(latex"This is auto mode with a formula \(\cos^2x\)"auto)""")
                cell(class = "q-pa-md bg-green-1", latex"This is auto mode with a formula \(\cos^2x\)"auto)
            ])
            row([
                cell(class = "q-pa-md bg-red-1", raw"""latex(class = "q-pa-md", raw"\tan^2x", display = true)""")
                cell(class = "bg-green-1 q-pa-md", latex(class = "q-pa-md", raw"\tan^2x", display = true))
            ])
            bignumber("Wait for 5", :x, color = R"x >= 5 ? 'negative' : 'positive'", icon = "calculate")

            row([
                cell(class = "q-pa-md bg-red-1", raw"""cell(class = "q-pa-md", @latex(raw"\tanh^2 y", display = R"x >= 5"))""")
                cell(class = "q-pa-md bg-green-1", @latex(raw"\tanh^2 y", display = R"x >= 5"))
            ])


            row([
                cell(class = "q-pa-md bg-red-1", raw"""@latex(raw"This is auto mode with an inline formula \(\cos^2x\) and a display formula $$\sin^2x$$""")
                cell(class = "q-pa-md bg-green-1", @latex(raw"This is auto mode with an inline formula \(\cos^2x\) and a display formula $$\sin^2x$$", auto = true))
            ])
        ]))

        row(cell(class = "st-module", [
            textfield(class = "q-pa-lg", "LaTeX", :formula_2)
            cell(class = "q-pa-md", "Result:")
            cell(class = "q-pa-md", latex":formula_2"display)
        ]))
    ]
end

route("/") do
    page(@init(), ui()) |> html
end

up()
latex

StippleMathjs

Плагин StippleMathjs добавляет mathjs в проект Stipple или GenieFramwork.

Кроме того, он добавляет автоматическое преобразование всех типов чисел Complex между сервером и клиентом.

Пример приложения

using Stipple, Stipple.ReactiveTools
using StippleUI
using StippleMathjs


x0 = 1.0
y0 = 2.0

@app begin
    @in x = x0
    @in y = y0
    @in z = x0 + y0 * im
    @in z2::ComplexF64 = x0 + y0 * im

    @onchange x, y begin
        # обновляем z, не инициируя `@onchange z`
        z[!] = x + y * im

        # обновляем x и y в клиенте
        @push z
    end

    @onchange z begin
        # обновляем x и y, не инициируя `@onchange x, y`
        x[!] = z.re
        y[!] = z.im

        # обновляем x и y в клиенте
        @push x
        @push y
    end
end

@deps StippleMathjs

function ui()
    [
        card(class = "q-pa-md", [
            numberfield(class = "q-ma-md", "x", :x)
            numberfield(class = "q-ma-md", "y", :y)
        ])

        card(class = "q-pa-md q-my-md", [
            row([cell(col = 2, "z"),        cell("{{ z }}")])
            row([cell(col = 2, "z.mul(z)"), cell("{{ z.mul(z) }}")])
            row([cell(col = 2, "z.abs()"),  cell("{{ z.abs() }}")])

            btn(class = "q-my-md", "square(z)", color = "primary", @click("z = z.mul(z)"))
        ])
    ]
end

@page("/", ui, debounce = 10)
up()

Пример приложения с правильным содержимым файла Manifest.toml доступен в StippleDemos.jl.

Примечание

  • Из-за имеющейся в настоящее время в Stipple ошибки x0 и y0 необходимо определять вне цикла, чтобы у z был правильный тип. В качестве альтернативы можно объявить z явно, например, так: z::ComplexF64.

  • Этот пакет может служить хорошим примером внедрения исходного кода JavaScript в собственный проект.

StippleKeplerGL.jl

StippleKeplerGL

Пакет Julia для интеграции карт KeplerGL в приложения Genie/Stipple.

Благодарности

В этом пакете используется замечательный пакет KeplerGL.jl.

Установка

Для установки пакета выполните в командной строке Julia следующую команду:

] add StippleKeplerGL

Пример

using Stipple, Stipple.ReactiveTools
using StippleUI
using StippleKeplerGL
using DataFrames
using CSV
using Colors
using ColorBrewer

keplergl_path = Base.pkgdir(isdefined(@__MODULE__, :KeplerGLBase) ? KeplerGLBase : KeplerGL)
df = CSV.read(joinpath(keplergl_path, "assets", "example_data", "data.csv"), DataFrame)

## token = "токен, пожалуйста"

m1 = KeplerGL.KeplerGLMap(token, center_map=false)

KeplerGL.add_point_layer!(m1, df, :Latitude, :Longitude,
    color = colorant"rgb(23,184,190)", color_field = :Magnitude, color_scale = "quantize",
    color_range = ColorBrewer.palette("PRGn", 6),
    radius_field = :Magnitude, radius_scale = "sqrt", radius_range = [4.2, 96.2], radius_fixed = false,
    filled = true, opacity = 0.39, outline = false
)

m1.config[:config][:mapState][:latitude] = 38.32068477880718
m1.config[:config][:mapState][:longitude]= -120.42806781055732
m1.config[:config][:mapState][:zoom] = 4.886825331541375
m1.window[:map_legend_show] = m1.window[:map_legend_active] = m1.window[:visible_layers_show] = m1.window[:visible_layers_active] = false

m2 = KeplerGL.KeplerGLMap(token, center_map=false)

KeplerGL.add_point_layer!(m2, df, :Latitude, :Longitude,
    color = colorant"rgb(23,184,190)", color_field = :Magnitude, color_scale = "quantize",
    color_range = ColorBrewer.palette("RdYlGn", 6),
    radius_field = :Magnitude, radius_scale = "sqrt", radius_range = [4.2, 96.2], radius_fixed = false,
    filled = true, opacity = 0.39, outline = false
)

m2.config[:config][:mapState][:latitude] = 38.32068477880718
m2.config[:config][:mapState][:longitude]= -122.42806781055732
m2.config[:config][:mapState][:zoom] = 4.886825331541375
m2.window[:map_legend_show] = m2.window[:map_legend_active] = m2.window[:visible_layers_show] = m2.window[:visible_layers_active] = false

d1, d2 = m1.datasets, m2.datasets

@app begin
    @out map1 = m1
    @out map2 = m2
    @in clear_data = false
    @in restore_data = false
    @in show_legend = false
    @in go_west = false
    @in go_east = false

    @onbutton clear_data begin
        __model__["map1.datasets"] = []
        __model__["map2.datasets"] = []
    end

    @onbutton restore_data begin
        __model__["map1.datasets"] = d1
        __model__["map2.datasets"] = d2
    end

    @onbutton go_west begin
        map1.config[:config][:mapState][:longitude] -= 1
        __model__["map1.config.config.mapState.longitude"] = map1.config[:config][:mapState][:longitude]

        map2.config[:config][:mapState][:longitude] -= 1
        __model__["map2.config.config.mapState.longitude"] = map2.config[:config][:mapState][:longitude]
    end

    @onbutton go_east begin
        map1.config[:config][:mapState][:longitude] += 1
        __model__["map1.config.config.mapState.longitude"] = map1.config[:config][:mapState][:longitude]

        map2.config[:config][:mapState][:longitude] += 1
        __model__["map2.config.config.mapState.longitude"] = map2.config[:config][:mapState][:longitude]
    end

    @onchange show_legend begin
        __model__["map1.window.map_legend_show"] = show_legend
        __model__["map2.window.map_legend_show"] = show_legend

        # в качестве альтернативы можно использовать следующие строки для отображения условных обозначений через бэкенд,
        # но при этом во фронтенд будут переданы все данные карты

        # map1.window[:map_legend_show] = show_legend
        # notify(map1)
    end
end

@deps StippleKeplerGL
isdefined(Stipple, :register_global_components) && Stipple.register_global_components("VueKeplerGl", legacy = true)

ui() = [
    column(class = "full-height", [
        row(col = :auto, class = "items-center", [
            h5(class = "col-auto q-pl-lg q-py-md", "KeplerGL Demo")
            cell()
            btn(col = :auto, "", icon = "west", @click(:go_west), class = "q-mr-md", [tooltip("go west")])
            btn(col = :auto, "", icon = "east", @click(:go_east), class = "q-mr-md", [tooltip("go east")])
            btn(col = :auto, "", icon = "delete", @click(:clear_data), class = "q-mr-md", [tooltip("clear data")])
            btn(col = :auto, "", icon = "restore_from_trash", @click(:restore_data), class = "q-mr-md", [tooltip("restore data")])
            toggle(col = :auto, "legend", :show_legend, class = "q-mr-md")
        ])

        cell(keplergl(:map1, ref = "map1", id = "map1"))
        cell(keplergl(:map2, ref = "map2"))
    ])
]

route("/") do
    # раскомментируйте следующую строку для тестирования или отладки
    global model
    model = @init
    page(class = "fixed-full", model, ui) |> html
end

up(open_browser = true)

StippleTypedArrays

StippleTypedArrays — это плагин Stipple для интеграции массивов TypedArrays из JavaScript.

Типичным вариантом их использования являются буферы, например для скачивания файлов или обработки и отправки информации по двоичным каналам.

В StippleTypedArrays введена векторная оболочка TypedArray, которую можно использовать в объявлениях типов для переменных приложения.

Обратите внимание:

на стороне бэкенда все обработчики работают обычным образом. Однако на стороне клиента JavaScript не может отслеживать типизированные массивы, поэтому любые изменения буфера на стороне клиента не будут автоматически синхронизированы.

Чтобы синхронизировать данные с сервером, после изменения значения необходимо вызвать this.push('data').

Демонстрационное приложение

using Stipple, Stipple.ReactiveTools
using StippleUI

using StippleTypedArrays
using StippleDownloads

@app begin
    @in data = TypedArray(UInt8[])a
    @in data64 = TypedArray(UInt64[])

    @in add_data = false
    @in clear_data = false

    @onbutton add_data begin
        x = rand(0:255)
        push!(data, x)
        notify(data)
        push!(data64, x + 1000)
        notify(data64)
    end

    @onbutton clear_data begin
        data = data64 = []
    end
end

@deps StippleTypedArrays

function ui()
    row(cell(class = "st-module q-ma-md", [

        row(class = "q-pa-md bg-green-2", "Data: [{{ data }}]")
        row(class = "q-pa-md q-my-lg bg-green-4", "Data64: [{{ data64 }}]")

        row([
            btn("Add data", icon = "add", @click(:add_data), color = "primary", nocaps = true)
            btn(class = "q-ml-lg", "Clear data", icon = "delete_forever", @click(:clear_data), color = "primary", nocaps = true)
        ])
    ]))
end

@page("/", ui)

up(open_browser = true)
typedarrays