Basic OVE editor implementation [SCI-8929]

This commit is contained in:
Martin Artnik 2023-08-02 14:13:04 +02:00
parent a6c2a3aa59
commit 76a0f433cd
26 changed files with 234561 additions and 8 deletions

View file

@ -0,0 +1,2 @@
//= require i18n.js
//= require i18n/translations

View file

@ -0,0 +1,21 @@
/* global iFrameModal */
// General-purpose iframe modal. For closing the modal, you need will to take care of triggering
// the 'hide' event on the modal itself, example from inside the iframe:
// parent.document.getElementById('iFrameModal').dispatchEvent(new Event('hide'));
$(document).on('turbolinks:load', function() {
window.iFrameModal = document.getElementById('iFrameModal');
let iFrameModalFrame = document.getElementById('iFrameModalFrame');
window.showIFrameModal = (url) => {
iFrameModalFrame.setAttribute('src', url);
iFrameModal.classList.remove('hidden');
iFrameModal.dispatchEvent(new Event('shown'));
};
iFrameModal.addEventListener('hide', () => {
iFrameModal.classList.add('hidden');
iFrameModalFrame.removeAttribute('src');
iFrameModal.dispatchEvent(new Event('hidden'));
});
});

View file

@ -0,0 +1,4 @@
/*
*= require sn-icon-font
*= require sn-inter-font
*/

View file

@ -0,0 +1,123 @@
# frozen_string_literal: true
class GeneSequenceAssetsController < ApplicationController
include ActiveStorage::SetCurrent
skip_before_action :verify_authenticity_token
before_action :load_vars, except: %i(new create)
before_action :load_create_vars, only: %i(new create)
before_action :check_read_permission
before_action :check_manage_permission, only: %i(new update create)
def new
render :edit, layout: false
end
def edit
@file_url = rails_representation_url(@asset.file)
@file_name = @asset.render_file_name
render :edit, layout: false
end
def create
save_asset!
head :ok
end
def update
save_asset!
head :ok
end
private
def save_asset!
ActiveRecord::Base.transaction do
ensure_asset!
@asset.file.purge
@asset.preview_image.purge
@asset.file.attach(
io: StringIO.new(params[:sequence_data].to_json),
filename: "#{params[:sequence_name]}.json"
)
@asset.preview_image.attach(
io: StringIO.new(Base64.decode64(params[:base64_image].split(',').last)),
filename: "#{params[:sequence_name]}.png"
)
file = @asset.file
file.blob.metadata['asset_type'] = 'gene_sequence'
file.blob.metadata['name'] = params[:sequence_name]
file.save!
@asset.save!
end
end
def ensure_asset!
return if @asset
return unless @parent
@asset = @parent.assets.create!(last_modified_by: current_user, team: current_team)
end
def load_vars
@asset = current_team.assets.find_by(id: params[:id])
return render_404 unless @asset
@parent ||= @asset.step
@parent ||= @asset.result
case @parent
when Step
@protocol = @parent.protocol
when Result
@my_module = @parent.my_module
end
end
def load_create_vars
@parent = case params[:parent_type]
when 'Step'
Step.find_by(id: params[:parent_id])
when 'Result'
Result.find_by(id: params[:parent_id])
end
case @parent
when Step
@protocol = @parent.protocol
when Result
@result = @parent
end
end
def check_read_permission
case @parent
when Step
return render_403 unless can_read_protocol_in_module?(@protocol) ||
can_read_protocol_in_repository?(@protocol)
when Result
return render_403 unless can_read_my_module?(@my_module)
else
render_403
end
end
def check_manage_permission
case @parent
when Step
return render_403 unless can_manage_step?(@parent)
when Result
return render_403 unless can_manage_my_module?(@parent)
else
render_403
end
end
end

View file

@ -0,0 +1,2 @@
import './ove/script.js';
import './ove/style.css';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import OpenVectorEditor from '../../vue/ove/OpenVectorEditor.vue';
Vue.use(TurbolinksAdapter);
Vue.prototype.i18n = window.I18n;
new Vue({
el: '#open-vector-editor',
components: { OpenVectorEditor }
});

