Implement a non-functional push rule control UI
This commit is contained in:
parent
97f6acbb0d
commit
bb8f394b78
|
@ -4,15 +4,20 @@
|
||||||
- PCN error handling
|
- PCN error handling
|
||||||
- Change docs linking to dev branch back to master
|
- Change docs linking to dev branch back to master
|
||||||
|
|
||||||
|
- fix DeviceSection padding
|
||||||
- Implement fallback QML notifications, usable if dbus isn't available
|
- Implement fallback QML notifications, usable if dbus isn't available
|
||||||
- annoying tooltips when menu open
|
- annoying tooltips when menu open
|
||||||
- profiles missing in notifications
|
- profiles missing in notifications
|
||||||
|
|
||||||
- add http_proxy support
|
- add http_proxy support
|
||||||
|
- image viewer: can't expand image in reduced window layout
|
||||||
- Encrypted rooms don't show invites in member list after Mirage restart
|
- Encrypted rooms don't show invites in member list after Mirage restart
|
||||||
- Room display name not updated when someone removes theirs
|
- Room display name not updated when someone removes theirs
|
||||||
- Fix right margin of own `<image url>\n<image url>` messages
|
- Fix right margin of own `<image url>\n<image url>` messages
|
||||||
|
- option to use plaintext notifications
|
||||||
|
- warn on ambiguously activated shortcut
|
||||||
|
|
||||||
|
- SSO device delete?
|
||||||
- filter > enter > room list is always scrolled to top
|
- filter > enter > room list is always scrolled to top
|
||||||
- session list: prevent tab-focusing the delegates
|
- session list: prevent tab-focusing the delegates
|
||||||
- refresh server list button
|
- refresh server list button
|
||||||
|
|
|
@ -66,6 +66,9 @@ class Backend:
|
||||||
|
|
||||||
- `"accounts"`: logged-in accounts;
|
- `"accounts"`: logged-in accounts;
|
||||||
|
|
||||||
|
- `("<user_id>", "pushrules")`: push rules configured for our
|
||||||
|
account `user_id`.
|
||||||
|
|
||||||
- `("<user_id>", "rooms")`: rooms our account `user_id` is part of;
|
- `("<user_id>", "rooms")`: rooms our account `user_id` is part of;
|
||||||
|
|
||||||
- `("<user_id>", "transfers")`: ongoing or failed file
|
- `("<user_id>", "transfers")`: ongoing or failed file
|
||||||
|
|
|
@ -85,7 +85,51 @@ class Account(ModelItem):
|
||||||
return (self.order, self.id) < (other.order, other.id)
|
return (self.order, self.id) < (other.order, other.id)
|
||||||
|
|
||||||
|
|
||||||
|
class PushRuleKind(AutoStrEnum):
|
||||||
|
Override = auto()
|
||||||
|
Content = auto()
|
||||||
|
Room = auto()
|
||||||
|
Sender = auto()
|
||||||
|
Underride = auto()
|
||||||
|
|
||||||
|
|
||||||
@dataclass(eq=False)
|
@dataclass(eq=False)
|
||||||
|
class PushRule(ModelItem):
|
||||||
|
"""A push rule configured for one of our account."""
|
||||||
|
|
||||||
|
id: str = field()
|
||||||
|
kind: PushRuleKind = field()
|
||||||
|
order: int = field()
|
||||||
|
default: bool = field()
|
||||||
|
enabled: bool = True
|
||||||
|
pattern: str = ""
|
||||||
|
notify: bool = False
|
||||||
|
highlight: bool = False
|
||||||
|
bubble: bool = False
|
||||||
|
sound: bool = False
|
||||||
|
urgency_hint: bool = False
|
||||||
|
|
||||||
|
def __lt__(self, other: "PushRule") -> bool:
|
||||||
|
"""Sort by `kind`, then `order`."""
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.kind is PushRuleKind.Underride,
|
||||||
|
self.kind is PushRuleKind.Sender,
|
||||||
|
self.kind is PushRuleKind.Room,
|
||||||
|
self.kind is PushRuleKind.Content,
|
||||||
|
self.kind is PushRuleKind.Override,
|
||||||
|
self.order,
|
||||||
|
) < (
|
||||||
|
other.kind is PushRuleKind.Underride,
|
||||||
|
other.kind is PushRuleKind.Sender,
|
||||||
|
other.kind is PushRuleKind.Room,
|
||||||
|
other.kind is PushRuleKind.Content,
|
||||||
|
other.kind is PushRuleKind.Override,
|
||||||
|
other.order,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Room(ModelItem):
|
class Room(ModelItem):
|
||||||
"""A matrix room we are invited to, are or were member of."""
|
"""A matrix room we are invited to, are or were member of."""
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import nio
|
||||||
|
|
||||||
from .html_markdown import HTML_PROCESSOR
|
from .html_markdown import HTML_PROCESSOR
|
||||||
from .media_cache import Media
|
from .media_cache import Media
|
||||||
from .models.items import TypeSpecifier
|
from .models.items import PushRule, PushRuleKind, TypeSpecifier
|
||||||
from .presence import Presence
|
from .presence import Presence
|
||||||
from .pyotherside_events import DevicesUpdated
|
from .pyotherside_events import DevicesUpdated
|
||||||
from .utils import classes_defined_in, plain2html
|
from .utils import classes_defined_in, plain2html
|
||||||
|
@ -53,22 +53,23 @@ class NioCallbacks:
|
||||||
if method:
|
if method:
|
||||||
self.client.add_response_callback(method, response_class)
|
self.client.add_response_callback(method, response_class)
|
||||||
|
|
||||||
for name, event_class in classes_defined_in(nio.events).items():
|
for name, ev_class in classes_defined_in(nio.events).items():
|
||||||
method = getattr(self, f"on{name}", None)
|
method = getattr(self, f"on{name}", None)
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if issubclass(event_class, nio.EphemeralEvent):
|
if issubclass(ev_class, nio.EphemeralEvent):
|
||||||
self.client.add_ephemeral_callback(method, event_class)
|
self.client.add_ephemeral_callback(method, ev_class)
|
||||||
elif issubclass(event_class, nio.ToDeviceEvent):
|
elif issubclass(ev_class, nio.ToDeviceEvent):
|
||||||
self.client.add_to_device_callback(method, event_class)
|
self.client.add_to_device_callback(method, ev_class)
|
||||||
elif issubclass(event_class, nio.AccountDataEvent):
|
elif issubclass(ev_class, nio.AccountDataEvent):
|
||||||
self.client.add_room_account_data_callback(method, event_class)
|
self.client.add_global_account_data_callback(method, ev_class)
|
||||||
elif issubclass(event_class, nio.PresenceEvent):
|
self.client.add_room_account_data_callback(method, ev_class)
|
||||||
self.client.add_presence_callback(method, event_class)
|
elif issubclass(ev_class, nio.PresenceEvent):
|
||||||
|
self.client.add_presence_callback(method, ev_class)
|
||||||
else:
|
else:
|
||||||
self.client.add_event_callback(method, event_class)
|
self.client.add_event_callback(method, ev_class)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -779,6 +780,54 @@ class NioCallbacks:
|
||||||
ev.read_by_count = len(ev.last_read_by)
|
ev.read_by_count = len(ev.last_read_by)
|
||||||
|
|
||||||
|
|
||||||
|
# Account data callbacks
|
||||||
|
|
||||||
|
async def onPushRulesEvent(self, ev: nio.PushRulesEvent) -> None:
|
||||||
|
model = self.models[self.user_id, "pushrules"]
|
||||||
|
model.clear()
|
||||||
|
|
||||||
|
kinds: Dict[PushRuleKind, List[nio.PushRule]] = {
|
||||||
|
PushRuleKind.Override: ev.global_rules.override,
|
||||||
|
PushRuleKind.Content: ev.global_rules.content,
|
||||||
|
PushRuleKind.Room: ev.global_rules.room,
|
||||||
|
PushRuleKind.Sender: ev.global_rules.sender,
|
||||||
|
PushRuleKind.Underride: ev.global_rules.underride,
|
||||||
|
}
|
||||||
|
|
||||||
|
for kind, rules in kinds.items():
|
||||||
|
for order, rule in enumerate(rules):
|
||||||
|
tweaks = {
|
||||||
|
action.tweak: action.value for action in rule.actions
|
||||||
|
if isinstance(action, nio.PushSetTweak)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note: The `dont_notify` action does nothing.
|
||||||
|
# As of now (sept 2020), `coalesce` is just a `notify` synonym.
|
||||||
|
notify = any(
|
||||||
|
isinstance(action, (nio.PushNotify, nio.PushCoalesce))
|
||||||
|
for action in rule.actions
|
||||||
|
)
|
||||||
|
|
||||||
|
high = tweaks.get("highlight", False) is not False
|
||||||
|
bubble = tweaks.get("bubble", notify) is not False
|
||||||
|
sound = tweaks.get("sound", False) is not False
|
||||||
|
hint = tweaks.get("urgency_hint", high) is not False
|
||||||
|
|
||||||
|
model[rule.id] = PushRule(
|
||||||
|
id = rule.id,
|
||||||
|
kind = kind,
|
||||||
|
order = order,
|
||||||
|
default = rule.default,
|
||||||
|
enabled = rule.enabled,
|
||||||
|
pattern = rule.pattern,
|
||||||
|
notify = notify,
|
||||||
|
highlight = high,
|
||||||
|
bubble = bubble,
|
||||||
|
sound = sound,
|
||||||
|
urgency_hint = hint,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Presence event callbacks
|
# Presence event callbacks
|
||||||
|
|
||||||
async def onPresenceEvent(self, ev: nio.PresenceEvent) -> None:
|
async def onPresenceEvent(self, ev: nio.PresenceEvent) -> None:
|
||||||
|
|
|
@ -18,11 +18,14 @@ HPage {
|
||||||
height: Math.min(implicitHeight, page.availableHeight)
|
height: Math.min(implicitHeight, page.availableHeight)
|
||||||
|
|
||||||
header: HTabBar {
|
header: HTabBar {
|
||||||
|
currentIndex: 1 // XXX
|
||||||
HTabButton { text: qsTr("Account") }
|
HTabButton { text: qsTr("Account") }
|
||||||
|
HTabButton { text: qsTr("Notifications") }
|
||||||
HTabButton { text: qsTr("Security") }
|
HTabButton { text: qsTr("Security") }
|
||||||
}
|
}
|
||||||
|
|
||||||
Account { userId: page.userId }
|
Account { userId: page.userId }
|
||||||
|
Notifications { userId: page.userId }
|
||||||
Security { userId: page.userId }
|
Security { userId: page.userId }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/gui/Pages/AccountSettings/NotificationRuleButton.qml
Normal file
12
src/gui/Pages/AccountSettings/NotificationRuleButton.qml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import "../../Base"
|
||||||
|
|
||||||
|
HButton {
|
||||||
|
property bool on: true
|
||||||
|
|
||||||
|
opacity: on ? 1 : theme.disabledElementsOpacity
|
||||||
|
hoverEnabled: true
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
}
|
157
src/gui/Pages/AccountSettings/NotificationRuleDelegate.qml
Normal file
157
src/gui/Pages/AccountSettings/NotificationRuleDelegate.qml
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import "../.."
|
||||||
|
import "../../Base"
|
||||||
|
import "../../Base/Buttons"
|
||||||
|
import "../../Base/HTile"
|
||||||
|
import "../../MainPane"
|
||||||
|
import "../../PythonBridge"
|
||||||
|
import "../../ShortcutBundles"
|
||||||
|
|
||||||
|
HTile {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string userId
|
||||||
|
|
||||||
|
readonly property QtObject matchingRoom:
|
||||||
|
model.kind === "Room" ?
|
||||||
|
ModelStore.get(userId, "rooms").find(model.id) :
|
||||||
|
null
|
||||||
|
|
||||||
|
|
||||||
|
contentOpacity: model.enabled ? 1 : theme.disabledElementsOpacity
|
||||||
|
hoverEnabled: false
|
||||||
|
|
||||||
|
contentItem: HColumnLayout {
|
||||||
|
spacing: root.spacing / 2
|
||||||
|
|
||||||
|
TitleLabel {
|
||||||
|
opacity: model.enabled ? 1 : theme.disabledElementsOpacity
|
||||||
|
elide: Text.ElideNone
|
||||||
|
wrapMode: HLabel.Wrap
|
||||||
|
|
||||||
|
textFormat:
|
||||||
|
model.id === ".m.rule.contains_user_name" ||
|
||||||
|
model.id === ".m.rule.roomnotif" ||
|
||||||
|
model.kind === "Sender" ?
|
||||||
|
HLabel.StyledText :
|
||||||
|
HLabel.PlainText
|
||||||
|
|
||||||
|
text:
|
||||||
|
model.id === ".m.rule.master" ?
|
||||||
|
qsTr("Any message") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.suppress_notices" ?
|
||||||
|
qsTr("Messages sent by bots") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.invite_for_me" ?
|
||||||
|
qsTr("Received room invites") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.member_event" ?
|
||||||
|
qsTr("Membership, name & avatar changes") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.contains_display_name" ?
|
||||||
|
qsTr("Messages containing my display name") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.tombstone" ?
|
||||||
|
qsTr("Room migration alerts") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.reaction" ?
|
||||||
|
qsTr("Emoji reactions") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.roomnotif" ?
|
||||||
|
qsTr("Messages containing %1").arg(
|
||||||
|
utils.htmlColorize("@room", theme.colors.accentText),
|
||||||
|
) :
|
||||||
|
|
||||||
|
model.id === ".m.rule.contains_user_name" ?
|
||||||
|
qsTr("Contains %1").arg(utils.coloredNameHtml(
|
||||||
|
"", userId, userId.split(":")[0].substring(1),
|
||||||
|
)):
|
||||||
|
|
||||||
|
model.id === ".m.rule.call" ?
|
||||||
|
qsTr("Incoming audio calls") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.encrypted_room_one_to_one" ?
|
||||||
|
qsTr("Encrypted 1-to-1 messages") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.room_one_to_one" ?
|
||||||
|
qsTr("Unencrypted 1-to-1 messages") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.message" ?
|
||||||
|
qsTr("Unencrypted group messages") :
|
||||||
|
|
||||||
|
model.id === ".m.rule.encrypted" ?
|
||||||
|
qsTr("Encrypted group messages") :
|
||||||
|
|
||||||
|
model.kind === "Content" ?
|
||||||
|
qsTr('Contains "%1"').arg(model.pattern) :
|
||||||
|
|
||||||
|
model.kind === "Sender" ?
|
||||||
|
utils.coloredNameHtml("", model.id) :
|
||||||
|
|
||||||
|
matchingRoom && matchingRoom.display_name ?
|
||||||
|
matchingRoom.display_name :
|
||||||
|
|
||||||
|
model.id
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
HRowLayout {
|
||||||
|
NotificationRuleButton {
|
||||||
|
on: model.notify
|
||||||
|
|
||||||
|
contentItem: MessageIndicator {
|
||||||
|
indicatorTheme:
|
||||||
|
theme.mainPane.listView.room.unreadIndicator
|
||||||
|
unreads: 1
|
||||||
|
text: "+1"
|
||||||
|
font.pixelSize: theme.fontSize.normal
|
||||||
|
topPadding: leftPadding / 3
|
||||||
|
bottomPadding: topPadding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationRuleButton {
|
||||||
|
on: model.highlight
|
||||||
|
|
||||||
|
contentItem: MessageIndicator {
|
||||||
|
indicatorTheme:
|
||||||
|
theme.mainPane.listView.room.unreadIndicator
|
||||||
|
|
||||||
|
unreads: 1
|
||||||
|
highlights: 1
|
||||||
|
text: "+1"
|
||||||
|
font.pixelSize: theme.fontSize.normal
|
||||||
|
topPadding: leftPadding / 3
|
||||||
|
bottomPadding: topPadding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationRuleButton {
|
||||||
|
icon.name: "pushrule-action-bubble"
|
||||||
|
on: model.bubble
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationRuleButton {
|
||||||
|
icon.name: "pushrule-action-sound"
|
||||||
|
on: model.sound
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationRuleButton {
|
||||||
|
icon.name: "pushrule-action-urgency-hint"
|
||||||
|
on: model.urgency_hint
|
||||||
|
}
|
||||||
|
|
||||||
|
HSpacer {}
|
||||||
|
|
||||||
|
NotificationRuleButton {
|
||||||
|
icon.name: "pushrule-edit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/gui/Pages/AccountSettings/Notifications.qml
Normal file
56
src/gui/Pages/AccountSettings/Notifications.qml
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import "../.."
|
||||||
|
import "../../Base"
|
||||||
|
import "../../Base/Buttons"
|
||||||
|
import "../../PythonBridge"
|
||||||
|
import "../../ShortcutBundles"
|
||||||
|
|
||||||
|
HListView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string userId
|
||||||
|
|
||||||
|
property bool enableFlickShortcuts:
|
||||||
|
SwipeView ? SwipeView.isCurrentItem : true
|
||||||
|
|
||||||
|
function takeFocus() {
|
||||||
|
// deviceList.headerItem.exportButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
model: ModelStore.get(userId, "pushrules")
|
||||||
|
bottomMargin: theme.spacing
|
||||||
|
implicitHeight: Math.min(window.height, contentHeight + bottomMargin)
|
||||||
|
|
||||||
|
section.property: "kind"
|
||||||
|
section.delegate: HLabel {
|
||||||
|
width: root.width
|
||||||
|
topPadding: padding * (section === "Override" ? 1 : 1.5)
|
||||||
|
padding: theme.spacing
|
||||||
|
font.pixelSize: theme.fontSize.big
|
||||||
|
text:
|
||||||
|
section === "Override" ? qsTr("High-priority general rules") :
|
||||||
|
section === "Content" ? qsTr("Message text rules") :
|
||||||
|
section === "Room" ? qsTr("Room rules") :
|
||||||
|
section === "Sender" ? qsTr("Sender rules") :
|
||||||
|
qsTr("General rules")
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: NotificationRuleDelegate {
|
||||||
|
userId: root.userId
|
||||||
|
width: root.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
FlickShortcuts {
|
||||||
|
flickable: root
|
||||||
|
active: ! mainUI.debugConsole.visible && root.enableFlickShortcuts
|
||||||
|
}
|
||||||
|
}
|
3
src/icons/thin/pushrule-action-bubble.svg
Normal file
3
src/icons/thin/pushrule-action-bubble.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m22 3v13h-6.961l-3.039 3.798-3.039-3.798h-6.961v-13zm2-2h-24v17h8l4 5 4-5h8z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 184 B |
11
src/icons/thin/pushrule-action-sound.svg
Normal file
11
src/icons/thin/pushrule-action-sound.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m14.509208 8.343398c1.202865.9346638 1.945334 2.226588 1.94209 3.651856-.0032 1.425268-.748954 2.714677-1.953439 3.648082l.978322 1.538485c1.713516-1.324631 2.775344-3.158736 2.780206-5.184051.0032-2.0265727-1.05048-3.8594193-2.759132-5.1890826z" stroke-width="1.428038"/>
|
||||||
|
<path d="m18.318701 4.9920248c2.307024 1.7957761 3.72921 4.2712232 3.723838 7.0065862-.0054 2.735361-1.440099 5.212198-3.754288 7.003804l.980846 1.74296c2.887361-2.234991 4.68032-5.328952 4.685693-8.743986.009-3.4150319-1.766089-6.5062134-4.648077-8.7467639z" stroke-width="1.57784"/>
|
||||||
|
<g transform="matrix(.6875 0 0 1.01875 0 -.225)">
|
||||||
|
<path d="m11 1.8125-9.1191406 5.09375v10.1875l9.1191406 5.09375zm-1.6875 2.9941406v14.3867184l-7.4324287-4.041727v-6.1767663z" transform="matrix(1.4545455 0 0 .98159509 0 .220859)"/>
|
||||||
|
<path d="m2.7346492 8.9958856h-2.7346492v6.1896554h2.7346492z" stroke-width=".786744"/>
|
||||||
|
<path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
|
||||||
|
<path d="m2.7357957 7-2.7357957 1.0208759v.9750097h2.7346492z"/>
|
||||||
|
<path d="m2.7357956 17-2.73579573-1.020876v-.97501h2.73464923z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
3
src/icons/thin/pushrule-action-urgency-hint.svg
Normal file
3
src/icons/thin/pushrule-action-urgency-hint.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m22.909091 9.2836364v1.4967276h-2.768727c.02182-.243273.04145-.488728.04145-.742909 0-.2585459-.01854-.5061824-.03818-.7527277h2.765455zm-11.616-7.1487273v-2.1349091h1.495636v2.1425455c-.264-.024-.528-.038183-.792-.038183-.234545 0-.469091.010909-.703636.030547zm6.022909 1.6843636 1.712727-1.9014545 1.111637 1.0014544-1.748728 1.9385456c-.272727-.324-.646909-.733091-1.075636-1.0385455zm-11.7141819 1.0385455-1.7487273-1.9385456 1.1116364-1.0014544 1.7127273 1.9014545c-.4276365.3054545-.8029091.7145455-1.0756364 1.0385455zm-1.7421818 5.9225458h-2.7687273v-1.4967276h2.7654545c-.019636.2465453-.038182.4952727-.038182.7538186 0 .254181.019636.499636.041455.742909zm10.3221817 9.946909h-4.363636c-.301091 0-.545455.244363-.545455.545454s.244364.545455.545455.545455h4.363636c.301091 0 .545455-.244364.545455-.545455s-.244364-.545454-.545455-.545454zm0 2.181818h-4.363636c-.301091 0-.545455.244364-.545455.545454 0 .301091.244364.545455.545455.545455h4.363636c.301091 0 .545455-.244364.545455-.545455 0-.30109-.244364-.545454-.545455-.545454zm4.363636-12.871636c0 3.893453-3.506181 6.526909-3.506181 9.598909h-2.169819c-.0033-2.026909.949091-3.697091 1.877455-5.309455.830182-1.445454 1.616727-2.811273 1.616727-4.289454 0-2.8276368-2.263636-4.1149096-4.366909-4.1149096-2.1 0-4.3603635 1.2872728-4.3603635 4.1149096 0 1.478181.7865455 2.844 1.6167275 4.289454.928363 1.612364 1.881818 3.282546 1.876363 5.309455h-2.1687268c0-3.072-3.5061818-5.705456-3.5061818-9.598909 0-4.0614551 3.2705454-6.2967278 6.5421816-6.2967278 3.273818 0 6.548727 2.2374545 6.548727 6.2967278z" stroke-width="1.09091"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
7
src/icons/thin/pushrule-edit.svg
Normal file
7
src/icons/thin/pushrule-edit.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g stroke-width=".853763" transform="translate(9.811836)">
|
||||||
|
<path d="m12 0c1.208596 0 2.188164.97956763 2.188164 2.1881632 0 1.2085956-.979568 2.1881633-2.188164 2.1881633s-2.1881636-.9795677-2.1881636-2.1881633c0-1.20859557.9795676-2.1881632 2.1881636-2.1881632z"/>
|
||||||
|
<path d="m12 9.8118368c1.208596 0 2.188164.9795672 2.188164 2.1881632s-.979568 2.188163-2.188164 2.188163-2.1881636-.979567-2.1881636-2.188163.9795676-2.1881632 2.1881636-2.1881632z"/>
|
||||||
|
<path d="m12 19.623674c1.208596 0 2.188164.979567 2.188164 2.188163s-.979568 2.188163-2.188164 2.188163-2.1881636-.979567-2.1881636-2.188163.9795676-2.188163 2.1881636-2.188163z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 760 B |
Loading…
Reference in New Issue
Block a user