diff --git a/src/client/webapp/elements/context-menu-guild-title.ts b/src/client/webapp/elements/context-menu-guild-title.ts index 037c260..3719e80 100644 --- a/src/client/webapp/elements/context-menu-guild-title.ts +++ b/src/client/webapp/elements/context-menu-guild-title.ts @@ -78,8 +78,6 @@ export default function createGuildTitleContextMenu(document: Document, q: Q, ui } else { const overlay = createGuildSettingsOverlay(document, q, guild, guildMeta); document.body.appendChild(overlay); - q.$$$(overlay, '.text-input').focus(); - ElementsUtil.setCursorToEnd(q.$$$(overlay, '.text-input')); } }); } diff --git a/src/client/webapp/elements/overlay-create-channel.ts b/src/client/webapp/elements/overlay-create-channel.ts index c817dbd..51bc838 100644 --- a/src/client/webapp/elements/overlay-create-channel.ts +++ b/src/client/webapp/elements/overlay-create-channel.ts @@ -21,8 +21,8 @@ export default function createCreateChannelOverlay(document: Document, q: Q, gui { class: 'channel-flavor-divider' }, { class: 'channel-flavor-text', content: '' } ] }, - { class: 'text-input channel-name', placeholder: 'channel-name', content: '', contenteditable: 'plaintext-only' }, - { class: 'text-input channel-flavor-text', placeholder: 'Flavor Text (optional)', content: '' || '', contenteditable: 'plaintext-only' }, + { class: 'text-input channel-name', 'data-placeholder': 'channel-name', content: '', contenteditable: 'plaintext-only' }, + { class: 'text-input channel-flavor-text', 'data-placeholder': 'Flavor Text (optional)', content: '' || '', contenteditable: 'plaintext-only' }, { class: 'lower', content: [ { class: 'error' }, { class: 'buttons', content: [ diff --git a/src/client/webapp/elements/overlay-guild-settings.ts b/src/client/webapp/elements/overlay-guild-settings.ts index efda56f..9e6ac6a 100644 --- a/src/client/webapp/elements/overlay-guild-settings.ts +++ b/src/client/webapp/elements/overlay-guild-settings.ts @@ -15,97 +15,159 @@ import CombinedGuild from '../guild-combined'; export default function createGuildSettingsOverlay(document: Document, q: Q, guild: CombinedGuild, guildMeta: GuildMetadata): HTMLElement { const element = BaseElements.createOverlay(document, { - class: 'content submit-dialog guild-settings', content: [ - { class: 'guild preview', content: [ - { class: 'icon', content: { tag: 'img', src: './img/loading.svg', alt: 'icon' } }, - { class: 'name', content: guildMeta.name } + class: 'content display-swapper guild-settings', content: [ + { class: 'options', content: [ + { class: 'title', content: guildMeta.name }, // TODO: update on change + { class: 'choosable chosen', content: 'Overview' }, + { class: 'choosable', content: 'Channels' }, + { class: 'choosable', content: 'Roles' }, + { class: 'choosable', content: 'Invites' }, ] }, - { class: 'text-input guild-name', placeholder: 'New Guild Name', - contenteditable: 'plaintext-only', content: guildMeta.name }, - { class: 'image-input guild-icon', content: [ - { tag: 'label', class: 'image-input-label button', content: [ - 'Select New Icon', - { class: 'image-input-upload', tag: 'input', type: 'file', accept: '.png,.jpg,.jpeg', style: 'display: none;' } - ] } - ] }, - { class: 'lower', content: [ - { class: 'error' }, - { class: 'buttons', content: [ - { class: 'button submit', content: 'Save Changes' } - ] } + { class: 'display', content: [ + { class: 'scroll', content: [ + { class: 'metadata', content: [ + { tag: 'label', class: 'image-input-label', content: [ + { class: 'image-input', content: [ + { class: 'icon', content: [ + { tag: 'img', class: 'guild-icon', src: './img/loading.svg', alt: 'icon' }, + { class: 'modify', content: [ + { tag: 'img', src: './img/pencil-icon.png' } + ] } + ] }, + ] }, + { tag: 'input', class: 'image-input-upload', type: 'file', accept: '.png,.jpg,.jpeg', style: 'display: none' } + ] }, + { class: 'name', content: [ + { class: 'label', content: 'Guild Name' }, + { tag: 'input', type: 'text', class: 'guild-name', 'placeholder': guildMeta.name, + contenteditable: 'plaintext-only', content: guildMeta.name }, + ] }, + ] } + ] }, + { class: 'popup changes', content: [ + { class: 'content', content: [ + { class: 'tip', content: 'You have unsaved changes' }, + { class: 'actions', content: [ + { class: 'button perdu reset', content: 'Reset' }, + { class: 'button positive save-changes', content: 'Save Changes' } + ] } + ]} + ] }, + { class: 'popup error', content: [ + { class: 'content', content: [ + { class: 'tip' }, + { class: 'actions', content: [ + { class: 'button perdu close', content: 'X' }, + ] } + ] } + ] }, ] } ] }); - (async () => { - (q.$$$(element, '.icon img') as HTMLImageElement).src = await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, guildMeta.iconResourceId); - })(); - let newIconBuff: Buffer | null = null; + let oldName = guildMeta.name; + let newName = guildMeta.name; + + async function updatePopups(errMsg: string | null = null) { + if (errMsg) { + q.$$$(element, '.popup.error .tip').innerText = errMsg; + q.$$$(element, '.popup.error').classList.add('enabled'); + q.$$$(element, '.popup.changes').classList.remove('enabled'); + await ElementsUtil.shakeElement(q.$$$(element, '.popup.error'), 400); + } else if (newIconBuff !== null || newName !== oldName) { + q.$$$(element, '.popup.changes .tip').innerText = 'You have unsaved changes'; // in case it had an error before + q.$$$(element, '.popup.changes .button.save-changes').innerText = 'Save Changes'; // in case it had try again before + q.$$$(element, '.popup.error').classList.remove('enabled'); + q.$$$(element, '.popup.changes').classList.add('enabled'); + } else { + q.$$$(element, '.popup.error').classList.remove('enabled'); + q.$$$(element, '.popup.changes').classList.remove('enabled'); + } + } + + function reset() { + q.$$$(element, 'input.guild-name').value = oldName; + (async () => { + q.$$$(element, '.icon img').src = await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, guildMeta.iconResourceId); + })(); + + newIconBuff = null; + newName = oldName; + } + + reset(); // sets icon + + q.$$$(element, '.popup.error .button.close').addEventListener('click', () => { + updatePopups() + }); + + q.$$$(element, '.popup.changes .button.reset').addEventListener('click', () => { + reset(); + updatePopups(); + }); + BaseElements.bindImageUploadEvents(q.$$$(element, '.image-input-upload') as HTMLInputElement, { maxSize: Globals.MAX_ICON_SIZE, acceptedMimeTypes: [ 'image/png', 'image/jpeg', 'image/jpg' ], - onChangeStart: () => { q.$$$(element, '.error').innerText = ''; }, + onChangeStart: async () => await updatePopups(), onCleared: () => {}, - onError: async (errMsg) => { - q.$$$(element, '.error').innerText = errMsg; - await ElementsUtil.shakeElement(q.$$$(element, '.image-input-upload.guild-icon'), 400); - }, - onLoaded: (buff, src) => { + onError: async (errMsg) => await updatePopups(errMsg), + onLoaded: async (buff, src) => { newIconBuff = buff; - (q.$$$(element, '.guild .icon img') as HTMLImageElement).src = src; + (q.$$$(element, 'img.guild-icon') as HTMLImageElement).src = src; + await updatePopups(); } }); - q.$$$(element, '.text-input').addEventListener('keydown', (e) => { - if (e.key == 'Enter') { + q.$$$(element, 'input.guild-name').addEventListener('input', (e) => { + newName = q.$$$(element, 'input.guild-name').value; + updatePopups(); + }); + + q.$$$(element, 'input.guild-name').addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); - q.$$$(element, '.button.submit').click(); + q.$$$(element, '.button.save-changes').click(); } }); - q.$$$(element, '.text-input').addEventListener('input', () => { - q.$$$(element, '.guild.preview .name').innerText = q.$$$(element, '.text-input').innerText; - }); - let submitting = false; - q.$$$(element, '.button.submit').addEventListener('click', async () => { + q.$$$(element, '.button.save-changes').addEventListener('click', async () => { if (submitting) return; submitting = true; - q.$$$(element, '.error').innerText = ''; - q.$$$(element, '.button.submit').innerText = 'Saving...'; + q.$$$(element, '.button.save-changes').innerText = 'Saving...'; - const newName = q.$$$(element, '.text-input').innerText; - - if (newName == guildMeta.name && newIconBuff == null) { - // nothing changed, close the dialog - element.removeSelf(); + if (newName == oldName && newIconBuff == null) { + // nothing changed + updatePopups(); + submitting = false; return; } let success = false; - if (newName != guildMeta.name && newName.length == 0) { + if (newName != oldName && newName.length == 0) { LOG.warn('attempted to set empty guild name'); - q.$$$(element, '.button.submit').innerText = 'Try Again'; - q.$$$(element, '.error').innerText = 'New name is empty'; - await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400); - } else if (newName != guildMeta.name && newName.length > Globals.MAX_GUILD_NAME_LENGTH) { + q.$$$(element, '.button.save-changes').innerText = 'Try Again'; + q.$$$(element, '.popup.changes .tip').innerText = 'New name is empty'; + await ElementsUtil.shakeElement(q.$$$(element, '.button.save-changes'), 400); + } else if (newName != oldName && newName.length > Globals.MAX_GUILD_NAME_LENGTH) { LOG.warn('attempted to oversized guild name'); - q.$$$(element, '.button.submit').innerText = 'Try Again'; - q.$$$(element, '.error').innerText = 'New name is too long. ' + newName.length + ' > ' + Globals.MAX_GUILD_NAME_LENGTH; - await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400); + q.$$$(element, '.button.save-changes').innerText = 'Try Again'; + q.$$$(element, '.popup.changes .tip').innerText = 'New name is too long. ' + newName.length + ' > ' + Globals.MAX_GUILD_NAME_LENGTH; + await ElementsUtil.shakeElement(q.$$$(element, '.button.save-changes'), 400); } else { // client-size icon size checks are handled above let failed = false; // Set Name - if (newName != guildMeta.name) { + if (newName != oldName) { try { await guild.requestSetGuildName(newName); guildMeta = await guild.fetchMetadata(); } catch (e) { LOG.error('error setting new guild name', e); - q.$$$(element, '.button.submit').innerText = 'Try Again'; - q.$$$(element, '.error').innerText = 'Error setting new guild name'; - await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400); + q.$$$(element, '.button.save-changes').innerText = 'Try Again'; + q.$$$(element, '.popup.changes .tip').innerText = 'Error setting new guild name'; + await ElementsUtil.shakeElement(q.$$$(element, '.button.save-changes'), 400); failed = true; } } @@ -118,7 +180,7 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui } catch (e) { LOG.error('error setting new guild icon', e); q.$$$(element, '.button.submit').innerText = 'Try Again'; - q.$$$(element, '.error').innerText = 'Error setting new guild icon'; + q.$$$(element, '.popup.changes .tip').innerText = 'Error setting new guild icon'; await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400); failed = true; } @@ -127,11 +189,12 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui success = !failed; } - q.$$$(element, '.text-input').setAttribute('contenteditable', 'plaintext-only'); - q.$$$(element, '.text-input').focus(); - if (success) { - element.removeSelf(); + q.$$$(element, '.options .title').innerText = newName; + q.$$$(element, '.guild-name').setAttribute('placeholder', newName); + oldName = newName; + newIconBuff = null; + updatePopups(); } submitting = false; diff --git a/src/client/webapp/elements/overlay-modify-channel.ts b/src/client/webapp/elements/overlay-modify-channel.ts index a900b3e..db88d53 100644 --- a/src/client/webapp/elements/overlay-modify-channel.ts +++ b/src/client/webapp/elements/overlay-modify-channel.ts @@ -21,8 +21,8 @@ export default function createModifyChannelOverlay(document: Document, q: Q, gui { class: 'channel-flavor-divider' }, { class: 'channel-flavor-text', content: channel.flavorText || '' } ] }, - { class: 'text-input channel-name', placeholder: 'channel-name', content: channel.name, contenteditable: 'plaintext-only' }, - { class: 'text-input channel-flavor-text', placeholder: 'Flavor Text (optional)', content: channel.flavorText || '', contenteditable: 'plaintext-only' }, + { class: 'text-input channel-name', 'data-placeholder': 'channel-name', content: channel.name, contenteditable: 'plaintext-only' }, + { class: 'text-input channel-flavor-text', 'data-placeholder': 'Flavor Text (optional)', content: channel.flavorText || '', contenteditable: 'plaintext-only' }, { class: 'lower', content: [ { class: 'error' }, { class: 'buttons', content: [ diff --git a/src/client/webapp/elements/require/base-elements.ts b/src/client/webapp/elements/require/base-elements.ts index b9c53c9..6e059aa 100644 --- a/src/client/webapp/elements/require/base-elements.ts +++ b/src/client/webapp/elements/require/base-elements.ts @@ -353,7 +353,7 @@ export default class BaseElements { const buff = Buffer.from(await file.arrayBuffer()); const typeResult = await FileType.fromBuffer(buff); if (!typeResult || !acceptedMimeTypes.includes(typeResult.mime)) { - await onError('Invalid Image Type. Accepted Types: ' + acceptedMimeTypes.join(', ')); + await onError('Invalid Image Type. Accepted Types: ' + acceptedMimeTypes.map(type => type.replace('image/', '')).join(', ')); return; } let src: string | null = null; diff --git a/src/client/webapp/q-module.ts b/src/client/webapp/q-module.ts index 7038182..c2a9725 100644 --- a/src/client/webapp/q-module.ts +++ b/src/client/webapp/q-module.ts @@ -15,24 +15,24 @@ export default class Q { this.document = document; } - public $(queryString: string): HTMLElement { + public $(queryString: string): T { const element = this.document.querySelector(queryString); if (element === null) { throw new SelectorError(`unable to find [${queryString}] in document`); } - return element; + return element as unknown as T; // dangerous but convenient } public $$(queryString: string): HTMLElement[] { return Array.from(this.document.querySelectorAll(queryString)); } - public $$$(baseElement: HTMLElement, queryString: string): HTMLElement { + public $$$(baseElement: HTMLElement, queryString: string): T { const element = baseElement.querySelector(queryString); if (element === null) { throw new SelectorError(`unable to find [${queryString}] in document`); } - return element; + return element as unknown as T; // dangerous but convenient } public $$$$(baseElement: HTMLElement, queryString: string): HTMLElement[] { diff --git a/src/client/webapp/styles/buttons.scss b/src/client/webapp/styles/buttons.scss index f91a51a..e6789ca 100644 --- a/src/client/webapp/styles/buttons.scss +++ b/src/client/webapp/styles/buttons.scss @@ -11,4 +11,28 @@ &:hover { background-color: $brand-hover; } + + &.positive { + background-color: $background-button-positive; + + &:hover { + background-color: $background-button-positive-hover; + } + } + + &.negative { + background-color: $background-button-negative; + + &:negative { + background-color: $background-button-negative-hover; + } + } + + &.perdu { + background-color: $background-button-perdu; + + &:hover { + background-color: $background-button-perdu-hover; + } + } } diff --git a/src/client/webapp/styles/general.scss b/src/client/webapp/styles/general.scss index a821664..7457f1e 100644 --- a/src/client/webapp/styles/general.scss +++ b/src/client/webapp/styles/general.scss @@ -32,4 +32,5 @@ body { [contenteditable=plaintext-only]:empty::before{ content:attr(data-placeholder); cursor: text; + color: $text-muted; } diff --git a/src/client/webapp/styles/overlays.scss b/src/client/webapp/styles/overlays.scss index 5658c9d..f655862 100644 --- a/src/client/webapp/styles/overlays.scss +++ b/src/client/webapp/styles/overlays.scss @@ -3,429 +3,571 @@ /* Popup Image Overlay */ body > .overlay { - /* Note: skip top 22px so we don't overlay on the title bar */ - position: absolute; - width: 100vw; - height: calc(100vh - 22px); - top: 22px; - left: 0; - display: flex; - justify-content: center; - align-items: center; - background-color: $background-overlay; - - /* Popup Image */ - - .content.popup-image { - display: flex; - flex-flow: column; - align-items: center; - - > img { - max-width: 70vw; - max-height: 70vh; - } - - .download { - background-color: $background-primary; - border-radius: 8px; - padding: 8px; - display: flex; - align-items: center; - margin-top: 8px; - justify-content: space-between; - } - - .download .info { - margin-right: 8px; - } - - .download .info .name { - font-weight: 600; - color: $text-normal; - } - - .download .info .size { - font-size: 12px; - font-weight: 500; - color: $text-sending; - } - } - - /* Drop Target Overlay */ - - .content.drop-target { - box-sizing: border-box; - width: calc(100vw - 32px); - height: calc(100vh - 22px - 32px); - margin: 16px; - border: 4px dashed $background-primary; - border-radius: 16px; - display: flex; - justify-content: center; - align-items: center; - - * { - pointer-events: none; - } - - .message { - border-radius: 8px; - padding: 32px; - background-color: $background-primary; - color: $header-primary; - font-size: 48px; - font-weight: 600; - } - } - - /* Upload Overlay */ - - .content.upload { - background-color: $background-primary; - width: 500px; - border-radius: 12px; - padding: 16px; - - .title { - display: flex; - align-items: flex-start; - margin-bottom: 16px; - } - - .title img { - max-width: 128px; - max-height: 128px; - border-radius: 8px; - margin-right: 16px; - } - - .title .right > *:not(:last-child) { - margin-bottom: 8px; - } - - .title .right > * { - color: $text-normal; - } - - .title .right > .name { - font-weight: 600; - font-size: 1.25em; - color: $header-primary; - } - - .text-input { - color: $text-normal; - background-color: $channeltextarea-background; - border-radius: 8px; - max-height: 100px; - overflow-y: scroll; - padding: 14px 16px 14px 16px; - } - - .text-input:focus { - outline: none; - } - - .lower { - margin-top: 16px; - display: flex; - align-items: center; - } - - .lower .error { - flex: 1; - color: $header-primary; - } - - .buttons { - display: flex; - justify-content: flex-end; - } - } - - /* General Submit Dialog Overlays */ - - .content.submit-dialog { - background-color: $background-secondary; - border-radius: 8px; - - .image-input { - margin: 16px; - display: flex; - align-items: center; - } - - .text-input { - margin: 16px; - color: $text-normal; - background-color: $channeltextarea-background; - border-radius: 8px; - max-height: 100px; - overflow-y: scroll; - padding: 14px 16px; - } - - .text-input:focus { - outline: none; - } - - .lower { - padding: 16px; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - display: flex; - justify-content: space-between; - align-items: center; - background-color: $background-tertiary; - } - - .error { - color: $text-normal; - } - - .buttons { - margin-left: 16px; - display: flex; - } - } - - /* Personalization Overlay */ - - .content.personalize { - min-width: 350px; - - .message { - margin: 16px; - padding: 0 4px; - } - } - - /* guild Settings Overlay */ - - .content.guild-settings { - min-width: 350px; - - .preview { - display: flex; - align-items: center; - margin: 16px; - } - - .preview .icon img { - width: 48px; - height: 48px; - border-radius: 24px; - } - - .preview .icon { - margin-right: 16px; - } - - .preview .name { - color: $header-primary; - font-weight: 500; - } - } - - /* Add guild Overlay */ - - .content.add-guild { - min-width: 350px; - background-color: $background-secondary; - border-radius: 12px; - - .divider { - margin: 16px; - height: 1px; - background-color: $background-primary; - } - - .preview { - margin: 16px; - display: flex; - align-items: center; - } - - .preview .icon { - width: 64px; - height: 64px; - margin-right: 16px; - border-radius: 16px; - } - - .preview .name { - color: $header-primary; - font-size: 1.25em; - font-weight: 600; - } - - .preview .url { - color: $text-normal; - } - - .preview .expires { - color: $text-muted; - font-size: 12px; - font-weight: 600; - } - - .message { - margin: 16px; - padding: 0; - } - - .avatar-input { - margin: 16px; - display: flex; - align-items: center; - } - - .display-name-input { - margin: 16px; - color: $text-normal; - background-color: $channeltextarea-background; - border-radius: 8px; - max-height: 100px; - overflow-y: scroll; - padding: 14px 16px; - } - - .display-name-input:focus { - outline: none; - } - - .lower { - padding: 16px; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - display: flex; - justify-content: space-between; - align-items: center; - background-color: $background-tertiary; - } - - .error { - color: $text-normal; - } - - .error::first-letter { - text-transform: uppercase; - } - - .buttons { - margin-left: 16px; - display: flex; - } - } - - /* Modify Channel Overlay */ - - .content.modify-channel { - min-width: 350px; - max-width: calc(100vw - 80px); - - .preview.channel-title { - border-top-left-radius: 12px; - border-top-right-radius: 12px; - padding-right: 8px; - } - - .text-input.channel-name { - text-transform: lowercase; - } - } - - /* Error Message Overlay */ - - .content.error-message { - background-color: $background-secondary; - padding: 16px; - border-radius: 12px; - - .title { - color: $header-primary; - font-size: 1.25em; - font-weight: 600; - line-height: 1; - margin-bottom: 12px; - } - - .message { - padding: 0; - color: $text-normal; - } - } - - /* View Tokens Overlay */ - .content.token-log { - background-color: $background-secondary; - padding: 16px; - border-radius: 12px; - color: $text-normal; - - .tokens .token-display { - border-radius: 8px; - padding: 8px; - display: flex; - align-items: center; - } - - .tokens .token-display:hover { - background-color: $background-secondary-alt; - } - - .tokens .token-display:last-child { - margin-bottom: 0; - } - - .tokens .instance { - display: flex; - justify-content: space-between; - width: 400px; - margin-right: 8px; - } - - .tokens .instance .left { - text-align: left; - } - - .tokens .instance .right { - text-align: right; - } - - .tokens .instance .member { - font-weight: 600; - font-size: 16px; - } - - .tokens .instance .token { - font-size: 12px; - color: $text-muted; - } - - .tokens .instance .expires, - .tokens .instance .created { - font-size: 14px; - } - - .tokens .buttons { - display: flex; - } - - .tokens .buttons .download { - cursor: pointer; - border-radius: 4px; - padding: 8px; - background-color: $away; - margin-right: 8px; - } - - .tokens .buttons .revoke { - cursor: pointer; - border-radius: 4px; - padding: 8px; - background-color: $busy - } - } + /* Note: skip top 22px so we don't overlay on the title bar */ + position: absolute; + width: 100vw; + height: calc(100vh - 22px); + top: 22px; + left: 0; + display: flex; + justify-content: center; + align-items: center; + background-color: $background-overlay; + + /* General Controls */ + input[type=text], .text-input { + font-size: inherit; + font-family: inherit; + color: $text-normal; + background-color: $background-input; + border: 1px solid $border-input; + border-radius: 3px; + max-height: 100px; + overflow-y: scroll; + padding: 8px; + + &:hover { + border-color: $border-input-hover; + } + + &:focus { + outline: none; + border-color: $border-input-focus; + } + } + + .label { + font-size: 0.75em; + font-weight: bold; + color: $interactive-normal; + text-transform: uppercase; + margin-bottom: 2px; + } + + /* Popup Image */ + + > .content.popup-image { + display: flex; + flex-flow: column; + align-items: center; + + > img { + max-width: 70vw; + max-height: 70vh; + } + + .download { + background-color: $background-primary; + border-radius: 8px; + padding: 8px; + display: flex; + align-items: center; + margin-top: 8px; + justify-content: space-between; + } + + .download .info { + margin-right: 8px; + } + + .download .info .name { + font-weight: 600; + color: $text-normal; + } + + .download .info .size { + font-size: 12px; + font-weight: 500; + color: $text-sending; + } + } + + /* Drop Target Overlay */ + + > .content.drop-target { + box-sizing: border-box; + width: calc(100vw - 32px); + height: calc(100vh - 22px - 32px); + margin: 16px; + border: 4px dashed $background-primary; + border-radius: 16px; + display: flex; + justify-content: center; + align-items: center; + + * { + pointer-events: none; + } + + .message { + border-radius: 8px; + padding: 32px; + background-color: $background-primary; + color: $header-primary; + font-size: 48px; + font-weight: 600; + } + } + + /* Upload Overlay */ + + > .content.upload { + background-color: $background-primary; + width: 500px; + border-radius: 12px; + padding: 16px; + + .title { + display: flex; + align-items: flex-start; + margin-bottom: 16px; + } + + .title img { + max-width: 128px; + max-height: 128px; + border-radius: 8px; + margin-right: 16px; + } + + .title .right > *:not(:last-child) { + margin-bottom: 8px; + } + + .title .right > * { + color: $text-normal; + } + + .title .right > .name { + font-weight: 600; + font-size: 1.25em; + color: $header-primary; + } + + .lower { + margin-top: 16px; + display: flex; + align-items: center; + } + + .lower .error { + flex: 1; + color: $header-primary; + } + + .buttons { + display: flex; + justify-content: flex-end; + } + } + + /* General Submit Dialog Overlays */ + + > .content.submit-dialog { + background-color: $background-secondary; + border-radius: 8px; + + .image-input { + margin: 16px; + display: flex; + align-items: center; + } + + .text-input { + margin: 16px; + color: $text-normal; + background-color: $channeltextarea-background; + border-radius: 8px; + max-height: 100px; + overflow-y: scroll; + padding: 14px 16px; + } + + .text-input:focus { + outline: none; + } + + .lower { + padding: 16px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: $background-tertiary; + } + + .error { + color: $text-normal; + } + + .buttons { + margin-left: 16px; + display: flex; + } + } + + /* Personalization Overlay */ + + > .content.personalize { + min-width: 350px; + + .message { + margin: 16px; + padding: 0 4px; + } + } + + /* Add guild Overlay */ + + > .content.add-guild { + min-width: 350px; + background-color: $background-secondary; + border-radius: 12px; + + .divider { + margin: 16px; + height: 1px; + background-color: $background-primary; + } + + .preview { + margin: 16px; + display: flex; + align-items: center; + } + + .preview .icon { + width: 64px; + height: 64px; + margin-right: 16px; + border-radius: 16px; + } + + .preview .name { + color: $header-primary; + font-size: 1.25em; + font-weight: 600; + } + + .preview .url { + color: $text-normal; + } + + .preview .expires { + color: $text-muted; + font-size: 12px; + font-weight: 600; + } + + .message { + margin: 16px; + padding: 0; + } + + .avatar-input { + margin: 16px; + display: flex; + align-items: center; + } + + .display-name-input { + margin: 16px; + color: $text-normal; + background-color: $channeltextarea-background; + border-radius: 8px; + max-height: 100px; + overflow-y: scroll; + padding: 14px 16px; + } + + .display-name-input:focus { + outline: none; + } + + .lower { + padding: 16px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: $background-tertiary; + } + + .error { + color: $text-normal; + } + + .error::first-letter { + text-transform: uppercase; + } + + .buttons { + margin-left: 16px; + display: flex; + } + } + + /* Modify Channel Overlay */ + + > .content.modify-channel { + min-width: 350px; + max-width: calc(100vw - 80px); + + .preview.channel-title { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + padding-right: 8px; + } + + .text-input.channel-name { + text-transform: lowercase; + } + } + + /* Offers selectors on left side and content on the right side */ + > .content.display-swapper { + height: calc(100vh - 50px - 22px); // fill height + width: 940px - 100px; + display: flex; + + $content-border-radius: 4px; + + > .options { + box-sizing: border-box; + background-color: $background-secondary; + width: 226px; + padding: 32px 16px; + border-top-left-radius: $content-border-radius; + border-bottom-left-radius: $content-border-radius; + + > .title { + font-size: 0.75em; + font-weight: bold; + color: $interactive-normal; + text-transform: uppercase; + padding: 3px 12px; + margin-bottom: 4px; + } + + > .choosable { + color: $interactive-normal; + cursor: pointer; + padding: 6px 12px; + border-radius: 3px; + margin-bottom: 4px; + + &.chosen { + color: $interactive-active; + background-color: $background-modifier-selected + } + + &:not(.chosen):hover { + color: $interactive-hover; + background-color: $background-modifier-hover; + } + } + } + + > .display { + flex: 1; + border-top-right-radius: $content-border-radius; + border-bottom-right-radius: $content-border-radius; + background-color: $background-primary; + position: relative; + + > .scroll { + margin: 32px; + } + + > .popup { + position: absolute; + bottom: 0; + left: 0; + box-sizing: border-box; + width: 100%; + + &:not(.enabled) { + display: none; + } + + .content { + margin: 16px; + padding: 12px 12px 12px 16px; + display: flex; + justify-content: space-between; + align-items: center; + color: $interactive-active; + background-color: $background-popup-message; + border-radius: 8px; + } + + .tip { + font-weight: 500; + } + + .actions { + display: flex; + + .button { + padding: 8px 12px; + font-size: 0.85em; + font-weight: bold; + } + + :not(:last-child) { + margin-right: 4px; + } + } + } + } + } + + /* guild Settings Overlay */ + + > .content.display-swapper.guild-settings { + min-width: 350px; + + .metadata { + display: flex; + margin-bottom: 12px; + + .image-input-label { + cursor: pointer; + } + + .icon { + position: relative; + + > img { + width: 64px; + height: 64px; + border-radius: 8px; + } + + .modify { + position: absolute; + background-color: $brand; + padding: 6px; + border-radius: 16px; + right: -4px; + bottom: -4px; + + > img { + display: block; + width: 10px; + height: 10px; + } + } + } + + .name { + margin-left: 16px; + } + + .guild-name.text-input { + color: $header-primary; + font-weight: 500; + width: 150px; + } + } + + .button.metadata-submit { + display: inline-block; + } + } + + /* Error Message Overlay */ + + > .content.error-message { + background-color: $background-secondary; + padding: 16px; + border-radius: 12px; + + .title { + color: $header-primary; + font-size: 1.25em; + font-weight: 600; + line-height: 1; + margin-bottom: 12px; + } + + .message { + padding: 0; + color: $text-normal; + } + } + + /* View Tokens Overlay */ + > .content.token-log { + background-color: $background-secondary; + padding: 16px; + border-radius: 12px; + color: $text-normal; + + .tokens .token-display { + border-radius: 8px; + padding: 8px; + display: flex; + align-items: center; + } + + .tokens .token-display:hover { + background-color: $background-secondary-alt; + } + + .tokens .token-display:last-child { + margin-bottom: 0; + } + + .tokens .instance { + display: flex; + justify-content: space-between; + width: 400px; + margin-right: 8px; + } + + .tokens .instance .left { + text-align: left; + } + + .tokens .instance .right { + text-align: right; + } + + .tokens .instance .member { + font-weight: 600; + font-size: 16px; + } + + .tokens .instance .token { + font-size: 12px; + color: $text-muted; + } + + .tokens .instance .expires, + .tokens .instance .created { + font-size: 14px; + } + + .tokens .buttons { + display: flex; + } + + .tokens .buttons .download { + cursor: pointer; + border-radius: 4px; + padding: 8px; + background-color: $away; + margin-right: 8px; + } + + .tokens .buttons .revoke { + cursor: pointer; + border-radius: 4px; + padding: 8px; + background-color: $busy + } + } } \ No newline at end of file diff --git a/src/client/webapp/styles/theme.scss b/src/client/webapp/styles/theme.scss index 58db9d5..360372e 100644 --- a/src/client/webapp/styles/theme.scss +++ b/src/client/webapp/styles/theme.scss @@ -2,14 +2,17 @@ $header-primary: #ffffff; $header-secondary: #b9bbbe; + $text-normal: #dcddde; $text-sending: #9ca1a8; $text-muted: #72767d; $text-link: #00b0f4; + $interactive-normal: #b9bbbe; $interactive-hover: #dcddde; $interactive-active: #ffffff; $interactive-muted: #4f545c; + $background-primary: #36393f; $background-secondary: #2f3136; $background-secondary-alt: #292b2f; @@ -19,9 +22,18 @@ $background-modifier-hover: rgba(79, 84, 92, 0.16); $background-modifier-selected: rgba(79, 84, 92, 0.32); $background-modifier-accent: hsla(0, 0%, 100%, 0.06); $background-message-hover: rgba(4, 4, 5, 0.07); -$background-overlay: rgba(0, 0, 0, 0.85); + +$background-overlay: rgba(12, 13, 14, 0.75); +$background-popup-message: rgba(30, 31, 34, 0.75); + +$background-input: #2f3136; +$border-input: #1d1e22; +$border-input-hover: #0b0c0e; +$border-input-focus: #0099ff; + $channels-default: #8e9297; $channeltextarea-background: #40444b; + $brand: #7289ba; /* yea that's a direct copy from discord */ $brand-hover: #677bc4; /* thicc */ $error: #d71f28; /* In fact, not copied! */ @@ -32,3 +44,10 @@ $away: #dab01d; $busy: #ce1515; $item-red: #da4040; + +$background-button-positive: #52a13e; +$background-button-positive-hover: #3f862d; +$background-button-negative: #cc302b; +$background-button-negative-hover: #b82722; +$background-button-perdu: rgba(0, 0, 0, 0); +$background-button-perdu-hover: #1e2024;