diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09627d83..013fff62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: "${{ env.NODE }}" diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 2fb9f934..74518112 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/git-command.yml b/.github/workflows/git-command.yml index 9c17ff7c..b9185c83 100644 --- a/.github/workflows/git-command.yml +++ b/.github/workflows/git-command.yml @@ -19,7 +19,7 @@ jobs: run: echo "$PAYLOAD_CONTEXT" - name: Checkout on chat command - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.GIT_PAT }} repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} diff --git a/src/components/CandidateTaskView/CandidateTaskView.js b/src/components/CandidateTaskView/CandidateTaskView.js deleted file mode 100644 index 0338a76a..00000000 --- a/src/components/CandidateTaskView/CandidateTaskView.js +++ /dev/null @@ -1,183 +0,0 @@ -import { observer } from "mobx-react"; -import React, { forwardRef, useCallback, useRef } from "react"; -import { Block, Elem } from "../../utils/bem"; -import { Spinner } from "../Common/Spinner"; -import "./CandidateTaskView.styl"; -import { getRoot } from "mobx-state-tree"; -import { useEffect, useState } from "react"; -import { format } from "date-fns"; -import { FF_LSDV_4711, isFF } from "../../utils/feature-flags"; - -const imgDefaultProps = {}; - -if (isFF(FF_LSDV_4711)) imgDefaultProps.crossOrigin = 'anonymous'; - -const DataItemVisual = forwardRef(({ columns, dataKey, data }, imageRef) => { - const [isInProgress, setIsInProgress] = useState(); - const [fileContent, setFileContent] = useState(); - const [isFileError, setIsFileError] = useState(false); - const isUrlData = /https?:\/\/.*/.test(data); - const columnDefinition = columns.find(colData => colData.alias === dataKey); - - useEffect(async () => { - if ( isUrlData && columnDefinition?.currentType === "Text" ) { - setIsInProgress(true); - setIsFileError(false); - let response; - - try { - response = await fetch(data); - } catch (ex) { - response = ex; - } - - if (response?.status === 200) { - setFileContent(await response.text()); - } else { - console.error("Error:", response); - setIsFileError(true); - } - setIsInProgress(false); - } - }, [isUrlData, data, columnDefinition]); - - if (columnDefinition?.currentType === "Image") { - return ( - - - - ); - } else if (columnDefinition?.currentType === "Text" && isUrlData && !isFileError) { - return ( - - {isInProgress ? : ( - - {fileContent} - - )} - - ); - } else if (isUrlData) { - return ( - - {data} - - ); - } - return ( - - {data} - - ); -}); - -const AttributeRow = (({ fieldName, value }) => { - return ( - - {fieldName} - {value} - - ); -}); -const dateDisplayFormat = "MMM dd, yyyy HH:mm a"; - -export const CandidateTaskView = observer(({ item, columns }) => { - const { candidate_task_id, id, data, exported } = item; - const dataset = getRoot(item)?.SDK?.dataset; - const [fName, setFName] = useState(); - const [fType, setFType] = useState(); - const [mType, setMType] = useState(); - const [created, setCreated] = useState(); - const [modified, setModified] = useState(); - const [size, setSize] = useState(); - const [dimensions, setDimensions] = useState([]); - const [bucket, setBucket] = useState(); - const imgRef = useRef({}); - const associatedList = getRoot(item).taskStore.associatedList; - - useEffect(() => { - const setDefaultMetadata = async () => { - const { metadata } = await getRoot(item).apiCall("candidateTaskMeta", { - candidate_task_id, - }); - - if (metadata) { - setFName(metadata.name.split('/').pop()); - setFType(metadata.contentType.split('/').shift()); - setMType(metadata.contentType); - setCreated(metadata.timeCreated ? format(new Date(metadata.timeCreated), dateDisplayFormat) : ""); - setModified(metadata.updated ? format(new Date(metadata.updated), dateDisplayFormat) : ""); - setSize(`${new Intl.NumberFormat().format(parseInt(metadata.size))} bytes`); - setBucket(metadata.bucket); - setDimensions(Object.values(imgRef.current).map(ref => `${ref?.naturalWidth ?? 0} x ${ref?.naturalHeight ?? 0} px`)); - } - }; - - setDefaultMetadata(); - }, [candidate_task_id]); - - - return ( - - - {Object.entries(data).map( ([dataKey, dataValue]) => ( - imgRef.current[dataKey] = ele} /> - ))} - - - - File Attributes - {fName} - - - - General - - - - - - - - {dim})}/> - - - - - Origin Storage - - - - - - - - Projects - { - (associatedList.length && exported.length) ? ( - exported.map((exportedEntry, index) => { - const { project_id, created_at } = exportedEntry; - const associtedRecord = associatedList?.find(associatedItem => associatedItem?.id === project_id); - const { title, workspace } = associtedRecord; - const clickHandler = useCallback(e => { - e.preventDefault(); - window.open(`/projects/${project_id}/data`, '_self'); - }, [project_id]); - - return ( - - {workspace?.length && `${workspace.join(" - ")} / `}{title} - {created_at && Added {format(new Date(created_at), dateDisplayFormat)}} - - ); - }) - ) : ( - <>This file hasn’t been imported to any projects. - ) - } - - - - - ); -}); \ No newline at end of file diff --git a/src/components/CandidateTaskView/CandidateTaskView.styl b/src/components/CandidateTaskView/CandidateTaskView.styl deleted file mode 100644 index 0474a206..00000000 --- a/src/components/CandidateTaskView/CandidateTaskView.styl +++ /dev/null @@ -1,116 +0,0 @@ -.candidate-task-view - --border-color rgba(137, 128, 152, 0.12) - --border-color-hard rgba(137, 128, 152, 0.16) - display flex - gap 16px - align-items stretch - &__data-display-container - display flex - flex 1 auto - flex-direction column - gap 16px - &__data-display - border 1px solid var(--border-color-hard) - flex 1 auto - &_image - background linear-gradient(0deg, rgba(9, 109, 217, 0.05), rgba(9, 109, 217, 0.05)), #FAFAFA - display flex - align-items center - justify-content center - img - display block - max-width 100% - height auto - width auto - max-height: calc(80vh - var(--header-height) - var(--ribbon-height)); - &_text, - &_link - padding 12px 16px - &__details - background-color #FAFAFA - border-radius 8px - display flex - width 256px - border 1px solid var(--border-color-hard) - flex-direction column - flex-shrink 0 - &__title - font-style normal - font-weight 500 - font-size 16px - line-height 24px - letter-spacing 0.1px - color #1F1F1F - &__detailContainer - padding 16px - border-top 1px solid var(--border-color) - &:first-child - border-top 0 - &__detailSubContainer - border-top 1px solid var(--border-color) - padding 18px 0 8px - &:first-child - border-top 0 - padding-top 0 - &__subtitle - font-family 'Roboto' - font-weight 500 - font-size 12px - line-height 16px - letter-spacing 0.5px - color #898098 - margin-bottom 16px - &__detailContent - font-family 'Roboto' - font-weight 400 - font-size 12px - line-height 16px - letter-spacing 0.4px - color #898098 - -.attributeRow - display flex - justify-content space-between - font-family 'Roboto' - margin-top 8px - flex-wrap wrap - &__name - font-weight 500 - font-size 12px - line-height 16px - letter-spacing 0.5px - color #1F1F1F - &__value - font-weight 400 - font-size 12px - line-height 16px - letter-spacing 0.4px - color #1F1F1F - overflow auto - &:first-child - margin-top 0 - -.projectNav - margin-top 8px - cursor pointer - &:first-child - margin-top 0 - &__name - font-weight 500 - font-size 11px - line-height 16px - letter-spacing 0.5px - color #096DD9 - &__date - font-weight 400 - font-size 12px - line-height 16px - letter-spacing 0.4px - color #898098 - -@media (max-width 600px) - .candidate-task-view - flex-direction column - &__details - width auto - flex 1 auto \ No newline at end of file diff --git a/src/components/CandidateTaskView/index.js b/src/components/CandidateTaskView/index.js deleted file mode 100644 index 755a60b5..00000000 --- a/src/components/CandidateTaskView/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import { CandidateTaskView } from "./CandidateTaskView"; -export { CandidateTaskView }; \ No newline at end of file diff --git a/src/components/CellViews/index.js b/src/components/CellViews/index.js index af9a0cd5..b4dd6c5f 100644 --- a/src/components/CellViews/index.js +++ b/src/components/CellViews/index.js @@ -11,4 +11,5 @@ export { DateTimeCell as Date, DateTimeCell as Datetime } from "./DateTimeCell"; export { ImageCell as Image } from "./ImageCell"; export { NumberCell as Number } from "./NumberCell"; export { StringCell as String } from "./StringCell"; +export { StringCell as Text } from "./StringCell"; export { VideoCell as Video } from "./VideoCell"; diff --git a/src/components/Common/SkeletonLoader/SkeletonGap.tsx b/src/components/Common/SkeletonLoader/SkeletonGap.tsx new file mode 100644 index 00000000..68b66000 --- /dev/null +++ b/src/components/Common/SkeletonLoader/SkeletonGap.tsx @@ -0,0 +1,7 @@ +import { Elem } from '../../../utils/bem'; + +export const SkeletonGap = ({ height = "4px" }: { height?: string }) => { + return ( + + ); +}; diff --git a/src/components/Common/SkeletonLoader/SkeletonLine.tsx b/src/components/Common/SkeletonLoader/SkeletonLine.tsx new file mode 100644 index 00000000..4c9d0abb --- /dev/null +++ b/src/components/Common/SkeletonLoader/SkeletonLine.tsx @@ -0,0 +1,14 @@ +import { Elem } from '../../../utils/bem'; + +export const SkeletonLine = ({ lineCount = 1, width = "60%", height = "16px" }: { lineCount?: number, width?: string, height?: string }) => { + const rows = []; + + for(let i = 0; i < lineCount; i++) { + rows.push(); + } + return ( + <> + {rows} + + ); +}; diff --git a/src/components/Common/SkeletonLoader/SkeletonLoader.styl b/src/components/Common/SkeletonLoader/SkeletonLoader.styl new file mode 100644 index 00000000..b8f3cd3b --- /dev/null +++ b/src/components/Common/SkeletonLoader/SkeletonLoader.styl @@ -0,0 +1,45 @@ +.skeletonLoader + --skeleton-light-color linear-gradient(0deg, rgba(9, 109, 217, 0.05), rgba(9, 109, 217, 0.05)), #FAFAFA + --skeleton-dark-color linear-gradient(0deg, rgba(9, 109, 217, 0.14), rgba(9, 109, 217, 0.14)), #FAFAFA + --skeleton-gap 4px + box-sizing border-box + display flex + flex-direction column + justify-content space-between + gap var(--skeleton-gap) + position relative + width 100% + flex 0 0 auto + margin-left 0 + z-index 1 + color transparent + &__line + --line-width 60% + --line-height 16px + height var(--line-height) + width var(--line-width) + min-width 20px + border-radius 8px + display block + background var(--skeleton-light-color) + animation-name: skeleton-animation; + animation-timing-function ease-in-out + animation-duration 0.5s + animation-iteration-count: infinite; + animation-direction: alternate; + &__gap + --height 8px + height var(--height) + + +@keyframes skeleton-animation + 0% + background var(--skeleton-light-color) + opacity 1 + + 50% + opacity 0.8 + + 100% + background var(--skeleton-dark-color) + opacity 1 \ No newline at end of file diff --git a/src/components/Common/SkeletonLoader/SkeletonLoader.tsx b/src/components/Common/SkeletonLoader/SkeletonLoader.tsx new file mode 100644 index 00000000..7144b83b --- /dev/null +++ b/src/components/Common/SkeletonLoader/SkeletonLoader.tsx @@ -0,0 +1,32 @@ +import React, { ReactChildren } from 'react'; +import './SkeletonLoader.styl'; +import { Block } from '../../../utils/bem'; +import { SkeletonLine } from './SkeletonLine'; +import { SkeletonGap } from './SkeletonGap'; + +interface SkeletonLoaderProps { + children?: ReactChildren, + gap?: string, + lightColor?: string, + darkColor?: string, +} + +export const SkeletonLoader = ({ children, gap = "4px", lightColor, darkColor }: SkeletonLoaderProps) => { + const styles:any = { "--skeleton-gap": gap }; + + lightColor && (styles["--skeleton-light-color"] = lightColor); + darkColor && (styles["--skeleton-dark-color"] = darkColor); + + return ( + + {children ? children : ( + <> + + + + + + )} + + ); +}; diff --git a/src/components/Common/SkeletonLoader/index.tsx b/src/components/Common/SkeletonLoader/index.tsx new file mode 100644 index 00000000..381941a4 --- /dev/null +++ b/src/components/Common/SkeletonLoader/index.tsx @@ -0,0 +1,3 @@ +export { SkeletonLoader } from "./SkeletonLoader"; +export { SkeletonLine } from "./SkeletonLine"; +export { SkeletonGap } from "./SkeletonGap"; \ No newline at end of file diff --git a/src/components/Common/Table/TableRow/TableRow.js b/src/components/Common/Table/TableRow/TableRow.js index b854d425..0d328a76 100644 --- a/src/components/Common/Table/TableRow/TableRow.js +++ b/src/components/Common/Table/TableRow/TableRow.js @@ -5,6 +5,8 @@ import { Block } from "../../../../utils/bem"; import { TableContext, TableElem } from "../TableContext"; import { getProperty, getStyle } from "../utils"; import "./TableRow.styl"; +import { SkeletonLoader } from "../../SkeletonLoader"; +import { FF_LOPS_E_3, isFF } from "../../../../utils/feature-flags"; const CellRenderer = observer( ({ col: colInput, data, decoration, cellViews }) => { @@ -29,6 +31,7 @@ const CellRenderer = observer( const renderProps = { column: col, original: data, value }; const Decoration = decoration?.get?.(col); const style = getStyle(cellViews, col, Decoration); + const cellIsLoading = isFF(FF_LOPS_E_3) && data.loading === colInput.alias; return ( @@ -37,10 +40,10 @@ const CellRenderer = observer( ...(style ?? {}), display: "flex", height: "100%", - alignItems: "center", + alignItems: cellIsLoading ? "" : "center", }} > - {Renderer ? : value} + {cellIsLoading ? : (Renderer ? : value)} ); diff --git a/src/components/Common/TableOld/Table.js b/src/components/Common/TableOld/Table.js index fcf88f9e..4fde4dbd 100644 --- a/src/components/Common/TableOld/Table.js +++ b/src/components/Common/TableOld/Table.js @@ -398,6 +398,8 @@ const StickyList = observer( itemCount={totalCount} loadMoreItems={loadMore} isItemLoaded={isItemLoaded} + threshold={5} + minimumBatchSize={30} > {({ onItemsRendered, ref }) => ( { @@ -29,6 +31,7 @@ const CellRenderer = observer( const renderProps = { column: col, original: data, value }; const Decoration = decoration?.get?.(col); const style = getStyle(cellViews, col, Decoration); + const cellIsLoading = isFF(FF_LOPS_E_3) && data.loading === colInput.alias; return ( @@ -37,10 +40,10 @@ const CellRenderer = observer( ...(style ?? {}), display: "flex", height: "100%", - alignItems: "center", + alignItems: cellIsLoading ? "" : "center", }} > - {Renderer ? : value} + {cellIsLoading ? : (Renderer ? : value)} ); diff --git a/src/components/DataManager/Toolbar/ActionsButton.js b/src/components/DataManager/Toolbar/ActionsButton.js index 28c2a91d..a38403f7 100644 --- a/src/components/DataManager/Toolbar/ActionsButton.js +++ b/src/components/DataManager/Toolbar/ActionsButton.js @@ -135,7 +135,7 @@ export const ActionsButton = injector(observer(({ store, size, hasSelected, ...r }; const actionButtons = actions.map(ActionButton); - const recordTypeLabel = isFFLOPSE3 && store.SDK.type === "DE" ? "Item" : "Task"; + const recordTypeLabel = isFFLOPSE3 && store.SDK.type === "DE" ? "Record" : "Task"; return ( isFFLOPSE3 && setIsOpen(visible)} >