diff --git a/examples/breakout-rooms/src/components/BreakoutManager/BreakoutManager.tsx b/examples/breakout-rooms/src/components/BreakoutManager/BreakoutManager.tsx index 109b9590c..723cc05bf 100644 --- a/examples/breakout-rooms/src/components/BreakoutManager/BreakoutManager.tsx +++ b/examples/breakout-rooms/src/components/BreakoutManager/BreakoutManager.tsx @@ -6,6 +6,7 @@ import { Participant, Room } from "../../types"; import { Frame, Json, OnlineUserInfo } from "@mirohq/websdk-types"; import { useBreakout, + useFeatureCheck, useOnlineUsers, useSelectedItems, useTimer, @@ -29,16 +30,21 @@ export const BreakoutManager: React.FC = () => { const [selectedRoom, setSelectedRoom] = React.useState(); const [duration, setTimerDuration] = React.useState(); const [currentTime, setCurrentTime] = React.useState(0); - const [canUseTimer] = React.useState(false); + const canUseTimer = useFeatureCheck("timer"); const onTimerStop = React.useCallback(() => { service.endSession(); }, [breakout?.id]); + const onTick = React.useCallback( + (timestamp: number) => setCurrentTime(timestamp), + [], + ); + const timer = useTimer({ duration: duration ?? DEFAULT_TIME, onStop: onTimerStop, - onTick: (timestamp) => setCurrentTime(timestamp), + onTick, }); const participantIds = rooms @@ -256,7 +262,7 @@ export const BreakoutManager: React.FC = () => { > Stop session {canUseTimer && timer.state === "started" - ? `(${formatDisplayTime(currentTime)})` + ? ` (${formatDisplayTime(timer.restDuration)})` : null} ) : ( diff --git a/examples/breakout-rooms/src/components/RoomConfig/RoomConfig.tsx b/examples/breakout-rooms/src/components/RoomConfig/RoomConfig.tsx index cc3e7b18a..17f448920 100644 --- a/examples/breakout-rooms/src/components/RoomConfig/RoomConfig.tsx +++ b/examples/breakout-rooms/src/components/RoomConfig/RoomConfig.tsx @@ -64,7 +64,7 @@ export const RoomConfig: React.FunctionComponent = ({ {room.participants.length ? (
{room.participants.map((participant) => ( - + ))}
) : null} diff --git a/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.css b/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.css index a91646677..49bc29d37 100644 --- a/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.css +++ b/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.css @@ -19,6 +19,11 @@ .breakout-controls { width: 100%; display: flex; - justify-content: space-between; + justify-content: flex-start; + gap: 1em; align-items: center; } + +.breakout-controls .facilitator-controls { + margin-left: auto; +} diff --git a/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.tsx b/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.tsx index eee5e95aa..63d173ea1 100644 --- a/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.tsx +++ b/examples/breakout-rooms/src/components/RoomsManager/RoomsManager.tsx @@ -81,21 +81,27 @@ export const RoomsManager: React.FC = ({ )} {isFacilitator && ( - - - - - - <> +
+ + + + + + + onReleaseFacilitator()}> Release facilitator role - - - + + +
)} diff --git a/examples/breakout-rooms/src/components/WaitingIcon.tsx b/examples/breakout-rooms/src/components/WaitingIcon.tsx index 5c71a64b9..361310a26 100644 --- a/examples/breakout-rooms/src/components/WaitingIcon.tsx +++ b/examples/breakout-rooms/src/components/WaitingIcon.tsx @@ -10,9 +10,9 @@ export const WaitingIcon = () => { > ); diff --git a/examples/breakout-rooms/src/hooks.tsx b/examples/breakout-rooms/src/hooks.tsx index 049a6b430..6d98c7530 100644 --- a/examples/breakout-rooms/src/hooks.tsx +++ b/examples/breakout-rooms/src/hooks.tsx @@ -7,6 +7,7 @@ import { TimerEvent, UserInfo, Session, + BoardFeature, } from "@mirohq/websdk-types"; import { @@ -476,33 +477,34 @@ export const useBreakout = () => { export const useTimer = (opts: TimerOpts) => { const [state, setState] = React.useState("idle"); + const [restDuration, setRestDuration] = React.useState(opts.duration); const interval = React.useRef>(); const tick = convertTime(opts.interval ?? 1_000, "milliseconds"); const start = React.useCallback(async () => { - const isStarted = await miro.board.experimental.timer.isStarted(); + const isStarted = await miro.board.timer.isStarted(); if (isStarted) { throw new Error("Timer is already running"); } log("[TIMER:START]", { opts }); - await miro.board.experimental.timer.start(opts.duration); + await miro.board.timer.start(opts.duration); }, [miro, opts.duration]); const pause = React.useCallback(async () => { - const isStarted = await miro.board.experimental.timer.isStarted(); + const isStarted = await miro.board.timer.isStarted(); if (isStarted) { - await miro.board.experimental.timer.pause(); + await miro.board.timer.pause(); } else { throw new Error("Timer is not running"); } }, [miro]); const stop = React.useCallback(async () => { - const isStarted = await miro.board.experimental.timer.isStarted(); + const isStarted = await miro.board.timer.isStarted(); if (isStarted) { - await miro.board.experimental.timer.stop(); + await miro.board.timer.stop(); } }, [miro]); @@ -511,7 +513,7 @@ export const useTimer = (opts: TimerOpts) => { setState("started"); opts.onStart?.(); - let timeStart = timer.startedAt; + let timeStart = Date.now(); const timeEnd = timeStart + convertTime(timer.restDuration, "milliseconds"); @@ -520,8 +522,9 @@ export const useTimer = (opts: TimerOpts) => { timeStart, timeEnd, opts, - startFormatted: new Date(timeStart).toTimeString(), - endFormatted: new Date(timeEnd).toTimeString(), + duration: formatDisplayTime(timer.restDuration), + startFormatted: formatDisplayTime(timeStart), + endFormatted: formatDisplayTime(timeEnd), }); clearInterval(interval.current); @@ -544,6 +547,7 @@ export const useTimer = (opts: TimerOpts) => { } opts.onTick?.(restDuration); + setRestDuration(restDuration); }, tick); }, [opts.onStart, opts.onTick, stop], @@ -556,7 +560,7 @@ export const useTimer = (opts: TimerOpts) => { setState("ended"); opts.onStop?.(); }, - [interval, interval, opts.onStop], + [interval, opts.onStop], ); const handleTimerUpdate = React.useCallback(async (event: TimerEvent) => { @@ -564,8 +568,10 @@ export const useTimer = (opts: TimerOpts) => { switch (event.timer.status) { case "STARTED": setState("started"); + handleTimerStart(event); break; case "PAUSED": + clearInterval(interval.current); setState("paused"); break; case "STOPPED": @@ -576,30 +582,51 @@ export const useTimer = (opts: TimerOpts) => { React.useEffect(() => { const fetchCurrent = async () => { - const isStarted = await miro.board.experimental.timer.isStarted(); - log("[TIMER:CURRENT]", { isStarted }); - setState(isStarted ? "started" : "idle"); + const currentState = await miro.board.timer.get(); + log("[TIMER:CURRENT]", { currentState }); + + if (currentState.status === "STARTED") { + handleTimerStart({ timer: currentState }); + setState("started"); + } + + setRestDuration(currentState.restDuration); }; fetchCurrent(); }, []); React.useEffect(() => { - miro.board.ui.on("experimental:timer:start", handleTimerStart); - miro.board.ui.on("experimental:timer:finish", handleTimerFinish); - miro.board.ui.on("experimental:timer:update", handleTimerUpdate); + miro.board.timer.on("start", handleTimerStart); + miro.board.timer.on("finish", handleTimerFinish); + miro.board.timer.on("update", handleTimerUpdate); return () => { - miro.board.ui.off("experimental:timer:start", handleTimerStart); - miro.board.ui.off("experimental:timer:finish", handleTimerFinish); - miro.board.ui.off("experimental:timer:update", handleTimerUpdate); + miro.board.timer.off("start", handleTimerStart); + miro.board.timer.off("finish", handleTimerFinish); + miro.board.timer.off("update", handleTimerUpdate); }; }, [handleTimerStart, handleTimerFinish, handleTimerUpdate]); return { state, + restDuration, start, stop, pause, }; }; + +export const useFeatureCheck = (feature: BoardFeature): boolean => { + const [canUse, setCanUse] = React.useState(false); + + React.useEffect(() => { + const fetch = async () => { + const canIUse = await miro.board.canUse(feature); + setCanUse(canIUse); + }; + fetch(); + }, [setCanUse, feature]); + + return canUse; +}; diff --git a/examples/breakout-rooms/src/utils.ts b/examples/breakout-rooms/src/utils.ts index 406c9e4c3..d5fb77ca0 100644 --- a/examples/breakout-rooms/src/utils.ts +++ b/examples/breakout-rooms/src/utils.ts @@ -63,8 +63,8 @@ export const formatDisplayTime = ( ): string => { const timestamp = convertTime(time, "seconds", unit); - const minutes = Math.floor(timestamp / 60); - const seconds = Math.floor(timestamp % 60); + const minutes = Math.round(timestamp / 60); + const seconds = Math.round(timestamp % 60); return [minutes, seconds] .map((unit) => unit.toString().padStart(2, "0")) diff --git a/yarn.lock b/yarn.lock index 9a58b70a8..b4bae1689 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,6 +1064,13 @@ node-fetch "^2.6.9" typedoc "0.23.24" +"@mirohq/websdk-types@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@mirohq/websdk-types/-/websdk-types-2.10.0.tgz#acb7335957be62290ae651ff6c23b29b24608f7f" + integrity sha512-JMBxUBZW1Pz/NeOuoXhpPGp7DcmkIOICpoSramhNpx5OFnzLed8ECZjQt13axIRpMqQ6IKcGQGGRmOh9zYIHhw== + dependencies: + typescript ">=4.6.3 || ~5" + "@mirohq/websdk-types@^2.0.0": version "2.3.0" resolved "https://registry.npmjs.org/@mirohq/websdk-types/-/websdk-types-2.3.0.tgz" @@ -1143,6 +1150,11 @@ resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.1.tgz#337f5f3d325250f91ed23d783cbf8297800ee7d3" integrity sha512-CIMWiOTyflFn/GFx33iYXkgLSQsMQZV4jB91qaj/TfxGaGOXxn8C1j72TaUSPIyN7ziS/AYG46kGmnvuk1oOpg== +"@next/env@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" + integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== + "@next/eslint-plugin-next@12.3.1": version "12.3.1" resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-12.3.1.tgz" @@ -1155,46 +1167,91 @@ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.1.tgz#dc21234afce27910a8d141e6a7ab5e821725dc94" integrity sha512-Bcd0VFrLHZnMmJy6LqV1CydZ7lYaBao8YBEdQUVzV8Ypn/l5s//j5ffjfvMzpEQ4mzlAj3fIY+Bmd9NxpWhACw== +"@next/swc-darwin-arm64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz#b15d139d8971360fca29be3bdd703c108c9a45fb" + integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== + "@next/swc-darwin-x64@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.1.tgz#29591ac96cc839903918621cf2d8f79c40cba3ca" integrity sha512-uvTZrZa4D0bdWa1jJ7X1tBGIxzpqSnw/ATxWvoRO9CVBvXSx87JyuISY+BWsfLFF59IRodESdeZwkWM2l6+Kjg== +"@next/swc-darwin-x64@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" + integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== + "@next/swc-linux-arm64-gnu@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.1.tgz#349ce2714dc87d1db6911758652f3b2b0810dd6f" integrity sha512-/52ThlqdORPQt3+AlMoO+omicdYyUEDeRDGPAj86ULpV4dg+/GCFCKAmFWT0Q4zChFwsAoZUECLcKbRdcc0SNg== +"@next/swc-linux-arm64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" + integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== + "@next/swc-linux-arm64-musl@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.1.tgz#3f8bc223db9100887ffda998ad963820f39d944b" integrity sha512-L4qNXSOHeu1hEAeeNsBgIYVnvm0gg9fj2O2Yx/qawgQEGuFBfcKqlmIE/Vp8z6gwlppxz5d7v6pmHs1NB6R37w== +"@next/swc-linux-arm64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" + integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== + "@next/swc-linux-x64-gnu@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.1.tgz#4503d43d27aa178efb4a5c9efe229faae37d67a8" integrity sha512-QVvMrlrFFYvLtABk092kcZ5Mzlmsk2+SV3xYuAu8sbTuIoh0U2+HGNhVklmuYCuM3DAAxdiMQTNlRQmNH11udw== +"@next/swc-linux-x64-gnu@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" + integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== + "@next/swc-linux-x64-musl@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.1.tgz#a6e81f0df0be2fac78392705f487036695cf7b10" integrity sha512-bBnr+XuWc28r9e8gQ35XBtyi5KLHLhTbEvrSgcWna8atI48sNggjIK8IyiEBO3KIrcUVXYkldAzGXPEYMnKt1g== +"@next/swc-linux-x64-musl@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" + integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== + "@next/swc-win32-arm64-msvc@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.1.tgz#9d8517fe1dd6aa348c7be36b933ad8195f961294" integrity sha512-EQGeE4S5c9v06jje9gr4UlxqUEA+zrsgPi6kg9VwR+dQHirzbnVJISF69UfKVkmLntknZJJI9XpWPB6q0Z7mTg== +"@next/swc-win32-arm64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" + integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== + "@next/swc-win32-ia32-msvc@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.1.tgz#42e711d00642f538edaabf9535545697d093b2f7" integrity sha512-1y31Q6awzofVjmbTLtRl92OX3s+W0ZfO8AP8fTnITcIo9a6ATDc/eqa08fd6tSpFu6IFpxOBbdevOjwYTGx/AQ== +"@next/swc-win32-ia32-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" + integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== + "@next/swc-win32-x64-msvc@13.5.1": version "13.5.1" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.1.tgz#34bc139cf37704d595fe84eb803b1578a539e35e" integrity sha512-+9XBQizy7X/GuwNegq+5QkkxAPV7SBsIwapVRQd9WSvvU20YO23B3bZUpevdabi4fsd25y9RJDDncljy/V54ww== +"@next/swc-win32-x64-msvc@13.5.6": + version "13.5.6" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" + integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -5822,6 +5879,29 @@ next@^13.5.1: "@next/swc-win32-ia32-msvc" "13.5.1" "@next/swc-win32-x64-msvc" "13.5.1" +next@^13.5.2, next@^13.5.3, next@^13.5.6: + version "13.5.6" + resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" + integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== + dependencies: + "@next/env" "13.5.6" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001406" + postcss "8.4.31" + styled-jsx "5.1.1" + watchpack "2.4.0" + optionalDependencies: + "@next/swc-darwin-arm64" "13.5.6" + "@next/swc-darwin-x64" "13.5.6" + "@next/swc-linux-arm64-gnu" "13.5.6" + "@next/swc-linux-arm64-musl" "13.5.6" + "@next/swc-linux-x64-gnu" "13.5.6" + "@next/swc-linux-x64-musl" "13.5.6" + "@next/swc-win32-arm64-msvc" "13.5.6" + "@next/swc-win32-ia32-msvc" "13.5.6" + "@next/swc-win32-x64-msvc" "13.5.6" + node-domexception@1.0.0, node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" @@ -6140,7 +6220,7 @@ postcss@8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.13, postcss@^8.4.14, postcss@^8.4.18: +postcss@8.4.31, postcss@^8.4.13, postcss@^8.4.14, postcss@^8.4.18: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==