= (typeof Views)[T];
11 |
12 | export default Views;
13 |
--------------------------------------------------------------------------------
/src/components/Toolbar/Toolbar.styl:
--------------------------------------------------------------------------------
1 | .toolbar
2 | width 40px
3 | background #FFFFFF
4 | box-shadow 0px 0px 0px 1px rgba(0, 0, 0, 0.05), 0px 5px 10px rgba(0, 0, 0, 0.1)
5 | border-radius 7px
6 | position sticky
7 | top 70px
8 | margin-top 50px
9 |
10 | &::before
11 | height 12px
12 | display block
13 | background-color rgba(#000, 0.05)
14 | content ""
15 | border-radius 7px 7px 0 0
16 |
17 | &__group ~ &__group
18 | margin-top 4px
19 | border-top 2px solid rgba(0, 0, 0, 0.05)
20 |
21 | &_expanded
22 | width auto
23 | min-width 210px
24 | display flex
25 | flex-direction column
26 |
--------------------------------------------------------------------------------
/src/components/Toolbar/ToolbarContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | export const ToolbarContext = createContext({ expanded: false });
4 |
5 | export const ToolbarProvider = ToolbarContext.Provider;
6 |
--------------------------------------------------------------------------------
/src/components/Tools/Styles.module.scss:
--------------------------------------------------------------------------------
1 | .block {
2 | display: flex;
3 | flex-flow: column;
4 | align-items: center;
5 | border: 1px solid rgba(34, 36, 38, 0.15);
6 | border-radius: 0.28571429rem;
7 | width: fit-content;
8 | padding: 0.5em;
9 | }
10 |
11 | .divider {
12 | margin: 12px 0;
13 | }
14 |
15 | .button {
16 | margin: 0.3rem 0;
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/TopBar/HistoryActions.styl:
--------------------------------------------------------------------------------
1 | .history-buttons
2 | display flex
3 |
4 | &__action
5 | width 36px
6 | height 36px
7 | border none
8 | padding 0 !important
9 | background none !important
10 |
11 | &:disabled
12 | opacity 0.6
13 |
14 | svg
15 | display block
16 |
17 | &_delete
18 | color #DD0000
19 |
--------------------------------------------------------------------------------
/src/components/TreeValidation/TreeValidation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PropTypes } from 'prop-types';
3 | import { getEnv } from 'mobx-state-tree';
4 | import { inject, observer } from 'mobx-react';
5 |
6 | import { ErrorMessage } from '../ErrorMessage/ErrorMessage';
7 |
8 | export const TreeValidation = inject('store')(
9 | observer(({ store, errors }) => {
10 | return (
11 |
12 | {errors.map((error, index) => (
13 |
14 | ))}
15 |
16 | );
17 | }),
18 | );
19 |
20 | TreeValidation.propTypes = {
21 | errors: PropTypes.array.isRequired,
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/VideoCanvas/VideoConstants.ts:
--------------------------------------------------------------------------------
1 | export const MIN_ZOOM = 0.1;
2 | export const MAX_ZOOM = 10;
3 | export const ZOOM_STEP = 0.1;
4 | export const ZOOM_STEP_WHEEL = 0.00025;
5 | export const MIN_ZOOM_WHEEL = 0.05;
6 | export const MAX_ZOOM_WHEEL = 0.5;
7 |
--------------------------------------------------------------------------------
/src/components/Waveform/Waveform.module.scss:
--------------------------------------------------------------------------------
1 | .progress {
2 | color: #ff5630;
3 | }
4 |
5 | .wave {
6 | position: relative;
7 |
8 | canvas {
9 | // prevent reset.css from breaking waveforms
10 | max-width: unset;
11 | }
12 | }
13 |
14 | .menu {
15 | margin: 2em 0;
16 | }
17 |
--------------------------------------------------------------------------------
/src/core/feature-flags/flags.json:
--------------------------------------------------------------------------------
1 | {
2 | "ff_front_1170_outliner_030222_short": true,
3 | "ff_front_DEV_1713_audio_ui_150222_short": false,
4 | "ff_front_dev_2715_audio_3_280722_short": true,
5 | "fflag_fix_front_dev_3391_interactive_view_all": false,
6 | "fflag_fix_front_dev_3617_taxonomy_memory_leaks_fix": true,
7 | "fflag_feat_front_dev_3873_labeling_ui_improvements_short": true,
8 | "fflag_feat_front_dev_4081_magic_wand_tool": true,
9 | "fflag_feat_front_lsdv_3012_syncable_tags_070423_short": true,
10 | "fflag_feat_front_lsdv_4832_new_ranker_tag_120423_short": true,
11 | "fflag_feat_front_lsdv_4620_richtext_opimization_060423_short": true,
12 | "fflag_fix_front_lsdv_4620_memory_leaks_100723_short": true
13 | }
14 |
--------------------------------------------------------------------------------
/src/core/feature-flags/index.ts:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV !== 'production' && !window.APP_SETTINGS) {
2 | const feature_flags = (() => {
3 | try {
4 | return require('./flags.json');
5 | } catch (err) {
6 | return {};
7 | }
8 | })();
9 |
10 | window.APP_SETTINGS = { feature_flags };
11 | }
12 |
--------------------------------------------------------------------------------
/src/core/settings/types.ts:
--------------------------------------------------------------------------------
1 | import { ChangeEvent } from 'react';
2 |
3 | export interface SettingsProperty {
4 | description: string;
5 | defaultValue: any;
6 | type: 'boolean' | 'number' | 'text';
7 | min?: number;
8 | max?: number;
9 | step?: number;
10 | ff?: string;
11 | onChangeEvent?: (e: ChangeEvent) => void;
12 | }
13 |
14 | export type SettingsProperties = Record
15 |
--------------------------------------------------------------------------------
/src/core/settings/videosettings.ts:
--------------------------------------------------------------------------------
1 | import { FF_DEV_3350 } from '../../utils/feature-flags';
2 | import { SettingsProperties } from './types';
3 |
4 | export default {
5 | 'videoDrawOutside': {
6 | 'description': 'Allow drawing outside of video boundaries',
7 | 'defaultValue': false,
8 | 'type': 'boolean',
9 | 'ff': FF_DEV_3350,
10 | },
11 | 'videoHopSize': {
12 | 'description': 'Video hop size',
13 | 'defaultValue': 10,
14 | 'type': 'number',
15 | },
16 | } as SettingsProperties;
17 |
18 |
--------------------------------------------------------------------------------
/src/defaultOptions.js:
--------------------------------------------------------------------------------
1 | export default {
2 | interfaces: [
3 | 'panel',
4 | 'update',
5 | 'submit',
6 | 'skip',
7 | 'controls',
8 | 'infobar',
9 | 'topbar',
10 | 'instruction',
11 | 'side-column',
12 | 'annotations:history',
13 | 'annotations:tabs',
14 | 'annotations:menu',
15 | 'annotations:current',
16 | 'annotations:add-new',
17 | 'annotations:delete',
18 | 'annotations:view-all',
19 | 'predictions:tabs',
20 | 'predictions:menu',
21 | 'auto-annotation',
22 | 'edit-history',
23 | ],
24 | };
25 |
--------------------------------------------------------------------------------
/src/hooks/useMedia.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export const useMedia = (query: string) => {
4 | const [match, setMatch] = useState(window.matchMedia(query));
5 |
6 | useEffect(() => {
7 | const handleWindowResize = () => {
8 | setMatch(window.matchMedia(query));
9 | };
10 |
11 | window.addEventListener('resize', handleWindowResize);
12 |
13 | return () => window.removeEventListener('resize', handleWindowResize);
14 | }, []);
15 |
16 | useEffect(() => {
17 | setMatch(window.matchMedia(query));
18 | }, [query]);
19 |
20 | return match;
21 | };
22 |
--------------------------------------------------------------------------------
/src/hooks/useMemoizedHandlers.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export const useMemoizedHandlers = >(handlers: T): T => {
4 | const handlersRef = useRef(handlers);
5 |
6 | useEffect(() => {
7 | Object.assign(handlersRef.current, handlers);
8 | }, [handlers]);
9 |
10 | return handlersRef.current;
11 | };
12 |
--------------------------------------------------------------------------------
/src/hooks/useToggle.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useState } from 'react';
2 |
3 | type ToggleHookReturn = [
4 | boolean,
5 | () => any,
6 | () => any,
7 | () => any,
8 | ]
9 |
10 | /**
11 | * Handle boolean states conveniently
12 | * @param {boolean=false} defaultValue
13 | */
14 | export const useToggle = (defaultValue = false): ToggleHookReturn => {
15 | const [value, setValue] = useState(defaultValue);
16 | const [setTrue, setFalse, toggleValue] = useMemo(() => [
17 | setValue.bind(null, true),
18 | setValue.bind(null, false),
19 | () => setValue(value => !value),
20 | ], []);
21 |
22 | return [value, setTrue, setFalse, toggleValue];
23 | };
24 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './core/feature-flags';
2 | import './assets/styles/global.scss';
3 | import { LabelStudio } from './LabelStudio';
4 |
5 | window.LabelStudio = LabelStudio;
6 |
7 | export default LabelStudio;
8 |
9 | export { LabelStudio };
10 |
--------------------------------------------------------------------------------
/src/lib/AudioUltra/Common/Cacheable.ts:
--------------------------------------------------------------------------------
1 | export class Cacheable {
2 | private cache = new Map();
3 |
4 | createKey(...args: any[]) {
5 | if (args.length === 1) {
6 | return args[0].toString();
7 | }
8 |
9 | return args.join(':');
10 | }
11 |
12 | clearCache() {
13 | this.cache.clear();
14 | }
15 |
16 | cached(key: number|string|Array, fn: () => any) {
17 | const cacheKey = this.createKey(key);
18 |
19 | if (this.cache.has(cacheKey)) {
20 | return this.cache.get(cacheKey);
21 | }
22 |
23 | const result = fn();
24 |
25 | this.cache.set(cacheKey, result);
26 |
27 | return result;
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/lib/AudioUltra/Common/Destructable.ts:
--------------------------------------------------------------------------------
1 | export class Destructable {
2 | private destroyed = false;
3 |
4 | get isDestroyed() {
5 | return this.destroyed;
6 | }
7 |
8 | destroy() {
9 | this.destroyed = true;
10 | this.destroy = () => null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/AudioUltra/Common/Style.ts:
--------------------------------------------------------------------------------
1 | export type FontWeight = 'normal'|'bold'|'bolder'|'lighter'|'initial'|'inherit'|'300'|'400'|'500'|'600'|'700'|'800'|'900';
2 |
3 | export interface Padding {
4 | top?: number;
5 | bottom?: number;
6 | left?: number;
7 | right?: number;
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/AudioUltra/index.ts:
--------------------------------------------------------------------------------
1 | export { Waveform } from './Waveform';
2 | export * from './Common/Utils';
3 |
--------------------------------------------------------------------------------
/src/mixins/IsReadyMixin.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 |
3 | const IsReadyMixin = types.model({}).volatile(() => {
4 | return {
5 | _isReady: true,
6 | };
7 | }).views(self => ({
8 | get isReady() {
9 | return self._isReady;
10 | },
11 | })).actions(self => {
12 | return {
13 | setReady(value) {
14 | self._isReady = value;
15 | },
16 | };
17 | });
18 |
19 | export default IsReadyMixin;
20 |
21 | export const IsReadyWithDepsMixin = IsReadyMixin.views(self => ({
22 | get isReady() {
23 | return self._isReady && !self.regs?.filter(r => !r.isReady).length;
24 | },
25 | }));
26 |
--------------------------------------------------------------------------------
/src/mixins/LabelMixin.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 |
3 | /**
4 | * @todo we didn't need all these methods, so mixin is empty for now.
5 | * Relevant parts of SelectedMixin can be moved here
6 | * to finally split Labels and Choices; so the file left in place.
7 | */
8 | const LabelMixin = types.model('LabelMixin');
9 |
10 | export default LabelMixin;
11 |
--------------------------------------------------------------------------------
/src/mixins/PerRegionModes.ts:
--------------------------------------------------------------------------------
1 | export const PER_REGION_MODES = {
2 | TAG: 'tag',
3 | REGION_LIST: 'region-list',
4 | };
5 |
--------------------------------------------------------------------------------
/src/mixins/SeparatedControlMixin.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 |
3 | const SeparatedControlMixin = types
4 | .model()
5 | .volatile(() => {
6 | return {
7 | isSeparated: true,
8 | };
9 | })
10 | .views(self => ({
11 | get obj() {
12 | return self.annotation?.names.get(self.toname);
13 | },
14 |
15 | get selectedLabels() {
16 | return [];
17 | },
18 | selectedValues() {
19 | return [];
20 | },
21 | getResultValue() {
22 | return {};
23 | },
24 | }));
25 |
26 | export default SeparatedControlMixin;
27 |
--------------------------------------------------------------------------------
/src/mixins/TagParentMixin.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 | import Types from '../core/Types';
3 |
4 | export const TagParentMixin = types.model('AnnotationMixin',
5 | {
6 | parentTypes: Types.tagsTypes([]),
7 | }).views((self) => ({
8 | get parent() {
9 | return Types.getParentTagOfTypeString(self, self.parentTypes);
10 | },
11 | }));
12 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/regions/HyperTextRegion/HyperTextRegion.module.scss:
--------------------------------------------------------------------------------
1 | .htx-highlight,
2 | .annotator-hl {
3 | }
4 |
5 | .htx-highlight:hover {
6 | cursor: pointer;
7 | background-color: red;
8 | }
9 |
--------------------------------------------------------------------------------
/src/regions/RegionWrapper.js:
--------------------------------------------------------------------------------
1 | import { observer } from 'mobx-react';
2 | import { Fragment, useContext } from 'react';
3 | import { ImageViewContext } from '../components/ImageView/ImageViewContext';
4 | import { SuggestionControls } from '../components/ImageView/SuggestionControls';
5 |
6 | export const RegionWrapper = observer(({ item, children }) => {
7 | const { suggestion } = useContext(ImageViewContext) ?? {};
8 |
9 | return (
10 |
11 | {children}
12 | {suggestion && (
13 |
14 | )}
15 |
16 | );
17 | });
18 |
--------------------------------------------------------------------------------
/src/regions/TextRegion.js:
--------------------------------------------------------------------------------
1 | // stub file to keep TextRegion docs
2 |
3 | /**
4 | * @example
5 | * {
6 | * "value": {
7 | * "start": 2,
8 | * "end": 81,
9 | * "labels": ["Car"]
10 | * }
11 | * }
12 | * @typedef {Object} TextRegionResult
13 | * @property {Object} value
14 | * @property {string} value.start position of the start of the region in characters
15 | * @property {string} value.end position of the end of the region in characters
16 | * @property {string} [value.text] text content of the region, can be skipped
17 | */
18 |
--------------------------------------------------------------------------------
/src/regions/TextRegion/TextRegion.module.scss:
--------------------------------------------------------------------------------
1 | .state {
2 | color: red;
3 | user-select: none;
4 | padding: 2px 0;
5 | font-size: 100%;
6 | top: 0;
7 | }
8 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Initializing Test Environment
3 | */
4 | /* global jest, global */
5 |
6 | import { configure } from 'enzyme';
7 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
8 |
9 | const localStorageMock = {
10 | getItem: jest.fn(),
11 | setItem: jest.fn(),
12 | removeItem: jest.fn(),
13 | clear: jest.fn(),
14 | };
15 |
16 | global.localStorage = localStorageMock;
17 |
18 | configure({ adapter: new Adapter() });
19 |
--------------------------------------------------------------------------------
/src/stores/ProjectStore.js:
--------------------------------------------------------------------------------
1 | import { getParent, types } from 'mobx-state-tree';
2 |
3 | /**
4 | * Project Store
5 | */
6 | const ProjectStore = types
7 | .model('Project', {
8 | /**
9 | * Project ID
10 | */
11 | id: types.identifierNumber,
12 | })
13 | .views(self => ({
14 | get app() {
15 | return getParent(self);
16 | },
17 | }));
18 |
19 | export default ProjectStore;
20 |
--------------------------------------------------------------------------------
/src/styles/global.module.scss:
--------------------------------------------------------------------------------
1 | .link {
2 | color: #1890ff;
3 | cursor: pointer;
4 |
5 | &:hover {
6 | color: #40a9ff;
7 | }
8 |
9 | &:focus {
10 | color: #1890ff;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/tags/TagBase.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 |
3 | const BaseTag = types
4 | .model('BaseTag');
5 |
6 | export { BaseTag };
7 |
--------------------------------------------------------------------------------
/src/tags/control/Choices/Choises.styl:
--------------------------------------------------------------------------------
1 | .choices
2 | margin-top 1em
3 | margin-bottom 1em
4 |
5 | &_hidden
6 | display none
--------------------------------------------------------------------------------
/src/tags/control/Labels/Labels.styl:
--------------------------------------------------------------------------------
1 | .labels
2 | margin 1em 0
3 | display flex
4 | justify-content flex-start
5 | align-items center
6 | flex-flow wrap
7 |
8 | &_hidden
9 | display none
10 |
11 | &:not(&_inline)
12 | margin 0
13 | flex-direction column
14 | align-items flex-start
15 |
16 |
--------------------------------------------------------------------------------
/src/tags/control/Taxonomy/Taxonomy.styl:
--------------------------------------------------------------------------------
1 | .taxonomy
2 | margin-top 1em
3 | margin-bottom 1em
4 |
5 | &__loading
6 | border 1px solid #d9d9d9
7 | border-radius 6px
8 | height 38px
9 | display flex
10 | align-items center
11 | justify-content center
12 | margin-top 42px
13 | margin-bottom 14px
14 | width 90px
15 | position relative
16 |
17 | & > div
18 | height 14px
19 |
20 | & > div > span
21 | display block
22 |
23 | &__new &__loading
24 | margin-top 0
25 | height 31px
26 |
--------------------------------------------------------------------------------
/src/tags/object/AudioNext/constants.ts:
--------------------------------------------------------------------------------
1 | export const WS_ZOOM_X = {
2 | min: 1,
3 | max: 1500,
4 | step: 10,
5 | default: 1,
6 | };
7 |
8 | export const WS_SPEED = {
9 | min: 0.5,
10 | max: 2,
11 | step: 0.01,
12 | default: 1,
13 | };
14 |
15 | export const WS_VOLUME = {
16 | min: 0,
17 | max: 1,
18 | step: 0.01,
19 | default: 1,
20 | };
21 |
--------------------------------------------------------------------------------
/src/tags/object/AudioPlus/AudioPlus.module.scss:
--------------------------------------------------------------------------------
1 | .play {
2 | background: #52c41a;
3 | border: 1px solid #52c41a;
4 |
5 | &:hover {
6 | background: #73d13d;
7 | border: 1px solid #73d13d;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/tags/object/AudioUltra/constants.ts:
--------------------------------------------------------------------------------
1 | export const WS_ZOOM_X = {
2 | min: 1,
3 | max: 1500,
4 | step: 10,
5 | default: 1,
6 | };
7 |
8 | export const WS_SPEED = {
9 | min: 0.5,
10 | max: 2,
11 | step: 0.01,
12 | default: 1,
13 | };
14 |
15 | export const WS_VOLUME = {
16 | min: 0,
17 | max: 1,
18 | step: 0.01,
19 | default: 1,
20 | };
21 |
--------------------------------------------------------------------------------
/src/tags/object/AudioUltra/view.styl:
--------------------------------------------------------------------------------
1 | .audio-tag
2 | width 100%
3 |
--------------------------------------------------------------------------------
/src/tags/object/Image/ImageSelectionPoint.js:
--------------------------------------------------------------------------------
1 | import { types } from 'mobx-state-tree';
2 |
3 | export const ImageSelectionPoint = types.model({
4 | x: types.number,
5 | y: types.number,
6 | });
7 |
--------------------------------------------------------------------------------
/src/tags/object/Image/index.js:
--------------------------------------------------------------------------------
1 | export { ImageModel, HtxImage } from './Image';
2 |
--------------------------------------------------------------------------------
/src/tags/object/Paragraphs/index.js:
--------------------------------------------------------------------------------
1 | import Registry from '../../../core/Registry';
2 | import { ParagraphsModel } from './model';
3 | import { HtxParagraphs } from './HtxParagraphs';
4 |
5 | Registry.addTag('paragraphs', ParagraphsModel, HtxParagraphs);
6 | Registry.addObjectType(ParagraphsModel);
7 |
8 | export * from './model';
9 | export * from './HtxParagraphs';
10 |
--------------------------------------------------------------------------------
/src/tags/object/RichText/index.js:
--------------------------------------------------------------------------------
1 | import { RichTextModel } from './model';
2 | import { HtxRichText } from './view';
3 | import Registry from '../../../core/Registry';
4 |
5 | Registry.addTag('text', RichTextModel, HtxRichText({ isText: true }));
6 | Registry.addTag('hypertext', RichTextModel, HtxRichText({ isText: false }));
7 | Registry.addObjectType(RichTextModel);
8 |
9 | export { RichTextModel, HtxRichText };
10 |
--------------------------------------------------------------------------------
/src/tags/object/Video/index.js:
--------------------------------------------------------------------------------
1 | import { inject, observer } from 'mobx-react';
2 | import Registry from '../../../core/Registry';
3 |
4 | import { HtxVideoView } from './HtxVideo';
5 | import { VideoModel } from './Video';
6 |
7 | const HtxVideo = inject('store')(observer(HtxVideoView));
8 |
9 | Registry.addTag('video', VideoModel, HtxVideo);
10 | Registry.addObjectType(VideoModel);
11 |
12 | export { VideoModel, HtxVideo };
13 |
--------------------------------------------------------------------------------
/src/tags/object/Video/types.ts:
--------------------------------------------------------------------------------
1 | import { Shape, ShapeConfig } from 'konva/lib/Shape';
2 | import { Stage } from 'konva/lib/Stage';
3 |
4 | export type WorkingArea = {
5 | width: number,
6 | height: number,
7 | x: number,
8 | y: number,
9 | scale: number,
10 | realWidth: number,
11 | realHeight: number,
12 | }
13 |
14 | export type KonvaNode = Shape | Stage;
15 |
--------------------------------------------------------------------------------
/src/tags/object/index.js:
--------------------------------------------------------------------------------
1 | import { AudioModel } from './AudioNext';
2 | import { ImageModel } from './Image';
3 | import { ParagraphsModel } from './Paragraphs';
4 | import { RichTextModel } from './RichText';
5 | import { TableModel } from './Table';
6 | import { TimeSeriesModel } from './TimeSeries';
7 | import { PagedViewModel } from './PagedView';
8 | import { VideoModel } from './Video';
9 | import { ListModel } from './List';
10 |
11 | // stub files to keep docs of these tags
12 | import './HyperText';
13 | import './Text';
14 |
15 | export {
16 | AudioModel,
17 | ImageModel,
18 | ParagraphsModel,
19 | TimeSeriesModel,
20 | RichTextModel,
21 | VideoModel,
22 | TableModel,
23 | PagedViewModel,
24 | ListModel
25 | };
26 |
--------------------------------------------------------------------------------
/src/tags/visual/__tests__/Header.test.jsx:
--------------------------------------------------------------------------------
1 | /* global test, expect, jest */
2 | import Enzyme, { render } from "enzyme";
3 | import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
4 | import { HtxHeader } from "../Header";
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
8 | jest.mock('react', () => ({
9 | ...jest.requireActual('react'),
10 | useLayoutEffect: jest.requireActual('react').useEffect,
11 | }));
12 |
13 | test("Header basic test", () => {
14 | const confStore = {
15 | _value: "header text",
16 | underline: true,
17 | size: 1,
18 | };
19 |
20 | const view = render();
21 | const text = view.text();
22 |
23 | expect(text).toBe("header text");
24 | });
25 |
--------------------------------------------------------------------------------
/src/tags/visual/index.js:
--------------------------------------------------------------------------------
1 | import { CollapseModel } from './Collapse';
2 | import { DialogModel } from './Dialog';
3 | import { HeaderModel } from './Header';
4 | import { ViewModel } from './View';
5 | import { StyleModel } from './Style';
6 | import { FilterModel } from './Filter';
7 |
8 | export { CollapseModel, DialogModel, HeaderModel, ViewModel, StyleModel, FilterModel };
9 |
--------------------------------------------------------------------------------
/src/themes/default/colors.styl:
--------------------------------------------------------------------------------
1 | $black = #000
2 | $accent_color = #0099FF
3 | $danger_color = #d00
4 | $magic_color = #944BFF
5 |
6 | $black_2 = rgba($black, 0.02)
7 | $black_10 = rgba($black, 0.1)
8 | $black_15 = rgba($black, 0.15)
9 | $black_20 = rgba($black, 0.20)
10 | $black_40 = rgba($black, 0.40)
11 |
12 | @require "./tools/*"
13 |
--------------------------------------------------------------------------------
/src/themes/default/scrollbar.styl:
--------------------------------------------------------------------------------
1 | styled-scrollbars()
2 | scrollbar-color: rgb(180, 180, 180) transparent;
3 | scrollbar-width: thin;
4 |
5 | &::-webkit-scrollbar
6 | width 4px
7 | height 4px
8 | background-color #fff
9 |
10 | &::-webkit-scrollbar-thumb
11 | background rgb(180, 180, 180)
12 |
--------------------------------------------------------------------------------
/src/themes/default/tools/scrollbar.styl:
--------------------------------------------------------------------------------
1 | styled-scrollbars()
2 | scrollbar-color: rgb(180, 180, 180) transparent;
3 | scrollbar-width: thin;
4 |
5 | &::-webkit-scrollbar
6 | width 4px
7 | height 4px
8 | background-color #fff
9 |
10 | &::-webkit-scrollbar-thumb
11 | background rgb(180, 180, 180)
12 |
--------------------------------------------------------------------------------
/src/themes/default/tools/waiting.styl:
--------------------------------------------------------------------------------
1 | waiting(c1 = #efefef, c2 = #fff)
2 | $base_color = rgba(c2, 0.2)
3 | $accent_color = c1
4 |
5 | background-image repeating-linear-gradient(
6 | -63.43deg,
7 | $base_color 1px,
8 | $accent_color 2px,
9 | $accent_color 6px,
10 | $base_color 7px,
11 | $base_color 12px
12 | )
13 |
14 | background-color c2
15 | background-size 37px 100%
16 |
17 | &_animated
18 | animation waiting 1s linear infinite
19 |
20 | @keyframes waiting
21 | 0%
22 | background-position 0 0
23 |
24 | 100%
25 | background-position 37px 0
26 |
--------------------------------------------------------------------------------
/src/tools/Tools.module.scss:
--------------------------------------------------------------------------------
1 | .tooltitle {
2 | text-transform: uppercase;
3 | font-weight: bold;
4 | font-size: 8px;
5 | color: #666;
6 | }
7 |
--------------------------------------------------------------------------------
/src/tools/index.js:
--------------------------------------------------------------------------------
1 | // export { default as Zoom } from "./Zoom";
2 | // export { default as KeyPoint } from "./KeyPoint";
3 |
4 | import { Brush } from './Brush';
5 | import { Erase } from './Erase';
6 | import { KeyPoint } from './KeyPoint';
7 | import { Polygon } from './Polygon';
8 | import { Rect, Rect3Point } from './Rect';
9 | import { Ellipse } from './Ellipse';
10 | import { Zoom } from './Zoom';
11 | import { Rotate } from './Rotate';
12 | import { Brightness } from './Brightness';
13 | import { Contrast } from './Contrast';
14 | import { MagicWand } from './MagicWand';
15 | import { Selection } from './Selection';
16 |
17 | export { Brush, Erase, KeyPoint, Polygon, Rect, Rect3Point, Ellipse, Brightness, Contrast, Rotate, Zoom, MagicWand, Selection };
18 |
--------------------------------------------------------------------------------
/src/utils/__tests__/debounce.test.js:
--------------------------------------------------------------------------------
1 | /* global jest, describe, expect, beforeEach, test */
2 | import { debounce } from '../debounce';
3 |
4 | jest.useFakeTimers();
5 |
6 | describe('Debounce function', () => {
7 | let func;
8 | let debouncedFunc;
9 |
10 | beforeEach(() => {
11 | func = jest.fn();
12 | debouncedFunc = debounce(func, 1000);
13 | });
14 |
15 | test('Execute just once', () => {
16 | for (let i = 0; i < 100; i++) {
17 | debouncedFunc();
18 | }
19 |
20 | jest.runAllTimers();
21 |
22 | expect(func).toBeCalledTimes(1);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/utils/__tests__/unique.test.js:
--------------------------------------------------------------------------------
1 | /* global it, expect */
2 | import { guidGenerator } from '../unique';
3 |
4 | it('Random ID generate', () => {
5 | expect(guidGenerator(10)).toHaveLength(10);
6 | });
7 |
--------------------------------------------------------------------------------
/src/utils/hooks.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const useRenderTime = name => {
4 | console.time(`RENDER TIME ${name}`);
5 |
6 | React.useEffect(() => console.timeEnd(`RENDER TIME ${name}`), [name]);
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as Checkers from './utilities';
2 | import * as Colors from './colors';
3 | import * as Magicwand from './magic-wand';
4 | import * as Image from './image';
5 | import * as UDate from './date';
6 | import * as HTML from './html';
7 | import * as Selection from './selection-tools';
8 | import { debounce } from './debounce';
9 | import { guidGenerator } from './unique';
10 | import { styleToProp } from './styles';
11 |
12 | export default {
13 | Image,
14 | HTML,
15 | Checkers,
16 | Colors,
17 | UDate,
18 | guidGenerator,
19 | debounce,
20 | styleToProp,
21 | Magicwand,
22 | Selection,
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/namedColors.ts:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | red: '#F5222D',
3 | volcano: '#FA541C',
4 | orange: '#FA8C16',
5 | gold: '#FAAD14',
6 | yellow: '#FADB14',
7 | lime: '#A0D911',
8 | green: '#52C41A',
9 | cyan: '#13C2C2',
10 | blue: '#1890FF',
11 | geekBlue: '#2F54EB',
12 | purple: '#722ED1',
13 | magenta: '#EB2F96',
14 | accent: '#0099FF',
15 | };
16 |
--------------------------------------------------------------------------------
/src/utils/resize-observer.ts:
--------------------------------------------------------------------------------
1 | class ResizeObserverFallback {
2 | observe() {
3 |
4 | }
5 | unobserve() {
6 |
7 | }
8 | disconnect() {
9 |
10 | }
11 | }
12 |
13 | const ResizeObserver = window.ResizeObserver ?? ResizeObserverFallback;
14 |
15 | export default ResizeObserver;
16 |
--------------------------------------------------------------------------------
/src/utils/unique.ts:
--------------------------------------------------------------------------------
1 | // @todo for nanoid@3 there should be default import
2 | import { nanoid } from 'nanoid';
3 |
4 | /**
5 | * Unique hash generator
6 | * @param {number} lgth
7 | */
8 | export const guidGenerator = (length = 10) => nanoid(length);
9 |
--------------------------------------------------------------------------------
/tests/functional/.gitignore:
--------------------------------------------------------------------------------
1 | output/*
2 | !output/snapshots/
3 | runner-results/
4 | xunit.xml
5 | test-results.xml
6 | .nyc_output
7 |
--------------------------------------------------------------------------------
/tests/functional/cypress.config.js:
--------------------------------------------------------------------------------
1 | import configure from '@heartexlabs/ls-test';
2 |
3 | export default configure();
4 |
--------------------------------------------------------------------------------
/tests/functional/cypress/parallel-weights.json:
--------------------------------------------------------------------------------
1 | {"specs/core/feature_flags.cy.ts":{"time":3845,"weight":7},"specs/core/label_studio.cy.ts":{"time":3791,"weight":7},"specs/image_segmentation/basic.cy.ts":{"time":6895,"weight":14}}
--------------------------------------------------------------------------------
/tests/functional/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | import { CURRENT_FLAGS } from '../../feature-flags';
2 | import '@heartexlabs/ls-test/cypress/support/e2e';
3 |
4 | beforeEach(() => {
5 | cy.on('uncaught:exception', err => {
6 | return !err.message.includes('ResizeObserver loop completed with undelivered notifications.');
7 | });
8 | cy.on('window:before:load', (win) => {
9 | console.log('Setting feature flags', CURRENT_FLAGS);
10 | Object.assign(win, {
11 | DISABLE_DEFAULT_LSF_INIT: true,
12 | APP_SETTINGS: {
13 | ...(win.APP_SETTINGS ?? {}),
14 | feature_flags: CURRENT_FLAGS,
15 | },
16 | });
17 | });
18 | cy.log('Feature flags set');
19 | });
20 |
--------------------------------------------------------------------------------
/tests/functional/data/core/hotkeys.ts:
--------------------------------------------------------------------------------
1 | export const createConfigWithHotkey = (hotkey: string) => `
2 |
3 |
4 |
5 |
6 | `;
7 |
--------------------------------------------------------------------------------
/tests/functional/data/image_segmentation/crosshair.ts:
--------------------------------------------------------------------------------
1 | export const crosshairConfig = `
2 |
3 |
4 |
5 | `;
6 |
--------------------------------------------------------------------------------
/tests/functional/data/image_segmentation/tools/magic-wand.ts:
--------------------------------------------------------------------------------
1 | export const magicWandConfig = `
2 |
3 |
4 |
5 |
6 | `;
7 |
8 | export const magicWandImageData = {
9 | image: 'https://htx-pub.s3.amazonaws.com/samples/magicwand/magic_wand_scale_1_20200902_015806_26_2235_1B_AnalyticMS_00750_00750.jpg',
10 | };
11 |
--------------------------------------------------------------------------------
/tests/functional/data/image_segmentation/tools/rect3point.ts:
--------------------------------------------------------------------------------
1 | export const rect3Config = `
2 |
3 |
4 |
5 |
6 | `;
7 |
8 | export const simpleImageData = {
9 | image: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg',
10 | };
--------------------------------------------------------------------------------
/tests/functional/data/image_segmentation/tools/zoom.ts:
--------------------------------------------------------------------------------
1 | export const simpleConfig = `
2 |
3 |
4 |
5 |
6 |
7 |
8 | `;
9 |
10 | export const simpleImageData = {
11 | image: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg',
12 | };
--------------------------------------------------------------------------------
/tests/functional/feature-flags.ts:
--------------------------------------------------------------------------------
1 | import * as FLAGS from '../../src/utils/feature-flags';
2 |
3 | export const CURRENT_FLAGS = {
4 | [FLAGS.FF_DEV_1284]: true,
5 | [FLAGS.FF_DEV_1170]: true,
6 | [FLAGS.FF_PROD_309]: true,
7 | [FLAGS.FF_LSDV_4930]: true,
8 | [FLAGS.FF_LSDV_4992]: true,
9 | [FLAGS.FF_LSDV_4673]: true,
10 | [FLAGS.FF_DEV_2100]: true,
11 | [FLAGS.FF_DEV_2715]: true,
12 | [FLAGS.FF_LSDV_4620_3]: true,
13 | [FLAGS.FF_LSDV_4620_3_ML]: true,
14 | [FLAGS.FF_OUTLINER_OPTIM]: true,
15 | [FLAGS.FF_DBLCLICK_DELAY]: true,
16 | [FLAGS.FF_ZOOM_OPTIM]: true,
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/tests/functional/multi-reporter-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "reporterEnabled": "cypress-parallel/json-stream.reporter.js, spec"
3 | }
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/Audio -- Renders audio with merged channels by default.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/Audio -- Renders audio with merged channels by default.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/Audio -- Renders separate audio channels with splitchannels=true.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/Audio -- Renders separate audio channels with splitchannels=true.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/Audio Paragraphs Sync -- Correctly loads with Paragraph segments as Audio segments.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/Audio Paragraphs Sync -- Correctly loads with Paragraph segments as Audio segments.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/Audio Paragraphs Sync -- Highlights the correct Audio segment whenever it is played or seeked.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/Audio Paragraphs Sync -- Highlights the correct Audio segment whenever it is played or seeked.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/Audio Paragraphs Sync -- Correctly loads with Paragraph segments as Audio segments.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/Audio Paragraphs Sync -- Correctly loads with Paragraph segments as Audio segments.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/HighlightAfterFinishedPlayback.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/HighlightAfterFinishedPlayback.snap.png
--------------------------------------------------------------------------------
/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/HighlightOnFirstSeek.snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HumanSignal/label-studio-frontend/4bc1393620845c06c5b59ccd643580ab5c0e6e67/tests/functional/output/snapshots/audio/audio_paragraphs.cy.ts/HighlightOnFirstSeek.snap.png
--------------------------------------------------------------------------------
/tests/functional/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "baseUrl": "./",
5 | "noImplicitThis": true,
6 | "lib": ["es5", "dom"],
7 | "types": ["cypress", "node"]
8 | },
9 | "include": [
10 | "./node_modules/cypress",
11 | "**/*.ts",
12 | "./node_modules/@heartexlabs/ls-test/**/*.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "allowJs": true,
6 | "checkJs": false,
7 | "jsx": "react",
8 | "strict": true,
9 | "rootDirs": ["./src"],
10 | "typeRoots": ["./types", "./node_modules/@types"],
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 | "forceConsistentCasingInFileNames": true,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "allowJs": false,
6 | "checkJs": false,
7 | "jsx": "preserve",
8 | "strict": true,
9 | "rootDirs": ["./src"],
10 | "typeRoots": ["./types", "./node_modules/@types"],
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 | "forceConsistentCasingInFileNames": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/types/Global.d.ts:
--------------------------------------------------------------------------------
1 | import { ComponentClass, FC, FunctionComponent, ReactHTML, ReactSVG } from "react";
2 |
3 | declare type AnyComponent = FC | keyof ReactHTML | keyof ReactSVG | ComponentClass | FunctionComponent | string
4 |
5 | declare global {
6 | interface Window {
7 | APP_SETTINGS: Record;
8 | FEATURE_FLAGS?: Record;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/types/Keymap.d.ts:
--------------------------------------------------------------------------------
1 | declare interface Hotkey {
2 | description: string;
3 | key: string;
4 | mac?: string;
5 | modifier?: string;
6 | modifierDescription?: string;
7 | }
8 |
9 | declare interface Keymap {
10 | [key: string]: Hotkey;
11 | }
12 |
13 | declare module '*/keymap.json' {
14 | export type K = Keymap;
15 | }
16 |
--------------------------------------------------------------------------------
/types/React.d.ts:
--------------------------------------------------------------------------------
1 | import "react";
2 |
3 | type CustomProp = { [key in `--${string}`]: string };
4 |
5 | declare module "react" {
6 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
7 | export interface CSSProperties extends CustomProp {}
8 | }
9 |
--------------------------------------------------------------------------------
/types/Regions.d.ts:
--------------------------------------------------------------------------------
1 | declare interface LSFResult {
2 | type: string;
3 | from_name: any; // tag on page
4 | to_name: any; // tag on page
5 | value: Record;
6 | mainValue: any;
7 | }
8 |
9 | declare interface LSFRegion {
10 | results: LSFResult[];
11 | }
12 |
--------------------------------------------------------------------------------
/types/shallow-equal.d.ts:
--------------------------------------------------------------------------------
1 | declare module "shallow-equal" {
2 | export function shallowEqualArrays(arr1: any[], arr2: any[]): boolean;
3 | export function shallowEqualObjects(arr1: Record, arr2: Record): boolean;
4 | }
5 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./webpack.config-builder")();
2 |
--------------------------------------------------------------------------------