mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
add separate menu for reminder mails
This commit is contained in:
@@ -46,6 +46,7 @@ class MyController < ApplicationController
|
||||
menu_item :password, only: [:password]
|
||||
menu_item :access_token, only: [:access_token]
|
||||
menu_item :notifications, only: [:notifications]
|
||||
menu_item :reminders, only: [:reminders]
|
||||
|
||||
def account; end
|
||||
|
||||
@@ -81,23 +82,30 @@ class MyController < ApplicationController
|
||||
# Administer access tokens
|
||||
def access_token; end
|
||||
|
||||
# Configure user's mail notifications
|
||||
# Configure user's in app notifications
|
||||
def notifications
|
||||
render html: '',
|
||||
layout: 'angular',
|
||||
locals: { menu_name: :my_menu }
|
||||
end
|
||||
|
||||
# Configure user's mail reminders
|
||||
def reminders
|
||||
render html: '',
|
||||
layout: 'angular',
|
||||
locals: { menu_name: :my_menu }
|
||||
end
|
||||
|
||||
# Create a new feeds key
|
||||
def generate_rss_key
|
||||
if request.post?
|
||||
token = Token::RSS.create!(user: current_user)
|
||||
flash[:info] = [
|
||||
t('my.access_token.notice_reset_token', type: 'RSS').html_safe,
|
||||
content_tag(:strong, token.plain_value),
|
||||
t('my.access_token.token_value_warning')
|
||||
]
|
||||
end
|
||||
token = Token::RSS.create!(user: current_user)
|
||||
flash[:info] = [
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
t('my.access_token.notice_reset_token', type: 'RSS').html_safe,
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
content_tag(:strong, token.plain_value),
|
||||
t('my.access_token.token_value_warning')
|
||||
]
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "Failed to reset user ##{current_user.id} RSS key: #{e}"
|
||||
flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message)
|
||||
@@ -107,14 +115,14 @@ class MyController < ApplicationController
|
||||
|
||||
# Create a new API key
|
||||
def generate_api_key
|
||||
if request.post?
|
||||
token = Token::API.create!(user: current_user)
|
||||
flash[:info] = [
|
||||
t('my.access_token.notice_reset_token', type: 'API').html_safe,
|
||||
content_tag(:strong, token.plain_value),
|
||||
t('my.access_token.token_value_warning')
|
||||
]
|
||||
end
|
||||
token = Token::API.create!(user: current_user)
|
||||
flash[:info] = [
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
t('my.access_token.notice_reset_token', type: 'API').html_safe,
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
content_tag(:strong, token.plain_value),
|
||||
t('my.access_token.token_value_warning')
|
||||
]
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "Failed to reset user ##{current_user.id} API key: #{e}"
|
||||
flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message)
|
||||
|
||||
@@ -137,6 +137,10 @@ Redmine::MenuManager.map :my_menu do |menu|
|
||||
{ controller: '/my', action: 'notifications' },
|
||||
caption: I18n.t('js.notifications.settings.title'),
|
||||
icon: 'icon2 icon-bell'
|
||||
menu.push :reminders,
|
||||
{ controller: '/my', action: 'reminders' },
|
||||
caption: I18n.t('js.reminders.settings.title'),
|
||||
icon: 'icon2 icon-email-alert'
|
||||
|
||||
menu.push :delete_account, :delete_my_account_info_path,
|
||||
caption: I18n.t('account.delete'),
|
||||
|
||||
@@ -627,6 +627,15 @@ en:
|
||||
autocompleter:
|
||||
label: 'Project autocompletion'
|
||||
|
||||
reminders:
|
||||
settings:
|
||||
daily:
|
||||
title: 'Send me daily email reminders for unread notifications'
|
||||
explanation: 'You will receive these reminders only for unread notifications and only at hours you specify.'
|
||||
label: 'Time %{counter}'
|
||||
title: 'Email reminders'
|
||||
|
||||
|
||||
text_are_you_sure: "Are you sure?"
|
||||
text_data_lost: "All entered data will be lost."
|
||||
|
||||
|
||||
@@ -552,6 +552,7 @@ OpenProject::Application.routes.draw do
|
||||
get '/my/account', action: 'account'
|
||||
get '/my/settings', action: 'settings'
|
||||
get '/my/notifications', action: 'notifications'
|
||||
get '/my/reminders', action: 'reminders'
|
||||
|
||||
patch '/my/account', action: 'update_account'
|
||||
patch '/my/settings', action: 'update_settings'
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<div class="title-container">
|
||||
<h2 [textContent]="text.title"></h2>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<section class="form--section">
|
||||
<h3 [textContent]="text.daily.title"></h3>
|
||||
<span [textContent]="text.daily.explanation"></span>
|
||||
|
||||
<op-reminder-settings-daily-time>
|
||||
</op-reminder-settings-daily-time>
|
||||
</section>
|
||||
<button
|
||||
class="button -highlight"
|
||||
[textContent]="text.save"
|
||||
(click)="saveChanges()"
|
||||
>
|
||||
</button>
|
||||
</form>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
ChangeDetectionStrategy, Component, Input, OnInit,
|
||||
} from '@angular/core';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
|
||||
import { take } from 'rxjs/internal/operators/take';
|
||||
import { UIRouterGlobals } from '@uirouter/core';
|
||||
import { UserPreferencesService } from 'core-app/features/user-preferences/state/user-preferences.service';
|
||||
import { UserPreferencesQuery } from 'core-app/features/user-preferences/state/user-preferences.query';
|
||||
|
||||
export const myReminderPageComponentSelector = 'op-reminders-page';
|
||||
|
||||
@Component({
|
||||
selector: myReminderPageComponentSelector,
|
||||
templateUrl: './reminder-settings-page.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReminderSettingsPageComponent implements OnInit {
|
||||
@Input() userId:string;
|
||||
|
||||
text = {
|
||||
title: this.I18n.t('js.reminders.settings.title'),
|
||||
save: this.I18n.t('js.button_save'),
|
||||
daily: {
|
||||
title: this.I18n.t('js.reminders.settings.daily.title'),
|
||||
explanation: this.I18n.t('js.reminders.settings.daily.explanation'),
|
||||
},
|
||||
};
|
||||
|
||||
constructor(
|
||||
private I18n:I18nService,
|
||||
private stateService:UserPreferencesService,
|
||||
private query:UserPreferencesQuery,
|
||||
private currentUserService:CurrentUserService,
|
||||
private uiRouterGlobals:UIRouterGlobals,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit():void {
|
||||
this.userId = this.userId || this.uiRouterGlobals.params.userId;
|
||||
this
|
||||
.currentUserService
|
||||
.user$
|
||||
.pipe(take(1))
|
||||
.subscribe((user) => {
|
||||
this.userId = this.userId || user.id!;
|
||||
this.stateService.get(this.userId);
|
||||
});
|
||||
}
|
||||
|
||||
public saveChanges():void {
|
||||
const prefs = this.query.getValue();
|
||||
this.stateService.update(this.userId, prefs);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<label
|
||||
*ngFor="let time of dailyReminderTimes; index as i"
|
||||
class="form--label-with-check-box">
|
||||
<div class="form--check-box-container">
|
||||
<input type="checkbox" class="form--check-box" checked>
|
||||
</div>
|
||||
{{text.label(i + 1)}}
|
||||
<div class="form--field -no-label">
|
||||
<div class="form--field-container">
|
||||
<div class="form--text-field-container">
|
||||
<input
|
||||
type="time"
|
||||
[value]="time"
|
||||
step="1800"
|
||||
required
|
||||
attr.data-qa-selector="op-settings-daily-time--time-{{i + 1}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { ChangeDetectionStrategy } from "@angular/core";
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { UserPreferencesStore } from 'core-app/features/user-preferences/state/user-preferences.store';
|
||||
|
||||
@Component({
|
||||
selector: 'op-reminder-settings-daily-time',
|
||||
templateUrl: './reminder-settings-daily-time.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReminderSettingsDailyTimeComponent implements OnInit {
|
||||
public dailyReminderTimes = ["08:00", "12:00", "16:00"]
|
||||
|
||||
text = {
|
||||
label: (counter:number):string => this.I18n.t('js.reminders.settings.daily.label', { counter: counter }),
|
||||
};
|
||||
|
||||
constructor(
|
||||
private I18n:I18nService,
|
||||
private store:UserPreferencesStore,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit():void {
|
||||
|
||||
}
|
||||
|
||||
public saveChanges():void {
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,9 @@ export const MY_ACCOUNT_LAZY_ROUTES:Ng2StateDeclaration[] = [
|
||||
url: '/users/:userId/edit/notifications',
|
||||
loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule),
|
||||
},
|
||||
{
|
||||
name: 'my_reminders.**',
|
||||
url: '/my/reminders',
|
||||
loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -13,6 +13,8 @@ import { NotificationSettingInlineCreateComponent } from 'core-app/features/user
|
||||
import { MY_ACCOUNT_ROUTES } from 'core-app/features/user-preferences/user-preferences.routes';
|
||||
import { NotificationsSettingsToolbarComponent } from './notifications-settings/toolbar/notifications-settings-toolbar.component';
|
||||
import { NotificationSettingsTableComponent } from './notifications-settings/table/notification-settings-table.component';
|
||||
import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component';
|
||||
import { ReminderSettingsDailyTimeComponent } from 'core-app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component';
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
@@ -26,6 +28,8 @@ import { NotificationSettingsTableComponent } from './notifications-settings/tab
|
||||
NotificationSettingInlineCreateComponent,
|
||||
NotificationsSettingsToolbarComponent,
|
||||
NotificationSettingsTableComponent,
|
||||
ReminderSettingsPageComponent,
|
||||
ReminderSettingsDailyTimeComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
import { Ng2StateDeclaration } from '@uirouter/angular';
|
||||
import { NotificationsSettingsPageComponent } from 'core-app/features/user-preferences/notifications-settings/page/notifications-settings-page.component';
|
||||
import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component';
|
||||
|
||||
export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [
|
||||
{
|
||||
@@ -40,4 +41,9 @@ export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [
|
||||
url: '/users/:userId/edit/notifications',
|
||||
component: NotificationsSettingsPageComponent,
|
||||
},
|
||||
{
|
||||
name: 'my_reminders',
|
||||
url: '/my/reminders',
|
||||
component: ReminderSettingsPageComponent,
|
||||
},
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 155 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,7 @@
|
||||
<li><span class="icon icon-drag-handle"></span>drag-handle</li>
|
||||
<li><span class="icon icon-duplicate"></span>duplicate</li>
|
||||
<li><span class="icon icon-edit"></span>edit</li>
|
||||
<li><span class="icon icon-email-alert"></span>email-alert</li>
|
||||
<li><span class="icon icon-enterprise"></span>enterprise</li>
|
||||
<li><span class="icon icon-enumerations"></span>enumerations</li>
|
||||
<li><span class="icon icon-error"></span>error</li>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require 'spec_helper'
|
||||
require 'support/pages/my/notifications'
|
||||
|
||||
# TODO: This feature spec is to be replaced by the reminder_mail_spec.rb in the same directory.
|
||||
describe "Digest email", type: :feature, js: true do
|
||||
let!(:project) { FactoryBot.create :project, members: { current_user => role } }
|
||||
let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } }
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
require 'spec_helper'
|
||||
require 'support/pages/my/notifications'
|
||||
|
||||
describe "Reminder email", type: :feature, js: true do
|
||||
let!(:project) { FactoryBot.create :project, members: { current_user => role } }
|
||||
let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } }
|
||||
let(:reminders_settings_page) { Pages::My::Reminders.new(current_user) }
|
||||
let(:role) { FactoryBot.create(:role, permissions: %i[view_work_packages]) }
|
||||
let(:other_user) { FactoryBot.create(:user) }
|
||||
let(:work_package) { FactoryBot.create(:work_package, project: project) }
|
||||
let(:watched_work_package) { FactoryBot.create(:work_package, project: project, watcher_users: [current_user]) }
|
||||
let(:involved_work_package) { FactoryBot.create(:work_package, project: project, assigned_to: current_user) }
|
||||
|
||||
current_user do
|
||||
FactoryBot.create :user,
|
||||
notification_settings: [
|
||||
FactoryBot.build(:mail_notification_setting,
|
||||
involved: false,
|
||||
watched: false,
|
||||
mentioned: false,
|
||||
work_package_commented: false,
|
||||
work_package_created: false,
|
||||
work_package_processed: false,
|
||||
work_package_prioritized: false,
|
||||
work_package_scheduled: false,
|
||||
all: false),
|
||||
FactoryBot.build(:in_app_notification_setting,
|
||||
involved: false,
|
||||
watched: false,
|
||||
mentioned: false,
|
||||
work_package_commented: false,
|
||||
work_package_created: false,
|
||||
work_package_processed: false,
|
||||
work_package_prioritized: false,
|
||||
work_package_scheduled: false,
|
||||
all: false),
|
||||
FactoryBot.build(:mail_digest_notification_setting,
|
||||
involved: true,
|
||||
watched: true,
|
||||
mentioned: true,
|
||||
work_package_commented: true,
|
||||
work_package_created: true,
|
||||
work_package_processed: true,
|
||||
work_package_prioritized: true,
|
||||
work_package_scheduled: true,
|
||||
all: false)
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
watched_work_package
|
||||
work_package
|
||||
involved_work_package
|
||||
|
||||
ActiveJob::Base.queue_adapter.enqueued_jobs.clear
|
||||
end
|
||||
|
||||
it 'sends a reminder mail based on the configuration', with_settings: { journal_aggregation_time_minutes: 0 } do
|
||||
# Configure the digest
|
||||
reminders_settings_page.visit!
|
||||
|
||||
# By default a reminder timed for 8:00 should be configured
|
||||
reminders_settings_page.expect_active_daily_times("08:00")
|
||||
end
|
||||
end
|
||||
+10
-2
@@ -45,6 +45,14 @@ describe 'my routes', type: :routing do
|
||||
expect(patch('/my/settings')).to route_to('my#update_settings')
|
||||
end
|
||||
|
||||
it '/my/notifications GET routes to my#notifications' do
|
||||
expect(get('/my/notifications')).to route_to('my#notifications')
|
||||
end
|
||||
|
||||
it '/my/reminders GET routes to my#notifications' do
|
||||
expect(get('/my/reminders')).to route_to('my#reminders')
|
||||
end
|
||||
|
||||
it '/my/generate_rss_key POST routes to my#generate_rss_key' do
|
||||
expect(post('/my/generate_rss_key')).to route_to('my#generate_rss_key')
|
||||
end
|
||||
@@ -53,8 +61,8 @@ describe 'my routes', type: :routing do
|
||||
expect(post('/my/generate_api_key')).to route_to('my#generate_api_key')
|
||||
end
|
||||
|
||||
it {
|
||||
it '/my/deletion_info GET routes to users#deletion_info' do
|
||||
expect(get('/my/deletion_info')).to route_to(controller: 'users',
|
||||
action: 'deletion_info')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2021 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
require 'support/pages/reminders/settings'
|
||||
|
||||
module Pages
|
||||
module My
|
||||
class Reminders < ::Pages::Reminders::Settings
|
||||
def path
|
||||
my_reminders_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2021 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See docs/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
require 'support/pages/page'
|
||||
|
||||
module Pages
|
||||
module Reminders
|
||||
class Settings < ::Pages::Page
|
||||
attr_reader :user
|
||||
|
||||
def initialize(user)
|
||||
super()
|
||||
@user = user
|
||||
end
|
||||
|
||||
def path
|
||||
edit_user_path(user, tab: :reminders)
|
||||
end
|
||||
|
||||
def expect_active_daily_times(*times)
|
||||
times.each_with_index do |time, index|
|
||||
expect(page)
|
||||
.to have_checked_field "Time #{index + 1}"
|
||||
|
||||
expect(page)
|
||||
.to have_css("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']")
|
||||
|
||||
expect(page.find("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']").value)
|
||||
.to eql(time)
|
||||
end
|
||||
end
|
||||
|
||||
def save
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 220.71875 145.367188 L 35.273438 145.367188 C 25.675781 145.367188 16.472656 149.023438 9.6875 155.535156 C 2.902344 162.046875 -0.910156 170.878906 -0.910156 180.089844 L -0.910156 457.890625 C -0.910156 467.101562 2.902344 475.933594 9.6875 482.445312 C 16.472656 488.957031 25.675781 492.613281 35.273438 492.613281 L 469.464844 492.613281 C 479.058594 492.613281 488.265625 488.957031 495.054688 482.445312 C 501.835938 475.933594 505.652344 467.101562 505.652344 457.890625 L 505.652344 249.679688 C 495.246094 261.734375 483.046875 272.25 469.464844 280.816406 L 469.464844 457.890625 L 35.273438 457.890625 L 35.273438 195.890625 L 242.058594 333.226562 C 245.082031 335.246094 248.683594 336.324219 252.371094 336.324219 C 256.058594 336.324219 259.65625 335.246094 262.683594 333.226562 L 321.476562 294.175781 C 309.035156 289.199219 297.371094 282.730469 286.707031 275.007812 L 252.371094 297.804688 L 75.078125 180.089844 L 224.277344 180.089844 C 221.941406 169.320312 220.710938 158.144531 220.710938 146.691406 C 220.710938 146.25 220.710938 145.804688 220.71875 145.367188 Z M 220.71875 145.367188 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 381.703125 19.089844 C 310.277344 19.089844 252.371094 76.835938 252.371094 148.046875 C 252.371094 219.261719 310.277344 277 381.703125 277 C 453.125 277 511.03125 219.261719 511.03125 148.046875 C 511.03125 76.835938 453.125 19.089844 381.703125 19.089844 Z M 372.460938 85.875 C 372.460938 84.601562 373.5 83.566406 374.777344 83.566406 L 388.625 83.566406 C 389.902344 83.566406 390.9375 84.601562 390.9375 85.875 C 390.9375 116.445312 390.9375 133.589844 390.9375 164.167969 C 390.9375 165.433594 389.902344 166.46875 388.625 166.46875 L 374.777344 166.46875 C 373.5 166.46875 372.460938 165.433594 372.460938 164.167969 Z M 381.703125 212.527344 C 378.074219 212.453125 374.621094 210.960938 372.082031 208.382812 C 369.546875 205.800781 368.125 202.324219 368.125 198.707031 C 368.125 195.09375 369.546875 191.621094 372.082031 189.035156 C 374.621094 186.453125 378.074219 184.96875 381.703125 184.890625 C 385.328125 184.96875 388.78125 186.453125 391.320312 189.035156 C 393.855469 191.621094 395.273438 195.09375 395.273438 198.707031 C 395.273438 202.324219 393.855469 205.800781 391.320312 208.382812 C 388.78125 210.960938 385.328125 212.453125 381.703125 212.527344 Z M 381.703125 212.527344 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
Reference in New Issue
Block a user