-
Notifications
You must be signed in to change notification settings - Fork 98
Canvas #1635
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
base: main
Are you sure you want to change the base?
Conversation
jason-ha
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick notes from brief look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consistency: Most files have header comments before imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consistency: Most files have header comments before imports
| LatestRaw, | ||
| LatestRawEvents, | ||
| LatestMapRaw, | ||
| LatestMapRawEvents, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably use the validated version of API in our sample.
Probably TypeBox is good solution. Presence readme has example.
If not, then we should comment why this example isn't even though validated pattern is our recommendation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dupe? Doesn't seem to be in the right place.
| @@ -0,0 +1,198 @@ | |||
| @import "tailwindcss"; | |||
|
|
|||
| /* | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the commented out styles?
| * Implementation of the SelectionManager interface. | ||
| * Handles multi-select operations and real-time state synchronization. | ||
| */ | ||
| class SelectionManagerImpl implements SelectionManager<Selection> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can reduce duplication between this and typedSelectionManager using a generics pattern like:
class SelectionManagerImpl<T extends Selection> implements SelectionManager<T> {
// Single implementation for both types
}| // Initialize iOS Safari z-index fixes | ||
| useEffect(() => { | ||
| // Temporarily disabled to debug toolbar visibility | ||
| // fixIOSZIndexIssues(); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still needed?
| // Commented out to suppress FluidDevToolsLogger console messages | ||
| // if (process.env.NODE_ENV === "development") { | ||
| // const { createDevtoolsLogger } = await import("@fluidframework/devtools/beta"); | ||
| // logger = createDevtoolsLogger(); | ||
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think we use these any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a dev doc - maybe move this and similar ones into a docs folder or delete? I think they'll be confusing to people reading the example.
|
|
||
| import { AzureMember, ITokenProvider, ITokenResponse, IUser } from "@fluidframework/azure-client"; | ||
| import { ScopeType } from "@fluidframework/protocol-definitions"; | ||
| import axios from "axios"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still bugs me that we use axios instead of fetch, but probably not in scope for now.
| /** The user's profile information (name, avatar, etc.) */ | ||
| value: TUserInfo; | ||
| /** The Fluid Framework client/attendee associated with this user */ | ||
| client: Attendee; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we should be specific with attendee naming rather than client.
| ) { | ||
| visibleCursors.push({ | ||
| state: cursorState, | ||
| clientId: remote.attendee.attendeeId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clientId is a seperate concept to attendeeId. Should probably replace clientId usage with attendeeId in CursorManager interface
| // import "../../../styles/ios-fixes.css"; | ||
| // import "../../../styles/ios-safari-fixes.css"; | ||
| // import { fixIOSZIndexIssues } from "../../../utils/iosZIndexFix.js"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove these commented out imports
| // A react component for displaying and interacting with notes using the Fluid Framework | ||
| // Note object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel like this comment can go
| // Custom sorting function for DateTime objects because | ||
| // the default alphanumeric sorting function does not work | ||
| // because the data is accessed via a second layer of the object | ||
| const voteSortingFn: SortingFn<FluidRow> = ( | ||
| rowA: Row<FluidRow>, | ||
| rowB: Row<FluidRow>, | ||
| columnId: string | ||
| ) => { | ||
| const valueA = rowA.getValue(columnId) as { value: Vote | undefined }; | ||
| const valueB = rowB.getValue(columnId) as { value: Vote | undefined }; | ||
| if (valueA === undefined && valueB === undefined) { | ||
| return 0; | ||
| } else if (valueA === undefined) { | ||
| return 1; | ||
| } else if (valueB === undefined) { | ||
| return -1; | ||
| } else if (Tree.is(valueA, Vote) && Tree.is(valueB, Vote)) { | ||
| const dateA = valueA.numberOfVotes; | ||
| const dateB = valueB.numberOfVotes; | ||
| if (dateA < dateB) { | ||
| return -1; | ||
| } else if (dateA > dateB) { | ||
| return 1; | ||
| } else { | ||
| return 0; | ||
| } | ||
| } | ||
| return 0; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment says it sorting DateTime objects and we use dateA/dateB naming but this is a function for sorting votes
| const valueA = rowA.getValue(columnId) as { value: Vote | undefined }; | ||
| const valueB = rowB.getValue(columnId) as { value: Vote | undefined }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing inconsistencies between voteSortingFn and dateSortingFn. We cast valueA/valueB as nested { value: TValue | undefined } but then we do valueA.numberOfVotes.
We also check for nested valueA.value for undefined in dateSortingFn but not for voteSortingFn.
In any case these seem like the same function besides the nested value being compared. Can have a generic sorting function that takes in a compareFn to not repeat code.
EDIT: Tried adding votes column in the demo app and ran into some issues. Potential bug there.
| // Get the sorting function and sort direction for a column | ||
| const getSortingConfig = ( | ||
| column: FluidColumn // Column object with id, name, and hint properties | ||
| ): { fn: SortingFnOption<FluidRow> | undefined; desc: boolean } => { | ||
| if (column.props.hint === hintValues.boolean) { | ||
| return { fn: "basic", desc: false }; | ||
| } else if (column.props.hint === hintValues.number) { | ||
| return { fn: "alphanumeric", desc: true }; | ||
| } else if (column.props.hint === hintValues.string) { | ||
| return { fn: "alphanumeric", desc: false }; | ||
| } else if (column.props.hint === hintValues.date) { | ||
| return { fn: dateSortingFn, desc: false }; | ||
| } else if (column.props.hint === hintValues.vote) { | ||
| return { fn: voteSortingFn, desc: true }; | ||
| } else { | ||
| console.error("Unknown column type", "Hint:", column.props.hint); | ||
| return { fn: "basic", desc: false }; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: record/map or switch case over if else chain
| export function getActiveDragForItem( | ||
| presence: React.ContextType<typeof PresenceContext>, | ||
| itemId: string | ||
| ): { id: string; x: number; y: number; rotation: number } | null { | ||
| const local = presence.drag?.state?.local as { | ||
| id: string; | ||
| x: number; | ||
| y: number; | ||
| rotation: number; | ||
| } | null; | ||
| if (local && local.id === itemId) return local; | ||
| const remotesIter = ( | ||
| presence.drag?.state as unknown as { getRemotes?: () => unknown } | ||
| )?.getRemotes?.(); | ||
| const isIterable = (obj: unknown): obj is Iterable<unknown> => { | ||
| return !!obj && typeof (obj as { [k: symbol]: unknown })[Symbol.iterator] === "function"; | ||
| }; | ||
| const isRecord = (v: unknown): v is Record<string, unknown> => | ||
| typeof v === "object" && v !== null; | ||
| if (isIterable(remotesIter)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of weird optional chaining and typecasts/typeguards here in this function. Is this a problem with Presence API? Seems like we should have more certainty around types used here.
Add a new example that includes extensive use of presence and SharedTree.