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

feat(presence-tracker): Add "reactions" using presence notifications #23294

Open
wants to merge 109 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
aaf918b
wip
tylerbutler Nov 6, 2024
a2a55e4
wip
tylerbutler Nov 6, 2024
39741a3
updates
tylerbutler Nov 6, 2024
c3a2b57
sort of works
tylerbutler Nov 6, 2024
5606c11
seems pretty good
tylerbutler Nov 7, 2024
7ce14c6
updates
tylerbutler Nov 7, 2024
5fc6ac7
cleanup
tylerbutler Nov 7, 2024
5f5aa4b
cleanup
tylerbutler Nov 7, 2024
2219cba
Merge branch 'main' into presence-tracker-revamp
tylerbutler Nov 7, 2024
c118fcd
wip
tylerbutler Nov 7, 2024
3bae7a4
revert removed files
tylerbutler Nov 7, 2024
dc9c829
mousetracker with presence workspace
tylerbutler Nov 7, 2024
ae317ab
focustracker with presence
tylerbutler Nov 7, 2024
3cbe492
cleanup
tylerbutler Nov 7, 2024
c635bf0
brng back the trackers
tylerbutler Nov 8, 2024
0d6476d
Merge branch 'main' into presence-tracker-revamp
tylerbutler Nov 8, 2024
a1649d3
breaking change
tylerbutler Nov 8, 2024
a5de697
Merge branch 'main' into presence-tracker-revamp
tylerbutler Nov 11, 2024
3a7f7cd
cleanup
tylerbutler Nov 11, 2024
069a052
use clientValues
tylerbutler Nov 11, 2024
a43de27
cleanup
tylerbutler Nov 11, 2024
d7bfbc0
sort of working
tylerbutler Nov 12, 2024
cd7ed6f
Merge branch 'main' into presence-tracker-revamp
tylerbutler Nov 12, 2024
9237db8
test
tylerbutler Nov 12, 2024
8257169
still sort of works once a second user joins
tylerbutler Nov 12, 2024
3320851
rm unused files
tylerbutler Nov 12, 2024
4b2e178
Merge branch 'main' into presence-tracker-revamp
tylerbutler Nov 27, 2024
116221f
package rename and lint config
tylerbutler Nov 27, 2024
f5fb796
add controllable latency
tylerbutler Nov 27, 2024
4b0dd7d
naming
tylerbutler Nov 27, 2024
3e044d3
notifications on click and keypress
tylerbutler Nov 28, 2024
0e5f297
Merge branch 'main' into presence-tracker-revamp
tylerbutler Dec 10, 2024
90cbc36
debugging
tylerbutler Dec 11, 2024
d68df9c
cleanup
tylerbutler Dec 11, 2024
7a8d795
Merge branch 'presence-tracker-revamp' into presence-tracker-notifica…
tylerbutler Dec 11, 2024
62f3c9d
formatting
tylerbutler Dec 11, 2024
0361460
Merge branch 'main' into presence-tracker-revamp
tylerbutler Dec 11, 2024
0f245d5
policy
tylerbutler Dec 11, 2024
4333d6c
Merge branch 'presence-tracker-revamp' into presence-tracker-notifica…
tylerbutler Dec 11, 2024
a4404e4
lockfile fixup
tylerbutler Dec 11, 2024
b0bdf4a
lockfile
tylerbutler Dec 11, 2024
70926be
Merge branch 'presence-tracker-revamp' into presence-tracker-notifica…
tylerbutler Dec 11, 2024
cb1b48d
feedback
tylerbutler Dec 11, 2024
b7615e5
update test config
tylerbutler Dec 11, 2024
38de027
test updates
tylerbutler Dec 12, 2024
b0650b4
multi-client tests
tylerbutler Dec 12, 2024
34345d0
Merge branch 'main' into presence-tracker-revamp
tylerbutler Dec 12, 2024
dfc37e4
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 6, 2025
7b594b9
run tinylicious for jest tests
tylerbutler Jan 6, 2025
7406ad5
azure function comments
tylerbutler Jan 7, 2025
e133d44
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 7, 2025
1277da7
use tinylicious exclsively
tylerbutler Jan 7, 2025
2683c70
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 13, 2025
3018bbf
fix policy
tylerbutler Jan 13, 2025
4b4647e
fixes
tylerbutler Jan 13, 2025
97a5dbe
feedback
tylerbutler Jan 13, 2025
33668d4
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 13, 2025
c7f5cdc
remove unused script
tylerbutler Jan 13, 2025
5adadea
clean up comments
tylerbutler Jan 13, 2025
f3b6667
Apply suggestions from code review
tylerbutler Jan 13, 2025
9427552
build failure
tylerbutler Jan 13, 2025
ed8bfca
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 14, 2025
5a155e8
feedback
tylerbutler Jan 14, 2025
62f14da
test feedback
tylerbutler Jan 14, 2025
54485e6
try removing the --ci flag
tylerbutler Jan 14, 2025
a52bba2
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 14, 2025
c5b9744
feedback
tylerbutler Jan 14, 2025
bcd24eb
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 15, 2025
7c46f35
feedback
tylerbutler Jan 15, 2025
6940986
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 15, 2025
9489b9b
fix
tylerbutler Jan 15, 2025
6051964
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 15, 2025
de56c92
updates
tylerbutler Jan 15, 2025
93e7767
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 15, 2025
a2d1cfd
Revert "updates"
tylerbutler Jan 15, 2025
e30c829
close browser after all tests to prevent hang
tylerbutler Jan 15, 2025
382b9b7
policy
tylerbutler Jan 15, 2025
61406a0
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 16, 2025
14b2c06
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 16, 2025
e83c8dc
feedback
tylerbutler Jan 16, 2025
0fde8ba
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 16, 2025
c490da8
feedback
tylerbutler Jan 17, 2025
498e470
Merge remote-tracking branch 'origin/presence-tracker-revamp' into pr…
tylerbutler Jan 22, 2025
5ce5210
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 22, 2025
aefd485
docs
tylerbutler Jan 23, 2025
e5a0415
tests
tylerbutler Jan 23, 2025
197cd08
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 23, 2025
31f0b19
Merge branch 'main' into presence-tracker-revamp
tylerbutler Jan 23, 2025
139d4d6
Merge remote-tracking branch 'origin/presence-tracker-revamp' into pr…
tylerbutler Jan 23, 2025
88b016b
fixes
tylerbutler Jan 23, 2025
7fce02b
try including position in notification
tylerbutler Jan 23, 2025
ef2878f
Merge branch 'main' into presence-tracker-notifications
tylerbutler Jan 23, 2025
ba78969
revert
tylerbutler Jan 23, 2025
2ed4fb5
emoji-picker-element
tylerbutler Jan 24, 2025
c53d531
works
tylerbutler Jan 24, 2025
5f09b3f
Merge branch 'main' into presence-tracker-notifications
tylerbutler Jan 24, 2025
8a4a37e
updates
tylerbutler Jan 24, 2025
205e1d6
Merge branch 'main' into presence-tracker-notifications
tylerbutler Jan 24, 2025
b6699c5
Update examples/apps/presence-tracker/src/app.ts
tylerbutler Jan 24, 2025
de423f2
Update examples/apps/presence-tracker/src/reactions.ts
tylerbutler Jan 24, 2025
f902b2b
Update examples/apps/presence-tracker/src/reactions.ts
tylerbutler Jan 24, 2025
055fa17
Merge branch 'main' into presence-tracker-notifications
tylerbutler Jan 28, 2025
00c3e87
update dep
tylerbutler Feb 6, 2025
365838e
Merge branch 'main' into presence-tracker-notifications
tylerbutler Feb 6, 2025
382da3a
feedback
tylerbutler Feb 6, 2025
a0436da
cleanup
tylerbutler Feb 6, 2025
65ccf6e
check for connection
tylerbutler Feb 6, 2025
8e87a02
add 'privacy cover' on blur and hide it on focus
tylerbutler Feb 6, 2025
a0974d8
Merge branch 'main' into presence-tracker-notifications
tylerbutler Feb 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/apps/presence-tracker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@fluidframework/presence": "workspace:~",
"@fluidframework/runtime-utils": "workspace:~",
"@fluidframework/tinylicious-client": "workspace:~",
"emoji-picker-element": "^1.26.1",
"fluid-framework": "workspace:~",
"process": "^0.11.10"
},
Expand Down
12 changes: 12 additions & 0 deletions examples/apps/presence-tracker/src/FocusTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,24 @@ export class FocusTracker extends TypedEventEmitter<IFocusTrackerEvents> {
this.focus.local = {
hasFocus: true,
};

const cover = document.querySelector<HTMLDivElement>("#cover");
if (cover !== null) {
cover.setAttribute("style", "opacity: 0; z-index: auto");
}

this.emit("focusChanged", this.focus.local);
});
window.addEventListener("blur", () => {
this.focus.local = {
hasFocus: false,
};

const cover = document.querySelector<HTMLDivElement>("#cover");
if (cover !== null) {
cover.setAttribute("style", "opacity: 0.7; z-index: 100");
}

this.emit("focusChanged", this.focus.local);
});
}
Expand Down
7 changes: 7 additions & 0 deletions examples/apps/presence-tracker/src/MouseTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,11 @@ export class MouseTracker extends TypedEventEmitter<IMouseTrackerEvents> {
public setAllowableLatency(latency: number | undefined): void {
this.cursor.controls.allowableUpdateLatencyMs = latency;
}

/**
* The most recent mouse position of the provided client.
*/
public getClientMousePosition(client: ISessionClient): IMousePosition {
return this.cursor.clientValue(client).value;
}
}
3 changes: 3 additions & 0 deletions examples/apps/presence-tracker/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { ContainerSchema, IFluidContainer } from "fluid-framework";

import { FocusTracker } from "./FocusTracker.js";
import { MouseTracker } from "./MouseTracker.js";
import { initializeReactions } from "./reactions.js";
import { renderControlPanel, renderFocusPresence, renderMousePresence } from "./view.js";

// Define the schema of the Fluid container.
Expand Down Expand Up @@ -71,6 +72,8 @@ async function start() {
const focusTracker = new FocusTracker(presence, appPresence);
const mouseTracker = new MouseTracker(presence, appPresence);

initializeReactions(presence, mouseTracker);

const focusDiv = document.getElementById("focus-content") as HTMLDivElement;
renderFocusPresence(focusTracker, focusDiv);

Expand Down
33 changes: 33 additions & 0 deletions examples/apps/presence-tracker/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,41 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test application</title>
<style>
.reaction {
position: absolute;
font-size: x-large;
animation: fadeUp 1s forwards;
}

@keyframes fadeUp {
0% {
opacity: 1;
transform: translateY(0) rotate(45deg);
}
100% {
opacity: 0;
transform: translateY(-50px) rotate(-45deg);
}
}

emoji-picker {
--num-columns: 6;
height: 200px;
width: 300px;
}

#cover {
position: absolute;
background-color: black;
height: 100%;
width: 100%;
}
</style>
</head>
<body style="margin: 0; height: 100%">
<div id="cover" style="opacity: 0">
</div>
<div id="control-panel" style="padding: 10px"></div>
<div id="focus-content" style="min-height: 100%; border: 1px solid black"></div>
<div id="mouse-position"></div>
Expand Down
74 changes: 74 additions & 0 deletions examples/apps/presence-tracker/src/reactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import { Notifications } from "@fluidframework/presence/alpha";
import type { IPresence, ISessionClient } from "@fluidframework/presence/alpha";

import type { IMousePosition, MouseTracker } from "./MouseTracker.js";

/**
* Initializes reactions support for the app. Initialization will create a presence Notifications workspace and connect
* relevant event handlers. Reaction elements are added to the DOM in response to incoming notifications. These DOM
* elements are automatically removed after a timeout.
*/
export function initializeReactions(presence: IPresence, mouseTracker: MouseTracker) {
// Create a notifications workspace to send reactions-related notifications. This workspace will be created if it
// doesn't exist. We also create a Notifications value manager. You can also
// add value managers to the workspace later.
const notificationsWorkspace = presence.getNotifications(
// A unique key identifying this workspace.
"name:reactions",
{
// Initialize a notifications manager with the provided message schema.
reactions:
Notifications<// This explicit generic type specification will not be required in the future.
{
reaction: (
// In the future, we'll be able to use IMousePosition here.
position: { x: number; y: number },
value: string,
) => void;
}>(
// Define a default listener. Listeners can also be added later.
{
reaction: onReaction,
},
),
},
);

// Send a reaction to all clients on click.
document.body.addEventListener("click", (e) => {
// Get the current reaction value
const selectedReaction = document.getElementById("selected-reaction") as HTMLSpanElement;
const reactionValue = selectedReaction.textContent;

// Check that we're connected before sending notifications.
if (presence.getMyself().getConnectionStatus() === "Connected") {
notificationsWorkspace.props.reactions.emit.broadcast(
"reaction",
mouseTracker.getClientMousePosition(presence.getMyself()),
reactionValue ?? "?",
);
}
});
}

