diff --git a/src/Layout/Routes.js b/src/Layout/Routes.js
index f1cb9c4d..daf8a693 100644
--- a/src/Layout/Routes.js
+++ b/src/Layout/Routes.js
@@ -34,6 +34,7 @@ import SupportRoute from "pages/SupportRoute";
import ToolsRoute from "pages/ToolsRoute";
import TopSectionSelector from "pages/TopSectionSelector";
import Valency from "pages/Valency";
+import Adverb from "pages/Adverb";
import VersionRoute from "pages/VersionRoute";
import WithoutGrants from "pages/WithoutGrants";
@@ -77,6 +78,7 @@ const AppRoutes = () => (
} />
} />
} />
+ } />
} />
);
diff --git a/src/pages/Adverb/index.js b/src/pages/Adverb/index.js
new file mode 100644
index 00000000..8dfa213f
--- /dev/null
+++ b/src/pages/Adverb/index.js
@@ -0,0 +1,1653 @@
+import React, { useContext } from "react";
+import { connect } from "react-redux";
+import {
+ Button,
+ Checkbox,
+ Header,
+ Icon,
+ Input,
+ Loader,
+ Message,
+ Pagination,
+ Popup,
+ Segment,
+ Select
+} from "semantic-ui-react";
+import { gql } from "@apollo/client";
+import { graphql, withApollo } from "@apollo/client/react/hoc";
+import { DndContext, KeyboardSensor, PointerSensor, rectIntersection, useSensor, useSensors } from "@dnd-kit/core";
+import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ useSortable,
+ verticalListSortingStrategy
+} from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import { isEqual } from "lodash";
+import { compose } from "recompose";
+
+import { chooseTranslation as T } from "api/i18n";
+import TranslationContext from "Layout/TranslationContext";
+import { compositeIdToString as id2str } from "utils/compositeId";
+
+import "./style.scss";
+
+const sourcePerspectiveQuery = gql`
+ query sourcePerspectiveData {
+ perspectives(with_valency_data: true) {
+ id
+ tree {
+ id
+ translations
+ marked_for_deletion
+ }
+ has_adverb_data
+ new_adverb_data_count
+ }
+ }
+`;
+
+export const adverbDataQuery = gql`
+ query adverbData(
+ $perspectiveId: LingvodocID!
+ $offset: Int
+ $limit: Int
+ $specificityFlag: Boolean
+ $adverbPrefix: String
+ $caseFlag: Boolean
+ $acceptValue: Boolean
+ $sortOrderList: [String]
+ ) {
+ adverb_data(
+ perspective_id: $perspectiveId
+ offset: $offset
+ limit: $limit
+ specificity_flag: $specificityFlag
+ adverb_prefix: $adverbPrefix
+ case_flag: $caseFlag
+ accept_value: $acceptValue
+ sort_order_list: $sortOrderList
+ )
+ }
+`;
+
+const createAdverbDataMutation = gql`
+ mutation createAdverbData($perspectiveId: LingvodocID!) {
+ create_adverb_data(perspective_id: $perspectiveId) {
+ triumph
+ }
+ }
+`;
+
+const setAdverbAnnotationMutation = gql`
+ mutation setAdverbAnnotation($annotationList: [AdverbInstanceAnnotation]!) {
+ set_adverb_annotation(annotation_list: $annotationList) {
+ triumph
+ }
+ }
+`;
+
+const saveAdverbDataMutation = gql`
+ mutation saveAdverbData($perspectiveId: LingvodocID!) {
+ save_adverb_data(perspective_id: $perspectiveId) {
+ triumph
+ data_url
+ }
+ }
+`;
+
+const SortSpecificity = ({ valency, setState }) => {
+ const getTranslation = useContext(TranslationContext);
+
+ return (
+
+ {
+ setState({
+ sort_specificity: checked,
+ current_page: 1,
+ input_go_to_page: 1,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ valency.queryAdverbData({
+ current_page: 1,
+ sort_specificity: checked
+ });
+ }}
+ />
+
+ );
+};
+
+const SortAdverb = ({ valency, setState }) => {
+ const getTranslation = useContext(TranslationContext);
+
+ const { prefix_filter, data_adverb_prefix, show_data_adverb_list, show_prefix_adverb_list, show_prefix_str_list } =
+ valency.state;
+
+ return (
+
+
{
+ setState({
+ sort_adverb: checked,
+ current_page: 1,
+ input_go_to_page: 1,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null,
+ prefix_filter: "",
+ all_adverb_list: [],
+ data_adverb_list: [],
+ prefix_adverb_list: [],
+ show_data_adverb_list: [],
+ show_prefix_adverb_list: [],
+ show_prefix_str_list: []
+ });
+
+ valency.queryAdverbData({
+ current_page: 1,
+ sort_adverb: checked,
+ adverb_prefix: ""
+ });
+ }}
+ />
+
+ {valency.state.sort_adverb && (
+
+
+ {show_data_adverb_list.length > 0
+ ? data_adverb_prefix
+ ? `${getTranslation("Adverbs")} (${getTranslation("prefix")} "${data_adverb_prefix}"): `
+ : `${getTranslation("Adverbs")}: `
+ : data_adverb_prefix
+ ? `${getTranslation("No adverbs")} (${getTranslation("prefix")} "${data_adverb_prefix}").`
+ : `${getTranslation("No adverbs")}.`}
+
+ {show_data_adverb_list.map((adverb, index) =>
+ show_data_adverb_list.length > 15 && adverb == "..." ? (
+ "..., "
+ ) : (
+ valency.setPrefix(adverb)}>
+ {adverb}
+ {index < show_data_adverb_list.length - 1 ? ", " : ""}
+
+ )
+ )}
+
+ {show_data_adverb_list.length > 0 &&
+ ` (${valency.state.data_adverb_list.length} ${getTranslation("adverbs")})`}
+
+
+ {
+ if (e.key === "Enter") {
+ valency.setPage(1);
+ }
+ }}
+ onChange={e => valency.setPrefix(e.target.value)}
+ icon={
+ prefix_filter ? (
+ valency.setPrefix("")} />
+ ) : (
+
+ )
+ }
+ />
+
+ {show_prefix_str_list.length > 0 && (
+
+ {show_prefix_str_list.map((prefix, index) => (
+ valency.setPrefix(prefix)}>
+ {prefix.charAt(0).toUpperCase() + prefix.substring(1)}
+ {index < show_prefix_str_list.length - 1 ? " " : ""}
+
+ ))}
+
+ )}
+
+
+ {show_prefix_adverb_list.length > 0
+ ? prefix_filter
+ ? `${getTranslation("Filtered adverbs")} (${getTranslation("prefix")} "${prefix_filter}"): `
+ : `${getTranslation("Filtered adverbs")}: `
+ : prefix_filter
+ ? `${getTranslation("No filtered adverbs")} (${getTranslation("prefix")} "${prefix_filter}").`
+ : `${getTranslation("No filtered adverbs")}.`}
+
+ {show_prefix_adverb_list.map((adverb, index) =>
+ show_prefix_adverb_list.length > 15 && adverb == "..." ? (
+ "..., "
+ ) : (
+ valency.setPrefix(adverb)}>
+ {adverb}
+ {index < show_prefix_adverb_list.length - 1 ? ", " : ""}
+
+ )
+ )}
+
+ {show_prefix_adverb_list.length > 0 &&
+ ` (${valency.state.prefix_adverb_list.length} ${getTranslation("adverbs")})`}
+
+
+
+
+ )}
+
+ );
+};
+
+const SortCase = ({ valency, setState }) => {
+ const getTranslation = useContext(TranslationContext);
+
+ return (
+
+ {
+ setState({
+ sort_case: checked,
+ current_page: 1,
+ input_go_to_page: 1,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ valency.queryAdverbData({
+ current_page: 1,
+ sort_case: checked
+ });
+ }}
+ />
+
+ );
+};
+
+const SortAccept = ({ valency, setState }) => {
+ const getTranslation = useContext(TranslationContext);
+
+ const { sort_accept, accept_value } = valency.state;
+
+ return (
+
+
+ {`${getTranslation("Sort by acceptance")} (${getTranslation(
+ accept_value ? "accepted first" : "accepted last"
+ )}) `}
+
+ ) : (
+ getTranslation("Sort by acceptance")
+ )
+ }
+ checked={sort_accept}
+ onChange={(e, { checked }) => {
+ setState({
+ sort_accept: checked,
+ current_page: 1,
+ input_go_to_page: 1,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ valency.queryAdverbData({
+ current_page: 1,
+ sort_accept: checked
+ });
+ }}
+ />
+ {sort_accept && }
+ {sort_accept && (
+ {
+ const new_accept_value = !accept_value;
+
+ setState({
+ current_page: 1,
+ input_go_to_page: 1,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null,
+ accept_value: new_accept_value
+ });
+
+ valency.queryAdverbData({
+ current_page: 1,
+ accept_value: new_accept_value
+ });
+ }}
+ />
+ )}
+
+ );
+};
+
+const SortingItem = ({ sort_type, ...props }) => {
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
+ id: sort_type,
+ transition: {
+ duration: 0,
+ easing: "step-start"
+ }
+ });
+
+ const style = {
+ transform: CSS.Translate.toString(transform),
+ transition
+ };
+
+ let sort_component = null;
+
+ switch (sort_type) {
+ case "specificity":
+ sort_component = ;
+ break;
+
+ case "adverb":
+ sort_component = ;
+ break;
+
+ case "case":
+ sort_component = ;
+ break;
+
+ case "accept":
+ sort_component = ;
+ break;
+
+ default:
+ throw `unknown sorting type '${sort_type}'`;
+ }
+
+ return (
+
+ {sort_component}
+
+ );
+};
+
+const Sorting = ({ sort_order_list, setSortOrder, ...props }) => {
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 4
+ }
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates
+ })
+ );
+
+ function collisionDetection({ active, droppableContainers, ...rest }) {
+ return rectIntersection({
+ active,
+ droppableContainers: droppableContainers.filter(d => d.id != active.id),
+ ...rest
+ });
+ }
+
+ const { sort_accept, sort_case, sort_adverb, sort_specificity } = props.valency.state;
+
+ return (
+
+
+ {sort_order_list.map((sort_type, index) => (
+
+ ))}
+
+
+ );
+};
+
+class Adverb extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ perspective: null,
+
+ sort_order_list: ["specificity", "adverb", "case", "accept"],
+
+ sort_specificity: true,
+ sort_adverb: false,
+ sort_case: false,
+ sort_accept: false,
+
+ creating_adverb_data: false,
+ creating_adverb_error: false,
+ loading_adverb_data: false,
+ loading_adverb_error: false,
+ saving_adverb_data: false,
+ saving_adverb_error: false,
+
+ adverb_data: null,
+
+ instance_count: null,
+ current_page: 1,
+ input_go_to_page: 1,
+ items_per_page: 25,
+ total_pages: null,
+
+ instance_list: null,
+ sentence_map: null,
+ annotation_map: null,
+ user_map: null,
+
+ prefix_filter: "",
+ all_adverb_list: [],
+ data_adverb_list: [],
+ data_adverb_prefix: "",
+ prefix_adverb_list: [],
+ show_data_adverb_list: [],
+ show_prefix_adverb_list: [],
+ show_prefix_str_list: [],
+
+ accept_value: true,
+
+ selection_default: false,
+ selection_dict: {},
+
+ downloadUrl: null
+ };
+
+ this.createAdverbData = this.createAdverbData.bind(this);
+ this.saveAdverbData = this.saveAdverbData.bind(this);
+ this.setAdverbAnnotation = this.setAdverbAnnotation.bind(this);
+ this.acceptRejectAllSelected = this.acceptRejectAllSelected.bind(this);
+
+ this.queryAdverbData = this.queryAdverbData.bind(this);
+
+ this.setPerspective = this.setPerspective.bind(this);
+ this.setPage = this.setPage.bind(this);
+ this.setItemsPerPage = this.setItemsPerPage.bind(this);
+ this.setPrefix = this.setPrefix.bind(this);
+
+ this.getEnabledSortOrder = this.getEnabledSortOrder.bind(this);
+ this.setSortOrder = this.setSortOrder.bind(this);
+
+ this.render_instance = this.render_instance.bind(this);
+
+ this.adverb_data_query_count = 0;
+ }
+
+ queryAdverbData({
+ perspective = null,
+ current_page = null,
+ items_per_page = null,
+ sort_specificity = null,
+ sort_adverb = null,
+ sort_case = null,
+ sort_accept = null,
+ adverb_prefix = null,
+ accept_value = null,
+ sort_order_list = null
+ } = {}) {
+ const { client } = this.props;
+
+ perspective = perspective || this.state.perspective;
+
+ if (current_page == null) {
+ current_page = this.state.current_page;
+ }
+
+ items_per_page = items_per_page || this.state.items_per_page;
+
+ if (sort_specificity == null) {
+ sort_specificity = this.state.sort_specificity;
+ }
+
+ if (sort_adverb == null) {
+ sort_adverb = this.state.sort_adverb;
+ }
+
+ if (sort_case == null) {
+ sort_case = this.state.sort_case;
+ }
+
+ if (sort_accept == null) {
+ sort_accept = this.state.sort_accept;
+ }
+
+ if (adverb_prefix == null && sort_adverb) {
+ adverb_prefix = this.state.prefix_filter;
+ }
+
+ if (accept_value == null && sort_accept) {
+ accept_value = this.state.accept_value;
+ }
+
+ if (sort_order_list == null) {
+ sort_order_list = this.state.sort_order_list;
+ }
+
+ const query_index = ++this.adverb_data_query_count;
+
+ client
+ .query({
+ query: adverbDataQuery,
+ variables: {
+ perspectiveId: perspective.id,
+ offset: (current_page - 1) * items_per_page,
+ limit: items_per_page,
+ specificityFlag: sort_specificity,
+ adverbPrefix: sort_adverb ? adverb_prefix : null,
+ caseFlag: sort_case,
+ acceptValue: sort_accept ? accept_value : null,
+ sortOrderList: sort_order_list
+ },
+ fetchPolicy: "no-cache"
+ })
+ .then(
+ ({ data }) => {
+ if (query_index < this.adverb_data_query_count) {
+ return;
+ }
+
+ const { instance_count, instance_list, sentence_list, annotation_list, user_list } = data.adverb_data;
+
+ const sentence_map = new Map(sentence_list.map(sentence => [sentence.id, sentence]));
+
+ const annotation_map = new Map(
+ annotation_list.map(([instance_id, user_annotation_list]) => [instance_id, new Map(user_annotation_list)])
+ );
+
+ const user_map = new Map(user_list);
+
+ const state_obj = {
+ adverb_data: data.adverb_data,
+ instance_count,
+ total_pages: Math.floor((instance_count + items_per_page - 1) / items_per_page),
+ instance_list,
+ sentence_map,
+ annotation_map,
+ user_map,
+ data_adverb_prefix: adverb_prefix,
+ loading_adverb_data: false
+ };
+
+ if (sort_adverb) {
+ const adverb_list = data.adverb_data.adverb_list;
+
+ const all_adverb_list = [];
+ const data_adverb_list = [];
+ const prefix_adverb_list = [];
+
+ for (const [adverb, has_prefix] of adverb_list) {
+ all_adverb_list.push(adverb);
+
+ if (has_prefix) {
+ data_adverb_list.push(adverb);
+ prefix_adverb_list.push(adverb);
+ }
+ }
+
+ state_obj.all_adverb_list = all_adverb_list;
+ state_obj.data_adverb_list = data_adverb_list;
+ state_obj.prefix_adverb_list = prefix_adverb_list;
+
+ let show_data_adverb_list = [];
+ let show_prefix_adverb_list = [];
+
+ if (data_adverb_list.length > 15) {
+ for (const adverb of data_adverb_list.slice(0, 10)) {
+ show_data_adverb_list.push(adverb);
+ }
+
+ show_data_adverb_list.push("...");
+
+ for (const adverb of data_adverb_list.slice(-5)) {
+ show_data_adverb_list.push(adverb);
+ }
+ } else {
+ show_data_adverb_list = data_adverb_list;
+ }
+
+ if (prefix_adverb_list.length > 15) {
+ for (const adverb of prefix_adverb_list.slice(0, 10)) {
+ show_prefix_adverb_list.push(adverb);
+ }
+
+ show_prefix_adverb_list.push("...");
+
+ for (const adverb of prefix_adverb_list.slice(-5)) {
+ show_prefix_adverb_list.push(adverb);
+ }
+ } else {
+ show_prefix_adverb_list = prefix_adverb_list;
+ }
+
+ state_obj.show_data_adverb_list = show_data_adverb_list;
+ state_obj.show_prefix_adverb_list = show_prefix_adverb_list;
+
+ const show_prefix_str_set = new Set();
+ const show_prefix_str_list = [];
+
+ const prefix_length = adverb_prefix.length;
+
+ for (const adverb of prefix_adverb_list) {
+ if (adverb.length < prefix_length) {
+ continue;
+ }
+
+ const prefix_str = adverb.slice(0, prefix_length + 1);
+
+ if (prefix_str.length > prefix_length && !show_prefix_str_set.has(prefix_str)) {
+ show_prefix_str_set.add(prefix_str);
+ show_prefix_str_list.push(prefix_str);
+ }
+ }
+
+ state_obj.show_prefix_str_list = show_prefix_str_list;
+ }
+
+ this.setState(state_obj);
+ },
+
+ error => {
+ this.setState({
+ loading_adverb_data: false,
+ loading_adverb_error: true
+ });
+ }
+ );
+ }
+
+ setPerspective(perspective) {
+ if (!perspective.has_adverb_data) {
+ this.setState({
+ perspective,
+ sort_specificity: true,
+ sort_adverb: false,
+ sort_case: false,
+ sort_accept: false,
+ adverb_data: null,
+ prefix_filter: "",
+ selection_dict: {},
+ downloadUrl: null
+ });
+
+ return;
+ }
+
+ this.setState({
+ perspective,
+ sort_specificity: true,
+ sort_adverb: false,
+ sort_case: false,
+ sort_accept: false,
+ adverb_data: null,
+ prefix_filter: "",
+ selection_dict: {},
+ downloadUrl: null,
+ loading_adverb_data: true,
+ loading_adverb_error: false
+ });
+
+ this.queryAdverbData({
+ perspective,
+ current_page: 1,
+ sort_specificity: true,
+ sort_adverb: false,
+ adverb_prefix: "",
+ sort_case: false,
+ sort_accept: false,
+ accept_value: null
+ });
+ }
+
+ createAdverbData() {
+ this.setState({ creating_adverb_data: true });
+
+ const { has_adverb_data } = this.state.perspective;
+
+ this.props
+ .createAdverbData({
+ variables: {
+ perspectiveId: this.state.perspective.id
+ }
+ })
+ .then(
+ () => {
+ window.logger.suc(this.context(has_adverb_data ? "Updated adverb data." : "Created adverb data."));
+
+ const { client } = this.props;
+ const id_str = client.cache.identify(this.state.perspective);
+
+ const result = client.writeFragment({
+ id: id_str,
+ fragment: gql`
+ fragment HasValencyData on DictionaryPerspective {
+ has_adverb_data
+ new_adverb_data_count
+ }
+ `,
+ data: {
+ has_adverb_data: true,
+ new_adverb_data_count: 0
+ }
+ });
+
+ const perspective = client.readFragment({
+ id: id_str,
+ fragment: gql`
+ fragment Perspective on DictionaryPerspective {
+ id
+ tree {
+ id
+ translations
+ marked_for_deletion
+ }
+ has_adverb_data
+ new_adverb_data_count
+ }
+ `
+ });
+
+ this.setState({
+ perspective,
+ current_page: 1,
+ input_go_to_page: 1,
+ creating_adverb_data: false,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ this.queryAdverbData({
+ perspective,
+ current_page: 1
+ });
+ },
+ () => {
+ this.setState({
+ creating_adverb_data: false,
+ creating_adverb_error: true
+ });
+ }
+ );
+ }
+
+ saveAdverbData() {
+ this.setState({
+ saving_adverb_data: true,
+ downloadUrl: null
+ });
+
+ this.props
+ .saveAdverbData({
+ variables: {
+ perspectiveId: this.state.perspective.id
+ }
+ })
+ .then(
+ ({
+ data: {
+ save_adverb_data: { data_url }
+ }
+ }) => {
+ this.setState({
+ saving_adverb_data: false,
+ downloadUrl: data_url
+ });
+
+ this.downloadA.href = data_url;
+ this.downloadA.click();
+ },
+ () => {
+ this.setState({
+ saving_adverb_data: false,
+ saving_adverb_error: true
+ });
+ }
+ );
+ }
+
+ setAdverbAnnotation(annotation_list) {
+ this.props
+ .setAdverbAnnotation({
+ variables: {
+ annotationList: annotation_list
+ }
+ })
+ .then(
+ () => {
+ window.logger.suc(this.context("Set adverb annotation."));
+
+ for (const [instance_id, annotation_value] of annotation_list) {
+ if (!this.state.annotation_map.has(instance_id)) {
+ this.state.annotation_map.set(instance_id, new Map([[this.props.user.id, annotation_value]]));
+ } else {
+ this.state.annotation_map.get(instance_id).set(this.props.user.id, annotation_value);
+ }
+
+ if (!this.state.user_map.has(this.props.user.id)) {
+ this.state.user_map.set(this.props.user.id, this.props.user.name);
+ }
+ }
+
+ this.setState({
+ annotation_map: this.state.annotation_map,
+ downloadUrl: null
+ });
+ },
+ () => {}
+ );
+ }
+
+ acceptRejectAllSelected(accept_value) {
+ const { annotation_map, selection_default, selection_dict } = this.state;
+
+ const user_id = this.props.user.id;
+ const annotation_list = [];
+
+ for (const instance of this.state.instance_list) {
+ const selected = selection_dict.hasOwnProperty(instance.id) ? selection_dict[instance.id] : selection_default;
+
+ if (!selected) {
+ continue;
+ }
+
+ const user_annotation_map = annotation_map.has(instance.id) ? annotation_map.get(instance.id) : null;
+
+ const annotation_value =
+ user_annotation_map && user_annotation_map.has(user_id) && user_annotation_map.get(user_id);
+
+ if (annotation_value != accept_value) {
+ annotation_list.push([instance.id, accept_value]);
+ }
+ }
+
+ if (annotation_list.length > 0) {
+ this.setAdverbAnnotation(annotation_list);
+ }
+ }
+
+ setPage(active_page) {
+ active_page = Math.max(1, Math.min(active_page, this.state.total_pages));
+
+ this.setState({
+ current_page: active_page,
+ input_go_to_page: active_page,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ this.queryAdverbData({ current_page: active_page });
+ }
+
+ setItemsPerPage(items_per_page) {
+ const current_page = Math.floor(((this.state.current_page - 1) * this.state.items_per_page) / items_per_page) + 1;
+
+ this.setState({
+ current_page,
+ input_go_to_page: current_page,
+ items_per_page,
+ loading_adverb_data: true,
+ loading_adverb_error: false,
+ adverb_data: null
+ });
+
+ this.queryAdverbData({ current_page, items_per_page });
+ }
+
+ setPrefix(prefix_str) {
+ let prefix_adverb_list = [];
+
+ /* Refinement. */
+
+ if (prefix_str.startsWith(this.state.prefix_filter)) {
+ prefix_adverb_list = this.state.prefix_adverb_list.filter(adverb => adverb.startsWith(prefix_str));
+ } else {
+ /* Not a refinement, have to start from the list of all adverbs. */
+ prefix_adverb_list = this.state.all_adverb_list.filter(adverb => adverb.startsWith(prefix_str));
+ }
+
+ let show_prefix_adverb_list = [];
+
+ if (prefix_adverb_list.length > 15) {
+ for (const adverb of prefix_adverb_list.slice(0, 10)) {
+ show_prefix_adverb_list.push(adverb);
+ }
+
+ show_prefix_adverb_list.push("...");
+
+ for (const adverb of prefix_adverb_list.slice(-5)) {
+ show_prefix_adverb_list.push(adverb);
+ }
+ } else {
+ show_prefix_adverb_list = prefix_adverb_list;
+ }
+
+ const show_prefix_str_set = new Set();
+ const show_prefix_str_list = [];
+
+ const prefix_length = prefix_str.length;
+
+ for (const adverb of prefix_adverb_list) {
+ if (adverb.length < prefix_length) {
+ continue;
+ }
+
+ const new_prefix_str = adverb.slice(0, prefix_length + 1);
+
+ if (new_prefix_str.length > prefix_length && !show_prefix_str_set.has(new_prefix_str)) {
+ show_prefix_str_set.add(new_prefix_str);
+ show_prefix_str_list.push(new_prefix_str);
+ }
+ }
+
+ this.setState({
+ prefix_filter: prefix_str,
+ prefix_adverb_list,
+ show_prefix_adverb_list,
+ show_prefix_str_list
+ });
+ }
+
+ getEnabledSortOrder(sort_order_list = null) {
+ if (sort_order_list == null) {
+ sort_order_list = this.state.sort_order_list;
+ }
+
+ const condition_dict = {
+ specificity: this.state.sort_specificity,
+ adverb: this.state.sort_adverb,
+ case: this.state.sort_case,
+ accept: this.state.sort_accept
+ };
+
+ return sort_order_list.filter(sort_type => condition_dict[sort_type]);
+ }
+
+ setSortOrder(event) {
+ const { active, over } = event;
+
+ if (!active || !over || active.id == over.id) {
+ return;
+ }
+
+ const { sort_order_list } = this.state;
+
+ const enabled_before_list = this.getEnabledSortOrder(sort_order_list);
+
+ const oldIndex = sort_order_list.indexOf(active.id);
+ const newIndex = sort_order_list.indexOf(over.id);
+
+ const new_sort_order_list = arrayMove(sort_order_list, oldIndex, newIndex);
+
+ const enabled_after_list = this.getEnabledSortOrder(new_sort_order_list);
+
+ this.setState({ sort_order_list: new_sort_order_list });
+
+ // Reloading data only if the order of _enabled_ sorting options is changed.
+
+ if (!isEqual(enabled_before_list, enabled_after_list)) {
+ this.queryAdverbData({
+ current_page: 1,
+ sort_order_list: new_sort_order_list
+ });
+ }
+ }
+
+ render_instance(instance) {
+ const sentence = this.state.sentence_map.get(instance.sentence_id);
+
+ const instance_data = sentence.instances_adv[instance.index];
+
+ const instance_case = instance_data["case"];
+ const [instance_from, instance_to] = instance_data["location"];
+
+ const annotation_map = this.state.annotation_map;
+ const user_id = this.props.user.id;
+
+ const user_annotation_map = annotation_map.has(instance.id) ? annotation_map.get(instance.id) : null;
+
+ const annotation_value =
+ user_annotation_map && user_annotation_map.has(user_id) && user_annotation_map.get(user_id);
+
+ const { selection_default, selection_dict } = this.state;
+
+ return (
+
+ {
+ selection_dict[instance.id] = checked;
+ this.setState({ selection_dict });
+ }}
+ />
+
+ {sentence.tokens.map((token, index) => {
+ const item_list = Object.entries(token)
+ .filter(item => item[0] != "token")
+ .sort();
+
+ const token_content =
+ index == instance_from ? (
+
+ {token.token}
+
+
+ ) : index == instance_to ? (
+
+ {token.token}
+
+ {instance_case.toUpperCase()}
+
+
+ ) : (
+ {token.token}
+ );
+
+ return item_list.length > 0 ? (
+
+ {item_list.map(item => (
+
+ {item[0]}: {item[1]}
+
+ ))}
+
+ ) : (
+ {token_content}
+ );
+ })}
+
+
+
+
+
+
+
+ {annotation_value && {this.context("Accepted")}}
+
+
+ {user_annotation_map && user_annotation_map.size > 0 && (
+
+ {Array.from(user_annotation_map.entries())
+ .filter(([annotation_user_id, annotation_value]) => annotation_value)
+ .map(([annotation_user_id, annotation_value]) => [
+ this.state.user_map.get(annotation_user_id),
+ annotation_user_id
+ ])
+ .sort()
+ .map(([user_name, user_id]) => (
+
+ {`${this.context("Accepted by")} ${user_name}`}
+
+ ))}
+
+ )}
+
+ );
+ }
+
+ render() {
+ if (this.props.error) {
+ return (
+
+
+ {this.context("User sign-in error, please sign in; if not successful, please contact administrators.")}
+
+
+ );
+ } else if (this.props.loading) {
+ return (
+
+
+
+ {`${this.context("Loading sign-in data")}...`}
+
+
+
+ );
+ } else if (this.props.user.id === undefined) {
+ return (
+
+
+ {this.context("Please sign in")}
+ {this.context("Only registered users can work with adverb data.")}
+
+
+ );
+ } else if (this.props.data.error) {
+ return (
+
+
+ {this.context("General error, please contact administrators.")}
+
+
+ );
+ } else if (this.props.data.loading) {
+ return (
+
+
+
+ {this.context("Loading perspective data")}...
+
+
+
+ );
+ }
+
+ const { perspectives } = this.props.data;
+
+ const perspective_option_list = [];
+ const perspective_id_map = new Map();
+
+ for (let i = 0; i < perspectives.length; i++) {
+ if (perspectives[i].tree.some(value => value.marked_for_deletion)) {
+ continue;
+ }
+
+ const id_str = id2str(perspectives[i].id);
+
+ const text_str = perspectives[i].tree
+ .map(value => T(value.translations))
+ .reverse()
+ .join(" \u203a ");
+
+ perspective_option_list.push({
+ key: i,
+ value: id_str,
+ text: text_str
+ });
+
+ perspective_id_map.set(id_str, perspectives[i]);
+ }
+
+ const {
+ perspective,
+
+ current_page,
+ items_per_page,
+ show_data_adverb_list,
+ show_prefix_adverb_list,
+ show_prefix_str_list,
+
+ annotation_map,
+ selection_default,
+ selection_dict
+ } = this.state;
+
+ const user_id = this.props.user.id;
+
+ const render_instance_list = [];
+
+ let has_selected_to_accept = false;
+ let has_selected_to_reject = false;
+
+ if (!this.state.loading_adverb_data && this.state.adverb_data && this.state.instance_list.length > 0) {
+ const prev_dict = {
+ specificity: null,
+ adverb: null,
+ case: null,
+ accept: null
+ };
+
+ /* Showing accepted / not accepted headers only if some other sorting option is enabled and goes after
+ * 'sort by acceptance' in the sorting option order. */
+
+ let enabled_list = this.getEnabledSortOrder();
+ let accept_header_flag = false;
+
+ if (this.state.sort_accept) {
+ let seen_accept = false;
+
+ for (const sort_type of enabled_list) {
+ if (sort_type == "accept") {
+ seen_accept = true;
+ } else if (seen_accept) {
+ accept_header_flag = true;
+ break;
+ }
+ }
+ }
+
+ if (!accept_header_flag) {
+ enabled_list = enabled_list.filter(sort_type => sort_type != "accept");
+ }
+
+ /* Checks if instance is accepted by at least 1 user. */
+
+ function is_instance_accepted(instance) {
+ const instance_id = instance.id;
+
+ if (annotation_map.has(instance_id)) {
+ for (const value of annotation_map.get(instance_id).values()) {
+ if (value) {
+ return true;
+ }
+ }
+ }
+ }
+
+ for (const sort_type of enabled_list) {
+ switch (sort_type) {
+ case "specificity":
+ break;
+
+ case "adverb":
+ const { adverb_lex } = this.state.instance_list[0];
+
+ render_instance_list.push(
+
+ );
+ prev_dict[sort_type] = adverb_lex;
+
+ break;
+
+ case "case":
+ const { case_str } = this.state.instance_list[0];
+
+ render_instance_list.push(
+
+ );
+
+ prev_dict[sort_type] = case_str;
+
+ break;
+
+ case "accept":
+ const accept_str = is_instance_accepted(this.state.instance_list[0])
+ ? this.context("Accepted")
+ : this.context("Not accepted");
+
+ render_instance_list.push(
+
+ );
+
+ prev_dict[sort_type] = accept_str;
+
+ break;
+
+ default:
+ throw `unknown sorting type '${sort_type}'`;
+ }
+ }
+
+ for (let i = 0; i < this.state.instance_list.length; i++) {
+ const instance = this.state.instance_list[i];
+
+ for (let j = 0; j < enabled_list.length; j++) {
+ const sort_type = enabled_list[j];
+
+ switch (sort_type) {
+ case "specificity":
+ break;
+
+ case "adverb":
+ const { adverb_lex } = instance;
+
+ if (adverb_lex != prev_dict[sort_type]) {
+ render_instance_list.push(
+
+ );
+
+ prev_dict[sort_type] = adverb_lex;
+
+ for (let k = j + 1; k < enabled_list.length; k++) {
+ prev_dict[enabled_list[k]] = null;
+ }
+ }
+
+ break;
+
+ case "case":
+ const { case_str } = instance;
+
+ if (case_str != prev_dict[sort_type]) {
+ render_instance_list.push(
+
+ );
+
+ prev_dict[sort_type] = case_str;
+
+ for (let k = j + 1; k < enabled_list.length; k++) {
+ prev_dict[enabled_list[k]] = null;
+ }
+ }
+
+ break;
+
+ case "accept":
+ const accept_str = is_instance_accepted(instance)
+ ? this.context("Accepted")
+ : this.context("Not accepted");
+
+ if (accept_str != prev_dict[sort_type]) {
+ render_instance_list.push(
+
+ );
+
+ prev_dict[sort_type] = accept_str;
+
+ for (let k = j + 1; k < enabled_list.length; k++) {
+ prev_dict[enabled_list[k]] = null;
+ }
+ }
+
+ break;
+
+ default:
+ throw `unknown sorting type '${sort_type}'`;
+ }
+ }
+
+ render_instance_list.push(this.render_instance(instance));
+
+ /* Checking if we have selected instances we can accept/reject. */
+
+ if (has_selected_to_accept && has_selected_to_reject) {
+ continue;
+ }
+
+ const selected = selection_dict.hasOwnProperty(instance.id) ? selection_dict[instance.id] : selection_default;
+
+ if (!selected) {
+ continue;
+ }
+
+ const user_annotation_map = annotation_map.has(instance.id) ? annotation_map.get(instance.id) : null;
+
+ const annotation_value =
+ user_annotation_map && user_annotation_map.has(user_id) && user_annotation_map.get(user_id);
+
+ if (annotation_value) {
+ has_selected_to_reject = true;
+ } else {
+ has_selected_to_accept = true;
+ }
+ }
+ }
+
+ return (
+
+
+ {this.context("Perspective")}:
+
+
+
+ );
+ }
+}
+
+Adverb.contextType = TranslationContext;
+
+export default compose(
+ connect(state => state.user),
+ graphql(sourcePerspectiveQuery, { skip: ({ user }) => user.id === undefined }),
+ graphql(createAdverbDataMutation, { name: "createAdverbData" }),
+ graphql(saveAdverbDataMutation, { name: "saveAdverbData" }),
+ graphql(setAdverbAnnotationMutation, { name: "setAdverbAnnotation" }),
+ withApollo
+)(Adverb);
diff --git a/src/pages/Adverb/style.scss b/src/pages/Adverb/style.scss
new file mode 100644
index 00000000..309d4b7d
--- /dev/null
+++ b/src/pages/Adverb/style.scss
@@ -0,0 +1,25 @@
+.token_from {
+ background-color: #a0ffff;
+}
+
+.token_case {
+ background-color: #eaeaff;
+}
+
+.token_to {
+ background-color: #b0ffe0;
+}
+
+.clickable {
+ cursor: pointer;
+}
+
+.sorting_item {
+ margin-top: 0.5em;
+}
+
+.sort_verb_selection {
+ margin-top: 0.5em !important;
+ margin-bottom: 0.5em !important;
+ padding: 0.5em !important;
+}
diff --git a/src/pages/ToolsRoute/index.js b/src/pages/ToolsRoute/index.js
index 2a7a4cfb..22f8fefc 100644
--- a/src/pages/ToolsRoute/index.js
+++ b/src/pages/ToolsRoute/index.js
@@ -64,6 +64,12 @@ function ToolsRoute(props) {
)}
+ {props.user.id !== undefined && (
+
+
+
+
+ )}
diff --git a/src/pages/Valency/index.js b/src/pages/Valency/index.js
index aee3c5b0..de8ee53b 100644
--- a/src/pages/Valency/index.js
+++ b/src/pages/Valency/index.js
@@ -247,7 +247,7 @@ const SortCase = ({ valency, setState }) => {
});
valency.queryValencyData({
- currenet_page: 1,
+ current_page: 1,
sort_case: checked
});
}}