View file

@ -0,0 +1,106 @@
<template>
<div class="ove-wrapper flex flex-col h-screen">
<div class="ove-header flex justify-between">
<span class="file-name flex items-center ml-3">
<div class="sci-input-container">
<input v-model="sequenceName" class="sci-input-field" type="text" />
</div>
</span>
<div class="ove-buttons">
<button @click="saveAndClose" class="btn btn-light">
<i class="sn-icon sn-icon-save"></i>
{{ i18n.t('SaveClose') }}
</button>
<button @click="closeModal" class="preview-close btn btn-light icon-btn">
<i class="sn-icon sn-icon-close"></i>
</button>
</div>
</div>
<div ref="container" class="ove-container w-full h-full">
</div>
</div>
</template>
<script>
import axios from 'axios';
import { blobToBase64 } from '../shared/blobToBase64.js'
export default {
name: 'OpenVectorEditor',
props: {
fileUrl: { type: String },
fileName: { type: String },
updateUrl: { type: String }
},
data() {
return {
editor: null,
sequenceName: null,
closeAfterSave: false
}
},
watch: {
sequenceName() {
if (this.editor && this.editor.getState()) {
this.editor.updateEditor({
sequenceData: { ...this.editor.getState().sequenceData, name: this.sequenceName }
});
}
}
},
mounted() {
this.editor = window.createVectorEditor(this.$refs.container, {
onSave: this.saveFile,
generatePng: true
});
this.sequenceName = this.fileName || this.i18n.t('open_vector_editor.default_sequence_name');
if (this.fileUrl) {
this.loadFile();
} else {
this.editor.updateEditor(
{
sequenceData: { circular: true, name: this.sequenceName },
readOnly: false
}
);
}
},
methods: {
loadFile() {
fetch(this.fileUrl).then((response) => response.json()).then(
(json) => this.editor.updateEditor(
{
sequenceData: json,
readOnly: false
}
)
);
},
saveAndClose() {
this.closeAfterSave = close;
document.querySelector('[data-test=saveTool]').click();
},
saveFile(opts, sequenceDataToSave, editorState, onSuccessCallback) {
blobToBase64(opts.pngFile).then((base64image) => {
(this.fileUrl ? axios.patch : axios.post)(
this.updateUrl,
{
sequence_data: sequenceDataToSave,
sequence_name: this.sequenceName,
base64_image: base64image
}
).then(() => {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('sequenceSaved'));
if (this.closeAfterSave) this.closeModal();
});
});
},
closeModal() {
if (parent !== window) {
parent.document.getElementById('iFrameModal').dispatchEvent(new Event('hide'));
}
}
}
}
</script>

View file

@ -141,6 +141,7 @@
} }
}, },
mounted() { mounted() {
this.initOVE();
this.initMarvinJS(); this.initMarvinJS();
$(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', this.handleDropdownPosition); $(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', this.handleDropdownPosition);
}, },
@ -165,6 +166,9 @@
deleteAttachment(id) { deleteAttachment(id) {
this.$emit('attachment:deleted', id) this.$emit('attachment:deleted', id)
}, },
initOVE() {
$(window.iFrameModal).on('sequenceSaved', () => this.$emit('attachment:uploaded'));
},
initMarvinJS() { initMarvinJS() {
// legacy logic from app/assets/javascripts/sitewide/marvinjs_editor.js // legacy logic from app/assets/javascripts/sitewide/marvinjs_editor.js
MarvinJsEditor.initNewButton( MarvinJsEditor.initNewButton(

View file

@ -20,6 +20,11 @@
{{ attachment.attributes.wopi_context.button_text }} {{ attachment.attributes.wopi_context.button_text }}
</a> </a>
</li> </li>
<li v-if="attachment.attributes.asset_type == 'gene_sequence'">
<a class="ove-edit-button" @click="openOVEditor(attachment.attributes.edit_url)">
{{ i18n.t('open_vector_editor.edit_sequence') }}
</a>
</li>
<li v-if="attachment.attributes.asset_type == 'marvinjs' && attachment.attributes.urls.marvin_js_start_edit"> <li v-if="attachment.attributes.asset_type == 'marvinjs' && attachment.attributes.urls.marvin_js_start_edit">
<a class="marvinjs-edit-button" <a class="marvinjs-edit-button"
:data-sketch-id="attachment.id" :data-sketch-id="attachment.id"
@ -129,6 +134,9 @@
deleteAttachment() { deleteAttachment() {
this.deleteModal = false this.deleteModal = false
this.$emit('attachment:delete') this.$emit('attachment:delete')
},
openOVEditor(url) {
window.showIFrameModal(url);
} }
} }
} }

