Skip to content

Commit 5836e17

Browse files
Merge pull request #83 from duynguyenhoang/feature/support_set_snooze
Add set snooze time feature
2 parents ca6eddb + 030471e commit 5836e17

File tree

9 files changed

+185
-13
lines changed

9 files changed

+185
-13
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,20 @@ at most 9 workspaces defined inside `workspaces`:
8888
}
8989
```
9090

91+
You can use the keys from 1 up to 9 to switch workspaces or event right-click the indicator:
92+
93+
![Multiple workspaces](./resources/example_7.png)
94+
95+
9196
### Quick Switcher
9297

9398
You can use <kbd>ctrl k</kbd> (or your custom shortcut) to navigate in your DMs and channels.
9499

95100
![](./resources/example_8.png)
96101

97-
You can use the keys from 1 up to 9 to switch workspaces or event right-click the indicator:
98-
99-
![Multiple workspaces](./resources/example_7.png)
102+
### Set snooze
100103

104+
You can use <kbd>ctrl d</kbd> (or your custom shortcut) to set snooze time.
101105

102106
### Default keybindings
103107
```json

app.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from sclack.store import Store
2323
from sclack.themes import themes
2424

25+
from sclack.widgets.set_snooze import SetSnoozeWidget
26+
2527
loop = asyncio.get_event_loop()
2628

2729
SCLACK_SUBTYPE = 'sclack_message'
@@ -54,6 +56,8 @@ def _exception_handler(self, loop, context):
5456
def __init__(self, config):
5557
self._loading = False
5658
self.config = config
59+
self.quick_switcher = None
60+
self.set_snooze_widget = None
5761
self.workspaces = list(config['workspaces'].items())
5862
self.store = Store(self.workspaces, self.config)
5963
Store.instance = self.store
@@ -75,7 +79,7 @@ def __init__(self, config):
7579
urwid.AttrWrap(chatbox, 'chatbox')
7680
])
7781
self._body = urwid.Frame(self.columns, header=self.workspaces_line)
78-
self.quick_switcher = None
82+
7983
self.urwid_loop = urwid.MainLoop(
8084
self._body,
8185
palette=palette,
@@ -253,6 +257,7 @@ def mount_chatbox(self, executor, channel):
253257
urwid.connect_signal(self.chatbox, 'set_insert_mode', self.set_insert_mode)
254258
urwid.connect_signal(self.chatbox, 'mark_read', self.handle_mark_read)
255259
urwid.connect_signal(self.chatbox, 'open_quick_switcher', self.open_quick_switcher)
260+
urwid.connect_signal(self.chatbox, 'open_set_snooze', self.open_set_snooze)
256261

257262
urwid.connect_signal(self.message_box.prompt_widget, 'submit_message', self.submit_message)
258263
urwid.connect_signal(self.message_box.prompt_widget, 'go_to_last_message', self.go_to_last_message)
@@ -587,6 +592,24 @@ def go_to_channel(self, channel_id):
587592
self.quick_switcher = None
588593
loop.create_task(self._go_to_channel(channel_id))
589594

595+
def handle_set_snooze_time(self, snoozed_time):
596+
loop.create_task(self.dispatch_snooze_time(snoozed_time))
597+
598+
def handle_close_set_snooze(self):
599+
"""
600+
Close set_snooze
601+
:return:
602+
"""
603+
if self.set_snooze_widget:
604+
urwid.disconnect_signal(self.set_snooze_widget, 'set_snooze_time', self.handle_set_snooze_time)
605+
urwid.disconnect_signal(self.set_snooze_widget, 'close_set_snooze', self.handle_close_set_snooze)
606+
self.urwid_loop.widget = self._body
607+
self.set_snooze_widget = None
608+
609+
@asyncio.coroutine
610+
def dispatch_snooze_time(self, snoozed_time):
611+
self.store.set_snooze(snoozed_time)
612+
590613
@asyncio.coroutine
591614
def load_picture_async(self, url, width, message_widget, auth=True):
592615
width = min(width, 800)
@@ -716,8 +739,10 @@ def go_to_sidebar(self):
716739
if len(self.columns.contents) > 2:
717740
self.columns.contents.pop()
718741
self.columns.focus_position = 0
742+
719743
if self.store.state.editing_widget:
720744
self.leave_edit_mode()
745+
721746
if self.quick_switcher:
722747
urwid.disconnect_signal(self.quick_switcher, 'go_to_channel', self.go_to_channel)
723748
self.urwid_loop.widget = self._body
@@ -743,6 +768,11 @@ def go_to_last_message(self):
743768
self.chatbox.body.go_to_last_message()
744769

745770
def unhandled_input(self, key):
771+
"""
772+
Handle shortcut key press
773+
:param key:
774+
:return:
775+
"""
746776
keymap = self.store.config['keymap']
747777

748778
if key == keymap['go_to_chatbox'] or key == keymap['cursor_right'] and self.message_box:
@@ -763,13 +793,22 @@ def unhandled_input(self, key):
763793
# Stop rtm to switch workspace
764794
self.real_time_task.cancel()
765795
return self.switch_to_workspace(int(key))
796+
elif key == keymap['set_snooze']:
797+
return self.open_set_snooze()
766798

767799
def open_quick_switcher(self):
768800
if not self.quick_switcher:
769801
self.quick_switcher = QuickSwitcher(self.urwid_loop.widget, self.urwid_loop)
770802
urwid.connect_signal(self.quick_switcher, 'go_to_channel', self.go_to_channel)
771803
self.urwid_loop.widget = self.quick_switcher
772804

805+
def open_set_snooze(self):
806+
if not self.set_snooze_widget:
807+
self.set_snooze_widget = SetSnoozeWidget(self.urwid_loop.widget, self.urwid_loop)
808+
urwid.connect_signal(self.set_snooze_widget, 'set_snooze_time', self.handle_set_snooze_time)
809+
urwid.connect_signal(self.set_snooze_widget, 'close_set_snooze', self.handle_close_set_snooze)
810+
self.urwid_loop.widget = self.set_snooze_widget
811+
773812
def configure_screen(self, screen):
774813
screen.set_terminal_properties(colors=self.store.config['colors'])
775814
screen.set_mouse_tracking()

config.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"quit_application": "q",
1616
"set_edit_topic_mode": "t",
1717
"set_insert_mode": "i",
18-
"yank_message": "y"
18+
"yank_message": "y",
19+
"set_snooze": "ctrl d"
1920
},
2021
"sidebar": {
2122
"width": 25,
@@ -50,6 +51,7 @@
5051
"square": "\uF445",
5152
"snooze": "\uF9B1",
5253
"status": "\uF075",
53-
"timezone": "\uF0AC"
54+
"timezone": "\uF0AC",
55+
"alarm_snooze": "\ufb8c"
5456
}
5557
}

sclack/components.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def keypress(self, size, key):
218218

219219
class ChatBox(urwid.Frame):
220220
__metaclass__ = urwid.MetaSignals
221-
signals = ['go_to_sidebar', 'open_quick_switcher', 'set_insert_mode', 'mark_read']
221+
signals = ['go_to_sidebar', 'open_quick_switcher', 'set_insert_mode', 'mark_read', 'open_set_snooze']
222222

223223
def __init__(self, messages, header, message_box, event_loop):
224224
self._header = header
@@ -241,6 +241,10 @@ def keypress(self, size, key):
241241
if key == keymap['open_quick_switcher']:
242242
urwid.emit_signal(self, 'open_quick_switcher')
243243
return True
244+
if key == keymap['set_snooze']:
245+
urwid.emit_signal(self, 'open_set_snooze')
246+
return True
247+
244248
return super(ChatBox, self).keypress(size, key)
245249

246250
@property

sclack/markdown.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .store import Store
44
from .emoji import emoji_codemap
55

6+
67
class MarkdownText(urwid.SelectableIcon):
78
_buffer = ''
89
_state = 'message'

sclack/quick_switcher.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import unicodedata
44
from .store import Store
55

6+
67
def get_icon(name):
78
return Store.instance.config['icons'][name]
89

10+
911
def remove_diacritic(input):
1012
'''
1113
Accept a unicode string, and return a normal string (bytes in Python 3)
1214
without any diacritical marks.
1315
'''
1416
return unicodedata.normalize('NFKD', input).encode('ASCII', 'ignore').decode()
1517

18+
1619
class QuickSwitcherItem(urwid.AttrMap):
1720
def __init__(self, icon, title, id):
1821
markup = [' ', icon, ' ', title]
@@ -27,6 +30,7 @@ def __init__(self, icon, title, id):
2730
}
2831
)
2932

33+
3034
class QuickSwitcherList(urwid.ListBox):
3135
def __init__(self, items):
3236
self.body = urwid.SimpleFocusListWalker(items)
@@ -69,7 +73,7 @@ def __init__(self, base, event_loop):
6973
priority.append({'id': dm['id'], 'icon': icon, 'title': name, 'type': 'user'})
7074
else:
7175
icon = ('quick_search_presence_away', get_icon('offline'))
72-
lines.append({'id': dm['id'],'icon': icon, 'title': name, 'type': 'user'})
76+
lines.append({'id': dm['id'], 'icon': icon, 'title': name, 'type': 'user'})
7377
priority.sort(key=lambda item: item['title'])
7478
lines.sort(key=lambda item: item['title'])
7579
self.header = urwid.Edit('')
@@ -84,10 +88,10 @@ def __init__(self, base, event_loop):
8488
overlay = urwid.Overlay(
8589
switcher,
8690
base,
87-
align = 'center',
88-
width = ('relative', 40),
89-
valign = 'middle',
90-
height = 15
91+
align='center',
92+
width=('relative', 40),
93+
valign='middle',
94+
height=15
9195
)
9296
self.last_keypress = (time.time() - 0.3, None)
9397
super(QuickSwitcher, self).__init__(overlay, 'quick_switcher_dialog')
@@ -139,4 +143,3 @@ def keypress(self, size, key):
139143
if now - self.last_keypress[0] < 0.3 and self.last_keypress[1] is not None:
140144
self.event_loop.remove_alarm(self.last_keypress[1])
141145
self.last_keypress = (now, self.event_loop.set_alarm_in(0.3, self.set_filter))
142-

