react-device-detect#isFirefox TypeScript Examples
The following examples show how to use
react-device-detect#isFirefox.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AttachmentActions.tsx From revite with GNU Affero General Public License v3.0 | 4 votes |
export default function AttachmentActions({ attachment }: Props) {
const client = useContext(AppContext);
const { filename, metadata, size } = attachment;
const url = client.generateFileURL(attachment);
const open_url = `${url}/${filename}`;
const download_url = url?.replace("attachments", "attachments/download");
const filesize = determineFileSize(size);
switch (metadata.type) {
case "Image":
return (
<div className={classNames(styles.actions, styles.imageAction)}>
<span className={styles.filename}>{filename}</span>
<span className={styles.filesize}>
{`${metadata.width}x${metadata.height}`} ({filesize})
</span>
<a
href={open_url}
target="_blank"
className={styles.iconType}
rel="noreferrer">
<IconButton>
<LinkExternal size={24} />
</IconButton>
</a>
<a
href={download_url}
className={styles.downloadIcon}
download
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
</IconButton>
</a>
</div>
);
case "Audio":
return (
<div className={classNames(styles.actions, styles.audioAction)}>
<Headphone size={24} className={styles.iconType} />
<span className={styles.filename}>{filename}</span>
<span className={styles.filesize}>{filesize}</span>
<a
href={download_url}
className={styles.downloadIcon}
download
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
</IconButton>
</a>
</div>
);
case "Video":
return (
<div className={classNames(styles.actions, styles.videoAction)}>
<Video size={24} className={styles.iconType} />
<span className={styles.filename}>{filename}</span>
<span className={styles.filesize}>
{`${metadata.width}x${metadata.height}`} ({filesize})
</span>
<a
href={download_url}
className={styles.downloadIcon}
download
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
</IconButton>
</a>
</div>
);
default:
return (
<div className={styles.actions}>
<File size={24} className={styles.iconType} />
<span className={styles.filename}>{filename}</span>
<span className={styles.filesize}>{filesize}</span>
{metadata.type === "Text" && (
<a
href={open_url}
target="_blank"
className={styles.externalType}
rel="noreferrer">
<IconButton>
<LinkExternal size={24} />
</IconButton>
</a>
)}
<a
href={download_url}
className={styles.downloadIcon}
download
target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />
</IconButton>
</a>
</div>
);
}
}
Example #2
Source File: ContextMenus.tsx From revite with GNU Affero General Public License v3.0 | 4 votes |
// ! FIXME: I dare someone to re-write this
// Tip: This should just be split into separate context menus per logical area.
export default function ContextMenus() {
const { openScreen, writeClipboard } = useIntermediate();
const client = useContext(AppContext);
const userId = client.user!._id;
const status = useContext(StatusContext);
const isOnline = status === ClientStatus.ONLINE;
const state = useApplicationState();
const history = useHistory();
function contextClick(data?: Action) {
if (typeof data === "undefined") return;
(async () => {
switch (data.action) {
case "copy_id":
writeClipboard(data.id);
break;
case "copy_message_link":
{
let pathname = `/channel/${data.message.channel_id}/${data.message._id}`;
const channel = data.message.channel;
if (channel?.channel_type === "TextChannel")
pathname = `/server/${channel.server_id}${pathname}`;
writeClipboard(window.origin + pathname);
}
break;
case "copy_selection":
writeClipboard(document.getSelection()?.toString() ?? "");
break;
case "mark_as_read":
{
if (
data.channel.channel_type === "SavedMessages" ||
data.channel.channel_type === "VoiceChannel"
)
return;
client.unreads!.markRead(
data.channel._id,
data.channel.last_message_id!,
true,
true,
);
}
break;
case "mark_server_as_read":
{
client.unreads!.markMultipleRead(
data.server.channel_ids,
);
data.server.ack();
}
break;
case "mark_unread":
{
const messages = getRenderer(
data.message.channel!,
).messages;
const index = messages.findIndex(
(x) => x._id === data.message._id,
);
let unread_id = data.message._id;
if (index > 0) {
unread_id = messages[index - 1]._id;
}
internalEmit("NewMessages", "mark", unread_id);
data.message.channel?.ack(unread_id, true);
}
break;
case "retry_message":
{
const nonce = data.message.id;
const fail = (error: string) =>
state.queue.fail(nonce, error);
client.channels
.get(data.message.channel)!
.sendMessage({
nonce: data.message.id,
content: data.message.data.content,
replies: data.message.data.replies,
})
.catch(fail);
state.queue.start(nonce);
}
break;
case "cancel_message":
{
state.queue.remove(data.message.id);
}
break;
case "mention":
{
internalEmit(
"MessageBox",
"append",
`<@${data.user}>`,
"mention",
);
}
break;
case "copy_text":
writeClipboard(data.content);
break;
case "reply_message":
{
internalEmit("ReplyBar", "add", data.target);
}
break;
case "quote_message":
{
internalEmit(
"MessageBox",
"append",
data.content,
"quote",
);
}
break;
case "edit_message":
{
internalEmit(
"MessageRenderer",
"edit_message",
data.id,
);
}
break;
case "open_file":
{
window
.open(
client.generateFileURL(data.attachment),
"_blank",
)
?.focus();
}
break;
case "save_file":
{
window.open(
// ! FIXME: do this from revolt.js
client
.generateFileURL(data.attachment)
?.replace(
"attachments",
"attachments/download",
),
isFirefox || window.native ? "_blank" : "_self",
);
}
break;
case "copy_file_link":
{
const { filename } = data.attachment;
writeClipboard(
// ! FIXME: do from r.js
`${client.generateFileURL(
data.attachment,
)}/${encodeURI(filename)}`,
);
}
break;
case "open_link":
{
window.open(data.link, "_blank")?.focus();
}
break;
case "copy_link":
{
writeClipboard(data.link);
}
break;
case "remove_member":
{
data.channel.removeMember(data.user._id);
}
break;
case "view_profile":
openScreen({ id: "profile", user_id: data.user._id });
break;
case "message_user":
{
const channel = await data.user.openDM();
if (channel) {
history.push(`/channel/${channel._id}`);
}
}
break;
case "add_friend":
{
await data.user.addFriend();
}
break;
case "block_user":
openScreen({
id: "special_prompt",
type: "block_user",
target: data.user,
});
break;
case "unblock_user":
await data.user.unblockUser();
break;
case "remove_friend":
openScreen({
id: "special_prompt",
type: "unfriend_user",
target: data.user,
});
break;
case "cancel_friend":
await data.user.removeFriend();
break;
case "set_presence":
{
await client.users.edit({
status: {
...client.user?.status,
presence: data.presence,
},
});
}
break;
case "set_status":
openScreen({
id: "special_input",
type: "set_custom_status",
});
break;
case "clear_status":
{
const { text: _text, ...status } =
client.user?.status ?? {};
await client.users.edit({ status });
}
break;
case "leave_group":
case "close_dm":
case "leave_server":
case "delete_channel":
case "delete_server":
case "delete_message":
case "create_channel":
case "create_category":
case "create_invite":
// Typescript flattens the case types into a single type and type structure and specifity is lost
openScreen({
id: "special_prompt",
type: data.action,
target: data.target,
} as unknown as Screen);
break;
case "edit_identity":
openScreen({
id: "server_identity",
server: data.target,
});
break;
case "ban_member":
case "kick_member":
openScreen({
id: "special_prompt",
type: data.action,
target: data.target,
user: data.user,
});
break;
case "open_notification_options": {
openContextMenu("NotificationOptions", {
channel: data.channel,
server: data.server,
});
break;
}
case "open_settings":
history.push("/settings");
break;
case "open_channel_settings":
history.push(`/channel/${data.id}/settings`);
break;
case "open_server_channel_settings":
history.push(
`/server/${data.server}/channel/${data.id}/settings`,
);
break;
case "open_server_settings":
history.push(`/server/${data.id}/settings`);
break;
}
})().catch((err) => {
openScreen({ id: "error", error: takeError(err) });
});
}
return (
<>
<ContextMenuWithData id="Menu" onClose={contextClick}>
{({
user: uid,
channel: cid,
server: sid,
message,
attachment,
server_list,
queued,
unread,
contextualChannel: cxid,
}: ContextMenuData) => {
const elements: Children[] = [];
let lastDivider = false;
function generateAction(
action: Action,
locale?: string,
disabled?: boolean,
tip?: Children,
color?: string,
) {
lastDivider = false;
elements.push(
<MenuItem data={action} disabled={disabled}>
<span style={{ color }}>
<Text
id={`app.context_menu.${
locale ?? action.action
}`}
/>
</span>
{tip && <div className="tip">{tip}</div>}
</MenuItem>,
);
}
function pushDivider() {
if (lastDivider || elements.length === 0) return;
lastDivider = true;
elements.push(<LineDivider />);
}
if (server_list) {
const server = client.servers.get(server_list)!;
if (server) {
if (server.havePermission("ManageChannel")) {
generateAction({
action: "create_category",
target: server,
});
generateAction({
action: "create_channel",
target: server,
});
}
if (server.havePermission("ManageServer"))
generateAction({
action: "open_server_settings",
id: server_list,
});
}
return elements;
}
if (document.getSelection()?.toString().length ?? 0 > 0) {
generateAction(
{ action: "copy_selection" },
undefined,
undefined,
<Text id="shortcuts.ctrlc" />,
);
pushDivider();
}
const channel = cid ? client.channels.get(cid) : undefined;
const contextualChannel = cxid
? client.channels.get(cxid)
: undefined;
const targetChannel = channel ?? contextualChannel;
const user = uid ? client.users.get(uid) : undefined;
const serverChannel =
targetChannel &&
(targetChannel.channel_type === "TextChannel" ||
targetChannel.channel_type === "VoiceChannel")
? targetChannel
: undefined;
const s = serverChannel ? serverChannel.server_id! : sid;
const server = s ? client.servers.get(s) : undefined;
const channelPermissions = targetChannel?.permission || 0;
const serverPermissions =
(server
? server.permission
: serverChannel
? serverChannel.server?.permission
: 0) || 0;
const userPermissions = (user ? user.permission : 0) || 0;
if (unread) {
if (channel) {
generateAction({ action: "mark_as_read", channel });
} else if (server) {
generateAction(
{
action: "mark_server_as_read",
server,
},
"mark_as_read",
);
}
}
if (contextualChannel) {
if (user && user._id !== userId) {
generateAction({
action: "mention",
user: user._id,
});
pushDivider();
}
}
if (user) {
let actions: (Action["action"] | boolean)[];
switch (user.relationship) {
case "User":
actions = [];
break;
case "Friend":
actions = [
!user.bot && "remove_friend",
"block_user",
];
break;
case "Incoming":
actions = [
"add_friend",
"cancel_friend",
"block_user",
];
break;
case "Outgoing":
actions = [
!user.bot && "cancel_friend",
"block_user",
];
break;
case "Blocked":
actions = ["unblock_user"];
break;
case "BlockedOther":
actions = ["block_user"];
break;
case "None":
default:
if ((user.flags && 2) || (user.flags && 4)) {
actions = ["block_user"];
} else {
actions = [
!user.bot && "add_friend",
"block_user",
];
}
}
if (userPermissions & UserPermission.ViewProfile) {
generateAction({
action: "view_profile",
user,
});
}
if (
user._id !== userId &&
userPermissions & UserPermission.SendMessage
) {
generateAction({
action: "message_user",
user,
});
}
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
if (action) {
generateAction({
action,
user,
} as unknown as Action);
}
}
}
if (contextualChannel) {
if (contextualChannel.channel_type === "Group" && uid) {
if (
contextualChannel.owner_id === userId &&
userId !== uid
) {
generateAction({
action: "remove_member",
channel: contextualChannel,
user: user!,
});
}
}
if (
server &&
uid &&
userId !== uid &&
uid !== server.owner
) {
if (serverPermissions & Permission.KickMembers)
generateAction(
{
action: "kick_member",
target: server,
user: user!,
},
undefined, // this is needed because generateAction uses positional, not named parameters
undefined,
null,
"var(--error)", // the only relevant part really
);
if (serverPermissions & Permission.BanMembers)
generateAction(
{
action: "ban_member",
target: server,
user: user!,
},
undefined,
undefined,
null,
"var(--error)",
);
}
}
if (queued) {
generateAction({
action: "retry_message",
message: queued,
});
generateAction({
action: "cancel_message",
message: queued,
});
}
if (message && !queued) {
const sendPermission =
message.channel &&
message.channel.permission & Permission.SendMessage;
if (sendPermission) {
generateAction({
action: "reply_message",
target: message,
});
}
generateAction({
action: "mark_unread",
message,
});
if (
typeof message.content === "string" &&
message.content.length > 0
) {
if (sendPermission) {
generateAction({
action: "quote_message",
content: message.content,
});
}
generateAction({
action: "copy_text",
content: message.content,
});
}
if (message.author_id === userId) {
generateAction({
action: "edit_message",
id: message._id,
});
}
if (
message.author_id === userId ||
channelPermissions & Permission.ManageMessages
) {
generateAction({
action: "delete_message",
target: message,
});
}
if (
message.attachments &&
message.attachments.length == 1 // if there are multiple attachments, the individual ones have to be clicked
) {
pushDivider();
const { metadata } = message.attachments[0];
const { type } = metadata;
generateAction(
{
action: "open_file",
attachment: message.attachments[0],
},
type === "Image"
? "open_image"
: type === "Video"
? "open_video"
: "open_file",
);
generateAction(
{
action: "save_file",
attachment: message.attachments[0],
},
type === "Image"
? "save_image"
: type === "Video"
? "save_video"
: "save_file",
);
generateAction(
{
action: "copy_file_link",
attachment: message.attachments[0],
},
"copy_link",
);
}
if (document.activeElement?.tagName === "A") {
const link =
document.activeElement.getAttribute("href");
if (link) {
pushDivider();
generateAction({ action: "open_link", link });
generateAction({ action: "copy_link", link });
}
}
}
if (attachment) {
pushDivider();
const { metadata } = attachment;
const { type } = metadata;
generateAction(
{
action: "open_file",
attachment,
},
type === "Image"
? "open_image"
: type === "Video"
? "open_video"
: "open_file",
);
generateAction(
{
action: "save_file",
attachment,
},
type === "Image"
? "save_image"
: type === "Video"
? "save_video"
: "save_file",
);
generateAction(
{
action: "copy_file_link",
attachment,
},
"copy_link",
);
}
const id = sid ?? cid ?? uid ?? message?._id;
if (id) {
pushDivider();
if (channel) {
if (channel.channel_type !== "VoiceChannel") {
generateAction(
{
action: "open_notification_options",
channel,
},
undefined,
undefined,
<ChevronRight size={24} />,
);
}
switch (channel.channel_type) {
case "Group":
// ! generateAction({ action: "create_invite", target: channel }); FIXME: add support for group invites
generateAction(
{
action: "open_channel_settings",
id: channel._id,
},
"open_group_settings",
);
generateAction(
{
action: "leave_group",
target: channel,
},
"leave_group",
);
break;
case "DirectMessage":
generateAction({
action: "close_dm",
target: channel,
});
break;
case "TextChannel":
case "VoiceChannel":
if (
channelPermissions &
Permission.InviteOthers
) {
generateAction({
action: "create_invite",
target: channel,
});
}
if (
serverPermissions &
Permission.ManageServer
)
generateAction(
{
action: "open_server_channel_settings",
server: channel.server_id!,
id: channel._id,
},
"open_channel_settings",
);
if (
serverPermissions &
Permission.ManageChannel
)
generateAction({
action: "delete_channel",
target: channel,
});
break;
}
}
if (sid && server) {
generateAction(
{
action: "open_notification_options",
server,
},
undefined,
undefined,
<ChevronRight size={24} />,
);
if (server.channels[0] !== undefined)
generateAction(
{
action: "create_invite",
target: server.channels[0],
},
"create_invite",
);
if (
serverPermissions & Permission.ChangeNickname ||
serverPermissions & Permission.ChangeAvatar
)
generateAction(
{ action: "edit_identity", target: server },
"edit_identity",
);
if (serverPermissions & Permission.ManageServer)
generateAction(
{
action: "open_server_settings",
id: server._id,
},
"open_server_settings",
);
if (userId === server.owner) {
generateAction(
{ action: "delete_server", target: server },
"delete_server",
);
} else {
generateAction(
{ action: "leave_server", target: server },
"leave_server",
);
}
}
if (message) {
generateAction({
action: "copy_message_link",
message,
});
}
generateAction(
{ action: "copy_id", id },
sid
? "copy_sid"
: cid
? "copy_cid"
: message
? "copy_mid"
: "copy_uid",
);
}
return elements;
}}
</ContextMenuWithData>
<ContextMenuWithData
id="Status"
onClose={contextClick}
className="Status">
{() => {
const user = client.user!;
return (
<>
<div className="header">
<div className="main">
<div
className="username"
onClick={() =>
writeClipboard(
client.user!.username,
)
}>
<Tooltip
content={
<Text id="app.special.copy_username" />
}>
@{user.username}
</Tooltip>
</div>
<div
className="status"
onClick={() =>
contextClick({
action: "set_status",
})
}>
<UserStatus user={user} />
</div>
</div>
<IconButton>
<MenuItem
data={{ action: "open_settings" }}>
<Cog size={22} />
</MenuItem>
</IconButton>
</div>
<LineDivider />
<MenuItem
data={{
action: "set_presence",
presence: "Online",
}}
disabled={!isOnline}>
<div className="indicator online" />
<Text id={`app.status.online`} />
</MenuItem>
<MenuItem
data={{
action: "set_presence",
presence: "Idle",
}}
disabled={!isOnline}>
<div className="indicator idle" />
<Text id={`app.status.idle`} />
</MenuItem>
<MenuItem
data={{
action: "set_presence",
presence: "Busy",
}}
disabled={!isOnline}>
<div className="indicator busy" />
<Text id={`app.status.busy`} />
</MenuItem>
<MenuItem
data={{
action: "set_presence",
presence: "Invisible",
}}
disabled={!isOnline}>
<div className="indicator invisible" />
<Text id={`app.status.invisible`} />
</MenuItem>
<LineDivider />
<MenuItem
data={{ action: "set_status" }}
disabled={!isOnline}>
<UserVoice size={18} />
<Text id={`app.context_menu.custom_status`} />
{client.user!.status?.text && (
<IconButton>
<MenuItem
data={{ action: "clear_status" }}>
<Trash size={18} />
</MenuItem>
</IconButton>
)}
</MenuItem>
</>
);
}}
</ContextMenuWithData>
<CMNotifications />
</>
);
}