View file

@ -61,6 +61,11 @@
</a> </a>
</div> </div>
</div> </div>
<div class="integration-block open-vector-editor-button">
<a @click="openOVEditor">
{{ i18n.t('open_vector_editor.new_sequence') }}
</a>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type='button' class='btn btn-secondary' @click="cancel"> <button type='button' class='btn btn-secondary' @click="cancel">
@ -148,6 +153,10 @@
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger'); HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
} }
}); });
},
openOVEditor() {
$(this.$refs.modal).modal('hide');
window.showIFrameModal(this.step.attributes.new_sequence_url);
} }
} }
} }

View file

@ -11,7 +11,7 @@
:data-preview-url="attachment.attributes.urls.preview" :data-preview-url="attachment.attributes.urls.preview"
> >
<div class="attachment-preview" :class= "attachment.attributes.asset_type"> <div class="attachment-preview" :class= "attachment.attributes.asset_type">
<img v-if="attachment.attributes.medium_preview !== null" <img v-if="attachment.attributes.medium_preview"
:src="attachment.attributes.medium_preview" :src="attachment.attributes.medium_preview"
@error="ActiveStoragePreviews.reCheckPreview" @error="ActiveStoragePreviews.reCheckPreview"
@load="ActiveStoragePreviews.showPreview" @load="ActiveStoragePreviews.showPreview"

View file

@ -0,0 +1,9 @@
export function blobToBase64(blob) {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise(resolve => {
reader.onloadend = () => {
resolve(reader.result);
};
});
}

View file

@ -17,6 +17,7 @@ class Asset < ApplicationRecord
# ActiveStorage configuration # ActiveStorage configuration
has_one_attached :file has_one_attached :file
has_one_attached :file_pdf_preview has_one_attached :file_pdf_preview
has_one_attached :preview_image
# Asset validation # Asset validation
# This could cause some problems if you create empty asset and want to # This could cause some problems if you create empty asset and want to
@ -165,17 +166,21 @@ class Asset < ApplicationRecord
end end
def previewable? def previewable?
return false unless file.attached? return false unless preview_image.attached? || file.attached?
previewable_document?(blob) || previewable_image? previewable_document?(blob) || previewable_image?
end end
def preview_attachment
preview_image.attached? ? preview_image : file
end
def medium_preview def medium_preview
file.representation(resize_to_limit: Constants::MEDIUM_PIC_FORMAT) preview_attachment.representation(resize_to_limit: Constants::MEDIUM_PIC_FORMAT)
end end
def large_preview def large_preview
file.representation(resize_to_limit: Constants::LARGE_PIC_FORMAT) preview_attachment.representation(resize_to_limit: Constants::LARGE_PIC_FORMAT)
end end
def file_name def file_name
@ -449,6 +454,7 @@ class Asset < ApplicationRecord
end end
def previewable_image? def previewable_image?
preview_image.attached? ||
file.blob&.content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}} file.blob&.content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
end end

View file

