Skip to content

Commit d178578

Browse files
committed
WIP Topbar with doc navigation
1 parent c6833e1 commit d178578

File tree

6 files changed

+203
-112
lines changed

6 files changed

+203
-112
lines changed

examples/introduction.ipynb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
{
2525
"data": {
2626
"application/vnd.jupyter.widget-view+json": {
27-
"model_id": "02d172394132421a994a671c9ebea9ec",
27+
"model_id": "775ca81880e24cd79694489a30522bed",
2828
"version_major": 2,
2929
"version_minor": 0
3030
},
@@ -84,7 +84,7 @@
8484
{
8585
"data": {
8686
"application/vnd.jupyter.widget-view+json": {
87-
"model_id": "83936e8edc8047638f3f7645ed238180",
87+
"model_id": "d137286814214b5493ffc90ccf68d28f",
8888
"version_major": 2,
8989
"version_minor": 0
9090
},
@@ -98,7 +98,9 @@
9898
],
9999
"source": [
100100
"abstract = \"\"\"\n",
101-
"Background: Despite advances in targeted therapy, treatment options for metastatic NSCLC progressing after initial therapy remains limited. HSP90 is an ATP-dependent molecular chaperone that plays a vital role in protein stabilization. Some HSP90 client proteins are key regulators in cell proliferation and survival. Many mutant oncoproteins are more dependent on HSP90 for proper folding and stability compared to their wildtype counterparts. AUY922 potently inhibits HSP90, showing preclinical activity in a wide range of cancer cell lines, including NSCLC (1). Phase I clinical trials established 70 mg/m2 as the dose for further development (2). A single agent phase II trial demonstrated clinical activity of AUY922 in NSCLC, particularly molecular subsets with driver mutations in the known HSP90 client proteins, epidermal growth factor receptor (EGFR) and anaplastic lymphoma kinase (ALK) (3). Pemetrexed is a folate antimetabolite chemotherapeutic approved for use in advanced non-squamous, NSCLC. In pre-clinical models, mRNA for dihydrofolate reductase (DHFR), a target of pemetrexed, reliably decreased in response to AUY922 exposure (1). These findings suggest that the combination of AUY922 and premetrexed in NSCLC is worthy of investigation. Methods: Adult patients with previously treated stage IV non-squamous, NSCLC, measureable disease per RECIST 1.1, ECOG performance status < 2, and life expectancy > 3 months are eligible for this open label phase Ib clinical trial (NCT01784640). A standard 3 x 3 design will evaluate 3 cohorts, all with pemetrexed at the standard 500 mg/m2 dose, plus: AUY922 40 mg/m2, 55 mg/ m2, and 70 mg/m2 qwk. Enrollment of the 70 mg/m2 qwk cohort has been open since November 2014 and is currently ongoing. After the optimal dose for further evaluation is determined, an additional 20 patients will be enrolled at that dose. This expansion phase will focus on patients with EGFR mutations and ALK gene rearrangements. The primary endpoint is safety and tolerability of AUY922 combined with pemetrexed in patients with previously treated non-squamous NSCLC.\"\"\"\n",
101+
"Background: Despite advances in targeted therapy, treatment options for metastatic NSCLC progressing after initial therapy remains limited. HSP90 is an ATP-dependent molecular chaperone that plays a vital role in protein stabilization. Some HSP90 client proteins are key regulators in cell proliferation and survival. Many mutant oncoproteins are more dependent on HSP90 for proper folding and stability compared to their wildtype counterparts. AUY922 potently inhibits HSP90, showing preclinical activity in a wide range of cancer cell lines, including NSCLC (1). Phase I clinical trials established 70 mg/m2 as the dose for further development (2). A single agent phase II trial demonstrated clinical activity of AUY922 in NSCLC, particularly molecular subsets with driver mutations in the known HSP90 client proteins, epidermal growth factor receptor (EGFR) and anaplastic lymphoma kinase (ALK) (3). Pemetrexed is a folate antimetabolite chemotherapeutic approved for use in advanced non-squamous, NSCLC. In pre-clinical models, mRNA for dihydrofolate reductase (DHFR), a target of pemetrexed, reliably decreased in response to AUY922 exposure (1). These findings suggest that the combination of AUY922 and premetrexed in NSCLC is worthy of investigation. Methods: Adult patients with previously treated stage IV non-squamous, NSCLC, measureable disease per RECIST 1.1, ECOG performance status < 2, and life expectancy > 3 months are eligible for this open label phase Ib clinical trial (NCT01784640). A standard 3 x 3 design will evaluate 3 cohorts, all with pemetrexed at the standard 500 mg/m2 dose, plus: AUY922 40 mg/m2, 55 mg/ m2, and 70 mg/m2 qwk. Enrollment of the 70 mg/m2 qwk cohort has been open since November 2014 and is currently ongoing. After the optimal dose for further evaluation is determined, an additional 20 patients will be enrolled at that dose. This expansion phase will focus on patients with EGFR mutations and ALK gene rearrangements. The primary endpoint is safety and tolerability of AUY922 combined with pemetrexed in patients with previously treated non-squamous NSCLC.\n",
102+
"\"\"\"\n",
103+
"\n",
102104
"other = jupyterannotate.AnnotateWidget(\n",
103105
" text=abstract,\n",
104106
" labels=[\"condition\", \"intervention\", \"outcome\"],\n",
@@ -108,16 +110,21 @@
108110
},
109111
{
110112
"cell_type": "code",
111-
"execution_count": 5,
113+
"execution_count": 6,
112114
"metadata": {},
113115
"outputs": [
114116
{
115117
"data": {
116118
"text/plain": [
117-
"[]"
119+
"[{'start': 84, 'end': 89, 'text': 'NSCLC', 'label': 'condition'},\n",
120+
" {'start': 1002, 'end': 1007, 'text': 'NSCLC', 'label': 'condition'},\n",
121+
" {'start': 1992,\n",
122+
" 'end': 2015,\n",
123+
" 'text': 'safety and tolerability',\n",
124+
" 'label': 'outcome'}]"
118125
]
119126
},
120-
"execution_count": 5,
127+
"execution_count": 6,
121128
"metadata": {},
122129
"output_type": "execute_result"
123130
}

src/components/Annotate.ts

Lines changed: 25 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,23 @@
11
import { h, VNode } from "preact";
2-
import { useEffect, useRef, useState } from "preact/hooks";
2+
import { useState } from "preact/hooks";
33
import { Span } from "../annotate";
44

5-
const SpanLabel = ({ text, label }: { text: string; label: string }): VNode => {
6-
return h("span", { className: "span" }, [
7-
text,
8-
h("span", { className: "spanLabel" }, label),
9-
]);
10-
};
11-
12-
const Labels = ({
13-
labels,
14-
selectedLabel,
15-
onChangeLabel,
16-
}: {
17-
labels: string[];
18-
selectedLabel: string;
19-
onChangeLabel: (label: string) => void;
20-
}): VNode => {
21-
const labelNodes = labels.map((label) => {
22-
const className = label === selectedLabel ? "label selected" : "label";
23-
24-
return h("div", { className, onClick: () => onChangeLabel(label) }, label);
25-
});
26-
return h("div", { className: "labelContainer" }, labelNodes);
27-
};
5+
import TopBar from "./TopBar";
6+
import Highlightable from "./Highlightable";
287

29-
const getHighlightedText = (text: string, spans: Span[]): VNode[] => {
30-
const chunks: VNode[] = [];
31-
let prevOffset = 0;
32-
33-
spans
34-
.sort((a, b) => (a.start > b.start ? 1 : -1))
35-
.forEach((span) => {
36-
chunks.push(
37-
h(
38-
"span",
39-
{ "data-offset": prevOffset },
40-
text.slice(prevOffset, span.start)
41-
)
42-
);
43-
chunks.push(SpanLabel({ text: span.text, label: span.label }));
44-
prevOffset = span.end;
45-
});
46-
chunks.push(h("span", { "data-offset": prevOffset }, text.slice(prevOffset)));
47-
48-
return chunks;
49-
};
50-
51-
function Highlightable({
52-
text,
53-
selectedLabel,
54-
spans,
55-
onUpdate,
56-
}: {
8+
interface Props {
579
text: string;
58-
selectedLabel: string;
59-
spans: Span[];
60-
onUpdate: (spans: Span[]) => void;
61-
}): VNode {
62-
const ref = useRef(null);
63-
64-
function onSelect(event: any): void {
65-
const dataset = event.target?.dataset || {};
66-
const offset = parseInt(dataset.offset || "0", 10);
67-
const selected = window.getSelection();
68-
const selectedText = selected?.toString() || "";
69-
if (!selectedText.trim() || !selected) {
70-
return;
71-
}
72-
73-
const start =
74-
selected.anchorOffset > selected.focusOffset
75-
? selected.focusOffset
76-
: selected.anchorOffset;
77-
const end =
78-
selected.anchorOffset < selected.focusOffset
79-
? selected.focusOffset
80-
: selected.anchorOffset;
81-
82-
onUpdate(
83-
spans.concat([
84-
{
85-
start: start + offset,
86-
end: end + offset,
87-
text: selectedText,
88-
label: selectedLabel,
89-
},
90-
])
91-
);
92-
}
93-
94-
useEffect(() => {
95-
const el: any = ref.current;
96-
if (el) {
97-
el.addEventListener("mouseup", onSelect);
98-
}
99-
}, [ref.current, onSelect]);
100-
101-
return h(
102-
"div",
103-
{ ref, className: "content" },
104-
getHighlightedText(text, spans)
105-
);
10+
labels: string[];
11+
onUpdateSpans: (span: Span[]) => void;
10612
}
10713

108-
export default function Annotate({ text, labels, onUpdateSpans }: any): VNode {
14+
export default function Annotate({
15+
text,
16+
labels,
17+
onUpdateSpans,
18+
}: Props): VNode {
19+
const totalDocs = 15;
20+
const docIndex = 5;
10921
const [selectedLabel, setSelectedLabel] = useState<string>("");
11022
const [spans, setSpans] = useState<Span[]>([]);
11123
const onChangeLabel = (label: string) => {
@@ -117,12 +29,19 @@ export default function Annotate({ text, labels, onUpdateSpans }: any): VNode {
11729
onUpdateSpans(updatedSpans);
11830
};
11931

32+
const onChangeNav = (docIndex: number) => {
33+
console.log(docIndex);
34+
};
35+
12036
return h("div", null, [
121-
h(
122-
"div",
123-
{ className: "topBar" },
124-
h(Labels, { selectedLabel, labels, onChangeLabel })
125-
),
37+
h(TopBar, {
38+
selectedLabel,
39+
labels,
40+
totalDocs,
41+
docIndex,
42+
onChangeLabel,
43+
onChangeNav,
44+
}),
12645
h(Highlightable, { text, selectedLabel, spans, onUpdate }),
12746
]);
12847
}

src/components/Highlightable.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { h, VNode } from "preact";
2+
import { useEffect, useRef } from "preact/hooks";
3+
import { Span } from "../annotate";
4+
5+
const SpanLabel = ({ text, label }: { text: string; label: string }): VNode => {
6+
return h("span", { className: "span" }, [
7+
text,
8+
h("span", { className: "spanLabel" }, label),
9+
]);
10+
};
11+
12+
const getHighlightedText = (text: string, spans: Span[]): VNode[] => {
13+
const chunks: VNode[] = [];
14+
let prevOffset = 0;
15+
16+
spans
17+
.sort((a, b) => (a.start > b.start ? 1 : -1))
18+
.forEach((span) => {
19+
chunks.push(
20+
h(
21+
"span",
22+
{ "data-offset": prevOffset },
23+
text.slice(prevOffset, span.start)
24+
)
25+
);
26+
chunks.push(SpanLabel({ text: span.text, label: span.label }));
27+
prevOffset = span.end;
28+
});
29+
chunks.push(h("span", { "data-offset": prevOffset }, text.slice(prevOffset)));
30+
31+
return chunks;
32+
};
33+
34+
interface Props {
35+
text: string;
36+
selectedLabel: string;
37+
spans: Span[];
38+
onUpdate: (span: Span[]) => void;
39+
}
40+
41+
const Highlightable = ({
42+
text,
43+
selectedLabel,
44+
spans,
45+
onUpdate,
46+
}: Props): VNode => {
47+
const ref = useRef(null);
48+
49+
function onSelect(event: any): void {
50+
const dataset = event.target?.dataset || {};
51+
const offset = parseInt(dataset.offset || "0", 10);
52+
const selected = window.getSelection();
53+
const selectedText = selected?.toString() || "";
54+
if (!selectedText.trim() || !selected) {
55+
return;
56+
}
57+
58+
const start =
59+
selected.anchorOffset > selected.focusOffset
60+
? selected.focusOffset
61+
: selected.anchorOffset;
62+
const end =
63+
selected.anchorOffset < selected.focusOffset
64+
? selected.focusOffset
65+
: selected.anchorOffset;
66+
67+
onUpdate(
68+
spans.concat([
69+
{
70+
start: start + offset,
71+
end: end + offset,
72+
text: selectedText,
73+
label: selectedLabel,
74+
},
75+
])
76+
);
77+
}
78+
79+
useEffect(() => {
80+
const el: any = ref.current;
81+
if (el) {
82+
el.addEventListener("mouseup", onSelect);
83+
}
84+
}, [ref.current, onSelect]);
85+
86+
return h(
87+
"div",
88+
{ ref, className: "content" },
89+
getHighlightedText(text, spans)
90+
);
91+
};
92+
93+
export default Highlightable;

src/components/Labels.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { h, VNode } from "preact";
2+
3+
const Labels = ({
4+
labels,
5+
selectedLabel,
6+
onChangeLabel,
7+
}: {
8+
labels: string[];
9+
selectedLabel: string;
10+
onChangeLabel: (label: string) => void;
11+
}): VNode => {
12+
const labelNodes = labels.map((label) => {
13+
const className = label === selectedLabel ? "label selected" : "label";
14+
15+
return h("div", { className, onClick: () => onChangeLabel(label) }, label);
16+
});
17+
return h("div", { className: "labelContainer" }, labelNodes);
18+
};
19+
20+
export default Labels;

src/components/Nav.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { h, VNode } from "preact";
2+
3+
interface Props {
4+
docIndex: number;
5+
totalDocs: number;
6+
onChangeNav: (docIndex: number) => void;
7+
}
8+
9+
const Nav = ({ docIndex, totalDocs, onChangeNav }: Props): VNode => {
10+
const onPrev = () => {
11+
onChangeNav(docIndex - 1);
12+
};
13+
const onNext = () => {
14+
onChangeNav(docIndex + 1);
15+
};
16+
17+
return h("div", { className: "nav" }, [
18+
h("div", { onClick: onPrev }, "<"),
19+
h("div", null, `${docIndex} / ${totalDocs}`),
20+
h("div", { onClick: onNext }, ">"),
21+
]);
22+
};
23+
24+
export default Nav;

src/components/TopBar.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { h, VNode } from "preact";
2+
import Labels from "./Labels";
3+
import Nav from "./Nav";
4+
5+
interface Props {
6+
labels: string[];
7+
selectedLabel: string;
8+
docIndex: number;
9+
totalDocs: number;
10+
onChangeLabel: (label: string) => void;
11+
onChangeNav: (docIndex: number) => void;
12+
}
13+
14+
const TopBar = ({
15+
labels,
16+
selectedLabel,
17+
docIndex,
18+
totalDocs,
19+
onChangeLabel,
20+
onChangeNav,
21+
}: Props): VNode => {
22+
return h("div", { className: "topBar" }, [
23+
h(Labels, { labels, selectedLabel, onChangeLabel }),
24+
h(Nav, { docIndex, totalDocs, onChangeNav }),
25+
]);
26+
};
27+
28+
export default TopBar;

0 commit comments

Comments
 (0)