Commit 85be6d83 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into no-ivar-in-modules

* upstream/master: (170 commits)
  support ordering of project notes in notes api
  Redirect to an already forked project if it exists
  Reschedule the migration to populate fork networks
  Create fork networks for forks for which the source was deleted.
  Fix item name and namespace text overflow in Projects dropdown
  Minor backport from EE
  fix link that was linking to `html` instead of `md`
  Backport epic tasklist
  Add timeouts for Gitaly calls
  SSHUploadPack over Gitaly is now OptOut
  fix icon colors in commit list
  Fix star icon color/stroke
  Backport border inline edit
  Add checkboxes to automatically run AutoDevops pipeline
  BE for automatic pipeline when enabling Auto DevOps
  I am certainly weary of debugging sidekiq but I don't think that's what was meant
  Ensure MRs always use branch refs for comparison
  Fix issue comment submit button disabled on GFM paste
  Lock seed-fu at the correct version in Gemfile.lock
  Improve indexes on merge_request_diffs
  ...
parents 68965845 f7254a40
...@@ -152,8 +152,10 @@ stages: ...@@ -152,8 +152,10 @@ stages:
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- master@gitlab/gitlab-ee - master@gitlab/gitlab-ee
##
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
build-package: #
package-qa:
image: ruby:2.4-alpine image: ruby:2.4-alpine
before_script: [] before_script: []
stage: build stage: build
...@@ -419,7 +421,7 @@ ee_compat_check: ...@@ -419,7 +421,7 @@ ee_compat_check:
retry: 0 retry: 0
artifacts: artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}" name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: on_failure when: always
expire_in: 10d expire_in: 10d
paths: paths:
- ee_compat_check/patches/*.patch - ee_compat_check/patches/*.patch
...@@ -577,7 +579,7 @@ codequality: ...@@ -577,7 +579,7 @@ codequality:
script: script:
- cp .rubocop.yml .rubocop.yml.bak - cp .rubocop.yml .rubocop.yml.bak
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml - grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > raw_codeclimate.json - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
- mv .rubocop.yml.bak .rubocop.yml - mv .rubocop.yml.bak .rubocop.yml
artifacts: artifacts:
......
Remove this section and replace it with a description of your MR. Also follow the Add a description of your merge request here. Merge requests without an adequate
checklist below and check off any tasks that are done. If a certain task can not description will not be reviewed until one is added.
be done you should explain so in the MR body. You are free to remove any
sections that do not apply to your MR.
When gathering statistics (e.g. the output of `EXPLAIN ANALYZE`) you should make
sure your database has enough data. Having around 10 000 rows in the tables
being queries should provide a reasonable estimate of how a query will behave.
Also make sure that PostgreSQL uses the following settings:
* `random_page_cost`: `1`
* `work_mem`: `16MB`
* `maintenance_work_mem`: at least `64MB`
* `shared_buffers`: at least `256MB`
If you have access to GitLab.com's staging environment you should also run your
measurements there, and include the results in this MR.
## Database Checklist ## Database Checklist
...@@ -23,34 +8,23 @@ When adding migrations: ...@@ -23,34 +8,23 @@ When adding migrations:
- [ ] Updated `db/schema.rb` - [ ] Updated `db/schema.rb`
- [ ] Added a `down` method so the migration can be reverted - [ ] Added a `down` method so the migration can be reverted
- [ ] Added the output of the migration(s) to the MR body - [ ] Added the output of the migration(s) to the MR body
- [ ] Added the execution time of the migration(s) to the MR body - [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when migrating data)
- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when
migrating data)
- [ ] Made sure the migration won't interfere with a running GitLab cluster,
for example by disabling transactions for long running migrations
When adding or modifying queries to improve performance: When adding or modifying queries to improve performance:
- [ ] Included the raw SQL queries of the relevant queries - [ ] Included data that shows the performance improvement, preferably in the form of a benchmark
- [ ] Included the output of `EXPLAIN ANALYZE` and execution timings of the - [ ] Included the output of `EXPLAIN (ANALYZE, BUFFERS)` of the relevant queries
relevant queries
- [ ] Added tests for the relevant changes
When adding indexes:
- [ ] Described the need for these indexes in the MR body
- [ ] Made sure existing indexes can not be reused instead
When adding foreign keys to existing tables: When adding foreign keys to existing tables:
- [ ] Included a migration to remove orphaned rows in the source table - [ ] Included a migration to remove orphaned rows in the source table before adding the foreign key
- [ ] Removed any instances of `dependent: ...` that may no longer be necessary - [ ] Removed any instances of `dependent: ...` that may no longer be necessary
When adding tables: When adding tables:
- [ ] Ordered columns based on their type sizes in descending order - [ ] Ordered columns based on the [Ordering Table Columns](https://docs.gitlab.com/ee/development/ordering_table_columns.html#ordering-table-columns) guidelines
- [ ] Added foreign keys if necessary - [ ] Added foreign keys to any columns pointing to data in other tables
- [ ] Added indexes if necessary - [ ] Added indexes for fields that are used in statements such as WHERE, ORDER BY, GROUP BY, and JOINs
When removing columns, tables, indexes or other structures: When removing columns, tables, indexes or other structures:
...@@ -64,8 +38,6 @@ When removing columns, tables, indexes or other structures: ...@@ -64,8 +38,6 @@ When removing columns, tables, indexes or other structures:
- [ ] API support added - [ ] API support added
- [ ] Tests added for this feature/bug - [ ] Tests added for this feature/bug
- Review - Review
- [ ] Has been reviewed by UX
- [ ] Has been reviewed by Frontend
- [ ] Has been reviewed by Backend - [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database - [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html) - [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
......
...@@ -2,6 +2,35 @@ ...@@ -2,6 +2,35 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.2.2 (2017-11-23)
### Fixed (5 changes)
- Label addition/removal are not going to be redacted wrongfully in the API. !15080
- Fix bitbucket wiki import with hashed storage enabled. !15490
- Impersonation no longer gets stuck on password change. !15497
- Fix blank states using old css.
- Fix promoting milestone updating all issuables without milestone.
### Performance (3 changes)
- Update Issue Boards to fetch the notification subscription status asynchronously.
- Update composite pipelines index to include "id".
- Use arrays in Pipeline#latest_builds_with_artifacts.
### Other (2 changes)
- Don't move repositories and attachments for projects using hashed storage. !15479
- Add logs for monitoring the merge process.
## 10.2.1 (2017-11-22)
### Fixed (1 change)
- Force disable Prometheus metrics.
## 10.2.0 (2017-11-22) ## 10.2.0 (2017-11-22)
### Security (4 changes) ### Security (4 changes)
......
...@@ -111,7 +111,7 @@ gem 'google-api-client', '~> 0.13.6' ...@@ -111,7 +111,7 @@ gem 'google-api-client', '~> 0.13.6'
gem 'unf', '~> 0.1.4' gem 'unf', '~> 0.1.4'
# Seed data # Seed data
gem 'seed-fu', '~> 2.3.5' gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
...@@ -245,7 +245,7 @@ gem 'font-awesome-rails', '~> 4.7' ...@@ -245,7 +245,7 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3' gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.1.0' gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0' gem 'jquery-rails', '~> 4.3.1'
gem 'request_store', '~> 1.3' gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
...@@ -283,7 +283,7 @@ group :metrics do ...@@ -283,7 +283,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta18' gem 'prometheus-client-mmap', '~> 0.7.0.beta39'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
......
...@@ -411,7 +411,7 @@ GEM ...@@ -411,7 +411,7 @@ GEM
multipart-post multipart-post
oauth (~> 0.5, >= 0.5.0) oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (4.1.1) jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
...@@ -488,7 +488,7 @@ GEM ...@@ -488,7 +488,7 @@ GEM
mini_mime (0.1.4) mini_mime (0.1.4)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.7.0) minitest (5.7.0)
mmap2 (2.2.7) mmap2 (2.2.9)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
multi_json (1.12.2) multi_json (1.12.2)
multi_xml (0.6.0) multi_xml (0.6.0)
...@@ -625,8 +625,8 @@ GEM ...@@ -625,8 +625,8 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.7.0.beta18) prometheus-client-mmap (0.7.0.beta39)
mmap2 (~> 2.2, >= 2.2.7) mmap2 (~> 2.2, >= 2.2.9)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -815,7 +815,7 @@ GEM ...@@ -815,7 +815,7 @@ GEM
rake (>= 0.9, < 13) rake (>= 0.9, < 13)
sass (~> 3.4.20) sass (~> 3.4.20)
securecompare (1.0.0) securecompare (1.0.0)
seed-fu (2.3.6) seed-fu (2.3.7)
activerecord (>= 3.1) activerecord (>= 3.1)
activesupport (>= 3.1) activesupport (>= 3.1)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
...@@ -1061,7 +1061,7 @@ DEPENDENCIES ...@@ -1061,7 +1061,7 @@ DEPENDENCIES
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0) jquery-rails (~> 4.3.1)
json-schema (~> 2.8.0) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
...@@ -1111,7 +1111,7 @@ DEPENDENCIES ...@@ -1111,7 +1111,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta18) prometheus-client-mmap (~> 0.7.0.beta39)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
...@@ -1153,7 +1153,7 @@ DEPENDENCIES ...@@ -1153,7 +1153,7 @@ DEPENDENCIES
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.6) sass-rails (~> 5.0.6)
scss_lint (~> 0.54.0) scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3) sentry-raven (~> 2.5.3)
......
{"iconCount":173,"spriteSize":75815,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":179,"spriteSize":81882,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg height="128" viewBox="0 0 142 128" width="142" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M94 62h20v4H94z" fill="#f0edf8"/><path d="M84.828 84l17.678 17.678-2.828 2.828L82 86.828z" fill="#fee1d3"/><path d="M42.828 24l17.678 17.678-2.828 2.828L40 26.828zM40 101.678L57.678 84l2.828 2.828-17.678 17.678z" fill="#f0edf8"/><g fill="#fee1d3"><path d="M82 41.678L99.678 24l2.828 2.828-17.678 17.678zM28 62h20v4H28z"/><rect height="30" rx="5" width="30" y="49"/></g><rect height="26" rx="5" stroke="#fdc4a8" stroke-width="4" width="26" x="2" y="51"/><rect fill="#c3b8e3" height="50" rx="10" width="50" x="46" y="39"/><rect height="46" rx="10" stroke="#6b4fbb" stroke-width="4" width="46" x="48" y="41"/><rect fill="#fef0e8" height="30" rx="5" width="30" x="84"/><rect height="26" rx="5" stroke="#fee1d3" stroke-width="4" width="26" x="86" y="2"/><rect fill="#fee1d3" height="30" rx="5" width="30" x="84" y="98"/><rect height="26" rx="5" stroke="#fdc4a8" stroke-width="4" width="26" x="86" y="100"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="112" y="49"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="114" y="51"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="28" y="98"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="30" y="100"/><rect fill="#f0edf8" height="30" rx="5" width="30" x="28"/><rect height="26" rx="5" stroke="#e1dbf1" stroke-width="4" width="26" x="30" y="2"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="220" viewBox="0 0 360 220"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".02" d="M125 44V24.003C125 18.48 129.483 14 135.005 14h89.99C230.52 14 235 18.477 235 24.003V43h84.992C326.624 43 332 48.372 332 55.002v144.996c0 6.63-5.38 12.002-12.008 12.002h-85.984c-6.632 0-12.008-5.372-12.008-12.002V183h-78v17.002c0 6.626-5.38 11.998-12.008 11.998H46.008C39.376 212 34 206.624 34 200.002V55.998C34 49.372 39.38 44 46.008 44H125z"/><g transform="translate(214 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><path fill="#F0EDF8" fill-rule="nonzero" d="M57 111c-11.598 0-21-9.402-21-21s9.402-21 21-21 21 9.402 21 21-9.402 21-21 21zm0-4c9.39 0 17-7.61 17-17s-7.61-17-17-17-17 7.61-17 17 7.61 17 17 17z"/><path fill="#6B4FBB" d="M58 88v-6.997c0-1.11-.895-2.003-2-2.003-1.112 0-2 .897-2 2.003v8.994a1.999 1.999 0 0 0 2.503 1.94c.162.04.33.063.506.063h7.98a2 2 0 0 0 .001-4H58z"/><rect width="8" height="4" x="8" y="14" fill="#EEE" rx="2"/><path fill="#EEE" d="M21 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(118 7)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006c0-2.208.896-4.27 2.457-5.77a2 2 0 0 0-2.773-2.883A11.974 11.974 0 0 0 0 12.006a2 2 0 1 0 4 0zM14.388 4h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm18 0h8a2 2 0 0 0 0-4h-8a2 2 0 1 0 0 4zm17.51.227a8.015 8.015 0 0 1 5.022 3.756 2 2 0 1 0 3.458-2.011A12.01 12.01 0 0 0 104.844.34a2 2 0 0 0-.946 3.887zM110 16.78v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm0 18v8a2 2 0 0 0 4 0v-8a2 2 0 1 0-4 0zm-.024 17.844a7.99 7.99 0 0 1-2.903 5.558 2 2 0 0 0 2.54 3.09 11.977 11.977 0 0 0 4.35-8.338 2.002 2.002 0 0 0-1.838-2.15 2.003 2.003 0 0 0-2.15 1.84zM98.826 168h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-18 0h-8a2 2 0 0 0 0 4h8a2 2 0 1 0 0-4zm-17.334-.4a8.032 8.032 0 0 1-4.71-4.143 1.998 1.998 0 0 0-2.667-.938 1.997 1.997 0 0 0-.938 2.667 12.022 12.022 0 0 0 7.063 6.21 1.998 1.998 0 1 0 1.252-3.798zM4 154.434v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0zm0-18v-8a2 2 0 0 0-4 0v8a2 2 0 1 0 4 0z"/><g fill-rule="nonzero"><path fill="#F0EDF8" d="M57 112c-12.15 0-22-9.85-22-22s9.85-22 22-22 22 9.85 22 22-9.85 22-22 22zm0-6c8.837 0 16-7.163 16-16s-7.163-16-16-16-16 7.163-16 16 7.163 16 16 16z"/><path fill="#6B4FBB" d="M41.692 105.8A21.93 21.93 0 0 0 57 112c12.15 0 22-9.85 22-22s-9.85-22-22-22v6c8.837 0 16 7.163 16 16s-7.163 16-16 16a15.935 15.935 0 0 1-11.133-4.508l-4.175 4.31z"/></g><path fill="#EEE" d="M8 16c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2H9.998A1.995 1.995 0 0 1 8 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 21 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 34 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 47 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 60 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 73 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 86 16zm13 0c0-1.105.887-2 1.998-2h4.004c1.103 0 1.998.888 1.998 2 0 1.105-.887 2-1.998 2h-4.004A1.995 1.995 0 0 1 99 16z"/></g><g transform="translate(26 36)"><rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/><path fill="#EEE" fill-rule="nonzero" d="M4 12.006v147.988A8 8 0 0 0 12.005 168h89.99a8.007 8.007 0 0 0 8.005-8.006V12.006A8 8 0 0 0 101.995 4h-89.99A8.007 8.007 0 0 0 4 12.006zm-4 0C0 5.376 5.377 0 12.005 0h89.99C108.628 0 114 5.37 114 12.006v147.988c0 6.63-5.377 12.006-12.005 12.006h-89.99C5.372 172 0 166.63 0 159.994V12.006z"/><g transform="translate(21 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(69 82)"><rect width="24" height="4" y="10" fill="#F0EDF8" rx="2"/><rect width="14" height="4" x="5" fill="#6B4FBB" rx="2"/></g><g transform="translate(38 42)"><rect width="22" height="4" x="8" fill="#FEE1D3" rx="2"/><rect width="38" height="4" y="12" fill="#FB722E" rx="2"/></g><path fill="#EEE" d="M4 14h106v4H4z"/><path fill="#333" d="M35.724 138h9.696v-2.856h-2.856V122.76h-2.592c-1.08.648-2.136 1.08-3.792 1.392v2.184h2.856v8.808h-3.312V138zm17.736.288c-2.952 0-5.76-2.208-5.76-7.56 0-5.688 2.952-8.256 6.168-8.256 2.016 0 3.48.84 4.44 1.824l-1.848 2.112c-.528-.576-1.488-1.08-2.376-1.08-1.68 0-3.024 1.2-3.144 4.752.792-1.008 2.112-1.608 3.048-1.608 2.616 0 4.536 1.488 4.536 4.704 0 3.168-2.304 5.112-5.064 5.112zm-.072-2.64c1.056 0 1.92-.744 1.92-2.472 0-1.608-.84-2.208-1.992-2.208-.792 0-1.68.432-2.304 1.512.312 2.4 1.32 3.168 2.376 3.168zM63.9 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/></g></g></svg>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M45.688 18.854c-4.869-1.989-10.488-1.975-15.29-.001a20.014 20.014 0 0 0-6.493 4.268 19.798 19.798 0 0 0-4.346 6.381 19.135 19.135 0 0 0-1.525 7.537c0 2.066.33 4.118.983 6.104a20.142 20.142 0 0 0 1.83 3.937 5.983 5.983 0 0 0-2.086 4.538c0 3.309 2.691 6 6 6s6-2.691 6-6-2.691-6-6-6c-.779 0-1.522.154-2.205.425a18.13 18.13 0 0 1-1.642-3.533 17.467 17.467 0 0 1-.881-5.472c0-2.351.459-4.623 1.391-6.814a17.721 17.721 0 0 1 3.88-5.675 18.057 18.057 0 0 1 5.85-3.845c4.329-1.778 9.392-1.79 13.78.002a18.077 18.077 0 0 1 5.843 3.84c3.39 3.34 5.257 7.776 5.257 12.493a17.463 17.463 0 0 1-.878 5.481 17.451 17.451 0 0 1-2.569 4.923c-2.134 2.866-3.818 4.698-5.174 6.173-2.424 2.643-3.98 4.599-4.383 8.384H32.215a1 1 0 1 0 0 2h11.739a1 1 0 0 0 .999-.947c.19-3.645 1.345-5.263 3.934-8.09 1.385-1.506 3.107-3.381 5.304-6.331a19.422 19.422 0 0 0 2.864-5.489c.651-1.98.98-4.04.979-6.109 0-5.256-2.078-10.198-5.856-13.92a20.079 20.079 0 0 0-6.49-4.265M28.761 51.612c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M40 74h-4a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2M42 70h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M38 10a1 1 0 0 0 1-1V1a1 1 0 1 0-2 0v8a1 1 0 0 0 1 1M20.828 15.828a.999.999 0 0 0 .707-1.707l-5.656-5.656a.999.999 0 1 0-1.414 1.414l5.656 5.656a.997.997 0 0 0 .707.293M10 33H2a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M60.12 8.465l-5.656 5.656a.999.999 0 1 0 1.414 1.414l5.656-5.656a.999.999 0 1 0-1.414-1.414M74 33h-8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2M43 66H33a1 1 0 1 0 0 2h10a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M5 43a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2H7v-4a1 1 0 1 0-2 0v4H1a1 1 0 1 0 0 2h4v4M75 37h-4v-4a1 1 0 1 0-2 0v4h-4a1 1 0 1 0 0 2h4v4a1 1 0 1 0 2 0v-4h4a1 1 0 1 0 0-2M21 38a1 1 0 0 0 .47.848l8 5a.999.999 0 0 0 1.061-1.696L23.887 38l6.644-4.152a1 1 0 1 0-1.061-1.695l-8 5A.998.998 0 0 0 21 38M55 38a1 1 0 0 0-.47-.848l-8-5a.999.999 0 1 0-1.061 1.695L52.113 38l-6.644 4.152a1 1 0 1 0 1.061 1.696l8-5A1 1 0 0 0 55 38M41.803 26.05a1 1 0 0 0-1.256.65l-7 22a1.001 1.001 0 0 0 .953 1.303 1 1 0 0 0 .953-.697l7-22a1.001 1.001 0 0 0-.65-1.256M62 7c3.859 0 7 3.141 7 7v11a1 1 0 1 0 2 0V14c0-4.963-4.04-9-9-9H45.91c-.479-2.833-2.943-5-5.91-5-3.309 0-6 2.691-6 6s2.691 6 6 6c2.967 0 5.431-2.167 5.91-5H62m-22 3c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M6 26a1 1 0 0 0 1-1V14c0-3.859 3.141-7 7-7h11.09l-3.293 3.293a.999.999 0 1 0 1.414 1.414l5-5a.999.999 0 0 0 0-1.414l-5-5a.999.999 0 1 0-1.414 1.414L25.09 5H14c-4.963 0-9 4.04-9 9v11a1 1 0 0 0 1 1M36 64c-2.967 0-5.431 2.167-5.91 5H14c-3.859 0-7-3.141-7-7V51a1 1 0 1 0-2 0v11c0 4.963 4.04 9 9 9h16.09c.478 2.833 2.942 5 5.91 5 3.309 0 6-2.691 6-6s-2.691-6-6-6m0 10c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4M70 50a1 1 0 0 0-1 1v11c0 3.859-3.141 7-7 7H50.91l3.293-3.293a.999.999 0 1 0-1.414-1.414l-5 5a.999.999 0 0 0 0 1.414l5 5a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414L50.91 71H62c4.963 0 9-4.04 9-9V51a1 1 0 0 0-1-1"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M42.26 40.44a.989.989 0 0 0 1.109-.877l2.625-22.444a.997.997 0 0 0-.993-1.117h-14a1 1 0 0 0-.994 1.108l3.454 31.575a6.981 6.981 0 0 0-2.46 5.317c0 3.859 3.141 7 7 7s7-3.141 7-7-3.141-7-7-7c-.94 0-1.835.189-2.655.527l-3.23-29.527h11.761L41.383 39.33a1 1 0 0 0 .877 1.11m.741 13.562c0 2.757-2.243 5-5 5s-5-2.243-5-5 2.243-5 5-5 5 2.243 5 5"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M12 8c0-3.309-2.691-6-6-6S0 4.691 0 8c0 2.967 2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909s2.167 5.431 5 5.91v8.181c-2.833.478-5 2.942-5 5.909 0 3.309 2.691 6 6 6s6-2.691 6-6c0-2.967-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.478 5-2.942 5-5.91s-2.167-5.431-5-5.91v-8.18c2.833-.479 5-2.943 5-5.91M2 8c0-2.206 1.794-4 4-4s4 1.794 4 4-1.794 4-4 4-4-1.794-4-4m8 60c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4m0-20c0 2.206-1.794 4-4 4s-4-1.794-4-4 1.794-4 4-4 4 1.794 4 4M21 6h54a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M21 12h35a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 24H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 32h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 44H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M21 52h34a1 1 0 1 0 0-2H21a1 1 0 1 0 0 2M75 64H21a1 1 0 1 0 0 2h54a1 1 0 1 0 0-2M55 70H21a1 1 0 1 0 0 2h34a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M67.7 10h-6.751C60.442 4.402 55.728 0 50 0c-6.06 0-11 4.935-11 11s4.935 11 11 11c5.728 0 10.442-4.402 10.949-10H67.7c1.269 0 2.3.987 2.3 2.2v57.6c0 1.213-1.031 2.2-2.3 2.2H8.3C7.031 74 6 73.013 6 71.8V14.2C6 12.987 7.031 12 8.3 12h15.15a1 1 0 1 0 0-2H8.3C5.929 10 4 11.884 4 14.2v57.6C4 74.116 5.929 76 8.3 76h59.4c2.371 0 4.3-1.884 4.3-4.2V14.2c0-2.316-1.929-4.2-4.3-4.2M50 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M21.293 29.29a.999.999 0 0 0 0 1.414l12.975 12.975-12.975 12.974a.999.999 0 1 0 1.414 1.414l13.682-13.682a.999.999 0 0 0 0-1.414L22.707 29.29a.999.999 0 0 0-1.414 0M54 59a1 1 0 1 0 0-2H42a1 1 0 1 0 0 2h12"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M48.949 37C48.442 31.402 43.728 27 38 27s-10.442 4.402-10.949 10h-13.05a1 1 0 1 0 0 2h13.05c.507 5.598 5.221 10 10.949 10s10.442-4.402 10.949-10h12.24a1 1 0 1 0 0-2h-12.24M38 47c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9"/><path d="M73.236 23.749a1 1 0 0 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M14.267 7.32l-4.896 5.277-1.702-1.533a.999.999 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M31 9h44a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2M31 15h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 0C4.93 0 0 4.935 0 11s4.935 11 11 11 11-4.935 11-11S17.065 0 11 0m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 34.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36M75 34H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M31 42h24a1 1 0 1 0 0-2H31a1 1 0 1 0 0 2"/><path d="M11 27C4.93 27 0 31.935 0 38s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M14.267 61.32l-4.896 5.277-1.702-1.533a1 1 0 1 0-1.338 1.486l2.434 2.192c.064.058.139.091.212.13.035.018.065.048.101.062a.99.99 0 0 0 .752-.016c.044-.019.077-.058.118-.084.076-.047.155-.086.219-.154l5.566-6a1 1 0 0 0-1.466-1.36"/><path d="M11 54C4.93 54 0 58.935 0 65s4.935 11 11 11 11-4.935 11-11-4.935-11-11-11m0 20c-4.963 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9M75 61H31a1 1 0 1 0 0 2h44a1 1 0 1 0 0-2M55 67H31a1 1 0 1 0 0 2h24a1 1 0 1 0 0-2"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M73.236 23.749a1 1 0 1 0-1.854.75A35.788 35.788 0 0 1 74 38c0 19.851-16.149 36-36 36S2 57.851 2 38 18.149 2 38 2c7.6 0 14.83 2.332 20.965 6.74A5.955 5.955 0 0 0 58 12c0 1.603.624 3.109 1.758 4.242A5.956 5.956 0 0 0 64 18a5.956 5.956 0 0 0 4.242-1.758C69.376 15.109 70 13.603 70 12s-.624-3.109-1.758-4.242A5.956 5.956 0 0 0 64 6a5.943 5.943 0 0 0-3.668 1.259C53.812 2.512 46.104 0 38 0 17.047 0 0 17.047 0 38s17.047 38 38 38 38-17.047 38-38c0-4.93-.93-9.725-2.764-14.251zM64 8c1.068 0 2.072.416 2.828 1.172S68 10.932 68 12s-.416 2.072-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0C60.416 14.072 60 13.068 60 12s.416-2.072 1.172-2.828S62.932 8 64 8z"/><path d="M27.19 32.17a.997.997 0 0 0-1.366-.364L13.17 39.132a1 1 0 0 0 0 1.73l12.654 7.326a1 1 0 0 0 1.002-1.73l-11.159-6.461 11.159-6.461a.998.998 0 0 0 .364-1.366M48.808 47.827a1 1 0 0 0 1.366.364l12.654-7.326a1 1 0 0 0 0-1.73l-12.654-7.326a1 1 0 0 0-1.002 1.73L60.331 40l-11.159 6.461a.998.998 0 0 0-.364 1.366M42.71 23.06L31.398 56.29a1 1 0 0 0 1.892.645l11.312-33.23a1 1 0 0 0-1.892-.645"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M62.44 54.765l-9.912-11.09c.315-3.881.481-7.241.508-10.271-.029-13.871-3.789-23.05-13.413-32.746-.855-.859-2.411-.828-3.294.059-7.594 7.65-11.139 13.934-12.575 22.3a6.94 6.94 0 0 0-4.699 2.039c-1.321 1.321-2.05 3.079-2.05 4.949s.729 3.628 2.051 4.949c1.321 1.322 3.079 2.051 4.949 2.051s3.628-.729 4.949-2.051a6.951 6.951 0 0 0 2.051-4.949 6.955 6.955 0 0 0-2.051-4.949c-.9-.9-2-1.517-3.205-1.824 1.373-7.859 4.764-13.818 11.999-21.11.128-.13.356-.158.456-.059 9.207 9.274 12.805 18.06 12.832 31.33-.026 3.079-.202 6.527-.536 10.54a.997.997 0 0 0 .25.749l10.166 11.379c.062.076.109.23.093.32l-4.547 17.407c-.004.015-.009.036-.079.106a.403.403 0 0 1-.2.106l-3.577.002c-.144-.009-.265-.077-.309-.153l-5.425-10.328a1.002 1.002 0 0 0-.886-.535H30.024c-.371 0-.713.206-.886.535l-5.407 10.303-.069.072a.366.366 0 0 1-.199.105l-3.588.001c-.179-.009-.304-.123-.33-.227l-4.531-17.338a.525.525 0 0 1 .049-.34L25.26 44.682a1 1 0 0 0-1.492-1.332L13.539 54.803c-.448.554-.63 1.312-.474 2.084l4.544 17.396c.253.963 1.146 1.669 2.218 1.719h3.636c.581 0 1.187-.261 1.615-.693.114-.114.286-.286.406-.528l5.144-9.793h14.754l5.16 9.822c.396.697 1.124 1.143 2.01 1.192l3.712-.003a2.396 2.396 0 0 0 1.544-.694c.313-.316.504-.646.598-1.022l4.557-17.451a2.502 2.502 0 0 0-.518-2.066M29.01 30.001c0 1.335-.521 2.591-1.465 3.535s-2.2 1.465-3.535 1.465-2.591-.521-3.535-1.465-1.465-2.2-1.465-3.535.521-2.591 1.465-3.535 2.2-1.465 3.535-1.465 2.591.521 3.535 1.465 1.465 2.2 1.465 3.535"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 76"><path d="M68 67c-1.725 0-3.36.541-4.723 1.545A12.998 12.998 0 0 0 52 62c-2.734 0-5.359.853-7.555 2.43L42.159 49h1.228l3.829 7.645c.339.598.962.979 1.724 1.022l2.812-.003a2.07 2.07 0 0 0 1.316-.595c.264-.266.433-.559.514-.882l3.433-13.145a2.138 2.138 0 0 0-.449-1.763l-7.385-8.268c.231-2.875.354-5.376.374-7.641C49.532 14.863 46.684 7.908 39.393.564c-.737-.742-2.072-.715-2.829.044-5.617 5.659-8.309 10.336-9.446 16.463a5.95 5.95 0 0 0-3.36 1.686C22.624 19.891 22 21.397 22 23s.624 3.109 1.758 4.242C24.891 28.376 26.397 29 28 29s3.109-.624 4.242-1.758C33.376 26.109 34 24.603 34 23s-.624-3.109-1.758-4.242a5.952 5.952 0 0 0-3.098-1.648c1.095-5.538 3.637-9.855 8.83-15.14 6.874 6.924 9.561 13.485 9.581 23.392-.021 2.316-.151 4.903-.402 7.91a.999.999 0 0 0 .25.749l7.663 8.572-3.391 13.07-2.695.036-4.081-8.15a1.001 1.001 0 0 0-.895-.553h-12.01c-.379 0-.725.214-.895.553l-4.04 8.114-2.707.015-3.427-13.07 7.671-8.588a1 1 0 0 0-1.492-1.332l-7.7 8.623c-.383.47-.54 1.116-.406 1.787l3.419 13.08c.216.829.98 1.438 1.907 1.48h2.735c.508 0 1.016-.218 1.391-.595.091-.09.242-.241.358-.475l3.804-7.597h1.228l-2.286 15.43a12.914 12.914 0 0 0-7.555-2.43c-4.685 0-8.979 2.53-11.277 6.545a7.943 7.943 0 0 0-4.723-1.545c-4.411 0-8 3.589-8 8a1 1 0 0 0 1 1h74a1 1 0 0 0 1-1c0-4.411-3.589-8-8-8m-36-44a3.973 3.973 0 0 1-1.172 2.828c-1.512 1.512-4.145 1.512-5.656 0-.756-.756-1.172-1.76-1.172-2.828s.416-2.072 1.172-2.828 1.76-1.172 2.828-1.172 2.072.416 2.828 1.172 1.172 1.76 1.172 2.828m-29.917 51a6.01 6.01 0 0 1 5.917-5c1.638 0 3.17.652 4.313 1.836a.998.998 0 0 0 1.634-.289 11.011 11.011 0 0 1 10.05-6.547c2.836 0 5.532 1.085 7.593 3.055a1.001 1.001 0 0 0 1.681-.576l2.588-17.479h4.275l2.589 17.479a.999.999 0 1 0 1.681.576 10.945 10.945 0 0 1 7.593-3.055c4.343 0 8.288 2.57 10.05 6.547a.998.998 0 0 0 1.634.289 5.948 5.948 0 0 1 4.313-1.836 6.01 6.01 0 0 1 5.917 5H2.076"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 16 16"><g fill-rule="evenodd"><path d="M14 16.01h1V7.99C15 4.128 11.866.999 8 .999c-3.858 0-7 3.13-7 6.991v8.02h1V7.99c0-3.306 2.691-5.991 6-5.991 3.314 0 6 2.682 6 5.991v8.02M3.48 2.656a2 2 0 1 0-2.155 3.228c.102-.321.226-.631.371-.93a1.001 1.001 0 1 1 1.069-1.599 6.96 6.96 0 0 1 .717-.699m9.04-.002a2 2 0 1 1 2.155 3.23 6.835 6.835 0 0 0-.37-.931 1 1 0 1 0-1.068-1.599 6.96 6.96 0 0 0-.717-.699"/><path d="M5.726 8.04h1.557v.124c0 .283-.033.534-.1.752a1.583 1.583 0 0 1-.33.566c-.35.394-.795.591-1.335.591-.527 0-.979-.19-1.355-.571a1.893 1.893 0 0 1-.564-1.377c0-.547.191-1.01.574-1.391a1.902 1.902 0 0 1 1.396-.574c.295 0 .57.06.825.181.244.12.484.316.72.586l-.405.388c-.309-.412-.686-.618-1.13-.618-.399 0-.733.138-1 .413-.27.27-.405.609-.405 1.015 0 .42.151.766.452 1.037.282.252.587.378.915.378.28 0 .531-.094.754-.283.223-.19.347-.418.373-.683h-.94v-.535m2.884.061c0-.53.194-.986.583-1.367a1.919 1.919 0 0 1 1.396-.571c.537 0 .998.192 1.382.576.386.384.578.845.578 1.384 0 .542-.194 1-.581 1.379a1.944 1.944 0 0 1-1.408.569c-.487 0-.923-.168-1.311-.505-.426-.373-.64-.861-.64-1.465m.574.007c0 .417.14.759.42 1.028.278.269.6.403.964.403.395 0 .729-.137 1-.41.272-.277.408-.613.408-1.01 0-.402-.134-.739-.403-1.01a1.33 1.33 0 0 0-.991-.41c-.392 0-.723.137-.993.41a1.36 1.36 0 0 0-.405 1m-.184 3.918c.525.026.812.063.812.063.271.025.324-.096.116-.273 0 0-.775-.813-1.933-.813-1.159 0-1.923.813-1.923.813-.211.174-.153.3.12.273 0 0 .286-.037.81-.063v.477c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.252.25c0 .268.224.5.5.5.268 0 .5-.223.5-.498v-.478m-1-1.023c.552 0 1-.224 1-.5s-.448-.5-1-.5-1 .224-1 .5.448.5 1 .5"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" version="1" viewBox="0 0 501 501"><path d="M236 .7C137.7 7.5 54 68.2 18.2 158.5c-32 81-19.6 172.8 33 242.5 39.8 53 97.2 87 164.3 97 16.5 2.7 48 3.2 63.5 1.2 48.7-6.3 92.2-24.6 129-54.2 13-10.5 33-31.2 42.2-43.7 26.4-35.5 42.8-75.8 49-120.3 1.6-12.3 1.6-48.7 0-61-4-28.3-12-54.8-24.2-79.5-12.8-26-26.5-45.3-46.8-65.8C417.8 64 400.2 49 398.4 49c-.6 0-.4 10.5.3 26l1.3 26 7 8.7c19 23.7 32.8 53.5 38.2 83 2.5 14 3 43 1 55.8-4.5 27.8-15.2 54-31 76.5-8.6 12.2-28 31.6-40.2 40.2-24 17-50 27.6-80 33-10 1.8-49 1.8-59 0-43-7.7-78.8-26-107.2-54.8-29.3-29.7-46.5-64-52.4-104.4-2-14-1.5-42 1-55C90 121.4 132 72 192 49.7c8-3 18.4-5.8 29.5-8.2 1.7-.4 34.4-38 35.3-40.6.3-1-10.2-1-20.8-.4z"/><path d="M322.2 24.6c-1.3.8-8.4 9.3-16 18.7-7.4 9.5-22.4 28-33.2 41.2-51 62.2-66 81.6-70.6 91-6 12-8.4 21-9 33-1.2 19.8 5 36 19 50C222 268 230 273 243 277.2c9 3 10.4 3.2 24 3.2 13.8 0 15 0 22.6-3 23.2-9 39-28.4 45-55.7 2-8.2 2-28.7.4-79.7l-2-72c-1-36.8-1.4-41.8-3-44-2-3-4.8-3.6-7.8-1.4z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="0" width="159.8" height="127.81" x=".196" y="5" rx="10"/><rect id="2" width="160" height="128" x=".666" y=".41" rx="10"/><rect id="4" width="160.19" height="128.19" x=".339" y=".59" rx="10"/><mask id="1" width="159.8" height="127.81" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask><mask id="3" width="160" height="128" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask><mask id="5" width="160.19" height="128.19" x="0" y="0" fill="#fff"><use xlink:href="#4"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(12 3)"><rect width="160" height="128" x="122.08" y="146.08" fill="#f9f9f9" transform="matrix(.99619.08716-.08716.99619 19.08-16.813)" rx="10"/><g transform="matrix(.96593.25882-.25882.96593 227.1 57.47)"><rect width="159.8" height="127.81" x="1.64" y="10.06" fill="#f9f9f9" rx="8"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#1)" xlink:href="#0"/><g transform="translate(24.368 36.951)"><path fill="#d2caea" fill-rule="nonzero" d="m71.785 44.2c.761.296 1.625.099 2.184-.496l35.956-38.34c.756-.806.715-2.071-.091-2.827-.806-.756-2.071-.715-2.827.091l-35.03 37.36-41.888-16.285c-.749-.291-1.6-.106-2.16.471l-26.368 27.16c-.769.793-.751 2.059.042 2.828.793.769 2.059.751 2.828-.042l25.444-26.21 41.911 16.294"/><g fill="#fff"><circle cx="5.716" cy="5.104" r="5" stroke="#6b4fbb" stroke-width="4" transform="translate(65.917 34.945)"/><g stroke="#fb722e"><ellipse cx="4.632" cy="50.05" stroke-width="3.2" rx="4" ry="3.999"/><g stroke-width="4"><ellipse cx="29.632" cy="27.05" rx="4" ry="3.999"/><ellipse cx="107.63" cy="4.048" rx="4" ry="3.999"/></g></g></g></g></g><rect width="160.19" height="128.19" x="36.28" y="86.74" fill="#f9f9f9" transform="matrix(.99619-.08716.08716.99619-12.703 10.717)" rx="10"/><g transform="matrix(.99619.08716-.08716.99619 126.61 137.8)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#3)" xlink:href="#2"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width="3.2" d="m84.67 28.41c18.225 0 33 15.07 33 33.651h-33v-33.651" stroke-linecap="round" stroke-linejoin="round"/><path fill="#d2caea" fill-rule="nonzero" d="m78.67 66.41h30c1.105 0 2 .895 2 2 0 18.778-15.222 34-34 34-18.778 0-34-15.222-34-34 0-18.778 15.222-34 34-34 1.105 0 2 .895 2 2v30m-32 2c0 16.569 13.431 30 30 30 15.896 0 28.905-12.364 29.934-28h-29.934c-1.105 0-2-.895-2-2v-29.934c-15.636 1.029-28 14.04-28 29.934"/></g><g transform="matrix(.99619-.08716.08716.99619 30 88.03)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#5)" xlink:href="#4"/><g transform="translate(42 34)"><path fill="#fef0ea" d="m0 13.391c0-.768.628-1.391 1.4-1.391h9.2c.773 0 1.4.626 1.4 1.391v49.609h-12v-49.609"/><path fill="#fb722e" d="m66 21.406c0-.777.628-1.406 1.4-1.406h9.2c.773 0 1.4.624 1.4 1.406v41.594h-12v-41.594"/><path fill="#6b4fbb" d="m22 1.404c0-.776.628-1.404 1.4-1.404h9.2c.773 0 1.4.624 1.4 1.404v61.6h-12v-61.6"/><path fill="#d2caea" d="m44 39.4c0-.772.628-1.398 1.4-1.398h9.2c.773 0 1.4.618 1.4 1.398v23.602h-12v-23.602"/></g></g><g fill="#fee8dc"><path d="m6.226 94.95l-2.84.631c-1.075.239-1.752-.445-1.515-1.515l.631-2.84-.631-2.84c-.239-1.075.445-1.752 1.515-1.515l2.84.631 2.84-.631c1.075-.239 1.752.445 1.515 1.515l-.631 2.84.631 2.84c.239 1.075-.445 1.752-1.515 1.515l-2.84-.631" transform="matrix(.70711.70711-.70711.70711 66.33 22.317)"/><path d="m312.78 53.43l-3.634.807c-1.296.288-2.115-.52-1.825-1.825l.807-3.634-.807-3.634c-.288-1.296.52-2.115 1.825-1.825l3.634.807 3.634-.807c1.296-.288 2.115.52 1.825 1.825l-.807 3.634.807 3.634c.288 1.296-.52 2.115-1.825 1.825l-3.634-.807" transform="matrix(.70711.70711-.70711.70711 126.1-206.88)"/></g><path fill="#e1dcf1" d="m124.78 12.43l-3.617.804c-1.306.29-2.129-.53-1.839-1.839l.804-3.617-.804-3.617c-.29-1.306.53-2.129 1.839-1.839l3.617.804 3.617-.804c1.306-.29 2.129.53 1.839 1.839l-.804 3.617.804 3.617c.29 1.306-.53 2.129-1.839 1.839l-3.617-.804" transform="matrix(.70711-.70711.70711.70711 31.05 90.51)"/><path fill="#d2caea" d="m374.78 244.43l-3.617.804c-1.306.29-2.129-.53-1.839-1.839l.804-3.617-.804-3.617c-.29-1.306.53-2.129 1.839-1.839l3.617.804 3.617-.804c1.306-.29 2.129.53 1.839 1.839l-.804 3.617.804 3.617c.29 1.306-.53 2.129-1.839 1.839l-3.617-.804" transform="matrix(.70711-.70711.70711.70711-59.779 335.24)"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="159.8" height="127.81" x=".196" y="5" rx="10"/><rect id="b" width="160" height="128" x=".666" y=".41" rx="10"/><rect id="c" width="160.19" height="128.19" x=".339" y=".59" rx="10"/><mask id="d" width="159.8" height="127.81" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><mask id="e" width="160" height="128" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><mask id="f" width="160.19" height="128.19" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(12 3)"><rect width="160" height="128" x="122.08" y="146.08" fill="#f9f9f9" transform="rotate(5 202.071 210.085)" rx="10"/><g transform="rotate(15 -104.714 891.23)"><rect width="159.8" height="127.81" x="1.64" y="10.06" fill="#f9f9f9" rx="8"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#d)" xlink:href="#a"/><path fill="#d2caea" fill-rule="nonzero" d="M96.153 81.151a2.001 2.001 0 0 0 2.184-.496l35.956-38.34a2 2 0 1 0-2.918-2.736l-35.03 37.36-41.888-16.285a2 2 0 0 0-2.16.471l-26.368 27.16a2 2 0 1 0 2.87 2.786l25.444-26.21 41.911 16.294"/><g fill="#fff" transform="translate(24.368 36.951)"><circle cx="5.716" cy="5.104" r="5" stroke="#6b4fbb" stroke-width="4" transform="translate(65.917 34.945)"/><g stroke="#fb722e"><ellipse cx="4.632" cy="50.05" stroke-width="3.2" rx="4" ry="3.999"/><g stroke-width="4"><ellipse cx="29.632" cy="27.05" rx="4" ry="3.999"/><ellipse cx="107.63" cy="4.048" rx="4" ry="3.999"/></g></g></g></g><rect width="160.19" height="128.19" x="36.28" y="86.74" fill="#f9f9f9" transform="rotate(-5 116.372 150.825)" rx="10"/><g transform="rotate(5 -1514.687 1518.752)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#e)" xlink:href="#b"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width="3.2" d="M84.67 28.41c18.225 0 33 15.07 33 33.651h-33V28.41" stroke-linecap="round" stroke-linejoin="round"/><path fill="#d2caea" fill-rule="nonzero" d="M78.67 66.41h30a2 2 0 0 1 2 2c0 18.778-15.222 34-34 34s-34-15.222-34-34 15.222-34 34-34a2 2 0 0 1 2 2v30m-32 2c0 16.569 13.431 30 30 30 15.896 0 28.905-12.364 29.934-28H76.67a2 2 0 0 1-2-2V38.476c-15.636 1.029-28 14.04-28 29.934"/></g><g transform="rotate(-5 1023.06 -299.524)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#f)" xlink:href="#c"/><path fill="#fef0ea" d="M42 47.391c0-.768.628-1.391 1.4-1.391h9.2c.773 0 1.4.626 1.4 1.391V97H42V47.391"/><path fill="#fb722e" d="M108 55.406c0-.777.628-1.406 1.4-1.406h9.2a1.4 1.4 0 0 1 1.4 1.406V97h-12V55.406"/><path fill="#6b4fbb" d="M64 35.404c0-.776.628-1.404 1.4-1.404h9.2a1.4 1.4 0 0 1 1.4 1.404v61.6H64v-61.6"/><path fill="#d2caea" d="M86 73.4a1.4 1.4 0 0 1 1.4-1.398h9.2c.773 0 1.4.618 1.4 1.398v23.602H86V73.4"/></g><g fill="#fee8dc"><path d="M3.592 93.86l-2.454-1.562c-.93-.592-.924-1.554 0-2.143l2.454-1.562 1.562-2.454c.592-.93 1.554-.925 2.143 0l1.562 2.454 2.454 1.562c.93.591.924 1.554 0 2.143L8.86 93.86l-1.562 2.454c-.591.93-1.554.924-2.143 0L3.592 93.86M309.489 52.07l-3.14-1.998c-1.12-.713-1.128-1.863 0-2.581l3.14-2 1.999-3.14c.713-1.12 1.863-1.127 2.58 0l2 3.14 3.14 2c1.12.713 1.128 1.863 0 2.58l-3.14 2-2 3.14c-.712 1.12-1.862 1.128-2.58 0l-1.999-3.14"/></g><path fill="#e1dcf1" d="M128.073 11.066l-1.99 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/><path fill="#d2caea" d="M378.07 243.068l-1.989 3.126c-.718 1.129-1.88 1.131-2.6 0l-1.99-3.126-3.126-1.989c-1.128-.718-1.13-1.88 0-2.6l3.127-1.99 1.989-3.126c.718-1.128 1.88-1.13 2.6 0l1.99 3.126 3.126 1.99c1.128.718 1.13 1.88 0 2.6l-3.126 1.99"/></g></svg>
\ No newline at end of file \ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 107" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="#eee" fill-rule="evenodd"><path d="M4.01 2h1.102a1 1 0 0 0 0-2H4.01A4.001 4.001 0 0 0 0 4a1 1 0 0 0 2 0c0-1.108.892-2 2.01-2m12.702 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7m11.6 0a1 1 0 0 0 0-2h-5.7a1 1 0 0 0 0 2h5.7M164 2c.822 0 1.554.503 1.86 1.254a1 1 0 1 0 1.853-.753 4.01 4.01 0 0 0-3.712-2.5h-2.188a1 1 0 0 0 0 2h2.188m2.01 12.518a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 11.6a1 1 0 0 0 2 0v-5.7a1 1 0 0 0-2 0v5.7m0 6.282c0 1.108-.892 2-2.01 2h-.72a1 1 0 0 0 0 2h.72a4.001 4.001 0 0 0 4.01-4v-.382a1 1 0 0 0-2 0v.382m-14.325 2a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-11.6 0a1 1 0 0 0 0 2h5.7a1 1 0 0 0 0-2h-5.7m-8.47 0a2.01 2.01 0 0 1-1.782-1.085 1 1 0 0 0-1.775.923 4.007 4.007 0 0 0 3.556 2.162h2.57a1 1 0 0 0 0-2h-2.57m-2.01-12.136a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-11.6a1 1 0 0 0-2 0v5.7a1 1 0 0 0 2 0v-5.7m0-6.664a1 1 0 0 0-2 0v.764a1 1 0 0 0 2 0v-.764" id="a"/><circle cx="21" cy="24" r="10"/><rect width="33" height="3" x="37" y="18" rx="1.5" id="b"/><rect width="53" height="3" x="37" y="27" rx="1.5" id="c"/><path d="M131 29c0 .552.447.999.996.999h22.01c.545 0 .996-.451.996-.999v-9a.998.998 0 0 0-.996-.999h-22.01c-.545 0-.996.451-.996.999v9m.996-12h22.01a2.998 2.998 0 0 1 2.996 2.999v9a3.003 3.003 0 0 1-2.996 2.999h-22.01A2.998 2.998 0 0 1 129 28.999v-9A3.003 3.003 0 0 1 131.996 17" id="d"/><g transform="translate(0 59)"><use xlink:href="#a"/><circle cx="21" cy="24" r="10"/><use xlink:href="#b"/><use xlink:href="#c"/><use xlink:href="#d"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M59.65 32.65H60l-2-2.42-2 2.4-2-2.4-2 2.4-2-2.4-2 2.4-2-2.4-2 2.42h.77C45.57 34.6 46 36.75 46 39c0 2.84-.7 5.5-1.92 7.86 1.97 2.28 4.83 3.64 7.92 3.64 5.8 0 10.5-4.74 10.5-10.6 0-2.8-1.08-5.36-2.85-7.25zM43.18 29.6c2.4-2.1 5.52-3.3 8.82-3.3 7.46 0 13.5 6.1 13.5 13.6S59.46 53.5 52 53.5c-3.68 0-7.1-1.5-9.6-4.04C39.3 53.44 34.44 56 29 56c-9.4 0-17-7.6-17-17s7.6-17 17-17c3.22 0 6.23.9 8.8 2.45 2.13 1.3 3.97 3.05 5.38 5.16zM17 34c-.65 1.54-1 3.23-1 5 0 7.18 5.82 13 13 13s13-5.82 13-13c0-1.77-.35-3.46-1-5h-9c-.53 0-1.04-.2-1.4-.6L29 31.84l-1.6 1.58c-.36.4-.87.6-1.4.6h-9zm21.38-4a12.996 12.996 0 0 0-18.76 0h5.55l2.42-2.4c.74-.8 2-.8 2.8 0l2.4 2.4h5.54z"/><path fill="#6B4FBB" d="M47.6 42.32c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zm8.8 0c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zM25 44h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-1c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 24c-2.21 0-4 1.79-4 4v22c0 2.21 1.79 4 4 4h18c2.21 0 4-1.79 4-4V28c0-2.21-1.79-4-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#6B4FBB" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z"/><path fill="#6B4FBB" d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M24.92 35.15a4.012 4.012 0 0 1-.6-5.63l1.26-1.55c1.4-1.72 3.9-2 5.63-.6l.7.56c.7-.4 1.4-.73 2.1-1V26c0-2.2 1.8-4 4-4h2c2.2 0 4 1.8 4 4v.92c.8.28 1.5.62 2.1 1l.7-.55c1.7-1.4 4.3-1.12 5.7.6l1.3 1.55c1.4 1.72 1.2 4.23-.6 5.63l-.7.6c.3.74.4 1.5.5 2.3l.9.2c2.2.5 3.5 2.64 3 4.8L56.4 45c-.5 2.15-2.64 3.5-4.8 3l-.88-.2c-.44.63-.92 1.24-1.46 1.8l.4.82c.9 1.98.1 4.38-1.9 5.35l-1.8.87c-2 .97-4.37.15-5.34-1.84l-.46-.85c-.34.03-.74.05-1.13.05-.4 0-.8-.02-1.2-.05l-.4.85c-.95 2-3.34 2.8-5.33 1.84l-1.8-.87a4.011 4.011 0 0 1-1.83-5.35l.4-.8c-.54-.58-1.02-1.2-1.46-1.83l-.8.2c-2.2.5-4.3-.9-4.8-3l-.4-2c-.5-2.2.85-4.3 3-4.8l.9-.2c.1-.8.3-1.6.5-2.3l-.7-.6zm4.95.77c-.53 1.2-.83 2.47-.87 3.8-.02.9-.66 1.68-1.55 1.9l-2.32.53.45 1.94 2.3-.6c.9-.2 1.8.2 2.23 1 .7 1.1 1.5 2.2 2.5 3 .7.6.9 1.6.5 2.4l-1 2.1 1.8.9 1.1-2.1c.4-.8 1.3-1.3 2.2-1.1.7.1 1.3.2 2 .2s1.3-.1 2-.2c.9-.2 1.8.3 2.2 1.1l1 2.1 1.8-.9-1.2-2c-.4-.8-.2-1.8.5-2.4 1-.85 1.84-1.88 2.45-3.05.4-.82 1.33-1.24 2.2-1.04l2.33.54.45-1.95-2.32-.54c-.9-.2-1.52-.97-1.54-1.88-.03-1.4-.33-2.6-.86-3.8-.4-.9-.2-1.8.5-2.4l1.9-1.5-1.3-1.6-1.8 1.5c-.8.5-1.8.6-2.5 0-1.1-.8-2.3-1.4-3.5-1.7-.9-.2-1.5-1-1.5-1.9V26h-2v2.38c0 .9-.6 1.7-1.5 1.93-1.3.4-2.5 1-3.5 1.7-.8.6-1.8.6-2.5 0l-1.9-1.5-1.26 1.6 1.8 1.5c.7.6.94 1.6.6 2.4z"/><path fill="#FC6D26" fill-rule="nonzero" d="M39 46c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="330" height="132" viewBox="0 0 330 132"><g fill="none" fill-rule="evenodd"><path fill="#000" fill-opacity=".03" d="M174.12 42c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M211 78c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S230.33 4 211 4s-35 15.67-35 35 15.67 35 35 35z"/><g fill-rule="nonzero"><path fill="#FEE1D3" d="M211.5 51c-6.42 0-12.26-2.84-17.43-8.4a4.008 4.008 0 0 1-.27-5.13C199 30.57 204.92 27 211.5 27s12.5 3.56 17.7 10.47a3.994 3.994 0 0 1-.27 5.12c-5.17 5.53-11 8.4-17.43 8.4zm0-4c5.25 0 10.05-2.34 14.5-7.13-4.5-5.98-9.3-8.87-14.5-8.87-5.2 0-10 2.9-14.5 8.87 4.45 4.8 9.25 7.13 14.5 7.13z"/><path fill="#FC6D26" d="M211 47c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-4c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm0-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"/></g><path fill="#000" fill-opacity=".03" d="M88.12 83c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M125 119c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M116 86.34c2.33.83 4 3.05 4 5.66 0 3.3-2.7 6-6 6s-6-2.7-6-6c0-2.6 1.67-4.83 4-5.66V72h4v14.34zM128 66c5.52 0 10 4.48 10 10v12h-4V76c0-3.3-2.7-6-6-6v1.83c0 .55-.45 1-1 1-.24 0-.47-.1-.65-.24l-4.46-3.87c-.46-.36-.5-1-.15-1.4.03-.05.07-.1.1-.12l4.47-3.82c.42-.35 1.05-.3 1.4.1.16.2.25.43.25.66V66zm-14 28c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#FC6D26" fill-rule="nonzero" d="M114 74c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm22 28c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/><path fill="#000" fill-opacity=".03" d="M2.12 52C2.04 53 2 54 2 55c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 71.03 58.42 86 39 86S3.65 71.03 2.12 52z"/><path fill="#EEE" fill-rule="nonzero" d="M39 88C17.46 88 0 70.54 0 49s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 14 39 14 4 29.67 4 49s15.67 35 35 35z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M48 41h-4c0-2.76-2.24-5-5-5s-5 2.24-5 5h-4a9 9 0 0 1 18 0zm-18 0h4v3h-4v-3zm14 0h4v3h-4v-3z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M30 47c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V48c0-.55-.45-1-1-1H30zm0-4h18c2.76 0 5 2.24 5 5v12c0 2.76-2.24 5-5 5H30c-2.76 0-5-2.24-5-5V48c0-2.76 2.24-5 5-5z"/><path fill="#6B4FBB" d="M38 53.73c-.6-.34-1-1-1-1.73 0-1.1.9-2 2-2s2 .9 2 2c0 .74-.4 1.4-1 1.73V55c0 .55-.45 1-1 1s-1-.45-1-1v-1.27z"/><path fill="#000" fill-opacity=".03" d="M254.12 92c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/><path fill="#EEE" fill-rule="nonzero" d="M291 128c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/><path fill="#6B4BBE" fill-rule="nonzero" d="M292 78c5.52 0 10 4.48 10 10 0 2.28-.76 4.43-2.14 6.18-1.03 1.3-.8 3.2.5 4.22 1.3 1.02 3.2.8 4.2-.5 2.22-2.8 3.44-6.26 3.44-9.9 0-8.84-7.16-16-16-16v-3.13c0-.2-.06-.4-.17-.56-.3-.42-.93-.54-1.38-.23l-9.2 6.13c-.1.06-.2.16-.28.27-.3.45-.18 1.08.28 1.38l9.2 6.13c.16.1.35.17.55.17.55 0 1-.45 1-1V78z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M290 100c-5.52 0-10-4.48-10-10 0-2.25.74-4.38 2.1-6.12 1-1.3.77-3.2-.54-4.2-1.3-1.02-3.2-.78-4.2.53A15.796 15.796 0 0 0 274 90c0 8.84 7.16 16 16 16v3.13c0 .55.45 1 1 1 .2 0 .4-.06.55-.17l9.2-6.13c.46-.3.6-.93.28-1.38-.07-.1-.17-.2-.28-.28l-9.2-6.13c-.45-.3-1.08-.2-1.38.27-.1.2-.17.4-.17.6v3.1z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M30.24 27.823A14.98 14.98 0 0 0 24 40c0 2.549.636 4.949 1.757 7.051-.297-2.684.644-4.026 2.823-4.026 3.707 0 2.462 5.365 4.473 5.761 2.01.396 4.175.396 4.267 3.29.04 1.257-.265 2.157-.917 2.7a15.095 15.095 0 0 0 8.555-1.006c.035-1.91.303-4.941 2.21-5.61 2.373-.833-.55-1.431.734-3.368 1.17-1.762-3.297-5.2 0-4.832 3.477.388 5.044-.816 6.024-1.456a14.903 14.903 0 0 0-1.373-4.94c-.873.4-2.19.465-3.702-.538-.757-.502-1.084-3.944-2.107-3.944-3.823 0-4.065 3.17-5.994 3.944-1.076.431-4.193 3.773-5.614 3.596-1.126-.14-1.071-4.417-2.45-5.166-1.359-.738-2.174-1.948-2.447-3.633zM39 59c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76S3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#6B4FBB" d="M33 52h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm1 5h10a2 2 0 1 1 0 4H34a2 2 0 1 1 0-4z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M45.542 46.932l.346-2.36a8.004 8.004 0 0 1 1.566-3.705c3.025-3.946 4.485-7.29 4.547-9.96C52.153 24.41 46.843 20 39 20c-7.777 0-13 4.374-13 11 0 2.4 1.462 5.73 4.573 9.846a8.009 8.009 0 0 1 1.536 3.683l.353 2.456 13.08-.054zm-17.038.624L28.15 45.1a3.997 3.997 0 0 0-.768-1.842C23.794 38.51 22 34.424 22 31c0-9.39 7.61-15 17-15s17.218 5.614 17 15c-.085 3.64-1.875 7.74-5.37 12.3a3.99 3.99 0 0 0-.784 1.853l-.346 2.36a4.003 4.003 0 0 1-3.942 3.42l-13.08.053a4 4 0 0 1-3.974-3.43z"/><path fill="#6B4FBB" d="M41 38.732a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268zm-6 0a2 2 0 1 1 2 0V42a1 1 0 0 1-2 0v-3.268z"/></g></svg>
\ No newline at end of file
...@@ -10,6 +10,7 @@ import 'core-js/fn/string/from-code-point'; ...@@ -10,6 +10,7 @@ import 'core-js/fn/string/from-code-point';
import 'core-js/fn/symbol'; import 'core-js/fn/symbol';
// Browser polyfills // Browser polyfills
import 'classlist-polyfill';
import './polyfills/custom_event'; import './polyfills/custom_event';
import './polyfills/element'; import './polyfills/element';
import './polyfills/event'; import './polyfills/event';
......
...@@ -317,7 +317,6 @@ import ProjectVariables from './project_variables'; ...@@ -317,7 +317,6 @@ import ProjectVariables from './project_variables';
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new Diff(); new Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
initIssuableSidebar(); initIssuableSidebar();
...@@ -327,6 +326,8 @@ import ProjectVariables from './project_variables'; ...@@ -327,6 +326,8 @@ import ProjectVariables from './project_variables';
window.mergeRequest = new MergeRequest({ window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction, action: mrShowNode.dataset.mrAction,
}); });
shortcut_handler = new ShortcutsIssuable(true);
break; break;
case 'dashboard:activity': case 'dashboard:activity':
new gl.Activities(); new gl.Activities();
...@@ -382,6 +383,7 @@ import ProjectVariables from './project_variables'; ...@@ -382,6 +383,7 @@ import ProjectVariables from './project_variables';
projectImport(); projectImport();
break; break;
case 'projects:pipelines:new': case 'projects:pipelines:new':
case 'projects:pipelines:create':
new NewBranchForm($('.js-new-pipeline-form')); new NewBranchForm($('.js-new-pipeline-form'));
break; break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
...@@ -520,6 +522,13 @@ import ProjectVariables from './project_variables'; ...@@ -520,6 +522,13 @@ import ProjectVariables from './project_variables';
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
import(/* webpackChunkName: "ci-cd-settings" */ './projects/ci_cd_settings_bundle')
.then(ciCdSettings => ciCdSettings.default())
.catch((err) => {
Flash(s__('ProjectSettings|Problem setting up the CI/CD settings JavaScript'));
throw err;
});
case 'groups:settings:ci_cd:show': case 'groups:settings:ci_cd:show':
new ProjectVariables(); new ProjectVariables();
break; break;
......
...@@ -3,6 +3,7 @@ const DATA_DROPDOWN = 'data-dropdown'; ...@@ -3,6 +3,7 @@ const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected'; const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active'; const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore'; const IGNORE_CLASS = 'droplab-item-ignore';
const IGNORE_HIDING_CLASS = 'droplab-item-ignore-hiding';
// Matches `{{anything}}` and `{{ everything }}`. // Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g; const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
...@@ -13,4 +14,5 @@ export { ...@@ -13,4 +14,5 @@ export {
ACTIVE_CLASS, ACTIVE_CLASS,
TEMPLATE_REGEX, TEMPLATE_REGEX,
IGNORE_CLASS, IGNORE_CLASS,
IGNORE_HIDING_CLASS,
}; };
import utils from './utils'; import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants'; import { SELECTED_CLASS, IGNORE_CLASS, IGNORE_HIDING_CLASS } from './constants';
class DropDown { class DropDown {
constructor(list) { constructor(list, config = {}) {
this.currentIndex = 0; this.currentIndex = 0;
this.hidden = true; this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list; this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = []; this.items = [];
this.eventWrapper = {}; this.eventWrapper = {};
if (config.addActiveClassToDropdownButton) {
this.dropdownToggle = this.list.parentNode.querySelector('.js-dropdown-toggle');
}
this.getItems(); this.getItems();
this.initTemplateString(); this.initTemplateString();
this.addEvents(); this.addEvents();
...@@ -42,7 +45,7 @@ class DropDown { ...@@ -42,7 +45,7 @@ class DropDown {
this.addSelectedClass(selected); this.addSelectedClass(selected);
e.preventDefault(); e.preventDefault();
this.hide(); if (!e.target.classList.contains(IGNORE_HIDING_CLASS)) this.hide();
const listEvent = new CustomEvent('click.dl', { const listEvent = new CustomEvent('click.dl', {
detail: { detail: {
...@@ -67,7 +70,20 @@ class DropDown { ...@@ -67,7 +70,20 @@ class DropDown {
addEvents() { addEvents() {
this.eventWrapper.clickEvent = this.clickEvent.bind(this); this.eventWrapper.clickEvent = this.clickEvent.bind(this);
this.eventWrapper.closeDropdown = this.closeDropdown.bind(this);
this.list.addEventListener('click', this.eventWrapper.clickEvent); this.list.addEventListener('click', this.eventWrapper.clickEvent);
this.list.addEventListener('keyup', this.eventWrapper.closeDropdown);
}
closeDropdown(event) {
// `ESC` key closes the dropdown.
if (event.keyCode === 27) {
event.preventDefault();
return this.toggle();
}
return true;
} }
setData(data) { setData(data) {
...@@ -110,6 +126,8 @@ class DropDown { ...@@ -110,6 +126,8 @@ class DropDown {
this.list.style.display = 'block'; this.list.style.display = 'block';
this.currentIndex = 0; this.currentIndex = 0;
this.hidden = false; this.hidden = false;
if (this.dropdownToggle) this.dropdownToggle.classList.add('active');
} }
hide() { hide() {
...@@ -117,6 +135,8 @@ class DropDown { ...@@ -117,6 +135,8 @@ class DropDown {
this.list.style.display = 'none'; this.list.style.display = 'none';
this.currentIndex = 0; this.currentIndex = 0;
this.hidden = true; this.hidden = true;
if (this.dropdownToggle) this.dropdownToggle.classList.remove('active');
} }
toggle() { toggle() {
...@@ -128,6 +148,7 @@ class DropDown { ...@@ -128,6 +148,7 @@ class DropDown {
destroy() { destroy() {
this.hide(); this.hide();
this.list.removeEventListener('click', this.eventWrapper.clickEvent); this.list.removeEventListener('click', this.eventWrapper.clickEvent);
this.list.removeEventListener('keyup', this.eventWrapper.closeDropdown);
} }
static setImagesSrc(template) { static setImagesSrc(template) {
......
...@@ -3,7 +3,7 @@ import DropDown from './drop_down'; ...@@ -3,7 +3,7 @@ import DropDown from './drop_down';
class Hook { class Hook {
constructor(trigger, list, plugins, config) { constructor(trigger, list, plugins, config) {
this.trigger = trigger; this.trigger = trigger;
this.list = new DropDown(list); this.list = new DropDown(list, config);
this.type = 'Hook'; this.type = 'Hook';
this.event = 'click'; this.event = 'click';
this.plugins = plugins || []; this.plugins = plugins || [];
......
...@@ -36,7 +36,10 @@ export default function dropzoneInput(form) { ...@@ -36,7 +36,10 @@ export default function dropzoneInput(form) {
$formDropzone.append(divHover); $formDropzone.append(divHover);
$formDropzone.find('.div-dropzone-hover').append(iconPaperclip); $formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
if (!uploadsPath) return; if (!uploadsPath) {
$formDropzone.addClass('js-invalid-dropzone');
return;
}
const dropzone = $formDropzone.dropzone({ const dropzone = $formDropzone.dropzone({
url: uploadsPath, url: uploadsPath,
......
<script>
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
environments: {
type: Array,
required: true,
},
pagination: {
type: Object,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
},
components: {
environmentTable,
loadingIcon,
tablePagination,
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
},
},
};
</script>
<template>
<div class="environments-container">
<loading-icon
label="Loading environments"
v-if="isLoading"
size="3"
/>
<slot name="emptyState"></slot>
<div
class="table-holder"
v-if="!isLoading && environments.length > 0">
<environment-table
:environments="environments"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
<table-pagination
v-if="pagination && pagination.totalPages > 1"
:change="onChangePage"
:pageInfo="pagination"
/>
</div>
</div>
</template>
<script>
export default {
name: 'environmentsEmptyState',
props: {
newPath: {
type: String,
required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="blank-state-row">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
{{s__("Environments|You don't have any environments right now.")}}
</h2>
<p class="blank-state-text">
{{s__("Environments|Environments are places where code gets deployed, such as staging or production.")}}
<br />
<a :href="helpPath">
{{s__("Environments|Read more about environments")}}
</a>
</p>
<a
v-if="canCreateEnvironment"
:href="newPath"
class="btn btn-create js-new-environment-button">
{{s__("Environments|New environment")}}
</a>
</div>
</div>
</template>
<script>
import Visibility from 'visibilityjs';
import Flash from '../../flash';
import EnvironmentsService from '../services/environments_service';
import environmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
import eventHub from '../event_hub';
import Poll from '../../lib/utils/poll';
import environmentsMixin from '../mixins/environments_mixin';
export default {
components: {
environmentTable,
tablePagination,
loadingIcon,
},
mixins: [
environmentsMixin,
],
data() {
const environmentsData = document.querySelector('#environments-list-view').dataset;
const store = new EnvironmentsStore();
return {
store,
state: store.state,
visibility: 'available',
isLoading: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
canCreateEnvironment: environmentsData.canCreateEnvironment,
projectEnvironmentsPath: environmentsData.projectEnvironmentsPath,
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
isMakingRequest: false,
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
};
},
computed: {
scope() {
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return convertPermissionToBoolean(this.canCreateDeployment);
},
canCreateEnvironmentParsed() {
return convertPermissionToBoolean(this.canCreateEnvironment);
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created() {
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
const poll = new Poll({
resource: this.service,
method: 'get',
data: { scope, page },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
},
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('toggleFolder', this.toggleFolder);
eventHub.$on('postAction', this.postAction);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
eventHub.$off('postAction');
},
methods: {
toggleFolder(folder) {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, true);
}
},
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
* @return {String}
*/
changePage(pageNumber) {
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;
return this.service.get({ scope, page })
.then(this.successCallback)
.catch(this.errorCallback);
},
fetchChildEnvironments(folder, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folder.folder_path)
.then(resp => resp.json())
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
postAction(endpoint) {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occurred while making the request.'));
}
},
successCallback(resp) {
this.saveData(resp);
// We need to verify if any folder is open to also update it
const openFolders = this.store.getOpenFolders();
if (openFolders.length) {
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
}
},
errorCallback() {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
},
},
};
</script>
<template>
<div :class="cssContainerClass">
<div class="top-area">
<ul
v-if="!isLoading"
class="nav-links">
<li :class="{ active: scope === null || scope === 'available' }">
<a :href="projectEnvironmentsPath">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li :class="{ active : scope === 'stopped' }">
<a :href="projectStoppedEnvironmentsPath">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
<div
v-if="canCreateEnvironmentParsed && !isLoading"
class="nav-controls">
<a
:href="newEnvironmentPath"
class="btn btn-create">
New environment
</a>
</div>
</div>
<div class="environments-container">
<loading-icon
label="Loading environments"
size="3"
v-if="isLoading"
/>
<div
class="blank-state-row"
v-if="!isLoading && state.environments.length === 0">
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production.
<br />
<a :href="helpPagePath">
Read more about environments
</a>
</p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New environment
</a>
</div>
</div>
<div
class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
/>
</div>
<table-pagination
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation" />
</div>
</div>
</template>
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { s__ } from '../../locale';
/** /**
* Renders the external url link in environments table. * Renders the external url link in environments table.
...@@ -18,7 +19,7 @@ export default { ...@@ -18,7 +19,7 @@ export default {
computed: { computed: {
title() { title() {
return 'Open'; return s__('Environments|Open');
}, },
}, },
}; };
......
...@@ -432,7 +432,7 @@ export default { ...@@ -432,7 +432,7 @@ export default {
v-if="!model.isFolder" v-if="!model.isFolder"
class="table-mobile-header" class="table-mobile-header"
role="rowheader"> role="rowheader">
Environment {{s__("Environments|Environment")}}
</div> </div>
<a <a
v-if="!model.isFolder" v-if="!model.isFolder"
...@@ -505,7 +505,7 @@ export default { ...@@ -505,7 +505,7 @@ export default {
<div <div
role="rowheader" role="rowheader"
class="table-mobile-header"> class="table-mobile-header">
Commit {{s__("Environments|Commit")}}
</div> </div>
<div <div
v-if="hasLastDeploymentKey" v-if="hasLastDeploymentKey"
...@@ -521,7 +521,7 @@ export default { ...@@ -521,7 +521,7 @@ export default {
<div <div
v-if="!hasLastDeploymentKey" v-if="!hasLastDeploymentKey"
class="commit-title table-mobile-content"> class="commit-title table-mobile-content">
No deployments yet {{s__("Environments|No deployments yet")}}
</div> </div>
</div> </div>
...@@ -531,7 +531,7 @@ export default { ...@@ -531,7 +531,7 @@ export default {
<div <div
role="rowheader" role="rowheader"
class="table-mobile-header"> class="table-mobile-header">
Updated {{s__("Environments|Updated")}}
</div> </div>
<span <span
v-if="canShowDate" v-if="canShowDate"
......
...@@ -34,6 +34,7 @@ export default { ...@@ -34,6 +34,7 @@ export default {
:aria-label="title"> :aria-label="title">
<i <i
class="fa fa-area-chart" class="fa fa-area-chart"
aria-hidden="true" /> aria-hidden="true"
/>
</a> </a>
</template> </template>
...@@ -48,10 +48,10 @@ export default { ...@@ -48,10 +48,10 @@ export default {
:disabled="isLoading"> :disabled="isLoading">
<span v-if="isLastDeployment"> <span v-if="isLastDeployment">
Re-deploy {{s__("Environments|Re-deploy")}}
</span> </span>
<span v-else> <span v-else>
Rollback {{s__("Environments|Rollback")}}
</span> </span>
<loading-icon v-if="isLoading" /> <loading-icon v-if="isLoading" />
......
<script>
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
props: {
endpoint: {
type: String,
required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
cssContainerClass: {
type: String,
required: true,
},
newEnvironmentPath: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
},
components: {
emptyState,
},
mixins: [
CIPaginationMixin,
environmentsMixin,
],
created() {
eventHub.$on('toggleFolder', this.toggleFolder);
},
beforeDestroy() {
eventHub.$off('toggleFolder');
},
methods: {
toggleFolder(folder) {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
this.fetchChildEnvironments(folder, true);
}
},
fetchChildEnvironments(folder, showLoader = false) {
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folder.folder_path)
.then(resp => resp.json())
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
Flash(s__('Environments|An error occurred while fetching the environments.'));
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
successCallback(resp) {
this.saveData(resp);
// We need to verify if any folder is open to also update it
const openFolders = this.store.getOpenFolders();
if (openFolders.length) {
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
}
},
},
};
</script>
<template>
<div :class="cssContainerClass">
<div class="top-area">
<tabs
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="environments"
/>
<div
v-if="canCreateEnvironment && !isLoading"
class="nav-controls">
<a
:href="newEnvironmentPath"
class="btn btn-create">
{{s__("Environments|New environment")}}
</a>
</div>
</div>
<container
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
>
<empty-state
slot="emptyState"
v-if="!isLoading && state.environments.length === 0"
:new-path="newEnvironmentPath"
:help-path="helpPagePath"
:can-create-environment="canCreateEnvironment"
/>
</container>
</div>
</template>
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
/** /**
* Render environments table. * Render environments table.
*/ */
import EnvironmentTableRowComponent from './environment_item.vue'; import environmentItem from './environment_item.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
components: { components: {
'environment-item': EnvironmentTableRowComponent, environmentItem,
loadingIcon, loadingIcon,
}, },
...@@ -42,19 +42,19 @@ export default { ...@@ -42,19 +42,19 @@ export default {
<div class="ci-table" role="grid"> <div class="ci-table" role="grid">
<div class="gl-responsive-table-row table-row-header" role="row"> <div class="gl-responsive-table-row table-row-header" role="row">
<div class="table-section section-10 environments-name" role="columnheader"> <div class="table-section section-10 environments-name" role="columnheader">
Environment {{s__("Environments|Environment")}}
</div> </div>
<div class="table-section section-10 environments-deploy" role="columnheader"> <div class="table-section section-10 environments-deploy" role="columnheader">
Deployment {{s__("Environments|Deployment")}}
</div> </div>
<div class="table-section section-15 environments-build" role="columnheader"> <div class="table-section section-15 environments-build" role="columnheader">
Job {{s__("Environments|Job")}}
</div> </div>
<div class="table-section section-25 environments-commit" role="columnheader"> <div class="table-section section-25 environments-commit" role="columnheader">
Commit {{s__("Environments|Commit")}}
</div> </div>
<div class="table-section section-10 environments-date" role="columnheader"> <div class="table-section section-10 environments-date" role="columnheader">
Updated {{s__("Environments|Updated")}}
</div> </div>
</div> </div>
<template <template
...@@ -86,7 +86,7 @@ export default { ...@@ -86,7 +86,7 @@ export default {
<a <a
:href="folderUrl(model)" :href="folderUrl(model)"
class="btn btn-default"> class="btn btn-default">
Show all {{s__("Environments|Show all")}}
</a> </a>
</div> </div>
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import EnvironmentsComponent from './components/environment.vue'; import environmentsComponent from './components/environments_app.vue';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-list-view', el: '#environments-list-view',
components: { components: {
'environments-table-app': EnvironmentsComponent, environmentsComponent,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-component', {
props: {
endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
}, },
render: createElement => createElement('environments-table-app'),
})); }));
import Vue from 'vue'; import Vue from 'vue';
import EnvironmentsFolderComponent from './environments_folder_view.vue'; import environmentsFolderApp from './environments_folder_view.vue';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#environments-folder-list-view', el: '#environments-folder-list-view',
components: { components: {
'environments-folder-app': EnvironmentsFolderComponent, environmentsFolderApp,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-folder-app', {
props: {
endpoint: this.endpoint,
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
}, },
render: createElement => createElement('environments-folder-app'),
})); }));
<script> <script>
import Visibility from 'visibilityjs'; import environmentsMixin from '../mixins/environments_mixin';
import Flash from '../../flash'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import EnvironmentsService from '../services/environments_service';
import environmentTable from '../components/environments_table.vue'; export default {
import EnvironmentsStore from '../stores/environments_store'; props: {
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; endpoint: {
import tablePagination from '../../vue_shared/components/table_pagination.vue'; type: String,
import Poll from '../../lib/utils/poll'; required: true,
import eventHub from '../event_hub'; },
import environmentsMixin from '../mixins/environments_mixin'; folderName: {
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils'; type: String,
required: true,
export default { },
components: { cssContainerClass: {
environmentTable, type: String,
tablePagination, required: true,
loadingIcon, },
}, canCreateDeployment: {
type: Boolean,
mixins: [ required: true,
environmentsMixin, },
], canReadEnvironment: {
type: Boolean,
data() { required: true,
const environmentsData = document.querySelector('#environments-folder-list-view').dataset;
const store = new EnvironmentsStore();
const pathname = window.location.pathname;
const endpoint = `${pathname}.json`;
const folderName = pathname.substr(pathname.lastIndexOf('/') + 1);
return {
store,
folderName,
endpoint,
state: store.state,
visibility: 'available',
isLoading: false,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
};
},
computed: {
scope() {
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return convertPermissionToBoolean(this.canCreateDeployment);
},
/**
* URL to link in the stopped tab.
*
* @return {String}
*/
stoppedPath() {
return `${window.location.pathname}?scope=stopped`;
},
/**
* URL to link in the available tab.
*
* @return {String}
*/
availablePath() {
return window.location.pathname;
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created() {
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
const poll = new Poll({
resource: this.service,
method: 'get',
data: { scope, page },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
}, },
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('postAction', this.postAction);
},
beforeDestroyed() {
eventHub.$off('postAction');
},
methods: {
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
*/
changePage(pageNumber) {
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;
return this.service.get({ scope, page })
.then(this.successCallback)
.catch(this.errorCallback);
},
successCallback(resp) {
this.saveData(resp);
},
errorCallback() {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
}, },
mixins: [
postAction(endpoint) { environmentsMixin,
if (!this.isMakingRequest) { CIPaginationMixin,
this.isLoading = true; ],
methods: {
this.service.postAction(endpoint) successCallback(resp) {
.then(() => this.fetchEnvironments()) this.saveData(resp);
.catch(() => new Flash('An error occurred while making the request.')); },
}
}, },
}, };
};
</script> </script>
<template> <template>
<div :class="cssContainerClass"> <div :class="cssContainerClass">
...@@ -171,56 +43,23 @@ export default { ...@@ -171,56 +43,23 @@ export default {
v-if="!isLoading"> v-if="!isLoading">
<h4 class="js-folder-name environments-folder-name"> <h4 class="js-folder-name environments-folder-name">
Environments / <b>{{folderName}}</b> {{s__("Environments|Environments")}} / <b>{{folderName}}</b>
</h4> </h4>
<ul class="nav-links"> <tabs
<li :class="{ active: scope === null || scope === 'available' }"> :tabs="tabs"
<a @onChangeTab="onChangeTab"
:href="availablePath" scope="environments"
class="js-available-environments-folder-tab">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li :class="{ active : scope === 'stopped' }">
<a
:href="stoppedPath"
class="js-stopped-environments-folder-tab">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
</div>
<div class="environments-container">
<loading-icon
label="Loading environments"
v-if="isLoading"
size="3"
/> />
<div
class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
/>
<table-pagination
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation"/>
</div>
</div> </div>
<container
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
/>
</div> </div>
</template> </template>
/**
* Common code between environmets app and folder view
*/
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
import {
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue';
export default { export default {
components: {
environmentTable,
container,
loadingIcon,
tabs,
tablePagination,
},
data() {
const store = new EnvironmentsStore();
return {
store,
state: store.state,
isLoading: false,
isMakingRequest: false,
scope: getParameterByName('scope') || 'available',
page: getParameterByName('page') || '1',
requestData: {},
};
},
methods: { methods: {
saveData(resp) { saveData(resp) {
const headers = resp.headers; const headers = resp.headers;
return resp.json().then((response) => { return resp.json().then((response) => {
this.isLoading = false; this.isLoading = false;
this.store.storeAvailableCount(response.available_count); if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
this.store.storeStoppedCount(response.stopped_count); this.store.storeAvailableCount(response.available_count);
this.store.storeEnvironments(response.environments); this.store.storeStoppedCount(response.stopped_count);
this.store.setPagination(headers); this.store.storeEnvironments(response.environments);
this.store.setPagination(headers);
}
}); });
}, },
/**
* Handles URL and query parameter changes.
* When the user uses the pagination or the tabs,
* - update URL
* - Make API request to the server with new parameters
* - Update the polling function
* - Update the internal state
*/
updateContent(parameters) {
this.updateInternalState(parameters);
// fetch new data
return this.service.get(this.requestData)
.then(response => this.successCallback(response))
.then(() => {
// restart polling
this.poll.restart({ data: this.requestData });
})
.catch(() => {
this.errorCallback();
// restart polling
this.poll.restart();
});
},
errorCallback() {
this.isLoading = false;
Flash(s__('Environments|An error occurred while fetching the environments.'));
},
postAction(endpoint) {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => {
this.isLoading = false;
Flash(s__('Environments|An error occurred while making the request.'));
});
}
},
fetchEnvironments() {
this.isLoading = true;
return this.service.get(this.requestData)
.then(this.successCallback)
.catch(this.errorCallback);
},
},
computed: {
tabs() {
return [
{
name: s__('Available'),
scope: 'available',
count: this.state.availableCounter,
isActive: this.scope === 'available',
},
{
name: s__('Stopped'),
scope: 'stopped',
count: this.state.stoppedCounter,
isActive: this.scope === 'stopped',
},
];
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created() {
this.service = new EnvironmentsService(this.endpoint);
this.requestData = { page: this.page, scope: this.scope };
this.poll = new Poll({
resource: this.service,
method: 'get',
data: this.requestData,
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
},
});
if (!Visibility.hidden()) {
this.isLoading = true;
this.poll.makeRequest();
} else {
this.fetchEnvironments();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
eventHub.$on('postAction', this.postAction);
},
beforeDestroyed() {
eventHub.$off('postAction');
}, },
}; };
...@@ -36,7 +36,12 @@ export default class EnvironmentsStore { ...@@ -36,7 +36,12 @@ export default class EnvironmentsStore {
storeEnvironments(environments = []) { storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => { const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments const oldEnvironmentState = this.state.environments
.find(element => element.id === env.latest.id) || {}; .find((element) => {
if (env.latest) {
return element.id === env.latest.id;
}
return element.id === env.id;
}) || {};
let filtered = {}; let filtered = {};
......
...@@ -41,7 +41,7 @@ const createFlashEl = (message, type, isInContentWrapper = false) => ` ...@@ -41,7 +41,7 @@ const createFlashEl = (message, type, isInContentWrapper = false) => `
`; `;
const removeFlashClickListener = (flashEl, fadeTransition) => { const removeFlashClickListener = (flashEl, fadeTransition) => {
flashEl.parentNode.addEventListener('click', () => hideFlash(flashEl, fadeTransition)); flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
}; };
/* /*
......
...@@ -4,9 +4,11 @@ import tooltip from '../../vue_shared/directives/tooltip'; ...@@ -4,9 +4,11 @@ import tooltip from '../../vue_shared/directives/tooltip';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { COMMON_STR } from '../constants'; import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
Icon,
PopupDialog, PopupDialog,
}, },
directives: { directives: {
...@@ -63,9 +65,9 @@ export default { ...@@ -63,9 +65,9 @@ export default {
:aria-label="editBtnTitle" :aria-label="editBtnTitle"
data-container="body" data-container="body"
class="edit-group btn no-expand"> class="edit-group btn no-expand">
<i <icon
class="fa fa-cogs" name="settings">
aria-hidden="true"/> </icon>
</a> </a>
<a <a
v-tooltip v-tooltip
......
...@@ -16,6 +16,10 @@ export default { ...@@ -16,6 +16,10 @@ export default {
required: true, required: true,
type: String, type: String,
}, },
updateEndpoint: {
required: true,
type: String,
},
canUpdate: { canUpdate: {
required: true, required: true,
type: Boolean, type: Boolean,
...@@ -34,6 +38,11 @@ export default { ...@@ -34,6 +38,11 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
issuableRef: { issuableRef: {
type: String, type: String,
required: true, required: true,
...@@ -240,6 +249,7 @@ export default { ...@@ -240,6 +249,7 @@ export default {
:project-namespace="projectNamespace" :project-namespace="projectNamespace"
:show-delete-button="showDeleteButton" :show-delete-button="showDeleteButton"
:can-attach-file="canAttachFile" :can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/> />
<div v-else> <div v-else>
<title-component <title-component
...@@ -256,6 +266,8 @@ export default { ...@@ -256,6 +266,8 @@ export default {
:description-text="state.descriptionText" :description-text="state.descriptionText"
:updated-at="state.updatedAt" :updated-at="state.updatedAt"
:task-status="state.taskStatus" :task-status="state.taskStatus"
:issuable-type="issuableType"
:update-url="updateEndpoint"
/> />
<edited-component <edited-component
v-if="hasUpdated" v-if="hasUpdated"
......
...@@ -22,6 +22,16 @@ ...@@ -22,6 +22,16 @@
required: false, required: false,
default: '', default: '',
}, },
issuableType: {
type: String,
required: false,
default: 'issue',
},
updateUrl: {
type: String,
required: false,
default: null,
},
}, },
data() { data() {
return { return {
...@@ -48,7 +58,7 @@ ...@@ -48,7 +58,7 @@
if (this.canUpdate) { if (this.canUpdate) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new TaskList({ new TaskList({
dataType: 'issue', dataType: this.issuableType,
fieldName: 'description', fieldName: 'description',
selector: '.detail-page-description', selector: '.detail-page-description',
}); });
...@@ -95,7 +105,9 @@ ...@@ -95,7 +105,9 @@
<textarea <textarea
class="hidden js-task-list-field" class="hidden js-task-list-field"
v-if="descriptionText" v-if="descriptionText"
v-model="descriptionText"> v-model="descriptionText"
:data-update-url="updateUrl"
>
</textarea> </textarea>
</div> </div>
</template> </template>
...@@ -22,6 +22,11 @@ ...@@ -22,6 +22,11 @@
required: false, required: false,
default: true, default: true,
}, },
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
components: { components: {
markdownField, markdownField,
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile"> :can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
>
<textarea <textarea
id="issue-description" id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area" class="note-textarea js-gfm-input js-autosize markdown-area"
......
...@@ -46,6 +46,11 @@ ...@@ -46,6 +46,11 @@
required: false, required: false,
default: true, default: true,
}, },
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
components: { components: {
lockedWarning, lockedWarning,
...@@ -89,7 +94,9 @@ ...@@ -89,7 +94,9 @@
:form-state="formState" :form-state="formState"
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile" /> :can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
<edit-actions <edit-actions
:form-state="formState" :form-state="formState"
:can-destroy="canDestroy" :can-destroy="canDestroy"
......
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
v-tooltip v-tooltip
v-if="showInlineEditButton && canUpdate" v-if="showInlineEditButton && canUpdate"
type="button" type="button"
class="btn-blank btn-edit note-action-button" class="btn btn-default btn-edit btn-svg"
v-html="pencilIcon" v-html="pencilIcon"
title="Edit title and description" title="Edit title and description"
data-placement="bottom" data-placement="bottom"
......
...@@ -190,7 +190,7 @@ export const insertText = (target, text) => { ...@@ -190,7 +190,7 @@ export const insertText = (target, text) => {
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave // Trigger autosave
$(target).trigger('input'); target.dispatchEvent(new Event('input'));
// Trigger autosize // Trigger autosize
const event = document.createEvent('Event'); const event = document.createEvent('Event');
...@@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({ ...@@ -269,46 +269,6 @@ export const parseIntPagination = paginationInformation => ({
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10), previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
}); });
/**
* Updates the search parameter of a URL given the parameter and value provided.
*
* If no search params are present we'll add it.
* If param for page is already present, we'll update it
* If there are params but not for the given one, we'll add it at the end.
* Returns the new search parameters.
*
* @param {String} param
* @param {Number|String|Undefined|Null} value
* @return {String}
*/
export const setParamInURL = (param, value) => {
let search;
const locationSearch = window.location.search;
if (locationSearch.length) {
const parameters = locationSearch.substring(1, locationSearch.length)
.split('&')
.reduce((acc, element) => {
const val = element.split('=');
// eslint-disable-next-line no-param-reassign
acc[val[0]] = decodeURIComponent(val[1]);
return acc;
}, {});
parameters[param] = value;
const toString = Object.keys(parameters)
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
.join('&');
search = `?${toString}`;
} else {
search = `?${param}=${value}`;
}
return search;
};
/** /**
* Given a string of query parameters creates an object. * Given a string of query parameters creates an object.
* *
......
...@@ -150,3 +150,17 @@ export function timeIntervalInWords(intervalInSeconds) { ...@@ -150,3 +150,17 @@ export function timeIntervalInWords(intervalInSeconds) {
} }
return text; return text;
} }
export function dateInWords(date, abbreviated = false) {
if (!date) return date;
const month = date.getMonth();
const year = date.getFullYear();
const monthNames = [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')];
const monthNamesAbbr = [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')];
const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month];
return `${monthName} ${date.getDate()}, ${year}`;
}
...@@ -55,3 +55,12 @@ export const slugify = str => str.trim().toLowerCase(); ...@@ -55,3 +55,12 @@ export const slugify = str => str.trim().toLowerCase();
*/ */
export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`; export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
/**
* Capitalizes first character
*
* @param {String} text
* @return {String}
*/
export function capitalizeFirstCharacter(text) {
return `${text[0].toUpperCase()}${text.slice(1)}`;
}
...@@ -69,7 +69,6 @@ import './project_import'; ...@@ -69,7 +69,6 @@ import './project_import';
import './projects_dropdown'; import './projects_dropdown';
import './projects_list'; import './projects_list';
import './syntax_highlight'; import './syntax_highlight';
import './render_math';
import './render_gfm'; import './render_gfm';
import './right_sidebar'; import './right_sidebar';
import './search'; import './search';
...@@ -302,6 +301,8 @@ $(function () { ...@@ -302,6 +301,8 @@ $(function () {
const flashContainer = document.querySelector('.flash-container'); const flashContainer = document.querySelector('.flash-container');
if (flashContainer && flashContainer.children.length) { if (flashContainer && flashContainer.children.length) {
removeFlashClickListener(flashContainer.children[0]); flashContainer.querySelectorAll('.flash-alert, .flash-notice, .flash-success').forEach((flashEl) => {
removeFlashClickListener(flashEl);
});
} }
}); });
import Vue from 'vue'; import axios from '../../lib/utils/axios_utils';
import VueResource from 'vue-resource';
import statusCodes from '../../lib/utils/http_status'; import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils'; import { backOff } from '../../lib/utils/common_utils';
Vue.use(VueResource);
const MAX_REQUESTS = 3; const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) { function backOffRequest(makeRequestCallback) {
...@@ -32,8 +29,8 @@ export default class MonitoringService { ...@@ -32,8 +29,8 @@ export default class MonitoringService {
} }
getGraphsData() { getGraphsData() {
return backOffRequest(() => Vue.http.get(this.metricsEndpoint)) return backOffRequest(() => axios.get(this.metricsEndpoint))
.then(resp => resp.json()) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.data) { if (!response || !response.data) {
throw new Error('Unexpected metrics data response from prometheus endpoint'); throw new Error('Unexpected metrics data response from prometheus endpoint');
...@@ -43,8 +40,8 @@ export default class MonitoringService { ...@@ -43,8 +40,8 @@ export default class MonitoringService {
} }
getDeploymentData() { getDeploymentData() {
return backOffRequest(() => Vue.http.get(this.deploymentEndpoint)) return backOffRequest(() => axios.get(this.deploymentEndpoint))
.then(resp => resp.json()) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.deployments) { if (!response || !response.deployments) {
throw new Error('Unexpected deployment data response from prometheus endpoint'); throw new Error('Unexpected deployment data response from prometheus endpoint');
......
...@@ -3,15 +3,14 @@ ...@@ -3,15 +3,14 @@
import PipelinesService from '../services/pipelines_service'; import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines'; import pipelinesMixin from '../mixins/pipelines';
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import navigationTabs from './navigation_tabs.vue'; import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import navigationControls from './nav_controls.vue'; import navigationControls from './nav_controls.vue';
import { import {
convertPermissionToBoolean, convertPermissionToBoolean,
getParameterByName, getParameterByName,
historyPushState,
buildUrlWithCurrentLocation,
parseQueryStringIntoObject, parseQueryStringIntoObject,
} from '../../lib/utils/common_utils'; } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default { export default {
props: { props: {
...@@ -36,6 +35,7 @@ ...@@ -36,6 +35,7 @@
}, },
mixins: [ mixins: [
pipelinesMixin, pipelinesMixin,
CIPaginationMixin,
], ],
data() { data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
...@@ -170,22 +170,8 @@ ...@@ -170,22 +170,8 @@
* - Update the internal state * - Update the internal state
*/ */
updateContent(parameters) { updateContent(parameters) {
// stop polling this.updateInternalState(parameters);
this.poll.stop();
const queryString = Object.keys(parameters).map((parameter) => {
const value = parameters[parameter];
// update internal state for UI
this[parameter] = value;
return `${parameter}=${encodeURIComponent(value)}`;
}).join('&');
// update polling parameters
this.requestData = parameters;
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
this.isLoading = true;
// fetch new data // fetch new data
return this.service.getPipelines(this.requestData) return this.service.getPipelines(this.requestData)
.then((response) => { .then((response) => {
...@@ -203,14 +189,6 @@ ...@@ -203,14 +189,6 @@
this.poll.restart(); this.poll.restart();
}); });
}, },
onChangeTab(scope) {
this.updateContent({ scope, page: '1' });
},
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
this.updateContent({ scope: this.scope, page: Number(page).toString() });
},
}, },
}; };
</script> </script>
...@@ -235,6 +213,7 @@ ...@@ -235,6 +213,7 @@
<navigation-tabs <navigation-tabs
:tabs="tabs" :tabs="tabs"
@onChangeTab="onChangeTab" @onChangeTab="onChangeTab"
scope="pipelines"
/> />
<navigation-controls <navigation-controls
......
function updateAutoDevopsRadios(radioWrappers) {
radioWrappers.forEach((radioWrapper) => {
const radio = radioWrapper.querySelector('.js-auto-devops-enable-radio');
const runPipelineCheckboxWrapper = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox-wrapper');
const runPipelineCheckbox = radioWrapper.querySelector('.js-run-auto-devops-pipeline-checkbox');
if (runPipelineCheckbox) {
runPipelineCheckbox.checked = radio.checked;
runPipelineCheckboxWrapper.classList.toggle('hide', !radio.checked);
}
});
}
export default function initCiCdSettings() {
const radioWrappers = document.querySelectorAll('.js-auto-devops-enable-radio-wrapper');
radioWrappers.forEach(radioWrapper =>
radioWrapper.addEventListener('change', () => updateAutoDevopsRadios(radioWrappers)),
);
}
...@@ -48,6 +48,27 @@ export default { ...@@ -48,6 +48,27 @@ export default {
} }
return this.projectName; return this.projectName;
}, },
/**
* Smartly truncates project namespace by doing two things;
* 1. Only include Group names in path by removing project name
* 2. Only include first and last group names in the path
* when namespace has more than 2 groups present
*
* First part (removal of project name from namespace) can be
* done from backend but doing so involves migration of
* existing project namespaces which is not wise thing to do.
*/
truncatedNamespace() {
const namespaceArr = this.namespace.split(' / ');
namespaceArr.splice(-1, 1);
let namespace = namespaceArr.join(' / ');
if (namespaceArr.length > 2) {
namespace = `${namespaceArr[0]} / ... / ${namespaceArr.pop()}`;
}
return namespace;
},
}, },
}; };
</script> </script>
...@@ -87,9 +108,7 @@ export default { ...@@ -87,9 +108,7 @@ export default {
<div <div
class="project-namespace" class="project-namespace"
:title="namespace" :title="namespace"
> >{{truncatedNamespace}}</div>
{{namespace}}
</div>
</div> </div>
</a> </a>
</li> </li>
......
/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */ import renderMath from './render_math';
import renderMermaid from './render_mermaid';
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
// Delegates to syntax highlight and render math // Delegates to syntax highlight and render math & mermaid diagrams.
// //
(function() { $.fn.renderGFM = function renderGFM() {
$.fn.renderGFM = function() { this.find('.js-syntax-highlight').syntaxHighlight();
this.find('.js-syntax-highlight').syntaxHighlight(); renderMath(this.find('.js-render-math'));
this.find('.js-render-math').renderMath(); renderMermaid(this.find('.js-render-mermaid'));
return this; return this;
}; };
$(() => $('body').renderGFM()); $(() => $('body').renderGFM());
}).call(window);
/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len, no-console */
/* global katex */ /* global katex */
// Renders math using KaTeX in any element with the // Renders math using KaTeX in any element with the
...@@ -8,49 +7,45 @@ ...@@ -8,49 +7,45 @@
// //
// <code class="js-render-math"></div> // <code class="js-render-math"></div>
// //
(function() {
// Only load once // Only load once
var katexLoaded = false; let katexLoaded = false;
// Loop over all math elements and render math // Loop over all math elements and render math
var renderWithKaTeX = function (elements) { function renderWithKaTeX(elements) {
elements.each(function () { elements.each(function katexElementsLoop() {
var mathNode = $('<span></span>'); const mathNode = $('<span></span>');
var $this = $(this); const $this = $(this);
var display = $this.attr('data-math-style') === 'display'; const display = $this.attr('data-math-style') === 'display';
try { try {
katex.render($this.text(), mathNode.get(0), { displayMode: display }); katex.render($this.text(), mathNode.get(0), { displayMode: display });
mathNode.insertAfter($this); mathNode.insertAfter($this);
$this.remove(); $this.remove();
} catch (err) { } catch (err) {
// What can we do?? throw err;
console.log(err.message); }
} });
}); }
};
$.fn.renderMath = function() { export default function renderMath($els) {
var $this = this; if (!$els.length) return;
if ($this.length === 0) return;
if (katexLoaded) renderWithKaTeX($this); if (katexLoaded) {
else { renderWithKaTeX($els);
// Request CSS file so it is in the cache } else {
$.get(gon.katex_css_url, function() { $.get(gon.katex_css_url, () => {
var css = $('<link>', const css = $('<link>', {
{ rel: 'stylesheet', rel: 'stylesheet',
type: 'text/css', type: 'text/css',
href: gon.katex_css_url, href: gon.katex_css_url,
}); });
css.appendTo('head'); css.appendTo('head');
// Load KaTeX js // Load KaTeX js
$.getScript(gon.katex_js_url, function() { $.getScript(gon.katex_js_url, () => {
katexLoaded = true; katexLoaded = true;
renderWithKaTeX($this); // Run KaTeX renderWithKaTeX($els); // Run KaTeX
});
}); });
} });
}; }
}).call(window); }
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
//
// Example markup:
//
// <pre class="js-render-mermaid">
// graph TD;
// A-- > B;
// A-- > C;
// B-- > D;
// C-- > D;
// </pre>
//
import Flash from './flash';
export default function renderMermaid($els) {
if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
loadOnStart: false,
theme: 'neutral',
});
$els.each((i, el) => {
mermaid.init(undefined, el);
});
}).catch((err) => {
Flash(`Can't load mermaid module: ${err}`);
});
}
<script>
import icon from '../../../vue_shared/components/icon.vue';
import listItem from './list_item.vue';
import listCollapsed from './list_collapsed.vue';
export default {
components: {
icon,
listItem,
listCollapsed,
},
props: {
title: {
type: String,
required: true,
},
fileList: {
type: Array,
required: true,
},
collapsed: {
type: Boolean,
required: true,
},
},
methods: {
toggleCollapsed() {
this.$emit('toggleCollapsed');
},
},
};
</script>
<template>
<div class="multi-file-commit-panel-section">
<header
class="multi-file-commit-panel-header"
:class="{
'is-collapsed': collapsed,
}"
>
<icon
name="list-bulleted"
:size="18"
css-classes="append-right-default"
/>
<template v-if="!collapsed">
{{ title }}
<button
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-right"
>
</i>
</button>
</template>
</header>
<div class="multi-file-commit-list">
<list-collapsed
v-if="collapsed"
/>
<template v-else>
<ul
v-if="fileList.length"
class="list-unstyled append-bottom-0"
>
<li
v-for="file in fileList"
:key="file.key"
>
<list-item
:file="file"
/>
</li>
</ul>
<div
v-else
class="help-block prepend-top-0"
>
No changes
</div>
</template>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
computed: {
...mapGetters([
'addedFiles',
'modifiedFiles',
]),
},
};
</script>
<template>
<div
class="multi-file-commit-list-collapsed text-center"
>
<icon
name="file-addition"
:size="18"
css-classes="multi-file-addition append-bottom-10"
/>
{{ addedFiles.length }}
<icon
name="file-modified"
:size="18"
css-classes="multi-file-modified prepend-top-10 append-bottom-10"
/>
{{ modifiedFiles.length }}
</div>
</template>
<script>
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
file: {
type: Object,
required: true,
},
},
computed: {
iconName() {
return this.file.tempFile ? 'file-addition' : 'file-modified';
},
iconClass() {
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
},
},
};
</script>
<template>
<div class="multi-file-commit-list-item">
<icon
:name="iconName"
:size="16"
:css-classes="iconClass"
/>
<span class="multi-file-commit-list-path">
{{ file.path }}
</span>
</div>
</template>
...@@ -40,20 +40,24 @@ export default { ...@@ -40,20 +40,24 @@ export default {
</script> </script>
<template> <template>
<div class="repository-view"> <div
<div class="tree-content-holder" :class="{'tree-content-holder-mini' : isCollapsed}"> class="multi-file"
<repo-sidebar/> :class="{
<div 'is-collapsed': isCollapsed
v-if="isCollapsed" }"
class="panel-right" >
> <repo-sidebar/>
<repo-tabs/> <div
<component v-if="isCollapsed"
:is="currentBlobView" class="multi-file-edit-pane"
/> >
<repo-file-buttons/> <repo-tabs />
</div> <component
class="multi-file-edit-pane-content"
:is="currentBlobView"
/>
<repo-file-buttons />
</div> </div>
<repo-commit-section v-if="changedFiles.length" /> <repo-commit-section />
</div> </div>
</template> </template>
<script> <script>
import { mapGetters, mapState, mapActions } from 'vuex'; import { mapGetters, mapState, mapActions } from 'vuex';
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import { n__ } from '../../locale'; import commitFilesList from './commit_sidebar/list.vue';
export default { export default {
components: { components: {
PopupDialog, PopupDialog,
icon,
commitFilesList,
},
directives: {
tooltip,
}, },
data() { data() {
return { return {
...@@ -13,6 +20,7 @@ export default { ...@@ -13,6 +20,7 @@ export default {
submitCommitsLoading: false, submitCommitsLoading: false,
startNewMR: false, startNewMR: false,
commitMessage: '', commitMessage: '',
collapsed: true,
}; };
}, },
computed: { computed: {
...@@ -23,10 +31,10 @@ export default { ...@@ -23,10 +31,10 @@ export default {
'changedFiles', 'changedFiles',
]), ]),
commitButtonDisabled() { commitButtonDisabled() {
return !this.commitMessage || this.submitCommitsLoading; return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length;
}, },
commitButtonText() { commitMessageCount() {
return n__('Commit %d file', 'Commit %d files', this.changedFiles.length); return this.commitMessage.length;
}, },
}, },
methods: { methods: {
...@@ -77,12 +85,20 @@ export default { ...@@ -77,12 +85,20 @@ export default {
this.submitCommitsLoading = false; this.submitCommitsLoading = false;
}); });
}, },
toggleCollapsed() {
this.collapsed = !this.collapsed;
},
}, },
}; };
</script> </script>
<template> <template>
<div id="commit-area"> <div
class="multi-file-commit-panel"
:class="{
'is-collapsed': collapsed,
}"
>
<popup-dialog <popup-dialog
v-if="showNewBranchDialog" v-if="showNewBranchDialog"
:primary-button-label="__('Create new branch')" :primary-button-label="__('Create new branch')"
...@@ -92,78 +108,71 @@ export default { ...@@ -92,78 +108,71 @@ export default {
@toggle="showNewBranchDialog = false" @toggle="showNewBranchDialog = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<button
v-if="collapsed"
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn is-collapsed prepend-top-10 append-bottom-10"
@click="toggleCollapsed"
>
<i
aria-hidden="true"
class="fa fa-angle-double-left"
>
</i>
</button>
<commit-files-list
title="Staged"
:file-list="changedFiles"
:collapsed="collapsed"
@toggleCollapsed="toggleCollapsed"
/>
<form <form
class="form-horizontal" class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit()"> @submit.prevent="tryCommit"
<fieldset> v-if="!collapsed"
<div class="form-group"> >
<label class="col-md-4 control-label staged-files"> <div class="multi-file-commit-fieldset">
Staged files ({{changedFiles.length}}) <textarea
</label> class="form-control multi-file-commit-message"
<div class="col-md-6"> name="commit-message"
<ul class="list-unstyled changed-files"> v-model="commitMessage"
<li placeholder="Commit message"
v-for="(file, index) in changedFiles" >
:key="index"> </textarea>
<span class="help-block"> </div>
{{ file.path }} <div class="multi-file-commit-fieldset">
</span> <label
</li> v-tooltip
</ul> title="Create a new merge request with these changes"
</div> data-container="body"
</div> data-placement="top"
<div class="form-group"> >
<label <input
class="col-md-4 control-label" type="checkbox"
for="commit-message"> v-model="startNewMR"
Commit message />
</label> Merge Request
<div class="col-md-6"> </label>
<textarea <button
id="commit-message" type="submit"
class="form-control" :disabled="commitButtonDisabled"
name="commit-message" class="btn btn-default btn-sm append-right-10 prepend-left-10"
v-model="commitMessage"> >
</textarea> <i
</div> v-if="submitCommitsLoading"
</div> class="js-commit-loading-icon fa fa-spinner fa-spin"
<div class="form-group target-branch"> aria-hidden="true"
<label aria-label="loading"
class="col-md-4 control-label" >
for="target-branch"> </i>
Target branch Commit
</label> </button>
<div class="col-md-6"> <div
<span class="help-block"> class="multi-file-commit-message-count"
{{currentBranch}} >
</span> {{ commitMessageCount }}
</div>
</div>
<div class="col-md-offset-4 col-md-6">
<button
type="submit"
:disabled="commitButtonDisabled"
class="btn btn-success">
<i
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading">
</i>
<span class="commit-summary">
{{ commitButtonText }}
</span>
</button>
</div>
<div class="col-md-offset-4 col-md-6">
<div class="checkbox">
<label>
<input type="checkbox" v-model="startNewMR">
<span>Start a <strong>new merge request</strong> with these changes</span>
</label>
</div>
</div> </div>
</fieldset> </div>
</form> </form>
</div> </div>
</template> </template>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
class="file" class="file"
@click.prevent="clickedTreeRow(file)"> @click.prevent="clickedTreeRow(file)">
<td <td
class="multi-file-table-col-name" class="multi-file-table-name"
:colspan="submoduleColSpan" :colspan="submoduleColSpan"
> >
<i <i
...@@ -85,12 +85,11 @@ ...@@ -85,12 +85,11 @@
</td> </td>
<template v-if="!isCollapsed && !isSubmodule"> <template v-if="!isCollapsed && !isSubmodule">
<td class="hidden-sm hidden-xs"> <td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
<a <a
v-if="file.lastCommit.message" v-if="file.lastCommit.message"
@click.stop @click.stop
:href="file.lastCommit.url" :href="file.lastCommit.url"
class="commit-message"
> >
{{ file.lastCommit.message }} {{ file.lastCommit.message }}
</a> </a>
......
...@@ -22,12 +22,12 @@ export default { ...@@ -22,12 +22,12 @@ export default {
<template> <template>
<div <div
v-if="showButtons" v-if="showButtons"
class="repo-file-buttons" class="multi-file-editor-btn-group"
> >
<a <a
:href="activeFile.rawPath" :href="activeFile.rawPath"
target="_blank" target="_blank"
class="btn btn-default raw" class="btn btn-default btn-sm raw"
rel="noopener noreferrer"> rel="noopener noreferrer">
{{ rawDownloadButtonLabel }} {{ rawDownloadButtonLabel }}
</a> </a>
...@@ -38,17 +38,17 @@ export default { ...@@ -38,17 +38,17 @@ export default {
aria-label="File actions"> aria-label="File actions">
<a <a
:href="activeFile.blamePath" :href="activeFile.blamePath"
class="btn btn-default blame"> class="btn btn-default btn-sm blame">
Blame Blame
</a> </a>
<a <a
:href="activeFile.commitsPath" :href="activeFile.commitsPath"
class="btn btn-default history"> class="btn btn-default btn-sm history">
History History
</a> </a>
<a <a
:href="activeFile.permalink" :href="activeFile.permalink"
class="btn btn-default permalink"> class="btn btn-default btn-sm permalink">
Permalink Permalink
</a> </a>
</div> </div>
......
...@@ -32,10 +32,12 @@ export default { ...@@ -32,10 +32,12 @@ export default {
</script> </script>
<template> <template>
<div class="blob-viewer-container"> <div>
<div <div
v-if="!activeFile.renderError" v-if="!activeFile.renderError"
v-html="activeFile.html"> v-html="activeFile.html"
class="multi-file-preview-holder"
>
</div> </div>
<div <div
v-else-if="activeFile.tempFile" v-else-if="activeFile.tempFile"
......
...@@ -44,20 +44,16 @@ export default { ...@@ -44,20 +44,16 @@ export default {
</script> </script>
<template> <template>
<div id="sidebar" :class="{'sidebar-mini' : isCollapsed}"> <div class="ide-file-list">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th <th
v-if="isCollapsed" v-if="isCollapsed"
class="repo-file-options title"
> >
<strong class="clgray">
{{ projectName }}
</strong>
</th> </th>
<template v-else> <template v-else>
<th class="name multi-file-table-col-name"> <th class="name multi-file-table-name">
Name Name
</th> </th>
<th class="hidden-sm hidden-xs last-commit"> <th class="hidden-sm hidden-xs last-commit">
...@@ -79,7 +75,7 @@ export default { ...@@ -79,7 +75,7 @@ export default {
:key="n" :key="n"
/> />
<repo-file <repo-file
v-for="(file, index) in treeList" v-for="file in treeList"
:key="file.key" :key="file.key"
:file="file" :file="file"
/> />
......
...@@ -36,27 +36,32 @@ export default { ...@@ -36,27 +36,32 @@ export default {
<template> <template>
<li <li
:class="{ active : tab.active }"
@click="setFileActive(tab)" @click="setFileActive(tab)"
> >
<button <button
type="button" type="button"
class="close-btn" class="multi-file-tab-close"
@click.stop.prevent="closeFile({ file: tab })" @click.stop.prevent="closeFile({ file: tab })"
:aria-label="closeLabel"> :aria-label="closeLabel"
:class="{
'modified': tab.changed,
}"
:disabled="tab.changed"
>
<i <i
class="fa" class="fa"
:class="changedClass" :class="changedClass"
aria-hidden="true"> aria-hidden="true"
>
</i> </i>
</button> </button>
<a <div
href="#" class="multi-file-tab"
class="repo-tab" :class="{active : tab.active }"
:title="tab.url" :title="tab.url"
@click.prevent.stop="setFileActive(tab)"> >
{{tab.name}} {{ tab.name }}
</a> </div>
</li> </li>
</template> </template>
...@@ -16,14 +16,12 @@ ...@@ -16,14 +16,12 @@
<template> <template>
<ul <ul
id="tabs" class="multi-file-tabs list-unstyled append-bottom-0"
class="list-unstyled"
> >
<repo-tab <repo-tab
v-for="tab in openFiles" v-for="tab in openFiles"
:key="tab.id" :key="tab.id"
:tab="tab" :tab="tab"
/> />
<li class="tabs-divider" />
</ul> </ul>
</template> </template>
...@@ -34,3 +34,7 @@ export const canEditFile = (state) => { ...@@ -34,3 +34,7 @@ export const canEditFile = (state) => {
openedFiles.length && openedFiles.length &&
(currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary); (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
}; };
export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
import Flash from './flash'; import Flash from './flash';
import { __, s__ } from './locale'; import { __, s__ } from './locale';
import { spriteIcon } from './lib/utils/common_utils';
export default class Star { export default class Star {
constructor() { constructor() {
...@@ -7,16 +8,18 @@ export default class Star { ...@@ -7,16 +8,18 @@ export default class Star {
.on('ajax:success', function handleSuccess(e, data) { .on('ajax:success', function handleSuccess(e, data) {
const $this = $(this); const $this = $(this);
const $starSpan = $this.find('span'); const $starSpan = $this.find('span');
const $starIcon = $this.find('i'); const $startIcon = $this.find('svg');
function toggleStar(isStarred) { function toggleStar(isStarred) {
$this.parent().find('.star-count').text(data.star_count); $this.parent().find('.star-count').text(data.star_count);
if (isStarred) { if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star')); $starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o'); $startIcon.remove();
$this.prepend(spriteIcon('star-o'));
} else { } else {
$starSpan.addClass('starred').text(__('Unstar')); $starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star'); $startIcon.remove();
$this.prepend(spriteIcon('star'));
} }
} }
......
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
statusObj() { statusObj() {
return { return {
group: this.status, group: this.status,
icon: `icon_status_${this.status}`, icon: `status_${this.status}`,
}; };
}, },
}, },
......
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
/> />
*/ */
// only allow classes in images.scss e.g. s12
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
export default { export default {
props: { props: {
name: { name: {
...@@ -22,7 +25,10 @@ ...@@ -22,7 +25,10 @@
size: { size: {
type: Number, type: Number,
required: false, required: false,
default: 0, default: 16,
validator(value) {
return validSizes.includes(value);
},
}, },
cssClasses: { cssClasses: {
...@@ -42,10 +48,11 @@ ...@@ -42,10 +48,11 @@
}, },
}; };
</script> </script>
<template> <template>
<svg <svg
:class="[iconSizeClass, cssClasses]"> :class="[iconSizeClass, cssClasses]">
<use <use
v-bind="{'xlink:href':spriteHref}"/> v-bind="{'xlink:href':spriteHref}"/>
</svg> </svg>
</template> </template>
...@@ -30,6 +30,11 @@ ...@@ -30,6 +30,11 @@
required: false, required: false,
default: true, default: true,
}, },
enableAutocomplete: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
return { return {
...@@ -97,7 +102,7 @@ ...@@ -97,7 +102,7 @@
/* /*
GLForm class handles all the toolbar buttons GLForm class handles all the toolbar buttons
*/ */
return new GLForm($(this.$refs['gl-form']), true); return new GLForm($(this.$refs['gl-form']), this.enableAutocomplete);
}, },
beforeDestroy() { beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('gl-form'); const glForm = $(this.$refs['gl-form']).data('gl-form');
......
<script> <script>
/**
* Given an array of tabs, renders non linked bootstrap tabs.
* When a tab is clicked it will trigger an event and provide the clicked scope.
*
* This component is used in apps that handle the API call.
* If you only need to change the URL this component should not be used.
*
* @example
* <navigation-tabs
* :tabs="[
* {
* name: String,
* scope: String,
* count: Number || Undefined,
* isActive: Boolean,
* },
* ]"
* @onChangeTab="onChangeTab"
* />
*/
export default { export default {
name: 'PipelineNavigationTabs', name: 'NavigationTabs',
props: { props: {
tabs: { tabs: {
type: Array, type: Array,
required: true, required: true,
}, },
scope: {
type: String,
required: false,
default: '',
},
}, },
mounted() { mounted() {
$(document).trigger('init.scrolling-tabs'); $(document).trigger('init.scrolling-tabs');
...@@ -34,7 +59,7 @@ ...@@ -34,7 +59,7 @@
<a <a
role="button" role="button"
@click="onTabClick(tab)" @click="onTabClick(tab)"
:class="`js-pipelines-tab-${tab.scope}`" :class="`js-${scope}-tab-${tab.scope}`"
> >
{{ tab.name }} {{ tab.name }}
......
<script>
import Pikaday from 'pikaday';
import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
export default {
name: 'datePicker',
props: {
label: {
type: String,
required: false,
default: 'Date picker',
},
selectedDate: {
type: Date,
required: false,
},
minDate: {
type: Date,
required: false,
},
maxDate: {
type: Date,
required: false,
},
},
methods: {
selected(dateText) {
this.$emit('newDateSelected', this.calendar.toString(dateText));
},
toggled() {
this.$emit('hidePicker');
},
},
mounted() {
this.calendar = new Pikaday({
field: this.$el.querySelector('.dropdown-menu-toggle'),
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: this.$el,
defaultDate: this.selectedDate,
setDefaultDate: !!this.selectedDate,
minDate: this.minDate,
maxDate: this.maxDate,
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
});
this.$el.append(this.calendar.el);
this.calendar.show();
},
beforeDestroy() {
this.calendar.destroy();
},
};
</script>
<template>
<div class="pikaday-container">
<div class="dropdown open">
<button
type="button"
class="dropdown-menu-toggle"
data-toggle="dropdown"
@click="toggled"
>
<span class="dropdown-toggle-text">
{{label}}
</span>
<i
class="fa fa-chevron-down"
aria-hidden="true"
>
</i>
</button>
</div>
</div>
</template>
<script>
export default {
name: 'collapsedCalendarIcon',
props: {
containerClass: {
type: String,
required: false,
default: '',
},
text: {
type: String,
required: false,
default: '',
},
showIcon: {
type: Boolean,
required: false,
default: true,
},
},
methods: {
click() {
this.$emit('click');
},
},
};
</script>
<template>
<div
:class="containerClass"
@click="click"
>
<i
v-if="showIcon"
class="fa fa-calendar"
aria-hidden="true"
>
</i>
<slot>
<span>
{{ text }}
</span>
</slot>
</div>
</template>
<script>
import { dateInWords } from '../../../lib/utils/datetime_utility';
import toggleSidebar from './toggle_sidebar.vue';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
export default {
name: 'sidebarCollapsedGroupedDatePicker',
props: {
collapsed: {
type: Boolean,
required: false,
default: true,
},
showToggleSidebar: {
type: Boolean,
required: false,
default: false,
},
minDate: {
type: Date,
required: false,
},
maxDate: {
type: Date,
required: false,
},
disableClickableIcons: {
type: Boolean,
required: false,
default: false,
},
},
components: {
toggleSidebar,
collapsedCalendarIcon,
},
computed: {
hasMinAndMaxDates() {
return this.minDate && this.maxDate;
},
hasNoMinAndMaxDates() {
return !this.minDate && !this.maxDate;
},
showMinDateBlock() {
return this.minDate || this.hasNoMinAndMaxDates;
},
showFromText() {
return !this.maxDate && this.minDate;
},
iconClass() {
const disabledClass = this.disableClickableIcons ? 'disabled' : '';
return `block sidebar-collapsed-icon calendar-icon ${disabledClass}`;
},
},
methods: {
toggleSidebar() {
this.$emit('toggleCollapse');
},
dateText(dateType = 'min') {
const date = this[`${dateType}Date`];
const dateWords = dateInWords(date, true);
const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords;
return date ? parsedDateWords : 'None';
},
},
};
</script>
<template>
<div class="block sidebar-grouped-item">
<div
v-if="showToggleSidebar"
class="issuable-sidebar-header"
>
<toggle-sidebar
:collapsed="collapsed"
@toggle="toggleSidebar"
/>
</div>
<collapsed-calendar-icon
v-if="showMinDateBlock"
:container-class="iconClass"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
<span v-if="showFromText">From</span>
<span>{{ dateText('min') }}</span>
</span>
</collapsed-calendar-icon>
<div
v-if="hasMinAndMaxDates"
class="text-center sidebar-collapsed-divider"
>
-
</div>
<collapsed-calendar-icon
v-if="maxDate"
:container-class="iconClass"
:show-icon="!minDate"
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
<span v-if="!minDate">Until</span>
<span>{{ dateText('max') }}</span>
</span>
</collapsed-calendar-icon>
</div>
</template>
<script>
import datePicker from '../pikaday.vue';
import loadingIcon from '../loading_icon.vue';
import toggleSidebar from './toggle_sidebar.vue';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
import { dateInWords } from '../../../lib/utils/datetime_utility';
export default {
name: 'sidebarDatePicker',
props: {
collapsed: {
type: Boolean,
required: false,
default: true,
},
showToggleSidebar: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
editable: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
default: 'Date picker',
},
selectedDate: {
type: Date,
required: false,
},
minDate: {
type: Date,
required: false,
},
maxDate: {
type: Date,
required: false,
},
},
data() {
return {
editing: false,
};
},
components: {
datePicker,
toggleSidebar,
loadingIcon,
collapsedCalendarIcon,
},
computed: {
selectedAndEditable() {
return this.selectedDate && this.editable;
},
selectedDateWords() {
return dateInWords(this.selectedDate, true);
},
collapsedText() {
return this.selectedDateWords ? this.selectedDateWords : 'None';
},
},
methods: {
stopEditing() {
this.editing = false;
},
toggleDatePicker() {
this.editing = !this.editing;
},
newDateSelected(date = null) {
this.date = date;
this.editing = false;
this.$emit('saveDate', date);
},
toggleSidebar() {
this.$emit('toggleCollapse');
},
},
};
</script>
<template>
<div class="block">
<div class="issuable-sidebar-header">
<toggle-sidebar
:collapsed="collapsed"
@toggle="toggleSidebar"
/>
</div>
<collapsed-calendar-icon
class="sidebar-collapsed-icon"
:text="collapsedText"
/>
<div class="title">
{{ label }}
<loading-icon
v-if="isLoading"
:inline="true"
/>
<div class="pull-right">
<button
v-if="editable && !editing"
type="button"
class="btn-blank btn-link btn-primary-hover-link btn-sidebar-action"
@click="toggleDatePicker"
>
Edit
</button>
<toggle-sidebar
v-if="showToggleSidebar"
:collapsed="collapsed"
@toggle="toggleSidebar"
/>
</div>
</div>
<div class="value">
<date-picker
v-if="editing"
:selected-date="selectedDate"
:min-date="minDate"
:max-date="maxDate"
:label="label"
@newDateSelected="newDateSelected"
@hidePicker="stopEditing"
/>
<span
v-else
class="value-content"
>
<template v-if="selectedDate">
<strong>{{ selectedDateWords }}</strong>
<span
v-if="selectedAndEditable"
class="no-value"
>
-
<button
type="button"
class="btn-blank btn-link btn-secondary-hover-link"
@click="newDateSelected(null)"
>
remove
</button>
</span>
</template>
<span
v-else
class="no-value"
>
None
</span>
</span>
</div>
</div>
</template>
<script>
export default {
name: 'toggleSidebar',
props: {
collapsed: {
type: Boolean,
required: true,
},
},
methods: {
toggle() {
this.$emit('toggle');
},
},
};
</script>
<template>
<button
type="button"
class="btn btn-blank gutter-toggle btn-sidebar-action"
@click="toggle"
>
<i
aria-label="toggle collapse"
class="fa"
:class="{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }"
></i>
</button>
</template>
/**
* API callbacks for pagination and tabs
* shared between Pipelines and Environments table.
*
* Components need to have `scope`, `page` and `requestData`
*/
import {
historyPushState,
buildUrlWithCurrentLocation,
} from '../../lib/utils/common_utils';
export default {
methods: {
onChangeTab(scope) {
this.updateContent({ scope, page: '1' });
},
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
this.updateContent({ scope: this.scope, page: Number(page).toString() });
},
updateInternalState(parameters) {
// stop polling
this.poll.stop();
const queryString = Object.keys(parameters).map((parameter) => {
const value = parameters[parameter];
// update internal state for UI
this[parameter] = value;
return `${parameter}=${encodeURIComponent(value)}`;
}).join('&');
// update polling parameters
this.requestData = parameters;
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
this.isLoading = true;
},
},
};
...@@ -71,7 +71,7 @@ export default class ZenMode { ...@@ -71,7 +71,7 @@ export default class ZenMode {
this.active_textarea = this.active_backdrop.find('textarea'); this.active_textarea = this.active_backdrop.find('textarea');
// Prevent a user-resized textarea from persisting to fullscreen // Prevent a user-resized textarea from persisting to fullscreen
this.active_textarea.removeAttr('style'); this.active_textarea.removeAttr('style');
return this.active_textarea.focus(); this.active_textarea.focus();
} }
exit() { exit() {
...@@ -81,7 +81,11 @@ export default class ZenMode { ...@@ -81,7 +81,11 @@ export default class ZenMode {
this.scrollTo(this.active_textarea); this.scrollTo(this.active_textarea);
this.active_textarea = null; this.active_textarea = null;
this.active_backdrop = null; this.active_backdrop = null;
return Dropzone.forElement('.div-dropzone').enable();
const $dropzone = $('.div-dropzone');
if ($dropzone && !$dropzone.hasClass('js-invalid-dropzone')) {
Dropzone.forElement('.div-dropzone').enable();
}
} }
} }
......
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
@include transition(border-color); @include transition(border-color);
} }
.note-action-button .link-highlight, .note-action-button,
.toolbar-btn, .toolbar-btn,
.dropdown-toggle-caret { .dropdown-toggle-caret {
@include transition(color); @include transition(color);
......
...@@ -88,17 +88,6 @@ ...@@ -88,17 +88,6 @@
border-color: $border-dark; border-color: $border-dark;
color: $color; color: $color;
} }
svg {
path {
fill: $color;
}
use {
stroke: $color;
}
}
} }
@mixin btn-green { @mixin btn-green {
...@@ -142,6 +131,13 @@ ...@@ -142,6 +131,13 @@
} }
} }
@mixin btn-svg {
height: $gl-padding;
width: $gl-padding;
top: 0;
vertical-align: text-top;
}
.btn { .btn {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
...@@ -408,6 +404,7 @@ ...@@ -408,6 +404,7 @@
padding: 0; padding: 0;
background: transparent; background: transparent;
border: 0; border: 0;
border-radius: 0;
&:hover, &:hover,
&:active, &:active,
...@@ -417,3 +414,29 @@ ...@@ -417,3 +414,29 @@
box-shadow: none; box-shadow: none;
} }
} }
.btn-link.btn-secondary-hover-link {
color: $gl-text-color-secondary;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
.btn-link.btn-primary-hover-link {
color: inherit;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
.btn-svg svg {
@include btn-svg;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment