Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/account/avatar/users-viewing.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { useInterval } from "react-interval-hook";67import {8CSS,9redux,10useMemo,11useState,12useTypedRedux,13} from "@cocalc/frontend/app-framework";14import { Loading } from "@cocalc/frontend/components";15import { cmp } from "@cocalc/util/misc";16import { Avatar } from "./avatar";1718// How frequently all UsersViewing componenents are completely updated.19// This is only needed to ensure that faces fade out; any newly added faces20// will still be displayed instantly. Also, updating more frequently updates21// the line positions in the tooltip.22const UPDATE_INTERVAL_S = 15;2324// Cutoff for how recent activity must be to show users. Should be significantly25// longer than default for the mark_file function in the file_use actions.26const MAX_AGE_S = 600;2728interface Activity {29project_id: string;30path: string;31last_used: Date;32}3334function most_recent(activity: Activity[]): Activity {35if (activity.length == 0) throw Error("must have some activity");36let { last_used } = activity[0];37let y = activity[0];38for (let x of activity.slice(1)) {39if (x.last_used >= last_used) {40y = x;41({ last_used } = x);42}43}44return y;45}4647const USERS_VIEWING_STYLE: React.CSSProperties = {48overflowX: "auto",49overflowY: "hidden",50zIndex: 1,51whiteSpace: "nowrap",52padding: "1px", // if not set, Chrome draws scrollbars around it #539953height: "32px",54} as const;5556const DEFAULT_STYLE: CSS = { maxWidth: "120px" } as const;5758// If neither project_id nor path given, then viewing all projects; if project_id59// given, then viewing that project; if both given, then viewing a particular file.60interface Props {61project_id?: string; // optional -- must be given if path is specified62path?: string; // optional -- if given, viewing a file.63max_age_s?: number;64size?: number;65style?: React.CSSProperties;66}6768function useUsersViewing(69project_id?: string,70path?: string,71max_age_s?: number,72) {73const [counter, set_counter] = useState(0); // used to force update periodically.7475// only so component is updated immediately whenever file use changes76const file_use = useTypedRedux("file_use", "file_use");77const users = useMemo(78() =>79redux.getStore("file_use")?.get_active_users({80project_id,81path,82max_age_s,83}),84[file_use, project_id, path, max_age_s],85);8687useInterval(() => {88// cause an update89set_counter(counter + 1);90}, UPDATE_INTERVAL_S * 1000);9192return { users, file_use };93}9495export function UsersViewing(props: Readonly<Props>) {96const {97path,98project_id,99max_age_s = MAX_AGE_S,100style = DEFAULT_STYLE,101size = 24,102} = props;103104const { users, file_use } = useUsersViewing(project_id, path, max_age_s);105106// so we can exclude ourselves from list of faces107const our_account_id: string | undefined = useTypedRedux(108"account",109"account_id",110);111112function render_active_users(users) {113const v: {114account_id: string;115activity: Activity;116}[] = [];117if (users != null) {118for (const account_id in users) {119const activity = users[account_id];120if (!activity || activity.length == 0) {121continue; // shouldn't happen, but just be extra careful122}123v.push({ account_id, activity: most_recent(activity) });124}125}126v.sort((a, b) => cmp(b.activity.last_used, a.activity.last_used));127let i = 0;128const r: JSX.Element[] = [];129for (const { account_id, activity } of v) {130// only show other users131if (account_id !== our_account_id) {132i += 1;133r.push(134<Avatar135key={account_id + i}136account_id={account_id}137max_age_s={max_age_s}138project_id={project_id}139path={path}140size={size}141activity={activity}142/>,143);144}145}146return r;147}148149if (file_use == null || our_account_id == null) {150return <Loading />;151}152153return (154<div style={{ ...USERS_VIEWING_STYLE, ...style }}>155{render_active_users(users)}156</div>157);158}159160161