@ynput/ayon-frontend-shared 0.3.14 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/src/components/FileThumbnail/FileThumbnail.cjs.js +6 -6
- package/dist/shared/src/components/FileThumbnail/FileThumbnail.cjs.js.map +1 -1
- package/dist/shared/src/components/FileThumbnail/FileThumbnail.es.js +54 -26
- package/dist/shared/src/components/FileThumbnail/FileThumbnail.es.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("../../../../_virtual/jsx-runtime.cjs.js"),g=require("@ynput/ayon-react-components"),i=require("react"),l=require("styled-components"),j=require("clsx"),I=l.keyframes`
|
|
2
2
|
from {
|
|
3
3
|
transform: rotate(0deg);
|
|
4
4
|
}
|
|
5
5
|
to {
|
|
6
6
|
transform: rotate(360deg);
|
|
7
7
|
}
|
|
8
|
-
`,
|
|
8
|
+
`,T=l.div`
|
|
9
9
|
position: relative;
|
|
10
10
|
display: flex;
|
|
11
11
|
justify-content: center;
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
overflow: hidden;
|
|
15
15
|
|
|
16
16
|
background-color: var(--md-sys-color-surface-container-low);
|
|
17
|
-
`,
|
|
18
|
-
animation: ${
|
|
17
|
+
`,L=l(g.Icon)`
|
|
18
|
+
animation: ${I} 1s linear infinite;
|
|
19
19
|
opacity: 0.6;
|
|
20
|
-
`,
|
|
20
|
+
`,q=l.img`
|
|
21
21
|
position: absolute;
|
|
22
22
|
inset: 0;
|
|
23
23
|
object-fit: cover;
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
&.hidden {
|
|
30
30
|
opacity: 0;
|
|
31
31
|
}
|
|
32
|
-
`,
|
|
32
|
+
`,F=({mimetype:E="",src:p,...u})=>{const[R,r]=i.useState(!1),[w,t]=i.useState(!1),s=i.useRef(null),a=i.useRef(0);return i.useEffect(()=>{const n=a.current+1;a.current=n,t(!1),r(!1);const e=s.current;let h=!1,d=0,m=0,f=0;const o=b=>{h||n!==a.current||(h=!0,cancelAnimationFrame(d),window.clearInterval(m),window.clearTimeout(f),b())},v=()=>{!e||n!==a.current||e.complete&&e.naturalWidth>0&&o(()=>{t(!0),r(!1)})},x=()=>o(()=>{t(!0),r(!1)}),y=()=>o(()=>{t(!0),r(!0)});return e&&(e.addEventListener("load",x),e.addEventListener("error",y),typeof e.decode=="function"&&e.decode().then(()=>o(()=>{t(!0),r(!1)})).catch(()=>{v()})),d=requestAnimationFrame(v),m=window.setInterval(v,250),f=window.setTimeout(()=>{o(()=>{e&&e.naturalWidth>0?(t(!0),r(!1)):t(!0)})},1e4),()=>{cancelAnimationFrame(d),window.clearInterval(m),window.clearTimeout(f),e&&(e.removeEventListener("load",x),e.removeEventListener("error",y))}},[p]),c.jsxRuntimeExports.jsxs(T,{...u,className:j("file-thumbnail",u.className),children:[w?c.jsxRuntimeExports.jsx(g.Icon,{icon:g.getMimeTypeIcon(E)}):c.jsxRuntimeExports.jsx(L,{icon:"progress_activity"}),c.jsxRuntimeExports.jsx(q,{ref:s,src:p,...u,onError:n=>{n.currentTarget===s.current&&(t(!0),r(!0))},onLoad:n=>{n.currentTarget===s.current&&(t(!0),r(!1))},className:j({hidden:!w||R})})]})};exports.FileThumbnail=F;
|
|
33
33
|
//# sourceMappingURL=FileThumbnail.cjs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileThumbnail.cjs.js","sources":["../../../../../src/components/FileThumbnail/FileThumbnail.tsx"],"sourcesContent":["import { getMimeTypeIcon, Icon } from '@ynput/ayon-react-components'\nimport { FC, HTMLAttributes, ImgHTMLAttributes, useEffect, useState } from 'react'\nimport styled, { keyframes } from 'styled-components'\nimport clsx from 'clsx'\n\nconst spin = keyframes`\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n`\n\nconst Wrapper = styled.div`\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n\n overflow: hidden;\n\n background-color: var(--md-sys-color-surface-container-low);\n`\n\nconst LoadingIcon = styled(Icon)`\n animation: ${spin} 1s linear infinite;\n opacity: 0.6;\n`\n\nconst Image = styled.img`\n position: absolute;\n inset: 0;\n object-fit: cover;\n background-color: var(--md-sys-color-surface-container-low);\n\n width: 100%;\n height: 100%;\n\n &.hidden {\n opacity: 0;\n }\n`\n\nexport interface FileThumbnailProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onError' | 'onLoad'> {\n src: string\n mimetype?: string\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'onError' | 'onLoad'>\n}\n\nexport const FileThumbnail: FC<FileThumbnailProps> = ({ mimetype = '', src, ...props }) => {\n const [error, setError] = useState(false)\n const [loaded, setLoaded] = useState(false)\n\n useEffect(() => {\n // reset
|
|
1
|
+
{"version":3,"file":"FileThumbnail.cjs.js","sources":["../../../../../src/components/FileThumbnail/FileThumbnail.tsx"],"sourcesContent":["import { getMimeTypeIcon, Icon } from '@ynput/ayon-react-components'\nimport { FC, HTMLAttributes, ImgHTMLAttributes, useEffect, useState, useRef } from 'react'\nimport styled, { keyframes } from 'styled-components'\nimport clsx from 'clsx'\n\nconst spin = keyframes`\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n`\n\nconst Wrapper = styled.div`\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n\n overflow: hidden;\n\n background-color: var(--md-sys-color-surface-container-low);\n`\n\nconst LoadingIcon = styled(Icon)`\n animation: ${spin} 1s linear infinite;\n opacity: 0.6;\n`\n\nconst Image = styled.img`\n position: absolute;\n inset: 0;\n object-fit: cover;\n background-color: var(--md-sys-color-surface-container-low);\n\n width: 100%;\n height: 100%;\n\n &.hidden {\n opacity: 0;\n }\n`\n\nexport interface FileThumbnailProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onError' | 'onLoad'> {\n src: string\n mimetype?: string\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'onError' | 'onLoad'>\n}\n\nexport const FileThumbnail: FC<FileThumbnailProps> = ({ mimetype = '', src, ...props }) => {\n const [error, setError] = useState(false)\n const [loaded, setLoaded] = useState(false)\n const imageRef = useRef<HTMLImageElement | null>(null)\n const loadAttemptRef = useRef(0)\n\n // Robust loading effect: attempt to detect when the image is actually ready.\n // Uses `decode()` when available, falls back to `complete` + `naturalWidth` checks,\n // adds RAF + polling and a watchdog timeout to avoid stuck states.\n useEffect(() => {\n const attemptId = loadAttemptRef.current + 1\n loadAttemptRef.current = attemptId\n\n // reset visible state for this attempt\n setLoaded(false)\n setError(false)\n\n const img = imageRef.current\n let resolved = false\n let frame = 0\n let interval = 0\n let timeout = 0\n\n const resolveOnce = (resolver: () => void) => {\n if (resolved || attemptId !== loadAttemptRef.current) return\n resolved = true\n cancelAnimationFrame(frame)\n window.clearInterval(interval)\n window.clearTimeout(timeout)\n resolver()\n }\n\n const checkComplete = () => {\n if (!img || attemptId !== loadAttemptRef.current) return\n if (img.complete && img.naturalWidth > 0) {\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n })\n }\n }\n\n const onLoad = () =>\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n })\n const onError = () =>\n resolveOnce(() => {\n // keep behavior: mark loaded so placeholder swaps to icon, and flag error\n setLoaded(true)\n setError(true)\n })\n\n if (img) {\n img.addEventListener('load', onLoad)\n img.addEventListener('error', onError)\n\n if (typeof img.decode === 'function') {\n img\n .decode()\n .then(() =>\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n }),\n )\n .catch(() => {\n checkComplete()\n })\n }\n }\n\n frame = requestAnimationFrame(checkComplete)\n interval = window.setInterval(checkComplete, 250)\n\n timeout = window.setTimeout(() => {\n // Force-resolve after a timeout to avoid infinite spinner states.\n resolveOnce(() => {\n if (img && img.naturalWidth > 0) {\n setLoaded(true)\n setError(false)\n } else {\n setLoaded(true)\n }\n })\n }, 10000)\n\n return () => {\n cancelAnimationFrame(frame)\n window.clearInterval(interval)\n window.clearTimeout(timeout)\n if (img) {\n img.removeEventListener('load', onLoad)\n img.removeEventListener('error', onError)\n }\n }\n }, [src])\n\n return (\n <Wrapper {...props} className={clsx('file-thumbnail', props.className)}>\n {!loaded ? (\n <LoadingIcon icon=\"progress_activity\" />\n ) : (\n <Icon icon={getMimeTypeIcon(mimetype)} />\n )}\n <Image\n ref={imageRef}\n src={src}\n {...props}\n onError={(event) => {\n if (event.currentTarget === imageRef.current) {\n setLoaded(true)\n setError(true)\n }\n }}\n onLoad={(event) => {\n if (event.currentTarget === imageRef.current) {\n setLoaded(true)\n setError(false)\n }\n }}\n className={clsx({ hidden: !loaded || error })}\n />\n </Wrapper>\n )\n}\n"],"names":["spin","keyframes","Wrapper","styled","LoadingIcon","Icon","Image","FileThumbnail","mimetype","src","props","error","setError","useState","loaded","setLoaded","imageRef","useRef","loadAttemptRef","useEffect","attemptId","img","resolved","frame","interval","timeout","resolveOnce","resolver","checkComplete","onLoad","onError","jsxs","clsx","jsx","getMimeTypeIcon","event"],"mappings":"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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { j as
|
|
2
|
-
import { Icon as
|
|
3
|
-
import { useState as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
const
|
|
1
|
+
import { j as s } from "../../../../_virtual/jsx-runtime.es.js";
|
|
2
|
+
import { Icon as j, getMimeTypeIcon as b } from "@ynput/ayon-react-components";
|
|
3
|
+
import { useState as y, useRef as x, useEffect as k } from "react";
|
|
4
|
+
import u, { keyframes as A } from "styled-components";
|
|
5
|
+
import E from "clsx";
|
|
6
|
+
const F = A`
|
|
7
7
|
from {
|
|
8
8
|
transform: rotate(0deg);
|
|
9
9
|
}
|
|
10
10
|
to {
|
|
11
11
|
transform: rotate(360deg);
|
|
12
12
|
}
|
|
13
|
-
`,
|
|
13
|
+
`, R = u.div`
|
|
14
14
|
position: relative;
|
|
15
15
|
display: flex;
|
|
16
16
|
justify-content: center;
|
|
@@ -19,10 +19,10 @@ const g = p`
|
|
|
19
19
|
overflow: hidden;
|
|
20
20
|
|
|
21
21
|
background-color: var(--md-sys-color-surface-container-low);
|
|
22
|
-
`,
|
|
23
|
-
animation: ${
|
|
22
|
+
`, N = u(j)`
|
|
23
|
+
animation: ${F} 1s linear infinite;
|
|
24
24
|
opacity: 0.6;
|
|
25
|
-
`,
|
|
25
|
+
`, W = u.img`
|
|
26
26
|
position: absolute;
|
|
27
27
|
inset: 0;
|
|
28
28
|
object-fit: cover;
|
|
@@ -34,29 +34,57 @@ const g = p`
|
|
|
34
34
|
&.hidden {
|
|
35
35
|
opacity: 0;
|
|
36
36
|
}
|
|
37
|
-
`,
|
|
38
|
-
const [
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
`, _ = ({ mimetype: I = "", src: v, ...c }) => {
|
|
38
|
+
const [L, r] = y(!1), [p, t] = y(!1), a = x(null), i = x(0);
|
|
39
|
+
return k(() => {
|
|
40
|
+
const o = i.current + 1;
|
|
41
|
+
i.current = o, t(!1), r(!1);
|
|
42
|
+
const e = a.current;
|
|
43
|
+
let w = !1, l = 0, m = 0, d = 0;
|
|
44
|
+
const n = (T) => {
|
|
45
|
+
w || o !== i.current || (w = !0, cancelAnimationFrame(l), window.clearInterval(m), window.clearTimeout(d), T());
|
|
46
|
+
}, f = () => {
|
|
47
|
+
!e || o !== i.current || e.complete && e.naturalWidth > 0 && n(() => {
|
|
48
|
+
t(!0), r(!1);
|
|
49
|
+
});
|
|
50
|
+
}, g = () => n(() => {
|
|
51
|
+
t(!0), r(!1);
|
|
52
|
+
}), h = () => n(() => {
|
|
53
|
+
t(!0), r(!0);
|
|
54
|
+
});
|
|
55
|
+
return e && (e.addEventListener("load", g), e.addEventListener("error", h), typeof e.decode == "function" && e.decode().then(
|
|
56
|
+
() => n(() => {
|
|
57
|
+
t(!0), r(!1);
|
|
58
|
+
})
|
|
59
|
+
).catch(() => {
|
|
60
|
+
f();
|
|
61
|
+
})), l = requestAnimationFrame(f), m = window.setInterval(f, 250), d = window.setTimeout(() => {
|
|
62
|
+
n(() => {
|
|
63
|
+
e && e.naturalWidth > 0 ? (t(!0), r(!1)) : t(!0);
|
|
64
|
+
});
|
|
65
|
+
}, 1e4), () => {
|
|
66
|
+
cancelAnimationFrame(l), window.clearInterval(m), window.clearTimeout(d), e && (e.removeEventListener("load", g), e.removeEventListener("error", h));
|
|
67
|
+
};
|
|
68
|
+
}, [v]), /* @__PURE__ */ s.jsxs(R, { ...c, className: E("file-thumbnail", c.className), children: [
|
|
69
|
+
p ? /* @__PURE__ */ s.jsx(j, { icon: b(I) }) : /* @__PURE__ */ s.jsx(N, { icon: "progress_activity" }),
|
|
70
|
+
/* @__PURE__ */ s.jsx(
|
|
71
|
+
W,
|
|
45
72
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
73
|
+
ref: a,
|
|
74
|
+
src: v,
|
|
75
|
+
...c,
|
|
76
|
+
onError: (o) => {
|
|
77
|
+
o.currentTarget === a.current && (t(!0), r(!0));
|
|
50
78
|
},
|
|
51
|
-
onLoad: () => {
|
|
52
|
-
|
|
79
|
+
onLoad: (o) => {
|
|
80
|
+
o.currentTarget === a.current && (t(!0), r(!1));
|
|
53
81
|
},
|
|
54
|
-
className:
|
|
82
|
+
className: E({ hidden: !p || L })
|
|
55
83
|
}
|
|
56
84
|
)
|
|
57
85
|
] });
|
|
58
86
|
};
|
|
59
87
|
export {
|
|
60
|
-
|
|
88
|
+
_ as FileThumbnail
|
|
61
89
|
};
|
|
62
90
|
//# sourceMappingURL=FileThumbnail.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileThumbnail.es.js","sources":["../../../../../src/components/FileThumbnail/FileThumbnail.tsx"],"sourcesContent":["import { getMimeTypeIcon, Icon } from '@ynput/ayon-react-components'\nimport { FC, HTMLAttributes, ImgHTMLAttributes, useEffect, useState } from 'react'\nimport styled, { keyframes } from 'styled-components'\nimport clsx from 'clsx'\n\nconst spin = keyframes`\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n`\n\nconst Wrapper = styled.div`\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n\n overflow: hidden;\n\n background-color: var(--md-sys-color-surface-container-low);\n`\n\nconst LoadingIcon = styled(Icon)`\n animation: ${spin} 1s linear infinite;\n opacity: 0.6;\n`\n\nconst Image = styled.img`\n position: absolute;\n inset: 0;\n object-fit: cover;\n background-color: var(--md-sys-color-surface-container-low);\n\n width: 100%;\n height: 100%;\n\n &.hidden {\n opacity: 0;\n }\n`\n\nexport interface FileThumbnailProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onError' | 'onLoad'> {\n src: string\n mimetype?: string\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'onError' | 'onLoad'>\n}\n\nexport const FileThumbnail: FC<FileThumbnailProps> = ({ mimetype = '', src, ...props }) => {\n const [error, setError] = useState(false)\n const [loaded, setLoaded] = useState(false)\n\n useEffect(() => {\n // reset
|
|
1
|
+
{"version":3,"file":"FileThumbnail.es.js","sources":["../../../../../src/components/FileThumbnail/FileThumbnail.tsx"],"sourcesContent":["import { getMimeTypeIcon, Icon } from '@ynput/ayon-react-components'\nimport { FC, HTMLAttributes, ImgHTMLAttributes, useEffect, useState, useRef } from 'react'\nimport styled, { keyframes } from 'styled-components'\nimport clsx from 'clsx'\n\nconst spin = keyframes`\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n`\n\nconst Wrapper = styled.div`\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n\n overflow: hidden;\n\n background-color: var(--md-sys-color-surface-container-low);\n`\n\nconst LoadingIcon = styled(Icon)`\n animation: ${spin} 1s linear infinite;\n opacity: 0.6;\n`\n\nconst Image = styled.img`\n position: absolute;\n inset: 0;\n object-fit: cover;\n background-color: var(--md-sys-color-surface-container-low);\n\n width: 100%;\n height: 100%;\n\n &.hidden {\n opacity: 0;\n }\n`\n\nexport interface FileThumbnailProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onError' | 'onLoad'> {\n src: string\n mimetype?: string\n imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'onError' | 'onLoad'>\n}\n\nexport const FileThumbnail: FC<FileThumbnailProps> = ({ mimetype = '', src, ...props }) => {\n const [error, setError] = useState(false)\n const [loaded, setLoaded] = useState(false)\n const imageRef = useRef<HTMLImageElement | null>(null)\n const loadAttemptRef = useRef(0)\n\n // Robust loading effect: attempt to detect when the image is actually ready.\n // Uses `decode()` when available, falls back to `complete` + `naturalWidth` checks,\n // adds RAF + polling and a watchdog timeout to avoid stuck states.\n useEffect(() => {\n const attemptId = loadAttemptRef.current + 1\n loadAttemptRef.current = attemptId\n\n // reset visible state for this attempt\n setLoaded(false)\n setError(false)\n\n const img = imageRef.current\n let resolved = false\n let frame = 0\n let interval = 0\n let timeout = 0\n\n const resolveOnce = (resolver: () => void) => {\n if (resolved || attemptId !== loadAttemptRef.current) return\n resolved = true\n cancelAnimationFrame(frame)\n window.clearInterval(interval)\n window.clearTimeout(timeout)\n resolver()\n }\n\n const checkComplete = () => {\n if (!img || attemptId !== loadAttemptRef.current) return\n if (img.complete && img.naturalWidth > 0) {\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n })\n }\n }\n\n const onLoad = () =>\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n })\n const onError = () =>\n resolveOnce(() => {\n // keep behavior: mark loaded so placeholder swaps to icon, and flag error\n setLoaded(true)\n setError(true)\n })\n\n if (img) {\n img.addEventListener('load', onLoad)\n img.addEventListener('error', onError)\n\n if (typeof img.decode === 'function') {\n img\n .decode()\n .then(() =>\n resolveOnce(() => {\n setLoaded(true)\n setError(false)\n }),\n )\n .catch(() => {\n checkComplete()\n })\n }\n }\n\n frame = requestAnimationFrame(checkComplete)\n interval = window.setInterval(checkComplete, 250)\n\n timeout = window.setTimeout(() => {\n // Force-resolve after a timeout to avoid infinite spinner states.\n resolveOnce(() => {\n if (img && img.naturalWidth > 0) {\n setLoaded(true)\n setError(false)\n } else {\n setLoaded(true)\n }\n })\n }, 10000)\n\n return () => {\n cancelAnimationFrame(frame)\n window.clearInterval(interval)\n window.clearTimeout(timeout)\n if (img) {\n img.removeEventListener('load', onLoad)\n img.removeEventListener('error', onError)\n }\n }\n }, [src])\n\n return (\n <Wrapper {...props} className={clsx('file-thumbnail', props.className)}>\n {!loaded ? (\n <LoadingIcon icon=\"progress_activity\" />\n ) : (\n <Icon icon={getMimeTypeIcon(mimetype)} />\n )}\n <Image\n ref={imageRef}\n src={src}\n {...props}\n onError={(event) => {\n if (event.currentTarget === imageRef.current) {\n setLoaded(true)\n setError(true)\n }\n }}\n onLoad={(event) => {\n if (event.currentTarget === imageRef.current) {\n setLoaded(true)\n setError(false)\n }\n }}\n className={clsx({ hidden: !loaded || error })}\n />\n </Wrapper>\n )\n}\n"],"names":["spin","keyframes","Wrapper","styled","LoadingIcon","Icon","Image","FileThumbnail","mimetype","src","props","error","setError","useState","loaded","setLoaded","imageRef","useRef","loadAttemptRef","useEffect","attemptId","img","resolved","frame","interval","timeout","resolveOnce","resolver","checkComplete","onLoad","onError","jsxs","clsx","jsx","getMimeTypeIcon","event"],"mappings":"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"}
|