Browse Source

v1.2.0 progress

tags/v1.4.4
Ivan Bravo Bravo 11 months ago
parent
commit
c79a4d0383
58 changed files with 1609 additions and 613 deletions
  1. 4
    0
      src/.env-cmdrc.js
  2. 0
    1
      src/.eslintrc.js
  3. 0
    2
      src/.vscode/tasks.json
  4. 14
    5
      src/assets/css/base/_reset.scss
  5. 3
    3
      src/assets/css/components/_box.scss
  6. 19
    26
      src/assets/css/components/_button.scss
  7. 1
    1
      src/assets/css/components/_container.scss
  8. 1
    1
      src/assets/css/components/_markdown.scss
  9. 2
    2
      src/assets/css/components/_range.scss
  10. 0
    3
      src/assets/css/tailwind.css
  11. BIN
      src/assets/images/dreamnet.png
  12. BIN
      src/assets/images/dreampower.png
  13. BIN
      src/assets/images/dreamtime.png
  14. 108
    12
      src/components/Layout/Jobs.vue
  15. 73
    0
      src/components/Layout/Navbar.vue
  16. 21
    64
      src/components/Layout/Navigation.vue
  17. 132
    0
      src/components/Layout/Topbar.vue
  18. 4
    0
      src/components/Layout/index.js
  19. 97
    108
      src/components/Nudity/Upload.vue
  20. 32
    0
      src/components/UI/AppNews.vue
  21. 10
    7
      src/components/UI/AppTitle.vue
  22. 8
    1
      src/components/UI/BoxItem.vue
  23. 2
    0
      src/components/UI/index.js
  24. 27
    8
      src/electron/src/index.js
  25. 33
    59
      src/electron/src/modules/app-error.js
  26. 79
    29
      src/electron/src/modules/tools/fs.js
  27. 3
    1
      src/electron/src/modules/tools/index.js
  28. 36
    0
      src/electron/src/modules/tools/instagram.js
  29. 25
    21
      src/electron/src/modules/tools/power.js
  30. 0
    8
      src/electron/src/provider.js
  31. 23
    7
      src/layouts/default.vue
  32. 3
    2
      src/mixins/BaseMixin.js
  33. 156
    0
      src/modules/app-error.js
  34. 12
    0
      src/modules/events.js
  35. 65
    89
      src/modules/file.js
  36. 4
    5
      src/modules/index.js
  37. 0
    67
      src/modules/nudify.js
  38. 10
    0
      src/modules/nudify/index.js
  39. 177
    0
      src/modules/nudify/nudify.js
  40. 121
    0
      src/modules/nudify/photo.js
  41. 1
    3
      src/modules/timer.js
  42. 5
    1
      src/nuxt.config.js
  43. 11
    2
      src/package.json
  44. 154
    0
      src/pages/dreamnet.vue
  45. 0
    16
      src/pages/index.vue
  46. 27
    21
      src/pages/nudify/_id.vue
  47. 0
    0
      src/pages/nudify/_id/crop.vue
  48. 0
    0
      src/pages/nudify/_id/preferences.vue
  49. 0
    0
      src/pages/nudify/_id/results.vue
  50. 3
    3
      src/pages/nudify/_id/summary.vue
  51. 3
    3
      src/pages/system/about.vue
  52. 30
    12
      src/plugins/boot.js
  53. 21
    0
      src/plugins/fontawesome.js
  54. BIN
      src/static/assets/images/background.png
  55. BIN
      src/static/assets/images/background2.png
  56. BIN
      src/static/assets/images/dreamnet.png
  57. BIN
      src/static/assets/videos/dreamnet.mp4
  58. 49
    20
      src/tailwind.config.js

+ 4
- 0
src/.env-cmdrc.js View File

@@ -13,5 +13,9 @@ module.exports = {
"production": {
"NODE_ENV": "production",
"LOG": "info"
},
"preview": {
"DEVTOOLS": true,
"ROLLBAR_ACCESS_TOKEN": "6ccfcf317ca54e67830b41570ce23d2a"
}
}

+ 0
- 1
src/.eslintrc.js View File

