Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ModelZoo
sam2_pytorch
Commits
3af09475
Commit
3af09475
authored
Dec 05, 2025
by
luopl
Browse files
"Initial commit"
parents
Pipeline
#3140
canceled with stages
Changes
585
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1933 additions
and
0 deletions
+1933
-0
demo/frontend/src/common/components/effects/MobileEffectsToolbar.tsx
...nd/src/common/components/effects/MobileEffectsToolbar.tsx
+115
-0
demo/frontend/src/common/components/effects/MoreFunEffects.tsx
...frontend/src/common/components/effects/MoreFunEffects.tsx
+54
-0
demo/frontend/src/common/components/gallery/ChangeVideoModal.tsx
...ontend/src/common/components/gallery/ChangeVideoModal.tsx
+83
-0
demo/frontend/src/common/components/gallery/DefaultVideoGalleryModalTrigger.tsx
...on/components/gallery/DefaultVideoGalleryModalTrigger.tsx
+32
-0
demo/frontend/src/common/components/gallery/DemoVideoGallery.tsx
...ontend/src/common/components/gallery/DemoVideoGallery.tsx
+209
-0
demo/frontend/src/common/components/gallery/DemoVideoGalleryModal.tsx
...d/src/common/components/gallery/DemoVideoGalleryModal.tsx
+148
-0
demo/frontend/src/common/components/gallery/VideoGalleryUploadPhoto.tsx
...src/common/components/gallery/VideoGalleryUploadPhoto.tsx
+102
-0
demo/frontend/src/common/components/gallery/VideoPhoto.tsx
demo/frontend/src/common/components/gallery/VideoPhoto.tsx
+112
-0
demo/frontend/src/common/components/gallery/__generated__/DemoVideoGalleryModalQuery.graphql.ts
...llery/__generated__/DemoVideoGalleryModalQuery.graphql.ts
+303
-0
demo/frontend/src/common/components/gallery/__generated__/DemoVideoGalleryQuery.graphql.ts
...ts/gallery/__generated__/DemoVideoGalleryQuery.graphql.ts
+148
-0
demo/frontend/src/common/components/gallery/__generated__/useUploadVideoMutation.graphql.ts
...s/gallery/__generated__/useUploadVideoMutation.graphql.ts
+137
-0
demo/frontend/src/common/components/gallery/useUploadVideo.ts
.../frontend/src/common/components/gallery/useUploadVideo.ts
+124
-0
demo/frontend/src/common/components/icons/GitHubIcon.tsx
demo/frontend/src/common/components/icons/GitHubIcon.tsx
+29
-0
demo/frontend/src/common/components/options/DownloadOption.tsx
...frontend/src/common/components/options/DownloadOption.tsx
+34
-0
demo/frontend/src/common/components/options/GalleryOption.tsx
.../frontend/src/common/components/options/GalleryOption.tsx
+46
-0
demo/frontend/src/common/components/options/MoreOptionsToolbar.tsx
...tend/src/common/components/options/MoreOptionsToolbar.tsx
+57
-0
demo/frontend/src/common/components/options/MoreOptionsToolbarBottomActions.tsx
...on/components/options/MoreOptionsToolbarBottomActions.tsx
+48
-0
demo/frontend/src/common/components/options/OptionButton.tsx
demo/frontend/src/common/components/options/OptionButton.tsx
+87
-0
demo/frontend/src/common/components/options/ShareSection.tsx
demo/frontend/src/common/components/options/ShareSection.tsx
+24
-0
demo/frontend/src/common/components/options/ShareUtils.ts
demo/frontend/src/common/components/options/ShareUtils.ts
+41
-0
No files found.
demo/frontend/src/common/components/effects/MobileEffectsToolbar.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
EffectsCarousel
from
'
@/common/components/effects/EffectsCarousel
'
;
import
{
backgroundEffects
}
from
'
@/common/components/effects/EffectsUtils
'
;
import
useVideoEffect
from
'
@/common/components/video/editor/useVideoEffect
'
;
import
{
EffectIndex
,
effectPresets
,
}
from
'
@/common/components/video/effects/Effects
'
;
import
{
ListBoxes
,
MagicWand
,
MagicWandFilled
}
from
'
@carbon/icons-react
'
;
import
{
useCallback
,
useRef
,
useState
}
from
'
react
'
;
import
{
Button
}
from
'
react-daisyui
'
;
import
EffectsToolbarBottomActions
from
'
@/common/components/effects/EffectsToolbarBottomActions
'
;
import
ToolbarProgressChip
from
'
@/common/components/toolbar/ToolbarProgressChip
'
;
import
{
activeBackgroundEffectAtom
,
activeHighlightEffectAtom
,
activeHighlightEffectGroupAtom
,
}
from
'
@/demo/atoms
'
;
import
{
BLUE_PINK_FILL
}
from
'
@/theme/gradientStyle
'
;
import
{
useAtomValue
}
from
'
jotai
'
;
type
Props
=
{
onTabChange
:
(
newIndex
:
number
)
=>
void
;
};
export
default
function
MobileEffectsToolbar
({
onTabChange
}:
Props
)
{
const
preset
=
useRef
(
0
);
const
setEffect
=
useVideoEffect
();
const
[
showEffectsCarousels
,
setShowEffectsCarousels
]
=
useState
<
boolean
>
();
const
activeBackground
=
useAtomValue
(
activeBackgroundEffectAtom
);
const
activeHighlight
=
useAtomValue
(
activeHighlightEffectAtom
);
const
activeHighlightEffectsGroup
=
useAtomValue
(
activeHighlightEffectGroupAtom
,
);
const
handleTogglePreset
=
useCallback
(()
=>
{
preset
.
current
++
;
const
[
background
,
highlight
]
=
effectPresets
[
preset
.
current
%
effectPresets
.
length
];
setEffect
(
background
.
name
,
EffectIndex
.
BACKGROUND
,
{
variant
:
background
.
variant
,
});
setEffect
(
highlight
.
name
,
EffectIndex
.
HIGHLIGHT
,
{
variant
:
highlight
.
variant
,
});
},
[
setEffect
]);
return
(
<
div
className
=
"w-full"
>
{
showEffectsCarousels
?
(
<
div
className
=
"flex gap-2 px-2 py-4 items-center p-6"
>
<
Button
color
=
"ghost"
className
=
"mt-6 !px-2 !text-[#FB73A5]"
startIcon
=
{
<
MagicWand
size
=
{
20
}
/>
}
onClick
=
{
handleTogglePreset
}
/>
<
EffectsCarousel
label
=
"Highlights"
effects
=
{
activeHighlightEffectsGroup
}
activeEffect
=
{
activeHighlight
.
name
}
index
=
{
1
}
/>
<
EffectsCarousel
label
=
"Background"
effects
=
{
backgroundEffects
}
activeEffect
=
{
activeBackground
.
name
}
index
=
{
0
}
/>
</
div
>
)
:
(
<
div
className
=
"flex flex-col gap-6 p-6"
>
<
div
className
=
"text-sm text-white"
>
<
ToolbarProgressChip
/>
Apply visual effects to your selected objects and the background.
</
div
>
<
div
className
=
"grid grid-cols-2 gap-2"
>
<
Button
color
=
"ghost"
endIcon
=
{
<
MagicWandFilled
size
=
{
20
}
/>
}
className
=
{
`font-bold bg-black !rounded-full !bg-gradient-to-br
${
BLUE_PINK_FILL
}
border-none text-white`
}
onClick
=
{
handleTogglePreset
}
>
Surprise Me
</
Button
>
<
Button
color
=
"ghost"
className
=
{
`font-bold bg-black !rounded-full border-none text-white`
}
startIcon
=
{
<
ListBoxes
size
=
{
20
}
/>
}
onClick
=
{
()
=>
setShowEffectsCarousels
(
true
)
}
>
More effects
</
Button
>
</
div
>
</
div
>
)
}
<
EffectsToolbarBottomActions
onTabChange
=
{
onTabChange
}
/>
</
div
>
);
}
demo/frontend/src/common/components/effects/MoreFunEffects.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
moreEffects
}
from
'
@/common/components/effects/EffectsUtils
'
;
import
EffectVariantBadge
from
'
@/common/components/effects/EffectVariantBadge
'
;
import
ToolbarActionIcon
from
'
@/common/components/toolbar/ToolbarActionIcon
'
;
import
ToolbarSection
from
'
@/common/components/toolbar/ToolbarSection
'
;
import
useVideoEffect
from
'
@/common/components/video/editor/useVideoEffect
'
;
import
{
EffectIndex
}
from
'
@/common/components/video/effects/Effects
'
;
import
{
activeHighlightEffectAtom
}
from
'
@/demo/atoms
'
;
import
{
useAtomValue
}
from
'
jotai
'
;
export
default
function
MoreFunEffects
()
{
const
setEffect
=
useVideoEffect
();
const
activeEffect
=
useAtomValue
(
activeHighlightEffectAtom
);
return
(
<
ToolbarSection
title
=
"Selected Objects"
borderBottom
=
{
true
}
>
{
moreEffects
.
map
(
effect
=>
{
return
(
<
ToolbarActionIcon
variant
=
"toggle"
key
=
{
effect
.
title
}
icon
=
{
effect
.
Icon
}
title
=
{
effect
.
title
}
isActive
=
{
activeEffect
.
name
===
effect
.
effectName
}
badge
=
{
activeEffect
.
name
===
effect
.
effectName
&&
(
<
EffectVariantBadge
label
=
{
`
${
activeEffect
.
variant
+
1
}
/
${
activeEffect
.
numVariants
}
`
}
/>
)
}
onClick
=
{
()
=>
{
setEffect
(
effect
.
effectName
,
EffectIndex
.
HIGHLIGHT
);
}
}
/>
);
})
}
</
ToolbarSection
>
);
}
demo/frontend/src/common/components/gallery/ChangeVideoModal.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
type
{
VideoGalleryTriggerProps
}
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
DemoVideoGalleryModal
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
useVideo
from
'
@/common/components/video/editor/useVideo
'
;
import
Logger
from
'
@/common/logger/Logger
'
;
import
{
isStreamingAtom
,
uploadingStateAtom
,
VideoData
}
from
'
@/demo/atoms
'
;
import
{
useAtomValue
,
useSetAtom
}
from
'
jotai
'
;
import
{
ComponentType
,
useCallback
}
from
'
react
'
;
import
{
useNavigate
}
from
'
react-router-dom
'
;
type
Props
=
{
videoGalleryModalTrigger
?:
ComponentType
<
VideoGalleryTriggerProps
>
;
showUploadInGallery
?:
boolean
;
onChangeVideo
?:
()
=>
void
;
};
export
default
function
ChangeVideoModal
({
videoGalleryModalTrigger
:
VideoGalleryModalTriggerComponent
,
showUploadInGallery
=
true
,
onChangeVideo
,
}:
Props
)
{
const
isStreaming
=
useAtomValue
(
isStreamingAtom
);
const
setUploadingState
=
useSetAtom
(
uploadingStateAtom
);
const
video
=
useVideo
();
const
navigate
=
useNavigate
();
const
handlePause
=
useCallback
(()
=>
{
video
?.
pause
();
},
[
video
]);
function
handlePauseOrAbortVideo
()
{
if
(
isStreaming
)
{
video
?.
abortStreamMasks
();
}
else
{
handlePause
();
}
}
function
handleSwitchVideos
(
video
:
VideoData
)
{
// Retain any search parameter
navigate
(
{
pathname
:
location
.
pathname
,
search
:
location
.
search
,
},
{
state
:
{
video
,
},
},
);
onChangeVideo
?.();
}
function
handleUploadVideoError
(
error
:
Error
)
{
setUploadingState
(
'
error
'
);
Logger
.
error
(
error
);
}
return
(
<
DemoVideoGalleryModal
trigger
=
{
VideoGalleryModalTriggerComponent
}
showUploadInGallery
=
{
showUploadInGallery
}
onOpen
=
{
handlePauseOrAbortVideo
}
onSelect
=
{
handleSwitchVideos
}
onUploadVideoError
=
{
handleUploadVideoError
}
/>
);
}
demo/frontend/src/common/components/gallery/DefaultVideoGalleryModalTrigger.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
ResponsiveButton
from
'
@/common/components/button/ResponsiveButton
'
;
import
type
{
VideoGalleryTriggerProps
}
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
{
ImageCopy
}
from
'
@carbon/icons-react
'
;
export
default
function
DefaultVideoGalleryModalTrigger
({
onClick
,
}:
VideoGalleryTriggerProps
)
{
return
(
<
ResponsiveButton
color
=
"ghost"
className
=
"hover:!bg-black"
startIcon
=
{
<
ImageCopy
size
=
{
20
}
/>
}
onClick
=
{
onClick
}
>
Change video
</
ResponsiveButton
>
);
}
demo/frontend/src/common/components/gallery/DemoVideoGallery.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
DemoVideoGalleryQuery
}
from
'
@/common/components/gallery/__generated__/DemoVideoGalleryQuery.graphql
'
;
import
VideoGalleryUploadVideo
from
'
@/common/components/gallery/VideoGalleryUploadPhoto
'
;
import
VideoPhoto
from
'
@/common/components/gallery/VideoPhoto
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
VideoData
}
from
'
@/demo/atoms
'
;
import
{
DEMO_SHORT_NAME
}
from
'
@/demo/DemoConfig
'
;
import
{
fontSize
,
fontWeight
,
spacing
}
from
'
@/theme/tokens.stylex
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
useMemo
}
from
'
react
'
;
import
PhotoAlbum
,
{
Photo
,
RenderPhotoProps
}
from
'
react-photo-album
'
;
import
{
graphql
,
useLazyLoadQuery
}
from
'
react-relay
'
;
import
{
useLocation
,
useNavigate
}
from
'
react-router-dom
'
;
const
styles
=
stylex
.
create
({
container
:
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
marginHorizontal
:
spacing
[
1
],
height
:
'
100%
'
,
lineHeight
:
1.2
,
paddingTop
:
spacing
[
8
],
},
headerContainer
:
{
marginBottom
:
spacing
[
8
],
fontWeight
:
fontWeight
[
'
medium
'
],
fontSize
:
fontSize
[
'
2xl
'
],
'
@media screen and (max-width: 768px)
'
:
{
marginTop
:
spacing
[
0
],
marginBottom
:
spacing
[
8
],
marginHorizontal
:
spacing
[
4
],
fontSize
:
fontSize
[
'
xl
'
],
},
},
albumContainer
:
{
flex
:
'
1 1 0%
'
,
width
:
'
100%
'
,
overflowY
:
'
auto
'
,
},
});
type
Props
=
{
showUploadInGallery
?:
boolean
;
onSelect
?:
(
video
:
VideoPhotoData
)
=>
void
;
onUpload
:
(
video
:
VideoData
)
=>
void
;
onUploadStart
?:
()
=>
void
;
onUploadError
?:
(
error
:
Error
)
=>
void
;
};
type
VideoPhotoData
=
Photo
&
VideoData
&
{
poster
:
string
;
isUploadOption
:
boolean
;
};
export
default
function
DemoVideoGallery
({
showUploadInGallery
=
false
,
onSelect
,
onUpload
,
onUploadStart
,
onUploadError
,
}:
Props
)
{
const
navigate
=
useNavigate
();
const
location
=
useLocation
();
const
{
isMobile
:
isMobileScreenSize
}
=
useScreenSize
();
const
data
=
useLazyLoadQuery
<
DemoVideoGalleryQuery
>
(
graphql
`
query DemoVideoGalleryQuery {
videos {
edges {
node {
id
path
posterPath
url
posterUrl
height
width
posterUrl
}
}
}
}
`
,
{},
);
const
allVideos
:
VideoPhotoData
[]
=
useMemo
(()
=>
{
return
data
.
videos
.
edges
.
map
(
video
=>
{
return
{
src
:
video
.
node
.
url
,
path
:
video
.
node
.
path
,
poster
:
video
.
node
.
posterPath
,
posterPath
:
video
.
node
.
posterPath
,
url
:
video
.
node
.
url
,
posterUrl
:
video
.
node
.
posterUrl
,
width
:
video
.
node
.
width
,
height
:
video
.
node
.
height
,
isUploadOption
:
false
,
}
as
VideoPhotoData
;
});
},
[
data
.
videos
.
edges
]);
const
shareableVideos
:
VideoPhotoData
[]
=
useMemo
(()
=>
{
const
filteredVideos
=
[...
allVideos
];
if
(
showUploadInGallery
)
{
const
uploadOption
=
{
src
:
''
,
width
:
1280
,
height
:
720
,
poster
:
''
,
isUploadOption
:
true
,
}
as
VideoPhotoData
;
filteredVideos
.
unshift
(
uploadOption
);
}
return
filteredVideos
;
},
[
allVideos
,
showUploadInGallery
]);
const
renderPhoto
=
({
photo
:
video
,
imageProps
,
}:
RenderPhotoProps
<
VideoPhotoData
>
)
=>
{
const
{
style
}
=
imageProps
;
const
{
url
,
posterUrl
}
=
video
;
return
video
.
isUploadOption
?
(
<
VideoGalleryUploadVideo
style
=
{
style
}
onUpload
=
{
handleUploadVideo
}
onUploadError
=
{
onUploadError
}
onUploadStart
=
{
onUploadStart
}
/>
)
:
(
<
VideoPhoto
src
=
{
url
}
poster
=
{
posterUrl
}
style
=
{
style
}
onClick
=
{
()
=>
{
navigate
(
location
.
pathname
,
{
state
:
{
video
,
},
});
onSelect
?.(
video
);
}
}
/>
);
};
function
handleUploadVideo
(
video
:
VideoData
)
{
navigate
(
location
.
pathname
,
{
state
:
{
video
,
},
});
onUpload
?.(
video
);
}
const
descriptionStyle
=
'
text-sm md:text-base text-gray-400 leading-snug
'
;
return
(
<
div
{
...
stylex
.
props
(
styles
.
container
)
}
>
<
div
{
...
stylex
.
props
(
styles
.
albumContainer
)
}
>
<
div
className
=
"pt-0 md:px-16 md:pt-8 md:pb-8"
>
<
div
{
...
stylex
.
props
(
styles
.
headerContainer
)
}
>
<
h3
className
=
"mb-2"
>
Select a video to try
{
'
'
}
<
span
className
=
"hidden md:inline"
>
with the
{
DEMO_SHORT_NAME
}
</
span
>
</
h3
>
<
p
className
=
{
descriptionStyle
}
>
You’ll be able to download what you make.
</
p
>
</
div
>
<
PhotoAlbum
<
VideoPhotoData
>
layout="rows"
photos=
{
shareableVideos
}
targetRowHeight=
{
isMobileScreenSize
?
120
:
200
}
rowConstraints=
{
{
singleRowMaxHeight
:
isMobileScreenSize
?
120
:
240
,
maxPhotos
:
3
,
}
}
renderPhoto=
{
renderPhoto
}
spacing=
{
4
}
/>
</
div
>
</
div
>
</
div
>
);
}
demo/frontend/src/common/components/gallery/DemoVideoGalleryModal.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
DefaultVideoGalleryModalTrigger
from
'
@/common/components/gallery/DefaultVideoGalleryModalTrigger
'
;
import
{
frameIndexAtom
,
sessionAtom
,
uploadingStateAtom
,
VideoData
,
}
from
'
@/demo/atoms
'
;
import
{
spacing
}
from
'
@/theme/tokens.stylex
'
;
import
{
Close
}
from
'
@carbon/icons-react
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
useSetAtom
}
from
'
jotai
'
;
import
{
ComponentType
,
useCallback
,
useRef
}
from
'
react
'
;
import
{
Modal
}
from
'
react-daisyui
'
;
import
DemoVideoGallery
from
'
./DemoVideoGallery
'
;
const
styles
=
stylex
.
create
({
container
:
{
position
:
'
relative
'
,
minWidth
:
'
85vw
'
,
minHeight
:
'
85vh
'
,
overflow
:
'
hidden
'
,
color
:
'
#fff
'
,
boxShadow
:
'
0 0 100px 50px #000
'
,
borderRadius
:
16
,
border
:
'
2px solid transparent
'
,
background
:
'
linear-gradient(#1A1C1F, #1A1C1F) padding-box, linear-gradient(to right bottom, #FB73A5,#595FEF,#94EAE2,#FCCB6B) border-box
'
,
},
closeButton
:
{
position
:
'
absolute
'
,
top
:
0
,
right
:
0
,
padding
:
spacing
[
3
],
zIndex
:
10
,
cursor
:
'
pointer
'
,
'
:hover
'
:
{
opacity
:
0.7
,
},
},
galleryContainer
:
{
position
:
'
absolute
'
,
top
:
spacing
[
4
],
left
:
0
,
right
:
0
,
bottom
:
0
,
overflowY
:
'
auto
'
,
},
});
export
type
VideoGalleryTriggerProps
=
{
onClick
:
()
=>
void
;
};
type
Props
=
{
trigger
?:
ComponentType
<
VideoGalleryTriggerProps
>
;
showUploadInGallery
?:
boolean
;
onOpen
?:
()
=>
void
;
onSelect
?:
(
video
:
VideoData
,
isUpload
?:
boolean
)
=>
void
;
onUploadVideoError
?:
(
error
:
Error
)
=>
void
;
};
export
default
function
DemoVideoGalleryModal
({
trigger
:
VideoGalleryModalTrigger
=
DefaultVideoGalleryModalTrigger
,
showUploadInGallery
=
false
,
onOpen
,
onSelect
,
onUploadVideoError
,
}:
Props
)
{
const
modalRef
=
useRef
<
HTMLDialogElement
|
null
>
(
null
);
const
setFrameIndex
=
useSetAtom
(
frameIndexAtom
);
const
setUploadingState
=
useSetAtom
(
uploadingStateAtom
);
const
setSession
=
useSetAtom
(
sessionAtom
);
function
openModal
()
{
const
modal
=
modalRef
.
current
;
if
(
modal
!=
null
)
{
modal
.
style
.
display
=
'
grid
'
;
modal
.
showModal
();
}
}
function
closeModal
()
{
const
modal
=
modalRef
.
current
;
if
(
modal
!=
null
)
{
modal
.
close
();
modal
.
style
.
display
=
'
none
'
;
}
}
const
handleSelect
=
useCallback
(
async
(
video
:
VideoData
,
isUpload
?:
boolean
)
=>
{
closeModal
();
setFrameIndex
(
0
);
onSelect
?.(
video
,
isUpload
);
setUploadingState
(
'
default
'
);
setSession
(
null
);
},
[
setFrameIndex
,
onSelect
,
setUploadingState
,
setSession
],
);
function
handleUploadVideoStart
()
{
setUploadingState
(
'
uploading
'
);
closeModal
();
}
function
handleOpenVideoGalleryModal
()
{
onOpen
?.();
openModal
();
}
return
(
<>
<
VideoGalleryModalTrigger
onClick
=
{
handleOpenVideoGalleryModal
}
/>
<
Modal
ref
=
{
modalRef
}
{
...
stylex
.
props
(
styles
.
container
)
}
>
<
div
onClick
=
{
closeModal
}
{
...
stylex
.
props
(
styles
.
closeButton
)
}
>
<
Close
size
=
{
28
}
/>
</
div
>
<
Modal
.
Body
>
<
div
{
...
stylex
.
props
(
styles
.
galleryContainer
)
}
>
<
DemoVideoGallery
showUploadInGallery
=
{
showUploadInGallery
}
onSelect
=
{
video
=>
handleSelect
(
video
)
}
onUpload
=
{
video
=>
handleSelect
(
video
,
true
)
}
onUploadStart
=
{
handleUploadVideoStart
}
onUploadError
=
{
onUploadVideoError
}
/>
</
div
>
</
Modal
.
Body
>
</
Modal
>
</>
);
}
demo/frontend/src/common/components/gallery/VideoGalleryUploadPhoto.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
useUploadVideo
from
'
@/common/components/gallery/useUploadVideo
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
VideoData
}
from
'
@/demo/atoms
'
;
import
{
MAX_UPLOAD_FILE_SIZE
}
from
'
@/demo/DemoConfig
'
;
import
{
BLUE_PINK_FILL_BR
}
from
'
@/theme/gradientStyle
'
;
import
{
RetryFailed
,
Upload
}
from
'
@carbon/icons-react
'
;
import
{
CSSProperties
,
ReactNode
}
from
'
react
'
;
import
{
Loading
}
from
'
react-daisyui
'
;
type
Props
=
{
style
:
CSSProperties
;
onUpload
:
(
video
:
VideoData
)
=>
void
;
onUploadStart
?:
()
=>
void
;
onUploadError
?:
(
error
:
Error
)
=>
void
;
};
export
default
function
VideoGalleryUploadVideo
({
style
,
onUpload
,
onUploadStart
,
onUploadError
,
}:
Props
)
{
const
{
getRootProps
,
getInputProps
,
isUploading
,
error
}
=
useUploadVideo
({
onUpload
,
onUploadStart
,
onUploadError
,
});
const
{
isMobile
}
=
useScreenSize
();
return
(
<
div
className
=
{
`cursor-pointer
${
BLUE_PINK_FILL_BR
}
`
}
style
=
{
style
}
>
<
span
{
...
getRootProps
()
}
>
<
input
{
...
getInputProps
()
}
/>
<
div
className
=
"relative w-full h-full"
>
<
div
className
=
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
>
{
isUploading
&&
(
<
IconWrapper
icon
=
{
<
Loading
size
=
{
isMobile
?
'
md
'
:
'
lg
'
}
className
=
"text-white"
/>
}
title
=
"Uploading ..."
/>
)
}
{
error
!==
null
&&
(
<
IconWrapper
icon
=
{
<
RetryFailed
color
=
"white"
size
=
{
isMobile
?
24
:
32
}
/>
}
title
=
{
error
}
/>
)
}
{
!
isUploading
&&
error
===
null
&&
(
<
IconWrapper
icon
=
{
<
Upload
color
=
"white"
size
=
{
isMobile
?
24
:
32
}
/>
}
title
=
{
<>
Upload
{
'
'
}
<
div
className
=
"text-xs opacity-70"
>
Max
{
MAX_UPLOAD_FILE_SIZE
}
</
div
>
</>
}
/>
)
}
</
div
>
</
div
>
</
span
>
</
div
>
);
}
type
IconWrapperProps
=
{
icon
:
ReactNode
;
title
:
ReactNode
|
string
;
};
function
IconWrapper
({
icon
,
title
}:
IconWrapperProps
)
{
return
(
<>
<
div
className
=
"flex justify-center"
>
{
icon
}
</
div
>
<
div
className
=
"mt-1 text-sm md:text-lg text-white font-medium text-center leading-tight"
>
{
title
}
</
div
>
</>
);
}
demo/frontend/src/common/components/gallery/VideoPhoto.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
Logger
from
'
@/common/logger/Logger
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
CSSProperties
,
MouseEventHandler
,
useCallback
,
useEffect
,
useRef
,
}
from
'
react
'
;
const
styles
=
stylex
.
create
({
background
:
{
backgroundRepeat
:
'
no-repeat
'
,
backgroundSize
:
'
cover
'
,
backgroundPosition
:
'
center
'
,
cursor
:
'
pointer
'
,
},
video
:
{
width
:
'
100%
'
,
height
:
'
100%
'
,
},
});
type
Props
=
{
onClick
:
MouseEventHandler
<
HTMLVideoElement
>
|
undefined
;
src
:
string
;
poster
:
string
;
style
:
CSSProperties
;
};
export
default
function
VideoPhoto
({
src
,
poster
,
style
,
onClick
}:
Props
)
{
const
videoRef
=
useRef
<
HTMLVideoElement
|
null
>
(
null
);
const
playPromiseRef
=
useRef
<
Promise
<
void
>
|
null
>
(
null
);
const
play
=
useCallback
(()
=>
{
const
video
=
videoRef
.
current
;
// Only play video if it is not already playing
if
(
video
!=
null
&&
video
.
paused
)
{
// This quirky way of handling video play/pause in the browser is needed
// due to the async nature of the video play API:
// https://developer.chrome.com/blog/play-request-was-interrupted/
const
playPromise
=
video
.
play
();
playPromise
.
catch
(
error
=>
{
Logger
.
error
(
'
Failed to play video
'
,
error
);
});
playPromiseRef
.
current
=
playPromise
;
}
},
[]);
const
pause
=
useCallback
(()
=>
{
// Only pause video if it is playing
const
playPromise
=
playPromiseRef
.
current
;
if
(
playPromise
!=
null
)
{
playPromise
.
then
(()
=>
{
videoRef
.
current
?.
pause
();
})
.
catch
(
error
=>
{
Logger
.
error
(
'
Failed to pause video
'
,
error
);
})
.
finally
(()
=>
{
playPromiseRef
.
current
=
null
;
});
}
},
[]);
useEffect
(()
=>
{
return
()
=>
{
pause
();
};
},
[
pause
]);
return
(
<
div
style
=
{
{
...
style
,
backgroundImage
:
`url(
${
poster
}
)`
,
}
}
{
...
stylex
.
props
(
styles
.
background
)
}
>
<
video
ref
=
{
videoRef
}
{
...
stylex
.
props
(
styles
.
video
)
}
preload
=
"none"
playsInline
loop
muted
title
=
"Gallery Video"
poster
=
{
poster
}
onMouseEnter
=
{
play
}
onMouseLeave
=
{
pause
}
onClick
=
{
onClick
}
>
<
source
src
=
{
src
}
type
=
"video/mp4"
/>
Sorry, your browser does not support embedded videos.
</
video
>
</
div
>
);
}
demo/frontend/src/common/components/gallery/__generated__/DemoVideoGalleryModalQuery.graphql.ts
0 → 100644
View file @
3af09475
/**
* @generated SignedSource<<db7e183e1996cf656749b4e33c2424e6>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import
{
ConcreteRequest
,
Query
}
from
'
relay-runtime
'
;
import
{
FragmentRefs
}
from
"
relay-runtime
"
;
export
type
DemoVideoGalleryModalQuery$variables
=
Record
<
PropertyKey
,
never
>
;
export
type
DemoVideoGalleryModalQuery$data
=
{
readonly
"
$fragmentSpreads
"
:
FragmentRefs
<
"
DatasetsDropdown_datasets
"
|
"
VideoGallery_videos
"
>
;
};
export
type
DemoVideoGalleryModalQuery
=
{
response
:
DemoVideoGalleryModalQuery$data
;
variables
:
DemoVideoGalleryModalQuery$variables
;
};
const
node
:
ConcreteRequest
=
(
function
(){
var
v0
=
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
name
"
,
"
storageKey
"
:
null
}
],
v1
=
[
{
"
kind
"
:
"
Literal
"
,
"
name
"
:
"
after
"
,
"
value
"
:
""
},
{
"
kind
"
:
"
Literal
"
,
"
name
"
:
"
first
"
,
"
value
"
:
20
}
],
v2
=
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
__typename
"
,
"
storageKey
"
:
null
};
return
{
"
fragment
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Fragment
"
,
"
metadata
"
:
null
,
"
name
"
:
"
DemoVideoGalleryModalQuery
"
,
"
selections
"
:
[
{
"
args
"
:
null
,
"
kind
"
:
"
FragmentSpread
"
,
"
name
"
:
"
DatasetsDropdown_datasets
"
},
{
"
args
"
:
null
,
"
kind
"
:
"
FragmentSpread
"
,
"
name
"
:
"
VideoGallery_videos
"
}
],
"
type
"
:
"
Query
"
,
"
abstractKey
"
:
null
},
"
kind
"
:
"
Request
"
,
"
operation
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Operation
"
,
"
name
"
:
"
DemoVideoGalleryModalQuery
"
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
DatasetConnection
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
datasets
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
DatasetEdge
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
edges
"
,
"
plural
"
:
true
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
Dataset
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
node
"
,
"
plural
"
:
false
,
"
selections
"
:
(
v0
/*: any*/
),
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
(
v1
/*: any*/
),
"
concreteType
"
:
"
VideoConnection
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
videos
"
,
"
plural
"
:
false
,
"
selections
"
:
[
(
v2
/*: any*/
),
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
PageInfo
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
pageInfo
"
,
"
plural
"
:
false
,
"
selections
"
:
[
(
v2
/*: any*/
),
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
hasPreviousPage
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
hasNextPage
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
startCursor
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
endCursor
"
,
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
VideoEdge
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
edges
"
,
"
plural
"
:
true
,
"
selections
"
:
[
(
v2
/*: any*/
),
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
Video
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
node
"
,
"
plural
"
:
false
,
"
selections
"
:
[
(
v2
/*: any*/
),
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
id
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
path
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterPath
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
url
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterUrl
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
width
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
height
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
Dataset
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
dataset
"
,
"
plural
"
:
false
,
"
selections
"
:
(
v0
/*: any*/
),
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
VideoPermissions
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
permissions
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
canShare
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
canDownload
"
,
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
cursor
"
,
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
],
"
storageKey
"
:
"
videos(after:
\"\"
,first:20)
"
},
{
"
alias
"
:
null
,
"
args
"
:
(
v1
/*: any*/
),
"
filters
"
:
[
"
datasetName
"
],
"
handle
"
:
"
connection
"
,
"
key
"
:
"
VideoGallery_videos
"
,
"
kind
"
:
"
LinkedHandle
"
,
"
name
"
:
"
videos
"
}
]
},
"
params
"
:
{
"
cacheID
"
:
"
e0bccf553377682e6bc283c2ce53bee5
"
,
"
id
"
:
null
,
"
metadata
"
:
{},
"
name
"
:
"
DemoVideoGalleryModalQuery
"
,
"
operationKind
"
:
"
query
"
,
"
text
"
:
"
query DemoVideoGalleryModalQuery {
\n
...DatasetsDropdown_datasets
\n
...VideoGallery_videos
\n
}
\n\n
fragment DatasetsDropdown_datasets on Query {
\n
datasets {
\n
edges {
\n
node {
\n
name
\n
}
\n
}
\n
}
\n
}
\n\n
fragment VideoGallery_videos on Query {
\n
videos(first: 20, after:
\"\"
) {
\n
__typename
\n
pageInfo {
\n
__typename
\n
hasPreviousPage
\n
hasNextPage
\n
startCursor
\n
endCursor
\n
}
\n
edges {
\n
__typename
\n
node {
\n
__typename
\n
id
\n
path
\n
posterPath
\n
url
\n
posterUrl
\n
width
\n
height
\n
dataset {
\n
name
\n
}
\n
permissions {
\n
canShare
\n
canDownload
\n
}
\n
}
\n
cursor
\n
}
\n
}
\n
}
\n
"
}
};
})();
(
node
as
any
).
hash
=
"
d09e34e2b9f2e25c2d564106de5f9c89
"
;
export
default
node
;
demo/frontend/src/common/components/gallery/__generated__/DemoVideoGalleryQuery.graphql.ts
0 → 100644
View file @
3af09475
/**
* @generated SignedSource<<20d31a82b5f3b251b0e42b4f0e3522b8>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import
{
ConcreteRequest
,
Query
}
from
'
relay-runtime
'
;
export
type
DemoVideoGalleryQuery$variables
=
Record
<
PropertyKey
,
never
>
;
export
type
DemoVideoGalleryQuery$data
=
{
readonly
videos
:
{
readonly
edges
:
ReadonlyArray
<
{
readonly
node
:
{
readonly
height
:
number
;
readonly
id
:
any
;
readonly
path
:
string
;
readonly
posterPath
:
string
|
null
|
undefined
;
readonly
posterUrl
:
string
;
readonly
url
:
string
;
readonly
width
:
number
;
};
}
>
;
};
};
export
type
DemoVideoGalleryQuery
=
{
response
:
DemoVideoGalleryQuery$data
;
variables
:
DemoVideoGalleryQuery$variables
;
};
const
node
:
ConcreteRequest
=
(
function
(){
var
v0
=
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
VideoConnection
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
videos
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
VideoEdge
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
edges
"
,
"
plural
"
:
true
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
Video
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
node
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
id
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
path
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterPath
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
url
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterUrl
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
height
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
width
"
,
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
];
return
{
"
fragment
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Fragment
"
,
"
metadata
"
:
null
,
"
name
"
:
"
DemoVideoGalleryQuery
"
,
"
selections
"
:
(
v0
/*: any*/
),
"
type
"
:
"
Query
"
,
"
abstractKey
"
:
null
},
"
kind
"
:
"
Request
"
,
"
operation
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Operation
"
,
"
name
"
:
"
DemoVideoGalleryQuery
"
,
"
selections
"
:
(
v0
/*: any*/
)
},
"
params
"
:
{
"
cacheID
"
:
"
4dae74153a5528f2631b59dfb0adb021
"
,
"
id
"
:
null
,
"
metadata
"
:
{},
"
name
"
:
"
DemoVideoGalleryQuery
"
,
"
operationKind
"
:
"
query
"
,
"
text
"
:
"
query DemoVideoGalleryQuery {
\n
videos {
\n
edges {
\n
node {
\n
id
\n
path
\n
posterPath
\n
url
\n
posterUrl
\n
height
\n
width
\n
}
\n
}
\n
}
\n
}
\n
"
}
};
})();
(
node
as
any
).
hash
=
"
d22ac5e58f6e4eb696651be49b410e4e
"
;
export
default
node
;
demo/frontend/src/common/components/gallery/__generated__/useUploadVideoMutation.graphql.ts
0 → 100644
View file @
3af09475
/**
* @generated SignedSource<<76014dced98d6c8989e7322712e38963>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import
{
ConcreteRequest
,
Mutation
}
from
'
relay-runtime
'
;
export
type
useUploadVideoMutation$variables
=
{
file
:
any
;
};
export
type
useUploadVideoMutation$data
=
{
readonly
uploadVideo
:
{
readonly
height
:
number
;
readonly
id
:
any
;
readonly
path
:
string
;
readonly
posterPath
:
string
|
null
|
undefined
;
readonly
posterUrl
:
string
;
readonly
url
:
string
;
readonly
width
:
number
;
};
};
export
type
useUploadVideoMutation
=
{
response
:
useUploadVideoMutation$data
;
variables
:
useUploadVideoMutation$variables
;
};
const
node
:
ConcreteRequest
=
(
function
(){
var
v0
=
[
{
"
defaultValue
"
:
null
,
"
kind
"
:
"
LocalArgument
"
,
"
name
"
:
"
file
"
}
],
v1
=
[
{
"
alias
"
:
null
,
"
args
"
:
[
{
"
kind
"
:
"
Variable
"
,
"
name
"
:
"
file
"
,
"
variableName
"
:
"
file
"
}
],
"
concreteType
"
:
"
Video
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
uploadVideo
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
id
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
height
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
width
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
url
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
path
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterPath
"
,
"
storageKey
"
:
null
},
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
kind
"
:
"
ScalarField
"
,
"
name
"
:
"
posterUrl
"
,
"
storageKey
"
:
null
}
],
"
storageKey
"
:
null
}
];
return
{
"
fragment
"
:
{
"
argumentDefinitions
"
:
(
v0
/*: any*/
),
"
kind
"
:
"
Fragment
"
,
"
metadata
"
:
null
,
"
name
"
:
"
useUploadVideoMutation
"
,
"
selections
"
:
(
v1
/*: any*/
),
"
type
"
:
"
Mutation
"
,
"
abstractKey
"
:
null
},
"
kind
"
:
"
Request
"
,
"
operation
"
:
{
"
argumentDefinitions
"
:
(
v0
/*: any*/
),
"
kind
"
:
"
Operation
"
,
"
name
"
:
"
useUploadVideoMutation
"
,
"
selections
"
:
(
v1
/*: any*/
)
},
"
params
"
:
{
"
cacheID
"
:
"
dcbaf1bf411627fdb9dfbb827592cfc0
"
,
"
id
"
:
null
,
"
metadata
"
:
{},
"
name
"
:
"
useUploadVideoMutation
"
,
"
operationKind
"
:
"
mutation
"
,
"
text
"
:
"
mutation useUploadVideoMutation(
\n
$file: Upload!
\n
) {
\n
uploadVideo(file: $file) {
\n
id
\n
height
\n
width
\n
url
\n
path
\n
posterPath
\n
posterUrl
\n
}
\n
}
\n
"
}
};
})();
(
node
as
any
).
hash
=
"
710e462504d76597af8695b7fc70b4cf
"
;
export
default
node
;
demo/frontend/src/common/components/gallery/useUploadVideo.ts
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
useUploadVideoMutation
}
from
'
@/common/components/gallery/__generated__/useUploadVideoMutation.graphql
'
;
import
Logger
from
'
@/common/logger/Logger
'
;
import
{
VideoData
}
from
'
@/demo/atoms
'
;
import
{
useState
}
from
'
react
'
;
import
{
FileRejection
,
FileWithPath
,
useDropzone
}
from
'
react-dropzone
'
;
import
{
graphql
,
useMutation
}
from
'
react-relay
'
;
const
ACCEPT_VIDEOS
=
{
'
video/mp4
'
:
[
'
.mp4
'
],
'
video/quicktime
'
:
[
'
.mov
'
],
};
// 70 MB default max video upload size
const
MAX_FILE_SIZE_IN_MB
=
70
;
const
MAX_VIDEO_UPLOAD_SIZE
=
MAX_FILE_SIZE_IN_MB
*
1024
**
2
;
type
Props
=
{
onUpload
:
(
video
:
VideoData
)
=>
void
;
onUploadStart
?:
()
=>
void
;
onUploadError
?:
(
error
:
Error
)
=>
void
;
};
export
default
function
useUploadVideo
({
onUpload
,
onUploadStart
,
onUploadError
,
}:
Props
)
{
const
[
error
,
setError
]
=
useState
<
string
|
null
>
(
null
);
const
[
commit
,
isMutationInFlight
]
=
useMutation
<
useUploadVideoMutation
>
(
graphql
`
mutation useUploadVideoMutation($file: Upload!) {
uploadVideo(file: $file) {
id
height
width
url
path
posterPath
posterUrl
}
}
`
,
);
const
{
getRootProps
,
getInputProps
}
=
useDropzone
({
accept
:
ACCEPT_VIDEOS
,
multiple
:
false
,
maxFiles
:
1
,
onDrop
:
(
acceptedFiles
:
FileWithPath
[],
fileRejections
:
FileRejection
[],
)
=>
{
setError
(
null
);
// Check if any of the files (only 1 file allowed) is rejected. The
// rejected file has an error (e.g., 'file-too-large'). Rendering an
// appropriate message.
if
(
fileRejections
.
length
>
0
&&
fileRejections
[
0
].
errors
.
length
>
0
)
{
const
code
=
fileRejections
[
0
].
errors
[
0
].
code
;
if
(
code
===
'
file-too-large
'
)
{
setError
(
`File too large. Try a video under
${
MAX_FILE_SIZE_IN_MB
}
MB`
,
);
return
;
}
}
if
(
acceptedFiles
.
length
===
0
)
{
setError
(
'
File not accepted. Please try again.
'
);
return
;
}
if
(
acceptedFiles
.
length
>
1
)
{
setError
(
'
Too many files. Please try again with 1 file.
'
);
return
;
}
onUploadStart
?.();
const
file
=
acceptedFiles
[
0
];
commit
({
variables
:
{
file
,
},
uploadables
:
{
file
,
},
onCompleted
:
response
=>
onUpload
(
response
.
uploadVideo
),
onError
:
error
=>
{
Logger
.
error
(
error
);
onUploadError
?.(
error
);
setError
(
'
Upload failed.
'
);
},
});
},
onError
:
error
=>
{
Logger
.
error
(
error
);
setError
(
'
File not supported.
'
);
},
maxSize
:
MAX_VIDEO_UPLOAD_SIZE
,
});
return
{
getRootProps
,
getInputProps
,
isUploading
:
isMutationInFlight
,
error
,
setError
,
};
}
demo/frontend/src/common/components/icons/GitHubIcon.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
type
Props
=
{
className
?:
string
;
};
export
function
GitHubIcon
({
className
}:
Props
)
{
return
(
<
svg
viewBox
=
"0 0 24 24"
aria
-
hidden
=
"true"
className
=
{
className
}
>
<
path
fillRule
=
"evenodd"
clipRule
=
"evenodd"
d
=
"M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.607 9.607 0 0 1 12 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48 3.97-1.32 6.833-5.054 6.833-9.458C22 6.463 17.522 2 12 2Z"
></
path
>
</
svg
>
);
}
demo/frontend/src/common/components/options/DownloadOption.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
Package
}
from
'
@carbon/icons-react
'
;
import
OptionButton
from
'
./OptionButton
'
;
import
useDownloadVideo
from
'
./useDownloadVideo
'
;
export
default
function
DownloadOption
()
{
const
{
download
,
state
}
=
useDownloadVideo
();
return
(
<
OptionButton
title
=
"Download"
Icon
=
{
Package
}
loadingProps
=
{
{
loading
:
state
===
'
started
'
||
state
===
'
encoding
'
,
label
:
'
Downloading...
'
,
}
}
onClick
=
{
download
}
/>
);
}
demo/frontend/src/common/components/options/GalleryOption.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
ChangeVideoModal
from
'
@/common/components/gallery/ChangeVideoModal
'
;
import
type
{
VideoGalleryTriggerProps
}
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
ImageCopy
}
from
'
@carbon/icons-react
'
;
import
OptionButton
from
'
./OptionButton
'
;
type
Props
=
{
onChangeVideo
:
()
=>
void
;
};
export
default
function
GalleryOption
({
onChangeVideo
}:
Props
)
{
return
(
<
ChangeVideoModal
videoGalleryModalTrigger
=
{
GalleryTrigger
}
showUploadInGallery
=
{
false
}
onChangeVideo
=
{
onChangeVideo
}
/>
);
}
function
GalleryTrigger
({
onClick
}:
VideoGalleryTriggerProps
)
{
const
{
isMobile
}
=
useScreenSize
();
return
(
<
OptionButton
variant
=
"flat"
title
=
{
isMobile
?
'
Gallery
'
:
'
Browse gallery
'
}
Icon
=
{
ImageCopy
}
onClick
=
{
onClick
}
/>
);
}
demo/frontend/src/common/components/options/MoreOptionsToolbar.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
MoreOptionsToolbarBottomActions
from
'
@/common/components/options/MoreOptionsToolbarBottomActions
'
;
import
ShareSection
from
'
@/common/components/options/ShareSection
'
;
import
TryAnotherVideoSection
from
'
@/common/components/options/TryAnotherVideoSection
'
;
import
useMessagesSnackbar
from
'
@/common/components/snackbar/useDemoMessagesSnackbar
'
;
import
ToolbarHeaderWrapper
from
'
@/common/components/toolbar/ToolbarHeaderWrapper
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
useEffect
,
useRef
}
from
'
react
'
;
type
Props
=
{
onTabChange
:
(
newIndex
:
number
)
=>
void
;
};
export
default
function
MoreOptionsToolbar
({
onTabChange
}:
Props
)
{
const
{
isMobile
}
=
useScreenSize
();
const
{
clearMessage
}
=
useMessagesSnackbar
();
const
didClearMessageSnackbar
=
useRef
(
false
);
useEffect
(()
=>
{
if
(
!
didClearMessageSnackbar
.
current
)
{
didClearMessageSnackbar
.
current
=
true
;
clearMessage
();
}
},
[
clearMessage
]);
return
(
<
div
className
=
"flex flex-col h-full"
>
<
div
className
=
"grow"
>
<
ToolbarHeaderWrapper
title
=
"Nice work! What's next?"
className
=
"pb-0 !border-b-0 !text-white"
showProgressChip
=
{
false
}
/>
<
ShareSection
/>
{
!
isMobile
&&
<
div
className
=
"h-[1px] bg-black mt-4 mb-8"
></
div
>
}
<
TryAnotherVideoSection
onTabChange
=
{
onTabChange
}
/>
</
div
>
{
!
isMobile
&&
(
<
MoreOptionsToolbarBottomActions
onTabChange
=
{
onTabChange
}
/>
)
}
</
div
>
);
}
demo/frontend/src/common/components/options/MoreOptionsToolbarBottomActions.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
RestartSessionButton
from
'
@/common/components/session/RestartSessionButton
'
;
import
{
EFFECT_TOOLBAR_INDEX
,
OBJECT_TOOLBAR_INDEX
,
}
from
'
@/common/components/toolbar/ToolbarConfig
'
;
import
{
ChevronLeft
}
from
'
@carbon/icons-react
'
;
import
{
Button
}
from
'
react-daisyui
'
;
import
ToolbarBottomActionsWrapper
from
'
../toolbar/ToolbarBottomActionsWrapper
'
;
type
Props
=
{
onTabChange
:
(
newIndex
:
number
)
=>
void
;
};
export
default
function
MoreOptionsToolbarBottomActions
({
onTabChange
}:
Props
)
{
function
handleReturnToEffectsTab
()
{
onTabChange
(
EFFECT_TOOLBAR_INDEX
);
}
return
(
<
ToolbarBottomActionsWrapper
>
<
Button
color
=
"ghost"
onClick
=
{
handleReturnToEffectsTab
}
className
=
"!px-4 !rounded-full font-medium text-white hover:bg-black"
startIcon
=
{
<
ChevronLeft
/>
}
>
Edit effects
</
Button
>
<
RestartSessionButton
onRestartSession
=
{
()
=>
onTabChange
(
OBJECT_TOOLBAR_INDEX
)
}
/>
</
ToolbarBottomActionsWrapper
>
);
}
demo/frontend/src/common/components/options/OptionButton.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
GradientBorder
from
'
@/common/components/button/GradientBorder
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
BLUE_PINK_FILL_BR
}
from
'
@/theme/gradientStyle
'
;
import
type
{
CarbonIconType
}
from
'
@carbon/icons-react
'
;
import
{
Loading
}
from
'
react-daisyui
'
;
type
Props
=
{
variant
?:
'
default
'
|
'
flat
'
|
'
gradient
'
;
title
:
string
|
React
.
ReactNode
;
Icon
:
CarbonIconType
;
isActive
?:
boolean
;
isDisabled
?:
boolean
;
loadingProps
?:
{
loading
:
boolean
;
label
?:
string
;
};
onClick
:
()
=>
void
;
};
export
default
function
OptionButton
({
variant
=
'
default
'
,
title
,
Icon
,
isActive
=
false
,
isDisabled
=
false
,
loadingProps
,
onClick
,
}:
Props
)
{
const
{
isMobile
}
=
useScreenSize
();
const
isLoading
=
loadingProps
?.
loading
===
true
;
function
handleClick
()
{
if
(
isDisabled
)
{
return
;
}
onClick
();
}
const
ButtonBase
=
(
<
div
onClick
=
{
handleClick
}
className
=
{
`relative rounded-lg h-full flex items-center justify-center
${
variant
===
'
default
'
?
'
bg-graydark-700
'
:
''
}
${
!
isDisabled
&&
'
cursor-pointer
'
}
${
isDisabled
?
'
text-gray-300
'
:
''
}
${
isActive
&&
BLUE_PINK_FILL_BR
}
`
}
>
<
div
className
=
"flex gap-2 items-center py-4 md:py-6"
>
{
isLoading
?
(
<
Loading
size
=
"md"
className
=
"mx-auto mt-1"
/>
)
:
(
<
Icon
size
=
{
isMobile
?
24
:
28
}
className
=
{
`mx-auto
${
isDisabled
?
'
text-gray-300
'
:
'
text-white
'
}
`
}
/>
)
}
<
div
className
=
"text-base font-medium text-white"
>
{
isLoading
&&
loadingProps
?.
label
!=
null
?
loadingProps
.
label
:
title
}
</
div
>
</
div
>
</
div
>
);
return
variant
===
'
gradient
'
?
(
<
GradientBorder
rounded
=
{
false
}
className
=
{
'
rounded-lg md:rounded-full
'
}
>
{
ButtonBase
}
</
GradientBorder
>
)
:
(
ButtonBase
);
}
demo/frontend/src/common/components/options/ShareSection.tsx
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
DownloadOption
from
'
./DownloadOption
'
;
export
default
function
ShareSection
()
{
return
(
<
div
className
=
"p-5 md:p-8"
>
<
DownloadOption
/>
</
div
>
);
}
demo/frontend/src/common/components/options/ShareUtils.ts
0 → 100644
View file @
3af09475
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export
async
function
handleSaveVideo
(
videoPath
:
string
,
fileName
?:
string
,
):
Promise
<
void
>
{
const
blob
=
await
fetch
(
videoPath
).
then
(
res
=>
res
.
blob
());
return
new
Promise
(
resolve
=>
{
const
reader
=
new
FileReader
();
reader
.
readAsDataURL
(
blob
);
reader
.
addEventListener
(
'
load
'
,
()
=>
{
const
elem
=
document
.
createElement
(
'
a
'
);
elem
.
download
=
fileName
??
getFileName
();
if
(
typeof
reader
.
result
===
'
string
'
)
{
elem
.
href
=
reader
.
result
;
}
elem
.
click
();
resolve
();
});
});
}
export
function
getFileName
()
{
const
date
=
new
Date
();
const
timestamp
=
date
.
getTime
();
return
`sam2_masked_video_
${
timestamp
}
.mp4`
;
}
Prev
1
2
3
4
5
6
7
8
9
10
…
30
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment