add community profile section to sidebar and improve icons
This commit is contained in:
parent
d5e5cadc10
commit
7692dd0808
13 changed files with 215 additions and 97 deletions
|
@ -7,7 +7,6 @@
|
|||
"dev": "rollup -c -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/material-icons-outlined": "^4.5.4",
|
||||
"@rollup/plugin-commonjs": "^23.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-terser": "^0.1.0",
|
||||
|
@ -18,6 +17,7 @@
|
|||
"svelte": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/material-symbols-outlined": "^5.0.7",
|
||||
"@fontsource-variable/open-sans": "^5.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export default {
|
|||
// Copy fonts
|
||||
copy({
|
||||
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" },
|
||||
]
|
||||
}),
|
||||
|
|
|
@ -2,10 +2,24 @@
|
|||
export let options = [];
|
||||
export let selectedOptionId = null;
|
||||
export let onSelect = (_) => {};
|
||||
export let smaller = false;
|
||||
export let doHighlight = false;
|
||||
|
||||
if (selectedOptionId) {
|
||||
onSelect(selectedOptionId);
|
||||
}
|
||||
|
||||
const optionClick = (option) => {
|
||||
if (option) {
|
||||
if (doHighlight) {
|
||||
selectedOptionId = option.id;
|
||||
}
|
||||
onSelect(selectedOptionId);
|
||||
if (option.handle) {
|
||||
option.handle();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -23,9 +37,11 @@
|
|||
color: var(--foreground-color-2);
|
||||
background-color: var(--background-color-2);
|
||||
padding: 0.5em;
|
||||
padding-left: 0.65em;
|
||||
padding-right: 0.65em;
|
||||
margin-top: var(--space-xs);
|
||||
margin-right: var(--space-sm);
|
||||
border-radius: 0.65em;
|
||||
border-radius: 9999px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
@ -42,19 +58,31 @@
|
|||
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);
|
||||
}
|
||||
|
||||
.smaller button {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div>
|
||||
<div class:smaller={smaller}>
|
||||
{#each options as option (option.id)}
|
||||
<button class="button" class:selected={ selectedOptionId === option.id } on:click={ () => { selectedOptionId = option.id; onSelect(selectedOptionId); } }>
|
||||
{#if option.icon}
|
||||
<span class="material-icons-outlined">{ option.icon }</span>
|
||||
{/if}
|
||||
{ option.text }
|
||||
</button>
|
||||
{#if !option.hidden}
|
||||
<button class="button" class:selected={ selectedOptionId === option.id } class:has-text={!!option.text} on:click={ optionClick(option) }>
|
||||
{#if option.icon}
|
||||
<span class="material-icons-outlined">{ option.icon }</span>
|
||||
{/if}
|
||||
{#if option.text}
|
||||
{ option.text }
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
|
@ -96,7 +96,7 @@
|
|||
{:else if renderAs === AttachmentRenderAs.DownloadableFile}
|
||||
<div class="attachment attachment-card">
|
||||
<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>
|
||||
{:else}
|
||||
<div class="attachment attachment-card">Couldn't render attachment</div>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { maybeFly, maybeFlyIf } from "../animations";
|
||||
import { avatarUrl } from "../storage";
|
||||
import { gatewayStatus, overlayStore, selectedChannel, showSidebar, smallViewport, userInfoStore, unreadStore, OverlayType, communities, selectedCommunity, filteredChannelsStore } from "../stores";
|
||||
import SidebarCommunity from "./SidebarCommunity.svelte";
|
||||
|
||||
const selectChannel = (channel) => {
|
||||
if ($smallViewport) {
|
||||
|
@ -45,11 +46,7 @@
|
|||
margin: var(--space-sm);
|
||||
margin-bottom: 0;
|
||||
transition-duration: 200ms;
|
||||
transition-property: border-radius background;
|
||||
}
|
||||
|
||||
.theme--light .communities .button:hover, .theme--light .communities .button.selected {
|
||||
color: var(--background-color-3);
|
||||
transition-property: border-radius background color;
|
||||
}
|
||||
|
||||
.communities .button:hover, .communities .button.selected {
|
||||
|
@ -58,6 +55,18 @@
|
|||
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 {
|
||||
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="communities">
|
||||
<button class="button" on:click={() => selectedCommunity.clear()} class:selected={ $selectedCommunity.id === -1 }>
|
||||
{#if $userInfoStore && $userInfoStore.avatar}
|
||||
<img src={avatarUrl($userInfoStore.avatar, 64)} alt=" ">
|
||||
{:else}
|
||||
<span>{ $userInfoStore ? $userInfoStore.username[0] || "" : "" }</span>
|
||||
{/if}
|
||||
<span class="material-icons-outlined">home</span>
|
||||
</button>
|
||||
{#each $communities as community (community.id)}
|
||||
<button class="button" on:click={() => $selectedCommunity = community} class:selected={ $selectedCommunity.id === community.id }>
|
||||
|
@ -94,13 +99,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="sidebar round-left">
|
||||
<div class="top-bar text-small text-bold">
|
||||
{#if $selectedCommunity.id !== -1}
|
||||
{$selectedCommunity.name || ""}
|
||||
{:else}
|
||||
{$userInfoStore ? $userInfoStore.username || "" : ""}
|
||||
{/if}
|
||||
</div>
|
||||
<SidebarCommunity communityLike={ $selectedCommunity.id === -1 ? $userInfoStore : $selectedCommunity } isUser={ $selectedCommunity.id === -1 } />
|
||||
<div class="sidebar-buttons">
|
||||
{#each $filteredChannelsStore as channel (channel.id)}
|
||||
<button on:click="{ selectChannel(channel) }" class="sidebar-button" class:selected={ channel.id === $selectedChannel.id }>
|
||||
|
@ -118,12 +117,6 @@
|
|||
</div>
|
||||
</button>
|
||||
{/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>
|
||||
{#if !$gatewayStatus.ready}
|
||||
<div class="top-bar darker">
|
||||
|
|
88
frontend/src/components/SidebarCommunity.svelte
Normal file
88
frontend/src/components/SidebarCommunity.svelte
Normal 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>
|
55
frontend/src/components/overlays/EditCommunity.svelte
Normal file
55
frontend/src/components/overlays/EditCommunity.svelte
Normal 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>
|
|
@ -11,6 +11,7 @@
|
|||
import Prompt from "./Prompt.svelte";
|
||||
import UserInfo from "./UserInfo.svelte";
|
||||
import AddCommunity from "./AddCommunity.svelte";
|
||||
import EditCommunity from "./EditCommunity.svelte";
|
||||
|
||||
const OverlayComponent = {
|
||||
0: CreateChannel,
|
||||
|
@ -23,6 +24,7 @@
|
|||
7: Prompt,
|
||||
8: UserInfo,
|
||||
9: AddCommunity,
|
||||
10: EditCommunity,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
<span class="h4" slot="header">Settings</span>
|
||||
|
||||
<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: "PRIVACY", text: "Privacy", icon: "lock" },
|
||||
{ id: "APPEARANCE", text: "Appearance", icon: "palette" },
|
||||
|
|
|
@ -6,7 +6,8 @@ import { useDebuggingApi } from './debuggingapi';
|
|||
import gateway, { GatewayEventType } from './gateway';
|
||||
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 { timeline } from './timeline';
|
||||
|
||||
|
|
|
@ -401,7 +401,8 @@ export const OverlayType = {
|
|||
Settings: 6,
|
||||
Prompt: 7,
|
||||
UserInfo: 8,
|
||||
AddCommunity: 9
|
||||
AddCommunity: 9,
|
||||
EditCommunity: 10
|
||||
};
|
||||
class OverlayStore extends Store {
|
||||
constructor() {
|
||||
|
|
|
@ -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 */
|
||||
|
||||
:root {
|
||||
|
@ -243,7 +190,6 @@ body {
|
|||
background-color: var(--background-color-2);
|
||||
border-radius: var(--radius-lg);
|
||||
contain: content;
|
||||
box-shadow: 0px 0px 8px rgba(91, 91, 97, 0.144);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
@ -549,6 +495,10 @@ body {
|
|||
|
||||
|
||||
.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;
|
||||
color: var(--foreground-special-color-1);
|
||||
}
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@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":
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource-variable/open-sans/-/open-sans-5.0.6.tgz#d92c5de9843b999d914e444afda54c39a752d949"
|
||||
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":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
|
||||
|
|
Loading…
Reference in a new issue