Skip to content

Commit

Permalink
feat(client-example): presence-tracker using IndependentMap
Browse files Browse the repository at this point in the history
+ set package module type to "module"
  • Loading branch information
jason-ha committed May 15, 2024
1 parent 691400e commit 55f8b04
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 64 deletions.
9 changes: 8 additions & 1 deletion examples/apps/presence-tracker/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@ module.exports = {
require.resolve("@fluidframework/eslint-config-fluid/minimal-deprecated"),
"prettier",
],
rules: {},
rules: {
"import/no-internal-modules": [
"error",
{
allow: ["@fluid-experimental/*/alpha"],
},
],
},
};
1 change: 1 addition & 0 deletions examples/apps/presence-tracker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dependencies": {
"@fluid-example/example-utils": "workspace:~",
"@fluid-experimental/data-objects": "workspace:~",
"@fluid-experimental/independent-state": "workspace:~",
"@fluid-internal/client-utils": "workspace:~",
"@fluidframework/container-definitions": "workspace:~",
"@fluidframework/container-runtime-definitions": "workspace:~",
Expand Down
80 changes: 26 additions & 54 deletions examples/apps/presence-tracker/src/MouseTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
* Licensed under the MIT License.
*/

import { Signaler } from "@fluid-experimental/data-objects";
// import { Signaler } from "@fluid-experimental/data-objects";
import {
type ClientId,
type IndependentMap,
Latest,
type LatestValueManager,
} from "@fluid-experimental/independent-state/alpha";
import type { IEvent } from "@fluidframework/core-interfaces";
import { TypedEventEmitter } from "@fluid-internal/client-utils";
import { IEvent } from "@fluidframework/core-interfaces";
import { IMember, IServiceAudience } from "fluid-framework";
import type { IMember, IServiceAudience } from "fluid-framework";

