mirror of
https://github.com/balzack/databag.git
synced 2025-02-15 04:59:16 +00:00
support uploading assets
This commit is contained in:
parent
2bdd2c4a68
commit
446b974aed
@ -7,6 +7,7 @@ import { Access } from 'src/access/Access';
|
|||||||
import { Session } from 'src/session/Session';
|
import { Session } from 'src/session/Session';
|
||||||
import { Admin } from 'src/admin/Admin';
|
import { Admin } from 'src/admin/Admin';
|
||||||
import { StoreContextProvider } from 'context/StoreContext';
|
import { StoreContextProvider } from 'context/StoreContext';
|
||||||
|
import { UploadContextProvider } from 'context/UploadContext';
|
||||||
import { AppContextProvider } from 'context/AppContext';
|
import { AppContextProvider } from 'context/AppContext';
|
||||||
import { AccountContextProvider } from 'context/AccountContext';
|
import { AccountContextProvider } from 'context/AccountContext';
|
||||||
import { ProfileContextProvider } from 'context/ProfileContext';
|
import { ProfileContextProvider } from 'context/ProfileContext';
|
||||||
@ -24,6 +25,7 @@ export default function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StoreContextProvider>
|
<StoreContextProvider>
|
||||||
|
<UploadContextProvider>
|
||||||
<CardContextProvider>
|
<CardContextProvider>
|
||||||
<ChannelContextProvider>
|
<ChannelContextProvider>
|
||||||
<AccountContextProvider>
|
<AccountContextProvider>
|
||||||
@ -48,6 +50,7 @@ export default function App() {
|
|||||||
</AccountContextProvider>
|
</AccountContextProvider>
|
||||||
</ChannelContextProvider>
|
</ChannelContextProvider>
|
||||||
</CardContextProvider>
|
</CardContextProvider>
|
||||||
|
</UploadContextProvider>
|
||||||
</StoreContextProvider>
|
</StoreContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"@react-navigation/native": "^6.0.13",
|
"@react-navigation/native": "^6.0.13",
|
||||||
"@react-navigation/stack": "^6.3.0",
|
"@react-navigation/stack": "^6.3.0",
|
||||||
"@stream-io/flat-list-mvcp": "^0.10.2",
|
"@stream-io/flat-list-mvcp": "^0.10.2",
|
||||||
|
"axios": "^1.1.0",
|
||||||
"expo": "~46.0.9",
|
"expo": "~46.0.9",
|
||||||
"expo-av": "^12.0.4",
|
"expo-av": "^12.0.4",
|
||||||
"expo-splash-screen": "~0.16.2",
|
"expo-splash-screen": "~0.16.2",
|
||||||
|
14
app/mobile/src/context/UploadContext.js
Normal file
14
app/mobile/src/context/UploadContext.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { useUploadContext } from './useUploadContext.hook';
|
||||||
|
|
||||||
|
export const UploadContext = createContext({});
|
||||||
|
|
||||||
|
export function UploadContextProvider({ children }) {
|
||||||
|
const { state, actions } = useUploadContext();
|
||||||
|
return (
|
||||||
|
<UploadContext.Provider value={{ state, actions }}>
|
||||||
|
{children}
|
||||||
|
</UploadContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
import { useState, useRef, useContext } from 'react';
|
import { useState, useRef, useContext } from 'react';
|
||||||
import { StoreContext } from 'context/StoreContext';
|
import { StoreContext } from 'context/StoreContext';
|
||||||
|
import { UploadContext } from 'context/UploadContext';
|
||||||
import { getCards } from 'api/getCards';
|
import { getCards } from 'api/getCards';
|
||||||
import { getCardProfile } from 'api/getCardProfile';
|
import { getCardProfile } from 'api/getCardProfile';
|
||||||
import { getCardDetail } from 'api/getCardDetail';
|
import { getCardDetail } from 'api/getCardDetail';
|
||||||
@ -30,6 +31,7 @@ export function useCardContext() {
|
|||||||
cards: new Map(),
|
cards: new Map(),
|
||||||
});
|
});
|
||||||
const store = useContext(StoreContext);
|
const store = useContext(StoreContext);
|
||||||
|
const upload = useContext(UploadContext);
|
||||||
|
|
||||||
const session = useRef(null);
|
const session = useRef(null);
|
||||||
const curRevision = useRef(null);
|
const curRevision = useRef(null);
|
||||||
@ -464,14 +466,26 @@ export function useCardContext() {
|
|||||||
const { detail, profile } = getCard(cardId);
|
const { detail, profile } = getCard(cardId);
|
||||||
return getContactChannelTopicAssetUrl(profile.node, `${profile.guid}.${detail.token}`, channelId, topicId, assetId);
|
return getContactChannelTopicAssetUrl(profile.node, `${profile.guid}.${detail.token}`, channelId, topicId, assetId);
|
||||||
},
|
},
|
||||||
addChannelTopic: async (cardId, channelId, message, assets) => {
|
addChannelTopic: async (cardId, channelId, message, files) => {
|
||||||
const { detail, profile } = getCard(cardId);
|
const { detail, profile } = getCard(cardId);
|
||||||
if (assets?.length > 0) {
|
const node = profile.node;
|
||||||
console.log("UPLOAD");
|
const token = `${profile.guid}.${detail.token}`;
|
||||||
|
if (files?.length > 0) {
|
||||||
|
const topicId = await addContactChannelTopic(node, token, channelId, null, null);
|
||||||
|
upload.actions.addContactTopic(node, token, cardId, channelId, topicId, files, async (assets) => {
|
||||||
|
message.assets = assets;
|
||||||
|
await setContactChannelTopicSubject(node, token, channelId, topicId, message);
|
||||||
|
}, async () => {
|
||||||
|
try {
|
||||||
|
await removeContactChannelTopic(node, token, channelId, topicId);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await addContactChannelTopic(profile.node, `${profile.guid}.${detail.token}`, channelId, message, []);
|
await addContactChannelTopic(node, token, channelId, message, []);
|
||||||
// sync channel
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setChannelTopicSubject: async (cardId, channelId, topicId, data) => {
|
setChannelTopicSubject: async (cardId, channelId, topicId, data) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useState, useRef, useContext } from 'react';
|
import { useState, useRef, useContext } from 'react';
|
||||||
import { StoreContext } from 'context/StoreContext';
|
import { StoreContext } from 'context/StoreContext';
|
||||||
|
import { UploadContext } from 'context/UploadContext';
|
||||||
import { getChannels } from 'api/getChannels';
|
import { getChannels } from 'api/getChannels';
|
||||||
import { getChannelDetail } from 'api/getChannelDetail';
|
import { getChannelDetail } from 'api/getChannelDetail';
|
||||||
import { getChannelSummary } from 'api/getChannelSummary';
|
import { getChannelSummary } from 'api/getChannelSummary';
|
||||||
@ -20,6 +21,7 @@ export function useChannelContext() {
|
|||||||
channels: new Map(),
|
channels: new Map(),
|
||||||
});
|
});
|
||||||
const store = useContext(StoreContext);
|
const store = useContext(StoreContext);
|
||||||
|
const upload = useContext(UploadContext);
|
||||||
|
|
||||||
const session = useRef(null);
|
const session = useRef(null);
|
||||||
const curRevision = useRef(null);
|
const curRevision = useRef(null);
|
||||||
@ -208,14 +210,24 @@ export function useChannelContext() {
|
|||||||
const { server, appToken } = session.current;
|
const { server, appToken } = session.current;
|
||||||
return getChannelTopicAssetUrl(server, appToken, channelId, topicId, assetId);
|
return getChannelTopicAssetUrl(server, appToken, channelId, topicId, assetId);
|
||||||
},
|
},
|
||||||
addTopic: async (channelId, message, assets) => {
|
addTopic: async (channelId, message, files) => {
|
||||||
const { server, appToken } = session.current;
|
const { server, appToken } = session.current;
|
||||||
if (assets?.length) {
|
if (files?.length > 0) {
|
||||||
console.log("UPLOAD");
|
const topicId = await addChannelTopic(server, appToken, channelId, null, null);
|
||||||
|
upload.actions.addTopic(server, appToken, channelId, topicId, files, async (assets) => {
|
||||||
|
message.assets = assets;
|
||||||
|
await setChannelTopicSubject(server, appToken, channelId, topicId, message);
|
||||||
|
}, async () => {
|
||||||
|
try {
|
||||||
|
await removeChannelTopic(server, appToken, channelId, topicId);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await addChannelTopic(server, appToken, channelId, message, []);
|
await addChannelTopic(server, appToken, channelId, message, []);
|
||||||
//sync channels
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setTopicSubject: async (channelId, topicId, data) => {
|
setTopicSubject: async (channelId, topicId, data) => {
|
||||||
|
@ -138,17 +138,25 @@ export function useConversationContext() {
|
|||||||
topics.current.delete(topic.id);
|
topics.current.delete(topic.id);
|
||||||
await clearTopicItem(cardId, channelId, topic.id);
|
await clearTopicItem(cardId, channelId, topic.id);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
const cached = topics.current.get(topic.id);
|
const cached = topics.current.get(topic.id);
|
||||||
if (!cached || cached.detailRevision != topic.data.detailRevision) {
|
if (!cached || cached.detailRevision != topic.data.detailRevision) {
|
||||||
if (!topic.data.topicDetail) {
|
if (!topic.data.topicDetail) {
|
||||||
const updated = await getTopic(cardId, channelId, topic.id);
|
const updated = await getTopic(cardId, channelId, topic.id);
|
||||||
topic.data.topicDetail = updated.data.topicDetail;
|
topic.data = updated.data;
|
||||||
}
|
}
|
||||||
|
if (!topic.data) {
|
||||||
|
topics.current.delete(topic.id);
|
||||||
|
await clearTopicItem(cardId, channelId, topic.id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
await setTopicItem(cardId, channelId, topic);
|
await setTopicItem(cardId, channelId, topic);
|
||||||
const { id, revision, data } = topic;
|
const { id, revision, data } = topic;
|
||||||
topics.current.set(id, { topicId: id, revision: revision, detailRevision: topic.data.detailRevision, detail: topic.data.topicDetail });
|
topics.current.set(id, { topicId: id, revision: revision, detailRevision: topic.data.detailRevision, detail: topic.data.topicDetail });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await setSyncRevision(cardId, channelId, res.revision);
|
await setSyncRevision(cardId, channelId, res.revision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
216
app/mobile/src/context/useUploadContext.hook.js
Normal file
216
app/mobile/src/context/useUploadContext.hook.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { useState, useRef } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export function useUploadContext() {
|
||||||
|
|
||||||
|
const [state, setState] = useState({
|
||||||
|
progress: new Map(),
|
||||||
|
});
|
||||||
|
const channels = useRef(new Map());
|
||||||
|
const index = useRef(0);
|
||||||
|
|
||||||
|
const updateState = (value) => {
|
||||||
|
setState((s) => ({ ...s, ...value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateComplete = (channel, topic) => {
|
||||||
|
let topics = channels.current.get(channel);
|
||||||
|
if (topics) {
|
||||||
|
topics.delete(topic);
|
||||||
|
}
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
let progress = new Map();
|
||||||
|
channels.current.forEach((topics, channel) => {
|
||||||
|
let assets = [];
|
||||||
|
topics.forEach((entry, topic, map) => {
|
||||||
|
let active = entry.active ? 1 : 0;
|
||||||
|
assets.push({
|
||||||
|
upload: entry.index,
|
||||||
|
topicId: topic,
|
||||||
|
active: entry.active,
|
||||||
|
uploaded: entry.assets.length,
|
||||||
|
index: entry.assets.length + active,
|
||||||
|
count: entry.assets.length + entry.files.length + active,
|
||||||
|
error: entry.error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (assets.length) {
|
||||||
|
progress.set(channel, assets.sort((a, b) => (a.upload < b.upload) ? 1 : -1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateState({ progress });
|
||||||
|
}
|
||||||
|
|
||||||
|
const abort = (channelId, topicId) => {
|
||||||
|
const channel = channels.current.get(channelId);
|
||||||
|
if (channel) {
|
||||||
|
const topic = channel.get(topicId);
|
||||||
|
if (topic) {
|
||||||
|
topic.cancel.abort();
|
||||||
|
channel.delete(topicId);
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
addTopic: (node, token, channelId, topicId, files, success, failure) => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const entry = {
|
||||||
|
index: index.current,
|
||||||
|
url: `https://${node}/content/channels/${channelId}/topics/${topicId}/assets?agent=${token}`,
|
||||||
|
files,
|
||||||
|
assets: [],
|
||||||
|
current: null,
|
||||||
|
error: false,
|
||||||
|
success,
|
||||||
|
failure,
|
||||||
|
cancel: controller,
|
||||||
|
}
|
||||||
|
index.current += 1;
|
||||||
|
const key = `:${channelId}`;
|
||||||
|
if (!channels.current.has(key)) {
|
||||||
|
channels.current.set(key, new Map());
|
||||||
|
}
|
||||||
|
const topics = channels.current.get(key);
|
||||||
|
topics.set(topicId, entry);
|
||||||
|
upload(entry, updateProgress, () => { updateComplete(key, topicId) } );
|
||||||
|
},
|
||||||
|
cancelTopic: (channelId, topicId) => {
|
||||||
|
abort(`:${channelId}`, topicId);
|
||||||
|
},
|
||||||
|
addContactTopic: (node, token, cardId, channelId, topicId, files, success, failure) => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const entry = {
|
||||||
|
index: index.current,
|
||||||
|
url: `https://${node}/content/channels/${channelId}/topics/${topicId}/assets?contact=${token}`,
|
||||||
|
files,
|
||||||
|
assets: [],
|
||||||
|
current: null,
|
||||||
|
error: false,
|
||||||
|
success,
|
||||||
|
failure,
|
||||||
|
cancel: controller,
|
||||||
|
}
|
||||||
|
index.current += 1;
|
||||||
|
const key = `${cardId}:${channelId}`;
|
||||||
|
if (!channels.current.has(key)) {
|
||||||
|
channels.current.set(key, new Map());
|
||||||
|
}
|
||||||
|
const topics = channels.current.get(key);
|
||||||
|
topics.set(topicId, entry);
|
||||||
|
upload(entry, updateProgress, () => { updateComplete(key, topicId) });
|
||||||
|
},
|
||||||
|
cancelContactTopic: (cardId, channelId, topicId) => {
|
||||||
|
abort(`${cardId}:${channelId}`, topicId);
|
||||||
|
},
|
||||||
|
clearErrors: (cardId, channelId) => {
|
||||||
|
const key = cardId ? `${cardId}:${channelId}` : `:${channelId}`;
|
||||||
|
const topics = channels.current.get(key);
|
||||||
|
if (topics) {
|
||||||
|
topics.forEach((topic, topicId) => {
|
||||||
|
if (topic.error) {
|
||||||
|
topic.cancel.abort();
|
||||||
|
topics.delete(topicId);
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
channels.current.forEach((topics, channelId) => {
|
||||||
|
topics.forEach((assets, topicId) => {
|
||||||
|
assets.cancel.abort();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
channels.current.clear();
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { state, actions }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload(entry, update, complete) {
|
||||||
|
if (!entry.files?.length) {
|
||||||
|
entry.success(entry.assets);
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const file = entry.files.shift();
|
||||||
|
entry.active = {};
|
||||||
|
try {
|
||||||
|
if (file.type === 'image') {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("asset", {uri: 'file://' + file.data, name: 'asset', type: 'application/octent-stream'});
|
||||||
|
let transform = encodeURIComponent(JSON.stringify(["ithumb;photo", "ilg;photo"]));
|
||||||
|
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||||
|
signal: entry.cancel.signal,
|
||||||
|
onUploadProgress: (ev) => {
|
||||||
|
const { loaded, total } = ev;
|
||||||
|
entry.active = { loaded, total }
|
||||||
|
update();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
entry.assets.push({
|
||||||
|
image: {
|
||||||
|
thumb: asset.data.find(item => item.transform === 'ithumb;photo').assetId,
|
||||||
|
full: asset.data.find(item => item.transform === 'ilg;photo').assetId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (file.type === 'video') {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("asset", {uri: 'file://' + file.data, name: 'asset', type: 'application/octent-stream'});
|
||||||
|
let thumb = 'vthumb;video;' + file.position;
|
||||||
|
let transform = encodeURIComponent(JSON.stringify(["vlq;video", "vhd;video", thumb]));
|
||||||
|
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||||
|
signal: entry.cancel.signal,
|
||||||
|
onUploadProgress: (ev) => {
|
||||||
|
const { loaded, total } = ev;
|
||||||
|
entry.active = { loaded, total }
|
||||||
|
update();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
entry.assets.push({
|
||||||
|
video: {
|
||||||
|
thumb: asset.data.find(item => item.transform === thumb).assetId,
|
||||||
|
lq: asset.data.find(item => item.transform === 'vlq;video').assetId,
|
||||||
|
hd: asset.data.find(item => item.transform === 'vhd;video').assetId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (file.type === 'audio') {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("asset", {uri: 'file://' + file.data, name: 'asset', type: 'application/octent-stream'});
|
||||||
|
let transform = encodeURIComponent(JSON.stringify(["acopy;audio"]));
|
||||||
|
let asset = await axios.post(`${entry.url}&transforms=${transform}`, formData, {
|
||||||
|
signal: entry.cancel.signal,
|
||||||
|
onUploadProgress: (ev) => {
|
||||||
|
const { loaded, total } = ev;
|
||||||
|
entry.active = { loaded, total }
|
||||||
|
update();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
entry.assets.push({
|
||||||
|
audio: {
|
||||||
|
label: file.label,
|
||||||
|
full: asset.data.find(item => item.transform === 'acopy;audio').assetId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
entry.active = null;
|
||||||
|
upload(entry, update, complete);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
entry.failure();
|
||||||
|
entry.error = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2111,6 +2111,15 @@ atob@^2.1.2:
|
|||||||
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz"
|
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
axios@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.0.tgz#94d25e6524743c7fc33954dd536687bbb957793a"
|
||||||
|
integrity sha512-hsJgcqz4JY7f+HZ4cWTrPZ6tZNCNFPTRx1MjRqu/hbpgpHdSCUpLVuplc+jE/h7dOvyANtw/ERA3HC2Rz/QoMg==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.0"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
babel-core@^7.0.0-bridge.0:
|
babel-core@^7.0.0-bridge.0:
|
||||||
version "7.0.0-bridge.0"
|
version "7.0.0-bridge.0"
|
||||||
resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz"
|
resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz"
|
||||||
@ -3441,6 +3450,11 @@ flow-parser@^0.121.0:
|
|||||||
resolved "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz"
|
resolved "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz"
|
||||||
integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==
|
integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==
|
||||||
|
|
||||||
|
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==
|
||||||
|
|
||||||
fontfaceobserver@^2.1.0:
|
fontfaceobserver@^2.1.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz"
|
resolved "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz"
|
||||||
@ -3460,6 +3474,15 @@ form-data@^3.0.1:
|
|||||||
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"
|
||||||
|
|
||||||
fragment-cache@^0.2.1:
|
fragment-cache@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz"
|
resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz"
|
||||||
@ -5516,6 +5539,11 @@ prop-types@*, prop-types@^15.5.10, prop-types@^15.7.2:
|
|||||||
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==
|
||||||
|
|
||||||
pump@^3.0.0:
|
pump@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user