Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### New features

* `Session` objects now have a `set_message_handler(name, fn)` method that allows you to register a message handler function that will be called when a request message with the given name is received from the client (via `Shiny.shinyapp.makeRequest()` (JS)). (#1253)

* Experimental: `@render.data_frame` return values of `DataTable` and `DataGrid` support `mode="edit"` to enable editing of the data table cells. (#1198)

* `ui.card()` and `ui.value_box()` now take an `id` argument that, when provided, is used to report the full screen state of the card or value box to the server. For example, when using `ui.card(id = "my_card", full_screen = TRUE)` you can determine if the card is currently in full screen mode by reading the boolean value of `input.my_card()["full_screen"]`. (#1215)
Expand Down
7 changes: 6 additions & 1 deletion js/dataframe/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getCellEditMapValue,
} from "./cell-edit-map";
import { updateCellsData } from "./data-update";
import type { PatchInfo } from "./types";

// States
// # √ Ready
Expand Down Expand Up @@ -50,6 +51,7 @@ export type CellState = keyof typeof CellStateEnum;
interface TableBodyCellProps {
id: string | null;
cell: Cell<unknown[], unknown>;
patchInfo: PatchInfo;
columns: readonly string[];
editCellsIsAllowed: boolean;
editRowIndex: number | null;
Expand All @@ -66,6 +68,7 @@ interface TableBodyCellProps {
export const TableBodyCell: FC<TableBodyCellProps> = ({
id,
cell,
patchInfo,
columns,
editCellsIsAllowed,
editRowIndex,
Expand Down Expand Up @@ -137,7 +140,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
// * When a td is focused, Have esc key move focus to the table
// * When table is focused, Have esc key blur the focus
// TODO-barret-future; Combat edit mode being independent of selection mode
// * In row / column selection mode, allow for arrowoutput_binding_request_handler key navigation by focusing on a single cell, not a TR
// * In row / column selection mode, allow for arrow key navigation by focusing on a single cell, not a TR
// * If a cell is focused,
// * `enter key` allows you to go into edit mode; If editing is turned off, the selection is toggled
// * `space key` allows you toggle the selection of the cell
Expand Down Expand Up @@ -253,6 +256,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
// updateCellsData updates the underlying data via `setData` and `setCellEditMap`
updateCellsData({
id,
patchInfo: patchInfo,
patches: [
{
rowIndex,
Expand All @@ -273,6 +277,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
});
}, [
id,
patchInfo,
rowIndex,
columnIndex,
value,
Expand Down
9 changes: 4 additions & 5 deletions js/dataframe/data-update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ResponseValue, makeRequestPromise } from "./request";
import type { CellState } from "./cell";
import { CellStateEnum } from "./cell";
import { CellEdit, SetCellEditMap, makeCellEditMapKey } from "./cell-edit-map";
import type { PatchInfo } from "./types";

export type CellPatch = {
rowIndex: number;
Expand All @@ -19,6 +20,7 @@ export type CellPatchPy = {

export function updateCellsData({
id,
patchInfo,
patches,
onSuccess,
onError,
Expand All @@ -27,6 +29,7 @@ export function updateCellsData({
setCellEditMap,
}: {
id: string | null;
patchInfo: PatchInfo;
patches: CellPatch[];
onSuccess: (values: CellPatch[]) => void;
onError: (err: string) => void;
Expand All @@ -47,12 +50,8 @@ export function updateCellsData({
});

makeRequestPromise({
method: "output_binding_request_handler",
method: patchInfo.key,
args: [
// id: string
id,
// handler: string
"patches",
// list[CellPatch]
patchesPy,
],
Expand Down
21 changes: 15 additions & 6 deletions js/dataframe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { SortArrow } from "./sort-arrows";
import css from "./styles.scss";
import { useTabindexGroup } from "./tabindex-group";
import { useSummary } from "./table-summary";
import { EditModeEnum, PandasData, TypeHint } from "./types";
import { EditModeEnum, PandasData, PatchInfo, TypeHint } from "./types";

// TODO-barret; Type support
// export interface PandasData<TIndex> {
Expand Down Expand Up @@ -87,11 +87,16 @@ declare module "@tanstack/table-core" {
interface ShinyDataGridProps<TIndex> {
id: string | null;
data: PandasData<TIndex>;
patchInfo: PatchInfo;
bgcolor?: string;
}

const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
const { id, data, bgcolor } = props;
const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
id,
data,
patchInfo,
bgcolor,
}) => {
const { columns, type_hints: typeHints, data: rowData } = data;
const { width, height, fill, filters: withFilters } = data.options;

Expand Down Expand Up @@ -458,6 +463,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
id={id}
key={cell.id}
cell={cell}
patchInfo={patchInfo}
editCellsIsAllowed={editCellsIsAllowed}
columns={columns}
editRowIndex={editRowIndex}
Expand Down Expand Up @@ -645,10 +651,12 @@ export class ShinyDataFrameOutput extends HTMLElement {
}
}

renderValue(data: unknown) {
renderValue(
value: null | { patchInfo: PatchInfo; data: PandasData<unknown> }
) {
this.clearError();

if (!data) {
if (!value) {
this.reactRoot!.render(null);
return;
}
Expand All @@ -657,7 +665,8 @@ export class ShinyDataFrameOutput extends HTMLElement {
<StrictMode>
<ShinyDataGrid
id={this.id}
data={data as PandasData<unknown>}
data={value.data}
patchInfo={value.patchInfo}
bgcolor={getComputedBgColor(this)}
></ShinyDataGrid>
</StrictMode>
Expand Down
4 changes: 4 additions & 0 deletions js/dataframe/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ export interface PandasData<TIndex> {
type_hints?: ReadonlyArray<TypeHint>;
options: DataGridOptions;
}

export interface PatchInfo {
key: string;
}
44 changes: 33 additions & 11 deletions shiny/render/_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from typing import (
TYPE_CHECKING,
Any,
Awaitable,
Callable,
Literal,
Optional,
Protocol,
Expand All @@ -23,7 +25,7 @@
from .._typing_extensions import Self
from ..session._utils import get_current_session, require_active_session
from ._dataframe_unsafe import serialize_numpy_dtypes
from .renderer import Jsonifiable, Renderer, ValueFn, output_binding_request_handler
from .renderer import Jsonifiable, Renderer, ValueFn
from .renderer._utils import JsonifiableDict

if TYPE_CHECKING:
Expand Down Expand Up @@ -618,19 +620,31 @@ async def patches_fn(

self.set_patch_fn(patch_fn)
self.set_patches_fn(patches_fn)
# self._add_message_handlers()

# # TODO-barret; Use dynamic route instead of message handlers? Gut all of `output_binding_request_handler?` TODO: Talk with Joe
# session = get_current_session()
# if session is not None:
# session.dynamic_route("data-frame-patches-{id}", self._handle_patches)
def _set_patches_handler_impl(
self,
handler: Callable[..., Awaitable[Jsonifiable]] | None,
) -> str:
session = self._get_session()
key = session.set_message_handler(
f"data_frame_patches_{self.output_id}",
handler,
)
return key

def _reset_patches_handler(self) -> str:
return self._set_patches_handler_impl(None)

# output_binding_request_handler(session, "name", self._handle_patches_1)
def _set_patches_handler(self) -> str:
"""
Set the client patches handler for the data frame.

This method **must be** called as late as possible as it depends on the ID of the output.
"""
return self._set_patches_handler_impl(self._patches_handler)

# To be called by session's output_binding_request_handler message handler on this data_frame instance
@output_binding_request_handler
# Do not change this method name unless you update corresponding code in `/js/dataframe/`!!
async def _handle_patches(self, patches: list[CellPatch]) -> Jsonifiable:
async def _patches_handler(self, patches: list[CellPatch]) -> Jsonifiable:
# TODO-barret; verify that the patches are in the correct format

# Call user's cell update method to retrieve formatted values
Expand Down Expand Up @@ -713,6 +727,7 @@ def _set_output_metadata(self, *, output_id: str) -> None:
async def render(self) -> Jsonifiable:
# Reset value
self._reset_reactives()
self._reset_patches_handler()

value = await self.fn()
if value is None:
Expand All @@ -726,8 +741,15 @@ async def render(self) -> Jsonifiable:
)
)

# Send info to client
patch_key = self._set_patches_handler()
self._value.set(value)
return value.to_payload()
return {
"data": value.to_payload(),
"patchInfo": {
"key": patch_key,
},
}

async def _send_message_to_browser(self, handler: str, obj: dict[str, Any]):

Expand Down
4 changes: 1 addition & 3 deletions shiny/render/renderer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from ._dispatch import output_binding_request_handler
from ._renderer import ( # noqa: F401
from ._renderer import (
AsyncValueFn,
Jsonifiable,
Renderer,
Expand All @@ -13,5 +12,4 @@
"Jsonifiable",
"AsyncValueFn",
"RendererT",
"output_binding_request_handler",
)
84 changes: 0 additions & 84 deletions shiny/render/renderer/_dispatch.py

This file was deleted.

Loading