sclack/store.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ def mark_read(self, channel_id, ts):
125125
elif self.is_dm(channel_id):
126126
return self.slack.api_call('im.mark', channel=channel_id, ts=ts)
127127

128+
def set_snooze(self, snoozed_time):
129+
return self.slack.api_call('dnd.setSnooze', num_minutes=snoozed_time)
130+
128131
def load_channel(self, channel_id):
129132
if channel_id[0] in ('C', 'G', 'D'):
130133
self.state.channel = self.get_channel_info(channel_id)

sclack/themes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
('selected_workspace_separator', '', '', '', 'h198', 'h54'),
4343
('previous_workspace_separator', '', '', '', 'h54', 'h198'),
4444
('quick_switcher_dialog', '', '', '', 'white', 'h239'),
45+
('set_snooze_dialog', '', '', '', 'white', 'h239'),
4546
('active_quick_switcher_item', '', '', '', 'white', 'h32'),
47+
('active_set_snooze_item', '', '', '', 'white', 'h32'),
4648
('quick_search_presence_active', '', '', '', 'h40', 'h239'),
4749
('quick_search_active_focus', '', '', '', 'h40', 'h32'),
4850
('quick_search_presence_away', '', '', '', 'white', 'h239')

sclack/widgets/set_snooze.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import urwid
2+
from sclack.store import Store
3+
4+
5+
def get_icon(name):
6+
return Store.instance.config['icons'][name]
7+
8+
9+
class SetSnoozeWidgetItem(urwid.AttrMap):
10+
def __init__(self, icon, title, id):
11+
markup = [' ', icon, ' ', title]
12+
self.id = id
13+
super(SetSnoozeWidgetItem, self).__init__(
14+
urwid.SelectableIcon(markup),
15+
None,
16+
{
17+
None: 'active_set_snooze_item',
18+
'quick_search_presence_active': 'quick_search_active_focus',
19+
}
20+
)
21+
22+
23+
class SetSnoozeWidgetList(urwid.ListBox):
24+
def __init__(self, items):
25+
self.body = urwid.SimpleFocusListWalker(items)
26+
super(SetSnoozeWidgetList, self).__init__(self.body)
27+
28+
29+
class SetSnoozeWidget(urwid.AttrWrap):
30+
__metaclass__ = urwid.MetaSignals
31+
signals = ['close_set_snooze', 'set_snooze_time']
32+
33+
def __init__(self, base, event_loop):
34+
lines = []
35+
self.event_loop = event_loop
36+
37+
snooze_times = [
38+
{
39+
'label': 'Off',
40+
'time': 0,
41+
},
42+
{
43+
'label': '20 minutes',
44+
'time': 20,
45+
},
46+
{
47+
'label': '1 hour',
48+
'time': 60,
49+
},
50+
{
51+
'label': '2 hours',
52+
'time': 120,
53+
},
54+
{
55+
'label': '4 hours',
56+
'time': 240,
57+
},
58+
{
59+
'label': '8 hours',
60+
'time': 480,
61+
},
62+
{
63+
'label': '24 hours',
64+
'time': 1400,
65+
},
66+
]
67+
68+
for snooze_time in snooze_times:
69+
lines.append({
70+
'icon': get_icon('alarm_snooze'),
71+
'title': snooze_time['label'],
72+
'time': snooze_time['time']
73+
})
74+
75+
self.header = urwid.Edit('')
76+
77+
self.original_items = lines
78+
widgets = [SetSnoozeWidgetItem(item['icon'], item['title'], item['time']) for item in self.original_items]
79+
80+
self.snooze_time_list = SetSnoozeWidgetList(widgets)
81+
82+
snooze_list = urwid.LineBox(
83+
urwid.Frame(self.snooze_time_list, header=self.header),
84+
title='Snooze notifications',
85+
title_align='left'
86+
)
87+
overlay = urwid.Overlay(
88+
snooze_list,
89+
base,
90+
align='center',
91+
width=('relative', 15),
92+
valign='middle',
93+
height=10,
94+
right=5
95+
)
96+
super(SetSnoozeWidget, self).__init__(overlay, 'set_snooze_dialog')
97+
98+
def keypress(self, size, key):
99+
reserved_keys = ('up', 'down', 'page up', 'page down')
100+
if key in reserved_keys:
101+
return super(SetSnoozeWidget, self).keypress(size, key)
102+
elif key == 'enter':
103+
focus = self.snooze_time_list.body.get_focus()
104+
if focus[0]:
105+
urwid.emit_signal(self, 'set_snooze_time', focus[0].id)
106+
urwid.emit_signal(self, 'close_set_snooze')
107+
return True
108+
elif key == 'esc':
109+
focus = self.snooze_time_list.body.get_focus()
110+
if focus[0]:
111+
urwid.emit_signal(self, 'close_set_snooze')
112+
return True
113+
114+
self.header.keypress((size[0],), key)

0 commit comments

Comments
 (0)