@ -11,7 +11,9 @@ class AssetSerializer < ActiveModel::Serializer
attributes :file_name, :view_mode, :icon, :urls, :updated_at_formatted, attributes :file_name, :view_mode, :icon, :urls, :updated_at_formatted,
:file_size, :medium_preview, :large_preview, :asset_type, :wopi, :file_size, :medium_preview, :large_preview, :asset_type, :wopi,
:wopi_context, :pdf_previewable, :file_size_formatted, :asset_order, :wopi_context, :pdf_previewable, :file_size_formatted, :asset_order,
:updated_at, :metadata, :image_editable, :image_context, :pdf, :attached :updated_at, :metadata, :image_editable, :image_context, :pdf, :attached,
:edit_url, :preview_image
def icon def icon
file_fa_icon_class(object) file_fa_icon_class(object)
@ -37,6 +39,10 @@ class AssetSerializer < ActiveModel::Serializer
number_to_human_size(object.file_size) number_to_human_size(object.file_size)
end end
def preview_image
rails_representation_url(object.preview_image) if object.preview_image.attached?
end
def medium_preview def medium_preview
rails_representation_url(object.medium_preview) if object.previewable? rails_representation_url(object.medium_preview) if object.previewable?
end end
@ -49,6 +55,10 @@ class AssetSerializer < ActiveModel::Serializer
object.file.metadata&.dig(:asset_type) object.file.metadata&.dig(:asset_type)
end end
def edit_url
edit_gene_sequence_asset_path(object.id) if object.file.metadata&.dig(:asset_type) == 'gene_sequence'
end
def metadata def metadata
object.file.metadata object.file.metadata
end end

View file

@ -9,7 +9,8 @@ class StepSerializer < ActiveModel::Serializer
attributes :name, :position, :completed, :attachments_manageble, :urls, :assets_view_mode, attributes :name, :position, :completed, :attachments_manageble, :urls, :assets_view_mode,
:marvinjs_enabled, :marvinjs_context, :created_by, :created_at, :assets_order, :marvinjs_enabled, :marvinjs_context, :created_by, :created_at, :assets_order,
:wopi_enabled, :wopi_context, :comments_count, :unseen_comments, :storage_limit :wopi_enabled, :wopi_context, :comments_count, :unseen_comments, :storage_limit,
:new_sequence_url
def marvinjs_enabled def marvinjs_enabled
MarvinJsService.enabled? MarvinJsService.enabled?
@ -24,6 +25,10 @@ class StepSerializer < ActiveModel::Serializer
end end
end end
def new_sequence_url
new_gene_sequence_asset_url(parent_type: 'Step', parent_id: object.id)
end
def comments_count def comments_count
object.comments.count object.comments.count
end end

View file

@ -0,0 +1,26 @@
<html>
<head>
<%= javascript_include_tag 'i18n_bundle' %>
<%= stylesheet_link_tag 'sn_icon_font' %>
<%= stylesheet_link_tag 'tailwind' %>
<%= stylesheet_link_tag 'open_vector_editor' %>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<div id="open-vector-editor" data-behaviour="vue">
<open-vector-editor
file-url="<%= @file_url %>"
file-name="<%= @file_name %>"
parent-id="<%= @parent.id %>"
parent-type="<%= @parent.class.name %>"
update-url="<%= @asset ? gene_sequence_asset_url(@asset) : gene_sequence_assets_url(parent_type: params[:parent_type], parent_id: params[:parent_id]) %>"
/>
</div>
<%= javascript_include_tag 'open_vector_editor' %>
<%= javascript_include_tag 'vue_components_open_vector_editor' %>
</body>
</html>

View file

@ -106,6 +106,7 @@
<%= render "shared/file_preview/modal" %> <%= render "shared/file_preview/modal" %>
<%= render "shared/file_edit_modal" %> <%= render "shared/file_edit_modal" %>
<%= render "shared/marvinjs_modal" %> <%= render "shared/marvinjs_modal" %>
<%= render "shared/iframe_modal" %>
<%= render "users/sessions/session_expire_modal" %> <%= render "users/sessions/session_expire_modal" %>
<%= render "users/sessions/session_end_modal" %> <%= render "users/sessions/session_end_modal" %>
<%= render "label_printers/label_printer_modal" %> <%= render "label_printers/label_printer_modal" %>

