ebta-workflow-prod 4.1.3 → 4.1.5
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/build/activitypack.json +1 -1
- package/build/main.js +5 -5
- package/package.json +3 -2
- package/src/elements/CsColorPicker.tsx +3 -1
- package/src/elements/PanovideoPlayer.tsx +225 -0
- package/src/elements/Timeline.tsx +24 -22
- package/src/index.ts +1 -0
- package/src/styles/mantine-scoped.css +3 -0
- package/ebta-workflow-prod-4.0.24.tgz +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ebta-workflow-prod",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.5",
|
|
5
5
|
"description": "",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"author": "",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@fseehawer/react-circular-slider": "^3.2.7",
|
|
33
33
|
"@mantine/core": "^7.16.0",
|
|
34
34
|
"@mantine/hooks": "^9.5.1",
|
|
35
|
-
"@mui/icons-material": "^5.17.1",
|
|
36
35
|
"@mui/base": "5.0.0-beta.70",
|
|
36
|
+
"@mui/icons-material": "^5.17.1",
|
|
37
37
|
"@mui/material": "^5.17.1",
|
|
38
38
|
"@mui/x-charts": "^7.6.2",
|
|
39
39
|
"@turf/turf": "^6.5.0",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"axios": "^1.7.7",
|
|
45
45
|
"dayjs": "^1.11.23",
|
|
46
46
|
"html2canvas": "^1.4.1",
|
|
47
|
+
"marzipano": "^0.10.2",
|
|
47
48
|
"pannellum-react-update": "^1.0.0",
|
|
48
49
|
"pdf-lib": "^1.17.1",
|
|
49
50
|
"qrcode": "^1.5.4",
|
|
@@ -5,7 +5,8 @@ import { ColorInput, ColorPicker } from '@mantine/core';
|
|
|
5
5
|
type ColorFormat = "hex" | "rgba" | "rgb" | "hsl" | "hsla";
|
|
6
6
|
|
|
7
7
|
import "@mantine/core/styles.css";
|
|
8
|
-
|
|
8
|
+
//Causing shrink
|
|
9
|
+
//import "../styles/mantine-scoped.css";
|
|
9
10
|
import Color from "@arcgis/core/Color";
|
|
10
11
|
|
|
11
12
|
|
|
@@ -79,6 +80,7 @@ function CsColorPickerV2(props: CsColorPickerV2Props): React.ReactElement {
|
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
return (
|
|
83
|
+
|
|
82
84
|
<MantineProvider withStaticClasses withGlobalClasses classNamesPrefix="ebta-mantime">
|
|
83
85
|
{componentType === "ColorInput" ? (
|
|
84
86
|
<ColorInput
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { FormElementProps, FormElementRegistration } from "@vertigis/workflow";
|
|
3
|
+
import Marzipano from "marzipano";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The generic type argument provided to `FormElementProps<T>` controls the type
|
|
8
|
+
* of `props.value` and `props.setValue(value)`. If your element doesn't need a
|
|
9
|
+
* `value`, you can omit the type argument.
|
|
10
|
+
*
|
|
11
|
+
* You can also declare additional public properties of your element by adding
|
|
12
|
+
* properties to this interface. The properties will be managed by the Workflow
|
|
13
|
+
* runtime, and passed in as `props`. You can update the properties by using
|
|
14
|
+
* `props.setProperty(key, value)`.
|
|
15
|
+
*/
|
|
16
|
+
interface PanovideoPlayerProps extends FormElementProps<string> {}
|
|
17
|
+
|
|
18
|
+
class VideoAsset extends (Marzipano as any).util.Emitter {
|
|
19
|
+
private _element: HTMLVideoElement;
|
|
20
|
+
|
|
21
|
+
constructor(videoElement: HTMLVideoElement) {
|
|
22
|
+
super();
|
|
23
|
+
this._element = videoElement;
|
|
24
|
+
// Marzipano polls isDynamic()/emits('change') to know when to redraw.
|
|
25
|
+
this._element.addEventListener("timeupdate", () => {
|
|
26
|
+
this.emit("change");
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
element(): HTMLVideoElement {
|
|
31
|
+
return this._element;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
width(): number {
|
|
35
|
+
return this._element.videoWidth;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
height(): number {
|
|
39
|
+
return this._element.videoHeight;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isDynamic(): boolean {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A Workflow element built using React.
|
|
49
|
+
* @displayName PanovideoPlayer
|
|
50
|
+
* @description Marzipano library panoramic video player
|
|
51
|
+
* @param props The props that will be provided by the Workflow runtime.
|
|
52
|
+
*/
|
|
53
|
+
function PanovideoPlayer(props: PanovideoPlayerProps): React.ReactElement {
|
|
54
|
+
const { setValue, value } = props;
|
|
55
|
+
|
|
56
|
+
const containerRef = React.useRef<HTMLDivElement | null>(null);
|
|
57
|
+
const videoElRef = React.useRef<HTMLVideoElement | null>(null);
|
|
58
|
+
const viewerRef = React.useRef<any>(null);
|
|
59
|
+
const [isPlaying, setIsPlaying] = React.useState(false);
|
|
60
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
61
|
+
|
|
62
|
+
// (Re)builds the Marzipano viewer whenever the video URL (props.value) changes.
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
if (!value || !containerRef.current) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setError(null);
|
|
69
|
+
|
|
70
|
+
// Create a hidden video element to drive the panorama. We don't
|
|
71
|
+
// attach it to the DOM directly - Marzipano reads frames from it
|
|
72
|
+
// and draws them into its own WebGL canvas.
|
|
73
|
+
const videoEl = document.createElement("video");
|
|
74
|
+
videoEl.crossOrigin = "anonymous";
|
|
75
|
+
videoEl.loop = true;
|
|
76
|
+
videoEl.muted = true; // required for autoplay in most browsers
|
|
77
|
+
videoEl.playsInline = true;
|
|
78
|
+
videoEl.src = value;
|
|
79
|
+
videoElRef.current = videoEl;
|
|
80
|
+
|
|
81
|
+
let viewer: any;
|
|
82
|
+
let cancelled = false;
|
|
83
|
+
|
|
84
|
+
const onLoadedMetadata = () => {
|
|
85
|
+
if (cancelled || !containerRef.current) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
viewer = new (Marzipano as any).Viewer(containerRef.current);
|
|
91
|
+
viewerRef.current = viewer;
|
|
92
|
+
|
|
93
|
+
const asset = new VideoAsset(videoEl);
|
|
94
|
+
const source = (Marzipano as any).SingleAssetSource.fromAsset(asset);
|
|
95
|
+
|
|
96
|
+
const geometry = new (Marzipano as any).EquirectGeometry([
|
|
97
|
+
{ width: videoEl.videoWidth || 1920 },
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const limiter = (Marzipano as any).RectilinearView.limit.traditional(
|
|
101
|
+
1024,
|
|
102
|
+
(100 * Math.PI) / 180
|
|
103
|
+
);
|
|
104
|
+
const view = new (Marzipano as any).RectilinearView(
|
|
105
|
+
{ yaw: 0, pitch: 0, fov: Math.PI / 2 },
|
|
106
|
+
limiter
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const scene = viewer.createScene({
|
|
110
|
+
source,
|
|
111
|
+
geometry,
|
|
112
|
+
view,
|
|
113
|
+
pinFirstLevel: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
scene.switchTo();
|
|
117
|
+
|
|
118
|
+
videoEl
|
|
119
|
+
.play()
|
|
120
|
+
.then(() => setIsPlaying(true))
|
|
121
|
+
.catch(() => {
|
|
122
|
+
// Autoplay was blocked - user needs to tap play.
|
|
123
|
+
setIsPlaying(false);
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
setError(
|
|
127
|
+
e instanceof Error ? e.message : "Failed to initialize panoramic video."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const onVideoError = () => {
|
|
133
|
+
if (!cancelled) {
|
|
134
|
+
setError("Unable to load video from the provided URL.");
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
videoEl.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
139
|
+
videoEl.addEventListener("error", onVideoError);
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
cancelled = true;
|
|
143
|
+
videoEl.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
144
|
+
videoEl.removeEventListener("error", onVideoError);
|
|
145
|
+
videoEl.pause();
|
|
146
|
+
videoEl.removeAttribute("src");
|
|
147
|
+
videoEl.load();
|
|
148
|
+
|
|
149
|
+
if (viewerRef.current) {
|
|
150
|
+
try {
|
|
151
|
+
// Not all Marzipano versions expose destroy(); guard defensively.
|
|
152
|
+
viewerRef.current.destroy?.();
|
|
153
|
+
} catch {
|
|
154
|
+
// no-op
|
|
155
|
+
}
|
|
156
|
+
viewerRef.current = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (containerRef.current) {
|
|
160
|
+
containerRef.current.innerHTML = "";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}, [value]);
|
|
164
|
+
|
|
165
|
+
const togglePlayback = () => {
|
|
166
|
+
const videoEl = videoElRef.current;
|
|
167
|
+
if (!videoEl) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (videoEl.paused) {
|
|
171
|
+
videoEl.play().then(() => setIsPlaying(true));
|
|
172
|
+
} else {
|
|
173
|
+
videoEl.pause();
|
|
174
|
+
setIsPlaying(false);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
180
|
+
<input
|
|
181
|
+
type="text"
|
|
182
|
+
placeholder="Enter 360° equirectangular video URL"
|
|
183
|
+
onChange={(event) => setValue(event.currentTarget.value)}
|
|
184
|
+
value={value ?? ""}
|
|
185
|
+
style={{ width: "100%" }}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
{error && (
|
|
189
|
+
<div style={{ color: "red", fontSize: 12 }}>{error}</div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{value && (
|
|
193
|
+
<div style={{ position: "relative" }}>
|
|
194
|
+
<div
|
|
195
|
+
ref={containerRef}
|
|
196
|
+
style={{
|
|
197
|
+
width: "100%",
|
|
198
|
+
height: 400,
|
|
199
|
+
background: "#000",
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
onClick={togglePlayback}
|
|
205
|
+
style={{
|
|
206
|
+
position: "absolute",
|
|
207
|
+
bottom: 8,
|
|
208
|
+
left: 8,
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{isPlaying ? "Pause" : "Play"}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const PanovideoPlayerElementRegistration: FormElementRegistration<PanovideoPlayerProps> = {
|
|
220
|
+
component: PanovideoPlayer,
|
|
221
|
+
getInitialProperties: () => ({ value: "" }),
|
|
222
|
+
id: "PanovideoPlayer",
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export default PanovideoPlayerElementRegistration;
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { FormElementProps, FormElementRegistration } from "@vertigis/workflow";
|
|
3
3
|
import { Timeline as MainTimeLine, Text } from '@mantine/core';
|
|
4
4
|
import { MantineProvider } from '@mantine/core';
|
|
5
|
-
|
|
5
|
+
//import "../styles/mantine-scoped.css";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The generic type argument provided to `FormElementProps<T>` controls the type
|
|
@@ -38,30 +38,32 @@ interface TimelineProps extends FormElementProps<string> {
|
|
|
38
38
|
* @param props The props that will be provided by the Workflow runtime.
|
|
39
39
|
*/
|
|
40
40
|
function Timeline(props: TimelineProps): React.ReactElement {
|
|
41
|
-
const { itemDetails,iconSize,lineWidth,active,color,reverseActive } = props;
|
|
41
|
+
const { itemDetails, iconSize, lineWidth, active, color, reverseActive } = props;
|
|
42
42
|
iconSize * 0.6
|
|
43
43
|
return (
|
|
44
|
-
<MantineProvider>
|
|
45
|
-
<MainTimeLine active={active} bulletSize={iconSize} lineWidth={lineWidth} color={color} reverseActive={reverseActive}>
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
<MantineProvider>
|
|
46
|
+
<MainTimeLine active={active} bulletSize={iconSize} lineWidth={lineWidth} color={color} reverseActive={reverseActive}>
|
|
47
|
+
|
|
48
|
+
{itemDetails.map((item, index) => {
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
|
|
52
|
+
<MainTimeLine.Item
|
|
53
|
+
key={index}
|
|
54
|
+
title={item.title}
|
|
55
|
+
lineVariant={item.lineVariant}
|
|
56
|
+
>
|
|
57
|
+
|
|
58
|
+
{item.description ? <Text c="dimmed" size="sm">{item.description}</Text> : undefined}
|
|
59
|
+
{item.time ? <Text size="xs" mt={4}>{item.time}</Text> : undefined}
|
|
60
|
+
|
|
61
|
+
</MainTimeLine.Item>
|
|
62
|
+
)
|
|
63
|
+
})}
|
|
64
|
+
</MainTimeLine>
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
</MantineProvider>
|
|
65
67
|
);
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -72,7 +74,7 @@ const TimelineElementRegistration: FormElementRegistration<TimelineProps> = {
|
|
|
72
74
|
lineWidth: 1,
|
|
73
75
|
iconSize: 40,
|
|
74
76
|
active: 2,
|
|
75
|
-
color:'var(--buttonIcon)',
|
|
77
|
+
color: 'var(--buttonIcon)',
|
|
76
78
|
reverseActive: true,
|
|
77
79
|
itemDetails: [
|
|
78
80
|
{
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { default as HTMLextractorActivity } from "./activities/HTMLextractor";
|
|
|
18
18
|
export { default as AttributeFilterElementRegistration } from "./elements/AttributeFilter"
|
|
19
19
|
export { default as ItemSearcher_V2DEVRegistration } from "./elements/ItemSearcher_v2";
|
|
20
20
|
export { default as Viewer360Registration } from "./elements/Viewer360";
|
|
21
|
+
export { default as PanovideoPlayerRegistration} from "./elements/PanovideoPlayer";
|
|
21
22
|
export { default as TableRadioGroupElementRegistration } from "./elements/TableRadioGoup";
|
|
22
23
|
export { MuiSingleToggleButtonGroupElementRegistration, MuiMultipleToggleButtonGroupElementRegistration } from "./elements/ToggleButtonGroup";
|
|
23
24
|
|
|
Binary file
|