Compare commits

...

2 commits

Author SHA1 Message Date
hippoz
7692dd0808
add community profile section to sidebar and improve icons 2023-08-11 00:46:37 +03:00
hippoz
d5e5cadc10
improve community sidebar 2023-08-10 00:21:38 +03:00
13 changed files with 217 additions and 93 deletions

View file

@ -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"
}
}

View file

@ -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" },
]
}),

View file

@ -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>

View file

@ -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>

View file

@ -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,25 @@
margin: var(--space-sm);
margin-bottom: 0;
transition-duration: 200ms;
transition-property: border-radius;
transition-property: border-radius background color;
}
.communities .button:hover, .communities .button.selected {
border-radius: 16px;
background: var(--purple-2);
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 {
@ -65,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 }>
@ -88,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 }>
@ -112,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">

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 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>

View file

@ -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" },

View file

@ -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';

View file

@ -401,7 +401,8 @@ export const OverlayType = {
Settings: 6,
Prompt: 7,
UserInfo: 8,
AddCommunity: 9
AddCommunity: 9,
EditCommunity: 10
};
class OverlayStore extends Store {
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 */
: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);
}

View file

@ -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"