/**
* Renders reactions to the window using absolute positioning.
*/
function onReaction(client: ISessionClient, position: IMousePosition, value: string): void {
const reactionDiv = document.createElement("div");
reactionDiv.className = "reaction";
reactionDiv.style.position = "absolute";
reactionDiv.style.left = `${position.x}px`;
reactionDiv.style.top = `${position.y}px`;
reactionDiv.textContent = value;
document.body.appendChild(reactionDiv);

setTimeout(() => {
reactionDiv.remove();
}, 1000);
}
48 changes: 29 additions & 19 deletions examples/apps/presence-tracker/src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,23 @@
* Licensed under the MIT License.
*/

import { Picker } from "emoji-picker-element";

import { FocusTracker, type IFocusState } from "./FocusTracker.js";
import { MouseTracker } from "./MouseTracker.js";

export function renderFocusPresence(focusTracker: FocusTracker, div: HTMLDivElement) {
const wrapperDiv = document.createElement("div");
wrapperDiv.style.textAlign = "left";
wrapperDiv.style.margin = "70px";
wrapperDiv.style.margin = "10px";
div.appendChild(wrapperDiv);

const focusDiv = document.createElement("div");
focusDiv.id = "focus-div";
focusDiv.style.fontSize = "14px";

const focusMessageDiv = document.createElement("div");
focusMessageDiv.id = "message-div";
focusMessageDiv.textContent = "Click to focus";
focusMessageDiv.style.position = "absolute";
focusMessageDiv.style.top = "50px";
focusMessageDiv.style.left = "10px";
focusMessageDiv.style.color = "red";
focusMessageDiv.style.fontWeight = "bold";
focusMessageDiv.style.fontSize = "18px";
focusMessageDiv.style.border = "2px solid red";
focusMessageDiv.style.padding = "10px";
focusMessageDiv.style.display = "none";
wrapperDiv.appendChild(focusMessageDiv);

const onFocusChanged = (focusState: IFocusState) => {
focusDiv.innerHTML = getFocusPresencesString("<br>", focusTracker);
const { hasFocus } = focusState;

// hasFocus === true should hide the message
focusMessageDiv.style.display = hasFocus ? "none" : "";
};

onFocusChanged({ hasFocus: window.document.hasFocus() });
Expand Down Expand Up @@ -106,4 +90,30 @@ export function renderControlPanel(mouseTracker: MouseTracker, controlPanel: HTM
const target = e.target as HTMLInputElement;
mouseTracker.setAllowableLatency(parseInt(target.value, 10));
});

const reactionsConfigDiv = document.createElement("div");
reactionsConfigDiv.id = "reactions-config";
const reactionLabelDiv = document.createElement("div");
reactionLabelDiv.style.marginTop = "10px";
reactionLabelDiv.style.marginBottom = "10px";
reactionLabelDiv.textContent = "Selected reaction:";
reactionsConfigDiv.appendChild(reactionLabelDiv);

// This span element contains the selected emoji
const selectedSpan = document.createElement("span");
selectedSpan.id = "selected-reaction";
selectedSpan.textContent = "❤️";
reactionLabelDiv.appendChild(selectedSpan);

// Create the emoji-picker element and add it to the panel
const picker = new Picker();
reactionsConfigDiv.appendChild(picker);
controlPanel.appendChild(reactionsConfigDiv);

// Update the selected reaction emoji when the picker is clicked
controlPanel
.querySelector("emoji-picker")
?.addEventListener("emoji-click", (event: Event & { detail?: any }) => {
selectedSpan.textContent = event.detail?.unicode;
});
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

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

Loading