Browse Source
Chat selection by context menu Clear selection Fix highlighting bubbles Fix highlight first unread bubblemaster
morethanwords
4 years ago
14 changed files with 558 additions and 243 deletions
@ -0,0 +1,106 @@
@@ -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<number> = 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) { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,195 @@
@@ -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; |
||||
} |
||||
} |
Loading…
Reference in new issue