Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download button host config #8584

Merged
merged 10 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion frontend/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,14 +409,19 @@ export class App extends PureComponent<Props, State> {
disableFullscreenMode,
enableCustomParentMessages,
mapboxToken,
enforceDownloadInNewTab,
} = response

const appConfig: AppConfig = {
allowedOrigins,
useExternalAuthToken,
enableCustomParentMessages,
}
const libConfig: LibConfig = { mapboxToken, disableFullscreenMode }
const libConfig: LibConfig = {
mapboxToken,
disableFullscreenMode,
enforceDownloadInNewTab,
}

// Set the allowed origins configuration for the host communication:
this.hostCommunicationMgr.setAllowedOrigins(appConfig)
Expand Down
2 changes: 2 additions & 0 deletions frontend/lib/src/components/core/LibContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type LibConfig = {
* Whether to disable the full screen mode all elements / widgets.
*/
disableFullscreenMode?: boolean

enforceDownloadInNewTab?: boolean
}

export interface LibContextProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { WidgetStateManager } from "@streamlit/lib/src/WidgetStateManager"

import { DownloadButton as DownloadButtonProto } from "@streamlit/lib/src/proto"
import { mockEndpoints } from "@streamlit/lib/src/mocks/mocks"
import DownloadButton, { Props } from "./DownloadButton"
import DownloadButton, { Props, createDownloadLink } from "./DownloadButton"

jest.mock("@streamlit/lib/src/WidgetStateManager")
jest.mock("@streamlit/lib/src/StreamlitEndpoints")
Expand Down Expand Up @@ -98,6 +98,23 @@ describe("DownloadButton widget", () => {
)
})

it("has a correct new tab behaviour download link", () => {
const props = getProps()
const sameTabLink = createDownloadLink(
props.endpoints,
props.element.url,
false
)
expect(sameTabLink.getAttribute("target")).toBe("_self")

const newTabLink = createDownloadLink(
props.endpoints,
props.element.url,
true
)
expect(newTabLink.getAttribute("target")).toBe("_blank")
})

it("can set fragmentId on click", () => {
const props = getProps(undefined, { fragmentId: "myFragmentId" })
render(<DownloadButton {...props} />)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import BaseButton, {
import { WidgetStateManager } from "@streamlit/lib/src/WidgetStateManager"
import StreamlitMarkdown from "@streamlit/lib/src/components/shared/StreamlitMarkdown"
import { StreamlitEndpoints } from "@streamlit/lib/src/StreamlitEndpoints"
import { LibContext } from "@streamlit/lib/src/components/core/LibContext"

export interface Props {
endpoints: StreamlitEndpoints
Expand All @@ -34,9 +35,29 @@ export interface Props {
fragmentId?: string
}

export function createDownloadLink(
endpoints: StreamlitEndpoints,
url: string,
enforceDownloadInNewTab: boolean
): HTMLAnchorElement {
const link = document.createElement("a")
const uri = endpoints.buildMediaURL(url)
link.setAttribute("href", uri)
if (enforceDownloadInNewTab) {
link.setAttribute("target", "_blank")
} else {
link.setAttribute("target", "_self")
}
link.setAttribute("download", "")
return link
}

function DownloadButton(props: Props): ReactElement {
const { disabled, element, widgetMgr, width, endpoints, fragmentId } = props
const style = { width }
const {
libConfig: { enforceDownloadInNewTab = false }, // Default to false, if no libConfig, e.g. for tests
} = React.useContext(LibContext)

const kind =
element.type === "primary"
Expand All @@ -47,11 +68,11 @@ function DownloadButton(props: Props): ReactElement {
// Downloads are only done on links, so create a hidden one and click it
// for the user.
widgetMgr.setTriggerValue(element, { fromUi: true }, fragmentId)
const link = document.createElement("a")
const uri = endpoints.buildMediaURL(element.url)
link.setAttribute("href", uri)
link.setAttribute("target", "_self")
link.setAttribute("download", "")
const link = createDownloadLink(
endpoints,
element.url,
enforceDownloadInNewTab
)
link.click()
}

Expand Down
1 change: 1 addition & 0 deletions lib/streamlit/web/server/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ async def get(self) -> None:
"useExternalAuthToken": False,
# Default host configuration settings.
"enableCustomParentMessages": False,
"enforceDownloadInNewTab": False,
}
)
self.set_status(200)
Expand Down
1 change: 1 addition & 0 deletions lib/tests/streamlit/web/server/routes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ def test_allowed_message_origins(self):
"useExternalAuthToken": False,
# Default host configuration settings:
"enableCustomParentMessages": False,
"enforceDownloadInNewTab": False,
},
response_body,
)
Expand Down
Loading