From 1909fe5c609becb3268dae846129d9836369c532 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sun, 18 Oct 2020 02:19:56 +0300 Subject: [PATCH] Prepare for chat multiselect: Chat selection by context menu Clear selection Fix highlighting bubbles Fix highlight first unread bubble --- src/components/chat/contextMenu.ts | 78 ++++++--- src/components/chat/selection.ts | 106 ++++++++++++ src/components/checkbox.ts | 9 +- src/components/wrappers.ts | 2 +- src/lib/appManagers/appDocsManager.ts | 2 + src/lib/appManagers/appImManager.ts | 29 +++- src/lib/appManagers/appMessagesManager.ts | 10 ++ src/lib/appManagers/appPhotosManager.ts | 2 +- src/scss/partials/_chat.scss | 64 +++++-- src/scss/partials/_chatBubble.scss | 96 ++++++++++- src/scss/partials/_checkbox.scss | 195 ++++++++++++++++++++++ src/scss/partials/_document.scss | 1 + src/scss/partials/_leftSidebar.scss | 36 ++-- src/scss/style.scss | 171 +------------------ 14 files changed, 558 insertions(+), 243 deletions(-) create mode 100644 src/components/chat/selection.ts create mode 100644 src/scss/partials/_checkbox.scss diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index e10b6698..db2a5d53 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -11,8 +11,8 @@ import { PopupButton } from "../popup"; import PopupPeer from "../popupPeer"; import appSidebarRight from "../sidebarRight"; -export class ChatContextMenu { - private buttons: (ButtonMenuItemOptions & {verify: (peerID: number, msgID: number, target: HTMLElement) => boolean})[]; +export default class ChatContextMenu { + private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean})[]; private element: HTMLElement; private target: HTMLElement; @@ -26,22 +26,19 @@ export class ChatContextMenu { this.init = null; } - let bubble: HTMLElement = null; + let bubble: HTMLElement, bubbleContainer: HTMLElement; try { - bubble = findUpClassName(e.target, 'bubble__container'); + bubbleContainer = findUpClassName(e.target, 'bubble__container'); + bubble = bubbleContainer ? bubbleContainer.parentElement : findUpClassName(e.target, 'bubble'); } catch(e) {} - if(!bubble) return; - if(e instanceof MouseEvent) e.preventDefault(); if(this.element.classList.contains('active')) { return false; } if(e instanceof MouseEvent) e.cancelBubble = true; - - bubble = bubble.parentElement as HTMLDivElement; // bc container - + const msgID = +bubble.dataset.mid; if(!msgID) return; @@ -57,7 +54,9 @@ export class ChatContextMenu { } this.buttons.forEach(button => { - const good = button.verify(this.peerID, this.msgID, this.target); + const good = bubbleContainer ? + button.verify() : + button.notDirect && button.notDirect() && button.verify(); button.element.classList.toggle('hide', !good); }); @@ -77,58 +76,75 @@ export class ChatContextMenu { icon: 'reply', text: 'Reply', onClick: this.onReplyClick, - verify: (peerID: number, msgID: number) => (peerID > 0 || appChatsManager.hasRights(-peerID, 'send')) && msgID > 0 + verify: () => (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0 }, { icon: 'edit', text: 'Edit', onClick: this.onEditClick, - verify: (peerID: number, msgID: number) => appMessagesManager.canEditMessage(msgID, 'text') + verify: () => appMessagesManager.canEditMessage(this.msgID, 'text') }, { icon: 'copy', text: 'Copy', onClick: this.onCopyClick, - verify: (peerID: number, msgID: number) => !!appMessagesManager.getMessage(msgID).message + verify: () => !!appMessagesManager.getMessage(this.msgID).message }, { icon: 'pin', text: 'Pin', onClick: this.onPinClick, - verify: (peerID: number, msgID: number) => { - const message = appMessagesManager.getMessage(msgID); - return msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))); + verify: () => { + const message = appMessagesManager.getMessage(this.msgID); + return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID == $rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin'))); } }, { icon: 'unpin', text: 'Unpin', onClick: this.onUnpinClick, - verify: (peerID: number, msgID: number) => appImManager.pinnedMsgID == msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) + verify: () => appImManager.pinnedMsgID == this.msgID && (this.peerID == $rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin'))) }, { icon: 'revote', text: 'Revote', onClick: this.onRetractVote, - verify: (peerID: number, msgID) => { - const message = appMessagesManager.getMessage(msgID); + verify: () => { + const message = appMessagesManager.getMessage(this.msgID); const poll = message.media?.poll as Poll; return poll && poll.chosenIndexes.length && !poll.pFlags.closed && !poll.pFlags.quiz; - } + } }, { - icon: 'lock', + icon: 'stop', text: 'Stop poll', onClick: this.onStopPoll, - verify: (peerID: number, msgID) => { - const message = appMessagesManager.getMessage(msgID); + verify: () => { + const message = appMessagesManager.getMessage(this.msgID); const poll = message.media?.poll; - return appMessagesManager.canEditMessage(msgID, 'poll') && poll && !poll.pFlags.closed && msgID > 0; - } + return appMessagesManager.canEditMessage(this.msgID, 'poll') && poll && !poll.pFlags.closed && this.msgID > 0; + } }, { icon: 'forward', text: 'Forward', onClick: this.onForwardClick, - verify: (peerID: number, msgID: number) => msgID > 0 + verify: () => this.msgID > 0 + }, { + icon: 'revote', + text: 'Select', + onClick: this.onSelectClick, + verify: () => { + const message = appMessagesManager.getMessage(this.msgID); + return !message.action && !appImManager.chatSelection.selectedMids.has(this.msgID); + }, + notDirect: () => true + }, { + icon: 'revote', + text: 'Clear selection', + onClick: this.onClearSelectionClick, + verify: () => { + return appImManager.chatSelection.selectedMids.has(this.msgID); + }, + notDirect: () => appImManager.chatSelection.selectedMids.has(this.msgID) }, { icon: 'delete danger', text: 'Delete', onClick: this.onDeleteClick, - verify: (peerID: number, msgID: number) => peerID > 0 || appMessagesManager.getMessage(msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-peerID, 'deleteRevoke') + verify: () => this.peerID > 0 || appMessagesManager.getMessage(this.msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-this.peerID, 'deleteRevoke') }]; this.element = ButtonMenu(this.buttons); @@ -193,6 +209,14 @@ export class ChatContextMenu { appSidebarRight.forwardTab.open([this.msgID]); }; + private onSelectClick = () => { + appImManager.chatSelection.toggleByBubble(findUpClassName(this.target, 'bubble')); + }; + + private onClearSelectionClick = () => { + appImManager.chatSelection.cancelSelection(); + }; + private onDeleteClick = () => { const peerID = $rootScope.selectedPeerID; const firstName = appPeersManager.getPeerTitle(peerID, false, true); diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts new file mode 100644 index 00000000..a245da51 --- /dev/null +++ b/src/components/chat/selection.ts @@ -0,0 +1,106 @@ +import type { AppImManager } from "../../lib/appManagers/appImManager"; +import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; +import CheckboxField from "../checkbox"; + +export default class ChatSelection { + public selectedMids: Set = new Set(); + public isSelecting = false; + + constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) { + + } + + public toggleBubbleCheckbox(bubble: HTMLElement, show: boolean) { + const hasCheckbox = !!this.getCheckboxInputFromBubble(bubble); + if(show) { + if(hasCheckbox) return; + + const checkboxField = CheckboxField('', bubble.dataset.mid, true); + checkboxField.label.classList.add('bubble-select-checkbox'); + + // * if it is a render of new message + const mid = +bubble.dataset.mid; + if(this.selectedMids.has(mid)) { + checkboxField.input.checked = true; + bubble.classList.add('is-selected'); + } + + bubble.append(checkboxField.label); + } else if(hasCheckbox) { + bubble.lastElementChild.remove(); + } + } + + public getCheckboxInputFromBubble(bubble: HTMLElement) { + return bubble.lastElementChild.tagName == 'LABEL' && bubble.lastElementChild.firstElementChild as HTMLInputElement; + } + + public toggleSelection() { + const wasSelecting = this.isSelecting; + this.isSelecting = this.selectedMids.size > 0; + + if(wasSelecting == this.isSelecting) return; + + this.appImManager.bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size); + + for(const mid in this.appImManager.bubbles) { + const bubble = this.appImManager.bubbles[mid]; + this.toggleBubbleCheckbox(bubble, this.isSelecting); + } + } + + public cancelSelection() { + for(const mid of this.selectedMids) { + const bubble = this.appImManager.bubbles[mid]; + if(bubble) { + this.toggleByBubble(bubble); + } + } + + this.selectedMids.clear(); + this.toggleSelection(); + } + + public cleanup() { + this.isSelecting = false; + this.selectedMids.clear(); + this.appImManager.bubblesContainer.classList.remove('is-selecting'); + } + + public toggleByBubble(bubble: HTMLElement) { + const mid = +bubble.dataset.mid; + const mids = this.appMessagesManager.getMidsByMid(mid); + + const found = mids.find(mid => this.selectedMids.has(mid)); + if(found) { + mids.forEach(mid => this.selectedMids.delete(mid)); + } else { + mids.forEach(mid => this.selectedMids.add(mid)); + } + + this.toggleBubbleCheckbox(bubble, true); + const input = this.getCheckboxInputFromBubble(bubble); + input.checked = !found; + + this.toggleSelection(); + if(found) { + bubble.classList.add('backwards'); + bubble.dataset.timeout = '' + setTimeout(() => { + delete bubble.dataset.timeout; + bubble.classList.remove('backwards', 'is-selected'); + }, 200); + } else { + bubble.classList.remove('backwards'); + const timeout = bubble.dataset.timeout; + if(timeout !== undefined) { + clearTimeout(+timeout); + } + + bubble.classList.add('is-selected'); + } + } + + public selectMessage(mid: number) { + + } +} \ No newline at end of file diff --git a/src/components/checkbox.ts b/src/components/checkbox.ts index de7aca4f..bc97f79a 100644 --- a/src/components/checkbox.ts +++ b/src/components/checkbox.ts @@ -1,13 +1,16 @@ -const CheckboxField = (text: string, name: string) => { +const CheckboxField = (text: string, name: string, round = false) => { const label = document.createElement('label'); - label.classList.add('checkbox-field'); + label.classList.add(round ? 'checkbox-field-round' : 'checkbox-field'); const input = document.createElement('input'); input.type = 'checkbox'; input.id = 'input-' + name; const span = document.createElement('span'); - span.innerText = text; + span.classList.add('checkbox-caption'); + if(text) { + span.innerText = text; + } label.append(input, span); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index e1d0391f..6c8c646c 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -716,7 +716,7 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo const items: {size: PhotoSize.photoSize, media: any, message: any}[] = []; // !higher msgID will be the FIRST in album - const storage = Object.keys(appMessagesManager.groupedMessagesStorage[groupID]).map(id => +id).sort((a, b) => a - b); + const storage = appMessagesManager.getMidsByAlbum(groupID); for(const mid of storage) { const m = appMessagesManager.getMessage(mid); const media = m.media.photo || m.media.document; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 169496d6..6558d4ba 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -238,6 +238,8 @@ class AppDocsManager { if(!thumb.url) { if('bytes' in thumb) { + // * exclude from state + defineNotNumerableProperties(thumb, ['url']); thumb.url = appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker); } else { //return this.getFileURL(doc, false, thumb); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 5dfbeeb1..5d43efe1 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -5,12 +5,14 @@ import AudioElement from '../../components/audio'; import AvatarElement from '../../components/avatar'; import BubbleGroups from '../../components/bubbleGroups'; import { ChatAudio } from '../../components/chat/audio'; -import { ChatContextMenu } from '../../components/chat/contextMenu'; +import ChatContextMenu from '../../components/chat/contextMenu'; import { ChatInput } from '../../components/chat/input'; import { MessageRender } from '../../components/chat/messageRender'; import PinnedContainer from '../../components/chat/pinnedContainer'; import ReplyContainer from '../../components/chat/replyContainer'; import { ChatSearch } from '../../components/chat/search'; +import ChatSelection from '../../components/chat/selection'; +import CheckboxField from '../../components/checkbox'; import { horizontalMenu } from '../../components/horizontalMenu'; import LazyLoadQueue from '../../components/lazyLoadQueue'; import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc'; @@ -74,6 +76,7 @@ export class AppImManager { public chatInputC: ChatInput; public chatAudio: ChatAudio; + public chatSelection: ChatSelection; private menuButtons: { search: HTMLButtonElement @@ -167,6 +170,8 @@ export class AppImManager { this.selectTab(0); parseMenuButtonsTo(this.menuButtons, this.columnEl.querySelector('.chat-more-button').firstElementChild.children); + + this.chatSelection = new ChatSelection(this, appMessagesManager/* this.bubblesContainer, this.bubbles */); this.chatAudio = new ChatAudio(); this.chatInfo.nextElementSibling.prepend(this.chatAudio.divAndCaption.container); @@ -430,6 +435,14 @@ export class AppImManager { return; } + // ! Trusted - due to audio autoclick + if(this.chatSelection.isSelecting && !bubble.classList.contains('service') && e.isTrusted) { + cancelEvent(e); + //console.log('bubble click', e); + this.chatSelection.toggleByBubble(bubble); + return; + } + let contactDiv: HTMLElement = findUpClassName(target, 'contact'); if(contactDiv) { this.setPeer(+contactDiv.dataset.peerID); @@ -1261,6 +1274,8 @@ export class AppImManager { let peerID = this.peerID; this.peerChanged = true; + this.chatSelection.cleanup(); + this.avatarEl.setAttribute('peer', '' + this.peerID); this.avatarEl.update(); @@ -1381,13 +1396,13 @@ export class AppImManager { public highlightBubble(element: HTMLElement) { if(element.dataset.timeout) { clearTimeout(+element.dataset.timeout); - element.classList.remove('is-selected'); + element.classList.remove('is-highlighted'); void element.offsetWidth; // reflow } - element.classList.add('is-selected'); + element.classList.add('is-highlighted'); element.dataset.timeout = '' + setTimeout(() => { - element.classList.remove('is-selected'); + element.classList.remove('is-highlighted'); delete element.dataset.timeout; }, 2000); } @@ -1575,7 +1590,7 @@ export class AppImManager { bubble.appendChild(bubbleContainer); this.bubbles[+message.mid] = bubble; } else { - const save = ['is-selected']; + const save = ['is-highlighted']; const wasClassNames = bubble.className.split(' '); const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c))); bubble.className = classNames.join(' '); @@ -1592,6 +1607,10 @@ export class AppImManager { bubble.dataset.mid = message.mid; + if(this.chatSelection.isSelecting) { + this.chatSelection.toggleBubbleCheckbox(bubble, true); + } + if(message._ == 'messageService') { let action = message.action; let _ = action._; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 8fad6ea2..76bec378 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -2195,6 +2195,16 @@ export class AppMessagesManager { }); } + public getMidsByAlbum(grouped_id: string) { + return Object.keys(this.groupedMessagesStorage[grouped_id]).map(id => +id).sort((a, b) => a - b); + } + + public getMidsByMid(mid: number) { + const message = this.messagesStorage[mid]; + if(message?.grouped_id) return this.getMidsByAlbum(message.grouped_id); + else return [mid]; + } + public saveMessages(messages: any[], options: { isEdited?: boolean } = {}) { diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index d40b1555..a61c3455 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -154,7 +154,7 @@ export class AppPhotosManager { } public getPreviewURLFromThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) { - return thumb.url ?? (thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); + return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); } public setAttachmentPreview(bytes: Uint8Array | number[], element: HTMLElement | SVGForeignObjectElement, isSticker = false, background = false) { diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 54749f06..b7fdfbe7 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1,8 +1,12 @@ +$btn-send-margin: .5625rem; +$chat-input-size: 3rem; +$chat-input-handhelds-size: 2.875rem; + #bubble-contextmenu > div { - padding: 0 84px 0 16px; + padding: 0 5.25 0 1rem; @include respond-to(handhelds) { - padding: 0 60px 0 16px; + padding: 0 3.75rem 0 1rem; } } @@ -10,10 +14,10 @@ width: 100%; background-color: #fff; user-select: none; - box-shadow: 0px 1px 5px -1px rgba(0,0,0,0.21); + box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21); z-index: 1; - min-height: 56px; - max-height: 56px; + min-height: 3.5rem; + max-height: 3.5rem; // border-bottom: 1px solid #DADCE0; @include respond-to(handhelds) { @@ -248,6 +252,7 @@ width: 100%; padding: 0 .5rem; flex: 0 0 auto; + position: relative; @include respond-to(not-handhelds) { padding-left: 1rem; @@ -311,9 +316,19 @@ #btn-record-cancel { visibility: hidden; opacity: 0; - transition: width .1s .1s, margin-right .1s .1s, visibility 0s .1s, opacity .1s 0s; + transition: visibility 0s .1s, opacity .1s 0s; padding: 0; z-index: 3; + position: absolute; + right: 0; + top: 0; + + // here percents can be used since there are no other transforms + transform: translateX(calc(-100% + #{-1rem + -$btn-send-margin})); + + @include respond-to(handhelds) { + transform: translateX(calc(-100% + #{-.5rem + -$btn-send-margin})); + } } .btn-send-container { @@ -404,8 +419,7 @@ #btn-record-cancel { opacity: 1; visibility: visible; - margin-right: 9px; - transition: width .1s, margin-right .1s, visibility 0s .1s, opacity .1s .1s; + transition: visibility 0s .1s, opacity .1s .1s; } // unlock @@ -413,6 +427,14 @@ pointer-events: all; } + .input-message { + width: calc(100% - #{$chat-input-size * 2 + $btn-send-margin * 3 + $btn-send-margin / 2}); + + @include respond-to(handhelds) { + width: calc(100% - #{$chat-input-handhelds-size * 2 + $btn-send-margin * 2}); + } + } + #attach-file { display: none; } @@ -544,29 +566,30 @@ display: flex; align-items: center; flex-direction: column; - width: calc(100% - 3.75rem); + width: calc(100% - #{$chat-input-size + $btn-send-margin + $btn-send-margin / 2}); justify-content: center; background-color: #fff; border-radius: 12px; border-bottom-right-radius: 0; box-shadow: 0 1px 2px 0 rgba(16, 35, 47, .07); - margin-right: 9px; padding: 4.5px .5rem; /* padding: 3px .5rem 6px .5rem; */ - min-height: 54px; + min-height: $chat-input-size; max-height: 30rem; caret-color: $button-primary-background; - flex: 1; + flex: 0 0 auto; position: relative; z-index: 3; + transition: width .1s; @include respond-to(handhelds) { - min-height: 46px; + width: calc(100% - #{$chat-input-handhelds-size + $btn-send-margin}); + min-height: $chat-input-handhelds-size; padding: .5px .5rem; } @include respond-to(esg-bottom) { - min-height: 46px; + min-height: $chat-input-handhelds-size; padding: .5px .5rem; } @@ -984,6 +1007,19 @@ } } + &.is-selecting { + cursor: default !important; + user-select: none; + + .is-in .bubble__container { + transform: translateX(2.5rem); + } + } + + &.is-selecting > .scrollable::-webkit-scrollbar { + display: none; + } + > .scrollable { height: auto; /* position: absolute; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 8f20ea5c..26f563e1 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -45,21 +45,42 @@ $bubble-margin: .25rem; z-index: 1; margin: 0 auto; - &.is-selected { + &.is-highlighted, &.is-selected, /* #bubbles.is-selecting */ & { &:after { position: absolute; left: -50%; - top: 0; - height: 100%; + /* top: 0; + bottom: 0; */ + top: #{$bubble-margin / 2}; + bottom: -#{$bubble-margin / 2}; content: " "; - background-color: rgba(0, 132, 255, .3); - animation: bubbleSelected 2s linear; z-index: 1; } + } + /* &.is-highlighted, &.is-selected { &:not(.is-group-last):after { - height: calc(100% + $bubble-margin); + height: calc(100% + #{$bubble-margin / 2}) !important; } + + & + &:not(.is-group-last) { + &:after { + top: .125rem !important; + height: calc(100% - #{$bubble-margin / 2}) !important; + } + } + } */ + + // ! if turn this on, there will be an empty space + /* &.is-highlighted, &.is-selected { + &.is-group-last:after { + bottom: #{$bubble-margin / 2} !important; + } + } */ + + &.is-highlighted:after { + background-color: rgba(0, 132, 255, .3); + animation: bubbleSelected 2s linear; } &.is-first-unread { @@ -78,10 +99,30 @@ $bubble-margin: .25rem; font-weight: 500; font-size: 15px; background-color: rgba(255, 255, 255, 0.95); + z-index: 2; + position: relative; + } + + &.is-highlighted, &.is-selected { + &:after { + top: #{2rem + $bubble-margin} !important; + } } } - &.is-selected:after, &.is-first-unread:before { + &.is-selected { + &:after { + background-color: rgba(77, 142, 80, .4); + animation: fade-in-opacity .2s linear forwards; + } + + &.backwards:after { + animation: fade-in-backwards-opacity .2s linear forwards; + } + } + + //&.is-highlighted:after, &.is-first-unread:before, &.is-selected:after { + &:after, &:before { width: 200%; display: block; } @@ -114,6 +155,45 @@ $bubble-margin: .25rem; cursor: pointer; } } + + &-select-checkbox { + z-index: 2; + position: absolute; + left: 0; + //bottom: .25rem; // * by avatar + left: 0; + top: 50%; + transform: translateY(-50%); + display: flex; + + [type="checkbox"] { + &:not(:checked) + .checkbox-caption:after { + + } + + &:checked + .checkbox-caption { + &:after { + background-color: #61c642; + } + } + } + + .checkbox-caption { + &:before { + top: 7px !important; + left: 3px !important; + width: 6px !important; + height: 11px !important; + } + + &:after { + box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, .4); + border: 2px solid #fff !important; + width: 22px; + height: 22px; + } + } + } &__container { //min-width: 60px; @@ -128,6 +208,7 @@ $bubble-margin: .25rem; width: max-content; height: fit-content; z-index: 2; + transition: .2s transform; @include respond-to(not-handhelds) { max-width: 85%; @@ -408,6 +489,7 @@ $bubble-margin: .25rem; max-width: 100%; border-radius: inherit; overflow: hidden; + user-select: none; display: flex; // lol justify-content: center; diff --git a/src/scss/partials/_checkbox.scss b/src/scss/partials/_checkbox.scss new file mode 100644 index 00000000..6b67996b --- /dev/null +++ b/src/scss/partials/_checkbox.scss @@ -0,0 +1,195 @@ +.checkbox-field { + margin: 1.25rem 0; + display: block; + text-align: left; + padding: 0 1.125rem; + /* font-weight: 500; */ + position: relative; + + @include respond-to(handhelds) { + margin-bottom: 27px; + } +} + +.checkbox-field-round { + display: block; + text-align: left; + + [type="checkbox"] { + &:checked + .checkbox-caption { + &:before { + top: 5px; + left: 0px; + } + + &:after { + background-color: #4EA4F6; + border: none; + } + } + } + + .checkbox-caption:after { + border-radius: 50%; + height: 20px; + width: 20px; + border-color: #dadbdc; + } +} + +.radio-field { + display: block; + position: relative; + padding-left: 3.5rem; + text-align: left; + margin: 1.25rem 0; + line-height: 1.5rem; + cursor: pointer; + + &.hidden-widget { + cursor: default; + + .radio-field-main { + &::before, &::after { + visibility: hidden; + } + } + } + + > input { + &:checked { + & ~ .radio-field-main { + &::before { + border-color: $button-primary-background; + } + + &::after { + opacity: 1; + } + } + } + } + + .radio-field-main { + &::before, &::after { + content: ''; + display: block; + position: absolute; + left: .25rem; + top: 50%; + width: 1.25rem; + height: 1.25rem; + transform: translateY(-50%); + } + + &::before { + border: 2px solid #8d969c; + border-radius: 50%; + background-color: white; + opacity: 1; + transition: border-color .1s ease, opacity .1s ease; + } + + &::after { + left: .5625rem; + width: .625rem; + height: .625rem; + border-radius: 50%; + background: $button-primary-background; + opacity: 0; + transition: opacity .1s ease; + } + + /* .label { + display: block; + word-break: break-word; + } + + .subLabel { + display: block; + font-size: 0.875rem; + line-height: 1rem; + color: var(--color-text-secondary); + } */ + } +} + +[type="checkbox"], [type="radio"] { + box-sizing: border-box; + padding: 0; + opacity: 0; + z-index: var(--z-below); + position: absolute; +} + +[type="checkbox"] { + & + span { + position: relative; + padding-left: 3.5rem; + cursor: pointer; + display: inline-block; + height: 25px; + line-height: 25px; + user-select: none; + transition: .2s opacity; + + &:before, &:after { + content: ''; + left: 0; + position: absolute; + transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s; + } + + &:before { + border-radius: 2px; + z-index: 1; + } + + &:after { + height: 18px; + width: 18px; + z-index: 0; + border: 2px solid $button-primary-background; + border-radius: 3px; + top: 50%; + transform: translateY(-50%); + } + } + + &:not(:checked) + span:before { + width: 0; + height: 0; + border: 2px solid transparent; + left: 6px; + top: 10px; + transform: rotateZ(45deg); + transform-origin: 100% 100%; + } + + &:checked + span:before { + top: 4px; + left: -1px; + width: 8px; + height: 14px; + border-top: 2px solid transparent; + border-left: 2px solid transparent; + border-right: 2px solid #fff; + border-bottom: 2px solid #fff; + transform: rotateZ(45deg); + transform-origin: 100% 100%; + } + + &:not(:checked) + span:after { + background-color: transparent; + border-color: #8d969c; + } + + &:checked + span:after { + background-color: $button-primary-background; + } + + &:disabled + span { + cursor: default; + opacity: .25; + } +} \ No newline at end of file diff --git a/src/scss/partials/_document.scss b/src/scss/partials/_document.scss index 84616090..f8e4ce9f 100644 --- a/src/scss/partials/_document.scss +++ b/src/scss/partials/_document.scss @@ -105,6 +105,7 @@ justify-content: center; cursor: pointer; position: relative; + user-select: none; &-ico, &-download { position: absolute; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index a29968a7..14c20acd 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -582,11 +582,6 @@ padding-left: 38px; } } - - [type="checkbox"]:checked+span:before { - top: 5px; - left: 0px; - } } } @@ -670,22 +665,33 @@ margin-top: 10px; } - [type="checkbox"]+span { + [type="checkbox"] + span { padding-left: 26px; } } } - .checkbox [type="checkbox"]+span:after { - border-radius: 50%; - height: 20px; - width: 20px; - border-color: #dadbdc; - } + .checkbox [type="checkbox"] { + & + span:after { + border-radius: 50%; + height: 20px; + width: 20px; + border-color: #dadbdc; + } - .checkbox [type="checkbox"]:checked+span:after { - background-color: #4EA4F6; - border: none; + &:checked { + & + span { + &:before { + top: 5px; + left: 0px; + } + + &:after { + background-color: #4EA4F6; + border: none; + } + } + } } .folder-category-button { diff --git a/src/scss/style.scss b/src/scss/style.scss index d5199b98..fe6db5f3 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -100,6 +100,7 @@ $floating-left-sidebar: 925px; } @import "partials/ico"; +@import "partials/checkbox"; @import "partials/chatlist"; @import "partials/chat"; @import "partials/chatBubble"; @@ -707,176 +708,6 @@ hr { } } -.checkbox-field { - margin: 1.25rem 0; - display: block; - text-align: left; - padding: 0 1.125rem; - /* font-weight: 500; */ - position: relative; - - @include respond-to(handhelds) { - margin-bottom: 27px; - } -} - -.radio-field { - display: block; - position: relative; - padding-left: 3.5rem; - text-align: left; - margin: 1.25rem 0; - line-height: 1.5rem; - cursor: pointer; - - &.hidden-widget { - cursor: default; - - .radio-field-main { - &::before, &::after { - visibility: hidden; - } - } - } - - > input { - &:checked { - & ~ .radio-field-main { - &::before { - border-color: $button-primary-background; - } - - &::after { - opacity: 1; - } - } - } - } - - .radio-field-main { - &::before, &::after { - content: ''; - display: block; - position: absolute; - left: .25rem; - top: 50%; - width: 1.25rem; - height: 1.25rem; - transform: translateY(-50%); - } - - &::before { - border: 2px solid #8d969c; - border-radius: 50%; - background-color: white; - opacity: 1; - transition: border-color .1s ease, opacity .1s ease; - } - - &::after { - left: .5625rem; - width: .625rem; - height: .625rem; - border-radius: 50%; - background: $button-primary-background; - opacity: 0; - transition: opacity .1s ease; - } - - /* .label { - display: block; - word-break: break-word; - } - - .subLabel { - display: block; - font-size: 0.875rem; - line-height: 1rem; - color: var(--color-text-secondary); - } */ - } -} - -[type="checkbox"], [type="radio"] { - box-sizing: border-box; - padding: 0; - opacity: 0; - z-index: var(--z-below); - position: absolute; -} - -[type="checkbox"] { - & + span { - position: relative; - padding-left: 3.5rem; - cursor: pointer; - display: inline-block; - height: 25px; - line-height: 25px; - user-select: none; - transition: .2s opacity; - - &:before, &:after { - content: ''; - left: 0; - position: absolute; - transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s; - } - - &:before { - border-radius: 2px; - z-index: 1; - } - - &:after { - height: 18px; - width: 18px; - z-index: 0; - border: 2px solid $button-primary-background; - border-radius: 3px; - top: 50%; - transform: translateY(-50%); - } - } - - &:not(:checked) + span:before { - width: 0; - height: 0; - border: 2px solid transparent; - left: 6px; - top: 10px; - transform: rotateZ(45deg); - transform-origin: 100% 100%; - } - - &:checked + span:before { - top: 4px; - left: -1px; - width: 8px; - height: 14px; - border-top: 2px solid transparent; - border-left: 2px solid transparent; - border-right: 2px solid #fff; - border-bottom: 2px solid #fff; - transform: rotateZ(45deg); - transform-origin: 100% 100%; - } - - &:not(:checked) + span:after { - background-color: transparent; - border-color: #8d969c; - } - - &:checked + span:after { - background-color: $button-primary-background; - } - - &:disabled + span { - cursor: default; - opacity: .25; - } -} - .input-wrapper > * + * { margin-top: 1.5rem; }