Skip to content

Commit

Permalink
feat: delete-modal pin guidance, improved language (#1644)
Browse files Browse the repository at this point in the history
* Delete modal language, standardization
* Delete -> Remove
* feat: add pinning services integration to the remove modal
* refactor: best-effort unpin after files.rm

This ensures we don't remove pins if removal failed first,
and also do pin removal in best-effort fashion to avoid crashing webui
on DAGs with multiple instances of the same CID

Also, closes #1740

* chore: change checkbox to match files page
* chore: fix selected files unsync after delete
* feat: add item/folder name to the description when removing
* style: move filename to modal title

This makes filename more prominent when a single item is removed.
Easier to scan when performing quick operations.

Co-authored-by: Rafael Ramalho <rafazelramalho19@gmail.com>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
  • Loading branch information
3 people authored Apr 1, 2021
1 parent f06350e commit 60585ef
Show file tree
Hide file tree
Showing 41 changed files with 13,503 additions and 13,706 deletions.
9 changes: 9 additions & 0 deletions @types/ipfs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ declare module 'ipfs' {
add(cid: CID, options?: PinAddOptions): Promise<Pin[]>;
ls(options?: PinListOptions): AsyncIterable<PinEntry>;
rm(cid: CID, options?: PinRemoveOptions): Promise<Pin[]>;
remote: RemotePinService;
}

declare export interface RemotePinServicesOptions {
cid: CID[];
service: string;
}
declare export interface RemotePinService {
rm(options: RemotePinServicesOptions): Promise<boolean>;
}

declare export interface FileService {
Expand Down
26,827 changes: 13,274 additions & 13,553 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/locales/ar/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "اختر اسمًا جديدًا لهذا الملف.",
"descriptionFolder": "اختر اسمًا جديدًا لهذا المجلد."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, zero {حذف العناصر} one {Delete item} two {Delete {count} items} few {Delete {count} items} many {Delete {count} items} other {Delete {count} items}}",
"titleFolder": "{count, plural, zero {حذف المجلدات} one {Delete folder} two {Delete {count} folders} few {Delete {count} folders} many {Delete {count} folders} other {Delete {count} folders}}",
"titleFile": "{count, plural, zero {حذف الملفات} one {Delete file} two {Delete {count} files} few {Delete {count} files} many {Delete {count} files} other {Delete {count} files}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/cs/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "Vyberte nové jméno pro tento soubor.",
"descriptionFolder": "Vyberte nové jméno pro tuto složku."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Smazat položku} few {Smazat {count} položky} many {Smazat {count} položek} other {Smazat {count} položek}}",
"titleFolder": "{count, plural, one {Smazat složku} few {Smazat {count} složky} many {Smazat {count} složek} other {Smazat {count} složek}}",
"titleFile": "{count, plural, one {Smazat soubor} few {Smazat {count} soubory} many {Smazat {count} souborů} other {Smazat {count} souborů}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/da/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"descriptionFile": "Vælg et nyt navn til denne fil.",
"descriptionFolder": "Vælg et nyt navn til denne mappe."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Fjern element} other {Fjern {count} elementer}}",
"titleFolder": "{count, plural, one {Fjern mappe} other {Fjern {count} mapper}}",
"titleFile": "{count, plural, one {Fjern fil} other {Fjern {count} filer}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/de/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "Wähle einen neuen Namen für diese Datei.",
"descriptionFolder": "Wähle einen neuen Namen für diesen Ordner."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Element löschen} other {{count} Elemente löschen}}",
"titleFolder": "{count, plural, one {Ordner löschen} other {{count} Ordner löschen}}",
"titleFile": "{count, plural, one {Datei löschen} other {{count} Dateien löschen}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"close": "Close",
"copy": "Copy",
"create": "Create",
"delete": "Delete",
"remove": "Remove",
"download": "Download",
"edit": "Edit",
"import": "Import",
Expand Down
20 changes: 11 additions & 9 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"breadcrumbs": "Breadcrumbs",
"finished": "Finished!",
"totalSize": "Total size: {size}",
"filesSelected": "{count, plural, one {File selected} other {Files selected}}",
"filesSelected": "{count, plural, one {Item selected} other {Items selected}}",
"menuOptions": "Options for selected item",
"selectedAllEntries": "Select all table entries",
"individualFilesOnly": "Only available for individual files",
Expand Down Expand Up @@ -32,13 +32,15 @@
"descriptionFile": "Choose a new name for this file.",
"descriptionFolder": "Choose a new name for this folder."
},
"deleteModal": {
"titleItem": "{count, plural, one {Delete item} other {Delete {count} items}}",
"titleFolder": "{count, plural, one {Delete folder} other {Delete {count} folders}}",
"titleFile": "{count, plural, one {Delete file} other {Delete {count} files}}",
"descriptionItem": "{count, plural, one {Are you sure you want to delete this item? This action is permanent and cannot be reversed.} other {Are you sure you want to delete these {count} items? This action is permanent and cannot be reversed.}}",
"descriptionFolder": "{count, plural, one {Are you sure you want to delete this folder? This action is permanent and cannot be reversed.} other {Are you sure you want to delete these {count} folders? This action is permanent and cannot be reversed.}}",
"descriptionFile": "{count, plural, one {Are you sure you want to delete this file? This action is permanent and cannot be reversed.} other {Are you sure you want to delete these {count} files? This action is permanent and cannot be reversed.}}"
"removeModal": {
"titleItem": "{count, plural, one {Remove item? {name}} other {Remove {count} items?}}",
"titleFolder": "{count, plural, one {Remove folder? {name}} other {Remove {count} folders?}}",
"titleFile": "{count, plural, one {Remove file? {name}} other {Remove {count} files?}}",
"descriptionItem": "{count, plural, one {Are you sure you want to remove this item? It will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action and cannot be reversed.} other {Are you sure you want to remove these {count} items? They will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action cannot be reversed.}}",
"descriptionFolder": "{count, plural, one {Are you sure you want to remove this folder? It will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action cannot be reversed.} other {Are you sure you want to remove these {count} folders? They will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action cannot be reversed.}}",
"descriptionFile": "{count, plural, one {Are you sure you want to remove this file? It will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action cannot be reversed.} other {Are you sure you want to remove these {count} files? They will no longer appear on the Files screen, and will be permanently removed in the next scheduled cleanup (though copies may exist on other IPFS nodes). This action cannot be reversed.}}",
"checkboxRemoveLocalPin": "Also remove local pin (recommended)",
"checkboxUnpinFromServices": "Unpin from all pinning services"
},
"pinningModal": {
"title": "Select where you would like to pin these items.",
Expand Down Expand Up @@ -90,7 +92,7 @@
"step5": {
"title": "Files list",
"paragraph1": "Finally, the listing where you can find your files and folders. Drag and drop files or folders to add them.",
"paragraph2": "You can share or download files, inspect them in the IPLD Explorer, rename or delete them!",
"paragraph2": "You can share or download files, inspect them in the IPLD Explorer, rename or remove them!",
"paragraph3": "Pro tip: drag and drop a file from any other page of the Web UI to add them to the root of your MFS."
}
},
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/notify.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"filesRenameFailed": "Failed to rename the files. Please try again.",
"filesMakeDirFailed": "Failed to create directory. Please try again.",
"filesCopyFailed": "Failed to copy the files. Please try again.",
"filesDeleteFailed": "Failed to delete the files. Please try again.",
"filesRemoveFailed": "Failed to remove the files. Please try again.",
"filesAddFailed": "Failed to add files to IPFS. Please try again.",
"filesEventFailed": "Failed to process your request. Please try again.",
"couldntConnectToPeer": "Could not connect to the provided peer.",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/es/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "Elija un nuevo nombre para este archivo.",
"descriptionFolder": "Elija un nuevo nombre para esta carpeta."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Delete item} other {Delete {count} items}}",
"titleFolder": "{count, plural, one {Delete folder} other {Delete {count} folders}}",
"titleFile": "{count, plural, one {Delete file} other {Delete {count} files}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/fr/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "Choisir un nouveau nom pour ce fichier.",
"descriptionFolder": "Choisir un nouveau nom pour ce dossier."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Supprimer l'élément} other {Supprimer {count} éléments}}",
"titleFolder": "{count, plural, one {Supprimer le dossier} other {Supprimer {count} dossiers}}",
"titleFile": "{count, plural, one {Supprimer le fichier} other {Supprimer {count} fichiers}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/it/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "Scegli un nuovo nome per questo file.",
"descriptionFolder": "Scegli un nuovo nome per questa cartella."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Elimina oggetto} other {Elimina {count} oggetti}}",
"titleFolder": "{count, plural, one {Elimina cartella} other {Elimina {count} cartelle}}",
"titleFile": "{count, plural, one {Elimina file} other {Elimina {count} file}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/ko-KR/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"descriptionFile": "파일의 새 이름을 적으세요",
"descriptionFolder": "폴더의 새 이름을 적으세요"
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, other {{count}개 삭제}}",
"titleFolder": "{count, plural, other {{count}개 폴더 삭제}}",
"titleFile": "{count, plural, other {{count}개 파일 삭제}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/nl/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"descriptionFile": "Kies een nieuwe naam voor dit bestand.",
"descriptionFolder": "Kies een nieuwe naam voor deze map."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Verwijder item} other {Verwijder {count}items}}",
"titleFolder": "{count, plural, one {Verwijder map} other {Verwijder {count}mappen}}",
"titleFile": "{count, plural, one {Verwijder bestand} other {Verwijder {count}bestanden}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/no/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"descriptionFile": "Velg et nytt navn for denne filen.",
"descriptionFolder": "Velg et nytt navn for denne mappen."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Slett element} other {Slett {count} elementer}}",
"titleFolder": "{count, plural, one {Slett mappe} other {Slett {count} mapper}}",
"titleFile": "{count, plural, one {Slett fil} other {Slett {count} filer}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/pt/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"descriptionFile": "Escolha um novo nome para este ficheiro.",
"descriptionFolder": "Escolha um novo nome para esta pasta."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Eliminar Item} other {Eliminar {count} Items.}}",
"titleFolder": "{count, plural, one {Eliminar Pasta} other {Eliminar {count} Pastas}}",
"titleFile": "{count, plural, one {Eliminar Ficheiro} other {Eliminar {count} Ficheiros}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/sk/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"descriptionFile": "Vyberte nové meno pre tento súbor.",
"descriptionFolder": "Vyberte nové meno pre túto zložku."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Zmazať položku} few {Zmazať {count} položky} many {Zmazať {count} položky} other {Zmazať {count} položky}}",
"titleFolder": "{count, plural, one {Zmazať zložku} few {Zmazať {count} zložky} many {Zmazať {count} zložky} other {Zmazať {count} zložky}}",
"titleFile": "{count, plural, one {Zmazať súbor} few {Zmazať {count} súbory} many {Zmazať {count} súbory} other {Zmazať {count} súbory}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/sv/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"descriptionFile": "Välj ett namn för denna fil.",
"descriptionFolder": "Välj ett namn för denna mapp."
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, one {Ta bort föremål} other {Ta bort {count} föremål}}",
"titleFolder": "{count, plural, one {Ta bort mapp} other {Ta bort {count} mappar}}",
"titleFile": "{count, plural, one {Ta bort fil} other {Ta bort {count} filer}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/zh-CN/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "重命名此文件",
"descriptionFolder": "重命名此文件夹"
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, other {删除{count}个项目}}",
"titleFolder": "{count, plural, other {删除{count}个文件夹}}",
"titleFile": "{count, plural, other {删除{count}个文件}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/zh-HK/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "重命名此文件",
"descriptionFolder": "重命名此文件夾"
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, other {移除{count}個項目}}",
"titleFolder": "{count, plural, other {移除{count}個文件夾}}",
"titleFile": "{count, plural, other {移除{count}個文件}}",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/zh-TW/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"descriptionFile": "重命名此文件",
"descriptionFolder": "重命名此文件夾"
},
"deleteModal": {
"removeModal": {
"titleItem": "{count, plural, other {刪除{count} 個項目}}",
"titleFolder": "{count, plural, other {刪除{count}個文件夾}}",
"titleFile": "{count, plural, other {刪除{count}個文件}}",
Expand Down
60 changes: 43 additions & 17 deletions src/bundles/files/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,31 +323,57 @@ const actions = () => ({
/**
* Deletes `files` with provided paths. On completion (success sor fail) will
* trigger `doFilesFetch` to update the state.
* @param {string[]} files
* @param {Object} args
* @param {FileStat[]} args.files
* @param {boolean} args.removeLocally
* @param {boolean} args.removeRemotely
* @param {string[]} args.remoteServices
*/
doFilesDelete: (files) => perform(ACTIONS.DELETE, async (ipfs, { store }) => {
doFilesDelete: ({ files, removeLocally, removeRemotely, remoteServices }) => perform(ACTIONS.DELETE, async (ipfs, { store }) => {
ensureMFS(store)

if (files.length > 0) {
const promises = files
.map(file => ipfs.files.rm(realMfsPath(file), {
if (files.length === 0) return undefined

/**
* Execute function asynchronously in a best-effort fashion.
* We don't want any edge case (like a directory with multiple copies of
* same file) to crash webui, nor want to bother user with false-negatives
* @param {Function} fn
*/
const tryAsync = async fn => { try { await fn() } catch (_) {} }

try {
// try removing from MFS first
await Promise.all(
files.map(async file => ipfs.files.rm(realMfsPath(file.path), {
recursive: true
}))
)

// Pin cleanup only if MFS removal was successful
if (removeRemotely) {
// remote unpin can be slow, so we do this async in best-effort fashion
files.forEach(file => remoteServices.map(async service => tryAsync(() =>
ipfs.pin.remote.rm({ cid: [file.cid], service })
)))
}

try {
await Promise.all(promises)
if (removeLocally) {
// removal of local pin can fail if same CID is present twice,
// this is done in best-effort as well
await Promise.all(files.map(async file => file.pinned && tryAsync(() =>
ipfs.pin.rm(file.cid)
)))
}

const src = files[0]
const path = src.slice(0, src.lastIndexOf('/'))
await store.doUpdateHash(path)
const src = files[0].path
const path = src.slice(0, src.lastIndexOf('/'))
await store.doUpdateHash(path)

return undefined
} finally {
await store.doFilesFetch()
}
return undefined
} finally {
await store.doFilesFetch()
}

return undefined
}),

/**
Expand All @@ -366,7 +392,7 @@ const actions = () => ({
const srcPath = src.startsWith('/') ? src : `/ipfs/${name}`

try {
return ipfs.files.cp(srcPath, dst)
return await ipfs.files.cp(srcPath, dst)
} finally {
await store.doFilesFetch()
}
Expand Down
4 changes: 2 additions & 2 deletions src/bundles/files/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const DEFAULT_STATE = {

export const cliCmdKeys = {
DOWNLOAD_OBJECT_COMMAND: 'downloadObjectCommand',
DELETE_FILE_FROM_IPFS: 'deleteFileFromIpfs',
REMOVE_FILE_FROM_IPFS: 'removeFileFromIpfs',
UPDATE_IPFS_CONFIG: 'updateIpfsConfig',
PIN_OBJECT: 'pinObject',
RENAME_IPFS_OBJECT: 'renameObject',
Expand All @@ -94,7 +94,7 @@ export const cliCommandList = {
/**
* @param {string} filePath
*/
[cliCmdKeys.DELETE_FILE_FROM_IPFS]: (filePath) => `ipfs files rm -r "${filePath}"`,
[cliCmdKeys.REMOVE_FILE_FROM_IPFS]: (filePath) => `ipfs files rm -r "${filePath}"`,
/**
* @param {string} cid
*/
Expand Down
2 changes: 1 addition & 1 deletion src/bundles/notify.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const notify = {
case FILES_ACTIONS.COPY:
return 'filesCopyFailed'
case FILES_ACTIONS.DELETE:
return 'filesDeleteFailed'
return 'filesRemoveFailed'
default:
return 'filesEventFailed'
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ModalBody = ({ className, Icon, title, children, ...props }) => (
</div>
)}

<p className='charcoal-muted fw5'>{title}</p>
<p className='charcoal fw6 truncate'>{title}</p>

{children}
</div>
Expand All @@ -46,7 +46,7 @@ ModalBody.defaultProps = {

export const Modal = ({ onCancel, children, className, ...props }) => {
return (
<div className={`${className} bg-white w-80 shadow-4 sans-serif relative`} style={{ maxWidth: '30em' }} {...props}>
<div className={`${className} bg-white w-80 shadow-4 sans-serif relative`} style={{ maxWidth: '34em' }} {...props}>
{ onCancel &&
<CancelIcon className='absolute pointer w2 h2 top-0 right-0 fill-gray' onClick={onCancel} />
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/text-input-modal/TextInputModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ class TextInputModal extends React.Component {
{ description && typeof description === 'object' && description }

{ description && typeof description === 'string' &&
<p className='gray w-80 center'>{description}</p>
<p className='charcoal w-90 tl center'>{description}</p>
}

<input
onChange={this.onChange}
onKeyPress={this.onKeyPress}
value={this.state.value}
required
className={`input-reset charcoal ba b--black-20 br1 pa2 mb2 db w-75 center focus-outline ${this.inputClass}`}
className={`input-reset charcoal ba b--black-20 br1 pa2 mb2 db w-90 center focus-outline ${this.inputClass}`}
type='text' />
</ModalBody>

Expand Down
6 changes: 3 additions & 3 deletions src/files/FilesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ class FilesPage extends React.Component {
downloadProgress={this.state.downloadProgress}
onShare={(files) => this.showModal(SHARE, files)}
onRename={(files) => this.showModal(RENAME, files)}
onDelete={(files) => this.showModal(DELETE, files)}
onSetPinning={(files) => this.showModal(PINNING, files)}
onRemove={(files) => this.showModal(DELETE, files)}
onInspect={this.onInspect}
onRemotePinClick={this.onRemotePinClick}
onDownload={this.onDownload}
Expand Down Expand Up @@ -233,7 +233,7 @@ class FilesPage extends React.Component {
pinned={contextMenu.file && contextMenu.file.pinned}
cid={contextMenu.file && contextMenu.file.cid}
onShare={() => this.showModal(SHARE, [contextMenu.file])}
onDelete={() => this.showModal(DELETE, [contextMenu.file])}
onRemove={() => this.showModal(DELETE, [contextMenu.file])}
onRename={() => this.showModal(RENAME, [contextMenu.file])}
onInspect={() => this.onInspect(contextMenu.file.cid)}
onDownload={() => this.onDownload([contextMenu.file])}
Expand Down Expand Up @@ -265,7 +265,7 @@ class FilesPage extends React.Component {
onMove={this.props.doFilesMove}
onMakeDir={this.props.doFilesMakeDir}
onShareLink={this.props.doFilesShareLink}
onDelete={this.props.doFilesDelete}
onRemove={this.props.doFilesDelete}
onAddByPath={this.onAddByPath}
onPinningSet={this.props.doSetPinning}
cliOptions={cliOptions}
Expand Down
Loading

0 comments on commit 60585ef

Please sign in to comment.