export interface IMouseTrackerEvents extends IEvent {
(event: "mousePositionChanged", listener: () => void): void;
Expand All @@ -18,94 +24,60 @@ export interface IMousePosition {
}

export interface IMouseSignalPayload {
userId: string;
userId?: string;
pos: IMousePosition;
}

export class MouseTracker extends TypedEventEmitter<IMouseTrackerEvents> {
private static readonly mouseSignalType = "positionChanged";
private readonly cursor: LatestValueManager<IMousePosition>;

/**
* Local map of mouse position status for clients
*
* ```
* Map<userId, Map<clientid, position>>
* Map<ClientId, IMousePosition>
* ```
*/
private readonly posMap = new Map<string, Map<string, IMousePosition>>();

private readonly onMouseSignalFn = (clientId: string, payload: IMouseSignalPayload) => {
const userId: string = payload.userId;
const position: IMousePosition = payload.pos;

let clientIdMap = this.posMap.get(userId);
if (clientIdMap === undefined) {
clientIdMap = new Map<string, IMousePosition>();
this.posMap.set(userId, clientIdMap);
}
clientIdMap.set(clientId, position);
this.emit("mousePositionChanged");
};
private readonly posMap = new Map<ClientId, IMousePosition>();

constructor(
public readonly audience: IServiceAudience<IMember>,
private readonly signaler: Signaler,
// eslint-disable-next-line @typescript-eslint/ban-types
map: IndependentMap<{}>,
) {
super();

map.add("cursor", Latest({ x: 0, y: 0 }));
this.cursor = map.cursor;

this.audience.on("memberRemoved", (clientId: string, member: IMember) => {
const clientIdMap = this.posMap.get(member.id);
if (clientIdMap !== undefined) {
clientIdMap.delete(clientId);
if (clientIdMap.size === 0) {
this.posMap.delete(member.id);
}
}
this.posMap.delete(clientId);
this.emit("mousePositionChanged");
});

this.signaler.on("error", (error) => {
this.emit("error", error);
this.cursor.on("updated", ({ clientId, value }) => {
this.posMap.set(clientId, value);
this.emit("mousePositionChanged");
});
this.signaler.onSignal(
MouseTracker.mouseSignalType,
(clientId: string, local: boolean, payload: IMouseSignalPayload) => {
this.onMouseSignalFn(clientId, payload);
},
);
window.addEventListener("mousemove", (e) => {
const position: IMousePosition = {
// Alert all connected clients that there has been a change to a client's mouse position
this.cursor.local = {
x: e.clientX,
y: e.clientY,
};
this.sendMouseSignal(position);
});
}

/**
* Alert all connected clients that there has been a change to a client's mouse position
*/
private sendMouseSignal(position: IMousePosition) {
this.signaler.submitSignal(MouseTracker.mouseSignalType, {
userId: this.audience.getMyself()?.id,
pos: position,
});
}

public getMousePresences(): Map<string, IMousePosition> {
const statuses: Map<string, IMousePosition> = new Map<string, IMousePosition>();
this.audience.getMembers().forEach((member, userId) => {
this.audience.getMembers().forEach((member) => {
member.connections.forEach((connection) => {
const position = this.getMousePresenceForUser(userId, connection.id);
const position = this.posMap.get(connection.id);
if (position !== undefined) {
statuses.set((member as any).userName, position);
}
});
});
return statuses;
}

public getMousePresenceForUser(userId: string, clientId: string): IMousePosition | undefined {
return this.posMap.get(userId)?.get(clientId);
}
}
36 changes: 27 additions & 9 deletions examples/apps/presence-tracker/src/containerCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { ModelContainerRuntimeFactory, getDataStoreEntryPoint } from "@fluid-example/example-utils";
import { Signaler } from "@fluid-experimental/data-objects";
import { IndependentMapFactory } from "@fluid-experimental/independent-state/alpha";
import { IContainer } from "@fluidframework/container-definitions/internal";
import { IContainerRuntime } from "@fluidframework/container-runtime-definitions/internal";
import { createServiceAudience } from "@fluidframework/fluid-static/internal";
Expand All @@ -27,33 +28,50 @@ class TrackerAppModel implements ITrackerAppModel {

const signalerId = "signaler";

function createIndependentMapFactory() {
return new IndependentMapFactory({});
}

export class TrackerContainerRuntimeFactory extends ModelContainerRuntimeFactory<ITrackerAppModel> {
private readonly independentMapFactory: ReturnType<typeof createIndependentMapFactory>;
constructor() {
const independentMapFactory = createIndependentMapFactory();
super(
new Map([Signaler.factory.registryEntry]), // registryEntries
new Map([
// registryEntries
Signaler.factory.registryEntry,
independentMapFactory.registryEntry,
]),
);
this.independentMapFactory = independentMapFactory;
}

/**
* {@inheritDoc ModelContainerRuntimeFactory.containerInitializingFirstTime}
*/
protected async containerInitializingFirstTime(runtime: IContainerRuntime) {
const signaler = await runtime.createDataStore(Signaler.factory.type);
await signaler.trySetAlias(signalerId);
await Promise.all([
runtime
.createDataStore(Signaler.factory.type)
.then(async (signaler) => signaler.trySetAlias(signalerId)),
this.independentMapFactory.initializingFirstTime(runtime),
]);
}

protected async createModel(runtime: IContainerRuntime, container: IContainer) {
const signaler = await getDataStoreEntryPoint<Signaler>(runtime, signalerId);
const focusTracker = getDataStoreEntryPoint<Signaler>(runtime, signalerId).then(
(signaler) => new FocusTracker(container, audience, signaler),
);

const mouseTracker = this.independentMapFactory
.getMap(runtime)
.then((map) => new MouseTracker(audience, map));

const audience = createServiceAudience({
container,
createServiceMember: createMockServiceMember,
});

const focusTracker = new FocusTracker(container, audience, signaler);

const mouseTracker = new MouseTracker(audience, signaler);

return new TrackerAppModel(focusTracker, mouseTracker);
return new TrackerAppModel(await focusTracker, await mouseTracker);
}
}
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 55f8b04

Please sign in to comment.