View file

@ -0,0 +1,3 @@
<div id="iFrameModal" class="fixed top-0 left-0 right-0 z-[2000] w-full h-full bg-sn-white hidden">
<iframe id="iFrameModalFrame" class="w-full h-full border-0"></iframe>
</div>

View file

@ -138,3 +138,9 @@ Rails.application.config.assets.precompile += %w(big.min.js)
# JQuery related includes # JQuery related includes
Rails.application.config.assets.precompile += %w(jquery_bundle.js) Rails.application.config.assets.precompile += %w(jquery_bundle.js)
# Separate icon font file
Rails.application.config.assets.precompile += %w(sn_icon_font.css)
# Separate translations file
Rails.application.config.assets.precompile += %w(i18n_bundle.js)

View file

@ -3636,6 +3636,11 @@ en:
checmical_drawing: "Chemical drawings" checmical_drawing: "Chemical drawings"
no_sketches_found: "No Sketches Found" no_sketches_found: "No Sketches Found"
open_vector_editor:
new_sequence: "New sequence"
edit_sequence: "Edit sequence"
default_sequence_name: "New sequence"
pdf_preview: pdf_preview:
fit_to_screen: 'Fit to screen' fit_to_screen: 'Fit to screen'
pages: pages:

View file

@ -975,4 +975,6 @@ Rails.application.routes.draw do
get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint' get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint'
post 'wopi/files/:id', to: 'wopi#post_file_endpoint' post 'wopi/files/:id', to: 'wopi#post_file_endpoint'
end end
resources :gene_sequence_assets, only: %i(new create edit update)
end end

View file

@ -14,6 +14,7 @@ const entryList = {
emoji_button: './app/javascript/packs/emoji_button.js', emoji_button: './app/javascript/packs/emoji_button.js',
fontawesome: './app/javascript/packs/fontawesome.scss', fontawesome: './app/javascript/packs/fontawesome.scss',
prism: './app/javascript/packs/prism.js', prism: './app/javascript/packs/prism.js',
open_vector_editor: './app/javascript/packs/open_vector_editor.js',
tiny_mce: './app/javascript/packs/tiny_mce.js', tiny_mce: './app/javascript/packs/tiny_mce.js',
tiny_mce_styles: './app/javascript/packs/tiny_mce_styles.scss', tiny_mce_styles: './app/javascript/packs/tiny_mce_styles.scss',
tui_image_editor: './app/javascript/packs/tui_image_editor.js', tui_image_editor: './app/javascript/packs/tui_image_editor.js',
@ -33,7 +34,8 @@ const entryList = {
vue_repository_assign_items_to_task_modal: './app/javascript/packs/vue/assign_items_to_task_modal.js', vue_repository_assign_items_to_task_modal: './app/javascript/packs/vue/assign_items_to_task_modal.js',
vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js', vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js',
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js', vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js',
vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js' vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js',
vue_components_open_vector_editor: './app/javascript/packs/vue/open_vector_editor.js'
} }
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949 // Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949

View file

@ -48,6 +48,7 @@
"@joeattardi/emoji-button": "^4.6.2", "@joeattardi/emoji-button": "^4.6.2",
"ajv": "6.12.6", "ajv": "6.12.6",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.4.0",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"bootstrap-sass": "^3.3.7", "bootstrap-sass": "^3.3.7",

View file

@ -1900,6 +1900,15 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
axios@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^0.1.0: axobject-query@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
@ -3601,6 +3610,11 @@ focus-trap@^5.1.0:
tabbable "^4.0.0" tabbable "^4.0.0"
xtend "^4.0.1" xtend "^4.0.1"
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
for-each@^0.3.3: for-each@^0.3.3:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@ -3617,6 +3631,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8" combined-stream "^1.0.8"
mime-types "^2.1.12" mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fraction.js@^4.2.0: fraction.js@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
@ -5753,6 +5776,11 @@ prop-types@^15.8.1:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pseudomap@^1.0.2: pseudomap@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"