Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@robojs/sync - <SyncBox> and <SyncZone> components #328

Open
Pkmmte opened this issue Oct 21, 2024 — with Volta.net · 1 comment
Open

@robojs/sync - <SyncBox> and <SyncZone> components #328

Pkmmte opened this issue Oct 21, 2024 — with Volta.net · 1 comment
Labels

Comments

Copy link
Member

Pkmmte commented Oct 21, 2024

Depends on #329
Points: 5


SyncBox

Two new components to abstract UI logic in a synchronous manner. Similar to React gesture libraries except synchronized across clients.

Example usage:

<SyncBox drag id={['block']} style={styles.block}>
  <span>Look at me I'm being dragged!</span>
<SyncBox/>

A <SyncBox> is more than just a draggable box. In fact, the draggability may be disabled in extra for fine grained control via either an imperative API or explicit positioning.

interface CharacterProps {
  position: {
    x: number
    y: number
  }
}

const Character = (props: CharacterProps) => {
  const { position } = props

  return (
    <SyncBox id={['character']} position={position} style={styles.block}>
       <span>Look at me I'm being moved!</span>
    <SyncBox/>
  )
}

The id prop is the usual dependency array.

SyncBox API

Sync box can also be referenced and used imperatively.

const Character = (props: CharacterProps) => {
  const ref = useRef<SyncBox>(null)

	// Control the box using the keyboard
	useEffect(() => {
		const handleKeyDown = (event: KeyboardEvent) => {
			if (event.key === 'ArrowLeft') {
				ref.current?.setPosition((prev) => ({ x: prev.x - 1, y: prev.y }))
			} else if (event.key === 'ArrowRight') {
				ref.current?.setPosition((prev) => ({ x: prev.x + 1, y: prev.y }))
			} else if (event.key === 'ArrowUp') {
				ref.current?.setPosition((prev) => ({ x: prev.x, y: prev.y - 1 }))
			} else if (event.key === 'ArrowDown') {
				ref.current?.setPosition((prev) => ({ x: prev.x, y: prev.y + 1 }))
			}
		}

		window.addEventListener('keydown', handleKeyDown)
	}, [])

  return (
    <SyncBox ref={ref} id={['character']} style={styles.block}>
       <span>Look at me I'm being moved imperatively!</span>
    <SyncBox/>
  )
}

SyncZone

To avoid large arrays, we can use <SyncZone> to have all <SyncBox> inherit from.

<div>
  <SyncZone id={['space-1']}>
    <SyncBox drag id={['block']} style={styles.block}>
       <span>Look at me I'm being dragged!</span>
    <SyncBox/>
  </SyncZone>
  <SyncZone id={['space-2']}>
    <SyncBox drag id={['block']} style={styles.block}>
       <span>Look at me I'm being dragged!</span>
    <SyncBox/>
  </SyncZone>
</div>

This has the side benefit of allowing component reusability without worrying about dependency array conflicts.

const Character = () => {
  return (
    <SyncBox drag id={['character']} style={styles.block}>
       <span>Look at me I'm being dragged!</span>
    <SyncBox/>
  )
}

const Game = () => {
  return (
    <div>
      <SyncZone id={['zone-1']}>
        <Character />
      </SyncZone>
      <SyncZone id={['zone-2']}>
        <Character />
      </SyncZone>
    </div>
  )
}

A <SyncZone> may also inherit hosts. This allows all contexts inside to have the same host, no matter who mounted child components earlier. A use case would be defining one user as the "primary" player by letting them enter a match first. By being the first to reach the <SyncZone> context, they are indefinitely set as the "host" for all sub-contexts until they either resign control or the server decides to do so.

You may either specify a direct host via the host prop or let the first user to enter the zone be the host by setting hostRules to 'first'. The latter is useful for games where the first player to enter a match is the host, essentially allowing you to define a "host" without needing to know who it is beforehand.

const Game = () => {
	return (
		<div>
			<SyncZone id={['zone-1']} hostRules="first">
				<Character />
			</SyncZone>
			<SyncZone id={['zone-2']} host="player-2">
				<Character />
			</SyncZone>
		</div>
	)
}

SyncZone API

You may clear or override the host by using the imperative API on the server side.

import { RoboResponse } from '@robojs/server'
import { SyncServer } from '@robojs/sync/server.js'

export default async (request, reply) => {
	const { hostId, namespace } = request.params
	const zone = SyncServer.getZone(namespace)
	zone.setHost(hostId ?? null)

	return RoboResponse.json({ success: true })
}
@Pkmmte Pkmmte changed the title @robojs/sync - <SyncBlock> and <SyncZone> components @robojs/sync - <SyncBox> and <SyncZone> components Oct 21, 2024
@Pkmmte Pkmmte added enhancement New feature or request good first issue Good for newcomers plugin hacktoberfest labels Oct 21, 2024 — with Volta.net
@Rishi-0007
Copy link

Hi @Pkmmte,

I’d like to contribute to issues #328 and #329 by implementing the <SyncBox>, <SyncZone>, useSyncBroadcast, and useSyncContext. Could you please assign these to me? I'd also appreciate any guidance on getting started, as I’m new to this repository.

Thanks!

@Pkmmte Pkmmte removed the good first issue Good for newcomers label Oct 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants