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

feat: Implement Data frame editor.WF-66 #529

Merged
merged 2 commits into from
Nov 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@
{"id": "e296866a-75d2-4677-b55d-3c1456113b89", "type": "text", "content": {"text": "Refreshing automatically using a timer."}, "isCodeManaged": false, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "position": 1}
{"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12", "type": "dataframe", "content": {"dataframe": "@{random_df}", "enableDownload": "", "enableSearch": "", "fontStyle": "monospace"}, "isCodeManaged": false, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121", "position": 1}
{"id": "fdf38e46-c01e-4a93-94d5-e187f9e4c823", "type": "text", "content": {"primaryTextColor": "#8a8a8a", "text": "_pgcf_ stands for \"Pigeon Coefficient\" and is a meaningless, randomly-generated value.", "useMarkdown": "yes"}, "isCodeManaged": false, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121", "position": 2}
{"id": "e2c46zr4072th36z", "type": "tab", "content": {"name": "Dataframe"}, "handlers": {}, "isCodeManaged": false, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360", "position": 6}
{"id": "qelh30k75scw87ma", "type": "dataframe", "content": {"dataframe": "@{editable_df}", "enableRecordAdd": "yes", "enableRecordUpdate": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "e2c46zr4072th36z", "position": 0}
39 changes: 38 additions & 1 deletion apps/hello/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import statistics

import numpy as np
Expand Down Expand Up @@ -65,6 +66,17 @@ def _get_main_df():
main_df = pd.read_csv("assets/main_df.csv")
return main_df


def _get_editable_df():

df = pd.DataFrame({
'number': [1, 2, 3],
'boolean': [True, False, True],
'object': [{"updatedAt": None}, {"updatedAt": None}, {"updatedAt": None}],
'text': ['one', 'two', 'three'],
})
return wf.EditableDataframe(df)

def _get_highlighted_members():
sample_df = _get_main_df().sample(3).set_index("name", drop=False)
sample = sample_df.to_dict("index")
Expand Down Expand Up @@ -126,11 +138,36 @@ def _update_scatter_chart(state):
)
state["scatter_chart"] = fig

# STATE INIT

# UPDATES DATAFRAME

# Subscribe this event handler to the `wf-dataframe-add` event
def on_editable_df_record_add(state, payload):
state['editable_df'].record_add(payload)


# Subscribe this event handler to the `wf-dataframe-update` event
def on_editable_df_record_change(state, payload):
payload['record']['object']['updatedAt'] = str(datetime.datetime.now())
state['editable_df'].record_update(payload)

# Subscribe this event handler to the `wf-dataframe-action` event
def on_editable_df_record_action(state, payload):
madeindjs marked this conversation as resolved.
Show resolved Hide resolved
record_index = payload['record_index']
if payload['action'] == 'remove':
state['editable_df'].record_remove(payload)
if payload['action'] == 'open':
state['editable_df_open_text'] = str(state['editable_df'].record(record_index)['text'])
if payload['action'] == 'notify':
state.add_notification("info", "Test", "Notify on this row : " + str(state['editable_df'].record(record_index)))


# STATE INIT

initial_state = wf.init_state({
"main_df": _get_main_df(),
"editable_df": _get_editable_df(),
"editable_df_open_text": "<none>",
"highlighted_members": _get_highlighted_members(),
"random_df": _generate_random_df(),
"hue_rotation": 26,
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/dataframe.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ def on_record_action(state, payload):
This event corresponds to a quick action in the drop-down menu to the left of the dataframe.
"""
record_index = payload['record_index']
if payload.action == 'remove':
if payload['action'] == 'remove':
state['mydf'].record_remove(payload)
if payload.action == 'open':
if payload['action'] == 'open':
state['record'] = state['df'].record(record_index) # dict representation of record
```

Expand Down
133 changes: 133 additions & 0 deletions src/ui/src/components/core/base/BaseDropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup lang="ts">
import { onMounted, onUnmounted, PropType, ref } from "vue";

defineProps({
options: {
type: Object as PropType<Record<string, string>>,
required: true,
},
});

const emits = defineEmits({
selected: (key: string) => typeof key === "string",
});

/**
* @deprecated use `useId` from Vue 3.5 when we upgrade
*/
function useId() {
return Math.random().toString();
}

const trigger = ref<Element>();

const popoverId = useId();
const popover = ref<Element & { hidePopover: () => void }>();
const closePopover = () => popover.value?.hidePopover();

const popoverTop = ref("unset");
const popoverLeft = ref("unset");

function computePopoverPosition() {
// the popover use an overlay with an absolute position, so we have to positionate it manually according to the trigger position
const boundingRect = trigger.value.getBoundingClientRect();
popoverTop.value = `${boundingRect.top + boundingRect.height + 4 + window.pageYOffset}px`;
popoverLeft.value = `${boundingRect.left + window.pageXOffset}px`;
}

function onItemClick(key: string) {
emits("selected", key);
closePopover();
}

onMounted(() => window.addEventListener("scroll", closePopover));
onUnmounted(() => window.removeEventListener("scroll", closePopover));
</script>

<template>
madeindjs marked this conversation as resolved.
Show resolved Hide resolved
<div class="BaseDropdown">
<button
ref="trigger"
:popovertarget="popoverId"
class="BaseDropdown__trigger"
@click="computePopoverPosition"
>
<i class="material-symbols-outlined">more_horiz</i>
</button>
<div
:id="popoverId"
ref="popover"
class="BaseDropdown__dropdown"
popover
:style="{ top: popoverTop, left: popoverLeft }"
>
<button
v-for="[key, value] of Object.entries(options)"
:key="key"
class="BaseDropdown__dropdown_item"
tabindex="1"
@click.capture="onItemClick(key)"
>
{{ value }}
</button>
</div>
</div>
</template>

<style scoped>
.BaseDropdown {
position: relative;
}

.BaseDropdown__trigger {
cursor: pointer;
background-color: var(--separatorColor);
border: 1px solid var(--emptinessColor);
height: 16px;
width: 16px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}

.BaseDropdown__dropdown {
position: absolute;
z-index: 1;
top: 18px;
background-color: white;

flex-direction: column;

min-width: 120px;
margin: 0;

border-radius: 4px;
border: 1px solid var(--separatorColor);
box-shadow: var(--containerShadow);
}
.BaseDropdown__dropdown:popover-open {
display: flex;
}

.BaseDropdown__dropdown_item {
/* reset button */
background-color: transparent;
border: none;

text-align: left;

padding: 8px 16px;
border-bottom: 1px solid var(--separatorColor);
cursor: pointer;
}
.BaseDropdown__dropdown_item:focus {
outline-offset: -2px;
}
.BaseDropdown__dropdown_item:hover {
background-color: var(--separatorColor);
}
.BaseDropdown__dropdown_item:last-child {
border-bottom: unset;
}
</style>
Loading