diff --git a/package.json b/package.json index dd037d3a..0964adf9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "start": "node dev-server.js", "test": "echo \"Error: no test specified\" && exit 0", "lint": "yarn run eslint ./src" - }, + }, "author": "Heartex, Inc.", "license": "ISC", "devDependencies": { @@ -80,7 +80,7 @@ "react-dom": "^17.0.2", "react-hot-loader": "^4.12.20", "react-hotkeys-hook": "^2.4.0", - "react-icons": "^3.11.0", + "react-icons": "^4.11.0", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.6", "react-window-infinite-loader": "^1.0.5", diff --git a/src/components/DataManager/DataManager.js b/src/components/DataManager/DataManager.js index 71899ab3..604bbf68 100644 --- a/src/components/DataManager/DataManager.js +++ b/src/components/DataManager/DataManager.js @@ -12,6 +12,8 @@ import { DataView } from "../MainView"; import "./DataManager.styl"; import { Toolbar } from "./Toolbar/Toolbar"; +const SIMILARITY_UPPER_LIMIT_PRECISION = 10; + const injector = inject(({ store }) => { const { sidebarEnabled, sidebarVisible } = store.viewsStore ?? {}; @@ -33,11 +35,17 @@ const summaryInjector = inject(({ store }) => { SDK, }; } else { + const similarityUpperLimit = taskStore?.similarityUpperLimit ? + (Math.ceil(taskStore?.similarityUpperLimit * SIMILARITY_UPPER_LIMIT_PRECISION) / SIMILARITY_UPPER_LIMIT_PRECISION) + : + null; + return { totalTasks: project?.task_count ?? project?.task_number ?? 0, totalFoundTasks: taskStore?.total ?? 0, totalAnnotations: taskStore?.totalAnnotations ?? 0, totalPredictions: taskStore?.totalPredictions ?? 0, + similarityUpperLimit, cloudSync: project.target_syncing ?? project.source_syncing ?? false, }; } diff --git a/src/components/DataManager/DataManager.styl b/src/components/DataManager/DataManager.styl index f43f69b2..d0b79ae2 100644 --- a/src/components/DataManager/DataManager.styl +++ b/src/components/DataManager/DataManager.styl @@ -9,4 +9,4 @@ width 100% &_shrink - width calc(100% - 350px) + width calc(100% - 420px) diff --git a/src/components/Filters/FilterLine/FilterLine.js b/src/components/Filters/FilterLine/FilterLine.js index 7a3e49ff..85e97a50 100644 --- a/src/components/Filters/FilterLine/FilterLine.js +++ b/src/components/Filters/FilterLine/FilterLine.js @@ -1,6 +1,6 @@ -import { observer } from "mobx-react"; +import { inject, observer } from "mobx-react"; import React, { Fragment } from "react"; -import { FaTrash } from "react-icons/fa"; +import { LiaTimesSolid } from "react-icons/lia"; import { BemWithSpecifiContext } from "../../../utils/bem"; import { Button } from "../../Common/Button/Button"; import { Icon } from "../../Common/Icon/Icon"; @@ -8,6 +8,7 @@ import { Tag } from "../../Common/Tag/Tag"; import { FilterDropdown } from "../FilterDropdown"; import "./FilterLine.styl"; import { FilterOperation } from "./FilterOperation"; +import { getRoot } from "mobx-state-tree"; const { Block, Elem } = BemWithSpecifiContext(); @@ -29,15 +30,31 @@ const Conjunction = observer(({ index, view }) => { const GroupWrapper = ({ children, wrap = false }) => { return wrap ? {children} : children; }; +const injector = inject(({ store }) => ({ + store, +})); +const CustomFilter = ({ CustomFilterLine, ...rest }) => ; -export const FilterLine = observer(({ +export const FilterLine = injector(observer(({ filter, availableFilters, index, view, sidebar, dropdownClassName, + store, }) => { + const customColumn = getRoot(view).SDK?.customColumns?.[filter.field.alias]; + const CustomFilterLine = customColumn?.renderFilter?.( + filter, + availableFilters, + index, + view, + sidebar, + dropdownClassName, + store, + ); + return ( @@ -56,7 +73,12 @@ export const FilterLine = observer(({ width={80} dropdownWidth={120} dropdownClassName={dropdownClassName} - onChange={(value) => filter.setFilterDelayed(value)} + onChange={(value) => { + const selectedAlias = value.split(":").pop(); + const customFilter = getRoot(view).SDK?.customColumns?.[selectedAlias]; + + customFilter ? customFilter.onFilterAdd?.(value, filter, view) : filter.setFilterDelayed(value); + }} optionRender={({ item: { original: filter } }) => ( {filter.field.title} @@ -76,24 +98,38 @@ export const FilterLine = observer(({ - + {CustomFilterLine ? ( + + + + ) : ( + + )} {!sidebarEnabled ? ( @@ -86,12 +67,30 @@ export const Filters = injector(({ views, currentView, filters }) => { size="small" about="Pin to sidebar" onClick={() => views.expandFilters()} - style={{ display: "inline-flex", alignItems: "center" }} - icon={} + style={{ display: "inline-flex", alignItems: "center", border: "none" }} + icon={} /> ) : null} + + {filters.length ? ( + filters.map((filter, i) => ( + + )) + ) : ( + No filters applied + )} + ); }); diff --git a/src/components/Filters/Filters.styl b/src/components/Filters/Filters.styl index 7255e8be..2a70357f 100644 --- a/src/components/Filters/Filters.styl +++ b/src/components/Filters/Filters.styl @@ -1,10 +1,11 @@ .filters background-color white position relative + padding-top 15px &:not(&_sidebar) padding-top 10px - min-width: 400px; + min-width 625px border-radius: 2px; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); @@ -37,8 +38,7 @@ &__actions display flex - margin-top 10px - padding 0 10px 10px + padding 0 16px 16px justify-content space-between &__empty @@ -47,12 +47,13 @@ color #585858 &__list - padding 0 10px + padding 0 10px 15px &_withFilters display grid grid-template-columns 65px min-content min-content 1fr 24px grid-gap 3px 4px + align-items center &_sidebar &__list &_withFilters @@ -61,6 +62,28 @@ grid-auto-flow row align-items center + .button + --button-color rgba(9, 109, 217, 1) + box-shadow none + border 1px solid rgba(9, 109, 217, 0.16) + height 32px + font-size 16px + font-style normal + font-weight 500 + line-height 24px + letter-spacing 0.15px + padding 8px 16px + &_withIcon + padding 0 + .icon + svg + width 18px + height 18px + + &__list + .button + border 0 none + --button-color rgba(137, 128, 152, 1) .filter-data-tag margin 1px font-size 12px diff --git a/src/sdk/dm-sdk.js b/src/sdk/dm-sdk.js index 1270213b..0323d74c 100644 --- a/src/sdk/dm-sdk.js +++ b/src/sdk/dm-sdk.js @@ -135,6 +135,7 @@ export class DataManager { edit: true, duplicate: true, } + customColumns = {}; /** @type {"dm" | "labelops"} */ type = "dm"; @@ -199,7 +200,7 @@ export class DataManager { this.actions.set(action.id, { action, callback }); }); } - + this.customColumns = config.customColumns ?? {}; this.type = config.type ?? "dm"; this.initApp(); diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 48e2b9c4..93248799 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js @@ -60,6 +60,8 @@ export const AppStore = types interfaces: types.map(types.boolean), toolbar: types.string, + + customColumns: types.optional(CustomJSON, {}), }) .views((self) => ({ /** @returns {import("../sdk/dm-sdk").DataManager} */ diff --git a/src/stores/DataStores/tasks.js b/src/stores/DataStores/tasks.js index 1ea7c0e1..11b4e532 100644 --- a/src/stores/DataStores/tasks.js +++ b/src/stores/DataStores/tasks.js @@ -218,17 +218,19 @@ export const create = (columns) => { }, postProcessData(data) { - const { total_annotations, total_predictions } = data; + const { total_annotations, total_predictions, similarity_score_upper_limit } = data; if (total_annotations !== null) self.totalAnnotations = total_annotations; if (total_predictions !== null) self.totalPredictions = total_predictions; + if (total_predictions !== null) + self.similarityUpperLimit = similarity_score_upper_limit; }, })) .preProcessSnapshot((snapshot) => { - const { total_annotations, total_predictions, ...sn } = snapshot; + const { total_annotations, total_predictions, similarity_score_upper_limit, ...sn } = snapshot; return { ...sn, @@ -239,6 +241,7 @@ export const create = (columns) => { })), totalAnnotations: total_annotations, totalPredictions: total_predictions, + similarityUpperLimit: similarity_score_upper_limit, }; }); }; diff --git a/src/stores/Tabs/store.js b/src/stores/Tabs/store.js index a9eb4890..d7472387 100644 --- a/src/stores/Tabs/store.js +++ b/src/stores/Tabs/store.js @@ -372,6 +372,8 @@ export const TabStore = types fetchColumns() { const columns = self.columnsRaw; const targets = unique(columns.map((c) => c.target)); + const rootSDK = getRoot(self).SDK; + const customColumns = rootSDK.customColumns ?? {}; const hiddenColumns = {}; const addedColumns = new Set(); @@ -434,7 +436,9 @@ export const TabStore = types addedColumns.add(column.id); - if (!col.children && column.filterable && (col?.visibility_defaults?.filter ?? true)) { + if (!col.children && column.filterable && ( + (col?.visibility_defaults?.filter ?? true) || (customColumns[col.id]?.filterable ?? false) + )) { self.availableFilters.push({ id: `filter:${columnID}`, type: col.type, diff --git a/src/stores/Tabs/tab.js b/src/stores/Tabs/tab.js index 72b81e54..9b8bd475 100644 --- a/src/stores/Tabs/tab.js +++ b/src/stores/Tabs/tab.js @@ -17,8 +17,6 @@ import { History } from '../../utils/history'; import { FF_DEV_1470, FF_LOPS_12, isFF } from "../../utils/feature-flags"; import { CustomJSON, StringOrNumberID, ThresholdType } from "../types"; -const DEFAULT_THRESHOLD = { min: 0, max: 10 }; - export const Tab = types .model("View", { id: StringOrNumberID, @@ -53,7 +51,7 @@ export const Tab = types editable: true, deletable: true, semantic_search: types.optional(types.array(CustomJSON), []), - threshold: types.optional(ThresholdType, DEFAULT_THRESHOLD), + threshold: types.optional(types.maybeNull(ThresholdType), null), }) .volatile(() => { const defaultWidth = getComputedStyle(document.body).getPropertyValue("--menu-sidebar-width").replace("px", "").trim(); @@ -218,7 +216,7 @@ export const Tab = types columnsDisplayType: self.columnsDisplayType.toPOJO(), gridWidth: self.gridWidth, semantic_search: self.semantic_search?.toJSON() ?? [], - threshold: self.threshold?.toJSON() ?? DEFAULT_THRESHOLD, + threshold: self.threshold?.toJSON(), }; if (self.saved || apiVersion === 1) { @@ -305,9 +303,12 @@ export const Tab = types self.selected = ids; }, - setSemanticSearch(semanticSearchList) { + setSemanticSearch(semanticSearchList, save = true) { self.semantic_search = semanticSearchList ?? []; - return self.save(); + if (self.semantic_search.length === 0) { + self.threshold = null; + } + return save && self.save(); }, setSemanticSearchThreshold(min, max) { @@ -315,6 +316,11 @@ export const Tab = types return self.save(); }, + clearSemanticSearchThreshold(save = true) { + self.threshold = null; + return save && self.save(); + }, + selectAll() { self.selected.toggleSelectedAll(); }, diff --git a/src/stores/Tabs/tab_filter.js b/src/stores/Tabs/tab_filter.js index 46315b73..d0dfcf32 100644 --- a/src/stores/Tabs/tab_filter.js +++ b/src/stores/Tabs/tab_filter.js @@ -54,9 +54,10 @@ export const TabFilter = types }, get component() { - const operationsList = Filters[self.filter.currentType] ?? Filters.String; + const rootSDK = getRoot(self)?.SDK; + const operationsList = rootSDK?.customColumns?.[self.filter.field.alias]?.operationsList ?? Filters[self.filter.currentType] ?? Filters.String; - return allowedFilterOperations(operationsList, getRoot(self)?.SDK?.type); + return allowedFilterOperations(operationsList, rootSDK?.type); }, get componentValueType() { @@ -74,8 +75,10 @@ export const TabFilter = types get isValidFilter() { const { currentValue: value } = self; - - if (!isDefined(value) || isBlank(value)) { + const rootSDK = getRoot(self)?.SDK; + const customFilter = rootSDK?.customColumns?.[self.filter.field.alias]; + + if ((customFilter && !customFilter.isFilterValid(self)) || !isDefined(value) || isBlank(value)) { return false; } else if (FilterValueRange.is(value)) { return isDefined(value.min) && isDefined(value.max); @@ -99,7 +102,7 @@ export const TabFilter = types get cellView() { const col = self.filter.field; - return CellViews[col.type] ?? CellViews[toStudlyCaps(col.alias)]; + return getRoot(self).SDK?.customColumns[col.alias] ?? CellViews[col.type] ?? CellViews[toStudlyCaps(col.alias)]; }, })) .volatile(() => ({ @@ -137,7 +140,7 @@ export const TabFilter = types self.setOperator(self.component[0].key); } - if (save) self.saved(); + if (save) self.save(); }, setFilterDelayed(value) { diff --git a/yarn.lock b/yarn.lock index cf2b3593..0073b315 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3282,11 +3282,6 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -6731,12 +6726,10 @@ react-hotkeys-hook@^2.4.0: dependencies: hotkeys-js "3.8.1" -react-icons@^3.11.0: - version "3.11.0" - resolved "https://registry.npmjs.org/react-icons/-/react-icons-3.11.0.tgz" - integrity sha512-JRgiI/vdF6uyBgyZhVyYJUZAop95Sy4XDe/jmT3R/bKliFWpO/uZBwvSjWEdxwzec7SYbEPNPck0Kff2tUGM2Q== - dependencies: - camelcase "^5.0.0" +react-icons@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.11.0.tgz#4b0e31c9bfc919608095cc429c4f1846f4d66c65" + integrity sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA== react-is@^16.13.1, react-is@^16.7.0: version "16.13.1"