Commit c5c669a7 authored by Dominik Schwabe's avatar Dominik Schwabe
Browse files

added markup scroll on click

parent 45b5b97a
......@@ -11,7 +11,7 @@ import { Badge } from "./utils/Badge";
import { Button } from "./utils/Button";
import { Bars, EyeClosed, EyeOpen, ThumbsDown, ThumbsUp } from "./utils/Icons";
import { CenterLoading } from "./utils/Loading";
import { Markup } from "./utils/Markup";
import { Markup, useMarkupScroll } from "./utils/Markup";
const Feedback = ({ summary }) => {
const { name, summaryText, originalText, url } = summary;
......@@ -215,7 +215,7 @@ const generateStatistics = (text, summaryMarkup) => {
};
};
const Summary = ({ markup, summary, markupState, showMarkup }) => {
const Summary = ({ markup, summary, markupState, scrollState, showMarkup }) => {
const { originalText, summaryText } = summary;
const { numWords, percentOverlap } = generateStatistics(originalText, markup[1]);
......@@ -225,7 +225,7 @@ const Summary = ({ markup, summary, markupState, showMarkup }) => {
<Badge>{`${numWords} words`}</Badge>
<Badge>{`${(percentOverlap * 100).toFixed(0)}% overlap`}</Badge>
</div>
<Markup markups={markup[1]} markupState={markupState} showMarkup={showMarkup} />
<Markup markups={markup[1]} markupState={markupState} scrollState={scrollState} showMarkup={showMarkup} />
<div className="uk-flex uk-flex-right">
<Feedback summary={summary} />
</div>
......@@ -233,7 +233,7 @@ const Summary = ({ markup, summary, markupState, showMarkup }) => {
);
};
// Processed document
const Document = ({ title, markup, markupState, showMarkup, clearMarkups }) => (
const Document = ({ title, markup, markupState, scrollState, showMarkup, clearMarkups }) => (
<>
<div>
<h4 style={{ margin: "10px", marginBottom: "0" }}>{title}</h4>
......@@ -241,7 +241,7 @@ const Document = ({ title, markup, markupState, showMarkup, clearMarkups }) => (
className="uk-card uk-card-default uk-card-body"
style={{ height: "60vh", width: "auto", overflow: "auto", padding: "20px" }}
>
<Markup markups={markup} markupState={markupState} showMarkup={showMarkup} />
<Markup markups={markup} markupState={markupState} scrollState={scrollState} showMarkup={showMarkup} />
</div>
<button
className=" uk-button uk-button-primary uk-margin-top uk-width-1-1"
......@@ -264,6 +264,7 @@ const SummaryTabView = ({
const [summaryIndex, setSummaryIndex] = useState(0);
const { summarizers } = useContext(SummarizersContext);
const markupState = useState(null);
const scrollState = useMarkupScroll();
return (
<div className="uk-flex">
......@@ -276,6 +277,7 @@ const SummaryTabView = ({
title={title}
markup={markups[summaryIndex][0]}
markupState={markupState}
scrollState={scrollState}
showMarkup={showOverlap}
/>
</div>
......@@ -309,6 +311,7 @@ const SummaryTabView = ({
summary={summaries[index]}
showMarkup={showOverlap}
markupState={markupState}
scrollState={scrollState}
/>
</li>
))}
......
import React, { useMemo, useState, memo } from "react";
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
const innerHoverStyle = { background: "yellow", color: "black", display: "relative" };
const baseMarkupStyle = { padding: "2px", borderRadius: "0px" };
const baseMarkupStyle = { padding: "1px", borderRadius: "0px" };
const outerHoverStyle = { ...baseMarkupStyle, ...innerHoverStyle };
const TaggedMarkup = ({ markup, markupState, showMarkup }) => {
const useMarkupScroll = () => useState([null, null, null]);
const Scroll = ({ docIndex, matchOrder, groupSizes, tag, scrollState, allowScroll, children }) => {
const [scrollMarkup, setScrollMarkup] = scrollState;
const [scrollDoc, scrollTag, scrollOrder] = scrollMarkup;
const scrollNext = () => {
if (allowScroll && scrollTag === tag && scrollDoc === 1 - docIndex)
setScrollMarkup([scrollDoc, scrollTag, (scrollOrder + 1) % groupSizes[scrollDoc]]);
else setScrollMarkup([1 - docIndex, tag, 0]);
};
const scrollRef = useRef();
useEffect(() => {
if (docIndex === scrollMarkup[0] && tag === scrollMarkup[1] && matchOrder === scrollMarkup[2]) {
scrollRef.current.scrollIntoView({ block: "center", behavior: "smooth", inline: "start" });
}
}, [scrollMarkup, docIndex, matchOrder]);
return (
<span ref={scrollRef} onClick={scrollNext}>
{children}
</span>
);
};
const TaggedMarkup = ({ markup, markupState, scrollState, allowScroll, showMarkup }) => {
let props = {};
let style = {};
const [content, information] = markup;
const [tag, bgcolor, fgcolor] = information
const [tag, bgcolor, fgcolor, docIndex, matchOrder, groupSizes] = information;
if (showMarkup) style = { ...baseMarkupStyle, background: bgcolor, color: fgcolor };
if (markupState) {
const [currMarkup, setCurrMarkup] = markupState;
......@@ -17,31 +40,77 @@ const TaggedMarkup = ({ markup, markupState, showMarkup }) => {
const onMouseLeave = showMarkup ? () => setCurrMarkup(null) : null;
props = { onMouseEnter, onMouseLeave };
}
return (
const markupContent = (
<span {...props} style={style}>
<Markup markups={content} markupState={markupState} showMarkup={false} />
<MarkupRoot
markups={content}
markupState={markupState}
scrollState={scrollState}
showMarkup={false}
/>
</span>
);
if (scrollState)
return (
<Scroll
tag={tag}
docIndex={docIndex}
matchOrder={matchOrder}
groupSizes={groupSizes}
markup={content}
scrollState={scrollState}
allowScroll={allowScroll}
>
{markupContent}
</Scroll>
);
return markupContent;
};
const StringContent = memo(({content}) => {
const lines = content.split("\n")
return <span>
<>{lines[0]}</>
{lines.slice(1).map(line => <><br/>{line}</>)}
</span>
})
const StringContent = memo(({ content }) => {
const lines = content.split("\n");
return (
<span>
<>{lines[0]}</>
{lines.slice(1).map((line) => (
<>
<br />
{line}
</>
))}
</span>
);
});
const Markup = ({ markups, markupState, matchState, showMarkup = true }) => (
const MarkupRoot = ({ markups, markupState, scrollState, allowScroll, showMarkup }) => {
console.log(scrollState)
return (
<>
{markups.map((child, i) =>
typeof child === "string" ? (
<StringContent key={i} content={child} />
) : (
<TaggedMarkup key={i} markup={child} markupState={markupState} showMarkup={showMarkup} matchState={matchState} />
<TaggedMarkup
key={i}
markup={child}
markupState={markupState}
scrollState={scrollState}
allowScroll={allowScroll}
showMarkup={showMarkup}
/>
)
)}
</>
)};
const Markup = ({ markups, markupState, scrollState, showMarkup = true }) => (
<MarkupRoot
markups={markups}
markupState={markupState}
scrollState={scrollState}
allowScroll={showMarkup}
showMarkup={showMarkup}
/>
);
export { Markup };
export { Markup, useMarkupScroll };
......@@ -95,30 +95,39 @@ const combine_documents = (docs) => {
};
const is_self_similar = (start, end, document_vector, suffix_array) => {
for (let i = start; i < end; i++) if (document_vector[suffix_array[i]] !== document_vector[suffix_array[i+1]] ) return false
return true
}
for (let i = start; i < end; i++)
if (document_vector[suffix_array[i]] !== document_vector[suffix_array[i + 1]]) return false;
return true;
};
const update_longest_match = (start, end, match_length, longest_match_array) => {
for (let i = start; i <= end; i++) if (longest_match_array[i] < match_length) longest_match_array[i] = match_length
}
for (let i = start; i <= end; i++)
if (longest_match_array[i] < match_length) longest_match_array[i] = match_length;
};
const is_nested = (start, end, match_length, suffix_array, inverse_suffix_array, longest_match_array) => {
const is_nested = (
start,
end,
match_length,
suffix_array,
inverse_suffix_array,
longest_match_array
) => {
for (let i = start; i <= end; i++) {
if (longest_match_array[i] === match_length && suffix_array[i] !== 0) {
const longest_match_before = longest_match_array[inverse_suffix_array[suffix_array[i] - 1]];
if (longest_match_before < match_length + 1) return false
if (longest_match_before < match_length + 1) return false;
}
}
return true
}
return true;
};
const compute_matches = (docs, min_length, allow_self_similarities) => {
const [combined_doc, document_vector, offsets] = combine_documents(docs);
const suffix_array = build_suffix_array(combined_doc);
const inverse_suffix_array = build_inverse_suffix_array(suffix_array);
const lcp_array = build_lcp_array(combined_doc, suffix_array, inverse_suffix_array);
const longest_match_array = Array(lcp_array.length).fill(0)
const longest_match_array = Array(lcp_array.length).fill(0);
lcp_array.shift();
lcp_array.push(0);
const match_groups = [];
......@@ -127,7 +136,7 @@ const compute_matches = (docs, min_length, allow_self_similarities) => {
let curr_start = 0;
let curr_depth = 0;
let i = 0
let i = 0;
while (i < lcp_array.length) {
if (lcp_array[i] > curr_depth) {
start.push(curr_start);
......@@ -136,9 +145,12 @@ const compute_matches = (docs, min_length, allow_self_similarities) => {
curr_start = i;
} else if (lcp_array[i] < curr_depth) {
if (curr_depth >= min_length) {
if (allow_self_similarities || !is_self_similar(curr_start, i, document_vector, suffix_array)) {
if (
allow_self_similarities ||
!is_self_similar(curr_start, i, document_vector, suffix_array)
) {
match_groups.push([curr_start, curr_depth, i]);
update_longest_match(curr_start, i, curr_depth, longest_match_array)
update_longest_match(curr_start, i, curr_depth, longest_match_array);
}
}
const prev_depth = depth[depth.length - 1];
......@@ -148,12 +160,15 @@ const compute_matches = (docs, min_length, allow_self_similarities) => {
curr_start = start.pop();
curr_depth = depth.pop();
}
} else i++
} else i++;
}
const matches = [];
match_groups.forEach(([index, match_length, end]) => {
if (is_nested(index, end, match_length, suffix_array, inverse_suffix_array, longest_match_array)) return
if (
is_nested(index, end, match_length, suffix_array, inverse_suffix_array, longest_match_array)
)
return;
const curr_matches = docs.map(() => []);
let pos = suffix_array[index];
let doc_idx = document_vector[pos];
......@@ -189,6 +204,12 @@ const insert_pos = (left, right, information, nodes) => {
nodes.splice(index, 0, [left, right, information]);
};
const nullify_tag_order = (information) => {
const newInformation = [...information];
newInformation[4] = null;
return newInformation;
};
const _collapse = (nodes, lower, upper) => {
const results = [];
while (nodes.length) {
......@@ -197,7 +218,7 @@ const _collapse = (nodes, lower, upper) => {
nodes.shift();
if (right > upper) {
insert_pos(upper + 1, right, information, nodes);
nodes.unshift([left, upper, information]);
nodes.unshift([left, upper, nullify_tag_order(information)]);
continue;
}
const children = _collapse(nodes, left, right);
......@@ -209,11 +230,13 @@ const _collapse = (nodes, lower, upper) => {
return results;
};
const collapse = (nodes) => {
// nest markups
const collapse = (nodes, max_tag) => {
let tag_array = max_tag ? Array(max_tag).fill(0) : null;
const results = [];
while (nodes.length) {
const range = nodes.shift();
const [left, right] = range;
const [left, right, information] = range;
const children = _collapse(nodes, left, right);
if (children.length) range.push(children);
results.push(range);
......@@ -221,6 +244,7 @@ const collapse = (nodes) => {
return results;
};
// resolve markup positions to words
const translate = (coll_markups, wstokens) => {
const result = [];
let last_end = 0;
......@@ -300,16 +324,17 @@ const clean_list = (words) => {
return [tokens, idx];
};
const cyrb53 = function(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1>>>0);
const cyrb53 = function (str, seed = 0) {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
const intToRGB = (i) => {
......@@ -336,7 +361,7 @@ const colorMarkup = (num) => {
return [`#${bgcolor}`, `#${fgcolor}`];
};
const computeMarkup = (docs, min_length, allow_self_similarities) => {
const computeMarkup = (docs, min_length = 3, allow_self_similarities = false) => {
const textblocks = docs.map((doc) => new Textblock(doc));
const clean_docs_idx = textblocks.map((textblock) => clean_list(textblock.words));
......@@ -349,14 +374,14 @@ const computeMarkup = (docs, min_length, allow_self_similarities) => {
let tag = 0;
matches.forEach(([match_length, text, groups]) => {
const color = colorMarkup(cyrb53(text));
const groupSizes = groups.map(start => start.length)
const groupSizes = groups.map((start) => start.length);
groups.forEach((group, i) => {
group.sort()
group.sort((a, b) => a - b);
return group.forEach((start, j) => {
const map_start = clean_docs_idx[i][1][start];
const map_end = clean_docs_idx[i][1][start + match_length - 1];
textblocks[i].add_range(map_start, map_end, [tag, ...color, j, groupSizes]);
})
textblocks[i].add_range(map_start, map_end, [tag, ...color, i, j, groupSizes]);
});
});
tag++;
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment