- macOS (GPU) critical bug fixed. - New alert center. - Portability integration. - New user improvements. - DreamTrack integration.tags/v1.4.4
@@ -77,7 +77,7 @@ jobs: | |||
working-directory: src | |||
run: yarn lint | |||
- name: Build | |||
- name: Build (Installer) | |||
working-directory: src | |||
env: | |||
GITHUB_SHA: ${{ github.sha }} |
@@ -1,30 +1,30 @@ | |||
const development = { | |||
"LOG": "debug", | |||
"DEVTOOLS": true, | |||
} | |||
module.exports = { | |||
"default": { | |||
"SERVER_HOST": "localhost", | |||
"SERVER_PORT": 4000, | |||
"NUCLEUS_APPID": "5d353cecbe5ccc0133cf90f4" | |||
"SERVER_PORT": 4000 | |||
}, | |||
"development": { | |||
"name": "development", | |||
"NODE_ENV": "development", | |||
"LOG": "debug", | |||
"DEVTOOLS": true, | |||
"ROLLBAR_ACCESS_TOKEN": "6ccfcf317ca54e67830b41570ce23d2a", | |||
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime" | |||
"ROLLBAR_ACCESS_TOKEN": "e62c909ec771492fa7f371dc61eea092", | |||
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime", | |||
"DREAMTRACK_HOST": "track.dreamnet.tech", | |||
...development | |||
}, | |||
"production": { | |||
"name": "production", | |||
"NODE_ENV": "production", | |||
"LOG": "info" | |||
"LOG": "info", | |||
"DREAMTRACK_HOST": "track.dreamnet.tech" | |||
}, | |||
"test": { | |||
"name": "test", | |||
"NODE_ENV": "test", | |||
"LOG": "debug" | |||
}, | |||
"preview": { | |||
"DEVTOOLS": true, | |||
"ROLLBAR_ACCESS_TOKEN": "6ccfcf317ca54e67830b41570ce23d2a", | |||
"LOGROCKET_ACCESS_TOKEN": "of2lox/dreamtime" | |||
...development | |||
} | |||
} |
@@ -11,7 +11,7 @@ | |||
.notification { | |||
@apply mb-4 py-2 px-4 border-2 border-dark-100 rounded-sm; | |||
@apply bg-transparent text-generic-100; | |||
@apply bg-transparent text-generic-500 text-sm; | |||
a { | |||
@apply underline; | |||
@@ -24,4 +24,12 @@ | |||
&.notification--danger { | |||
@apply text-danger-400 border-danger; | |||
} | |||
&.notification--success { | |||
@apply text-success-400 border-success; | |||
} | |||
.icon { | |||
@apply mr-2; | |||
} | |||
} |
@@ -13,6 +13,26 @@ | |||
.project__content { | |||
@apply flex mb-6 pb-6 border-b border-dark-500; | |||
.project__update { | |||
@apply flex-1; | |||
} | |||
.project__description { | |||
@apply flex-1 flex flex-col justify-center; | |||
p { | |||
@apply text-xl mb-6; | |||
} | |||
a { | |||
@apply text-white underline; | |||
} | |||
} | |||
} | |||
.project__installation { | |||
@apply flex; | |||
.project__overview { | |||
@apply flex-1 flex flex-col justify-center items-center; | |||
@@ -43,26 +63,6 @@ | |||
} | |||
} | |||
.project__description { | |||
@apply flex-1 flex flex-col justify-center; | |||
p { | |||
@apply text-xl mb-6; | |||
} | |||
a { | |||
@apply text-white underline; | |||
} | |||
} | |||
} | |||
.project__installation { | |||
@apply flex; | |||
.project__update { | |||
@apply flex-1; | |||
} | |||
.project__settings { | |||
@apply flex-1; | |||
} |
@@ -1,37 +1,57 @@ | |||
<template> | |||
<div class="layout__navbar"> | |||
<div class="navbar__left"> | |||
<nuxt-link v-if="canNudify" to="/" class="navbar__item"> | |||
Nudify | |||
<nuxt-link v-if="canNudify" to="/" class="navbar__item navbar__item--home"> | |||
Upload | |||
</nuxt-link> | |||
<nuxt-link id="settings" class="navbar__item" to="/settings"> | |||
Settings | |||
</nuxt-link> | |||
<nuxt-link v-if="unlockedBadTime" class="navbar__item" to="/games/badtime"> | |||
Bad Time | |||
<nuxt-link v-if="unlockedBadTime" v-tooltip="{ content: 'Mini-game. Can you survive until the end? ๐ฎ๐', placement: 'bottom' }" class="navbar__item" to="/games/badtime"> | |||
Bad Time Game | |||
</nuxt-link> | |||
<a id="guide" class="navbar__item" :href="manualURL" target="_blank"> | |||
Help | |||
</a> | |||
<a v-if="isDev" class="navbar__item" @click.prevent="createError"> | |||
Force Error | |||
</a> | |||
</div> | |||
<div class="navbar__right"> | |||
<nuxt-link v-tooltip="{placement: 'bottom', content: 'Alert Center'}" class="navbar__icon" to="/alerts"> | |||
<font-awesome-icon v-if="hasAlerts" icon="exclamation-triangle" class="alerts--active" /> | |||
<font-awesome-icon v-else icon="check-circle" class="alerts--ok" /> | |||
</nuxt-link> | |||
<nuxt-link v-tooltip="{placement: 'bottom', content: 'About'}" class="navbar__icon" to="/about"> | |||
<font-awesome-icon icon="info-circle" /> | |||
</nuxt-link> | |||
<nuxt-link v-tooltip="{placement: 'bottom', content: 'DreamNet'}" class="navbar__icon" to="/dreamnet"> | |||
<nuxt-link | |||
v-if="$provider.system.online" | |||
v-tooltip="{placement: 'bottom', content: 'DreamNet'}" | |||
class="navbar__icon" | |||
to="/dreamnet"> | |||
<font-awesome-icon icon="users" /> | |||
</nuxt-link> | |||
<a v-tooltip="{placement: 'bottom', content: 'Donate and get benefits!'}" class="navbar__icon" :href="donateUrl" target="_blank"> | |||
<a | |||
v-if="$provider.system.online" | |||
id="guide" | |||
v-tooltip="{placement: 'bottom', content: 'User\'s Guide.'}" | |||
class="navbar__icon" | |||
:href="manualURL" | |||
target="_blank"> | |||
<font-awesome-icon icon="question-circle" /> | |||
</a> | |||
<a | |||
v-if="$provider.system.online" | |||
v-tooltip="{placement: 'bottom', content: 'Donate and get benefits!'}" | |||
class="navbar__icon" | |||
:href="donateUrl" | |||
target="_blank"> | |||
<font-awesome-icon :icon="['fab', 'patreon']" /> | |||
</a> | |||
</div> | |||
@@ -40,7 +60,7 @@ | |||
<script> | |||
import { requirements, settings } from '~/modules/system' | |||
import { nucleus } from '~/modules/services' | |||
import { dreamtrack } from '~/modules/services' | |||
import { events } from '~/modules' | |||
export default { | |||
@@ -54,15 +74,19 @@ export default { | |||
}, | |||
donateUrl() { | |||
return nucleus.urls?.support?.patreon || 'https://www.patreon.com/dreamnet' | |||
return dreamtrack.get('urls.support.patreon', 'https://www.patreon.com/dreamnet') | |||
}, | |||
manualURL() { | |||
return nucleus.urls?.docs?.manual || 'https://time.dreamnet.tech/docs/guide/upload' | |||
return dreamtrack.get('urls.docs.manual', 'https://time.dreamnet.tech/docs/guide/upload') | |||
}, | |||
isDev() { | |||
return process.env.name === 'development' | |||
return process.env.NODE_ENV === 'development' | |||
}, | |||
hasAlerts() { | |||
return requirements.hasAlerts | |||
}, | |||
}, | |||
@@ -81,6 +105,18 @@ export default { | |||
</script> | |||
<style lang="scss" scoped> | |||
@keyframes alertAnim { | |||
0% { | |||
@apply text-danger-500; | |||
} | |||
50% { | |||
@apply text-warning-500; | |||
} | |||
100% { | |||
@apply text-danger-500; | |||
} | |||
} | |||
.layout__navbar { | |||
@apply flex bg-dark-500 z-10; | |||
@apply border-b border-dark-100; | |||
@@ -105,6 +141,18 @@ export default { | |||
&:hover { | |||
@apply text-white; | |||
} | |||
&:not(.navbar__item--home) { | |||
&.nuxt-link-active { | |||
@apply text-primary-500; | |||
} | |||
} | |||
&.navbar__item--home { | |||
&.nuxt-link-exact-active { | |||
@apply text-primary-500; | |||
} | |||
} | |||
} | |||
.navbar__icon { | |||
@@ -113,6 +161,21 @@ export default { | |||
&:hover { | |||
@apply text-white; | |||
} | |||
&.nuxt-link-active { | |||
@apply text-primary-500; | |||
} | |||
} | |||
} | |||
.alerts--active { | |||
animation-name: alertAnim; | |||
animation-iteration-count: infinite; | |||
animation-duration: 3s; | |||
animation-timing-function: ease-in-out; | |||
} | |||
.alerts--ok { | |||
@apply text-success-500; | |||
} | |||
</style> |
@@ -135,6 +135,11 @@ export default { | |||
); | |||
background-size: 200% 100%; | |||
animation-name: bgAnim; | |||
animation-iteration-count: infinite; | |||
animation-duration: 10s; | |||
animation-timing-function: ease-in-out; | |||
} | |||
.topbar__greetings { |
@@ -2,7 +2,9 @@ | |||
<div class="photo-run" :class="previewClass" data-private> | |||
<div class="run__preview" :style="previewStyle" /> | |||
<div v-if="run.preferences.body.randomize || run.preferences.body.progressive.enabled" class="run__preferences"> | |||
<div | |||
v-if="run.preferences.body.randomize || run.preferences.body.progressive.enabled" | |||
class="run__preferences"> | |||
<div class="preference"> | |||
<span>Boobs</span> | |||
<span>{{ run.preferences.body.boobs.size | fixedValue }}</span> | |||
@@ -32,39 +34,59 @@ | |||
<div class="run__content"> | |||
<div v-if="run.running" class="content__item"> | |||
<p class="text-white"> | |||
<span><font-awesome-icon icon="running" /></span> | |||
<span> | |||
<font-awesome-icon icon="running" /> | |||
</span> | |||
<span>{{ run.timer.duration }}s</span> | |||
</p> | |||
</div> | |||
<div v-else-if="run.failed" class="content__item"> | |||
<p class="text-danger-500"> | |||
<span><font-awesome-icon icon="exclamation-circle" /></span> | |||
<span> | |||
<font-awesome-icon icon="exclamation-circle" /> | |||
</span> | |||
<span>Error!</span> | |||
</p> | |||
</div> | |||
<div v-else-if="run.finished" class="content__item"> | |||
<p class="text-white"> | |||
<span><font-awesome-icon icon="heart" /></span> | |||
<span> | |||
<font-awesome-icon icon="heart" /> | |||
</span> | |||
<span>{{ run.timer.duration }}s</span> | |||
</p> | |||
</div> | |||
<div v-else class="content__item"> | |||
<p class="text-white"> | |||
<span><font-awesome-icon icon="clock" /></span> | |||
<span> | |||
<font-awesome-icon icon="clock" /> | |||
</span> | |||
</p> | |||
</div> | |||
<div v-show="run.finished && run.outputFile.exists" class="content__item"> | |||
<button v-tooltip="'Save photo'" class="button button--success button--sm" @click.prevent="save"> | |||
<button | |||
v-tooltip="'Open photo'" | |||
class="button button--info button--sm" | |||
@click.prevent="open"> | |||
<font-awesome-icon icon="image" /> | |||
</button> | |||
</div> | |||
<div v-show="run.finished && run.outputFile.exists" class="content__item"> | |||
<button | |||
v-tooltip="'Save photo'" | |||
class="button button--info button--sm" | |||
@click.prevent="save"> | |||
<font-awesome-icon icon="save" /> | |||
</button> | |||
</div> | |||
<div v-show="run.finished" class="content__item"> | |||
<button v-tooltip="'Rerun'" class="button button--danger button--sm" @click.prevent="rerun"> | |||
<button v-tooltip="'Rerun'" class="button button--success button--sm" @click.prevent="rerun"> | |||
<font-awesome-icon icon="undo" /> | |||
</button> | |||
</div> | |||
@@ -76,13 +98,19 @@ | |||
</div> | |||
<div v-show="hasMaskfin" class="content__item"> | |||
<button v-tooltip="'View Maskfin'" class="button button--info button--sm" @click.prevent="$refs.maskfinDialog.showModal()"> | |||
<button | |||
v-tooltip="'View Maskfin'" | |||
class="button button--sm" | |||
@click.prevent="$refs.maskfinDialog.showModal()"> | |||
<font-awesome-icon icon="mask" /> | |||
</button> | |||
</div> | |||
<div class="content__item"> | |||
<button v-tooltip="'View terminal'" class="button button--sm" @click.prevent="$refs.terminalDialog.showModal()"> | |||
<button | |||
v-tooltip="'View terminal'" | |||
class="button button--sm" | |||
@click.prevent="$refs.terminalDialog.showModal()"> | |||
<font-awesome-icon icon="terminal" /> | |||
</button> | |||
</div> | |||
@@ -98,7 +126,10 @@ | |||
<div class="maskfin__description"> | |||
<p>This is the Maskfin, a mask that represents in layers the areas that the algorithm will replace with the fake nude.</p> | |||
<p>Click on the "Add to queue" button to add it as an additional photo, edit the layers with the editor and continue with the nudification. You can also save it to your computer, edit it with an external program and continue the nudification manually.</p> | |||
<p>For more information please consult the <a :href="manualURL" target="_blank">guide</a>.</p> | |||
<p> | |||
For more information please consult the | |||
<a :href="manualURL" target="_blank">guide</a>. | |||
</p> | |||
</div> | |||
<div class="dialog__buttons"> | |||
@@ -121,7 +152,10 @@ | |||
<dialog ref="terminalDialog"> | |||
<div class="dialog__content"> | |||
<div class="terminal"> | |||
<li v-for="(item, index) in run.cli.lines" :key="index" :class="item.css"> | |||
<li | |||
v-for="(item, index) in run.cli.lines" | |||
:key="index" | |||
:class="item.css"> | |||
> {{ item.text }} | |||
</li> | |||
</div> | |||
@@ -137,12 +171,9 @@ | |||
</template> | |||
<script> | |||
import { isNil } from 'lodash' | |||
import { nucleus } from '~/modules/services' | |||
import { dreamtrack } from '~/modules/services' | |||
import { Nudify } from '~/modules/nudify' | |||
const { showSaveDialogSync } = $provider.api.dialog | |||
export default { | |||
filters: { | |||
size(value) { | |||
@@ -185,7 +216,10 @@ export default { | |||
}, | |||
manualURL() { | |||
return nucleus.urls?.docs?.manual || 'https://time.dreamnet.tech/docs/guide/upload' | |||
return dreamtrack.get( | |||
'urls.docs.manual', | |||
'https://time.dreamnet.tech/docs/guide/upload', | |||
) | |||
}, | |||
}, | |||
@@ -194,6 +228,10 @@ export default { | |||
this.run.outputFile.save(this.run.outputName) | |||
}, | |||
open() { | |||
this.run.outputFile.openItem() | |||
}, | |||
rerun() { | |||
this.run.add() | |||
}, | |||
@@ -215,10 +253,10 @@ export default { | |||
<style lang="scss" scoped> | |||
.photo-run { | |||
@apply relative border-2 border-dark-100; | |||
background-image: url('~@/assets/images/curls.png'); /* Background pattern from Toptal Subtle Patterns */ | |||
@apply relative border-2 border-dark-500; | |||
background-image: url("~@/assets/images/curls.png"); /* Background pattern from Toptal Subtle Patterns */ | |||
min-height: 512px; | |||
transition: border-color .2s linear; | |||
transition: border-color 0.2s linear; | |||
&.run--failed { | |||
@apply border-danger-500; | |||
@@ -235,6 +273,8 @@ export default { | |||
} | |||
&:hover { | |||
@apply border-primary-300; | |||
.run__content, | |||
.run__preferences { | |||
@apply opacity-100; | |||
@@ -244,16 +284,16 @@ export default { | |||
.run__preview { | |||
@apply absolute opacity-0 left-0 right-0 top-0 bottom-0 z-10; | |||
@apply bg-contain bg-center bg-no-repeat; | |||
transition: opacity .3s linear; | |||
transition: opacity 0.3s linear; | |||
} | |||
} | |||
.run__content, | |||
.run__preferences { | |||
@apply absolute z-20; | |||
@apply flex opacity-0 bg-dark-500-80 w-full; | |||
@apply flex opacity-0 bg-dark-800-80 w-full; | |||
backdrop-filter: blur(6px); | |||
transition: opacity .1s linear; | |||
transition: opacity 0.1s linear; | |||
} | |||
.run__preferences { |
@@ -21,13 +21,6 @@ | |||
<div id="uploader-methods" class="box box--items"> | |||
<div class="box__content"> | |||
<box-item | |||
label="Web" | |||
icon="globe" | |||
:is-link="true" | |||
:class="{'box__item--active': selectionId === 0}" | |||
@click="selectionId = 0" /> | |||
<box-item | |||
label="Instagram" | |||
:icon="['fab', 'instagram']" | |||
@@ -35,6 +28,13 @@ | |||
:class="{'box__item--active': selectionId === 1}" | |||
@click="selectionId = 1" /> | |||
<box-item | |||
label="Web" | |||
icon="globe" | |||
:is-link="true" | |||
:class="{'box__item--active': selectionId === 0}" | |||
@click="selectionId = 0" /> | |||
<box-item | |||
label="File" | |||
icon="file" | |||
@@ -72,11 +72,12 @@ | |||
<input v-model="webAddress" type="url" class="input mb-2" placeholder="https://" data-private="lipsum"> | |||
<p class="help"> | |||
It must end in a valid extension (jpg, png, gif) | |||
Enter the web address of a photo that ends in a valid extension. <i>(jpg, png, gif)</i> | |||
</p> | |||
<button class="button" @click="openUrl"> | |||
Submit | |||
<span class="icon"><font-awesome-icon icon="globe" /></span> | |||
<span>Submit</span> | |||
</button> | |||
</div> | |||
@@ -85,11 +86,12 @@ | |||
<input v-model="instagramPhoto" type="url" class="input mb-2" placeholder="https://www.instagram.com/p/dU4fHDw-Ho/" data-private="lipsum"> | |||
<p class="help"> | |||
Enter the web address or Media ID of an Instagram photo. | |||
Enter the web address or ID of an Instagram photo. | |||
</p> | |||
<button class="button" @click="openInstagramPhoto"> | |||
Submit | |||
<span class="icon"><font-awesome-icon :icon="['fab', 'instagram']" /></span> | |||
<span>Submit</span> | |||
</button> | |||
</div> | |||
@@ -119,7 +121,7 @@ | |||
</button> | |||
<p class="help"> | |||
Select one or more folders on your computer. All valid photos inside will be uploaded. | |||
Select a folder from your computer. All valid photos inside will be uploaded. | |||
</p> | |||
</div> | |||
</div> | |||
@@ -129,7 +131,7 @@ | |||
<script> | |||
import { | |||
isEmpty, startsWith, map, | |||
isEmpty, startsWith, map, toNumber, | |||
} from 'lodash' | |||
import { Nudify } from '~/modules/nudify' | |||
import { tutorial } from '~/modules' | |||
@@ -148,11 +150,22 @@ export default { | |||
}, | |||
data: () => ({ | |||
selectionId: 0, | |||
selectionId: 1, | |||
webAddress: '', | |||
instagramPhoto: '', | |||
}), | |||
watch: { | |||
selectionId(value) { | |||
localStorage.uploadSelectionId = value | |||
}, | |||
}, | |||
created() { | |||
this.selectionId = localStorage.uploadSelectionId || 1 | |||
this.selectionId = toNumber(this.selectionId) | |||
}, | |||
mounted() { | |||
tutorial.upload() | |||
}, | |||
@@ -170,7 +183,7 @@ export default { | |||
const paths = map(files, 'path') | |||
consola.track('FILE') | |||
consola.track('UPLOAD_FILE') | |||
this.addFiles(paths) | |||
@@ -187,7 +200,7 @@ export default { | |||
Nudify.addUrl(this.webAddress) | |||
consola.track('URL') | |||
consola.track('UPLOAD_URL') | |||
this.webAddress = '' | |||
}, | |||
@@ -214,7 +227,7 @@ export default { | |||
Nudify.addUrl(post.downloadUrl) | |||
consola.track('INSTAGRAM') | |||
consola.track('UPLOAD_INSTAGRAM') | |||
this.instagramPhoto = '' | |||
}, | |||
@@ -244,7 +257,7 @@ export default { | |||
.selection__content__body { | |||
@apply text-center; | |||
width: 50%; | |||
width: 70%; | |||
} | |||
.input { | |||
@@ -252,7 +265,7 @@ export default { | |||
} | |||
.help { | |||
@apply text-sm; | |||
@apply text-sm text-generic-700; | |||
&:not(:last-child) { | |||
@apply mb-4; |
@@ -9,7 +9,7 @@ | |||
<font-awesome-icon icon="external-link-square-alt" /> | |||
</button> | |||
<button v-show="!photo.running && !photo.waiting" v-tooltip="'Add to Queue'" @click="add"> | |||
<button v-show="photo.pending" v-tooltip="'Add to Queue'" @click="add"> | |||
<font-awesome-icon icon="play" /> | |||
</button> | |||
@@ -80,7 +80,8 @@ export default { | |||
<style lang="scss" scoped> | |||
.photo { | |||
@apply w-1/2 relative border-2 border-transparent; | |||
@apply w-1/2 relative border-2 border-dark-300; | |||
background-image: url("~@/assets/images/curls.png"); | |||
height: 150px; | |||
will-change: transform; | |||
@@ -93,8 +94,6 @@ export default { | |||
} | |||
&:hover { | |||
@apply bg-dark-900; | |||
.photo__content { | |||
@apply opacity-100; | |||
} |
@@ -2,13 +2,15 @@ | |||
<section class="box box--items"> | |||
<div class="box__content"> | |||
<box-item :description="`Value: ${currentValue.size}`" :label="`${label} size`"> | |||
<VueSlider v-model="currentValue.size" :min="min" :max="max" :interval="0.05" /> | |||
<div :style="{ opacity: body.randomize ? 0.3 : 1.0 }"> | |||
<VueSlider v-model="currentValue.size" :min="min" :max="max" :interval="0.05" /> | |||
</div> | |||
</box-item> | |||
<box-item | |||
v-show="!body.randomize && body.progressive.enabled" | |||
label="Progressive." | |||
description="Increase the value progressively in each run"> | |||
label="Progressive?" | |||
description="Increase this body part progressively in each run."> | |||
<select v-model="currentValue.progressive" class="input"> | |||
<option :value="true"> | |||
Enabled | |||
@@ -21,8 +23,8 @@ | |||
<div v-show="body.randomize"> | |||
<box-item | |||
label="Randomize." | |||
description="Randomize the value in each run."> | |||
label="Randomize?" | |||
description="Randomize this body part in each run."> | |||
<select v-model="currentValue.randomize.enabled" class="input"> | |||
<option :value="true"> | |||
Enabled | |||
@@ -34,7 +36,7 @@ | |||
</box-item> | |||
<box-item | |||
label="Range." | |||
label="Random range" | |||
:description="`Min: ${currentValue.randomize.min} - Max: ${currentValue.randomize.max}`"> | |||
<VueSlider | |||
v-model="randomizeRange" |
@@ -83,7 +83,7 @@ | |||
<box-item | |||
id="preferences-advanced-scale" | |||
label="Scale method." | |||
description="Method that will be used to scale the photo. Each option can offer different results in each photo."> | |||
description="Indicates how the photo will be scaled, this changes the quality of the result dramatically."> | |||
<select v-model="currentValue.advanced.scaleMode" class="input"> | |||
<option value="none"> | |||
None | |||
@@ -101,7 +101,7 @@ | |||
Overlay | |||
</option> | |||
<option value="cropjs"> | |||
Manual crop | |||
Crop | |||
</option> | |||
</select> | |||
</box-item> |
@@ -13,12 +13,12 @@ | |||
</template> | |||
<script> | |||
import { nucleus } from '~/modules/services' | |||
import { dreamtrack } from '~/modules/services' | |||
export default { | |||
computed: { | |||
twitterUrl() { | |||
return nucleus.urls?.social?.twitter || 'https://twitter.com/DreamNetTechno' | |||
return dreamtrack.get('urls.social.twitter', 'https://twitter.com/DreamNetTechno') | |||
}, | |||
}, | |||
} |
@@ -2,15 +2,16 @@ | |||
<!-- Cannot update, only show the version... --> | |||
<box-item | |||
v-if="!updater.enabled" | |||
v-tooltip="{ content: 'Installed version', placement: 'bottom' }" | |||
:label="currentVersion" | |||
icon="globe" /> | |||
icon="rocket" /> | |||
<!-- Updated! --> | |||
<box-item | |||
v-else-if="!updater.available" | |||
:label="`${projectTitle} is up to date.`" | |||
:description="currentVersion" | |||
icon="globe" /> | |||
icon="rocket" /> | |||
<!-- Update available --> | |||
<box-item |
@@ -26,7 +26,7 @@ | |||
<script> | |||
import { isNil, startsWith } from 'lodash' | |||
import { nucleus } from '~/modules/services' | |||
import { dreamtrack } from '~/modules/services' | |||
import { dream } from '~/modules' | |||
const { shell } = $provider.api | |||
@@ -92,7 +92,7 @@ export default { | |||
if (startsWith(this.href, '/')) { | |||
this.$router.push(this.href) | |||
} else { | |||
nucleus.track('EXTERNAL_LINK', { href: this.href }) | |||
dreamtrack.track('CLICK_LINK', { href: this.href }) | |||
shell.openExternal(this.href) | |||
} | |||
} |
@@ -1,126 +0,0 @@ | |||
<template> | |||
<itemtag :version="version" :tag="tag" :href="href" class="box-section-item"> | |||
<slot name="icon"> | |||
<figure v-if="isURL(icon)" class="item-icon"> | |||
<img :src="icon"> | |||
</figure> | |||
<span v-else-if="icon" class="item-icon">{{ icon }}</span> | |||
</slot> | |||
<div class="item-text"> | |||
<p class="item-label"> | |||
{{ label }} | |||
</p> | |||
<slot name="description"> | |||
<p v-if="description" class="item-description"> | |||
{{ description }} | |||
</p> | |||
</slot> | |||
</div> | |||
<div class="item-extra"> | |||
<slot /> | |||
</div> | |||
</itemtag> | |||
</template> | |||
<script> | |||
import _ from 'lodash' | |||
export default { | |||
components: { | |||
itemtag: { | |||
name: 'itemtag', | |||
props: ['tag', 'href', 'version'], | |||
computed: { | |||
isVisible() { | |||
if (_.isNil(this.version)) { | |||
return true | |||
} | |||
return this.version === this.$dream.version | |||
}, | |||
}, | |||
render(createElement) { | |||
if (!this.isVisible) { | |||
return null | |||
} | |||
return createElement( | |||
this.tag, | |||
{ | |||
props: { | |||
href: this.href, | |||
}, | |||
}, | |||
this.$slots.default, | |||
) | |||
}, | |||
}, | |||
}, | |||
props: { | |||
label: { | |||
type: String, | |||
required: true, | |||
}, | |||
description: { | |||
type: String, | |||
default: '', | |||
}, | |||
icon: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
href: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
version: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
}, | |||
computed: { | |||
tag() { | |||
let tag = 'div' | |||
if (!_.isNil(this.href)) { | |||
tag = 'app-external-link' | |||
} | |||
return tag | |||
}, | |||
}, | |||
methods: { | |||
isURL(str) { | |||
if (_.isNil(str)) { | |||
return false | |||
} | |||
if (_.startsWith(str, '~')) { | |||
return true | |||
} | |||
const pattern = new RegExp( | |||
'^(https?:\\/\\/)?' // protocol | |||
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name | |||
+ '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address | |||
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path | |||
+ '(\\?[;&a-z\\d%_.~+=-]*)?' // query string | |||
+ '(\\#[-a-z\\d_]*)?$', | |||
'i', | |||
) // fragment locator | |||
return !!pattern.test(str) | |||
}, | |||
}, | |||
} | |||
</script> |
@@ -1,8 +1,11 @@ | |||
<template> | |||
<div class="project-update"> | |||
<!-- Update available --> | |||
<div v-if="!updater.update.active" class="update__status"> | |||
{{ updater.latest.tag_name }} available: | |||
<div | |||
v-if="!updater.update.active" | |||
v-tooltip="'New version'" | |||
class="update__status"> | |||
<strong>{{ updater.latest.tag_name }}</strong> | |||
</div> | |||
<!-- Downloading --> | |||
@@ -23,14 +26,14 @@ | |||
<!-- Actions --> | |||
<div class="update__actions"> | |||
<button v-show="!updater.update.active" class="button button--success" @click.prevent="updater.start()"> | |||
Download | |||
Start | |||
</button> | |||
<button v-show="updater.update.active" class="button button--danger" @click.prevent="updater.cancel()"> | |||
Cancel | |||
</button> | |||
<button v-tooltip="'Show a list of links to download the update and install it manually.'" class="button button--info" @click.prevent="$refs.mirrorsDialog.show()"> | |||
<button v-tooltip="'Show a list of links to download the update manually.'" class="button button--info" @click.prevent="$refs.mirrorsDialog.show()"> | |||
Mirrors | |||
</button> | |||
</div> |
@@ -10,9 +10,9 @@ | |||
import Vue from 'vue' | |||
import Title from './AppTitle' | |||
import Update from './AppUpdate' | |||
import BoxItem from './BoxItem' | |||
import AppPhoto from './AppPhoto' | |||
import AppNews from './AppNews' | |||
import BoxItem from './BoxItem' | |||
import ProjectUpdate from './ProjectUpdate' | |||
Vue.component('app-title', Title) |
@@ -14,7 +14,9 @@ import Logger from '@dreamnet/logplease' | |||
import fs from 'fs-extra' | |||
import { AppError } from './modules/app-error' | |||
import { system } from './modules/tools/system' | |||
import { getPath } from './modules/tools/paths' | |||
import { | |||
getPath, getModelsPath, getMasksPath, getAppPath, | |||
} from './modules/tools/paths' | |||
import { settings, ngrok } from './modules' | |||
import config from '~/nuxt.config' | |||
@@ -23,7 +25,7 @@ const logger = Logger.create('electron') | |||
// NuxtJS root directory | |||
config.rootDir = dirname(dirname(__dirname)) | |||
if (process.env.name === 'production') { | |||
if (process.env.NODE_ENV === 'production') { | |||
// make sure that the working directory is where the executable is | |||
process.chdir(getPath('exe', '..')) | |||
} | |||
@@ -49,7 +51,8 @@ class DreamApp { | |||
logger.info('Booting...') | |||
logger.debug(`Enviroment: ${process.env.name}`) | |||
logger.debug(`Enviroment: ${process.env.NODE_ENV}`) | |||
logger.debug(`Portable: ${process.env.BUILD_PORTABLE}`) | |||
logger.debug(`App Path: ${app.getAppPath()}`) | |||
logger.debug(`Exe Path: ${app.getPath('exe')}`) | |||
@@ -74,6 +77,10 @@ class DreamApp { | |||
// https://pracucci.com/electron-slow-background-performances.html | |||
app.commandLine.appendSwitch('disable-renderer-backgrounding') | |||
if (process.env.BUILD_PORTABLE) { | |||
this.bootPortable() | |||
} | |||
// user settings. | |||
await settings.boot() | |||
@@ -84,6 +91,32 @@ class DreamApp { | |||
} | |||
} | |||
/** | |||
* | |||
*/ | |||
static bootPortable() { | |||
// Portable component files | |||
fs.ensureDirSync(getAppPath('AppData')) | |||
const settingsPath = getPath('userData', 'settings.json') | |||
const portableSettingsPath = getAppPath('AppData', 'settings.json') | |||
const powerPath = getPath('userData', 'dreampower') | |||
const portablePowerPath = getAppPath('AppData', 'dreampower') | |||
try { | |||
if (fs.existsSync(settingsPath)) { | |||
fs.moveSync(settingsPath, portableSettingsPath) | |||
} | |||
if (fs.existsSync(powerPath)) { | |||
fs.moveSync(powerPath, portablePowerPath) | |||
} | |||
} catch (error) { | |||
logger.warn('Portable boot fail!', error) | |||
} | |||
} | |||
/** | |||
* Start the app! | |||
*/ | |||
@@ -104,6 +137,9 @@ class DreamApp { | |||
// https://github.com/sindresorhus/electron-util#enforcemacosapplocation-macos | |||
enforceMacOSAppLocation() | |||
// PyTorch does not have support for GPU in macOS | |||
settings.processing.device = 'CPU' | |||
} | |||
// application exit. | |||
@@ -175,7 +211,7 @@ class DreamApp { | |||
this.createDirs() | |||
/* | |||
if (process.env.name === 'development') { | |||
if (process.env.NODE_ENV === 'development') { | |||
const address = await ngrok.connect() | |||
logger.debug(`Proxy for debugging: ${address}`) | |||
} | |||
@@ -189,7 +225,7 @@ class DreamApp { | |||
static async shutdown() { | |||
logger.debug('Shutting down services...') | |||
if (process.env.name === 'development') { | |||
if (process.env.NODE_ENV === 'development') { | |||
await ngrok.disconnect() | |||
} | |||
} | |||
@@ -280,8 +316,8 @@ class DreamApp { | |||
*/ | |||
static createDirs() { | |||
const dirs = [ | |||
resolve(settings.folders.models, 'Uncategorized'), | |||
settings.folders.masks, | |||
getModelsPath('Uncategorized'), | |||
getMasksPath(), | |||
] | |||
dirs.forEach((dir) => { |
@@ -12,7 +12,7 @@ import { isNil } from 'lodash' | |||
let address | |||
export async function connect() { | |||
if (process.env.name !== 'development') { | |||
if (process.env.NODE_ENV !== 'development') { | |||
return null | |||
} | |||
@@ -39,6 +39,10 @@ class Settings { | |||
* @type {string} | |||
*/ | |||
get path() { | |||
if (process.env.BUILD_PORTABLE) { | |||
return paths.getAppPath('AppData', 'settings.json') | |||
} | |||
return paths.getPath('userData', 'settings.json') | |||
} | |||
@@ -112,7 +116,7 @@ class Settings { | |||
*/ | |||
_loadDefault() { | |||
const uuid = require('uuid') | |||
const hasGPU = system.graphics.length > 0 | |||
const hasGPU = process.platform === 'darwin' ? false : system.graphics.length > 0 | |||
const cores = round(system.cpu?.cores / 2) || 1 | |||
this.payload = { | |||
@@ -144,7 +148,7 @@ class Settings { | |||
folders: { | |||
cropped: paths.getPath('temp'), | |||
models: paths.getPath('pictures', 'DreamTime'), | |||
models: paths.getPath('userData', 'Pictures'), | |||
masks: paths.getPath('temp'), | |||
cli: paths.getPath('userData', 'dreampower'), | |||
}, | |||
@@ -158,7 +162,7 @@ class Settings { | |||
device: hasGPU ? 'GPU' : 'CPU', | |||
gpus: [0], | |||
cores, | |||
usePython: process.env.NODE_ENV === 'development', | |||
usePython: false, | |||
}, | |||
preferences: { |
@@ -28,8 +28,6 @@ export function getPath(name, ...args) { | |||
* @param {...string} args | |||
*/ | |||
export const getAppPath = (...args) => { | |||
// return join(app.getAppPath(), ...args) | |||
if (process.platform === 'darwin') { | |||
// /Applications/DreamTime.app/Contents/MacOS/DreamTime | |||
// /Applications/DreamTime.app/Contents | |||
@@ -48,10 +46,12 @@ export const getAppResourcesPath = (...args) => { | |||
} | |||
export const getPowerPath = (...args) => { | |||
let folder = settings.folders.cli | |||
let folder | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('userData', 'dreampower') | |||
if (process.env.BUILD_PORTABLE) { | |||
folder = getAppPath('AppData', 'dreampower') | |||
} else { | |||
folder = settings.folders.cli | |||
} | |||
return join(folder, ...args) | |||
@@ -60,24 +60,40 @@ export const getPowerPath = (...args) => { | |||
export const getCheckpointsPath = (...args) => getPowerPath('checkpoints', ...args) | |||
export const getCropPath = (...args) => { | |||
let folder = settings.folders.cropped | |||
let folder | |||
attempt(() => fs.ensureDirSync(folder)) | |||
if (!fs.existsSync(folder)) { | |||
if (process.env.BUILD_PORTABLE) { | |||
folder = getPath('temp') | |||
if (!fs.existsSync(folder)) { | |||
folder = getAppPath('AppData', 'Temp') | |||
} | |||
} else { | |||
folder = settings.folders.cropped | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('temp') | |||
} | |||
} | |||
attempt(() => fs.ensureDirSync(folder)) | |||
return join(folder, ...args) | |||
} | |||
export const getModelsPath = (...args) => { | |||
let folder = settings.folders.models | |||
let folder | |||
if (process.env.BUILD_PORTABLE) { | |||
folder = getAppPath('AppData', 'Pictures') | |||
} else { | |||
folder = settings.folders.models | |||
} | |||
attempt(() => fs.ensureDirSync(folder)) | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('userData', 'models') | |||
folder = getPath('userData', 'Pictures') | |||
} | |||
if (!fs.existsSync(folder)) { | |||
@@ -88,17 +104,27 @@ export const getModelsPath = (...args) => { | |||
} | |||
export const getMasksPath = (...args) => { | |||
let folder = settings.folders.masks | |||
let folder | |||
attempt(() => fs.ensureDirSync(folder)) | |||
if (process.env.BUILD_PORTABLE) { | |||
folder = getPath('temp') | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('userData', 'masks') | |||
} | |||
if (!fs.existsSync(folder)) { | |||
folder = getAppPath('AppData', 'Temp') | |||
} | |||
} else { | |||
folder = settings.folders.masks | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('temp') | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('userData', 'masks') | |||
} | |||
if (!fs.existsSync(folder)) { | |||
folder = getPath('temp') | |||
} | |||
} | |||
attempt(() => fs.ensureDirSync(folder)) | |||
return join(folder, ...args) | |||
} |
@@ -12,6 +12,8 @@ import { | |||
} from 'lodash' | |||
import si from 'systeminformation' | |||
import isOnline from 'is-online' | |||
import { settings } from '../settings' | |||
const logger = require('@dreamnet/logplease').create('system') | |||
@@ -91,12 +93,11 @@ class System { | |||
async takeSnapshot() { | |||
logger.info('Taking snapshot...') | |||
const [load, cpuSpeed, cpuTemperature, memory, online] = await Promise.all([ | |||
const [load, cpuSpeed, cpuTemperature, memory] = await Promise.all([ | |||
si.currentLoad(), | |||
si.cpuCurrentspeed(), | |||
si.cpuTemperature(), | |||
si.mem(), | |||
isOnline(), | |||
]) | |||
this.snapshot = { | |||
@@ -106,14 +107,15 @@ class System { | |||
temperature: cpuTemperature, | |||
}, | |||
memory, | |||
online, | |||
settings: settings.payload, | |||
} | |||
logger.info(`Current load:`, load) | |||
logger.info(`CPU Speed:`, cpuSpeed) | |||
logger.info(`CPU Temperature:`, cpuTemperature) | |||
logger.info(`Memory:`, memory) | |||
logger.info(`Online: ${online}`) | |||
return this.snapshot | |||
} | |||
/** |
@@ -8,12 +8,14 @@ | |||
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019. | |||
import { settings, requirements } from '~/modules/system' | |||
import { consola } from '~/modules/consola' | |||
export default function ({ route, redirect }) { | |||
const { wizard } = settings | |||
if (!wizard.welcome) { | |||
if (route.path !== '/wizard/welcome') { | |||
consola.track('WELCOME') | |||
redirect('/wizard/welcome') | |||
} | |||
@@ -61,6 +61,8 @@ export const UploadMixin = { | |||
properties: ['openDirectory'], | |||
}) | |||
consola.track('UPLOAD_FOLDER') | |||
this.addFiles(paths) | |||
}, | |||
@@ -110,12 +112,6 @@ export const UploadMixin = { | |||
const { files } = event.dataTransfer | |||
const url = event.dataTransfer.getData('url') | |||
consola.debug('onDrop', { | |||
event, | |||
files, | |||
url, | |||
}) | |||
if (url.length > 0) { | |||
Nudify.addUrl(url) | |||
consola.track('DROP_URL') |
@@ -53,11 +53,7 @@ | |||
}, | |||
{ | |||
"query": "cv2.error", | |||
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well located." | |||
}, | |||
{ | |||
"query": "OpenCV", | |||
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well located." | |||
"message": "There was a problem trying to load the photo. Please ensure that it is a valid photo, that it has not been deleted or that the Cropper/Overlay are well positioned." | |||
}, | |||
{ | |||
"query": "is not a valid file or directory or url", | |||
@@ -71,6 +67,10 @@ | |||
"query": "invalid extension format", | |||
"message": "There was a problem trying to load the photo. Please make sure it is a valid photo and has not been deleted." | |||
}, | |||
{ | |||
"query": "The file might be corrupted", | |||
"message": "There was a problem trying to load the photo. Please make sure it is a valid photo and has not been deleted." | |||
}, | |||
{ | |||
"query": "not compiled with CUDA", | |||
"message": "You have installed the CPU-only version of DreamPower. Please reinstall DreamPower to get GPU support." |
@@ -93,16 +93,14 @@ export class Consola { | |||
* @param {Object} payload | |||
*/ | |||
track(event, payload = {}) { | |||
const { nucleus, logrocket } = require('../services') | |||
const { dreamtrack, logrocket } = require('../services') | |||
const category = this.category.toUpperCase() | |||
if (nucleus.enabled) { | |||
nucleus.track(`${category}.${event}`, payload) | |||
if (dreamtrack.enabled) { | |||
dreamtrack.track(`${event}`, payload) | |||
} | |||
if (logrocket.enabled) { | |||
logrocket.track(`${category}.${event}`) | |||
logrocket.track(`${event}`) | |||
} | |||
return this |
@@ -14,10 +14,8 @@ import he from 'he' | |||
import Swal from 'sweetalert2/dist/sweetalert2.js' | |||
import Logger from '@dreamnet/logplease' | |||
import isPlainObject from 'lodash/isPlainObject' | |||
import { mapStackTrace } from 'sourcemapped-stacktrace' | |||
import { HandledError } from './errors' | |||
import { settings } from '../system/settings' | |||
const { system } = $provider | |||
const LEVELS = [ | |||
'debug', | |||
@@ -171,11 +169,11 @@ export class Log { | |||
} | |||
if (this.isError) { | |||
await this.report() | |||
} | |||
this.report() | |||
if (this.isError && !this.quiet) { | |||
this.show() | |||
if (!this.quiet) { | |||
this.show() | |||
} | |||
} | |||
} | |||
@@ -187,49 +185,81 @@ export class Log { | |||
return this | |||
} | |||
// System snapshot. | |||
await system.takeSnapshot() | |||
const { rollbar, logrocket, dreamtrack } = require('../services') | |||
if (!rollbar.enabled) { | |||
return this | |||
} | |||
const error = await this.getSourceMapError() | |||
const snapshotUrl = await dreamtrack.takeSnapshot() | |||
const sessionUrl = logrocket.sessionURL | |||
let rollbarUrl | |||
// Bug tracking. | |||
try { | |||
const response = rollbar[this.level](this.title || this.message, error, { | |||
...this.extra, | |||
sessionUrl, | |||
snapshotUrl, | |||
}) | |||
const { rollbar, logrocket } = require('../services') | |||
rollbarUrl = `https://rollbar.com/occurrence/uuid/?uuid=${response?.uuid}` | |||
let rollbarResponse | |||
this.reported = true | |||
} catch (err) { | |||
this.logger.warn('Rollbar report fail!', err) | |||
} | |||
// bug tracking. | |||
if (rollbar.enabled) { | |||
// Session tracking. | |||
if (logrocket.enabled && isError(error)) { | |||
try { | |||
rollbarResponse = rollbar[this.level](this.title || this.message, this.error, { | |||
...this.extra, | |||
sessionURL: logrocket.sessionURL, | |||
snapshot: { | |||
system: system.snapshot, | |||
settings: settings.payload, | |||
logrocket.captureException(error, { | |||
extra: { | |||
rollbarUrl, | |||
snapshotUrl, | |||
}, | |||
}) | |||
this.reported = true | |||
} catch (err) { | |||
this.logger.warn('Rollbar report fail!', err) | |||
this.logger.warn('LogRocket report fail!', err) | |||
} | |||
} | |||
// session tracking. | |||
if (logrocket.enabled && isError(this.error)) { | |||
try { | |||
logrocket.captureException(this.error, { | |||
extra: { | |||
rollbarURL: `https://rollbar.com/occurrence/uuid/?uuid=${rollbarResponse?.uuid}`, | |||
}, | |||
}) | |||
this.reported = true | |||
} catch (err) { | |||
this.logger.warn('LogRocket report fail!', err) | |||
} | |||
} | |||
const urls = { | |||
snapshotUrl, | |||
sessionUrl, | |||
rollbarUrl, | |||
} | |||
dreamtrack.track('ERROR', urls) | |||
consola.debug('The error has been reported.') | |||
consola.debug(urls) | |||
return this | |||
} | |||
async getSourceMapError() { | |||
if (!isError(this.error)) { | |||
return this.error | |||
} | |||
const { error } = this | |||
const getStack = () => new Promise((resolve) => { | |||
mapStackTrace(error.stack, (stack) => { | |||
resolve(`${error.message}\n${stack.join('\n')}`) | |||
}, { cacheGlobally: true }) | |||
}) | |||
error.stack = await getStack() | |||
return error | |||
} | |||
/** | |||
* | |||
*/ | |||
@@ -249,7 +279,7 @@ export class Log { | |||
title: title || this.title || 'Unexpected problem!', | |||
html, | |||
icon: this.isError ? 'error' : 'warning', | |||
footer: this.reported ? `<code>๐ This problem has been reported to DreamNet.<br>It will be fixed as soon as possible.</code>` : null, | |||
// footer: this.reported ? `<code>๐ This problem has been reported to DreamNet.<br>It will be fixed as soon as possible.</code>` : null, | |||
}) | |||
this.showed = true |
@@ -7,6 +7,8 @@ | |||
// | |||
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2020. | |||
import { isNil } from 'lodash' | |||
const { getAppPath, getPowerPath, getPath } = $provider.paths | |||
const { shell } = $provider.api | |||
@@ -31,6 +33,11 @@ export default { | |||
*/ | |||
version: `v${process.env.npm_package_version}`, | |||
/** | |||
* @type {boolean} | |||
*/ | |||
isPortable: !isNil(process.env.BUILD_PORTABLE), | |||
/** | |||
* | |||
*/ |
@@ -6,7 +6,7 @@ import { getMetadata } from '~/workers/fs' | |||
const consola = Consola.create('file') | |||
const { fs } = $provider | |||
const { dialog } = $provider.api | |||
const { dialog, shell } = $provider.api | |||
const { getPath } = $provider.paths | |||
/** | |||
@@ -110,7 +110,7 @@ export class File { | |||
if (create) { | |||
attempt(() => { | |||
fs.unlinkSync(filepath) | |||
consola.debug(`File deleted: ${filepath}`) | |||
consola.debug(`Deleted: ${filepath}`) | |||
}) | |||
} | |||
@@ -147,9 +147,9 @@ export class File { | |||
this.md5 = metadata.md5 | |||
if (this.exists) { | |||
consola.debug(`File opened: ${this.path} (${this.md5})`) | |||
consola.debug(`Opened: ${this.path} (${this.md5})`) | |||
} else { | |||
consola.debug(`File opened: ${this.path} (does not exist)`) | |||
consola.debug(`Opened: ${this.path} (does not exist)`) | |||
} | |||
return this | |||
@@ -166,7 +166,7 @@ export class File { | |||
fs.unlinkSync(this.path) | |||
await this.open() | |||
consola.debug(`File deleted: ${this.fullname}`) | |||
consola.debug(`Deleted: ${this.fullname}`) | |||
return this | |||
} | |||
@@ -191,7 +191,7 @@ export class File { | |||
} | |||
fs.copySync(this.path, destination) | |||
consola.debug(`File copied: ${this.path} -> ${destination}`) | |||
consola.debug(`Copied: ${this.path} -> ${destination}`) | |||
return this | |||
} | |||
@@ -220,4 +220,12 @@ export class File { | |||
return this | |||
} | |||
openItem() { | |||
if (!fs.existsSync(this.path)) { | |||
throw new Warning('The photo no longer exists.', 'Could not open the photo because it has been deleted, this could be caused due to cleaning or antivirus programs.') | |||
} | |||
shell.openItem(this.path) | |||
} | |||
} |
@@ -155,6 +155,11 @@ export class Photo { | |||
return this.file.mimetype !== 'image/gif' | |||
} | |||
get isScaleModeCorrected() { | |||
const { scaleMode } = this.preferences.advanced | |||
return scaleMode !== this.scaleMode | |||
} | |||
get scaleMode() { | |||
const { scaleMode } = this.preferences.advanced | |||
@@ -165,8 +170,6 @@ export class Photo { | |||
} | |||
if (!this.fileCrop.exists) { | |||
this.consola.warn('Wanted to use the cropper but the file does not exist.') | |||
// The Cropper has not been used. | |||
return 'auto-rescale' | |||
} | |||
@@ -270,6 +273,10 @@ export class Photo { | |||
imageSmoothingQuality: 'high', | |||
}) | |||
if (!canvas) { | |||
throw new Warning('The cropper has failed.', 'There was a problem executing the cropper, please open the tool and try again.') | |||
} | |||
const dataURL = canvas.toDataURL(this.fileCrop.mimetype, 1) | |||
await this.fileCrop.writeDataURL(dataURL) | |||
@@ -465,6 +472,36 @@ export class Photo { | |||
Nudify.forget(this) | |||
} | |||
/** | |||
* | |||
*/ | |||
track() { | |||
const { useColorTransfer, transformMode } = this.preferences.advanced | |||
const { randomize, progressive } = this.preferences.body | |||
consola.track('DREAM_START') | |||
if (transformMode === 'export-maskfin') { | |||
consola.track('DREAM_EXPORT_MASKFIN') | |||
} | |||
if (transformMode === 'import-maskfin') { | |||
consola.track('DREAM_IMPORT_MASKFIN') | |||
} | |||
if (useColorTransfer) { | |||
consola.track('DREAM_COLOR_TRANSFER') | |||
} | |||
if (randomize) { | |||
consola.track('DREAM_RANDOMIZE') | |||
} | |||
if (progressive.enabled) { | |||
consola.track('DREAM_PROGRESSIVE') | |||
} | |||
} | |||
/** | |||
* Nudification start. | |||
* This should only be called from the queue. | |||
@@ -477,7 +514,7 @@ export class Photo { | |||
await this.syncEditor() | |||
await this.syncCrop() | |||
// this.onStart() | |||
this.track() | |||
for (let it = 1; it <= this.executions; it += 1) { | |||
const run = new PhotoRun(it, this) |
@@ -8,7 +8,7 @@ | |||
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019. | |||
import { | |||
isEmpty, get, | |||
isEmpty, get, cloneDeep, | |||
} from 'lodash' | |||
export class BaseService { | |||
@@ -58,11 +58,13 @@ export class BaseService { | |||
/** | |||
* @param {string} path | |||
*/ | |||
get(path = '') { | |||
get(path = '', defaultValue = null) { | |||
const payload = cloneDeep(this.payload) | |||
if (isEmpty(path)) { | |||
return this.payload | |||
return payload | |||
} | |||
return get(this.payload, path) | |||
return get(payload, path, defaultValue) | |||
} | |||
} |
@@ -0,0 +1,279 @@ | |||
// 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>, 2020. | |||
import { isNil } from 'lodash' | |||
import Ws from '@adonisjs/websocket-client/index' | |||
import Deferred from 'deferred' | |||
import { BaseService } from './base' | |||
import { settings } from '../system/settings' | |||
import { Consola } from '../consola' | |||
const { system } = $provider | |||
const consola = Consola.create('dreamtrack') | |||
const CONNECT_TIMEOUT = 1500 | |||
const SNAPSHOT_TIMEOUT = 3000 | |||
/** | |||
* DreamTrack. | |||
* Analytics and remote settings for DreamTime. | |||
*/ | |||
export class DreamTrackService extends BaseService { | |||
/** | |||
* @type {Object} | |||
*/ | |||
promise | |||
/** | |||
* @type {import('@adonisjs/websocket-client/src/Socket').default} | |||
*/ | |||
channel | |||
/** | |||
* @type {number} | |||
*/ | |||
timeout | |||
/** | |||
* @type {string} | |||
*/ | |||
get host() { | |||
const host = process.env.DREAMTRACK_HOST || 'localhost:3000' | |||
const protocol = host === 'track.dreamnet.tech' ? 'wss' : 'ws' | |||
return `${protocol}://${host}` | |||
} | |||
/** | |||
* @type {boolean} | |||
*/ | |||
get can() { | |||
return system.online && !isNil(this.host) | |||
} | |||
/** | |||
* @type {Object} | |||
*/ | |||
get config() { | |||
return { | |||
reconnectionAttempts: 20, | |||
reconnectionDelay: 500, | |||
} | |||
} | |||
/** | |||
* User information to generate anonymous statistics. | |||
* | |||
* @type {Object} | |||
*/ | |||
get userData() { | |||
return { | |||
userId: settings.user, | |||
version: process.env.npm_package_version, | |||
language: navigator.language, | |||
session: { | |||
graphics: system.graphics, | |||
cpu: system.cpu, | |||
os: system.os, | |||
memory: system.memory.total, | |||
}, | |||
} | |||
} | |||
/** | |||
* Setup service | |||
*/ | |||
setup() { | |||
if (!this.can) { | |||
return Promise.resolve() | |||
} | |||
this.promise = Deferred() | |||
consola.info(`Connecting to ${this.host}...`) | |||
try { | |||
this.timeout = setTimeout(() => { | |||
this.resolve() | |||
}, CONNECT_TIMEOUT) | |||
const ws = Ws(this.host, this.config) | |||
ws.on('open', this.onOpen.bind(this)) | |||
ws.on('close', this.onClosed.bind(this)) | |||
ws.on('reconnect', this.onReconnect.bind(this)) | |||
ws.connect() | |||
/** @type {import('@adonisjs/websocket-client/src/Connection').default} */ | |||
this.service = ws | |||
} catch (err) { | |||
consola.warn('Setup failed!', err) | |||
return Promise.resolve() | |||
} | |||
return this.promise.promise | |||
} | |||
resolve() { | |||
if (!this.promise) { | |||
return | |||
} | |||
this.promise.resolve() | |||
this.promise = null | |||
if (this.timeout) { | |||
clearTimeout(this.timeout) | |||
this.timeout = null | |||
} | |||
} | |||
reject(error) { | |||
if (!this.promise) { | |||
return | |||
} | |||
this.promise.reject(error) | |||
this.promise = null | |||
} | |||
/** | |||
* Connected to the server. | |||
*/ | |||
onOpen() { | |||
consola.info('Connected to the server.') | |||
this.channel = this.service.subscribe('dreamtime:master') | |||
this.channel.on('ready', this.onReady.bind(this)) | |||
this.channel.on('error', (error) => { | |||
consola.warn('Could not subscribe to channel.', error) | |||
this.reject(error) | |||
}) | |||
this.channel.on('settings', this.onSettings.bind(this)) | |||
} | |||
/** | |||
* The connection to the server has been lost. | |||
*/ | |||
onClosed() { | |||
consola.warn('Connection lost.') | |||
this.enabled = false | |||
} | |||
/** | |||
* Reconnect attempt. | |||
* | |||
* @param {number} attempts | |||
*/ | |||
onReconnect(attempts) { | |||
if (!this.promise) { | |||
return | |||
} | |||
if (attempts !== this.config.reconnectionAttempts) { | |||
return | |||
} | |||
// The connection to the server could not be established. | |||
consola.warn('Could not connect to server.') | |||
this.resolve() | |||
} | |||
/** | |||
* Successfully subscribed to the primary channel of the server. | |||
*/ | |||
onReady() { | |||
this.channel.emit('hello', this.userData) | |||
} | |||
/** | |||
* The remote settings of the application has been received. | |||
* | |||
* @param {object} payload Remote settings | |||
*/ | |||
onSettings(payload) { | |||
consola.debug('Remote settings received.') | |||
this.payload = payload | |||
this.enabled = true | |||
this.resolve() | |||
} | |||
/** | |||
* Track an event. | |||
* | |||
* @param {string} name Event name | |||
* @param {object} payload Event data | |||
*/ | |||
track(name, payload = {}) { | |||
if (!this.enabled) { | |||
return this | |||
} | |||
consola.debug(`Event: ${name}`) | |||
this.channel.emit('event', { | |||
name, | |||
payload, | |||
version: process.env.npm_package_version, | |||
}) | |||
return this | |||
} | |||
/** | |||
* Create a snapshot of the system (cpu load, memory, etc.) | |||
* and send it to the server. | |||
* | |||
* @returns {string} | |||
*/ | |||
async takeSnapshot() { | |||
if (!this.enabled) { | |||
return null | |||
} | |||
// Create snapshot. | |||
const snapshot = await system.takeSnapshot() | |||
// Server response promise. | |||
const responsePromise = new Promise((resolve) => { | |||
const timeout = setTimeout(resolve, SNAPSHOT_TIMEOUT) | |||
const handler = (url) => { | |||
clearTimeout(timeout) | |||
this.channel.off('snapshot', handler) | |||
resolve(url) | |||
} | |||
this.channel.on('snapshot', handler) | |||
}) | |||
// Send to server and wait for response. | |||
this.channel.emit('snapshot', snapshot) | |||
const response = await responsePromise | |||
consola.debug(`Snapshot sent to the server: ${response}`) | |||
return response | |||
} | |||
} | |||
export const dreamtrack = DreamTrackService.make() |
@@ -7,6 +7,7 @@ | |||
// | |||
// Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019. | |||
export { nucleus } from './nucleus' | |||
// export { nucleus } from './nucleus' | |||
export { logrocket } from './logrocket' | |||
export { rollbar } from './rollbar' | |||
export { dreamtrack } from './dreamtrack' |
@@ -10,10 +10,12 @@ | |||
import { isString, endsWith } from 'lodash' | |||
import LogRocket from 'logrocket' | |||
import { BaseService } from './base' | |||
import { nucleus } from './nucleus' | |||
import { dreamtrack } from './dreamtrack' | |||
import { settings } from '../system/settings' | |||
import { Consola } from '../consola' | |||
const { system } = $provider | |||
const consola = Consola.create('logrocket') | |||
const privateExtensions = ['.jpg', '.jpeg', '.png', '.gif'] | |||
@@ -27,14 +29,14 @@ class LogRocketService extends BaseService { | |||
* @type {string} | |||
*/ | |||
get accessToken() { | |||
return process.env.LOGROCKET_ACCESS_TOKEN || nucleus.keys?.logrocket | |||
return process.env.LOGROCKET_ACCESS_TOKEN || dreamtrack.get('keys.logrocket') | |||
} | |||
/** | |||
* @type {boolean} | |||
*/ | |||
get can() { | |||
return isString(this.accessToken) && settings.telemetry?.dom && process.env.name === 'production' | |||
return system.online && isString(this.accessToken) && settings.telemetry?.dom // && process.env.NODE_ENV === 'production' | |||
} | |||
/** | |||
@@ -72,7 +74,7 @@ class LogRocketService extends BaseService { | |||
}, | |||
dom: { | |||
isEnabled: true, | |||
baseHref: $provider.ngrok.getAddress() || nucleus.urls?.internal?.cdn, | |||
baseHref: $provider.ngrok.getAddress() || dreamtrack.get('urls.internal.cdn'), | |||
}, | |||
} | |||
} | |||
@@ -92,7 +94,7 @@ class LogRocketService extends BaseService { | |||
this.service = LogRocket | |||
this.enabled = true | |||
consola.info('LogRocket enabled!') | |||
consola.info('Recording session.') | |||
consola.debug(`Access Token: ${this.accessToken}`) | |||
consola.debug(`User: ${settings.user}`) | |||
consola.debug(this.config) |
@@ -13,7 +13,7 @@ import { | |||
import Rollbar from 'rollbar' | |||
import { remote } from 'electron' | |||
import { BaseService } from './base' | |||
import { nucleus } from './nucleus' | |||
import { dreamtrack } from './dreamtrack' | |||
import { Consola } from '../consola' | |||