@marimo-team/islands 0.18.2 → 0.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{constants-DWBOe162.js → constants-D_G8vnDk.js} +5 -4
- package/dist/{formats-7RSCCoSI.js → formats-Bi_tbdwB.js} +21 -22
- package/dist/{glide-data-editor-D-Ia_Jsv.js → glide-data-editor-DXF8E-QD.js} +2 -2
- package/dist/main.js +280 -148
- package/dist/style.css +1 -1
- package/dist/{types-Dunk85GC.js → types-DclGb0Yh.js} +1 -1
- package/dist/{vega-component-kU4hFYYJ.js → vega-component-BFcH2SqR.js} +8 -8
- package/package.json +1 -1
- package/src/components/app-config/user-config-form.tsx +14 -1
- package/src/components/data-table/context-menu.tsx +7 -3
- package/src/components/data-table/filter-pills.tsx +2 -1
- package/src/components/data-table/filters.ts +11 -2
- package/src/components/editor/cell/CreateCellButton.tsx +5 -3
- package/src/components/editor/cell/collapse.tsx +2 -2
- package/src/components/editor/chrome/components/contribute-snippet-button.tsx +22 -103
- package/src/components/editor/controls/duplicate-shortcut-banner.tsx +50 -0
- package/src/components/editor/controls/keyboard-shortcuts.tsx +25 -2
- package/src/components/editor/notebook-banner.tsx +1 -1
- package/src/components/editor/notebook-cell.tsx +4 -3
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +6 -6
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +3 -3
- package/src/components/pages/home-page.tsx +6 -0
- package/src/components/scratchpad/scratchpad.tsx +2 -1
- package/src/core/constants.ts +10 -0
- package/src/core/layout/useTogglePresenting.ts +69 -25
- package/src/core/state/__mocks__/mocks.ts +1 -0
- package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +449 -0
- package/src/hooks/useDuplicateShortcuts.ts +145 -0
- package/src/plugins/impl/NumberPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +1 -1
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +67 -47
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +2 -57
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +23 -19
- package/src/plugins/impl/anywidget/model.ts +68 -41
- package/src/plugins/impl/data-frames/utils/__tests__/operators.test.ts +2 -0
- package/src/plugins/impl/data-frames/utils/operators.ts +1 -0
- package/src/plugins/impl/vega/vega.css +5 -0
- package/src/plugins/layout/NavigationMenuPlugin.tsx +24 -22
- package/src/plugins/layout/StatPlugin.tsx +43 -23
- package/src/utils/__tests__/data-views.test.ts +495 -13
- package/src/utils/__tests__/json-parser.test.ts +1 -1
- package/src/utils/data-views.ts +134 -16
- package/src/utils/json/base64.ts +8 -0
|
@@ -15,7 +15,7 @@ import { t as require_jsx_runtime } from "./jsx-runtime-CTBg5pdT.js";
|
|
|
15
15
|
import { t as require_react_dom } from "./react-dom-BZdwbVNI.js";
|
|
16
16
|
import { m as useEvent_default } from "./useTheme-ByTGDerd.js";
|
|
17
17
|
import { t as toString_default } from "./toString-DBXBHXIe.js";
|
|
18
|
-
import {
|
|
18
|
+
import { i as debounce_default, n as Constants } from "./constants-D_G8vnDk.js";
|
|
19
19
|
import { t as memoizeLastValue } from "./once-DjP4Kbhy.js";
|
|
20
20
|
var ChevronRight = createLucideIcon("chevron-right", [["path", {
|
|
21
21
|
d: "m9 18 6-6-6-6",
|
|
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { t as require_react } from "./react-BSzAiXXz.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-CNX0xYDF.js";
|
|
4
4
|
import "./Combination-DnWHe36P.js";
|
|
5
|
-
import {
|
|
5
|
+
import { S as CircleQuestionMark, a as AlertTitle, m as asRemoteURL, n as useDeepCompareMemoize, o as isValid, r as Alert, t as arrow } from "./formats-Bi_tbdwB.js";
|
|
6
6
|
import "./clsx-D2KVTYnW.js";
|
|
7
7
|
import { l as Events } from "./button-XnD6ylpt.js";
|
|
8
8
|
import { o as Objects, s as Logger } from "./hotkeys-CwkyZ6ZF.js";
|
|
@@ -18,7 +18,7 @@ import "./_baseUniq-CPOFUArp.js";
|
|
|
18
18
|
import "./_baseIsEqual-CwglS7T6.js";
|
|
19
19
|
import "./merge-DPdZQMPt.js";
|
|
20
20
|
import "./now-BA1FgVte.js";
|
|
21
|
-
import {
|
|
21
|
+
import { i as debounce_default } from "./constants-D_G8vnDk.js";
|
|
22
22
|
import { a as tooltipHandler, n as vegaLoadData } from "./loader-ZngagiXO.js";
|
|
23
23
|
import { t as uniq_default } from "./uniq-CCIhWjDg.js";
|
|
24
24
|
import "./zod-Bvx56F8M.js";
|
|
@@ -509,8 +509,8 @@ async function resolveVegaSpecData(e) {
|
|
|
509
509
|
} catch {
|
|
510
510
|
return e2;
|
|
511
511
|
}
|
|
512
|
-
let
|
|
513
|
-
return w[E2.pathname] =
|
|
512
|
+
let D = await vegaLoadData(E2.href, e2.data.format);
|
|
513
|
+
return w[E2.pathname] = D, {
|
|
514
514
|
...e2,
|
|
515
515
|
data: { name: E2.pathname }
|
|
516
516
|
};
|
|
@@ -540,17 +540,17 @@ var VegaComponent = (e) => {
|
|
|
540
540
|
spec: P,
|
|
541
541
|
embedOptions: A
|
|
542
542
|
}), w[5] = D, w[6] = A, w[7] = O, w[8] = P, w[9] = E, w[10] = T, w[11] = I) : I = w[11], I;
|
|
543
|
-
}, LoadedVegaComponent = ({ value: e, setValue: w, chartSelection: T, fieldSelection:
|
|
543
|
+
}, LoadedVegaComponent = ({ value: e, setValue: w, chartSelection: T, fieldSelection: O, spec: A, embedOptions: N }) => {
|
|
544
544
|
let { theme: L } = useTheme(), R = (0, import_react.useRef)(null), z = (0, import_react.useRef)(void 0), [B, V] = (0, import_react.useState)(), H = (0, import_react.useMemo)(() => N && "actions" in N ? N.actions : {
|
|
545
545
|
source: false,
|
|
546
546
|
compiled: false
|
|
547
|
-
}, [N]), U = useDeepCompareMemoize(
|
|
547
|
+
}, [N]), U = useDeepCompareMemoize(A), W = (0, import_react.useMemo)(() => makeSelectable(fixRelativeUrl(U), {
|
|
548
548
|
chartSelection: T,
|
|
549
|
-
fieldSelection:
|
|
549
|
+
fieldSelection: O
|
|
550
550
|
}), [
|
|
551
551
|
U,
|
|
552
552
|
T,
|
|
553
|
-
|
|
553
|
+
O
|
|
554
554
|
]), G = (0, import_react.useMemo)(() => getSelectionParamNames(W), [W]), K = useEvent_default((T2) => {
|
|
555
555
|
w({
|
|
556
556
|
...e,
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from "@/core/config/config-schema";
|
|
42
42
|
import { getAppWidths } from "@/core/config/widths";
|
|
43
43
|
import { marimoVersionAtom } from "@/core/meta/state";
|
|
44
|
+
import { viewStateAtom } from "@/core/mode";
|
|
44
45
|
import { useRequestClient } from "@/core/network/requests";
|
|
45
46
|
import { isWasm } from "@/core/wasm/utils";
|
|
46
47
|
import { useDebouncedCallback } from "@/hooks/useDebounce";
|
|
@@ -119,7 +120,19 @@ export const UserConfigForm: React.FC = () => {
|
|
|
119
120
|
const [activeCategory, setActiveCategory] = useAtom(
|
|
120
121
|
activeUserConfigCategoryAtom,
|
|
121
122
|
);
|
|
122
|
-
|
|
123
|
+
|
|
124
|
+
let capabilities = useAtomValue(capabilitiesAtom);
|
|
125
|
+
const isHome = useAtomValue(viewStateAtom).mode === "home";
|
|
126
|
+
// The home page does not fetch kernel capabilities, so we just turn them all on
|
|
127
|
+
if (isHome) {
|
|
128
|
+
capabilities = {
|
|
129
|
+
terminal: true,
|
|
130
|
+
pylsp: true,
|
|
131
|
+
ty: true,
|
|
132
|
+
basedpyright: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
const marimoVersion = useAtomValue(marimoVersionAtom);
|
|
124
137
|
const { locale } = useLocale();
|
|
125
138
|
const { saveUserConfig } = useRequestClient();
|
|
@@ -95,11 +95,11 @@ export const CellContextMenu = <TData,>({
|
|
|
95
95
|
const column = cell.column;
|
|
96
96
|
const canFilter = column.getCanFilter() && column.columnDef.meta?.filterType;
|
|
97
97
|
|
|
98
|
-
const handleFilterCell = () => {
|
|
98
|
+
const handleFilterCell = (operator: "in" | "not_in") => {
|
|
99
99
|
column.setFilterValue(
|
|
100
100
|
Filter.select({
|
|
101
101
|
options: [cell.getValue()],
|
|
102
|
-
operator
|
|
102
|
+
operator,
|
|
103
103
|
}),
|
|
104
104
|
);
|
|
105
105
|
};
|
|
@@ -119,10 +119,14 @@ export const CellContextMenu = <TData,>({
|
|
|
119
119
|
{canFilter && (
|
|
120
120
|
<>
|
|
121
121
|
<ContextMenuSeparator />
|
|
122
|
-
<ContextMenuItem onClick={handleFilterCell}>
|
|
122
|
+
<ContextMenuItem onClick={() => handleFilterCell("in")}>
|
|
123
123
|
<FilterIcon className="mo-dropdown-icon h-3 w-3" />
|
|
124
124
|
Filter by this value
|
|
125
125
|
</ContextMenuItem>
|
|
126
|
+
<ContextMenuItem onClick={() => handleFilterCell("not_in")}>
|
|
127
|
+
<FilterIcon className="mo-dropdown-icon h-3 w-3" />
|
|
128
|
+
Remove rows with this value
|
|
129
|
+
</ContextMenuItem>
|
|
126
130
|
</>
|
|
127
131
|
)}
|
|
128
132
|
</ContextMenuContent>
|
|
@@ -96,7 +96,8 @@ function formatValue(value: ColumnFilterValue, timeFormatter: DateFormatter) {
|
|
|
96
96
|
const stringifiedOptions = value.options.map((o) =>
|
|
97
97
|
stringifyUnknownValue({ value: o }),
|
|
98
98
|
);
|
|
99
|
-
|
|
99
|
+
const operator = value.operator === "in" ? "is in" : "not in";
|
|
100
|
+
return `${operator} [${stringifiedOptions.join(", ")}]`;
|
|
100
101
|
}
|
|
101
102
|
if (value.type === "text") {
|
|
102
103
|
return `contains "${value.text}"`;
|
|
@@ -7,6 +7,7 @@ import type { ConditionType } from "@/plugins/impl/data-frames/schema";
|
|
|
7
7
|
import type { ColumnId } from "@/plugins/impl/data-frames/types";
|
|
8
8
|
import type { OperatorType } from "@/plugins/impl/data-frames/utils/operators";
|
|
9
9
|
import { assertNever } from "@/utils/assertNever";
|
|
10
|
+
import { Logger } from "@/utils/Logger";
|
|
10
11
|
|
|
11
12
|
declare module "@tanstack/react-table" {
|
|
12
13
|
//allows us to define custom properties for our columns
|
|
@@ -192,12 +193,20 @@ export function filterToFilterCondition(
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
return [];
|
|
195
|
-
case "select":
|
|
196
|
+
case "select": {
|
|
197
|
+
let operator = filter.operator;
|
|
198
|
+
if (filter.operator !== "in" && filter.operator !== "not_in") {
|
|
199
|
+
Logger.warn("Invalid operator for select filter", {
|
|
200
|
+
operator: filter.operator,
|
|
201
|
+
});
|
|
202
|
+
operator = "in"; // default to in operator
|
|
203
|
+
}
|
|
196
204
|
return {
|
|
197
205
|
column_id: columnId,
|
|
198
|
-
operator
|
|
206
|
+
operator,
|
|
199
207
|
value: filter.options,
|
|
200
208
|
};
|
|
209
|
+
}
|
|
201
210
|
|
|
202
211
|
default:
|
|
203
212
|
assertNever(filter);
|
|
@@ -46,7 +46,7 @@ export const CreateCellButton = ({
|
|
|
46
46
|
<div>{baseTooltipContent}</div>
|
|
47
47
|
<div className="text-xs text-muted-foreground font-medium pt-1 -mt-2 border-t border-border">
|
|
48
48
|
{<MinimalHotkeys shortcut={shortcut} className="inline" />}{" "}
|
|
49
|
-
<span>
|
|
49
|
+
<span>for other cell types</span>
|
|
50
50
|
</div>
|
|
51
51
|
</div>
|
|
52
52
|
);
|
|
@@ -81,7 +81,9 @@ export const CreateCellButton = ({
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const handleButtonClick = (e: React.MouseEvent) => {
|
|
84
|
-
|
|
84
|
+
const hasModifier =
|
|
85
|
+
oneClickShortcut === "shift" ? e.shiftKey : e.metaKey || e.ctrlKey;
|
|
86
|
+
if (!hasModifier) {
|
|
85
87
|
e.preventDefault();
|
|
86
88
|
e.stopPropagation();
|
|
87
89
|
addPythonCell();
|
|
@@ -123,7 +125,7 @@ export const CreateCellButton = ({
|
|
|
123
125
|
>
|
|
124
126
|
<Tooltip content={finalTooltipContent}>
|
|
125
127
|
<PlusIcon
|
|
126
|
-
strokeWidth={
|
|
128
|
+
strokeWidth={3}
|
|
127
129
|
size={14}
|
|
128
130
|
className="opacity-60 hover:opacity-90"
|
|
129
131
|
/>
|
|
@@ -44,9 +44,9 @@ export const CollapseToggle: React.FC<Props> = (props) => {
|
|
|
44
44
|
|
|
45
45
|
const Arrow = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|
46
46
|
return isCollapsed ? (
|
|
47
|
-
<ChevronRightIcon className="w-5 h-5 shrink-0" />
|
|
47
|
+
<ChevronRightIcon className="w-5 h-5 shrink-0 opacity-60" strokeWidth={2} />
|
|
48
48
|
) : (
|
|
49
|
-
<ChevronDownIcon className="w-5 h-5 shrink-0" />
|
|
49
|
+
<ChevronDownIcon className="w-5 h-5 shrink-0 opacity-60" strokeWidth={2} />
|
|
50
50
|
);
|
|
51
51
|
};
|
|
52
52
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { EditorView } from "@codemirror/view";
|
|
4
3
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
-
import React, { type PropsWithChildren
|
|
4
|
+
import React, { type PropsWithChildren } from "react";
|
|
6
5
|
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
7
6
|
import { Button } from "@/components/ui/button";
|
|
8
7
|
import {
|
|
@@ -12,12 +11,7 @@ import {
|
|
|
12
11
|
DialogHeader,
|
|
13
12
|
DialogTitle,
|
|
14
13
|
} from "@/components/ui/dialog";
|
|
15
|
-
import { Input } from "@/components/ui/input";
|
|
16
|
-
import { Textarea } from "@/components/ui/textarea";
|
|
17
|
-
import { toast } from "@/components/ui/use-toast";
|
|
18
14
|
import { Constants } from "@/core/constants";
|
|
19
|
-
import { LazyAnyLanguageCodeMirror } from "@/plugins/impl/code/LazyAnyLanguageCodeMirror";
|
|
20
|
-
import { useTheme } from "@/theme/useTheme";
|
|
21
15
|
|
|
22
16
|
export const ContributeSnippetButton: React.FC<PropsWithChildren> = ({
|
|
23
17
|
children,
|
|
@@ -33,106 +27,31 @@ export const ContributeSnippetButton: React.FC<PropsWithChildren> = ({
|
|
|
33
27
|
);
|
|
34
28
|
};
|
|
35
29
|
|
|
36
|
-
const extensions = [EditorView.lineWrapping];
|
|
37
|
-
|
|
38
30
|
const ContributeSnippetModal: React.FC<{
|
|
39
31
|
onClose: () => void;
|
|
40
32
|
}> = ({ onClose }) => {
|
|
41
|
-
const [code, setCode] = useState("");
|
|
42
|
-
const { theme } = useTheme();
|
|
43
|
-
|
|
44
33
|
return (
|
|
45
|
-
<DialogContent className="w-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
onClose();
|
|
68
|
-
toast({
|
|
69
|
-
title: "Snippet Submitted",
|
|
70
|
-
description:
|
|
71
|
-
"Thank you for contributing! We will review your snippet shortly.",
|
|
72
|
-
});
|
|
73
|
-
}}
|
|
74
|
-
>
|
|
75
|
-
<DialogHeader>
|
|
76
|
-
<DialogTitle>Contribute a Snippet</DialogTitle>
|
|
77
|
-
<DialogDescription>
|
|
78
|
-
Have a useful snippet you want to share with the community? Submit
|
|
79
|
-
it here or make a pull request{" "}
|
|
80
|
-
<a
|
|
81
|
-
href={Constants.githubPage}
|
|
82
|
-
target="_blank"
|
|
83
|
-
className="underline"
|
|
84
|
-
>
|
|
85
|
-
on GitHub
|
|
86
|
-
</a>
|
|
87
|
-
.
|
|
88
|
-
</DialogDescription>
|
|
89
|
-
</DialogHeader>
|
|
90
|
-
<div className="flex flex-col gap-6 py-4">
|
|
91
|
-
<Input
|
|
92
|
-
id="title"
|
|
93
|
-
name="title"
|
|
94
|
-
autoFocus={true}
|
|
95
|
-
placeholder="Title"
|
|
96
|
-
required={true}
|
|
97
|
-
autoComplete="off"
|
|
98
|
-
/>
|
|
99
|
-
<Textarea
|
|
100
|
-
id="description"
|
|
101
|
-
name="description"
|
|
102
|
-
autoFocus={true}
|
|
103
|
-
placeholder="Description"
|
|
104
|
-
rows={5}
|
|
105
|
-
required={true}
|
|
106
|
-
autoComplete="off"
|
|
107
|
-
/>
|
|
108
|
-
<input type="hidden" name="code" value={code} />
|
|
109
|
-
<LazyAnyLanguageCodeMirror
|
|
110
|
-
theme={theme === "dark" ? "dark" : "light"}
|
|
111
|
-
language="python"
|
|
112
|
-
className="cm border rounded overflow-hidden"
|
|
113
|
-
extensions={extensions}
|
|
114
|
-
value={code}
|
|
115
|
-
onChange={setCode}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
<DialogFooter>
|
|
119
|
-
<Button
|
|
120
|
-
data-testid="snippet-cancel-button"
|
|
121
|
-
variant="secondary"
|
|
122
|
-
onClick={onClose}
|
|
123
|
-
>
|
|
124
|
-
Cancel
|
|
125
|
-
</Button>
|
|
126
|
-
<Button
|
|
127
|
-
data-testid="snippet-send-button"
|
|
128
|
-
aria-label="Save"
|
|
129
|
-
variant="default"
|
|
130
|
-
type="submit"
|
|
131
|
-
>
|
|
132
|
-
Send
|
|
133
|
-
</Button>
|
|
134
|
-
</DialogFooter>
|
|
135
|
-
</form>
|
|
34
|
+
<DialogContent className="max-w-md">
|
|
35
|
+
<DialogHeader>
|
|
36
|
+
<DialogTitle>Contribute a Snippet</DialogTitle>
|
|
37
|
+
<DialogDescription>
|
|
38
|
+
Have a useful snippet you want to share with the community? Make a
|
|
39
|
+
pull request{" "}
|
|
40
|
+
<a href={Constants.githubPage} target="_blank" className="underline">
|
|
41
|
+
on GitHub
|
|
42
|
+
</a>
|
|
43
|
+
.
|
|
44
|
+
</DialogDescription>
|
|
45
|
+
</DialogHeader>
|
|
46
|
+
<DialogFooter>
|
|
47
|
+
<Button
|
|
48
|
+
data-testid="snippet-close-button"
|
|
49
|
+
variant="default"
|
|
50
|
+
onClick={onClose}
|
|
51
|
+
>
|
|
52
|
+
Close
|
|
53
|
+
</Button>
|
|
54
|
+
</DialogFooter>
|
|
136
55
|
</DialogContent>
|
|
137
56
|
);
|
|
138
57
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { AlertTriangleIcon } from "lucide-react";
|
|
4
|
+
import { KeyboardHotkeys } from "@/components/shortcuts/renderShortcut";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
6
|
+
import type { DuplicateGroup } from "@/hooks/useDuplicateShortcuts";
|
|
7
|
+
|
|
8
|
+
interface DuplicateShortcutBannerProps {
|
|
9
|
+
duplicates: DuplicateGroup[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Banner component that warns about duplicate keyboard shortcuts.
|
|
14
|
+
* Displays a warning when multiple actions share the same key binding.
|
|
15
|
+
*/
|
|
16
|
+
export const DuplicateShortcutBanner: React.FC<
|
|
17
|
+
DuplicateShortcutBannerProps
|
|
18
|
+
> = ({ duplicates }) => {
|
|
19
|
+
// Don't render if no duplicates
|
|
20
|
+
if (duplicates.length === 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Alert variant="warning" className="mb-4">
|
|
26
|
+
<AlertTriangleIcon className="h-4 w-4" />
|
|
27
|
+
<AlertTitle>Duplicate shortcuts</AlertTitle>
|
|
28
|
+
<AlertDescription>
|
|
29
|
+
<p className="mb-2">
|
|
30
|
+
Multiple actions are assigned to the same keyboard shortcut:
|
|
31
|
+
</p>
|
|
32
|
+
<ul className="space-y-2">
|
|
33
|
+
{duplicates.map(({ key, actions }) => (
|
|
34
|
+
<li key={key} className="text-xs">
|
|
35
|
+
<div className="flex items-center gap-2 mb-1">
|
|
36
|
+
<KeyboardHotkeys shortcut={key} />
|
|
37
|
+
<span className="font-semibold">is used by:</span>
|
|
38
|
+
</div>
|
|
39
|
+
<ul className="ml-6 list-disc">
|
|
40
|
+
{actions.map(({ action, name }) => (
|
|
41
|
+
<li key={action}>{name}</li>
|
|
42
|
+
))}
|
|
43
|
+
</ul>
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
</ul>
|
|
47
|
+
</AlertDescription>
|
|
48
|
+
</Alert>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { atom, useAtom, useAtomValue } from "jotai";
|
|
4
|
-
import { EditIcon, XIcon } from "lucide-react";
|
|
4
|
+
import { AlertTriangleIcon, EditIcon, XIcon } from "lucide-react";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Input } from "@/components/ui/input";
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "@/core/hotkeys/hotkeys";
|
|
16
16
|
import { isPlatformMac } from "@/core/hotkeys/shortcuts";
|
|
17
17
|
import { useRequestClient } from "@/core/network/requests";
|
|
18
|
+
import { useDuplicateShortcuts } from "../../../hooks/useDuplicateShortcuts";
|
|
18
19
|
import { useHotkey } from "../../../hooks/useHotkey";
|
|
19
20
|
import { KeyboardHotkeys } from "../../shortcuts/renderShortcut";
|
|
20
21
|
import {
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
DialogPortal,
|
|
26
27
|
DialogTitle,
|
|
27
28
|
} from "../../ui/dialog";
|
|
29
|
+
import { DuplicateShortcutBanner } from "./duplicate-shortcut-banner";
|
|
28
30
|
|
|
29
31
|
export const keyboardShortcutsAtom = atom(false);
|
|
30
32
|
|
|
@@ -37,6 +39,10 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
37
39
|
const [config, setConfig] = useResolvedMarimoConfig();
|
|
38
40
|
const hotkeys = useAtomValue(hotkeysAtom);
|
|
39
41
|
const { saveUserConfig } = useRequestClient();
|
|
42
|
+
const { duplicates, hasDuplicate, getDuplicatesFor } = useDuplicateShortcuts(
|
|
43
|
+
hotkeys,
|
|
44
|
+
"Markdown",
|
|
45
|
+
);
|
|
40
46
|
|
|
41
47
|
useHotkey("global.showHelp", () => setIsOpen((v) => !v));
|
|
42
48
|
|
|
@@ -214,6 +220,9 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
214
220
|
);
|
|
215
221
|
}
|
|
216
222
|
|
|
223
|
+
const isDuplicate = hasDuplicate(action);
|
|
224
|
+
const duplicateActions = isDuplicate ? getDuplicatesFor(action) : [];
|
|
225
|
+
|
|
217
226
|
return (
|
|
218
227
|
<div
|
|
219
228
|
key={action}
|
|
@@ -231,7 +240,20 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
231
240
|
<div className="w-3 h-3" />
|
|
232
241
|
)}
|
|
233
242
|
<KeyboardHotkeys className="justify-end" shortcut={hotkey.key} />
|
|
234
|
-
<
|
|
243
|
+
<div className="flex items-center gap-1">
|
|
244
|
+
<span>{hotkey.name.toLowerCase()}</span>
|
|
245
|
+
{isDuplicate && (
|
|
246
|
+
<div className="group relative inline-flex">
|
|
247
|
+
<AlertTriangleIcon className="w-3 h-3 text-(--yellow-11)" />
|
|
248
|
+
<div className="invisible group-hover:visible absolute left-0 top-5 z-10 w-max max-w-xs rounded-md bg-(--yellow-2) border border-(--yellow-7) p-2 text-xs text-(--yellow-11) shadow-md">
|
|
249
|
+
Also used by:{" "}
|
|
250
|
+
{duplicateActions
|
|
251
|
+
.map((a) => hotkeys.getHotkey(a).name.toLowerCase())
|
|
252
|
+
.join(", ")}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
235
257
|
</div>
|
|
236
258
|
);
|
|
237
259
|
};
|
|
@@ -279,6 +301,7 @@ export const KeyboardShortcuts: React.FC = () => {
|
|
|
279
301
|
<DialogHeader>
|
|
280
302
|
<DialogTitle>Shortcuts</DialogTitle>
|
|
281
303
|
</DialogHeader>
|
|
304
|
+
<DuplicateShortcutBanner duplicates={duplicates} />
|
|
282
305
|
<div className="flex flex-row gap-3">
|
|
283
306
|
<div className="w-1/2">
|
|
284
307
|
{renderGroup("Editing")}
|
|
@@ -26,7 +26,7 @@ export const NotebookBanner: React.FC<Props> = ({ width }) => {
|
|
|
26
26
|
<div
|
|
27
27
|
className={cn(
|
|
28
28
|
"flex flex-col gap-4 mb-5 print:hidden",
|
|
29
|
-
width === "columns" && "
|
|
29
|
+
width === "columns" && "w-full max-w-[80vw]",
|
|
30
30
|
)}
|
|
31
31
|
>
|
|
32
32
|
{banners.map((banner) => (
|
|
@@ -29,6 +29,7 @@ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
|
|
|
29
29
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
30
30
|
import { autocompletionKeymap } from "@/core/codemirror/cm";
|
|
31
31
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
32
|
+
import { CSSClasses } from "@/core/constants";
|
|
32
33
|
import { canCollapseOutline } from "@/core/dom/outline";
|
|
33
34
|
import { isErrorMime } from "@/core/mime";
|
|
34
35
|
import type { AppMode } from "@/core/mode";
|
|
@@ -349,7 +350,7 @@ const ReadonlyCellComponent = forwardRef(
|
|
|
349
350
|
<OutputArea
|
|
350
351
|
allowExpand={false}
|
|
351
352
|
forceExpand={true}
|
|
352
|
-
className=
|
|
353
|
+
className={CSSClasses.outputArea}
|
|
353
354
|
cellId={cellId}
|
|
354
355
|
output={cellRuntime.output}
|
|
355
356
|
stale={outputIsStale(cellRuntime, cellData.edited)}
|
|
@@ -508,7 +509,7 @@ const EditableCellComponent = ({
|
|
|
508
509
|
allowExpand={true}
|
|
509
510
|
// Force expand when markdown is hidden
|
|
510
511
|
forceExpand={isMarkdownCodeHidden}
|
|
511
|
-
className=
|
|
512
|
+
className={CSSClasses.outputArea}
|
|
512
513
|
cellId={cellId}
|
|
513
514
|
output={cellRuntime.output}
|
|
514
515
|
stale={isStaleCell}
|
|
@@ -1168,7 +1169,7 @@ const SetupCellComponent = ({
|
|
|
1168
1169
|
<OutputArea
|
|
1169
1170
|
allowExpand={true}
|
|
1170
1171
|
forceExpand={true}
|
|
1171
|
-
className=
|
|
1172
|
+
className={CSSClasses.outputArea}
|
|
1172
1173
|
cellId={cellId}
|
|
1173
1174
|
output={cellRuntime.output}
|
|
1174
1175
|
stale={false}
|
|
@@ -534,13 +534,13 @@ describe("AnsiReducer streaming with append()", () => {
|
|
|
534
534
|
describe("AnsiReducer color preservation", () => {
|
|
535
535
|
const CASES = [
|
|
536
536
|
// SGR sequences
|
|
537
|
-
"\
|
|
537
|
+
"\u001B[34mBlue text\u001B[m normal text\u001B[31mRed text\u001B[0m",
|
|
538
538
|
// Complex SGR with parameters
|
|
539
|
-
"\
|
|
539
|
+
"\u001B[1;31mBold Red\u001B[0m \u001B[48;5;240mGray bg\u001B[0m",
|
|
540
540
|
// Character set selection
|
|
541
|
-
"Text\
|
|
541
|
+
"Text\u001B(BMore\u001B(0Graphics\u001B(B",
|
|
542
542
|
// Complex case
|
|
543
|
-
"\
|
|
543
|
+
"\u001B[34m[D 251201 15:32:24 cell_runner:695]\u001B(B\u001B[m Running post_execution hooks in context\n\u001B[34m[D 251201 15:32:24 hooks_post_execution:65]\u001B(B\u001B[m Acquiring graph lock to update cell import workspace\n\u001B[34m[D 251201 15:32:24 hooks_post_execution:67]\u001B(B\u001B[m Acquired graph lock to update import workspace.\n",
|
|
544
544
|
];
|
|
545
545
|
|
|
546
546
|
test.each(CASES)("preserves ANSI color codes", (input) => {
|
|
@@ -554,12 +554,12 @@ describe("AnsiReducer color preservation", () => {
|
|
|
554
554
|
// Test that color codes work alongside cursor movements
|
|
555
555
|
// Note: when cursor moves up, lines below are discarded (tqdm behavior)
|
|
556
556
|
const result = reducer.reduce(
|
|
557
|
-
"Line1\n\
|
|
557
|
+
"Line1\n\u001B[31mRed\u001B[0m\u001B[1A\u001B[32mGreen\u001B[0m",
|
|
558
558
|
);
|
|
559
559
|
// After moving up from row 1 to row 0, row 1 is discarded
|
|
560
560
|
// Green is written at the end of row 0
|
|
561
561
|
expect(result).toMatchInlineSnapshot(
|
|
562
|
-
`"Line1 \
|
|
562
|
+
`"Line1 \u001B[32mGreen\u001B[0m"`,
|
|
563
563
|
);
|
|
564
564
|
});
|
|
565
565
|
});
|
|
@@ -29,7 +29,7 @@ import { isOutputEmpty } from "@/core/cells/outputs";
|
|
|
29
29
|
import type { CellData, CellRuntimeState } from "@/core/cells/types";
|
|
30
30
|
import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
|
|
31
31
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
32
|
-
import { KnownQueryParams } from "@/core/constants";
|
|
32
|
+
import { CSSClasses, KnownQueryParams } from "@/core/constants";
|
|
33
33
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
34
34
|
import { showCodeInRunModeAtom } from "@/core/meta/state";
|
|
35
35
|
import { isErrorMime } from "@/core/mime";
|
|
@@ -339,7 +339,7 @@ const VerticalCell = memo(
|
|
|
339
339
|
<OutputArea
|
|
340
340
|
allowExpand={true}
|
|
341
341
|
output={output}
|
|
342
|
-
className=
|
|
342
|
+
className={CSSClasses.outputArea}
|
|
343
343
|
cellId={cellId}
|
|
344
344
|
stale={outputStale}
|
|
345
345
|
loading={loading}
|
|
@@ -394,7 +394,7 @@ const VerticalCell = memo(
|
|
|
394
394
|
<OutputArea
|
|
395
395
|
allowExpand={mode === "edit"}
|
|
396
396
|
output={output}
|
|
397
|
-
className=
|
|
397
|
+
className={CSSClasses.outputArea}
|
|
398
398
|
cellId={cellId}
|
|
399
399
|
stale={outputStale}
|
|
400
400
|
loading={loading}
|
|
@@ -169,6 +169,12 @@ const WorkspaceNotebooks: React.FC = () => {
|
|
|
169
169
|
return (
|
|
170
170
|
<WorkspaceRootContext value={workspace.root}>
|
|
171
171
|
<div className="flex flex-col gap-2">
|
|
172
|
+
{workspace.hasMore && (
|
|
173
|
+
<Banner kind="warn" className="rounded p-4">
|
|
174
|
+
Showing first {workspace.fileCount} files. Your workspace has more
|
|
175
|
+
files.
|
|
176
|
+
</Banner>
|
|
177
|
+
)}
|
|
172
178
|
<Header
|
|
173
179
|
Icon={BookTextIcon}
|
|
174
180
|
control={
|
|
@@ -17,6 +17,7 @@ import { HTMLCellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
|
17
17
|
import { DEFAULT_CELL_NAME } from "@/core/cells/names";
|
|
18
18
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
19
19
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
20
|
+
import { CSSClasses } from "@/core/constants";
|
|
20
21
|
import { useRequestClient } from "@/core/network/requests";
|
|
21
22
|
import type { CellConfig } from "@/core/network/types";
|
|
22
23
|
import { LazyAnyLanguageCodeMirror } from "@/plugins/impl/code/LazyAnyLanguageCodeMirror";
|
|
@@ -149,7 +150,7 @@ export const ScratchPad: React.FC = () => {
|
|
|
149
150
|
<OutputArea
|
|
150
151
|
allowExpand={false}
|
|
151
152
|
output={output}
|
|
152
|
-
className=
|
|
153
|
+
className={CSSClasses.outputArea}
|
|
153
154
|
cellId={cellId}
|
|
154
155
|
stale={false}
|
|
155
156
|
loading={false}
|
package/src/core/constants.ts
CHANGED
|
@@ -50,3 +50,13 @@ export const KnownQueryParams = {
|
|
|
50
50
|
*/
|
|
51
51
|
showChrome: "show-chrome",
|
|
52
52
|
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* CSS class names used throughout the application
|
|
56
|
+
*/
|
|
57
|
+
export const CSSClasses = {
|
|
58
|
+
/**
|
|
59
|
+
* Class name for cell output areas
|
|
60
|
+
*/
|
|
61
|
+
outputArea: "output-area",
|
|
62
|
+
};
|