use new notification state
This commit is contained in:
parent
6139cede26
commit
b295a7db57
12 changed files with 1041 additions and 772 deletions
1368
dist/bundle.js
vendored
1368
dist/bundle.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -28,7 +28,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"proxy-polyfill": "0.1.6",
|
||||
"reselect": "^3.0.0"
|
||||
"reselect": "^3.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
|
|
|
@ -210,5 +210,10 @@ export const REMOVE_PENDING_PUBLISH = 'REMOVE_PENDING_PUBLISH';
|
|||
export const DO_PREPARE_EDIT = 'DO_PREPARE_EDIT';
|
||||
|
||||
// Notifications
|
||||
export const CREATE_NOTIFICATION = 'CREATE_NOTIFICATION';
|
||||
export const DISMISS_NOTIFICATION = 'DISMISS_NOTIFICATION';
|
||||
export const CREATE_EVENT = 'CREATE_EVENT';
|
||||
export const EDIT_EVENT = 'EDIT_EVENT';
|
||||
export const DELETE_EVENT = 'DELETE_EVENT';
|
||||
export const CREATE_TOAST = 'CREATE_TOAST';
|
||||
export const DISMISS_TOAST = 'DISMISS_TOAST';
|
||||
export const CREATE_ERROR = 'CREATE_ERROR';
|
||||
export const DISMISS_ERROR = 'DISMISS_ERROR';
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
export const CONFIRM_FILE_REMOVE = 'confirm_file_remove';
|
||||
export const CONFIRM_EXTERNAL_LINK = 'confirm_external_link';
|
||||
export const INCOMPATIBLE_DAEMON = 'incompatible_daemon';
|
||||
export const FILE_TIMEOUT = 'file_timeout';
|
||||
export const DOWNLOADING = 'downloading';
|
||||
export const AUTO_UPDATE_DOWNLOADED = 'auto_update_downloaded';
|
||||
export const AUTO_UPDATE_CONFIRM = 'auto_update_confirm';
|
||||
export const ERROR = 'error';
|
||||
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
|
||||
export const UPGRADE = 'upgrade';
|
||||
export const WELCOME = 'welcome';
|
||||
export const EMAIL_COLLECTION = 'email_collection';
|
||||
export const PHONE_COLLECTION = 'phone_collection';
|
||||
export const FIRST_REWARD = 'first_reward';
|
||||
export const AUTHENTICATION_FAILURE = 'auth_failure';
|
||||
export const TRANSACTION_FAILED = 'transaction_failed';
|
||||
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
|
||||
export const REWARD_GENERATED_CODE = 'reward_generated_code';
|
||||
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
||||
export const CONFIRM_CLAIM_REVOKE = 'confirm_claim_revoke';
|
||||
export const FIRST_SUBSCRIPTION = 'firstSubscription';
|
||||
export const SEND_TIP = 'send_tip';
|
||||
export const SOCIAL_SHARE = 'social_share';
|
||||
export const PUBLISH = 'publish';
|
||||
export const SEARCH = 'search';
|
||||
export const CONFIRM_TRANSACTION = 'confirm_transaction';
|
||||
export const CONFIRM_THUMBNAIL_UPLOAD = 'confirm_thumbnail_upload';
|
||||
export const WALLET_ENCRYPT = 'wallet_encrypt';
|
||||
export const WALLET_DECRYPT = 'wallet_decrypt';
|
||||
export const WALLET_UNLOCK = 'wallet_unlock';
|
22
src/index.js
22
src/index.js
|
@ -1,5 +1,4 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import * as SEARCH_TYPES from 'constants/search';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
|
@ -12,19 +11,10 @@ import Lbryapi from 'lbryapi';
|
|||
import { selectState as selectSearchState } from 'redux/selectors/search';
|
||||
|
||||
// types
|
||||
export { Notification } from 'types/Notification';
|
||||
export { Toast } from 'types/Notification';
|
||||
|
||||
// constants
|
||||
export {
|
||||
ACTIONS,
|
||||
MODALS,
|
||||
THUMBNAIL_STATUSES,
|
||||
SEARCH_TYPES,
|
||||
SETTINGS,
|
||||
TRANSACTIONS,
|
||||
SORT_OPTIONS,
|
||||
PAGES,
|
||||
};
|
||||
export { ACTIONS, THUMBNAIL_STATUSES, SEARCH_TYPES, SETTINGS, TRANSACTIONS, SORT_OPTIONS, PAGES };
|
||||
|
||||
// common
|
||||
export { Lbry, Lbryapi };
|
||||
|
@ -41,7 +31,7 @@ export {
|
|||
} from 'lbryURI';
|
||||
|
||||
// actions
|
||||
export { doNotify, doHideNotification } from 'redux/actions/notifications';
|
||||
export { doToast, doDismissToast, doError, doDismissError } from 'redux/actions/notifications';
|
||||
|
||||
export {
|
||||
doFetchClaimsByChannel,
|
||||
|
@ -108,11 +98,7 @@ export { blacklistReducer } from 'redux/reducers/blacklist';
|
|||
// selectors
|
||||
export { selectBlackListedOutpoints } from 'redux/selectors/blacklist';
|
||||
|
||||
export {
|
||||
selectNotification,
|
||||
selectNotificationProps,
|
||||
selectSnack,
|
||||
} from 'redux/selectors/notifications';
|
||||
export { selectToast, selectError } from 'redux/selectors/notifications';
|
||||
|
||||
export {
|
||||
makeSelectClaimForUri,
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as ACTIONS from 'constants/action_types';
|
|||
import Lbry from 'lbry';
|
||||
import Lbryapi from 'lbryapi';
|
||||
import { normalizeURI } from 'lbryURI';
|
||||
import { doNotify } from 'redux/actions/notifications';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { selectMyClaimsRaw, selectResolvingUris } from 'redux/selectors/claims';
|
||||
import { batchActions } from 'util/batchActions';
|
||||
import { doFetchTransactions } from 'redux/actions/wallet';
|
||||
|
@ -86,11 +86,9 @@ export function doAbandonClaim(txid, nout) {
|
|||
|
||||
const errorCallback = () => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Transaction failed',
|
||||
message: 'Error abandoning claim',
|
||||
type: 'error',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
doToast({
|
||||
message: 'Transaction failed',
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
@ -104,10 +102,8 @@ export function doAbandonClaim(txid, nout) {
|
|||
},
|
||||
});
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Transaction successful',
|
||||
doToast({
|
||||
message: 'Successfully abandoned your claim',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -117,11 +113,9 @@ export function doAbandonClaim(txid, nout) {
|
|||
dispatch(doFetchTransactions());
|
||||
} else {
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Transaction failed',
|
||||
doToast({
|
||||
message: 'Error abandoning claim',
|
||||
type: 'error',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,39 @@
|
|||
// @flow
|
||||
import type { ToastParams } from 'types/Notification';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import type { Notification, NotificationProps } from 'types/Notification';
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
export function doToast(params: ToastParams) {
|
||||
if (!params) {
|
||||
throw Error("'params' object is required to create a toast notification");
|
||||
}
|
||||
|
||||
export function doNotify(notification: Notification, notificationProps: NotificationProps) {
|
||||
return {
|
||||
type: ACTIONS.CREATE_NOTIFICATION,
|
||||
type: ACTIONS.CREATE_TOAST,
|
||||
data: {
|
||||
notification,
|
||||
// using this syntax to create an object if notificationProps is undefined
|
||||
notificationProps: { ...notificationProps },
|
||||
id: uuid(),
|
||||
params,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function doHideNotification() {
|
||||
export function doDismissToast() {
|
||||
return {
|
||||
type: ACTIONS.DISMISS_NOTIFICATION,
|
||||
type: ACTIONS.DISMISS_TOAST,
|
||||
};
|
||||
}
|
||||
|
||||
export function doError(error: string | {}) {
|
||||
return {
|
||||
type: ACTIONS.CREATE_ERROR,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function doDismissError() {
|
||||
return {
|
||||
type: ACTIONS.DISMISS_ERROR,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import Lbry from 'lbry';
|
||||
import { doNotify } from 'redux/actions/notifications';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import { creditsToString } from 'util/formatCredits';
|
||||
|
||||
|
@ -96,11 +96,9 @@ export function doSendDraftTransaction(address, amount) {
|
|||
|
||||
if (balance - amount <= 0) {
|
||||
dispatch(
|
||||
doNotify({
|
||||
doToast({
|
||||
title: 'Insufficient credits',
|
||||
message: 'Insufficient credits',
|
||||
type: 'error',
|
||||
displayType: ['modal', 'toast'],
|
||||
})
|
||||
);
|
||||
return;
|
||||
|
@ -116,11 +114,8 @@ export function doSendDraftTransaction(address, amount) {
|
|||
type: ACTIONS.SEND_TRANSACTION_COMPLETED,
|
||||
});
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Credits sent',
|
||||
doToast({
|
||||
message: `You sent ${amount} LBC`,
|
||||
type: 'error',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
linkText: 'History',
|
||||
linkTarget: '/wallet',
|
||||
})
|
||||
|
@ -131,11 +126,9 @@ export function doSendDraftTransaction(address, amount) {
|
|||
data: { error: response },
|
||||
});
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Transaction failed',
|
||||
doToast({
|
||||
message: 'Transaction failed',
|
||||
type: 'error',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -147,11 +140,9 @@ export function doSendDraftTransaction(address, amount) {
|
|||
data: { error: error.message },
|
||||
});
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Transaction failed',
|
||||
doToast({
|
||||
message: 'Transaction failed',
|
||||
type: 'error',
|
||||
displayType: ['snackbar', 'toast'],
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
@ -184,11 +175,9 @@ export function doSendTip(amount, claimId, uri, successCallback, errorCallback)
|
|||
|
||||
if (balance - amount <= 0) {
|
||||
dispatch(
|
||||
doNotify({
|
||||
title: 'Insufficient credits',
|
||||
doToast({
|
||||
message: 'Insufficient credits',
|
||||
type: 'error',
|
||||
displayType: ['modal', 'toast'],
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
return;
|
||||
|
@ -196,11 +185,10 @@ export function doSendTip(amount, claimId, uri, successCallback, errorCallback)
|
|||
|
||||
const success = () => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
doToast({
|
||||
message: __(`You sent ${amount} LBC as a tip, Mahalo!`),
|
||||
linkText: __('History'),
|
||||
linkTarget: __('/wallet'),
|
||||
displayType: ['snackbar'],
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -215,9 +203,9 @@ export function doSendTip(amount, claimId, uri, successCallback, errorCallback)
|
|||
|
||||
const error = (err) => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
doToast({
|
||||
message: __(`There was an error sending support funds.`),
|
||||
displayType: ['snackbar'],
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -1,55 +1,99 @@
|
|||
// @flow
|
||||
import type {
|
||||
NotificationState,
|
||||
DoToast,
|
||||
DoEvent,
|
||||
DoEditEvent,
|
||||
DoDeleteEvent,
|
||||
} from 'types/Notification';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const reducers = {};
|
||||
|
||||
const defaultState = {
|
||||
// First-in, first-out
|
||||
queue: [],
|
||||
const defaultState: NotificationState = {
|
||||
events: [],
|
||||
toasts: [],
|
||||
errors: [],
|
||||
};
|
||||
|
||||
reducers[ACTIONS.CREATE_NOTIFICATION] = (state, action) => {
|
||||
const { notification, notificationProps } = action.data;
|
||||
const { title, message, type, error, displayType, id } = notification;
|
||||
const notificationsReducer = handleActions(
|
||||
{
|
||||
// Toasts
|
||||
[ACTIONS.CREATE_TOAST]: (state: NotificationState, action: DoToast) => {
|
||||
const toast = action.data;
|
||||
const newToasts = state.toasts.slice();
|
||||
newToasts.push(toast);
|
||||
|
||||
const queue = Object.assign([], state.queue);
|
||||
queue.push({
|
||||
notification: {
|
||||
id,
|
||||
title,
|
||||
message,
|
||||
type,
|
||||
error,
|
||||
displayType,
|
||||
return {
|
||||
...state,
|
||||
toasts: newToasts,
|
||||
};
|
||||
},
|
||||
notificationProps,
|
||||
});
|
||||
[ACTIONS.DISMISS_TOAST]: (state: NotificationState) => {
|
||||
const newToasts = state.toasts.slice();
|
||||
newToasts.shift();
|
||||
|
||||
return Object.assign({}, state, {
|
||||
queue,
|
||||
});
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
toasts: newToasts,
|
||||
};
|
||||
},
|
||||
|
||||
reducers[ACTIONS.DISMISS_NOTIFICATION] = state => {
|
||||
const queue = Object.assign([], state.queue);
|
||||
queue.shift();
|
||||
// Events
|
||||
[ACTIONS.CREATE_EVENT]: (state: NotificationState, action: DoEvent) => {
|
||||
const event = action.data;
|
||||
const newEvents = state.events.slice();
|
||||
newEvents.push(event);
|
||||
|
||||
return Object.assign({}, state, {
|
||||
queue,
|
||||
});
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
events: newEvents,
|
||||
};
|
||||
},
|
||||
// Used to mark notifications as read/dismissed
|
||||
[ACTIONS.EDIT_EVENT]: (state: NotificationState, action: DoEditEvent) => {
|
||||
const { event } = action.data;
|
||||
let events = state.events.slice();
|
||||
|
||||
reducers[ACTIONS.HISTORY_NAVIGATE] = state => {
|
||||
const queue = Object.assign([], state.queue);
|
||||
if (queue[0] && queue[0].notification.id === MODALS.SEARCH) {
|
||||
queue.shift();
|
||||
return Object.assign({}, state, { queue });
|
||||
}
|
||||
return state;
|
||||
};
|
||||
events = events.map((pastEvent) => (pastEvent.id === event.id ? event : pastEvent));
|
||||
|
||||
export function notificationsReducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
events,
|
||||
};
|
||||
},
|
||||
[ACTIONS.DELETE_EVENT]: (state: NotificationState, action: DoDeleteEvent) => {
|
||||
const { id } = action.data;
|
||||
let newEvents = state.events.slice();
|
||||
newEvents = newEvents.filter((notification) => notification.id !== id);
|
||||
|
||||
return {
|
||||
...state,
|
||||
events: newEvents,
|
||||
};
|
||||
},
|
||||
|
||||
// Errors
|
||||
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoToast) => {
|
||||
const error = action.data;
|
||||
const newErrors = state.errors.slice();
|
||||
newErrors.push(error);
|
||||
|
||||
return {
|
||||
...state,
|
||||
errors: newErrors,
|
||||
};
|
||||
},
|
||||
[ACTIONS.DISMISS_ERROR]: (state: NotificationState) => {
|
||||
const newErrors = state.errors.slice();
|
||||
newErrors.shift();
|
||||
|
||||
return {
|
||||
...state,
|
||||
errors: newErrors,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
||||
|
||||
export { notificationsReducer };
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = state => state.notifications || {};
|
||||
export const selectState = (state) => state.notifications || {};
|
||||
|
||||
export const selectNotificationData = createSelector(
|
||||
selectState,
|
||||
state => (state.queue.length > 0 ? state.queue[0] : {})
|
||||
);
|
||||
|
||||
export const selectNotification = createSelector(
|
||||
selectNotificationData,
|
||||
notificationData => notificationData.notification
|
||||
);
|
||||
|
||||
export const selectNotificationProps = createSelector(
|
||||
selectNotificationData,
|
||||
notificationData => notificationData.notificationProps
|
||||
);
|
||||
|
||||
export const selectSnack = createSelector(
|
||||
// No props for snackbar
|
||||
selectNotification,
|
||||
notification => {
|
||||
if (notification && notification.displayType) {
|
||||
return notification.displayType.indexOf('snackbar') > -1 ? notification : undefined;
|
||||
export const selectToast = createSelector(selectState, (state) => {
|
||||
if (state.toasts.length) {
|
||||
const { id, params } = state.toasts[0];
|
||||
return {
|
||||
id,
|
||||
...params,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
});
|
||||
|
||||
export const selectError = createSelector(selectState, (state) => {
|
||||
if (state.errors.length) {
|
||||
const { error } = state.errors[0];
|
||||
return {
|
||||
error,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -1,19 +1,97 @@
|
|||
// @flow
|
||||
export type Notification = {
|
||||
id: ?string,
|
||||
title: ?string,
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
/*
|
||||
Toasts:
|
||||
- First-in, first-out queue
|
||||
- Simple messages that are shown in response to user interactions
|
||||
- Never saved
|
||||
- If they are the result of errors, use the isError flag when creating
|
||||
- For errors that should interrupt user behavior, use Error
|
||||
*/
|
||||
export type ToastParams = {
|
||||
message: string,
|
||||
type: string,
|
||||
error: ?string,
|
||||
displayType: mixed,
|
||||
|
||||
// additional properties for SnackBar
|
||||
linkText: ?string,
|
||||
linkTarget: ?string,
|
||||
title?: string,
|
||||
linkText?: string,
|
||||
linkTarget?: string,
|
||||
isError?: boolean,
|
||||
};
|
||||
|
||||
// Used for retreiving data from redux store
|
||||
export type NotificationProps = {
|
||||
uri: ?string,
|
||||
path: ?string,
|
||||
export type Toast = {
|
||||
id: string,
|
||||
params: ToastParams,
|
||||
};
|
||||
|
||||
export type DoToast = {
|
||||
type: ACTIONS.CREATE_TOAST,
|
||||
data: Toast,
|
||||
};
|
||||
|
||||
/*
|
||||
Events:
|
||||
- List of notifications based on user interactions/app events
|
||||
- Always saved, but can be manually deleted
|
||||
- Can happen in the background, or because of user interaction (ex: publish confirmed)
|
||||
*/
|
||||
export type Event = {
|
||||
id: string, // Unique id
|
||||
dateCreated: number,
|
||||
isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet
|
||||
source?: string, // The type/area an event is from. Used for sorting (ex: publishes, transactions)
|
||||
// We may want to use priority/isDismissed in the future to specify how urgent a notification is
|
||||
// and if the user should see it immediately
|
||||
// isDissmied: boolean,
|
||||
// priority?: number
|
||||
};
|
||||
|
||||
export type DoEvent = {
|
||||
type: ACTIONS.CREATE_EVENT,
|
||||
data: Event,
|
||||
};
|
||||
|
||||
export type DoEditEvent = {
|
||||
type: ACTIONS.EDIT_EVENT,
|
||||
data: {
|
||||
id: string,
|
||||
isRead: boolean,
|
||||
// In the future we can add `isDismissed` if we decide to show notifications as they come in
|
||||
// Similar to Facebook's notifications in the corner of the screen
|
||||
// isDismissed: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
export type DoDeleteEvent = {
|
||||
type: ACTIONS.DELETE_EVENT,
|
||||
data: {
|
||||
id: string, // The id to delete
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
Errors:
|
||||
- First-in, first-out queue
|
||||
- Errors that should interupt user behavior
|
||||
- For errors that can be shown without interrupting a user, use Toast with the isError flag
|
||||
*/
|
||||
export type Error = {
|
||||
title: string,
|
||||
text: string,
|
||||
};
|
||||
|
||||
export type DoError = {
|
||||
type: ACTIONS.CREATE_ERROR,
|
||||
data: Error,
|
||||
};
|
||||
|
||||
export type DoDismissError = {
|
||||
type: ACTIONS.DISMISS_ERROR,
|
||||
};
|
||||
|
||||
/*
|
||||
NotificationState
|
||||
*/
|
||||
export type NotificationState = {
|
||||
events: Array<Event>,
|
||||
errors: Array<Error>,
|
||||
toasts: Array<Toast>,
|
||||
};
|
||||
|
|
|
@ -5977,6 +5977,10 @@ uuid@^3.1.0:
|
|||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
|
||||
v8-compile-cache@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4"
|
||||
|
|
Loading…
Reference in a new issue