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
Bw-bestperf
SAM2
Commits
17d316f3
Commit
17d316f3
authored
Feb 04, 2026
by
suily
Browse files
Initial commit
parents
Pipeline
#3368
failed with stages
in 0 seconds
Changes
959
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1483 additions
and
0 deletions
+1483
-0
demo/frontend/src/graphql/RelayEnvironment.ts
demo/frontend/src/graphql/RelayEnvironment.ts
+53
-0
demo/frontend/src/graphql/RelayEnvironmentProvider.tsx
demo/frontend/src/graphql/RelayEnvironmentProvider.tsx
+65
-0
demo/frontend/src/graphql/errors/CreateFilmstripError.ts
demo/frontend/src/graphql/errors/CreateFilmstripError.ts
+21
-0
demo/frontend/src/graphql/errors/DrawFrameError.ts
demo/frontend/src/graphql/errors/DrawFrameError.ts
+21
-0
demo/frontend/src/graphql/errors/WebGLContextError.ts
demo/frontend/src/graphql/errors/WebGLContextError.ts
+21
-0
demo/frontend/src/graphql/fetchGraphQL.ts
demo/frontend/src/graphql/fetchGraphQL.ts
+104
-0
demo/frontend/src/jscocotools/mask.ts
demo/frontend/src/jscocotools/mask.ts
+301
-0
demo/frontend/src/layouts/DemoPageLayout.tsx
demo/frontend/src/layouts/DemoPageLayout.tsx
+46
-0
demo/frontend/src/layouts/RootLayout.tsx
demo/frontend/src/layouts/RootLayout.tsx
+91
-0
demo/frontend/src/main.tsx
demo/frontend/src/main.tsx
+24
-0
demo/frontend/src/routes/DemoPage.tsx
demo/frontend/src/routes/DemoPage.tsx
+65
-0
demo/frontend/src/routes/DemoPageWrapper.tsx
demo/frontend/src/routes/DemoPageWrapper.tsx
+87
-0
demo/frontend/src/routes/PageNotFoundPage.tsx
demo/frontend/src/routes/PageNotFoundPage.tsx
+29
-0
demo/frontend/src/routes/__generated__/DemoPageQuery.graphql.ts
...rontend/src/routes/__generated__/DemoPageQuery.graphql.ts
+114
-0
demo/frontend/src/settings/ApprovableInput.tsx
demo/frontend/src/settings/ApprovableInput.tsx
+119
-0
demo/frontend/src/settings/SAM2Settings.tsx
demo/frontend/src/settings/SAM2Settings.tsx
+39
-0
demo/frontend/src/settings/SettingsContextProvider.tsx
demo/frontend/src/settings/SettingsContextProvider.tsx
+97
-0
demo/frontend/src/settings/SettingsModal.tsx
demo/frontend/src/settings/SettingsModal.tsx
+95
-0
demo/frontend/src/settings/SettingsReducer.ts
demo/frontend/src/settings/SettingsReducer.ts
+70
-0
demo/frontend/src/settings/useSettingsContext.tsx
demo/frontend/src/settings/useSettingsContext.tsx
+21
-0
No files found.
demo/frontend/src/graphql/RelayEnvironment.ts
0 → 100644
View file @
17d316f3
/**
* 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
{
CacheConfig
,
Environment
,
FetchFunction
,
GraphQLResponse
,
LogEvent
,
Network
,
ObservableFromValue
,
RecordSource
,
RequestParameters
,
Store
,
UploadableMap
,
Variables
,
}
from
'
relay-runtime
'
;
import
fetchGraphQL
from
'
./fetchGraphQL
'
;
function
createFetchRelay
(
endpoint
:
string
):
FetchFunction
{
return
(
request
:
RequestParameters
,
variables
:
Variables
,
cacheConfig
:
CacheConfig
,
uploadables
?:
UploadableMap
|
null
,
):
ObservableFromValue
<
GraphQLResponse
>
=>
{
Logger
.
debug
(
`fetching query
${
request
.
name
}
with
${
JSON
.
stringify
(
variables
)}
`
,
);
return
fetchGraphQL
(
endpoint
,
request
,
variables
,
cacheConfig
,
uploadables
);
};
}
export
function
createEnvironment
(
endpoint
:
string
):
Environment
{
return
new
Environment
({
log
:
(
logEvent
:
LogEvent
)
=>
Logger
.
debug
(
logEvent
.
name
,
logEvent
),
network
:
Network
.
create
(
createFetchRelay
(
endpoint
)),
store
:
new
Store
(
new
RecordSource
()),
});
}
demo/frontend/src/graphql/RelayEnvironmentProvider.tsx
0 → 100644
View file @
17d316f3
/**
* 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
ErrorFallback
from
'
@/common/error/ErrorFallback
'
;
import
LoadingMessage
from
'
@/common/loading/LoadingMessage
'
;
import
{
createEnvironment
}
from
'
@/graphql/RelayEnvironment
'
;
import
{
ComponentType
,
PropsWithChildren
,
ReactNode
,
Suspense
,
useMemo
,
useState
,
}
from
'
react
'
;
import
{
ErrorBoundary
,
FallbackProps
}
from
'
react-error-boundary
'
;
import
{
RelayEnvironmentProvider
}
from
'
react-relay
'
;
type
Props
=
PropsWithChildren
<
{
suspenseFallback
?:
ReactNode
;
errorFallback
?:
ComponentType
<
FallbackProps
>
;
endpoint
:
string
;
}
>
;
export
default
function
OnevisionRelayEnvironmentProvider
({
suspenseFallback
,
errorFallback
=
ErrorFallback
,
endpoint
,
children
,
}:
Props
)
{
const
[
retryKey
,
setRetryKey
]
=
useState
<
number
>
(
0
);
const
environment
=
useMemo
(()
=>
{
return
createEnvironment
(
endpoint
);
// The retryKey is needed to force a new Relay Environment
// instance when the user retries after an error occurred.
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
endpoint
,
retryKey
]);
// Force re-creating Relay Environment
function
handleReset
()
{
setRetryKey
(
k
=>
k
+
1
);
}
return
(
<
ErrorBoundary
onReset
=
{
handleReset
}
FallbackComponent
=
{
errorFallback
}
>
<
RelayEnvironmentProvider
environment
=
{
environment
}
>
<
Suspense
fallback
=
{
suspenseFallback
??
<
LoadingMessage
/>
}
>
{
children
}
</
Suspense
>
</
RelayEnvironmentProvider
>
</
ErrorBoundary
>
);
}
demo/frontend/src/graphql/errors/CreateFilmstripError.ts
0 → 100644
View file @
17d316f3
/**
* 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
default
class
CreateFilmstripError
extends
Error
{
override
name
=
'
CreateFilmstripError
'
;
constructor
(
message
?:
string
)
{
super
(
message
);
}
}
demo/frontend/src/graphql/errors/DrawFrameError.ts
0 → 100644
View file @
17d316f3
/**
* 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
default
class
DrawFrameError
extends
Error
{
override
name
=
'
DrawFrameError
'
;
constructor
(
message
?:
string
)
{
super
(
message
);
}
}
demo/frontend/src/graphql/errors/WebGLContextError.ts
0 → 100644
View file @
17d316f3
/**
* 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
default
class
WebGLContextError
extends
Error
{
override
name
=
'
WebGLContextError
'
;
constructor
(
message
?:
string
)
{
super
(
message
);
}
}
demo/frontend/src/graphql/fetchGraphQL.ts
0 → 100644
View file @
17d316f3
/**
* 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
{
CacheConfig
,
GraphQLResponse
,
RequestParameters
,
UploadableMap
,
Variables
,
}
from
'
relay-runtime
'
;
/**
* Inspired by https://github.com/facebook/relay/issues/1844
*/
export
default
async
function
fetchGraphQL
(
endpoint
:
string
,
request
:
RequestParameters
,
variables
:
Variables
,
cacheConfig
:
CacheConfig
,
uploadables
?:
UploadableMap
|
null
,
):
Promise
<
GraphQLResponse
>
{
const
url
=
`
${
endpoint
}
/graphql`
;
const
headers
:
{[
name
:
string
]:
string
}
=
{};
const
requestInit
:
RequestInit
=
{
method
:
'
POST
'
,
headers
,
credentials
:
'
include
'
,
};
const
customHeaders
=
(
cacheConfig
?.
metadata
?.
headers
??
{})
as
{
[
key
:
string
]:
string
;
};
requestInit
.
headers
=
Object
.
assign
(
customHeaders
,
requestInit
.
headers
);
if
(
uploadables
!=
null
)
{
const
formData
=
new
FormData
();
formData
.
append
(
'
operations
'
,
JSON
.
stringify
({
query
:
request
.
text
,
variables
,
}),
);
const
uploadableMap
:
{
[
key
:
string
]:
string
[];
}
=
{};
Object
.
keys
(
uploadables
).
forEach
(
key
=>
{
uploadableMap
[
key
]
=
[
`variables.
${
key
}
`
];
});
formData
.
append
(
'
map
'
,
JSON
.
stringify
(
uploadableMap
));
Object
.
keys
(
uploadables
).
forEach
(
key
=>
{
formData
.
append
(
key
,
uploadables
[
key
]);
});
requestInit
.
body
=
formData
;
}
else
{
requestInit
.
headers
=
Object
.
assign
(
{
'
Content-Type
'
:
'
application/json
'
},
requestInit
.
headers
,
);
requestInit
.
body
=
JSON
.
stringify
({
query
:
request
.
text
,
variables
,
});
}
try
{
const
response
=
await
fetch
(
url
,
requestInit
);
const
result
=
await
response
.
json
();
// Handle any intentional GraphQL errors, which are passed through the
// errors property in the JSON payload.
if
(
'
errors
'
in
result
)
{
for
(
const
error
of
result
.
errors
)
{
Logger
.
error
(
error
);
}
}
return
result
;
}
catch
(
error
)
{
Logger
.
error
(
`Could not connect to GraphQL endpoint
${
url
}
`
,
error
);
throw
error
;
}
}
demo/frontend/src/jscocotools/mask.ts
0 → 100644
View file @
17d316f3
/**
* 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
class
DataArray
{
data
:
Uint8Array
;
readonly
shape
:
number
[];
constructor
(
data
:
Uint8Array
,
shape
:
Array
<
number
>
)
{
this
.
data
=
data
;
this
.
shape
=
shape
;
}
}
export
type
RLEObject
=
{
size
:
[
h
:
number
,
w
:
number
];
counts
:
string
;
};
type
RLE
=
{
h
:
number
;
w
:
number
;
m
:
number
;
cnts
:
number
[];
};
type
BB
=
number
[];
function
rleInit
(
R
:
RLE
,
h
:
number
,
w
:
number
,
m
:
number
,
cnts
:
number
[])
{
R
.
h
=
h
;
R
.
w
=
w
;
R
.
m
=
m
;
R
.
cnts
=
m
===
0
?
[
0
]
:
cnts
;
}
function
rlesInit
(
R
:
RLE
[],
n
:
number
)
{
let
i
;
for
(
i
=
0
;
i
<
n
;
i
++
)
{
R
[
i
]
=
{
h
:
0
,
w
:
0
,
m
:
0
,
cnts
:
[
0
]};
rleInit
(
R
[
i
],
0
,
0
,
0
,
[
0
]);
}
}
class
RLEs
{
_R
:
RLE
[];
_n
:
number
;
constructor
(
n
:
number
)
{
this
.
_R
=
[];
rlesInit
(
this
.
_R
,
n
);
this
.
_n
=
n
;
}
}
export
class
Masks
{
_mask
:
Uint8Array
;
_h
:
number
;
_w
:
number
;
_n
:
number
;
constructor
(
h
:
number
,
w
:
number
,
n
:
number
)
{
this
.
_mask
=
new
Uint8Array
(
h
*
w
*
n
);
this
.
_h
=
h
;
this
.
_w
=
w
;
this
.
_n
=
n
;
}
toDataArray
():
DataArray
{
return
new
DataArray
(
this
.
_mask
,
[
this
.
_h
,
this
.
_w
,
this
.
_n
]);
}
}
// encode mask to RLEs objects
// list of RLE string can be generated by RLEs member function
export
function
encode
(
mask
:
DataArray
):
RLEObject
[]
{
const
h
=
mask
.
shape
[
0
];
const
w
=
mask
.
shape
[
1
];
const
n
=
mask
.
shape
[
2
];
const
Rs
=
new
RLEs
(
n
);
rleEncode
(
Rs
.
_R
,
mask
.
data
,
h
,
w
,
n
);
const
objs
=
_toString
(
Rs
);
return
objs
;
}
// decode mask from compressed list of RLE string or RLEs object
export
function
decode
(
rleObjs
:
RLEObject
[]):
DataArray
{
const
Rs
=
_frString
(
rleObjs
);
const
h
=
Rs
.
_R
[
0
].
h
;
const
w
=
Rs
.
_R
[
0
].
w
;
const
n
=
Rs
.
_n
;
const
masks
=
new
Masks
(
h
,
w
,
n
);
rleDecode
(
Rs
.
_R
,
masks
.
_mask
,
n
);
return
masks
.
toDataArray
();
}
export
function
toBbox
(
rleObjs
:
RLEObject
[]):
BB
{
const
Rs
=
_frString
(
rleObjs
);
const
n
=
Rs
.
_n
;
const
bb
:
BB
=
[];
rleToBbox
(
Rs
.
_R
,
bb
,
n
);
return
bb
;
}
function
rleEncode
(
R
:
RLE
[],
M
:
Uint8Array
,
h
:
number
,
w
:
number
,
n
:
number
)
{
let
i
;
let
j
;
let
k
;
const
a
=
w
*
h
;
let
c
;
const
cnts
:
number
[]
=
[];
let
p
;
for
(
i
=
0
;
i
<
n
;
i
++
)
{
const
from
=
a
*
i
;
const
to
=
a
*
(
i
+
1
);
// Slice data for current RLE object
const
T
=
M
.
slice
(
from
,
to
);
k
=
0
;
p
=
0
;
c
=
0
;
for
(
j
=
0
;
j
<
a
;
j
++
)
{
if
(
T
[
j
]
!==
p
)
{
cnts
[
k
++
]
=
c
;
c
=
0
;
p
=
T
[
j
];
}
c
++
;
}
cnts
[
k
++
]
=
c
;
rleInit
(
R
[
i
],
h
,
w
,
k
,
[...
cnts
]);
}
}
function
rleDecode
(
R
:
RLE
[],
M
:
Uint8Array
,
n
:
number
):
void
{
let
i
;
let
j
;
let
k
;
let
p
=
0
;
for
(
i
=
0
;
i
<
n
;
i
++
)
{
let
v
=
false
;
for
(
j
=
0
;
j
<
R
[
i
].
m
;
j
++
)
{
for
(
k
=
0
;
k
<
R
[
i
].
cnts
[
j
];
k
++
)
{
M
[
p
++
]
=
v
===
false
?
0
:
1
;
}
v
=
!
v
;
}
}
}
function
rleToString
(
R
:
RLE
):
string
{
/* Similar to LEB128 but using 6 bits/char and ascii chars 48-111. */
let
i
;
const
m
=
R
.
m
;
let
p
=
0
;
let
x
:
number
;
let
more
;
const
s
:
string
[]
=
[];
for
(
i
=
0
;
i
<
m
;
i
++
)
{
x
=
R
.
cnts
[
i
];
if
(
i
>
2
)
{
x
-=
R
.
cnts
[
i
-
2
];
}
more
=
true
;
// 1;
while
(
more
)
{
let
c
=
x
&
0x1f
;
x
>>=
5
;
more
=
c
&
0x10
?
x
!=
-
1
:
x
!=
0
;
if
(
more
)
{
c
|=
0x20
;
}
c
+=
48
;
s
[
p
++
]
=
String
.
fromCharCode
(
c
);
}
}
return
s
.
join
(
''
);
}
// internal conversion from Python RLEs object to compressed RLE format
function
_toString
(
Rs
:
RLEs
):
RLEObject
[]
{
const
n
=
Rs
.
_n
;
let
py_string
;
let
c_string
;
const
objs
:
RLEObject
[]
=
[];
for
(
let
i
=
0
;
i
<
n
;
i
++
)
{
c_string
=
rleToString
(
Rs
.
_R
[
i
]);
py_string
=
c_string
;
objs
.
push
({
size
:
[
Rs
.
_R
[
i
].
h
,
Rs
.
_R
[
i
].
w
],
counts
:
py_string
,
});
}
return
objs
;
}
// internal conversion from compressed RLE format to Python RLEs object
function
_frString
(
rleObjs
:
RLEObject
[]):
RLEs
{
const
n
=
rleObjs
.
length
;
const
Rs
=
new
RLEs
(
n
);
let
py_string
;
let
c_string
;
for
(
let
i
=
0
;
i
<
rleObjs
.
length
;
i
++
)
{
const
obj
=
rleObjs
[
i
];
py_string
=
obj
.
counts
;
c_string
=
py_string
;
rleFrString
(
Rs
.
_R
[
i
],
c_string
,
obj
.
size
[
0
],
obj
.
size
[
1
]);
}
return
Rs
;
}
function
rleToBbox
(
R
:
RLE
[],
bb
:
BB
,
n
:
number
)
{
for
(
let
i
=
0
;
i
<
n
;
i
++
)
{
const
h
=
R
[
i
].
h
;
const
w
=
R
[
i
].
w
;
let
m
=
R
[
i
].
m
;
// The RLE structure likely contains run-length encoded data where each
// element represents a count of consecutive pixels with the same value in
// a binary image (black or white). Since the counts represent both black
// and white pixels, this operation ((siz)(m/2)) * 2 is used to ensure that
// m is always an even number. By doing so, the code can later check
// whether the current pixel is black or white based on whether the index j
// is even or odd.
m
=
Math
.
floor
(
m
/
2
)
*
2
;
let
xs
=
w
;
let
ys
=
h
;
let
xe
=
0
;
let
ye
=
0
;
let
cc
=
0
;
let
t
;
let
y
;
let
x
;
let
xp
=
0
;
if
(
m
===
0
)
{
bb
[
4
*
i
]
=
bb
[
4
*
i
+
1
]
=
bb
[
4
*
i
+
2
]
=
bb
[
4
*
i
+
3
]
=
0
;
continue
;
}
for
(
let
j
=
0
;
j
<
m
;
j
++
)
{
cc
+=
R
[
i
].
cnts
[
j
];
t
=
cc
-
(
j
%
2
);
y
=
t
%
h
;
x
=
Math
.
floor
((
t
-
y
)
/
h
);
if
(
j
%
2
===
0
)
{
xp
=
x
;
}
else
if
(
xp
<
x
)
{
ys
=
0
;
ye
=
h
-
1
;
}
xs
=
Math
.
min
(
xs
,
x
);
xe
=
Math
.
max
(
xe
,
x
);
ys
=
Math
.
min
(
ys
,
y
);
ye
=
Math
.
max
(
ye
,
y
);
}
bb
[
4
*
i
]
=
xs
;
bb
[
4
*
i
+
2
]
=
xe
-
xs
+
1
;
bb
[
4
*
i
+
1
]
=
ys
;
bb
[
4
*
i
+
3
]
=
ye
-
ys
+
1
;
}
}
function
rleFrString
(
R
:
RLE
,
s
:
string
,
h
:
number
,
w
:
number
):
void
{
let
m
=
0
;
let
p
=
0
;
let
k
;
let
x
;
let
more
;
let
cnts
=
[];
while
(
s
[
m
])
{
m
++
;
}
cnts
=
[];
m
=
0
;
while
(
s
[
p
])
{
x
=
0
;
k
=
0
;
more
=
1
;
while
(
more
)
{
const
c
=
s
.
charCodeAt
(
p
)
-
48
;
x
|=
(
c
&
0x1f
)
<<
(
5
*
k
);
more
=
c
&
0x20
;
p
++
;
k
++
;
if
(
!
more
&&
c
&
0x10
)
{
x
|=
-
1
<<
(
5
*
k
);
}
}
if
(
m
>
2
)
{
x
+=
cnts
[
m
-
2
];
}
cnts
[
m
++
]
=
x
;
}
rleInit
(
R
,
h
,
w
,
m
,
cnts
);
}
demo/frontend/src/layouts/DemoPageLayout.tsx
0 → 100644
View file @
17d316f3
/**
* 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
{
spacing
}
from
'
@/theme/tokens.stylex
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
PropsWithChildren
}
from
'
react
'
;
type
Props
=
PropsWithChildren
;
const
styles
=
stylex
.
create
({
container
:
{
width
:
'
100%
'
,
height
:
'
100%
'
,
display
:
'
flex
'
,
justifyContent
:
'
stretch
'
,
alignItems
:
'
stretch
'
,
gap
:
spacing
[
12
],
paddingHorizontal
:
spacing
[
12
],
paddingVertical
:
spacing
[
4
],
'
@media screen and (max-width: 768px)
'
:
{
display
:
'
flex
'
,
flexDirection
:
'
column-reverse
'
,
gap
:
0
,
marginTop
:
spacing
[
0
],
marginBottom
:
spacing
[
0
],
paddingHorizontal
:
spacing
[
0
],
paddingBottom
:
spacing
[
0
],
},
},
});
export
default
function
DemoPageLayout
({
children
}:
Props
)
{
return
<
div
{
...
stylex
.
props
(
styles
.
container
)
}
>
{
children
}
</
div
>;
}
demo/frontend/src/layouts/RootLayout.tsx
0 → 100644
View file @
17d316f3
/**
* 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
LoadingStateScreen
from
'
@/common/loading/LoadingStateScreen
'
;
import
useSettingsContext
from
'
@/settings/useSettingsContext
'
;
import
{
Cog6ToothIcon
}
from
'
@heroicons/react/24/outline
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
Suspense
}
from
'
react
'
;
import
{
Button
,
Indicator
}
from
'
react-daisyui
'
;
import
{
Outlet
}
from
'
react-router-dom
'
;
const
styles
=
stylex
.
create
({
container
:
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
height
:
'
100%
'
,
maxHeight
:
'
100vh
'
,
backgroundColor
:
'
#000
'
,
},
content
:
{
position
:
'
relative
'
,
flex
:
'
1 1 0%
'
,
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
overflowX
:
'
auto
'
,
overflowY
:
{
default
:
'
auto
'
,
'
@media screen and (max-width: 768px)
'
:
'
auto
'
,
},
},
debugActions
:
{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
position
:
'
fixed
'
,
top
:
100
,
right
:
0
,
backgroundColor
:
'
white
'
,
borderRadius
:
3
,
},
});
export
default
function
RootLayout
()
{
const
{
openModal
,
hasChanged
}
=
useSettingsContext
();
return
(
<
div
{
...
stylex
.
props
(
styles
.
container
)
}
>
<
div
{
...
stylex
.
props
(
styles
.
content
)
}
>
<
Suspense
fallback
=
{
<
LoadingStateScreen
title
=
"Loading demo..."
description
=
"This may take a few moments, you're almost there!"
/>
}
>
<
Outlet
/>
</
Suspense
>
</
div
>
<
div
{
...
stylex
.
props
(
styles
.
debugActions
)
}
>
<
Indicator
>
{
hasChanged
&&
(
<
Indicator
.
Item
className
=
"badge badge-primary scale-50"
horizontal
=
"start"
vertical
=
"top"
/>
)
}
<
Button
color
=
"ghost"
onClick
=
{
openModal
}
shape
=
"circle"
size
=
"xs"
startIcon
=
{
<
Cog6ToothIcon
className
=
"w-4 h-4"
/>
}
title
=
"Bugnub"
/>
</
Indicator
>
</
div
>
</
div
>
);
}
demo/frontend/src/main.tsx
0 → 100644
View file @
17d316f3
/**
* 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
App
from
'
@/App.tsx
'
;
import
*
as
React
from
'
react
'
;
import
*
as
ReactDOM
from
'
react-dom/client
'
;
ReactDOM
.
createRoot
(
document
.
getElementById
(
'
root
'
)
!
).
render
(
<
React
.
StrictMode
>
<
App
/>
</
React
.
StrictMode
>,
);
demo/frontend/src/routes/DemoPage.tsx
0 → 100644
View file @
17d316f3
/**
* 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
Toolbar
from
'
@/common/components/toolbar/Toolbar
'
;
import
DemoVideoEditor
from
'
@/common/components/video/editor/DemoVideoEditor
'
;
import
useInputVideo
from
'
@/common/components/video/useInputVideo
'
;
import
StatsView
from
'
@/debug/stats/StatsView
'
;
import
{
VideoData
}
from
'
@/demo/atoms
'
;
import
DemoPageLayout
from
'
@/layouts/DemoPageLayout
'
;
import
{
DemoPageQuery
}
from
'
@/routes/__generated__/DemoPageQuery.graphql
'
;
import
{
useEffect
,
useMemo
}
from
'
react
'
;
import
{
graphql
,
useLazyLoadQuery
}
from
'
react-relay
'
;
import
{
Location
,
useLocation
}
from
'
react-router-dom
'
;
type
LocationState
=
{
video
?:
VideoData
;
};
export
default
function
DemoPage
()
{
const
{
state
}
=
useLocation
()
as
Location
<
LocationState
>
;
const
data
=
useLazyLoadQuery
<
DemoPageQuery
>
(
graphql
`
query DemoPageQuery {
defaultVideo {
path
posterPath
url
posterUrl
height
width
}
}
`
,
{},
);
const
{
setInputVideo
}
=
useInputVideo
();
const
video
=
useMemo
(()
=>
{
return
state
?.
video
??
data
.
defaultVideo
;
},
[
state
,
data
]);
useEffect
(()
=>
{
setInputVideo
(
video
);
},
[
video
,
setInputVideo
]);
return
(
<
DemoPageLayout
>
<
StatsView
/>
<
Toolbar
/>
<
DemoVideoEditor
video
=
{
video
}
/>
</
DemoPageLayout
>
);
}
demo/frontend/src/routes/DemoPageWrapper.tsx
0 → 100644
View file @
17d316f3
/**
* 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
LoadingStateScreen
from
'
@/common/loading/LoadingStateScreen
'
;
import
DemoPage
from
'
@/routes/DemoPage
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
isFirefox
}
from
'
react-device-detect
'
;
const
styles
=
stylex
.
create
({
link
:
{
textDecorationLine
:
'
underline
'
,
color
:
'
#A7B3BF
'
,
},
});
const
REQUIRED_WINDOW_APIS
=
[
'
VideoEncoder
'
,
'
VideoDecoder
'
,
'
VideoFrame
'
];
function
isBrowserSupported
()
{
for
(
const
api
of
REQUIRED_WINDOW_APIS
)
{
if
(
!
(
api
in
window
))
{
return
false
;
}
}
// Test if transferControlToOffscreen is supported. For example, this will
// fail on iOS version < 16.4
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen
const
canvas
=
document
.
createElement
(
'
canvas
'
);
if
(
typeof
canvas
.
transferControlToOffscreen
!==
'
function
'
)
{
return
false
;
}
return
true
;
}
export
default
function
DemoPageWrapper
()
{
const
isBrowserUnsupported
=
!
isBrowserSupported
();
if
(
isBrowserUnsupported
&&
isFirefox
)
{
const
nightlyUrl
=
'
https://wiki.mozilla.org/Nightly
'
;
return
(
<
LoadingStateScreen
title
=
"Sorry Firefox!"
description
=
{
<
div
>
This version of Firefox doesn’t support the video features we’ll
need to run this demo. You can either update Firefox to the latest
nightly build
{
'
'
}
<
a
{
...
stylex
.
props
(
styles
.
link
)
}
href
=
{
nightlyUrl
}
target
=
"_blank"
rel
=
"noreferrer"
>
here
</
a
>
, or try again using Chrome or Safari.
</
div
>
}
linkProps
=
{
{
to
:
'
..
'
,
label
:
'
Back to homepage
'
}
}
/>
);
}
if
(
isBrowserUnsupported
)
{
return
(
<
LoadingStateScreen
title
=
"Uh oh, this browser isn’t supported."
description
=
"This browser doesn’t support the video features we’ll need to run this demo. Try again using Chrome, Safari, or Firefox Nightly."
linkProps
=
{
{
to
:
'
..
'
,
label
:
'
Back to homepage
'
}
}
/>
);
}
return
<
DemoPage
/>;
}
demo/frontend/src/routes/PageNotFoundPage.tsx
0 → 100644
View file @
17d316f3
/**
* 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
LoadingStateScreen
from
'
@/common/loading/LoadingStateScreen
'
;
export
default
function
PageNotFoundPage
()
{
return
(
<
LoadingStateScreen
title
=
"Page not found"
description
=
"It looks like you might be in the wrong place."
linkProps
=
{
{
to
:
'
..
'
,
label
:
'
Click here to access the SAM 2 Demo
'
,
}
}
/>
);
}
demo/frontend/src/routes/__generated__/DemoPageQuery.graphql.ts
0 → 100644
View file @
17d316f3
/**
* @generated SignedSource<<f457eacd20a61cba601921caee2a18f5>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import
{
ConcreteRequest
,
Query
}
from
'
relay-runtime
'
;
export
type
DemoPageQuery$variables
=
Record
<
PropertyKey
,
never
>
;
export
type
DemoPageQuery$data
=
{
readonly
defaultVideo
:
{
readonly
height
:
number
;
readonly
path
:
string
;
readonly
posterPath
:
string
|
null
|
undefined
;
readonly
posterUrl
:
string
;
readonly
url
:
string
;
readonly
width
:
number
;
};
};
export
type
DemoPageQuery
=
{
response
:
DemoPageQuery$data
;
variables
:
DemoPageQuery$variables
;
};
const
node
:
ConcreteRequest
=
(
function
(){
var
v0
=
[
{
"
alias
"
:
null
,
"
args
"
:
null
,
"
concreteType
"
:
"
Video
"
,
"
kind
"
:
"
LinkedField
"
,
"
name
"
:
"
defaultVideo
"
,
"
plural
"
:
false
,
"
selections
"
:
[
{
"
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
}
];
return
{
"
fragment
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Fragment
"
,
"
metadata
"
:
null
,
"
name
"
:
"
DemoPageQuery
"
,
"
selections
"
:
(
v0
/*: any*/
),
"
type
"
:
"
Query
"
,
"
abstractKey
"
:
null
},
"
kind
"
:
"
Request
"
,
"
operation
"
:
{
"
argumentDefinitions
"
:
[],
"
kind
"
:
"
Operation
"
,
"
name
"
:
"
DemoPageQuery
"
,
"
selections
"
:
(
v0
/*: any*/
)
},
"
params
"
:
{
"
cacheID
"
:
"
71cbafce4d2d047acdc54d86504f2d2e
"
,
"
id
"
:
null
,
"
metadata
"
:
{},
"
name
"
:
"
DemoPageQuery
"
,
"
operationKind
"
:
"
query
"
,
"
text
"
:
"
query DemoPageQuery {
\n
defaultVideo {
\n
path
\n
posterPath
\n
url
\n
posterUrl
\n
height
\n
width
\n
}
\n
}
\n
"
}
};
})();
(
node
as
any
).
hash
=
"
63c9465d78b30d42d6fc11e50a9af142
"
;
export
default
node
;
demo/frontend/src/settings/ApprovableInput.tsx
0 → 100644
View file @
17d316f3
/**
* 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
Tooltip
from
'
@/common/components/Tooltip
'
;
import
{
ArrowPathIcon
,
CheckIcon
,
XMarkIcon
}
from
'
@heroicons/react/24/solid
'
;
import
{
ChangeEvent
,
KeyboardEvent
,
useEffect
,
useMemo
,
useState
}
from
'
react
'
;
import
{
Button
,
Form
,
Input
,
Join
}
from
'
react-daisyui
'
;
type
Props
<
T
extends
string
|
number
>
=
Omit
<
React
.
InputHTMLAttributes
<
HTMLInputElement
>
,
'
size
'
|
'
color
'
|
'
onChange
'
>
&
{
label
:
string
;
defaultValue
:
T
;
initialValue
:
T
;
onChange
:
(
value
:
string
)
=>
void
;
};
function
getStep
(
value
:
number
)
{
const
stringValue
=
String
(
value
);
const
decimals
=
stringValue
.
split
(
'
.
'
)[
1
];
if
(
decimals
!=
null
)
{
// Not using 0.1 ** decimals.length because this will result in rounding
// errors, e.g., 0.1 ** 2 => 0.010000000000000002.
return
1
/
10
**
decimals
.
length
;
}
return
1
;
}
export
default
function
ApprovableInput
<
T
extends
string
|
number
>
({
label
,
defaultValue
,
initialValue
,
onChange
,
...
otherProps
}:
Props
<
T
>
)
{
const
[
value
,
setValue
]
=
useState
<
string
>
(
`
${
initialValue
}
`
);
useEffect
(()
=>
{
setValue
(
`
${
initialValue
}
`
);
},
[
initialValue
]);
const
step
=
useMemo
(()
=>
{
return
typeof
defaultValue
===
'
number
'
&&
isFinite
(
defaultValue
)
?
getStep
(
defaultValue
)
:
undefined
;
},
[
defaultValue
]);
return
(
<
div
>
<
Form
.
Label
className
=
"flex-col items-start gap-2"
title
=
{
label
}
>
<
Join
className
=
"w-full"
>
<
Input
{
...
otherProps
}
className
=
"w-full join-item"
value
=
{
value
}
step
=
{
step
}
placeholder
=
{
`
${
defaultValue
}
`
}
onChange
=
{
(
event
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setValue
(
event
.
target
.
value
);
}
}
onKeyDown
=
{
(
event
:
KeyboardEvent
<
HTMLInputElement
>
)
=>
{
if
(
event
.
key
===
'
Enter
'
)
{
event
.
preventDefault
();
onChange
(
value
);
}
}
}
/>
<
Tooltip
message
=
"Reset to default"
>
<
Button
className
=
"join-item"
onClick
=
{
event
=>
{
event
.
preventDefault
();
setValue
(
`
${
defaultValue
}
`
);
}
}
>
<
ArrowPathIcon
className
=
"h-4 w-4"
/>
</
Button
>
</
Tooltip
>
<
Tooltip
message
=
"Revert change"
>
<
Button
className
=
"join-item"
color
=
"neutral"
disabled
=
{
initialValue
==
value
}
onClick
=
{
event
=>
{
event
.
preventDefault
();
setValue
(
`
${
initialValue
}
`
);
}
}
>
<
XMarkIcon
className
=
"h-4 w-4"
/>
</
Button
>
</
Tooltip
>
<
Tooltip
message
=
"Apply change"
>
<
Button
className
=
"join-item"
color
=
"primary"
disabled
=
{
initialValue
==
value
}
onClick
=
{
event
=>
{
event
.
preventDefault
();
onChange
(
value
);
}
}
>
<
CheckIcon
className
=
"h-4 w-4"
/>
</
Button
>
</
Tooltip
>
</
Join
>
</
Form
.
Label
>
</
div
>
);
}
demo/frontend/src/settings/SAM2Settings.tsx
0 → 100644
View file @
17d316f3
/**
* 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
{
INFERENCE_API_ENDPOINT
,
VIDEO_API_ENDPOINT
}
from
'
@/demo/DemoConfig
'
;
import
ApprovableInput
from
'
@/settings/ApprovableInput
'
;
import
useSettingsContext
from
'
@/settings/useSettingsContext
'
;
export
default
function
SAMVSettings
()
{
const
{
settings
,
dispatch
}
=
useSettingsContext
();
return
(
<
div
>
<
ApprovableInput
label
=
"Video API Endpoint"
defaultValue
=
{
VIDEO_API_ENDPOINT
}
initialValue
=
{
settings
.
videoAPIEndpoint
}
onChange
=
{
url
=>
dispatch
({
type
:
'
change-video-api-endpoint
'
,
url
})
}
/>
<
ApprovableInput
label
=
"Inference API Endpoint"
defaultValue
=
{
INFERENCE_API_ENDPOINT
}
initialValue
=
{
settings
.
inferenceAPIEndpoint
}
onChange
=
{
url
=>
dispatch
({
type
:
'
change-inference-api-endpoint
'
,
url
})
}
/>
</
div
>
);
}
demo/frontend/src/settings/SettingsContextProvider.tsx
0 → 100644
View file @
17d316f3
/**
* 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
emptyFunction
from
'
@/common/utils/emptyFunction
'
;
import
{
INFERENCE_API_ENDPOINT
,
VIDEO_API_ENDPOINT
}
from
'
@/demo/DemoConfig
'
;
import
SettingsModal
from
'
@/settings/SettingsModal
'
;
import
{
Action
,
DEFAULT_SETTINGS
,
Settings
,
settingsReducer
,
}
from
'
@/settings/SettingsReducer
'
;
import
{
PropsWithChildren
,
createContext
,
useCallback
,
useMemo
,
useRef
,
}
from
'
react
'
;
import
{
useImmerReducer
}
from
'
use-immer
'
;
type
ContextProps
=
{
settings
:
Settings
;
dispatch
:
React
.
Dispatch
<
Action
>
;
openModal
:
()
=>
void
;
closeModal
:
()
=>
void
;
hasChanged
:
boolean
;
};
export
const
SettingsContext
=
createContext
<
ContextProps
>
({
settings
:
DEFAULT_SETTINGS
,
dispatch
:
emptyFunction
,
openModal
:
emptyFunction
,
closeModal
:
emptyFunction
,
hasChanged
:
false
,
});
type
Props
=
PropsWithChildren
;
export
default
function
SettingsContextProvider
({
children
}:
Props
)
{
const
[
state
,
dispatch
]
=
useImmerReducer
(
settingsReducer
,
DEFAULT_SETTINGS
,
settings
=>
{
// Load the settings from local storage. Eventually use the reducer init
// to handle initial loading.
return
settingsReducer
(
settings
,
{
type
:
'
load-state
'
});
},
);
const
modalRef
=
useRef
<
HTMLDialogElement
>
(
null
);
const
openModal
=
useCallback
(()
=>
{
modalRef
.
current
?.
showModal
();
},
[
modalRef
]);
const
handleCloseModal
=
useCallback
(()
=>
{
modalRef
.
current
?.
close
();
},
[
modalRef
]);
const
hasChanged
=
useMemo
(()
=>
{
return
(
VIDEO_API_ENDPOINT
!==
state
.
videoAPIEndpoint
||
INFERENCE_API_ENDPOINT
!==
state
.
inferenceAPIEndpoint
);
},
[
state
.
videoAPIEndpoint
,
state
.
inferenceAPIEndpoint
]);
const
value
=
useMemo
(
()
=>
({
settings
:
state
,
dispatch
,
openModal
,
closeModal
:
handleCloseModal
,
hasChanged
,
}),
[
state
,
dispatch
,
openModal
,
handleCloseModal
,
hasChanged
],
);
return
(
<
SettingsContext
.
Provider
value
=
{
value
}
>
{
children
}
<
SettingsModal
ref
=
{
modalRef
}
/>
</
SettingsContext
.
Provider
>
);
}
demo/frontend/src/settings/SettingsModal.tsx
0 → 100644
View file @
17d316f3
/**
* 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
{
DEMO_FRIENDLY_NAME
}
from
'
@/demo/DemoConfig
'
;
import
SAM2Settings
from
'
@/settings/SAM2Settings
'
;
import
{
XMarkIcon
}
from
'
@heroicons/react/24/solid
'
;
import
{
forwardRef
,
useState
}
from
'
react
'
;
import
{
Button
,
Modal
}
from
'
react-daisyui
'
;
import
useSettingsContext
from
'
./useSettingsContext
'
;
type
Props
=
unknown
;
type
Config
=
{
key
:
'
sam2
'
;
title
:
string
;
component
:
React
.
ElementType
;
};
const
SettingsConfig
:
Config
[]
=
[
{
key
:
'
sam2
'
,
title
:
DEMO_FRIENDLY_NAME
,
component
:
SAM2Settings
,
},
];
export
default
forwardRef
<
HTMLDialogElement
,
Props
>
(
function
SettingsModal
(
_props
,
ref
)
{
const
{
closeModal
}
=
useSettingsContext
();
const
[
activeConfig
,
setActiveConfig
]
=
useState
<
Config
>
(
SettingsConfig
[
0
]);
const
SettingsComponent
=
activeConfig
.
component
;
return
(
<
Modal
data
-
testid
=
"settings-modal"
ref
=
{
ref
}
className
=
"lg:absolute lg:top-10 lg:w-11/12 lg:max-w-4xl flex flex-col"
responsive
=
{
true
}
>
<
Button
size
=
"sm"
color
=
"ghost"
shape
=
"circle"
className
=
"absolute right-2 top-2"
startIcon
=
{
<
XMarkIcon
className
=
"w-6 h-6"
/>
}
onClick
=
{
closeModal
}
/>
<
Modal
.
Header
className
=
"font-bold"
>
Settings
</
Modal
.
Header
>
<
Modal
.
Body
className
=
"flex flex-col grow overflow-hidden"
>
<
div
className
=
"flex flex-col md:lg:flex-row gap-4 md:lg:gap-12 overflow-hidden"
>
<
div
className
=
"flex flex-row shrink-0 md:lg:flex-col gap-4 md:lg:py-2 overflow-x-auto"
>
{
SettingsConfig
.
map
(
config
=>
(
<
div
key
=
{
config
.
key
}
data
-
testid
=
{
`show-settings-
${
config
.
key
}
`
}
className
=
{
`cursor-pointer whitespace-nowrap
${
activeConfig
.
key
===
config
.
key
&&
'
text-primary
'
}
${
activeConfig
.
key
===
config
.
key
&&
'
sm:underline md:lg:no-underline sm:underline-offset-4
'
}
`
}
onClick
=
{
()
=>
setActiveConfig
(
config
)
}
>
{
config
.
title
}
</
div
>
))
}
</
div
>
<
div
data
-
testid
=
{
`settings-
${
activeConfig
.
key
}
`
}
className
=
"overflow-hidden overflow-y-auto grow md:lg:pt-2"
>
<
div
className
=
"flex flex-col grow-0 flex-1"
>
<
h1
className
=
"hidden md:lg:block"
>
{
activeConfig
.
title
}
</
h1
>
<
SettingsComponent
/>
</
div
>
</
div
>
</
div
>
</
Modal
.
Body
>
<
Modal
.
Actions
className
=
"shrink-0"
>
<
Button
onClick
=
{
closeModal
}
>
Close
</
Button
>
</
Modal
.
Actions
>
</
Modal
>
);
},
);
demo/frontend/src/settings/SettingsReducer.ts
0 → 100644
View file @
17d316f3
/**
* 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
{
INFERENCE_API_ENDPOINT
,
VIDEO_API_ENDPOINT
}
from
'
@/demo/DemoConfig
'
;
export
type
Settings
=
{
videoAPIEndpoint
:
string
;
inferenceAPIEndpoint
:
string
;
};
// Key used to store the settings in the browser's local storage.
export
const
SAM2_SETTINGS_KEY
=
'
SAM2_SETTINGS_KEY
'
;
export
type
Action
=
|
{
type
:
'
load-state
'
}
|
{
type
:
'
change-video-api-endpoint
'
;
url
:
string
}
|
{
type
:
'
change-inference-api-endpoint
'
;
url
:
string
};
export
const
DEFAULT_SETTINGS
:
Settings
=
{
videoAPIEndpoint
:
VIDEO_API_ENDPOINT
,
inferenceAPIEndpoint
:
INFERENCE_API_ENDPOINT
,
};
export
function
settingsReducer
(
state
:
Settings
,
action
:
Action
):
Settings
{
function
storeSettings
(
newState
:
Settings
):
void
{
localStorage
.
setItem
(
SAM2_SETTINGS_KEY
,
JSON
.
stringify
(
newState
));
}
switch
(
action
.
type
)
{
case
'
load-state
'
:
{
try
{
const
serializedSettings
=
localStorage
.
getItem
(
SAM2_SETTINGS_KEY
);
if
(
serializedSettings
!=
null
)
{
return
JSON
.
parse
(
serializedSettings
)
as
Settings
;
}
else
{
// Store default settings in local storage. This will populate the
// settings in the local storage on first app load or when user
// cleared the browser cache.
storeSettings
(
state
);
}
}
catch
{
// Could not parse settings. Using default settings instead.
}
return
state
;
}
case
'
change-video-api-endpoint
'
:
state
.
videoAPIEndpoint
=
action
.
url
;
break
;
case
'
change-inference-api-endpoint
'
:
state
.
inferenceAPIEndpoint
=
action
.
url
;
break
;
}
// Store the settings state on every change
storeSettings
(
state
);
return
state
;
}
demo/frontend/src/settings/useSettingsContext.tsx
0 → 100644
View file @
17d316f3
/**
* 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
{
useContext
}
from
'
react
'
;
import
{
SettingsContext
}
from
'
@/settings/SettingsContextProvider
'
;
export
default
function
useSettingsContext
()
{
return
useContext
(
SettingsContext
);
}
Prev
1
…
29
30
31
32
33
34
35
36
37
…
48
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