add community profile section to sidebar and improve icons

This commit is contained in:
hippoz 2023-08-11 00:46:37 +03:00
parent d5e5cadc10
commit 7692dd0808
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
13 changed files with 215 additions and 97 deletions

View file

@ -7,7 +7,6 @@
"dev": "rollup -c -w" "dev": "rollup -c -w"
}, },
"devDependencies": { "devDependencies": {
"@fontsource/material-icons-outlined": "^4.5.4",
"@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.1.0", "@rollup/plugin-terser": "^0.1.0",
@ -18,6 +17,7 @@
"svelte": "^4.0.0" "svelte": "^4.0.0"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/material-symbols-outlined": "^5.0.7",
"@fontsource-variable/open-sans": "^5.0.6" "@fontsource-variable/open-sans": "^5.0.6"
} }
} }

View file

@ -44,7 +44,7 @@ export default {
// Copy fonts // Copy fonts
copy({ copy({
targets: [ targets: [
{ src: "node_modules/@fontsource/material-icons-outlined/files/material-icons-outlined-all-400-normal.woff2", dest: "public/build/files" }, { src: "node_modules/@fontsource-variable/material-symbols-outlined/files/material-symbols-outlined-latin-opsz-normal.woff2", dest: "public/build/files" },
{ src: "node_modules/@fontsource-variable/open-sans/files/open-sans-latin-wght-*", dest: "public/build/files" }, { src: "node_modules/@fontsource-variable/open-sans/files/open-sans-latin-wght-*", dest: "public/build/files" },
] ]
}), }),

View file

@ -2,10 +2,24 @@
export let options = []; export let options = [];
export let selectedOptionId = null; export let selectedOptionId = null;
export let onSelect = (_) => {}; export let onSelect = (_) => {};
export let smaller = false;
export let doHighlight = false;
if (selectedOptionId) { if (selectedOptionId) {
onSelect(selectedOptionId); onSelect(selectedOptionId);
} }
const optionClick = (option) => {
if (option) {
if (doHighlight) {
selectedOptionId = option.id;
}
onSelect(selectedOptionId);
if (option.handle) {
option.handle();
}
}
};
</script> </script>
<style> <style>
@ -23,9 +37,11 @@
color: var(--foreground-color-2); color: var(--foreground-color-2);
background-color: var(--background-color-2); background-color: var(--background-color-2);
padding: 0.5em; padding: 0.5em;
padding-left: 0.65em;
padding-right: 0.65em;
margin-top: var(--space-xs); margin-top: var(--space-xs);
margin-right: var(--space-sm); margin-right: var(--space-sm);
border-radius: 0.65em; border-radius: 9999px;
font-weight: 500; font-weight: 500;
} }
@ -42,19 +58,31 @@
color: var(--background-color-2); color: var(--background-color-2);
} }
.material-icons-outlined { .button:not(.has-text) {
aspect-ratio: 1 / 1;
}
.has-text .material-icons-outlined {
margin-right: var(--space-xxs); margin-right: var(--space-xxs);
} }
.smaller button {
font-size: 0.85em;
}
</style> </style>
<div> <div class:smaller={smaller}>
{#each options as option (option.id)} {#each options as option (option.id)}
<button class="button" class:selected={ selectedOptionId === option.id } on:click={ () => { selectedOptionId = option.id; onSelect(selectedOptionId); } }> {#if !option.hidden}
{#if option.icon} <button class="button" class:selected={ selectedOptionId === option.id } class:has-text={!!option.text} on:click={ optionClick(option) }>
<span class="material-icons-outlined">{ option.icon }</span> {#if option.icon}
{/if} <span class="material-icons-outlined">{ option.icon }</span>
{ option.text } {/if}
</button> {#if option.text}
{ option.text }
{/if}
</button>
{/if}
{/each} {/each}
</div> </div>

View file

@ -96,7 +96,7 @@
{:else if renderAs === AttachmentRenderAs.DownloadableFile} {:else if renderAs === AttachmentRenderAs.DownloadableFile}
<div class="attachment attachment-card"> <div class="attachment attachment-card">
<div class="attachment-filename">{ attachment.file_name }</div> <div class="attachment-filename">{ attachment.file_name }</div>
<a class="icon-button material-icons-outlined download" href="{ attachmentUrl(attachment.file) }" target="_blank">download</a> <a class="icon-button material-icons-outlined small download" href="{ attachmentUrl(attachment.file) }" target="_blank">download</a>
</div> </div>
{:else} {:else}
<div class="attachment attachment-card">Couldn't render attachment</div> <div class="attachment attachment-card">Couldn't render attachment</div>

View file

@ -3,6 +3,7 @@
import { maybeFly, maybeFlyIf } from "../animations"; import { maybeFly, maybeFlyIf } from "../animations";
import { avatarUrl } from "../storage"; import { avatarUrl } from "../storage";
import { gatewayStatus, overlayStore, selectedChannel, showSidebar, smallViewport, userInfoStore, unreadStore, OverlayType, communities, selectedCommunity, filteredChannelsStore } from "../stores"; import { gatewayStatus, overlayStore, selectedChannel, showSidebar, smallViewport, userInfoStore, unreadStore, OverlayType, communities, selectedCommunity, filteredChannelsStore } from "../stores";
import SidebarCommunity from "./SidebarCommunity.svelte";
const selectChannel = (channel) => { const selectChannel = (channel) => {
if ($smallViewport) { if ($smallViewport) {
@ -45,11 +46,7 @@
margin: var(--space-sm); margin: var(--space-sm);
margin-bottom: 0; margin-bottom: 0;
transition-duration: 200ms; transition-duration: 200ms;
transition-property: border-radius background; transition-property: border-radius background color;
}
.theme--light .communities .button:hover, .theme--light .communities .button.selected {
color: var(--background-color-3);
} }
.communities .button:hover, .communities .button.selected { .communities .button:hover, .communities .button.selected {
@ -58,6 +55,18 @@
color: var(--foreground-color-1); color: var(--foreground-color-1);
} }
.theme--light .communities .button.selected .material-icons-outlined,
.theme--light .communities .button:hover .material-icons-outlined,
.theme--light .communities .button:hover, .theme--light .communities .button.selected {
color: var(--background-color-3);
}
.theme--dark .communities .button .material-icons-outlined {
color: var(--foreground-color-2);
}
.theme--dark .communities .button.selected .material-icons-outlined {
color: var(--foreground-color-1);
}
.communities .button span { .communities .button span {
margin: 0.45em; margin: 0.45em;
} }
@ -71,11 +80,7 @@
<div class="sidebar-container" in:maybeFly="{{ duration: 175, easing: quadInOut, x: -10 }}" out:maybeFlyIf="{{ _condition: !smallViewport.value, duration: 175, easing: quadInOut, x: -10 }}"> <div class="sidebar-container" in:maybeFly="{{ duration: 175, easing: quadInOut, x: -10 }}" out:maybeFlyIf="{{ _condition: !smallViewport.value, duration: 175, easing: quadInOut, x: -10 }}">
<div class="communities"> <div class="communities">
<button class="button" on:click={() => selectedCommunity.clear()} class:selected={ $selectedCommunity.id === -1 }> <button class="button" on:click={() => selectedCommunity.clear()} class:selected={ $selectedCommunity.id === -1 }>
{#if $userInfoStore && $userInfoStore.avatar} <span class="material-icons-outlined">home</span>
<img src={avatarUrl($userInfoStore.avatar, 64)} alt=" ">
{:else}
<span>{ $userInfoStore ? $userInfoStore.username[0] || "" : "" }</span>
{/if}
</button> </button>
{#each $communities as community (community.id)} {#each $communities as community (community.id)}
<button class="button" on:click={() => $selectedCommunity = community} class:selected={ $selectedCommunity.id === community.id }> <button class="button" on:click={() => $selectedCommunity = community} class:selected={ $selectedCommunity.id === community.id }>
@ -94,13 +99,7 @@
</button> </button>
</div> </div>
<div class="sidebar round-left"> <div class="sidebar round-left">
<div class="top-bar text-small text-bold"> <SidebarCommunity communityLike={ $selectedCommunity.id === -1 ? $userInfoStore : $selectedCommunity } isUser={ $selectedCommunity.id === -1 } />
{#if $selectedCommunity.id !== -1}
{$selectedCommunity.name || ""}
{:else}
{$userInfoStore ? $userInfoStore.username || "" : ""}
{/if}
</div>
<div class="sidebar-buttons"> <div class="sidebar-buttons">
{#each $filteredChannelsStore as channel (channel.id)} {#each $filteredChannelsStore as channel (channel.id)}
<button on:click="{ selectChannel(channel) }" class="sidebar-button" class:selected={ channel.id === $selectedChannel.id }> <button on:click="{ selectChannel(channel) }" class="sidebar-button" class:selected={ channel.id === $selectedChannel.id }>
@ -118,12 +117,6 @@
</div> </div>
</button> </button>
{/each} {/each}
{#if $userInfoStore && $userInfoStore.permissions.create_channel}
<button on:click="{ () => overlayStore.push(OverlayType.CreateChannel, { community: selectedCommunity.value }) }" class="sidebar-button">
<span class="material-icons-outlined">add</span>
<span class="sidebar-button-text">Create Channel</span>
</button>
{/if}
</div> </div>
{#if !$gatewayStatus.ready} {#if !$gatewayStatus.ready}
<div class="top-bar darker"> <div class="top-bar darker">

View file

@ -0,0 +1,88 @@
<script>
import { avatarUrl } from "../storage";
import { OverlayType, overlayStore, userInfoStore } from "../stores";
import ChipBar from "./ChipBar.svelte";
export let communityLike;
export let isUser = false;
$: options = [
{
id: "SETTINGS",
icon: "settings",
hidden: !isUser,
handle() {
overlayStore.push(OverlayType.Settings);
}
},
{
id: "EDIT_COMMUNITY",
icon: "edit",
hidden: !(!isUser && communityLike && $userInfoStore && communityLike.owner_id === $userInfoStore.id),
handle() {
overlayStore.push(OverlayType.EditCommunity, { community: communityLike });
}
},
{
id: "NEW_CHANNEL",
icon: "add",
hidden: (!$userInfoStore || !$userInfoStore.permissions.create_channel),
handle() {
overlayStore.push(OverlayType.CreateChannel, { community: isUser ? { id: -1 } : communityLike });
}
}
];
let heading = "";
$: if (communityLike) {
heading = communityLike.username || communityLike.name || "";
} else {
heading = "";
}
</script>
<style>
.container {
margin: var(--space-sm);
margin-bottom: 8px;
}
.container .avatar {
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
position: relative;
background-color: var(--background-color-3);
margin-top: -24px;
margin-left: 12px;
border-radius: 9999px;
width: 48px;
height: 48px;
}
.banner {
background-color: var(--background-color-0);
height: 90px;
border-radius: var(--radius-norm);
}
.container .text-bold {
position: relative;
margin: 12px;
margin-top: 8px;
margin-bottom: 6px;
}
</style>
<div class="container">
<div class="banner"></div>
{#if communityLike && communityLike.avatar}
<img class="avatar" src={avatarUrl(communityLike.avatar, 64)} alt=" ">
{:else}
<div class="avatar">{heading && heading[0] ? heading[0] : ""}</div>
{/if}
<div class="text-bold">{ heading }</div>
<ChipBar options={options} smaller={true} />
</div>

View file

@ -0,0 +1,55 @@
<script>
import { overlayStore } from "../../stores";
import { getMessageFromResponse, methods, remoteSignal, responseOk } from "../../request";
import Modal from "./Modal.svelte";
import RpcErrorDisplay from "../rpc/RpcErrorDisplay.svelte";
export let community;
let communityName = community.name;
let buttonsEnabled = true;
let response;
export let close = () => {};
const save = async () => {
buttonsEnabled = false;
response = await remoteSignal(methods.updateCommunityName, community.id, communityName);
buttonsEnabled = true;
if (responseOk(response)) {
close();
}
};
const deleteCommunity = async () => {
buttonsEnabled = false;
const res = await remoteSignal(methods.deleteCommunity, community.id);
if (!responseOk(res)) {
overlayStore.toast(`Couldn't delete community: ${getMessageFromResponse(res)}`);
}
close();
};
</script>
<style>
.delete-button {
color: var(--red-2);
}
</style>
<Modal {close} enter={save}>
<span class="h4" slot="header">Edit Community</span>
<svelte:fragment slot="content">
<RpcErrorDisplay response={response} />
<RpcErrorDisplay validationIndex={1} response={response} />
<label class="input-label">
<span>Community Name</span>
<input class="input full-width" minlength="1" maxlength="32" bind:value={ communityName } />
</label>
</svelte:fragment>
<svelte:fragment slot="footer">
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
<button class="button modal-secondary-action delete-button" on:click="{ deleteCommunity }" disabled="{ !buttonsEnabled }">Delete</button>
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
</svelte:fragment>
</Modal>

View file

@ -11,6 +11,7 @@
import Prompt from "./Prompt.svelte"; import Prompt from "./Prompt.svelte";
import UserInfo from "./UserInfo.svelte"; import UserInfo from "./UserInfo.svelte";
import AddCommunity from "./AddCommunity.svelte"; import AddCommunity from "./AddCommunity.svelte";
import EditCommunity from "./EditCommunity.svelte";
const OverlayComponent = { const OverlayComponent = {
0: CreateChannel, 0: CreateChannel,
@ -23,6 +24,7 @@
7: Prompt, 7: Prompt,
8: UserInfo, 8: UserInfo,
9: AddCommunity, 9: AddCommunity,
10: EditCommunity,
}; };
</script> </script>

View file

@ -147,7 +147,7 @@
<span class="h4" slot="header">Settings</span> <span class="h4" slot="header">Settings</span>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<ChipBar selectedOptionId="ACCOUNT" onSelect={ (tab) => selectedTab = tab } options={[ <ChipBar selectedOptionId="ACCOUNT" onSelect={ (tab) => selectedTab = tab } doHighlight={true} options={[
{ id: "ACCOUNT", text: "Account", icon: "person" }, { id: "ACCOUNT", text: "Account", icon: "person" },
{ id: "PRIVACY", text: "Privacy", icon: "lock" }, { id: "PRIVACY", text: "Privacy", icon: "lock" },
{ id: "APPEARANCE", text: "Appearance", icon: "palette" }, { id: "APPEARANCE", text: "Appearance", icon: "palette" },

View file

@ -6,7 +6,8 @@ import { useDebuggingApi } from './debuggingapi';
import gateway, { GatewayEventType } from './gateway'; import gateway, { GatewayEventType } from './gateway';
import { pluginStore } from './stores'; import { pluginStore } from './stores';
import "@fontsource/material-icons-outlined" import "@fontsource-variable/material-symbols-outlined/opsz.css";
import "@fontsource-variable/open-sans";
import "./styles/global.css"; import "./styles/global.css";
import { timeline } from './timeline'; import { timeline } from './timeline';

View file

@ -401,7 +401,8 @@ export const OverlayType = {
Settings: 6, Settings: 6,
Prompt: 7, Prompt: 7,
UserInfo: 8, UserInfo: 8,
AddCommunity: 9 AddCommunity: 9,
EditCommunity: 10
}; };
class OverlayStore extends Store { class OverlayStore extends Store {
constructor() { constructor() {

View file

@ -1,56 +1,3 @@
/* fonts */
/* FIXME: fonts greatly increase payload size */
/* open-sans-latin-ext-wght-normal */
@font-face {
font-family: 'Open Sans Variable';
font-style: normal;
font-display: swap;
font-weight: 300 800;
src: url(./files/open-sans-latin-ext-wght-normal.woff2) format('woff2-variations');
unicode-range: U+0100-02AF,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* open-sans-latin-wght-normal */
@font-face {
font-family: 'Open Sans Variable';
font-style: normal;
font-display: swap;
font-weight: 300 800;
src: url(./files/open-sans-latin-wght-normal.woff2) format('woff2-variations');
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
font-family: "Iosevka Waffle Web";
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url("/assets/woff2/iosevka-waffle-regular.woff2") format("woff2");
font-display: fallback;
}
@font-face {
font-family: "Iosevka Waffle Web";
font-display: swap;
font-weight: 700;
font-stretch: normal;
font-style: normal;
src: url("/assets/woff2/iosevka-waffle-bold.woff2") format("woff2");
font-display: fallback;
}
@font-face {
font-family: "Iosevka Waffle Web";
font-display: swap;
font-weight: 300;
font-stretch: normal;
font-style: normal;
src: url("/assets/woff2/iosevka-waffle-light.woff2") format("woff2");
font-display: fallback;
}
/* top-level */ /* top-level */
:root { :root {
@ -243,7 +190,6 @@ body {
background-color: var(--background-color-2); background-color: var(--background-color-2);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
contain: content; contain: content;
box-shadow: 0px 0px 8px rgba(91, 91, 97, 0.144);
} }
.modal-header { .modal-header {
@ -549,6 +495,10 @@ body {
.material-icons-outlined, .material-icons { .material-icons-outlined, .material-icons {
font-family: "Material Symbols Outlined Variable";
font-variation-settings: 'opsz' 20;
font-size: 20px;
line-height: 20px;
user-select: none; user-select: none;
color: var(--foreground-special-color-1); color: var(--foreground-special-color-1);
} }

View file

@ -10,16 +10,16 @@
"@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/trace-mapping" "^0.3.9"
"@fontsource-variable/material-symbols-outlined@^5.0.7":
version "5.0.7"
resolved "https://registry.yarnpkg.com/@fontsource-variable/material-symbols-outlined/-/material-symbols-outlined-5.0.7.tgz#9a114edbf2eb8de9527eab0eafc9d7495ca2c83e"
integrity sha512-44V8EQez2d7H305mwHPOu4b6vpVhcklhA8MN7hO5jnfuSeIS3zEzzRS4/4wnHk80F3GFuqgROh2GJg631PmGvQ==
"@fontsource-variable/open-sans@^5.0.6": "@fontsource-variable/open-sans@^5.0.6":
version "5.0.6" version "5.0.6"
resolved "https://registry.yarnpkg.com/@fontsource-variable/open-sans/-/open-sans-5.0.6.tgz#d92c5de9843b999d914e444afda54c39a752d949" resolved "https://registry.yarnpkg.com/@fontsource-variable/open-sans/-/open-sans-5.0.6.tgz#d92c5de9843b999d914e444afda54c39a752d949"
integrity sha512-ZEbqIsXo84fQeQwU2CPyI3W+8QMdbTVBS+CRbWBDA8nPbV9pyRCBy3UceyPrA7l1MJx4qaz5wv5hHQXDadgCzg== integrity sha512-ZEbqIsXo84fQeQwU2CPyI3W+8QMdbTVBS+CRbWBDA8nPbV9pyRCBy3UceyPrA7l1MJx4qaz5wv5hHQXDadgCzg==
"@fontsource/material-icons-outlined@^4.5.4":
version "4.5.4"
resolved "https://registry.yarnpkg.com/@fontsource/material-icons-outlined/-/material-icons-outlined-4.5.4.tgz#23ce468b7c569d1c717061cb8c5a69b3cb3fba12"
integrity sha512-2SLQe/pAlOzoE2Kd5cBxqTgI9U63hf3a7RrCF8GFvgPkYhF6WOcIzFzsLc1Fdf+UhcYS+Hgpp6o8peguwZGK9Q==
"@jridgewell/gen-mapping@^0.3.0": "@jridgewell/gen-mapping@^0.3.0":
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"