@@ -16,7 +16,6 @@ module.exports = {
"plugin:nuxt/recommended"
],
globals: {
$dream: false,
$provider: false,
Logger: false,
AppError: false

+ 0
- 2
src/.vscode/tasks.json View File

@@ -7,7 +7,6 @@
"label": "Build: Nuxt",
"type": "npm",
"script": "start:nuxt",
"group": "build",
"isBackground": true,
"presentation": {
"reveal": "always",
@@ -27,7 +26,6 @@
"label": "Build: Electron",
"type": "npm",
"script": "start:babel",
"group": "build",
"isBackground": true,
"presentation": {
"reveal": "always",

+ 14
- 5
src/assets/css/base/_reset.scss View File

@@ -22,6 +22,12 @@ html {
@apply overflow-hidden;
}

body,
#__nuxt,
#__layout {
@apply h-full;
}

.swal-content {
font-size: 16px;

@@ -30,7 +36,7 @@ html {
}

a {
@apply text-primary underline;
@apply text-primary-500 underline;
}
}

@@ -43,13 +49,16 @@ html {

*::-webkit-scrollbar
{
width: 12px;
width: 10px;
@apply bg-dark-800;
}

*::-webkit-scrollbar-thumb
{
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
@apply bg-dark-500;
@apply bg-dark-100 rounded-sm;
transition: all .1s ease-in-out;

&:hover {
@apply bg-primary-700;
}
}

+ 3
- 3
src/assets/css/components/_box.scss View File

@@ -10,7 +10,7 @@
*/

.box {
@apply bg-dark-500 rounded shadow mb-6;
@apply bg-dark-500 rounded-sm shadow mb-6;

.box__photo {
@apply relative;
@@ -24,14 +24,14 @@
}

.box__header {
@apply px-4 pt-2;
@apply px-4 pt-4;

.title {
@apply text-lg font-bold text-white;
}

.subtitle {
@apply text-sm mb-2 font-light text-white;
@apply text-sm font-light mb-2;
}
}


+ 19
- 26
src/assets/css/components/_button.scss View File

@@ -1,17 +1,9 @@
.button {
@apply inline-flex
items-center
justify-center
px-4
rounded
uppercase
font-semibold;

@apply text-primary;

@apply inline-flex items-center justify-center;
@apply px-4 rounded uppercase font-semibold outline-none;
@apply text-primary-500 border border-primary-500-30;
height: 40px;
transition: all 0.2s ease-in-out;
outline: none !important;

&:hover {
@apply bg-primary-500-10;
@@ -21,13 +13,24 @@
@apply bg-primary-500-20;
}

&.is-active,
&.nuxt-link-exact-active {
@apply text-white bg-primary;
&.button--xs {
@apply px-2 text-xs;
height: 21px;
}

&.button--sm {
@apply px-2 text-sm;
height: 31px;
}

&.button--xl {
@apply px-6 text-xl;
height: 51px;
}

&.is-outlined {
@apply border border-primary;
&.is-active,
&.nuxt-link-exact-active {
@apply text-white bg-primary-500;
}

&.is-danger {
@@ -53,16 +56,6 @@
@apply bg-success-500-20;
}
}

&.is-sm {
@apply px-2 text-sm;
height: 30px;
}

&.is-xl {
@apply px-6 text-xl;
height: 50px;
}
}

.buttons {

+ 1
- 1
src/assets/css/components/_container.scss View File

@@ -21,5 +21,5 @@

.content__body {
@apply pt-6 pl-6 pr-6;
height: calc(100vh - 70px);
//height: calc(100vh - 70px);
}

+ 1
- 1
src/assets/css/components/_markdown.scss View File

@@ -28,7 +28,7 @@
}

a {
@apply text-primary;
@apply text-primary-500;

&:hover {
@apply underline;

+ 2
- 2
src/assets/css/components/_range.scss View File

@@ -24,7 +24,7 @@
width: 20px; /* Set a specific slider handle width */
height: 20px; /* Slider handle height */
border-radius: 10px;
@apply bg-primary; /* Green background */
@apply bg-primary-500; /* Green background */
cursor: pointer; /* Cursor on hover */
}

@@ -32,7 +32,7 @@
width: 20px; /* Set a specific slider handle width */
height: 20px; /* Slider handle height */
border-radius: 10px;
@apply bg-primary; /* Green background */
@apply bg-primary-500; /* Green background */
cursor: pointer; /* Cursor on hover */
}
.slider-container{

+ 0
- 3
src/assets/css/tailwind.css View File

@@ -1,3 +0,0 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

BIN
src/assets/images/dreamnet.png View File


BIN
src/assets/images/dreampower.png View File


BIN
src/assets/images/dreamtime.png View File


+ 108
- 12
src/components/Layout/Jobs.vue View File

@@ -1,24 +1,120 @@
<template>
<div class="layout-jobs">
<div class="jobs-pending">
<div class="jobs-section">
<h3 class="section-title">
๐Ÿšฆ Pending Jobs
</h3>
<div class="layout__jobs">
<div class="jobs__section">
<div class="section__title">
<span class="icon"><font-awesome-icon icon="running" /></span>
<span>Running</span>
</div>
</div>

<div class="jobs-recent">
<div class="jobs-section">
<h3 class="section-title">
๐Ÿ‘ Jobs Done
</h3>
<div class="jobs__section">
<div class="section__title">
<div class="flex-1">
<span class="icon"><font-awesome-icon icon="clipboard-list" /></span>
<span>Pending</span>
</div>

<button class="button button--xs">
Run all
</button>
</div>

<div class="jobs__list">
<div v-for="(photo, index) of pending" :key="index" class="job" @click.prevent="openJob(photo.id)">
<figure class="job__preview">
<img :src="photo.file.dataUrl">
</figure>

<span class="job__name">{{ photo.file.fullname }}</span>
</div>
</div>
</div>

<div class="jobs__section">
<div class="section__title">
<span class="icon"><font-awesome-icon icon="clipboard-check" /></span>
<span>Done</span>
</div>
</div>
</div>
</template>

<style lang="scss">
<script>
import { events } from '~/modules'
import { Nudify } from '~/modules/nudify'

export default {
data: () => ({
running: [],
pending: [],
done: [],
}),

created() {
events.on('nudify.add', () => {
this.running = Nudify.running
this.pending = Nudify.pending
this.done = Nudify.done
})
},

methods: {
openJob(photoId) {
this.$router.push(`/nudify/${photoId}`)
},
},
}
</script>

<style lang="scss" scoped>
.layout__jobs {
@apply h-screen bg-dark-500;
@apply flex flex-col;
width: 200px;
}

.jobs__section {
@apply flex-1 flex flex-col;
@apply overflow-hidden;
}

.section__title {
@apply px-4 pt-2 text-sm uppercase font-semibold;
@apply flex items-center;

.icon {
@apply mr-2;
}
}

.jobs__list {
@apply flex-1 overflow-y-auto max-h-full;

.job {
@apply px-4 py-2 flex items-center cursor-pointer;
transition: all .1s ease-in-out;

&:hover {
@apply bg-dark-300 text-white;
}
}

.job__preview {
@apply mr-4 h-full;
width: 42px;

img {
width: 42px;
height: 42px;
}
}

.job__name {
@apply flex-1 overflow-hidden whitespace-no-wrap;
text-overflow: ellipsis;
}
}

.layout-jobs {
@apply p-2 shadow h-screen flex flex-col;
width: 200px;

+ 73
- 0
src/components/Layout/Navbar.vue View File

@@ -0,0 +1,73 @@
<template>
<div class="layout__navbar">
<div class="navbar__left">
<nuxt-link v-if="$provider.tools.system.canNudify" to="/" class="navbar__item">
Nudify
</nuxt-link>

<nuxt-link class="navbar__item" to="/about">
About
</nuxt-link>

<nuxt-link class="navbar__item" to="/dreamnet">
DreamNet
</nuxt-link>
</div>

<div class="navbar__right">
<nuxt-link v-tooltip="{placement: 'bottom', content: 'Settings'}" class="navbar__icon" to="/settings">
<font-awesome-icon icon="cog" />
</nuxt-link>

<nuxt-link v-if="false" v-tooltip="{placement: 'bottom', content: 'About'}" class="navbar__icon" to="/about">
<font-awesome-icon icon="info-circle" />
</nuxt-link>

<nuxt-link v-if="false" v-tooltip="{placement: 'bottom', content: 'DreamNet'}" class="navbar__icon" to="/dreamnet">
<font-awesome-icon icon="code" />
</nuxt-link>

<nuxt-link v-tooltip="{placement: 'bottom', content: 'Donate'}" class="navbar__icon" to="/donate">
<font-awesome-icon icon="donate" />
</nuxt-link>
</div>
</div>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped>
.layout__navbar {
@apply flex bg-dark-500 shadow-lg z-10;
height: 50px;

.navbar__left,
.navbar__right {
@apply flex-1 flex items-center;
}

.navbar__right {
@apply justify-end;
}

.navbar__item {
@apply mx-6 text-sm uppercase font-bold;

&:hover {
@apply text-white;
}
}

.navbar__icon {
@apply mx-4;

&:hover {
@apply text-white;
}
}
}
</style>

+ 21
- 64
src/components/Layout/Navigation.vue View File

@@ -1,31 +1,20 @@
<template>
<div class="layout-navbar">
<div class="layout-menu">
<div :class="{ 'is-active': isActive }" class="navbar">
<!-- Welcome! -->
<div class="navbar-header">
<h1 class="header-title">
{{ $dream.name }}
</h1>

<h3 class="header-greetings">
{{ greetings }}
</h3>
</div>

<!-- App Navigation -->
<section class="navbar-section">
<nav class="navbar-items">
<nuxt-link v-if="$provider.tools.system.canNudify" to="/" class="navbar-item">
<section class="menu-section">
<nav class="menu-items">
<nuxt-link v-if="$provider.tools.system.canNudify" to="/" class="menu-item">
<span class="icon">๐Ÿ“ท</span>
<span>Nudify</span>
</nuxt-link>

<nuxt-link to="/system/about" class="navbar-item">
<nuxt-link to="/system/about" class="menu-item">
<span class="icon">๐ŸŒŒ</span>
<span>About</span>
</nuxt-link>

<nuxt-link to="/system/settings/processing" class="navbar-item">
<nuxt-link to="/system/settings/processing" class="menu-item">
<span class="icon">๐Ÿ”ง</span>
<span>Settings</span>
</nuxt-link>
@@ -33,19 +22,19 @@
</section>

<!-- Nice links -->
<section v-if="$provider.tools.system.online" class="navbar-section">
<nav class="navbar-items">
<app-external-link :href="$provider.services.nucleus.urls.web" class="navbar-item">
<section v-if="$provider.tools.system.online" class="menu-section">
<nav class="menu-items">
<app-external-link :href="$provider.services.nucleus.urls.web" class="menu-item">
<span class="icon">๐ŸŒŽ</span>
<span>Website</span>
</app-external-link>

<app-external-link :href="$provider.services.nucleus.urls.chat" class="navbar-item">
<app-external-link :href="$provider.services.nucleus.urls.chat" class="menu-item">
<span class="icon">๐Ÿ’ฌ</span>
<span>Chat</span>
</app-external-link>

<app-external-link :href="$provider.services.nucleus.urls.forum" class="navbar-item">
<app-external-link :href="$provider.services.nucleus.urls.forum" class="menu-item">
<span class="icon">๐Ÿ‘ฅ</span>
<span>Forum</span>
</app-external-link>
@@ -53,38 +42,18 @@
</section>

<!-- Developer Navigation -->
<section v-if="isDev" class="navbar-section">
<nav class="navbar-items" />
<section v-if="isDev" class="menu-section">
<nav class="menu-items" />
</section>
</div>
</div>
</template>

<script>
import moment from 'moment'

const { settings } = $provider.services

export default {
computed: {
greetings() {
const hours = moment().hours()

if (hours >= 6 && hours <= 11) {
return 'Good morning โ˜•'
}

if (hours >= 12 && hours <= 19) {
return 'Good afternoon ๐ŸŒž'
}

if (hours >= 0 && hours <= 5) {
return 'Sweet dreams ๐Ÿ'
}

return 'Good night ๐ŸŒ›'
},

isDev() {
return process.env.NODE_ENV === 'development'
},
@@ -103,19 +72,7 @@ export default {
}
</script>

<style lang="scss">
@keyframes bgAnim {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 0%;
}
100% {
background-position: 0% 0%;
}
}

<style lang="scss" scoped>
@keyframes navShowAnim {
0% {
left: -200px;
@@ -126,11 +83,11 @@ export default {
}
}

.layout-navbar {
.layout-menu {
@apply pb-6 shadow h-screen bg-dark-500 relative;
width: 200px;

.navbar {
.menu {
@apply absolute top-0 bottom-0;
left: -200px;
width: inherit;
@@ -143,7 +100,7 @@ export default {
}
}

.navbar-header {
.menu-header {
@apply mb-4 text-gray-300 flex flex-col items-center justify-center;
animation: 20s ease-in-out infinite bgAnim;
height: 70px;
@@ -179,7 +136,7 @@ export default {
}
}

.navbar-section {
.menu-section {
@apply mb-4;
}

@@ -187,8 +144,8 @@ export default {
@apply text-center font-bold;
}

.navbar-items {
.navbar-item {
.menu-items {
.menu-item {
@apply border-l-4 border-transparent pl-4 font-semibold flex items-center;
height: 50px;
transition: all 0.1s ease-in-out;
@@ -202,7 +159,7 @@ export default {

&:hover,
&.nuxt-link-exact-active {
@apply text-primary border-primary;
@apply text-primary-500 border-primary-500;

.icon {
filter: unset;

+ 132
- 0
src/components/Layout/Topbar.vue View File

@@ -0,0 +1,132 @@
<template>
<div class="layout__topbar">
<div class="topbar__left">
<div class="topbar__logo">
{{ $dream.name }} {{ $dream.version }}
</div>

<div class="topbar__greetings">
{{ greetings }}
</div>
</div>

<div class="topbar__buttons">
<button type="button" @click="minimize">
<font-awesome-icon icon="minus" />
</button>

<button type="button" @click="maximize">
<font-awesome-icon :icon="['far', 'square']" />
</button>

<button type="button" class="close" @click="close">
<font-awesome-icon icon="times" />
</button>
</div>
</div>
</template>

<script>
import moment from 'moment'

const { activeWindow, api } = $provider.util

export default {
computed: {
greetings() {
const hours = moment().hours()

if (hours >= 6 && hours <= 11) {
return 'โ˜• Good morning'
}

if (hours >= 12 && hours <= 19) {
return '๐ŸŒž Good afternoon'
}

if (hours >= 0 && hours <= 5) {
return '๐Ÿ Sweet dreams'
}

return '๐ŸŒ› Good night'
},
},

methods: {
minimize() {
activeWindow().minimize()
},

maximize() {
activeWindow().maximize()
},

close() {
api.app.quit()
},
},
}
</script>

<style lang="scss" scoped>
@keyframes bgAnim {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 0%;
}
100% {
background-position: 0% 0%;
}
}

.layout__topbar {
@apply flex bg-black text-white z-50;
height: 30px;

.topbar__left {
@apply flex-1 flex;
@apply text-sm;
-webkit-app-region: drag;
}

.topbar__logo {
@apply flex flex-col items-center justify-center;
@apply font-bold px-4;
background: rgb(99, 66, 245);

background: linear-gradient(
40deg,
rgba(99, 66, 245, 1) 0%,
rgba(239, 125, 199, 1) 100%
);

background-size: 200% 100%;
}

.topbar__greetings {
@apply flex flex-col items-center justify-center;
@apply font-light px-4;
}

.topbar__buttons {
@apply flex;

button {
@apply flex items-center justify-center outline-none;
@apply text-xs;
width: 50px;
height: 30px;

&:hover {
@apply bg-black-50;

&.close {
@apply bg-danger-500;
}
}
}
}
}
</style>

+ 4
- 0
src/components/Layout/index.js View File

@@ -1,7 +1,11 @@
import Vue from 'vue'

import LayoutTopbar from './Topbar'
import LayoutNavigation from './Navigation'
import LayoutNavbar from './Navbar'
import LayoutJobs from './Jobs'

Vue.component('layout-topbar', LayoutTopbar)
Vue.component('layout-navbar', LayoutNavbar)
Vue.component('layout-navigation', LayoutNavigation)
Vue.component('layout-jobs', LayoutJobs)

+ 97
- 108
src/components/Nudity/Upload.vue View File

@@ -7,18 +7,20 @@
@dragenter="onDragEnter"
@dragover="onDragOver"
@dragleave="onDragLeave"
@drop="onDrop">
@drop="openDrop">
<p class="dropzone-hint">
๐Ÿ“ท Drop the photo here!
<font-awesome-icon icon="camera" />
Drop the photo(s)/folder here!
</p>
</div>

<div class="uploader__alt">
<!-- Computer File -->
<!-- File -->
<div class="box">
<div class="box__header">
<h2 class="title">
Computer File
<span class="icon"><font-awesome-icon icon="image" /></span>
<span>File.</span>
</h2>
<h3 class="subtitle">
Select a file from your computer.
@@ -31,47 +33,70 @@
ref="photo"
type="file"
accept="image/jpeg, image/png"
@change="onPhotoSelected">
@change="openFile">

<button class="button" @click.prevent="$refs.photo.click()">
๐Ÿ“‚ open a photo...
<span>open file</span>
</button>
</div>
</div>

<!-- Computer Folder
<!-- Folder -->
<div class="box">
<div class="box__header">
<h2 class="title">
Computer Folder
<span class="icon"><font-awesome-icon icon="folder-open" /></span>
<span>Folder.</span>
</h2>
<h3 class="subtitle">
All valid photos in the folder will be processed.
Select a folder from your computer. All valid photos will be transformed.
</h3>
</div>

<div class="box__content">
<button class="button" @click.prevent="openFolder">
๐Ÿ“‚ import folder...
<button class="button" @click.prevent="openDirectory">
<span>import folder</span>
</button>
</div>
</div>-->
</div>

<!-- Web Address -->
<div class="box">
<div class="box__header">
<h2 class="title">
Web Address
<span class="icon"><font-awesome-icon icon="globe" /></span>
<span>Web Address.</span>
</h2>
<h3 class="subtitle">
It must be the direct web address to a photo and must end with the jpg, png or gif format.
Enter the web address of a photo. It must end in a valid extension (jpg, png, gif)
</h3>
</div>

<div class="box__content">
<input v-model="webAddress" type="url" class="input mb-2" placeholder="https://">

<button class="button" @click="onURL">
<button class="button" @click="openUrl">
Go!
</button>
</div>
</div>

<!-- Web Address -->
<div class="box">
<div class="box__header">
<h2 class="title">
<span class="icon"><font-awesome-icon :icon="['fab', 'instagram']" /></span>
<span>Instagram photo.</span>
</h2>
<h3 class="subtitle">
Enter the web address or Media ID of an Instagram photo.
</h3>
</div>

<div class="box__content">
<input v-model="instagramPhoto" type="url" class="input mb-2" placeholder="https://www.instagram.com/p/dU4fHDw-Ho/">

<button class="button" @click="openInstagramPhoto">
Go!
</button>
</div>
@@ -82,13 +107,16 @@

<script>
/* eslint-disable no-param-reassign */
// eslint-disable-next-line lodash/import-scope
import _ from 'lodash'
import {
isNil, isEmpty, startsWith,
map,
} from 'lodash'
import swal from 'sweetalert'
import { Photo } from '~/modules/models'
import { Nudify, Photo } from '~/modules/nudify'
import { File } from '~/modules'

const { nucleus, rollbar } = $provider.services
const { instagram } = $provider.tools

export default {
props: {
@@ -100,130 +128,93 @@ export default {

data: () => ({
webAddress: '',

// Indicates if the user is dragging a file in the window (we apply the drag style)
instagramPhoto: '',
isDragging: false,
}),

created() {
// Restarts the information of a previous process
this.$nudify.reset()

},

methods: {
/**
* File selected, start a new transformation process
*/
startFromFile(inputFile) {
if (_.isNil(inputFile)) {
swal(
'Upload failed',
'It seems that you have not selected a photo!',
'info',
)
return
}

// New File instance
const file = File.fromPath(inputFile.path)

this.start(file)
addFile(file) {
Nudify.addFile(file.path)
},

/**
*
*/
async startFromURL(url) {
if (_.isNil(url)) {
swal('Upload failed', 'This does not seem like a valid URL', 'info')
return
async addFiles(files) {
if (files.length > 1) {
swal({
title: 'Importing files...',
text: 'One moment, please.',
button: false,
closeOnClickOutside: false,
closeOnEsc: false,
})
}

swal({
title: 'Loading...',
text: 'We are downloading the photo and preparing it!',
button: false,
closeOnClickOutside: false,
closeOnEsc: false,
})

try {
// New File instance
const file = await File.fromURL(url)
await Nudify.addFiles(files)

if (files.length > 1) {
swal.close()

this.start(file)
} catch (err) {
swal({
icon: 'error',
title: 'Upload failed',
text: `An error has occurred downloading the photo or saving it in the temporary folder, please make sure you are connected to the Internet and that ${
$dream.name
} has permissions to save files.`,
})

rollbar.warn(err)
}
},

/**
*
*/
start(file) {
// Create a photo for the model ("null" model for now)
const photo = new Photo(null, file)

// Get any error message from the file
const validationErrorMessage = photo.getValidationErrorMessage()
openFile(event) {
const { files } = event.target

if (!_.isNil(validationErrorMessage)) {
swal('Upload failed', validationErrorMessage, 'error')
if (files.length === 0) {
return
}

// Start the transformation process!
this.$nudify.start(photo)
nucleus.track('UPLOAD_FILE')

// It's time to nudify the photo
this.$router.push('/nudify')
this.addFile(files[0])

event.target.value = ''
},

/**
*
*/
openFolder() {
openDirectory() {

},

/**
*
*/
onPhotoSelected(event) {
const { files } = event.target

if (files.length === 0) {
openUrl() {
if (isEmpty(this.webAddress) || (!startsWith(this.webAddress, 'http://') && !startsWith(this.webAddress, 'https://'))) {
swal('Upload failed.', 'Please enter a valid web address.', 'error')
return
}

nucleus.track('UPLOAD_SELECTED')
nucleus.track('UPLOAD_URL')

this.startFromFile(files[0])
event.target.value = ''
Nudify.addUrl(this.webAddress)
},

/**
*
*/
onURL() {
if (_.isNil(this.webAddress) || this.webAddress.length === 0) {
swal('Upload failed', 'Please enter a valid web address', 'error')
return
async openInstagramPhoto() {
if (isEmpty(this.instagramPhoto)) {
throw new AppError('Please enter a valid Instagram photo.', { title: 'Upload failed.', level: 'warning' })
}

nucleus.track('UPLOAD_URL')
const post = await instagram.getPost(this.instagramPhoto)

this.startFromURL(this.webAddress)
if (post.isVideo) {
throw new AppError('The videos are not supported yet.', { title: 'Upload failed.', level: 'warning' })
}

Nudify.addUrl(post.downloadUrl)
},

/**
@@ -254,20 +245,22 @@ export default {
/**
*
*/
onDrop(event) {
openDrop(event) {
event.preventDefault()
event.stopPropagation()

this.isDragging = false

const { files } = event.dataTransfer
const externalURL = event.dataTransfer.getData('url')
const url = event.dataTransfer.getData('url')

if (files.length > 0) {
nucleus.track('UPLOAD_DROP')
this.startFromFile(files[0])
} else if (externalURL.length > 0) {
if (url.length > 0) {
nucleus.track('UPLOAD_DROP_URL')
this.startFromURL(externalURL)
Nudify.addUrl(url)
} else if (files.length > 0) {
const paths = map(files, 'path')
this.addFiles(paths)
nucleus.track('UPLOAD_DROP')
}
},
},
@@ -279,17 +272,13 @@ export default {
@apply w-full relative;

.uploader__alt {
@apply flex flex-wrap;
@apply flex flex-wrap justify-between;

.box {
@apply flex flex-col;
width: 48%;
width: calc(1/2*100% - (1 - 1/2)*1rem);
min-height: 200px;

&:not(:last-child) {
@apply mr-4;
}

.box__header {
h2 {
@apply text-lg font-bold;
@@ -313,13 +302,13 @@ export default {

.uploader__dropzone {
@apply flex items-center justify-center;
@apply bg-dark-400 mb-6;
@apply rounded border-2 border-dashed border-gray-600;
@apply bg-dark-500 mb-6;
@apply rounded border-2 border-dashed border-dark-400;
height: 200px;
transition: all 0.1s linear;

&.is-dragging {
@apply bg-dark-700 border-white;
@apply bg-dark-700 border-dark-200;

.dropzone-hint {
@apply text-white text-xl;

+ 32
- 0
src/components/UI/AppNews.vue View File

@@ -0,0 +1,32 @@
<template>
<div class="c-news">
<a
class="twitter-timeline"
data-lang="en"
data-height="1200"
data-dnt="true"
data-theme="dark"
data-link-color="#D67411"
:href="`${twitterUrl}?ref_src=twsrc%5Etfw`">Tweets by DreamNetTechno</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8" />
</div>
</template>

<script>
const { nucleus } = $provider.services

export default {
computed: {
twitterUrl() {
return nucleus.urls?.social?.twitter || 'https://twitter.com/DreamNetTechno'
},
},
}
</script>

<style lang="scss" scoped>
.c-news {
width: 100%;
max-width: 1200px;
}
</style>

+ 10
- 7
src/components/UI/AppTitle.vue View File

@@ -1,16 +1,19 @@
<template>
<div class="app-title">
<div class="c-title">
<slot />
</div>
</template>

<style lang="scss">
.app-title {
@apply px-4 py-2 bg-dark-500 shadow flex flex-col justify-center;
height: 70px;
<style lang="scss" scoped>
.c-title {
@apply px-4 bg-dark-500 shadow-lg flex items-center;
position: sticky;
top: 0;
height: 50px;

.title {
@apply font-bold text-white text-lg;
@apply text-base text-white font-bold pr-2 mr-2;
@apply border-r border-dark-100;

sup {
@apply text-white;
@@ -18,7 +21,7 @@
}

.subtitle {
@apply text-generic-300;
@apply text-gray-400 text-sm;
}

a {

+ 8
- 1
src/components/UI/BoxItem.vue View File

@@ -50,27 +50,33 @@ export default {
default: false,
},
},

computed: {
isVisible() {
return _.isNil(this.version) || this.version === this.app.version
},

isImageIcon() {
return this.isURL(this.icon)
},

cssClass() {
return {
'is-link': !_.isNil(this.href) || this.isLink,
}
},
},

methods: {
click() {
this.$emit('click')

if (!_.isNil(this.href)) {
$provider.services.nucleus.track('EXTERNAL_LINK', { href: this.href })
shell.openExternal(this.href)
}
},

isURL(str) {
if (_.isNil(str)) {
return false
@@ -101,6 +107,7 @@ export default {

.box__item {
@apply flex px-4 py-2;
min-height: 50px;
transition: all .1s ease-in-out;

&.box__item--sub {
@@ -133,7 +140,7 @@ export default {
@apply flex-1 flex;

.item__text {
@apply flex-1;
@apply flex-1 flex items-center;

.item__label {
@apply block font-semibold;

+ 2
- 0
src/components/UI/index.js View File

@@ -16,10 +16,12 @@ import Update from './AppUpdate'
import SectionItem from './BoxSectionItem'
import BoxItem from './BoxItem'
import AppPhoto from './AppPhoto'
import AppNews from './AppNews'

Vue.component('app-title', Title)
Vue.component('app-external-link', ExternalLink)
Vue.component('app-update', Update)
Vue.component('app-photo', AppPhoto)
Vue.component('app-news', AppNews)
Vue.component('box-section-item', SectionItem)
Vue.component('box-item', BoxItem)

+ 27
- 8
src/electron/src/index.js View File

@@ -7,7 +7,8 @@
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import { app, BrowserWindow } from 'electron'
import { startsWith } from 'lodash'
import { app, BrowserWindow, shell } from 'electron'
import http from 'http'
import { dirname, join } from 'path'
import { URL } from 'url'
@@ -25,7 +26,7 @@ import config from '~/nuxt.config'
const logger = Logger.create('electron')

// NuxtJS root directory
config.rootDir = dirname(__dirname)
config.rootDir = dirname(dirname(__dirname))

if (!is.development) {
process.chdir(getPath('exe', '../'))
@@ -127,6 +128,7 @@ class DreamApp {
height: 700,
minWidth: 1200,
minHeight: 700,
frame: false,
icon: join(config.rootDir, 'dist', 'icon.ico'),
webPreferences: {
nodeIntegration: true,
@@ -216,17 +218,34 @@ process.on('unhandledRejection', (err) => {
return true
})

app.on('web-contents-created', (event, contents) => {
app.on('web-contents-created', (e, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)
const url = new URL(navigationUrl)

console.log(parsedUrl)
if (url.host === `${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`) {
// ok
return
}

event.preventDefault()

/**
if (parsedUrl.origin !== 'https://example.com') {
logger.warn('Blocked attempt to load an external page.', {
event,
url,
})
})

contents.on('new-window', (event, url) => {
if (startsWith(url, 'http')) {
event.preventDefault()
shell.openExternal(url)
return
}
* */

logger.debug('Opening new window.', {
event,
url,
})
})
})


+ 33
- 59
src/electron/src/modules/app-error.js View File

@@ -10,11 +10,10 @@
import {
isError, isString, isObject, isArray,
} from 'lodash'
import { app, dialog, ipcMain } from 'electron'
import { activeWindow } from 'electron-util'
import { app, dialog } from 'electron'
import { rollbar } from './services/rollbar'

const logger = require('logplease').create('electron:app-error')
const logger = require('logplease').create('app-error:main')

/**
* @typedef {Object} ErrorOptions
@@ -25,12 +24,10 @@ const logger = require('logplease').create('electron:app-error')
*/

export class AppError extends Error {
renderer = false

options = {
title: null,
error: null,
level: 'error',
error: null,
fatal: false,
quiet: false,
}
@@ -50,7 +47,7 @@ export class AppError extends Error {

this.options.error = input

if (input instanceof AppError) {
if (input.options) {
this.options = {
...this.options,
...input.options,
@@ -64,27 +61,26 @@ export class AppError extends Error {
...this.options,
...options,
}

if (this.options.level === 'warning') {
this.options.level = 'warn'
}
}

report() {
let { level } = this.options

if (level === 'warning') {
level = 'warn'
if (!rollbar.enabled) {
return
}

try {
// logger
logger[level](this.message)
const { level } = this.options

if (rollbar.enabled) {
const error = this.options.error || this
try {
const error = this.options.error || this

const response = rollbar[level](this.message, error, this.options)
const response = rollbar[level](this.message, error, this.options)

if (response.uuid) {
this.message += `\n\nShare this with a developer:\nhttps://rollbar.com/occurrence/uuid/?uuid=${response.uuid}`
}
if (response.uuid) {
this.message += `\n\nShare this with a developer:\nhttps://rollbar.com/occurrence/uuid/?uuid=${response.uuid}`
}
} catch (err) {
logger.warn('Error report fail!', err)
@@ -92,42 +88,31 @@ export class AppError extends Error {
}

show() {
const window = activeWindow()

if (this.renderer && window) {
let icon = 'error'

if (this.level === 'warning' || this.level === 'warn') {
icon = 'warning'
}

if (this.level === 'info') {
icon = 'info'
}

window.webContents.send('alert', {
title: this.options.title,
text: this.message,
icon,
})
} else {
dialog.showErrorBox(
this.options.title || 'A problem has occurred.',
this.message,
)
}
dialog.showErrorBox(
this.options.title || 'A problem has occurred.',
this.message,
)
}

handle() {
const {
level, quiet, fatal, error,
} = this.options

// logger
logger[level](this.message, {
error,
})

if (process.env.NODE_ENV !== 'development') {
this.report()
}

if (!this.options.quiet) {
if (!quiet) {
this.show()
}

if (this.options.fatal) {
if (fatal) {
app.quit()
}
}
@@ -146,24 +131,13 @@ export class AppError extends Error {
reportError = new Error(error)
}

appError = new AppError(
isError(error) ? error : 'The application has encountered an unexpected error. It\'s all we know, try again or restart the application.',
appError = new AppError(`The application has encountered an unexpected error:\n<code>${reportError ?.message}</code>`,
{
error: reportError,
title: 'Unexpected error!',
},
)
})
}

appError.handle()
}

static handleRenderer(message, stack, options = {}) {
const appError = new AppError(message, options)

appError.stack = stack
appError.renderer = true

appError.handle()
}
}

+ 79
- 29
src/electron/src/modules/tools/fs.js View File

@@ -1,19 +1,21 @@
import { basename, join } from 'path'
import { basename, join, parse } from 'path'
import {
statSync, readFileSync, writeFileSync, existsSync,
unlinkSync, createWriteStream, createReadStream,
unlinkSync, createWriteStream, createReadStream, copy,
} from 'fs-extra'
import { isNil } from 'lodash'
import { app, dialog } from 'electron'
import { is, platform } from 'electron-util'
import mime from 'mime-types'
import EventBus from 'js-event-bus'
import axios from 'axios'
import { api, is, platform } from 'electron-util'
import md5File from 'md5-file'
import filesize from 'filesize'
import unzipper from 'unzipper'
import deferred from 'deferred'
import sevenBin from '7zip-bin'
import { extractFull } from 'node-7z'
import { getAppResourcesPath } from './paths'
import { getPath, getAppResourcesPath } from './paths'
import { AppError } from '../app-error'

const logger = require('logplease').create('electron:modules:tools:fs')
@@ -37,18 +39,21 @@ export function getBase64Data(dataURL) {

/**
*
* @param {string} path
* @param {string} filepath
*/
export function getInfo(path) {
const exists = this.exists(path)
const mimetype = mime.lookup(path)
const { name, ext, dir } = path.parse(path)
export function getInfo(filepath) {
const exists = existsSync(filepath)
const mimetype = mime.lookup(filepath)
const { name, ext, dir } = parse(filepath)

let size
let size = -1
let md5

if (exists) {
const stats = statSync(path)
const stats = statSync(filepath)
size = stats.size / 1000000.0

md5 = md5File.sync(filepath)
}

return {
@@ -58,6 +63,7 @@ export function getInfo(path) {
dir,
mimetype,
size,
md5,
}
}

@@ -142,7 +148,7 @@ export function extractSeven(path, destinationPath) {
/**
*
* @param {string} url
* @param {Object} options
* @param {Object} [options]
*/
export function download(url, options = {}) {
const bus = new EventBus()
@@ -150,39 +156,82 @@ export function download(url, options = {}) {
// eslint-disable-next-line no-param-reassign
options = {
showSaveAs: false,
directory: api.app.getPath('downloads'),
directory: app.getPath('downloads'),
filename: basename(url).split('?')[0].split('#')[0],
...options,
}

let filepath = join(options.directory, options.filename)

/**
* @type {ReadStream}
*/
let stream

if (options.showSaveAs) {
filepath = api.dialog.showSaveDialogSync({
// save as dialog
filepath = dialog.showSaveDialogSync({
defaultPath: filepath,
})
}

const deleteFile = () => {
if (existsSync(filepath)) {
unlinkSync(filepath)
}
}
const writeStream = createWriteStream(filepath)

axios.request({
url,
timeout: 5000,
responseType: 'stream',
maxContentLength: -1,
}).then(({ data, headers }) => {
const contentLength = headers['content-length'] || -1

data.on('data', () => {
const progress = writeStream.bytesWritten / contentLength

bus.emit('progress', null, {
progress,
written: (writeStream.bytesWritten / 1048576).toFixed(2),
total: (contentLength / 1048576).toFixed(2),
})
})

data.on('error', (err) => {
throw new AppError(err, { title: 'Download failed.' })
})

writeStream.on('error', (err) => {
throw new AppError(err, { title: 'Download failed.' })
})

writeStream.on('finish', () => {
if (!existsSync(filepath)) {
throw new AppError('The file was not saved correctly.', { title: 'Download failed.' })
}

bus.emit('end', null, filepath)
})

data.pipe(writeStream)

bus.on('cancel', () => {
writeStream.destroy()
data.destroy()

logger.info('Download canceled by user.')
bus.emit('end')
})

return true
}).catch((err) => {
writeStream.destroy(err)

logger.warn('Download canceled due to an error.', err)
bus.emit('error', null, err)
})


/*
axios.request({
url,
responseType: 'stream',
maxContentLength: -1,
}).then((response) => {
const contentLength = response.data.headers['content-length'] || -1
const totalSize = filesize(contentLength, { exponent: 2, output: 'object' }).value

const output = createWriteStream(filepath)

stream = response.data
@@ -190,7 +239,7 @@ export function download(url, options = {}) {
stream.on('data', (chunk) => {
output.write(Buffer.from(chunk))

const written = filesize(output.bytesWritten, { exponent: 2, output: 'object' }).value
const written = filesize(writeStream.bytesWritten, { exponent: 2, output: 'object' }).value

if (contentLength > 0) {
const progress = output.bytesWritten / contentLength
@@ -244,6 +293,7 @@ export function download(url, options = {}) {
logger.warn('Download canceled due to an error.', err)
bus.emit('error', null, err)
})
*/

return bus
}
@@ -251,11 +301,11 @@ export function download(url, options = {}) {
/**
*
* @param {string} url
* @param {Object} options
* @param {Object} [options]
*/
export function downloadAsync(url, options = {}) {
return new Promise((resolve, reject) => {
const bus = this.download(url, options)
const bus = download(url, options)

bus.on('end', (filepath) => {
resolve(filepath)

+ 3
- 1
src/electron/src/modules/tools/index.js View File

@@ -11,11 +11,13 @@ import * as paths from './paths'
import * as shell from './shell'
import * as power from './power'
import * as fs from './fs'
import * as instagram from './instagram'

// eslint-disable-next-line import/no-cycle
export { fs }
export { shell }
export { paths }
export { power }
export utils from 'electron-util'
export { instagram }
export { system } from './system'
export utils from 'electron-util'

+ 36
- 0
src/electron/src/modules/tools/instagram.js View File

@@ -0,0 +1,36 @@
// DreamTime.
// Copyright (C) DreamNet. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License 3.0 as published by
// the Free Software Foundation. See <https://www.gnu.org/licenses/gpl-3.0.html>
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import instagramSave from 'instagram-save'
import createUrl from 'instagram-save/lib/create-url'
import parsePage from 'instagram-save/lib/parse-page'
import { getPath } from './paths'

/**
*
* @param {string} input
* @param {string} [destination]
*/
export async function download(input, destination) {
if (!destination) {
// eslint-disable-next-line no-param-reassign
destination = getPath('temp')
}

return instagramSave(input, destination)
}

/**
*
* @param {string} input
*/
export function getPost(input) {
const url = createUrl(input)
return parsePage(url)
}

+ 25
- 21
src/electron/src/modules/tools/power.js View File

@@ -124,32 +124,36 @@ export const transform = (job) => {
export const getVersion = () => {
const def = deferred()

let process
let response = ''
try {
let process
let response = ''

if (settings.processing.usePython) {
// python script
process = spawn('python3', ['main.py', '--version'], {
cwd: getPowerPath(),
})
} else {
process = spawn(getPowerPath('dreampower'), ['--version'])
}

if (settings.processing.usePython) {
// python script
process = spawn('python3', ['main.py', '--version'], {
cwd: getPowerPath(),
process.on('error', () => {
def.resolve()
})
} else {
process = spawn(getPowerPath('dreampower'), ['--version'])
}

process.on('error', () => {
def.resolve()
})

process.stdout.on('data', (data) => {
response += data
})
process.stdout.on('data', (data) => {
response += data
})

process.on('close', () => {
response = semverRegex().exec(response)
response = `v${response[0]}`
process.on('close', () => {
response = semverRegex().exec(response)
response = `v${response[0]}`

def.resolve(response)
})
def.resolve(response)
})
} catch (err) {
def.resolve()
}

return def.promise
}

+ 0
- 8
src/electron/src/provider.js View File

@@ -2,8 +2,6 @@ const { remote } = require('electron')
const { makeServiceProxy } = require('./modules/services')

const util = remote.require('electron-util')
const Logger = remote.require('logplease')
const { AppError } = remote.require('./modules/app-error')
const services = remote.require('./modules/services')
const updater = remote.require('./modules/updater')
const tools = remote.require('./modules/tools')
@@ -22,9 +20,3 @@ window.$provider = {
api: util.api,
util,
}

// logger
window.Logger = Logger

// application error
window.AppError = AppError

+ 23
- 7
src/layouts/default.vue View File

@@ -1,12 +1,17 @@
<template>
<div class="layout">
<layout-navigation />
<layout-topbar />

<div class="content">
<nuxt />
</div>
<layout-navbar />

<div class="layout__body">
<layout-navigation v-if="false" />
<layout-jobs />

<layout-jobs v-if="false" />
<div class="layout__content">
<nuxt />
</div>
</div>
</div>
</template>

@@ -19,10 +24,21 @@ export default {

<style lang="scss">
.layout {
@apply flex h-screen;
@apply flex flex-col h-full;

.content {
.layout__body {
@apply flex-1 flex;
}

.layout__content {
@apply flex-1 h-screen overflow-hidden overflow-y-auto;

/*
.content {
@apply flex-1 relative;
height: 90vh;
}
*/
}
}
</style>

+ 3
- 2
src/mixins/BaseMixin.js View File

@@ -1,6 +1,6 @@
import _ from 'lodash'
import tippy from 'tippy.js'
import { nudify } from '~/modules'
import { Nudify } from '~/modules/nudify'

export default {
directives: {
@@ -16,13 +16,14 @@ export default {

tippy(el, settings)
},

},
},

filters: {},

data: () => ({
$nudify: nudify,
$nudify: Nudify,
$settings: $provider.services.settings,
}),
}

+ 156
- 0
src/modules/app-error.js View File

@@ -0,0 +1,156 @@
// DreamTime.
// Copyright (C) DreamNet. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License 3.0 as published by
// the Free Software Foundation. See <https://www.gnu.org/licenses/gpl-3.0.html>
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import {
isError, isString, isObject, isArray,
} from 'lodash'
import swal from 'sweetalert'

const logger = require('logplease').create('app-error:renderer')

const { rollbar } = $provider.services
const { app } = $provider.api

/**
* @typedef {Object} ErrorOptions
* @property {string} title
* @property {Error} error
* @property {string} level
* @property {Object} extra
*/

export class AppError extends Error {
options = {
title: null,
level: 'error',
error: null,
fatal: false,
quiet: false,
}

/**
*
* @param {string} message
* @param {ErrorOptions} options
*/
constructor(input, options = {}) {
if (isString(input)) {
super(input)
} else if (isError(input)) {
super(input.message)

this.stack = input.stack

this.options.error = input

if (input.options) {
this.options = {
...this.options,
...input.options,
}
}
} else {
super()
}

this.options = {
...this.options,
...options,
}

if (this.options.level === 'warning') {
this.options.level = 'warn'
}
}

report() {
if (!rollbar.enabled) {
return
}

const { level } = this.options

try {
const error = this.options.error || this

const response = rollbar[level](this.message, error, this.options)

if (response.uuid) {
this.message += `\n\nShare this with a developer:\nhttps://rollbar.com/occurrence/uuid/?uuid=${response.uuid}`
}
} catch (err) {
logger.warn('Error report fail!', err)
}
}

show() {
let icon = 'error'

if (this.level === 'warning' || this.level === 'warn') {
icon = 'warning'
}

if (this.level === 'info') {
icon = 'info'
}

swal({
title: this.options.title,
text: this.message,
icon,
})
}

handle() {
const {
level, quiet, fatal, error,
} = this.options

// logger
logger[level](this.message, {
error,
})

if (process.env.NODE_ENV !== 'development') {
this.report()
}

if (!quiet) {
this.show()
}

if (fatal) {
app.quit()
}
}

static handle(error) {
let appError = error

if (!(error instanceof AppError)) {
let reportError

if (isError(error)) {
reportError = error
} else if (isObject(error) || isArray(error)) {
reportError = JSON.stringify(error)
} else {
reportError = new Error(error)
}

appError = new AppError(`The application has encountered an unexpected error:\n<code>${reportError ?.message}</code>`,
{
error: reportError,
title: 'Unexpected error!',
})
}

appError.handle()
}
}

+ 12
- 0
src/modules/events.js View File

@@ -0,0 +1,12 @@
// DreamTime.
// Copyright (C) DreamNet. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License 3.0 as published by
// the Free Software Foundation. See <https://www.gnu.org/licenses/gpl-3.0.html>
//
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019.

import EventBus from 'js-event-bus'

export const events = new EventBus()

+ 65
- 89
src/modules/file.js View File

@@ -1,166 +1,142 @@
// eslint-disable-next-line lodash/import-scope
import _ from 'lodash'
import path from 'path'
import { isNil } from 'lodash'
import { join } from 'path'

/* eslint-disable-next-line */
const debug = require('debug').default('app:modules:file')

const {
writeDataUrl, downloadAsync, getInfo, unlinkSync,
read, copySync,
<