From 6065c880694ef65eef0dc5fe2bb38d62616be14d Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Mon, 15 Jun 2020 08:08:59 -0400 Subject: [PATCH 01/10] Start v3.0.12 --- electron-app/package.json | 2 +- package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electron-app/package.json b/electron-app/package.json index 7d430d5c..afad2922 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-plus", - "version": "3.0.11", + "version": "3.0.12", "description": "(ClientServer) PostyBirb is an application that helps artists post art and other multimedia to multiple websites more quickly.", "main": "main.js", "author": "Michael DiCarlo", diff --git a/package.json b/package.json index 08e38b09..68f6bf5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-plus", - "version": "3.0.11", + "version": "3.0.12", "description": "PostyBirb is an application that helps artists post art and other multimedia to multiple websites more quickly..", "main": "index.js", "scripts": { diff --git a/ui/package.json b/ui/package.json index 7d585d5b..cfe77cee 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-ui", - "version": "3.0.11", + "version": "3.0.12", "license": "BSD-3-Clause", "private": true, "Author": "Michael DiCarlo", From 1a94a935ffb7f0a9761b1a2350d171f08fb50df6 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Mon, 15 Jun 2020 08:35:22 -0400 Subject: [PATCH 02/10] Feature: Added ability to specify whether to keep titles or not from imports --- .../EditableSubmissions.tsx | 232 +----------------- .../FileSubmissionCreator.tsx | 123 ++++++++++ .../NotificationSubmissionCreator.tsx | 108 ++++++++ .../form-components/ImportDataSelect.tsx | 32 ++- 4 files changed, 261 insertions(+), 234 deletions(-) create mode 100644 ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx create mode 100644 ui/src/views/submissions/editable-submissions/NotificationSubmissionCreator.tsx diff --git a/ui/src/views/submissions/editable-submissions/EditableSubmissions.tsx b/ui/src/views/submissions/editable-submissions/EditableSubmissions.tsx index 1381917b..0795803c 100644 --- a/ui/src/views/submissions/editable-submissions/EditableSubmissions.tsx +++ b/ui/src/views/submissions/editable-submissions/EditableSubmissions.tsx @@ -10,25 +10,11 @@ import { SubmissionType } from '../../../shared/enums/submission-type.enum'; import { Submission } from '../../../../../electron-app/src/submission/interfaces/submission.interface'; import PostService from '../../../services/post.service'; import { submissionStore } from '../../../stores/submission.store'; -import { - Button, - DatePicker, - Input, - List, - message, - Form, - InputNumber, - Icon, - Modal, - Upload -} from 'antd'; +import { Button, DatePicker, Input, List, message, Form, InputNumber } from 'antd'; import { EditableSubmissionListItem } from './EditableSubmissionListItem'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; -import { RcFile } from 'antd/lib/upload'; -import SubmissionTemplateSelect from '../submission-template-select/SubmissionTemplateSelect'; -import { SubmissionPart } from '../../../../../electron-app/src/submission/submission-part/interfaces/submission-part.interface'; -import Axios from 'axios'; -const { Dragger } = Upload; +import { NotificationSubmissionCreator } from './NotificationSubmissionCreator'; +import { FileSubmissionCreator } from './FileSubmissionCreator'; interface Props { submissions: SubmissionPackage[]; @@ -291,215 +277,3 @@ export class EditableSubmissions extends React.Component { ); } } - -interface FileSubmissionCreateState { - canCopyClipboard: boolean; - importUrl: string; -} - -class FileSubmissionCreator extends React.Component { - state: FileSubmissionCreateState = { - canCopyClipboard: window.electron.clipboard.availableFormats().includes('image/png'), - importUrl: '' - }; - - private clipboardCheckInterval: any; - private uploadProps = { - name: 'file', - multiple: true, - showUploadList: false, - beforeUpload: (file: RcFile) => { - SubmissionService.create({ - type: SubmissionType.FILE, - file: file, - path: file['path'] - }) - .then(() => { - message.success(`${file.name} file uploaded successfully.`); - }) - .catch(() => { - message.error(`${file.name} file upload failed.`); - }); - return Promise.reject(); // don't want to upload using component method - } - }; - - constructor(props: any) { - super(props); - this.clipboardCheckInterval = setInterval(() => { - if (window.electron.clipboard.availableFormats().includes('image/png')) { - if (!this.state.canCopyClipboard) { - this.setState({ canCopyClipboard: true }); - } - } else if (this.state.canCopyClipboard) { - this.setState({ canCopyClipboard: false }); - } - }, 2000); - } - - componentWillUnmount() { - clearInterval(this.clipboardCheckInterval); - } - - createFromClipboard() { - SubmissionService.createFromClipboard() - .then(() => message.success('Submission created.')) - .catch(() => message.error('Failed to create submission.')); - } - - async createFromImportURL() { - const importUrl = this.state.importUrl.trim(); - if (importUrl.length) { - try { - const filename = importUrl.split('/').pop() || 'import'; - const res = await Axios.get(importUrl, { responseType: 'arraybuffer' }); - const blob: Blob = new Blob([res.data], { type: res.headers['content-type'] }); - const file: File = new File([blob], filename, { - type: res.headers['content-type'] - }); - SubmissionService.create({ - type: SubmissionType.FILE, - title: filename, - file - }) - .then(() => { - message.success('Image imported.'); - }) - .catch(() => { - message.error('Unable to load file for import.'); - }); - } catch (err) { - message.error('Unable to load file for import.'); - } - } - } - - render() { - return ( -
- -

- -

-

- Click or drag file to this area to create a submission -

-
-
- -
-
- this.setState({ importUrl: e.target.value })} - /> - -
-
- ); - } -} - -interface NotificationSubmissionCreateState { - modalVisible: boolean; - value: string; - parts?: Record>; -} - -class NotificationSubmissionCreator extends React.Component< - any, - NotificationSubmissionCreateState -> { - state: NotificationSubmissionCreateState = { - modalVisible: false, - value: '', - parts: undefined - }; - - createSubmission() { - if (this.isValid()) { - SubmissionService.create({ - type: SubmissionType.NOTIFICATION, - title: this.state.value, - parts: this.state.parts ? JSON.stringify(Object.values(this.state.parts)) : undefined - }) - .then(() => message.success('Submission created.')) - .catch(() => message.error('Failed to create submission.')); - this.hideModal(); - } - } - - hideModal() { - this.setState({ modalVisible: false }); - } - - showModal() { - this.setState({ modalVisible: true, value: '' }); - } - - onNameChange({ target }) { - this.setState({ value: target.value }); - } - - isValid(): boolean { - return !!this.state.value && !!this.state.value.trim().length; - } - - render() { - return ( -
- - -
{ - e.preventDefault(); - this.createSubmission(); - }} - > - - - - this.setState({ parts: undefined })} - onSelect={(id, type, parts) => this.setState({ parts })} - /> - -
-
- ); - } -} diff --git a/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx new file mode 100644 index 00000000..4140651b --- /dev/null +++ b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import SubmissionService from '../../../services/submission.service'; +import { SubmissionType } from '../../../shared/enums/submission-type.enum'; +import { Button, Input, message, Icon, Upload } from 'antd'; +import { RcFile } from 'antd/lib/upload'; +import Axios from 'axios'; +const { Dragger } = Upload; + +interface FileSubmissionCreateState { + canCopyClipboard: boolean; + importUrl: string; +} + +export class FileSubmissionCreator extends React.Component { + state: FileSubmissionCreateState = { + canCopyClipboard: window.electron.clipboard.availableFormats().includes('image/png'), + importUrl: '' + }; + private clipboardCheckInterval: any; + private uploadProps = { + name: 'file', + multiple: true, + showUploadList: false, + beforeUpload: (file: RcFile) => { + SubmissionService.create({ + type: SubmissionType.FILE, + file: file, + path: file['path'] + }) + .then(() => { + message.success(`${file.name} file uploaded successfully.`); + }) + .catch(() => { + message.error(`${file.name} file upload failed.`); + }); + return Promise.reject(); // don't want to upload using component method + } + }; + constructor(props: any) { + super(props); + this.clipboardCheckInterval = setInterval(() => { + if (window.electron.clipboard.availableFormats().includes('image/png')) { + if (!this.state.canCopyClipboard) { + this.setState({ canCopyClipboard: true }); + } + } else if (this.state.canCopyClipboard) { + this.setState({ canCopyClipboard: false }); + } + }, 2000); + } + componentWillUnmount() { + clearInterval(this.clipboardCheckInterval); + } + createFromClipboard() { + SubmissionService.createFromClipboard() + .then(() => message.success('Submission created.')) + .catch(() => message.error('Failed to create submission.')); + } + async createFromImportURL() { + const importUrl = this.state.importUrl.trim(); + if (importUrl.length) { + try { + const filename = importUrl.split('/').pop() || 'import'; + const res = await Axios.get(importUrl, { responseType: 'arraybuffer' }); + const blob: Blob = new Blob([res.data], { type: res.headers['content-type'] }); + const file: File = new File([blob], filename, { + type: res.headers['content-type'] + }); + SubmissionService.create({ + type: SubmissionType.FILE, + title: filename, + file + }) + .then(() => { + message.success('Image imported.'); + }) + .catch(() => { + message.error('Unable to load file for import.'); + }); + } catch (err) { + message.error('Unable to load file for import.'); + } + } + } + render() { + return ( +
+ +

+ +

+

Click or drag file to this area to create a submission

+
+
+ +
+
+ this.setState({ importUrl: e.target.value })} + /> + +
+
+ ); + } +} diff --git a/ui/src/views/submissions/editable-submissions/NotificationSubmissionCreator.tsx b/ui/src/views/submissions/editable-submissions/NotificationSubmissionCreator.tsx new file mode 100644 index 00000000..c18f3717 --- /dev/null +++ b/ui/src/views/submissions/editable-submissions/NotificationSubmissionCreator.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import SubmissionService from '../../../services/submission.service'; +import { SubmissionType } from '../../../shared/enums/submission-type.enum'; +import { Button, Input, message, Form, Modal, Checkbox } from 'antd'; +import SubmissionTemplateSelect from '../submission-template-select/SubmissionTemplateSelect'; +import { SubmissionPart } from '../../../../../electron-app/src/submission/submission-part/interfaces/submission-part.interface'; +import * as _ from 'lodash'; + +interface NotificationSubmissionCreateState { + keepTemplateTitle: boolean; + modalVisible: boolean; + value: string; + parts?: Record>; +} + +export class NotificationSubmissionCreator extends React.Component< + any, + NotificationSubmissionCreateState +> { + state: NotificationSubmissionCreateState = { + keepTemplateTitle: false, + modalVisible: false, + value: '', + parts: undefined + }; + + createSubmission() { + if (this.isValid()) { + const sanitizedParts = _.cloneDeep(this.state.parts); + if (sanitizedParts && !this.state.keepTemplateTitle) { + Object.values(sanitizedParts).forEach(part => { + delete part.data.title; + }); + } + SubmissionService.create({ + type: SubmissionType.NOTIFICATION, + title: this.state.value, + parts: sanitizedParts ? JSON.stringify(Object.values(sanitizedParts)) : undefined + }) + .then(() => message.success('Submission created.')) + .catch(() => message.error('Failed to create submission.')); + this.hideModal(); + } + } + hideModal() { + this.setState({ modalVisible: false, value: '', parts: undefined, keepTemplateTitle: false }); + } + showModal() { + this.setState({ modalVisible: true }); + } + onNameChange({ target }) { + this.setState({ value: target.value }); + } + isValid(): boolean { + return !!this.state.value && !!this.state.value.trim().length; + } + render() { + return ( +
+ + +
{ + e.preventDefault(); + this.createSubmission(); + }} + > + + + + this.setState({ parts: undefined })} + onSelect={(id, type, parts) => this.setState({ parts })} + /> + {this.state.parts ? ( + + this.setState({ keepTemplateTitle: e.target.checked })} + > + Use title from template + + + ) : null} + +
+
+ ); + } +} diff --git a/ui/src/views/submissions/submission-forms/form-components/ImportDataSelect.tsx b/ui/src/views/submissions/submission-forms/form-components/ImportDataSelect.tsx index 889f1ab8..0cad5461 100644 --- a/ui/src/views/submissions/submission-forms/form-components/ImportDataSelect.tsx +++ b/ui/src/views/submissions/submission-forms/form-components/ImportDataSelect.tsx @@ -2,7 +2,7 @@ import React from 'react'; import _ from 'lodash'; import { inject, observer } from 'mobx-react'; import { LoginStatusStore } from '../../../../stores/login-status.store'; -import { Modal, Button, Form, TreeSelect } from 'antd'; +import { Modal, Button, Form, TreeSelect, Checkbox } from 'antd'; import { SubmissionPart } from '../../../../../../electron-app/src/submission/submission-part/interfaces/submission-part.interface'; import { TreeNode } from 'antd/lib/tree-select'; import { WebsiteRegistry } from '../../../../websites/website-registry'; @@ -19,6 +19,7 @@ interface Props { } interface State { + keepTemplateTitle: boolean; modalOpen: boolean; selected?: { [key: string]: SubmissionPart }; selectedFields: string[]; @@ -28,13 +29,19 @@ interface State { @observer export default class ImportDataSelect extends React.Component { state: State = { + keepTemplateTitle: false, modalOpen: false, selected: undefined, selectedFields: [] }; hideModal = () => { - this.setState({ modalOpen: false, selected: undefined, selectedFields: [] }); + this.setState({ + modalOpen: false, + selected: undefined, + selectedFields: [], + keepTemplateTitle: false + }); }; showModal = () => this.setState({ modalOpen: true }); @@ -42,9 +49,14 @@ export default class ImportDataSelect extends React.Component { handleComplete = () => { this.props.onPropsSelect( _.cloneDeep( - Object.values(this.state.selected!).filter(p => - this.state.selectedFields.includes(p.accountId) - ) + Object.values(this.state.selected!) + .filter(p => this.state.selectedFields.includes(p.accountId)) + .map(p => { + if (!this.state.keepTemplateTitle) { + delete p.data.title; + } + return p; + }) ) ); this.hideModal(); @@ -118,6 +130,16 @@ export default class ImportDataSelect extends React.Component { value={this.state.selectedFields} /> + {this.state.selected ? ( + + this.setState({ keepTemplateTitle: e.target.checked })} + > + Use title from import + + + ) : null} From 1df0cc3e3a208e3205834eec94ce62e27d3e82d0 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Mon, 15 Jun 2020 09:04:50 -0400 Subject: [PATCH 03/10] UI: Added ErrorBoundary that should at least keep the entire UI from crashing when errors happen in routes --- ui/src/components/ErrorBoundary.tsx | 36 +++++++++++++++++++ ui/src/views/app-layout/AppLayout.tsx | 36 +++++++++++-------- .../forms/MultiSubmissionEditForm.tsx | 18 ---------- .../forms/SubmissionEditForm.tsx | 19 ---------- .../forms/SubmissionTemplateEditForm.tsx | 32 ++--------------- 5 files changed, 59 insertions(+), 82 deletions(-) create mode 100644 ui/src/components/ErrorBoundary.tsx diff --git a/ui/src/components/ErrorBoundary.tsx b/ui/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..ec539b28 --- /dev/null +++ b/ui/src/components/ErrorBoundary.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +interface State { + hasError: boolean; + error?: Error; +} + +export default class ErrorBoundary extends React.Component { + state: State = { + hasError: false, + error: undefined + }; + + static getDerivedStateFromError(error) { + console.error(error); + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error(error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong.

+ {this.state.error!.toString()} +
+ {this.state.error!.stack || ''} +
+ ); + } + return this.props.children; + } +} diff --git a/ui/src/views/app-layout/AppLayout.tsx b/ui/src/views/app-layout/AppLayout.tsx index 00d9b0d0..9cc88f33 100644 --- a/ui/src/views/app-layout/AppLayout.tsx +++ b/ui/src/views/app-layout/AppLayout.tsx @@ -34,6 +34,7 @@ import { Tabs, message } from 'antd'; +import ErrorBoundary from '../../components/ErrorBoundary'; const { Content, Sider } = Layout; @@ -385,21 +386,26 @@ export default class AppLayout extends React.Component {
- - - - - - - - document.getElementById('primary-container') || window} /> - + + + + + + + + + document.getElementById('primary-container') || window} /> + +
diff --git a/ui/src/views/submissions/submission-forms/forms/MultiSubmissionEditForm.tsx b/ui/src/views/submissions/submission-forms/forms/MultiSubmissionEditForm.tsx index 1e6ed261..0221166c 100644 --- a/ui/src/views/submissions/submission-forms/forms/MultiSubmissionEditForm.tsx +++ b/ui/src/views/submissions/submission-forms/forms/MultiSubmissionEditForm.tsx @@ -41,7 +41,6 @@ export interface MultiSubmissionEditFormState { loading: boolean; touched: boolean; removedParts: string[]; - hasError: boolean; saveVisible: boolean; } @@ -79,7 +78,6 @@ class MultiSubmissionEditForm extends React.Component - - - ); - } - if (!this.state.loading) { this.removeDeletedAccountParts(); uiStore.setPendingChanges(this.formHasChanges()); diff --git a/ui/src/views/submissions/submission-forms/forms/SubmissionEditForm.tsx b/ui/src/views/submissions/submission-forms/forms/SubmissionEditForm.tsx index 558469c1..4bd02192 100644 --- a/ui/src/views/submissions/submission-forms/forms/SubmissionEditForm.tsx +++ b/ui/src/views/submissions/submission-forms/forms/SubmissionEditForm.tsx @@ -61,7 +61,6 @@ export interface SubmissionEditFormState { submission?: Submission; submissionType: SubmissionType; touched: boolean; - hasError: boolean; showThumbnailCropper: boolean; thumbnailFileForCrop?: File; imageCropperResolve?: (file: File) => void; @@ -88,7 +87,6 @@ class SubmissionEditForm extends React.Component }; state: SubmissionEditFormState = { - hasError: false, loading: true, parts: {}, postAt: undefined, @@ -530,28 +528,11 @@ class SubmissionEditForm extends React.Component }); } - static getDerivedStateFromError(error: Error) { - console.error(error); - alert(`${error.message}\n\n${error.stack}`); - return { hasError: true }; - } - isFileSubmission(submission: Submission): submission is FileSubmission { return submission.type === SubmissionType.FILE; } render() { - if (this.state.hasError) { - return ( -
- -
- ); - } - if (!this.state.loading) { this.removeDeletedAccountParts(); uiStore.setPendingChanges(this.formHasChanges()); diff --git a/ui/src/views/submissions/submission-forms/forms/SubmissionTemplateEditForm.tsx b/ui/src/views/submissions/submission-forms/forms/SubmissionTemplateEditForm.tsx index 35a16951..c18553f3 100644 --- a/ui/src/views/submissions/submission-forms/forms/SubmissionTemplateEditForm.tsx +++ b/ui/src/views/submissions/submission-forms/forms/SubmissionTemplateEditForm.tsx @@ -16,17 +16,7 @@ import { SubmissionType } from '../../../../shared/enums/submission-type.enum'; import { SubmissionTemplate } from '../../../../../../electron-app/src/submission/submission-template/interfaces/submission-template.interface'; import SubmissionTemplateService from '../../../../services/submission-template.service'; import { DefaultOptions } from '../../../../../../electron-app/src/submission/submission-part/interfaces/default-options.interface'; -import { - Form, - Button, - Typography, - Spin, - message, - TreeSelect, - Anchor, - Popconfirm, - Alert -} from 'antd'; +import { Form, Button, Typography, Spin, message, TreeSelect, Anchor, Popconfirm } from 'antd'; interface Props { match: Match; @@ -40,7 +30,6 @@ export interface SubmissionTemplateEditFormState { loading: boolean; touched: boolean; removedParts: string[]; - hasError: boolean; } @inject('loginStatusStore') @@ -67,8 +56,7 @@ class SubmissionTemplateEditForm extends React.Component - - - ); - } - if (!this.state.loading) { this.removeDeletedAccountParts(); uiStore.setPendingChanges(this.formHasChanges()); From 3b9b0f135e6d3cf34ef3019a8c269dbf62db2ce4 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Thu, 25 Jun 2020 07:51:38 -0400 Subject: [PATCH 04/10] Update: Observe file order from UI submission creation --- .../FileSubmissionCreator.tsx | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx index 4140651b..141a7bed 100644 --- a/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx +++ b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import * as _ from 'lodash'; import SubmissionService from '../../../services/submission.service'; import { SubmissionType } from '../../../shared/enums/submission-type.enum'; import { Button, Input, message, Icon, Upload } from 'antd'; @@ -16,26 +17,18 @@ export class FileSubmissionCreator extends React.Component { - SubmissionService.create({ - type: SubmissionType.FILE, - file: file, - path: file['path'] - }) - .then(() => { - message.success(`${file.name} file uploaded successfully.`); - }) - .catch(() => { - message.error(`${file.name} file upload failed.`); - }); + beforeUpload: (file: RcFile, list: RcFile[]) => { + this.performUpload(list); return Promise.reject(); // don't want to upload using component method } }; + constructor(props: any) { super(props); this.clipboardCheckInterval = setInterval(() => { @@ -48,14 +41,39 @@ export class FileSubmissionCreator extends React.Component message.success('Submission created.')) .catch(() => message.error('Failed to create submission.')); } + + performUpload = _.debounce(async (files: RcFile[]) => { + const isPoster: boolean = this.uploadQueue.length === 0; + if (files) { + this.uploadQueue.push(...files.filter(f => !this.uploadQueue.includes(f))); + } + if (isPoster) { + let file: RcFile | undefined = undefined; + while ((file = this.uploadQueue.shift()) !== undefined) { + try { + await SubmissionService.create({ + type: SubmissionType.FILE, + file: file, + path: file['path'] + }); + message.success(`${file!.name} file uploaded successfully.`); + } catch { + message.error(`${file!.name} file upload failed.`); + } + } + } + }, 100); + async createFromImportURL() { const importUrl = this.state.importUrl.trim(); if (importUrl.length) { @@ -82,6 +100,7 @@ export class FileSubmissionCreator extends React.Component From 579d2b3b9d03a8e87057f10ffe9a54900ef8ca97 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Thu, 25 Jun 2020 08:54:14 -0400 Subject: [PATCH 05/10] Fix: Submission reordering should work in a more predictable manner now --- .../src/submission/submission.service.ts | 25 +++++----------- ui/src/stores/submission.store.ts | 30 ++++++++----------- .../FileSubmissionCreator.tsx | 2 +- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/electron-app/src/submission/submission.service.ts b/electron-app/src/submission/submission.service.ts index 4aa1ba60..333ccdbf 100644 --- a/electron-app/src/submission/submission.service.ts +++ b/electron-app/src/submission/submission.service.ts @@ -390,24 +390,15 @@ export class SubmissionService { const submissions = (await this.getAll(movingSubmission.type)).sort( (a, b) => a.order - b.order, ); - while (from < 0) { - from += submissions.length; - } - while (to < 0) { - to += submissions.length; - } - if (to >= submissions.length) { - let k = to - submissions.length + 1; - while (k--) { - submissions.push(undefined); - } - } - submissions.splice(to, 0, submissions.splice(from, 1)[0]); + const fromSubmission = submissions.find(s => s._id === id); + fromSubmission.order = from < to ? to + 0.1 : to - 0.1; await Promise.all( - submissions.map((record, index) => { - record.order = index; - return this.repository.update(record); - }), + submissions + .sort((a, b) => a.order - b.order) + .map((record, index) => { + record.order = index; + return this.repository.update(record); + }), ); this.orderSubmissions(movingSubmission.type); // somewhat doubles up, but ensures all UI get notified } diff --git a/ui/src/stores/submission.store.ts b/ui/src/stores/submission.store.ts index 749ed069..3f1092d5 100644 --- a/ui/src/stores/submission.store.ts +++ b/ui/src/stores/submission.store.ts @@ -98,7 +98,10 @@ export class SubmissionStore { @action updateOrder(orderRecords: Record) { this.state.submissions.forEach(record => { - if (orderRecords[record.submission._id] !== undefined) { + if ( + orderRecords[record.submission._id] && + record.submission.order !== orderRecords[record.submission._id] + ) { record.submission.order = orderRecords[record.submission._id]; } }); @@ -119,25 +122,16 @@ export class SubmissionStore { @action changeOrder(id: string, from: number, to: number) { const fromSubmission = this.state.submissions.find(s => s.submission._id === id)!; - const submissions: any[] = this.all.filter( + const submissions: SubmissionPackage[] = this.all.filter( s => s.submission.type === fromSubmission.submission.type ); - while (from < 0) { - from += submissions.length; - } - while (to < 0) { - to += submissions.length; - } - if (to >= submissions.length) { - let k = to - submissions.length + 1; - while (k--) { - submissions.push(undefined); - } - } - submissions.splice(to, 0, submissions.splice(from, 1)[0]); - submissions.forEach((record, index) => { - record.submission.order = index; - }); + fromSubmission.submission.order = from < to ? to + 0.1 : to - 0.1; + submissions + .sort((a, b) => a.submission.order - b.submission.order) + .forEach((record, index) => { + record.submission.order = index; + }); + SubmissionService.changeOrder(id, to, from); } } diff --git a/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx index 141a7bed..f090191f 100644 --- a/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx +++ b/ui/src/views/submissions/editable-submissions/FileSubmissionCreator.tsx @@ -25,7 +25,7 @@ export class FileSubmissionCreator extends React.Component { this.performUpload(list); - return Promise.reject(); // don't want to upload using component method + return false; // don't want to upload using component method } }; From eb43c5f971ce9c94acfdec164791308e7dbc34e0 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Thu, 25 Jun 2020 08:57:31 -0400 Subject: [PATCH 06/10] Fix: Ko-fi login detection --- electron-app/src/websites/ko-fi/ko-fi.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron-app/src/websites/ko-fi/ko-fi.service.ts b/electron-app/src/websites/ko-fi/ko-fi.service.ts index 4791104f..74fefe0d 100644 --- a/electron-app/src/websites/ko-fi/ko-fi.service.ts +++ b/electron-app/src/websites/ko-fi/ko-fi.service.ts @@ -33,7 +33,7 @@ export class KoFi extends Website { async checkLoginStatus(data: UserAccountEntity): Promise { const status: LoginResponse = { loggedIn: false, username: null }; const res = await Http.get(`${this.BASE_URL}/settings`, data._id); - if (!res.body.includes('Start a Page')) { + if (!res.body.includes('btn-login')) { status.loggedIn = true; status.username = HtmlParserUtil.getInputValue(res.body, 'DisplayName'); this.storeAccountInformation(data._id, 'id', res.body.match(/pageId:\s'(.*?)'/)[1]); From e0f67d36056804fd48a26481c90f0a8186ee7b9b Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Mon, 29 Jun 2020 11:23:39 -0400 Subject: [PATCH 07/10] Log: Attempt to log FurryNetwork chunk upload failure better --- .../src/websites/furry-network/furry-network.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron-app/src/websites/furry-network/furry-network.service.ts b/electron-app/src/websites/furry-network/furry-network.service.ts index ecd8d1b1..d94dc8bb 100644 --- a/electron-app/src/websites/furry-network/furry-network.service.ts +++ b/electron-app/src/websites/furry-network/furry-network.service.ts @@ -219,8 +219,8 @@ export class FurryNetwork extends Website { ); const res = responses.shift(); - if (!res.body.id) { - throw this.createPostResponse({ additionalInfo: res.body }); + if (!res.body || !res.body.id) { + throw this.createPostResponse({ additionalInfo: res.body || res }); } return res.body; } From d635a541f9f7ecbd861863a6e7bfce5536cdcf7d Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Tue, 30 Jun 2020 14:14:48 -0400 Subject: [PATCH 08/10] Tweak: Updated package names Added new commons package to move common modules into --- commons/.gitignore | 1 + commons/.prettierrc | 5 + commons/package-lock.json | 326 ++++++++++++++++++++++++++++++++++++++ commons/package.json | 21 +++ commons/src/index.ts | 0 commons/tsconfig.json | 17 ++ commons/tslint.json | 18 +++ electron-app/package.json | 2 +- ui/package.json | 2 +- 9 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 commons/.gitignore create mode 100644 commons/.prettierrc create mode 100644 commons/package-lock.json create mode 100644 commons/package.json create mode 100644 commons/src/index.ts create mode 100644 commons/tsconfig.json create mode 100644 commons/tslint.json diff --git a/commons/.gitignore b/commons/.gitignore new file mode 100644 index 00000000..77738287 --- /dev/null +++ b/commons/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/commons/.prettierrc b/commons/.prettierrc new file mode 100644 index 00000000..63bac4c4 --- /dev/null +++ b/commons/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100 +} \ No newline at end of file diff --git a/commons/package-lock.json b/commons/package-lock.json new file mode 100644 index 00000000..ae95fed8 --- /dev/null +++ b/commons/package-lock.json @@ -0,0 +1,326 @@ +{ + "name": "postybirb-commons", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.2.tgz", + "integrity": "sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.10.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/commons/package.json b/commons/package.json new file mode 100644 index 00000000..56803300 --- /dev/null +++ b/commons/package.json @@ -0,0 +1,21 @@ +{ + "name": "postybirb-plus-commons", + "version": "1.0.0", + "description": "Common classes, interfaces, enums, etc. for PostyBirb that would be shared between UI and Electron-App", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "rimraf dist", + "lint": "tslint -p tsconfig.json -c tslint.json", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"" + }, + "author": "Michael DiCarlo", + "license": "ISC", + "devDependencies": { + "prettier": "^2.0.5", + "rimraf": "^3.0.2", + "tslint": "^6.1.2", + "typescript": "^3.9.5" + } +} diff --git a/commons/src/index.ts b/commons/src/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/commons/tsconfig.json b/commons/tsconfig.json new file mode 100644 index 00000000..be6bea2a --- /dev/null +++ b/commons/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": true, + "outDir": "./dist", + "skipLibCheck": true, + "strict": true, + "target": "es2017", + }, + "include": ["./src"], + "exclude": ["node_modules", "dist"] +} diff --git a/commons/tslint.json b/commons/tslint.json new file mode 100644 index 00000000..5651b2f3 --- /dev/null +++ b/commons/tslint.json @@ -0,0 +1,18 @@ +{ + "defaultSeverity": "error", + "extends": ["tslint:recommended"], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "quotemark": [true, "single"], + "member-access": [false], + "ordered-imports": [false], + "max-line-length": [true, 150], + "member-ordering": [false], + "interface-name": [false], + "arrow-parens": false, + "object-literal-sort-keys": false + }, + "rulesDirectory": [] +} diff --git a/electron-app/package.json b/electron-app/package.json index afad2922..9abeaf88 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,5 +1,5 @@ { - "name": "postybirb-plus", + "name": "postybirb-plus-client-server", "version": "3.0.12", "description": "(ClientServer) PostyBirb is an application that helps artists post art and other multimedia to multiple websites more quickly.", "main": "main.js", diff --git a/ui/package.json b/ui/package.json index cfe77cee..b818669b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,5 +1,5 @@ { - "name": "postybirb-ui", + "name": "postybirb-plus-ui", "version": "3.0.12", "license": "BSD-3-Clause", "private": true, From 6f0ea5e3a2bdf035667a8f058e64a5b94b0f9274 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Wed, 1 Jul 2020 08:51:32 -0400 Subject: [PATCH 09/10] SubscribeStar: Users can now select custom access tiers --- electron-app/src/utils/browser-window.util.ts | 4 +- .../subscribe-star.controller.ts | 10 +++ .../subscribe-star/subscribe-star.module.ts | 2 + .../subscribe-star/subscribe-star.service.ts | 81 ++++++++++++++++++- .../websites/subscribe-star/SubscribeStar.tsx | 54 ++++++++++++- ui/src/websites/weasyl/Weasyl.tsx | 4 +- 6 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 electron-app/src/websites/subscribe-star/subscribe-star.controller.ts diff --git a/electron-app/src/utils/browser-window.util.ts b/electron-app/src/utils/browser-window.util.ts index 0f2af12a..5c661483 100644 --- a/electron-app/src/utils/browser-window.util.ts +++ b/electron-app/src/utils/browser-window.util.ts @@ -48,8 +48,8 @@ export default class BrowserWindowUtil { } } - public static async getPage(partition: string, url: string): Promise { - return BrowserWindowUtil.runScriptOnPage(partition, url, 'document.body.innerText'); + public static async getPage(partition: string, url: string, html: boolean = false): Promise { + return BrowserWindowUtil.runScriptOnPage(partition, url, html ? 'document.body.innerHTML' : 'document.body.innerText'); } public static async runScriptOnPage( diff --git a/electron-app/src/websites/subscribe-star/subscribe-star.controller.ts b/electron-app/src/websites/subscribe-star/subscribe-star.controller.ts new file mode 100644 index 00000000..8e910437 --- /dev/null +++ b/electron-app/src/websites/subscribe-star/subscribe-star.controller.ts @@ -0,0 +1,10 @@ +import { Controller } from '@nestjs/common'; +import { SubscribeStar } from './subscribe-star.service'; +import { GenericWebsiteController } from '../generic/generic.controller'; + +@Controller('subscribestar') +export class SubscribeStarController extends GenericWebsiteController { + constructor(readonly service: SubscribeStar) { + super(service); + } +} diff --git a/electron-app/src/websites/subscribe-star/subscribe-star.module.ts b/electron-app/src/websites/subscribe-star/subscribe-star.module.ts index e1726954..00e0e1e0 100644 --- a/electron-app/src/websites/subscribe-star/subscribe-star.module.ts +++ b/electron-app/src/websites/subscribe-star/subscribe-star.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { SubscribeStar } from './subscribe-star.service'; +import { SubscribeStarController } from './subscribe-star.controller'; @Module({ providers: [SubscribeStar], exports: [SubscribeStar], + controllers: [SubscribeStarController], }) export class SubscribeStarModule {} diff --git a/electron-app/src/websites/subscribe-star/subscribe-star.service.ts b/electron-app/src/websites/subscribe-star/subscribe-star.service.ts index e53c00f1..231054e8 100644 --- a/electron-app/src/websites/subscribe-star/subscribe-star.service.ts +++ b/electron-app/src/websites/subscribe-star/subscribe-star.service.ts @@ -24,7 +24,14 @@ import { SubscribeStarDefaultFileOptions, SubscribeStarDefaultNotificationOptions, } from './subscribe-star.defaults'; -import { SubscribeStarFileOptions } from './subscribe-star.interface'; +import { + SubscribeStarFileOptions, + SubscribeStarNotificationOptions, +} from './subscribe-star.interface'; +import { Folder } from '../interfaces/folder.interface'; +import BrowserWindowUtil from 'src/utils/browser-window.util'; +import { GenericAccountProp } from '../generic/generic-account-props.enum'; +import _ = require('lodash'); @Injectable() export class SubscribeStar extends Website { @@ -52,10 +59,36 @@ export class SubscribeStar extends Website { 'username', res.body.match(/class="top_bar-branding">(.*?)href="(.*?)"/ims)[2], ); + await this.getTiers(data._id); } return status; } + private async getTiers(profileId: string) { + const tiers: Folder[] = [ + { + label: 'Public', + value: 'free', + }, + { + label: 'Subscribers Only', + value: 'basic', + }, + ]; + + const body = await BrowserWindowUtil.getPage(profileId, `${this.BASE_URL}/profile/settings`, true); + const $ = cheerio.load(body); + $('.tiers-settings_item').each((i, el) => { + const $el = $(el); + tiers.push({ + label: $el.find('.tiers-settings_item-title').text(), + value: $el.attr('data-id'), + }); + }); + + this.storeAccountInformation(profileId, GenericAccountProp.FOLDERS, tiers); + } + getScalingOptions(file: FileRecord): ScalingOptions { return { maxSize: FileSize.MBtoBytes(5) }; } @@ -226,6 +259,17 @@ export class SubscribeStar extends Website { problems.push('No access tier selected.'); } + if (submissionPart.data.tier) { + const folders: Folder[] = _.get( + this.accountInformation.get(submissionPart.accountId), + GenericAccountProp.FOLDERS, + [], + ); + if (!folders.find(f => f.value === submissionPart.data.tier)) { + warnings.push(`Access Tier (${submissionPart.data.tier}) not found.`); + } + } + const files = [ submission.primary, ...(submission.additional || []).filter( @@ -236,9 +280,13 @@ export class SubscribeStar extends Website { files.forEach(file => { const { type, size, name, mimetype } = file; let maxMB = 5; - if (type === FileSubmissionType.AUDIO) maxMB = 50; - else if (type === FileSubmissionType.TEXT) maxMB = 300; - else if (type === FileSubmissionType.VIDEO) maxMB = 250; + if (type === FileSubmissionType.AUDIO) { + maxMB = 50; + } else if (type === FileSubmissionType.TEXT) { + maxMB = 300; + } else if (type === FileSubmissionType.VIDEO) { + maxMB = 250; + } if (FileSize.MBtoBytes(maxMB) < size) { if ( @@ -255,4 +303,29 @@ export class SubscribeStar extends Website { return { problems, warnings }; } + + validateNotificationSubmission( + submission: Submission, + submissionPart: SubmissionPart, + defaultPart: SubmissionPart, + ): ValidationParts { + const problems: string[] = []; + const warnings: string[] = []; + + if (!submissionPart.data.tier) { + problems.push('No access tier selected.'); + } + + if (submissionPart.data.tier) { + const folders: Folder[] = _.get( + this.accountInformation.get(submissionPart.accountId), + GenericAccountProp.FOLDERS, + [], + ); + if (!folders.find(f => f.value === submissionPart.data.tier)) { + warnings.push(`Access Tier (${submissionPart.data.tier}) not found.`); + } + } + return { problems, warnings }; + } } diff --git a/ui/src/websites/subscribe-star/SubscribeStar.tsx b/ui/src/websites/subscribe-star/SubscribeStar.tsx index bc9e8f99..feceb610 100644 --- a/ui/src/websites/subscribe-star/SubscribeStar.tsx +++ b/ui/src/websites/subscribe-star/SubscribeStar.tsx @@ -16,6 +16,8 @@ import { GenericSelectProps } from '../generic/GenericSelectProps'; import { SubmissionType } from '../../shared/enums/submission-type.enum'; import { GenericDefaultNotificationOptions } from '../../shared/objects/generic-default-notification-options'; import { GenericDefaultFileOptions } from '../../shared/objects/generic-default-file-options'; +import { Folder } from '../../../../electron-app/src/websites/interfaces/folder.interface'; +import WebsiteService from '../../services/website.service'; const defaultFileOptions: SubscribeStarFileOptions = { ...GenericDefaultFileOptions, @@ -73,9 +75,32 @@ export class SubscribeStar implements Website { } } +interface SubscribeStarSubmissionState { + tiers: Folder[]; +} + export class SubscribeStarNotificationSubmissionForm extends GenericSubmissionSection< SubscribeStarNotificationOptions > { + state: SubscribeStarSubmissionState = { + tiers: [] + }; + + constructor(props: WebsiteSectionProps) { + super(props); + this.state = { + tiers: [] + }; + + WebsiteService.getAccountFolders(this.props.part.website, this.props.part.accountId).then( + ({ data }) => { + if (data) { + this.setState({ tiers: data }); + } + } + ); + } + renderLeftForm(data: SubscribeStarNotificationOptions) { const elements = super.renderLeftForm(data); elements.push( @@ -86,8 +111,9 @@ export class SubscribeStarNotificationSubmissionForm extends GenericSubmissionSe value={data.tier} onSelect={this.setValue.bind(this, 'tier')} > - Free - Subscribers Only + {this.state.tiers.map(tier => ( + {tier.label} + ))} ); @@ -98,6 +124,25 @@ export class SubscribeStarNotificationSubmissionForm extends GenericSubmissionSe export class SubscribeStarFileSubmissionForm extends GenericFileSubmissionSection< SubscribeStarFileOptions > { + state: SubscribeStarSubmissionState = { + tiers: [] + }; + + constructor(props: WebsiteSectionProps) { + super(props); + this.state = { + tiers: [] + }; + + WebsiteService.getAccountFolders(this.props.part.website, this.props.part.accountId).then( + ({ data }) => { + if (data) { + this.setState({ tiers: data }); + } + } + ); + } + renderRightForm(data: SubscribeStarFileOptions) { const elements = super.renderRightForm(data); elements.push( @@ -108,8 +153,9 @@ export class SubscribeStarFileSubmissionForm extends GenericFileSubmissionSectio value={data.tier} onSelect={this.setValue.bind(this, 'tier')} > - Free - Subscribers Only + {this.state.tiers.map(tier => ( + {tier.label} + ))} ); diff --git a/ui/src/websites/weasyl/Weasyl.tsx b/ui/src/websites/weasyl/Weasyl.tsx index 637feb89..e8484c18 100644 --- a/ui/src/websites/weasyl/Weasyl.tsx +++ b/ui/src/websites/weasyl/Weasyl.tsx @@ -109,9 +109,7 @@ export class WeasylFileSubmissionForm extends GenericFileSubmissionSection { if (data) { - if (!_.isEqual(this.state.folders, data)) { - this.setState({ folders: data }); - } + this.setState({ folders: data }); } } ); From 0f225d4a7f8d3115c2b3f59f8fd57d91cbfbb786 Mon Sep 17 00:00:00 2001 From: Michael DiCarlo Date: Wed, 1 Jul 2020 08:53:32 -0400 Subject: [PATCH 10/10] Mastodon: Description will no longer be auto-truncated to 500 for the rare case that a higher length is allowed on some instances --- electron-app/src/websites/mastodon/mastodon.service.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/electron-app/src/websites/mastodon/mastodon.service.ts b/electron-app/src/websites/mastodon/mastodon.service.ts index 33029e07..fc65f7cd 100644 --- a/electron-app/src/websites/mastodon/mastodon.service.ts +++ b/electron-app/src/websites/mastodon/mastodon.service.ts @@ -149,7 +149,7 @@ export class Mastodon extends Website { const { options } = data; const form: any = { - status: `${options.useTitle ? `${data.title}\n` : ''}${data.description}`.substring(0, 500), + status: `${options.useTitle ? `${data.title}\n` : ''}${data.description}`, sensitive: isSensitive || options.spoilerText ? 'yes' @@ -178,7 +178,9 @@ export class Mastodon extends Website { ); if (description.length > 500) { - warnings.push('Max description length allowed is 500 characters.'); + warnings.push( + 'Max description length allowed is 500 characters (for most Mastodon clients).', + ); } const files = [ @@ -222,7 +224,9 @@ export class Mastodon extends Website { ); if (description.length > 500) { - warnings.push('Max description length allowed is 500 characters.'); + warnings.push( + 'Max description length allowed is 500 characters (for most Mastodon clients).', + ); } return { problems: [], warnings }; }