├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── screenshot.png
├── setup.py
└── streamlit_toggle
├── __init__.py
└── frontend
├── .env
├── .prettierrc
├── package-lock.json
├── package.json
├── public
├── bootstrap.min.css
└── index.html
├── src
├── StreamlitToggle.tsx
├── index.tsx
├── react-app-env.d.ts
└── streamlit
│ ├── ArrowTable.ts
│ ├── StreamlitReact.tsx
│ ├── index.tsx
│ └── streamlit.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | streamlit_toggle/frontend/node_modules/
2 | .venv/
3 | .vscode/
4 | *.code-workspace
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Sam Dobson
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include streamlit_toggle/frontend/build *
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A toggle switch widget for Streamlit
2 |
3 | 
4 |
5 | ## Usage
6 |
7 | ```python
8 | import streamlit as st
9 | from streamlit_toggle import st_toggleswitch
10 |
11 | awesomeness_enabled = st_toggleswitch("Enable awesomeness")
12 | if awesomeness_enabled:
13 | st.write("Awesomeness has been enabled!")
14 | ```
15 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samdobson/streamlit-toggle/cb78455eb843e6176baf6609f2292be60c48066c/screenshot.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | setuptools.setup(
4 | name="streamlit-toggle",
5 | version="1.0.0",
6 | author="Sam Dobson",
7 | author_email="1309834+samdobson@users.noreply.github.com",
8 | description="Toggle widget for Streamlit",
9 | long_description="",
10 | long_description_content_type="text/plain",
11 | url="https://github.com/samdobson/streamlit-toggle",
12 | packages=setuptools.find_packages(),
13 | include_package_data=True,
14 | classifiers=[],
15 | python_requires=">=3.6",
16 | install_requires=[
17 | "streamlit >= 0.63",
18 | ],
19 | )
20 |
--------------------------------------------------------------------------------
/streamlit_toggle/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import streamlit.components.v1 as components
3 |
4 | _RELEASE = False
5 | if not _RELEASE:
6 | _component_func = components.declare_component(
7 | "st_toggleswitch",
8 | url="http://localhost:3001",
9 | )
10 | else:
11 | parent_dir = os.path.dirname(os.path.abspath(__file__))
12 | build_dir = os.path.join(parent_dir, "frontend/build")
13 | _component_func = components.declare_component("st_toggleswitch", path=build_dir)
14 |
15 | def st_toggleswitch(label, value=True, disabled=False, key=None):
16 | """Display a toggle switch.
17 |
18 | Parameters
19 | ----------
20 | label: str
21 | A short label explaining to the user what this toggle switch is for.
22 | value: bool
23 | Default the toggle switch to activated when it first renders
24 | (True if not specified).
25 | disabled: bool
26 | Disable the widget so that it cannot be changed
27 | (False if not specified).
28 | key: str or None
29 | An optional key that uniquely identifies this component. If this is
30 | None, and the component's arguments are changed, the component will
31 | be re-mounted in the Streamlit frontend and lose its current state.
32 |
33 | Returns
34 | -------
35 | bool
36 | Whether or not the toggle switch is activated.
37 | """
38 | component_value = _component_func(label=label, value=value, disabled=disabled, key=key, default=value)
39 | return component_value
40 |
41 | if not _RELEASE:
42 | import streamlit as st
43 |
44 | st.subheader("Toggle Switch Streamlit Component")
45 | awesomeness_enabled = st_toggleswitch("Enable awesomeness")
46 | if awesomeness_enabled:
47 | st.markdown("Awesomeness has been enabled!")
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/.env:
--------------------------------------------------------------------------------
1 | # Run the component's dev server on :3001
2 | # (The Streamlit dev server already runs on :3000)
3 | PORT=3001
4 |
5 | # Don't automatically open the web browser on `npm run start`.
6 | BROWSER=none
7 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": false,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "streamlit_component_template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/hoist-non-react-statics": "^3.3.1",
10 | "@types/jest": "^24.0.0",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^16.9.0",
13 | "@types/react-dom": "^16.9.0",
14 | "@types/styletron-engine-atomic": "^1.1.0",
15 | "@types/styletron-react": "^5.0.2",
16 | "@types/styletron-standard": "^2.0.0",
17 | "apache-arrow": "^0.17.0",
18 | "baseui": "^9.85.0",
19 | "bootstrap": "^4.4.1",
20 | "event-target-shim": "^5.0.1",
21 | "hoist-non-react-statics": "^3.3.2",
22 | "react": "^16.13.1",
23 | "react-dom": "^16.13.1",
24 | "react-scripts": "3.4.1",
25 | "styletron-engine-atomic": "^1.4.6",
26 | "styletron-react": "^5.2.7",
27 | "typescript": "~3.7.2"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject"
34 | },
35 | "eslintConfig": {
36 | "extends": "react-app"
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "homepage": "."
51 | }
52 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Streamlit Component
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/StreamlitToggle.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from "react"
2 | import { Checkbox, STYLE_TYPE } from 'baseui/checkbox';
3 | import {
4 | withStreamlitConnection,
5 | StreamlitComponentBase,
6 | Streamlit,
7 | } from "./streamlit"
8 |
9 | interface State {
10 | value: boolean
11 | }
12 |
13 | class StreamlitToggle extends StreamlitComponentBase {
14 | public state = { value: this.props.args["value"] }
15 |
16 | public render = (): ReactNode => {
17 |
18 | return (
19 |
20 |
26 |
27 | {this.props.args["label"]}
28 |
29 | )
30 | }
31 |
32 | private onChange = (e: React.ChangeEvent): void => {
33 | const isChecked = e.currentTarget.checked
34 | this.setState({ value: isChecked }, () => {
35 | Streamlit.setComponentValue(isChecked)
36 | })
37 | }
38 | }
39 |
40 | export default withStreamlitConnection(StreamlitToggle)
41 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import StreamlitToggle from "./StreamlitToggle"
4 | import {Client as Styletron} from 'styletron-engine-atomic';
5 | import {Provider as StyletronProvider} from 'styletron-react';
6 | import {ThemeProvider, LightTheme} from 'baseui';
7 |
8 | const engine = new Styletron();
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById("root")
19 | )
20 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/streamlit/ArrowTable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018-2019 Streamlit Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import { Table, Type } from "apache-arrow"
19 |
20 | type CellType = "blank" | "index" | "columns" | "data"
21 |
22 | export interface ArrowDataframeProto {
23 | data: ArrowTableProto
24 | height: string
25 | width: string
26 | }
27 |
28 | export interface ArrowTableProto {
29 | data: Uint8Array
30 | index: Uint8Array
31 | columns: Uint8Array
32 | styler: Styler
33 | }
34 |
35 | interface Cell {
36 | classNames: string
37 | content: string
38 | id?: string
39 | type: CellType
40 | }
41 |
42 | interface Styler {
43 | caption?: string
44 | displayValuesTable: Table
45 | styles?: string
46 | uuid: string
47 | }
48 |
49 | export class ArrowTable {
50 | private readonly dataTable: Table
51 | private readonly indexTable: Table
52 | private readonly columnsTable: Table
53 | private readonly styler?: Styler
54 |
55 | constructor(
56 | dataBuffer: Uint8Array,
57 | indexBuffer: Uint8Array,
58 | columnsBuffer: Uint8Array,
59 | styler?: any
60 | ) {
61 | this.dataTable = Table.from(dataBuffer)
62 | this.indexTable = Table.from(indexBuffer)
63 | this.columnsTable = Table.from(columnsBuffer)
64 | this.styler = styler
65 | ? {
66 | caption: styler.get("caption"),
67 | displayValuesTable: Table.from(styler.get("displayValues")),
68 | styles: styler.get("styles"),
69 | uuid: styler.get("uuid"),
70 | }
71 | : undefined
72 | }
73 |
74 | get rows(): number {
75 | return this.indexTable.length + this.columnsTable.numCols
76 | }
77 |
78 | get columns(): number {
79 | return this.indexTable.numCols + this.columnsTable.length
80 | }
81 |
82 | get headerRows(): number {
83 | return this.rows - this.dataRows
84 | }
85 |
86 | get headerColumns(): number {
87 | return this.columns - this.dataColumns
88 | }
89 |
90 | get dataRows(): number {
91 | return this.dataTable.length
92 | }
93 |
94 | get dataColumns(): number {
95 | return this.dataTable.numCols
96 | }
97 |
98 | get uuid(): string | undefined {
99 | return this.styler && this.styler.uuid
100 | }
101 |
102 | get caption(): string | undefined {
103 | return this.styler && this.styler.caption
104 | }
105 |
106 | get styles(): string | undefined {
107 | return this.styler && this.styler.styles
108 | }
109 |
110 | get table(): Table {
111 | return this.dataTable
112 | }
113 |
114 | get index(): Table {
115 | return this.indexTable
116 | }
117 |
118 | get columnTable(): Table {
119 | return this.columnsTable
120 | }
121 |
122 | public getCell = (rowIndex: number, columnIndex: number): Cell => {
123 | const isBlankCell =
124 | rowIndex < this.headerRows && columnIndex < this.headerColumns
125 | const isIndexCell =
126 | rowIndex >= this.headerRows && columnIndex < this.headerColumns
127 | const isColumnsCell =
128 | rowIndex < this.headerRows && columnIndex >= this.headerColumns
129 |
130 | if (isBlankCell) {
131 | const classNames = ["blank"]
132 | if (columnIndex > 0) {
133 | classNames.push("level" + rowIndex)
134 | }
135 |
136 | return {
137 | type: "blank",
138 | classNames: classNames.join(" "),
139 | content: "",
140 | }
141 | } else if (isColumnsCell) {
142 | const dataColumnIndex = columnIndex - this.headerColumns
143 | const classNames = [
144 | "col_heading",
145 | "level" + rowIndex,
146 | "col" + dataColumnIndex,
147 | ]
148 |
149 | return {
150 | type: "columns",
151 | classNames: classNames.join(" "),
152 | content: this.getContent(this.columnsTable, dataColumnIndex, rowIndex),
153 | }
154 | } else if (isIndexCell) {
155 | const dataRowIndex = rowIndex - this.headerRows
156 | const classNames = [
157 | "row_heading",
158 | "level" + columnIndex,
159 | "row" + dataRowIndex,
160 | ]
161 |
162 | return {
163 | type: "index",
164 | id: `T_${this.uuid}level${columnIndex}_row${dataRowIndex}`,
165 | classNames: classNames.join(" "),
166 | content: this.getContent(this.indexTable, dataRowIndex, columnIndex),
167 | }
168 | } else {
169 | const dataRowIndex = rowIndex - this.headerRows
170 | const dataColumnIndex = columnIndex - this.headerColumns
171 | const classNames = [
172 | "data",
173 | "row" + dataRowIndex,
174 | "col" + dataColumnIndex,
175 | ]
176 | const content = this.styler
177 | ? this.getContent(
178 | this.styler.displayValuesTable,
179 | dataRowIndex,
180 | dataColumnIndex
181 | )
182 | : this.getContent(this.dataTable, dataRowIndex, dataColumnIndex)
183 |
184 | return {
185 | type: "data",
186 | id: `T_${this.uuid}row${dataRowIndex}_col${dataColumnIndex}`,
187 | classNames: classNames.join(" "),
188 | content,
189 | }
190 | }
191 | }
192 |
193 | public getContent = (
194 | table: Table,
195 | rowIndex: number,
196 | columnIndex: number
197 | ): any => {
198 | const column = table.getColumnAt(columnIndex)
199 | if (column === null) {
200 | return ""
201 | }
202 |
203 | const columnTypeId = this.getColumnTypeId(table, columnIndex)
204 | switch (columnTypeId) {
205 | case Type.Timestamp: {
206 | return this.nanosToDate(column.get(rowIndex))
207 | }
208 | default: {
209 | return column.get(rowIndex)
210 | }
211 | }
212 | }
213 |
214 | /**
215 | * Returns apache-arrow specific typeId of column.
216 | */
217 | private getColumnTypeId(table: Table, columnIndex: number): Type {
218 | return table.schema.fields[columnIndex].type.typeId
219 | }
220 |
221 | private nanosToDate(nanos: number): Date {
222 | return new Date(nanos / 1e6)
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/streamlit/StreamlitReact.tsx:
--------------------------------------------------------------------------------
1 | import hoistNonReactStatics from "hoist-non-react-statics"
2 | import React, { ReactNode } from "react"
3 | import { RenderData, Streamlit } from "./streamlit"
4 |
5 | /**
6 | * Props passed to custom Streamlit components.
7 | */
8 | export interface ComponentProps {
9 | /** Named dictionary of arguments passed from Python. */
10 | args: any
11 |
12 | /** The component's width. */
13 | width: number
14 |
15 | /**
16 | * True if the component should be disabled.
17 | * All components get disabled while the app is being re-run,
18 | * and become re-enabled when the re-run has finished.
19 | */
20 | disabled: boolean
21 | }
22 |
23 | /**
24 | * Optional Streamlit React-based component base class.
25 | *
26 | * You are not required to extend this base class to create a Streamlit
27 | * component. If you decide not to extend it, you should implement the
28 | * `componentDidMount` and `componentDidUpdate` functions in your own class,
29 | * so that your plugin properly resizes.
30 | */
31 | export class StreamlitComponentBase extends React.PureComponent<
32 | ComponentProps,
33 | S
34 | > {
35 | public componentDidMount(): void {
36 | // After we're rendered for the first time, tell Streamlit that our height
37 | // has changed.
38 | Streamlit.setFrameHeight()
39 | }
40 |
41 | public componentDidUpdate(): void {
42 | // After we're updated, tell Streamlit that our height may have changed.
43 | Streamlit.setFrameHeight()
44 | }
45 | }
46 |
47 | /**
48 | * Wrapper for React-based Streamlit components.
49 | *
50 | * Bootstraps the communication interface between Streamlit and the component.
51 | */
52 | export function withStreamlitConnection(
53 | WrappedComponent: React.ComponentType
54 | ): React.ComponentType {
55 | interface WrapperProps {}
56 |
57 | interface WrapperState {
58 | renderData?: RenderData
59 | componentError?: Error
60 | }
61 |
62 | class ComponentWrapper extends React.PureComponent<
63 | WrapperProps,
64 | WrapperState
65 | > {
66 | public constructor(props: WrapperProps) {
67 | super(props)
68 | this.state = {
69 | renderData: undefined,
70 | componentError: undefined,
71 | }
72 | }
73 |
74 | /**
75 | * Error boundary function. This will be called if our wrapped
76 | * component throws an error. We store the caught error in our state,
77 | * and display it in the next render().
78 | */
79 | public static getDerivedStateFromError = (
80 | error: Error
81 | ): Partial => {
82 | return { componentError: error }
83 | }
84 |
85 | public componentDidMount = (): void => {
86 | // Set up event listeners, and signal to Streamlit that we're ready.
87 | // We won't render the component until we receive the first RENDER_EVENT.
88 | Streamlit.events.addEventListener(
89 | Streamlit.RENDER_EVENT,
90 | this.onRenderEvent
91 | )
92 | Streamlit.setComponentReady()
93 | }
94 |
95 | public componentDidUpdate = (): void => {
96 | // If our child threw an error, we display it in render(). In this
97 | // case, the child won't be mounted and therefore won't call
98 | // `setFrameHeight` on its own. We do it here so that the rendered
99 | // error will be visible.
100 | if (this.state.componentError != null) {
101 | Streamlit.setFrameHeight()
102 | }
103 | }
104 |
105 | public componentWillUnmount = (): void => {
106 | Streamlit.events.removeEventListener(
107 | Streamlit.RENDER_EVENT,
108 | this.onRenderEvent
109 | )
110 | }
111 |
112 | /**
113 | * Streamlit is telling this component to redraw.
114 | * We save the render data in State, so that it can be passed to the
115 | * component in our own render() function.
116 | */
117 | private onRenderEvent = (event: Event): void => {
118 | // Update our state with the newest render data
119 | const renderEvent = event as CustomEvent
120 | this.setState({ renderData: renderEvent.detail })
121 | }
122 |
123 | public render = (): ReactNode => {
124 | // If our wrapped component threw an error, display it.
125 | if (this.state.componentError != null) {
126 | return (
127 |
128 |
Component Error
129 | {this.state.componentError.message}
130 |
131 | )
132 | }
133 |
134 | // Don't render until we've gotten our first RENDER_EVENT from Streamlit.
135 | if (this.state.renderData == null) {
136 | return null
137 | }
138 |
139 | return (
140 |
145 | )
146 | }
147 | }
148 |
149 | return hoistNonReactStatics(ComponentWrapper, WrappedComponent)
150 | }
151 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/streamlit/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018-2020 Streamlit Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | // Workaround for type-only exports:
19 | // https://stackoverflow.com/questions/53728230/cannot-re-export-a-type-when-using-the-isolatedmodules-with-ts-3-2-2
20 | import { ComponentProps as ComponentProps_ } from "./StreamlitReact"
21 | import { RenderData as RenderData_ } from "./streamlit"
22 |
23 | export {
24 | StreamlitComponentBase,
25 | withStreamlitConnection,
26 | } from "./StreamlitReact"
27 | export { ArrowTable } from "./ArrowTable"
28 | export { Streamlit } from "./streamlit"
29 | export type ComponentProps = ComponentProps_
30 | export type RenderData = RenderData_
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/src/streamlit/streamlit.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2018-2020 Streamlit Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | // Safari doesn't support the EventTarget class, so we use a shim.
19 | import { EventTarget } from "event-target-shim"
20 | import { ArrowDataframeProto, ArrowTable } from "./ArrowTable"
21 |
22 | /** Data sent in the custom Streamlit render event. */
23 | export interface RenderData {
24 | args: any
25 | disabled: boolean
26 | }
27 |
28 | /** Messages from Component -> Streamlit */
29 | enum ComponentMessageType {
30 | // A component sends this message when it's ready to receive messages
31 | // from Streamlit. Streamlit won't send any messages until it gets this.
32 | // Data: { apiVersion: number }
33 | COMPONENT_READY = "streamlit:componentReady",
34 |
35 | // The component has a new widget value. Send it back to Streamlit, which
36 | // will then re-run the app.
37 | // Data: { value: any }
38 | SET_COMPONENT_VALUE = "streamlit:setComponentValue",
39 |
40 | // The component has a new height for its iframe.
41 | // Data: { height: number }
42 | SET_FRAME_HEIGHT = "streamlit:setFrameHeight",
43 | }
44 |
45 | /**
46 | * Streamlit communication API.
47 | *
48 | * Components can send data to Streamlit via the functions defined here,
49 | * and receive data from Streamlit via the `events` property.
50 | */
51 | export class Streamlit {
52 | /**
53 | * The Streamlit component API version we're targetting.
54 | * There's currently only 1!
55 | */
56 | public static readonly API_VERSION = 1
57 |
58 | public static readonly RENDER_EVENT = "streamlit:render"
59 |
60 | /** Dispatches events received from Streamlit. */
61 | public static readonly events = new EventTarget()
62 |
63 | private static registeredMessageListener = false
64 | private static lastFrameHeight?: number
65 |
66 | /**
67 | * Tell Streamlit that the component is ready to start receiving data.
68 | * Streamlit will defer emitting RENDER events until it receives the
69 | * COMPONENT_READY message.
70 | */
71 | public static setComponentReady = (): void => {
72 | if (!Streamlit.registeredMessageListener) {
73 | // Register for message events if we haven't already
74 | window.addEventListener("message", Streamlit.onMessageEvent)
75 | Streamlit.registeredMessageListener = true
76 | }
77 |
78 | Streamlit.sendBackMsg(ComponentMessageType.COMPONENT_READY, {
79 | apiVersion: Streamlit.API_VERSION,
80 | })
81 | }
82 |
83 | /**
84 | * Report the component's height to Streamlit.
85 | * This should be called every time the component changes its DOM - that is,
86 | * when it's first loaded, and any time it updates.
87 | */
88 | public static setFrameHeight = (height?: number): void => {
89 | if (height === undefined) {
90 | // `height` is optional. If undefined, it defaults to scrollHeight,
91 | // which is the entire height of the element minus its border,
92 | // scrollbar, and margin.
93 | height = document.body.scrollHeight
94 | }
95 |
96 | if (height === Streamlit.lastFrameHeight) {
97 | // Don't bother updating if our height hasn't changed.
98 | return
99 | }
100 |
101 | Streamlit.lastFrameHeight = height
102 | Streamlit.sendBackMsg(ComponentMessageType.SET_FRAME_HEIGHT, { height })
103 | }
104 |
105 | /**
106 | * Set the component's value. This value will be returned to the Python
107 | * script, and the script will be re-run.
108 | *
109 | * For example:
110 | *
111 | * JavaScript:
112 | * Streamlit.setComponentValue("ahoy!")
113 | *
114 | * Python:
115 | * value = st.my_component(...)
116 | * st.write(value) # -> "ahoy!"
117 | *
118 | * The value must be serializable into JSON.
119 | */
120 | public static setComponentValue = (value: any): void => {
121 | Streamlit.sendBackMsg(ComponentMessageType.SET_COMPONENT_VALUE, { value })
122 | }
123 |
124 | /** Receive a ForwardMsg from the Streamlit app */
125 | private static onMessageEvent = (event: MessageEvent): void => {
126 | const type = event.data["type"]
127 | switch (type) {
128 | case Streamlit.RENDER_EVENT:
129 | Streamlit.onRenderMessage(event.data)
130 | break
131 | }
132 | }
133 |
134 | /**
135 | * Handle an untyped Streamlit render event and redispatch it as a
136 | * StreamlitRenderEvent.
137 | */
138 | private static onRenderMessage = (data: any): void => {
139 | let args = data["args"]
140 | if (args == null) {
141 | console.error(
142 | `Got null args in onRenderMessage. This should never happen`
143 | )
144 | args = {}
145 | }
146 |
147 | // Parse our dataframe arguments with arrow, and merge them into our args dict
148 | const dataframeArgs =
149 | data["dfs"] && data["dfs"].length > 0
150 | ? Streamlit.argsDataframeToObject(data["dfs"])
151 | : {}
152 |
153 | args = {
154 | ...args,
155 | ...dataframeArgs,
156 | }
157 |
158 | const disabled = Boolean(data["disabled"])
159 |
160 | // Dispatch a render event!
161 | const eventData = { disabled, args }
162 | const event = new CustomEvent(Streamlit.RENDER_EVENT, {
163 | detail: eventData,
164 | })
165 | Streamlit.events.dispatchEvent(event)
166 | }
167 |
168 | private static argsDataframeToObject = (
169 | argsDataframe: ArgsDataframe[]
170 | ): object => {
171 | const argsDataframeArrow = argsDataframe.map(
172 | ({ key, value }: ArgsDataframe) => [key, Streamlit.toArrowTable(value)]
173 | )
174 | return Object.fromEntries(argsDataframeArrow)
175 | }
176 |
177 | private static toArrowTable = (df: ArrowDataframeProto): ArrowTable => {
178 | const { data, index, columns } = df.data
179 | return new ArrowTable(data, index, columns)
180 | }
181 |
182 | /** Post a message to the Streamlit app. */
183 | private static sendBackMsg = (type: string, data?: any): void => {
184 | window.parent.postMessage(
185 | {
186 | isStreamlitMessage: true,
187 | type: type,
188 | ...data,
189 | },
190 | "*"
191 | )
192 | }
193 | }
194 |
195 | interface ArgsDataframe {
196 | key: string
197 | value: ArrowDataframeProto
198 | }
199 |
--------------------------------------------------------------------------------
/streamlit_toggle/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "react"
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------