@@ -1,20 +1,10 @@ | |||
module.exports = { | |||
root: true, | |||
env: { | |||
browser: true, | |||
node: true, | |||
mocha: true | |||
}, | |||
extends: [ | |||
"@nuxtjs", | |||
"airbnb-base", | |||
"plugin:import/errors", | |||
"plugin:import/warnings", | |||
"plugin:promise/recommended", | |||
"plugin:lodash/recommended", | |||
"plugin:vue/recommended", | |||
"plugin:nuxt/recommended", | |||
"plugin:mocha/recommended" | |||
], | |||
globals: { | |||
$provider: false, | |||
AppError: false, | |||
@@ -24,109 +14,84 @@ module.exports = { | |||
consola: false | |||
}, | |||
parserOptions: { | |||
parser: "babel-eslint", | |||
parser: 'babel-eslint', | |||
ecmaVersion: 2020, | |||
allowImportExportEverywhere: true | |||
}, | |||
extends: [ | |||
'@nuxtjs', | |||
'plugin:nuxt/recommended', | |||
'airbnb-base', | |||
'plugin:import/errors', | |||
'plugin:import/warnings', | |||
'plugin:promise/recommended', | |||
'plugin:lodash/recommended', | |||
'plugin:vue/recommended', | |||
'plugin:mocha/recommended' | |||
], | |||
plugins: [ | |||
"import", | |||
"promise", | |||
"lodash", | |||
"vue", | |||
"mocha" | |||
'nuxt', | |||
'import', | |||
'promise', | |||
'lodash', | |||
'vue', | |||
'mocha' | |||
], | |||
root: true, | |||
rules: { | |||
"no-param-reassign": "off", | |||
"class-methods-use-this": "off", | |||
"no-trailing-spaces": "warn", | |||
"comma-dangle": "warn", | |||
"global-require": "off", | |||
"import/default": "warn", | |||
"import/no-webpack-loader-syntax": "off", | |||
"import/order": ['error'], | |||
"import/prefer-default-export": "off", | |||
"import/no-extraneous-dependencies": "off", | |||
"import/named": "warn", | |||
"import/no-cycle": "off", | |||
"promise/no-callback-in-promise": "off", | |||
"promise/catch-or-return": "off", | |||
"linebreak-style": "warn", | |||
"new-parens": "off", | |||
"lodash/import-scope": [ | |||
"off", | |||
"member" | |||
], | |||
"lodash/prefer-constant": "off", | |||
"lodash/prefer-immutable-method": "warn", | |||
"lodash/prefer-includes": "warn", | |||
"lodash/prefer-lodash-method": "off", | |||
"lodash/prefer-lodash-typecheck": "warn", | |||
"lodash/prefer-noop": "off", | |||
"lodash/prefer-spread": "off", | |||
"import/extensions": "off", | |||
"max-len": "off", | |||
"func-names": "off", | |||
"no-await-in-loop": "warn", | |||
"no-console": "warn", | |||
"no-continue": "off", | |||
"no-debugger": "error", | |||
"no-lone-blocks": "error", | |||
"no-restricted-globals": "warn", | |||
"no-restricted-syntax": "off", | |||
"no-shadow": "off", | |||
"no-underscore-dangle": [ | |||
"error", | |||
{ | |||
allowAfterThis: true | |||
} | |||
], | |||
"no-unreachable": "warn", | |||
"no-unused-vars": "warn", | |||
"no-useless-constructor": "warn", | |||
"nuxt/no-globals-in-created": "off", | |||
"object-shorthand": [ | |||
"error", | |||
"always" | |||
], | |||
"padded-blocks": [ | |||
"error", | |||
"never" | |||
], | |||
"prefer-spread": "off", | |||
"quote-props": [ | |||
"error", | |||
"as-needed" | |||
], | |||
quotes: [ | |||
"error", | |||
"single", | |||
{ | |||
allowTemplateLiterals: true | |||
} | |||
], | |||
semi: [ | |||
"error", | |||
"never" | |||
], | |||
"spaced-comment": "warn", | |||
"vue/html-closing-bracket-newline": [ | |||
"warn", | |||
{ | |||
multiline: "never", | |||
singleline: "never" | |||
'import/named': 'error', | |||
'import/no-cycle': 'off', | |||
'import/no-extraneous-dependencies': 'off', | |||
'import/no-webpack-loader-syntax': 'off', | |||
'import/order': 'error', | |||
'import/prefer-default-export': 'off', | |||
'import/no-duplicates': 'off', | |||
'lodash/import-scope': ['error', 'member'], | |||
'lodash/prefer-constant': 'off', | |||
'lodash/prefer-immutable-method': 'warn', | |||
'lodash/prefer-includes': 'warn', | |||
'lodash/prefer-lodash-method': 'off', | |||
'lodash/prefer-lodash-typecheck': 'warn', | |||
'lodash/prefer-noop': 'off', | |||
'lodash/prefer-spread': 'off', | |||
'vue/no-v-html': 'off', | |||
'vue/singleline-html-element-content-newline': 'warn', | |||
'vue/html-closing-bracket-newline': ['warn', { multiline: 'never', singleline: 'never' }], | |||
'vue/max-attributes-per-line': ['warn', { | |||
'singleline': 1, | |||
'multiline': { | |||
'max': 1, | |||
'allowFirstLine': true | |||
} | |||
], | |||
"vue/html-indent": [ | |||
"warn", | |||
2 | |||
], | |||
"vue/html-self-closing": "error", | |||
"vue/no-v-html": "off", | |||
"vue/singleline-html-element-content-newline": "warn", | |||
'nuxt/no-cjs-in-config': 'off' | |||
}], | |||
'nuxt/no-cjs-in-config': 'off', | |||
'nuxt/no-globals-in-created': 'off', | |||
'linebreak-style': 'error', | |||
'max-len': ['warn', { code: 120 }], | |||
'no-await-in-loop': 'warn', | |||
'no-continue': 'off', | |||
'no-param-reassign': 'off', | |||
'no-restricted-globals': 'warn', | |||
'no-restricted-syntax': 'off', | |||
'no-trailing-spaces': 'warn', | |||
'no-tabs': 'error', | |||
'no-undef': 'warn', | |||
'class-methods-use-this': 'off', | |||
'comma-dangle': 'warn', | |||
'func-names': 'off', | |||
'global-require': 'off', | |||
'prefer-arrow-callback': 'off', | |||
'no-underscore-dangle': ['error', { allowAfterThis: true }], | |||
'object-shorthand': ['error', 'always'], | |||
'padded-blocks': ['error', 'never'], | |||
'prefer-spread': 'off', | |||
'promise/no-callback-in-promise': 'off', | |||
'quote-props': ['error', 'as-needed'], | |||
'spaced-comment': 'warn', | |||
'quotes': ['error', 'single'], | |||
'semi': ['error', 'never'] | |||
}, | |||
settings: { | |||
"import/resolver": { | |||
'import/resolver': { | |||
nuxt: {}, | |||
node: {}, | |||
webpack: {} |
@@ -10,13 +10,22 @@ | |||
*/ | |||
.box { | |||
@apply bg-dark-600 shadow mb-6 border-t border-l border-r border-dark-100; | |||
@apply flex flex-col; | |||
@apply pb-3 bg-dark-500 shadow-lg rounded-lg; | |||
&:not(:last-child) { | |||
@apply mb-6; | |||
} | |||
.box__photo { | |||
@apply relative; | |||
@apply bg-cover bg-center; | |||
@apply bg-cover bg-center bg-no-repeat; | |||
min-height: 120px; | |||
&:first-child { | |||
@apply rounded-t-lg; | |||
} | |||
.box__photo__content { | |||
@apply absolute w-full h-full; | |||
@apply bg-black-70; | |||
@@ -24,10 +33,14 @@ | |||
} | |||
.box__header { | |||
@apply px-4 pt-2; | |||
@apply px-6; | |||
&:not(:first-child) { | |||
@apply pt-3; | |||
} | |||
.title { | |||
@apply text-base font-semibold text-generic-200; | |||
@apply font-semibold text-generic-300; | |||
} | |||
.subtitle { | |||
@@ -36,11 +49,27 @@ | |||
} | |||
.box__content { | |||
@apply px-4 py-2; | |||
@apply flex-1 px-6; | |||
&:not(:first-child) { | |||
@apply pt-3; | |||
} | |||
&:not(:last-child) { | |||
@apply pb-3; | |||
} | |||
p { | |||
@apply text-sm mb-3; | |||
} | |||
.item { | |||
@apply px-0; | |||
} | |||
} | |||
.box__footer { | |||
@apply px-4 py-2 text-sm; | |||
@apply px-6 pt-3; | |||
@apply border-t border-dark-400; | |||
} | |||
} |
@@ -11,8 +11,8 @@ | |||
.button { | |||
@apply inline-flex items-center justify-center; | |||
@apply border border-primary-500-30; | |||
@apply px-4 rounded; | |||
@apply border border-primary-500; | |||
@apply px-4 rounded-lg; | |||
@apply text-primary-400 font-semibold uppercase; | |||
@apply outline-none #{!important}; | |||
height: 40px; |
@@ -9,16 +9,7 @@ | |||
* Written by Ivan Bravo Bravo <ivan@dreamnet.tech>, 2019. | |||
*/ | |||
.content-body, | |||
.content__body { | |||
@apply p-6; | |||
} | |||
.content__body { | |||
@apply p-6; | |||
} | |||
.wrapper { | |||
@apply ml-auto mr-auto; | |||
.container { | |||
@apply p-6 ml-auto mr-auto; | |||
max-width: 1920px; | |||
} |
@@ -22,13 +22,13 @@ | |||
} | |||
.input { | |||
@apply border border-dark-300 bg-dark-700; | |||
@apply rounded py-2 px-4 w-full text-generic-300 shadow-inner; | |||
@apply border border-dark-400 bg-dark-500 text-generic-500; | |||
@apply py-2 px-3 rounded w-full shadow-inner; | |||
@include transition('background-color, color'); | |||
outline: none !important; | |||
transition: all .2s ease-in-out; | |||
&:hover, &:focus { | |||
@apply text-generic-100 shadow-inner-md; | |||
@apply text-white bg-dark-400; | |||
} | |||
&::placeholder { | |||
@@ -43,7 +43,3 @@ | |||
@apply text-sm px-2; | |||
} | |||
} | |||
select.input { | |||
@apply cursor-pointer; | |||
} |
@@ -10,8 +10,8 @@ | |||
*/ | |||
.notification { | |||
@apply mb-4 py-2 px-4 border-2 border-dark-100 rounded-sm; | |||
@apply bg-transparent text-generic-500 text-sm; | |||
@apply mb-6 py-2 px-3 border-2 border-dark-100 bg-dark-100; | |||
@apply text-sm text-black font-semibold; | |||
a { | |||
@apply underline; |
@@ -17,8 +17,7 @@ | |||
} | |||
html { | |||
@apply bg-black text-generic-500 font-sans; | |||
//background-image: url('~assets/images/papyrus-dark.webp'); /* Background pattern from Toptal Subtle Patterns */ | |||
@apply bg-background text-generic-500 font-sans; | |||
font-size: 16px; | |||
text-size-adjust: 100%; | |||
font-smoothing: antialiased; | |||
@@ -35,7 +34,3 @@ html, | |||
#__layout { | |||
@apply h-full; | |||
} | |||
.title { | |||
@apply font-serif; | |||
} |
@@ -61,8 +61,8 @@ | |||
.introjs-tooltip { | |||
@apply bg-dark-500-90 #{!important}; | |||
min-width: 350px !important; | |||
max-width: 400px !important; | |||
min-width: 350px !important; | |||
} | |||
.introjs-arrow { | |||
@@ -75,4 +75,4 @@ | |||
.introjs-overlay { | |||
backdrop-filter: blur(10px); | |||
} | |||
} |
@@ -11,10 +11,9 @@ | |||
// the entire scrollbar | |||
*::-webkit-scrollbar | |||
{ | |||
@apply bg-dark-800; | |||
width: 10px; | |||
*::-webkit-scrollbar { | |||
@apply bg-dark-900; | |||
width: 10px; | |||
} | |||
// the buttons on the scrollbar (arrows pointing upwards and downwards). | |||
@@ -23,9 +22,8 @@ | |||
} | |||
// the draggable scrolling handle. | |||
*::-webkit-scrollbar-thumb | |||
{ | |||
@apply bg-dark-100; | |||
*::-webkit-scrollbar-thumb { | |||
@apply bg-dark-300; | |||
&:hover { | |||
@apply bg-primary-700; | |||
@@ -33,10 +31,9 @@ | |||
} | |||
// he track (progress bar) of the scrollbar. | |||
*::-webkit-scrollbar-track | |||
{ | |||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); | |||
@apply bg-dark-800; | |||
*::-webkit-scrollbar-track { | |||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); | |||
@apply bg-dark-800; | |||
} | |||
// the part of the track (progress bar) not covered by the handle. |
@@ -0,0 +1,5 @@ | |||
@mixin transition($property, $duration: .1s, $timing: ease-in-out) { | |||
transition-property: #{$property}; | |||
transition-duration: $duration; | |||
transition-timing-function: $timing; | |||
} |
@@ -0,0 +1,112 @@ | |||
<template> | |||
<div class="box lesson" | |||
:class="{ 'lesson--small': small }" | |||
@click="$emit('click')"> | |||
<div class="box__photo" | |||
:class="[`photo--${lesson.photo}`]" /> | |||
<div class="box__header"> | |||
<span class="title">{{ lesson.title }}</span> | |||
</div> | |||
<div class="box__content" | |||
v-html="content" /> | |||
<div v-if="!small" | |||
class="box__footer text-center"> | |||
<a v-for="(button,key) in lesson.buttons" | |||
:key="key" | |||
:href="button.href" | |||
target="_blank" | |||
class="button button--sm"> | |||
{{ button.text }} | |||
</a> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import MarkdownIt from 'markdown-it' | |||
import { truncate } from 'lodash' | |||
const md = new MarkdownIt() | |||
export default { | |||
props: { | |||
lesson: { | |||
type: Object, | |||
required: true, | |||
}, | |||
small: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
}, | |||
computed: { | |||
content() { | |||
let content | |||
if (this.small) { | |||
content = this.lesson.summary || truncate(this.lesson.content, { length: 80 }) | |||
} else { | |||
content = this.lesson.content | |||
} | |||
return md.render(content) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.lesson { | |||
&::v-deep ul { | |||
@apply list-disc; | |||
li { | |||
@apply text-sm ml-3; | |||
} | |||
} | |||
} | |||
.lesson--small { | |||
.title { | |||
@apply text-sm; | |||
} | |||
.box__content { | |||
&::v-deep p { | |||
@apply text-xs #{!important}; | |||
} | |||
} | |||
.box__photo { | |||
min-height: 80px !important; | |||
} | |||
} | |||
.box__photo { | |||
@apply bg-contain; | |||
&.photo--tips { | |||
background-color: #e0719e; | |||
background-image: url('~assets/images/undraw/undraw_depi_wexf.svg') | |||
} | |||
&.photo--drag { | |||
background-color: #778BB0; | |||
background-image: url('~assets/images/undraw/undraw_logic_4ocy.svg') | |||
} | |||
&.photo--settings { | |||
background-color: #46766B; | |||
background-image: url('~assets/images/undraw/undraw_personal_settings_kihd.svg') | |||
} | |||
} | |||
.box__content { | |||
@apply text-sm; | |||
} | |||
</style> |
@@ -0,0 +1,4 @@ | |||
import Vue from 'vue' | |||
import HelpLesson from './Lesson.vue' | |||
Vue.component('HelpLesson', HelpLesson) |
@@ -0,0 +1,71 @@ | |||
<template> | |||
<div class="menu"> | |||
<section> | |||
<!-- Upload Mode --> | |||
<select | |||
v-model="$settings.app.uploadMode" | |||
v-tooltip="{ content: 'Upload mode. What will happen when uploading a photo.', placement: 'right' }" | |||
class="input input--menu"> | |||
<option value="none"> | |||
Add to Pending | |||
</option> | |||
<option value="add-queue"> | |||
Add to Queue | |||
</option> | |||
<option value="go-preferences"> | |||
Add to Pending and Open preferences | |||
</option> | |||
</select> | |||
</section> | |||
<!-- Custom menu --> | |||
<portal-target name="menu" | |||
class="menu__custom" /> | |||
<!-- Random Lesson --> | |||
<section v-if="help.randomLesson"> | |||
<HelpLesson :lesson="help.randomLesson" | |||
:small="true" | |||
@click="$router.push('/help')" /> | |||
</section> | |||
</div> | |||
</template> | |||
<script> | |||
import { Help } from '~/modules' | |||
export default { | |||
data: () => ({ | |||
help: Help, | |||
}), | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.menu { | |||
@apply flex flex-col; | |||
@apply p-3 bg-dark-500; | |||
grid-area: menu; | |||
section { | |||
&:not(:last-child) { | |||
@apply pb-6 border-b-2 border-dark-400; | |||
@apply mb-3; | |||
} | |||
} | |||
} | |||
.menu__custom { | |||
@apply flex-1; | |||
} | |||
.lesson { | |||
@apply bg-dark-900 cursor-pointer; | |||
@apply shadow; | |||
@include transition('background-color, box-shadow', 0.2s); | |||
&:hover { | |||
@apply bg-dark-800 shadow-lg; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,181 @@ | |||
<template> | |||
<div class="layout__navbar"> | |||
<div class="navbar__left"> | |||
<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" 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 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-if="$provider.system.online" | |||
v-tooltip="{placement: 'bottom', content: 'DreamNet'}" | |||
class="navbar__icon" | |||
to="/dreamnet"> | |||
<font-awesome-icon icon="users" /> | |||
</nuxt-link> | |||
<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> | |||
</div> | |||
</template> | |||
<script> | |||
import { requirements, settings } from '~/modules/system' | |||
import { dreamtrack } from '~/modules/services' | |||
import { events } from '~/modules' | |||
export default { | |||
data: () => ({ | |||
unlockedBadTime: settings.achievements.badtime, | |||
}), | |||
computed: { | |||
canNudify() { | |||
return requirements.canNudify | |||
}, | |||
donateUrl() { | |||
return dreamtrack.get('urls.support.patreon', 'https://www.patreon.com/dreamnet') | |||
}, | |||
manualURL() { | |||
return dreamtrack.get('urls.docs.manual', 'https://time.dreamnet.tech/docs/guide/upload') | |||
}, | |||
isDev() { | |||
return process.env.NODE_ENV === 'development' | |||
}, | |||
hasAlerts() { | |||
return requirements.hasAlerts | |||
}, | |||
}, | |||
mounted() { | |||
events.on('achievements.badtime', () => { | |||
this.unlockedBadTime = true | |||
}) | |||
}, | |||
methods: { | |||
createError() { | |||
throw new Error('UI TEST ERROR') | |||
}, | |||
}, | |||
} | |||
</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; | |||
height: 50px; | |||
.navbar__left, | |||
.navbar__right { | |||
@apply flex items-center; | |||
} | |||
.navbar__left { | |||
@apply flex-1 mr-2; | |||
} | |||
.navbar__right { | |||
@apply justify-end; | |||
} | |||
.navbar__item { | |||
@apply mx-6 text-sm uppercase font-bold; | |||
&: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 { | |||
@apply mx-4; | |||
&: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> |
@@ -1,74 +1,90 @@ | |||
<template> | |||
<div class="layout__navbar"> | |||
<div class="navbar__left"> | |||
<nuxt-link v-if="canNudify" to="/" class="navbar__item navbar__item--home"> | |||
Upload | |||
</nuxt-link> | |||
<div class="nav"> | |||
<div class="nav__left"> | |||
<div v-tooltip="$dream.version" | |||
class="nav__item nav__item--logo"> | |||
<span>{{ $dream.name }}</span> | |||
</div> | |||
<nuxt-link id="settings" class="navbar__item" to="/settings"> | |||
Settings | |||
</nuxt-link> | |||
<div class="nav__item nav__item--greetings"> | |||
<span v-if="!isBadTime">{{ greetings }}</span> | |||
<span v-else><img src="~/assets/images/games/sans.png"> i don't like what you are doing.</span> | |||
</div> | |||
</div> | |||
<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 | |||
<div class="nav__center"> | |||
<nuxt-link v-tooltip="'Upload'" | |||
to="/" | |||
class="nav__item nav__item--link"> | |||
<font-awesome-icon icon="upload" /> | |||
</nuxt-link> | |||
<a v-if="isDev" class="navbar__item" @click.prevent="createError"> | |||
Force Error | |||
</a> | |||
</div> | |||
<div v-tooltip="'My panel'" | |||
class="nav__item nav__item--link"> | |||
<img :src="avatar" | |||
alt="Me"> | |||
</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> | |||
<div v-tooltip="'Advanced Mode'" | |||
class="nav__item nav__item--link"> | |||
<font-awesome-icon icon="mask" /> | |||
</div> | |||
</div> | |||
<nuxt-link v-tooltip="{placement: 'bottom', content: 'About'}" class="navbar__icon" to="/about"> | |||
<font-awesome-icon icon="info-circle" /> | |||
</nuxt-link> | |||
<div class="nav__right"> | |||
<div v-if="isBadTimeAvailable" | |||
v-tooltip="'Bad Time Game'" | |||
class="nav__item nav__item--button"> | |||
<img src="~/assets/images/games/sans.png"> | |||
</div> | |||
<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 v-tooltip="'Settings'" | |||
to="/settings" | |||
class="nav__item nav__item--button"> | |||
<font-awesome-icon icon="cog" /> | |||
</nuxt-link> | |||
<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> | |||
</div> | |||
</template> | |||
<script> | |||
import dayjs from 'dayjs' | |||
import Avatars from '@dicebear/avatars' | |||
import sprites from '@dicebear/avatars-jdenticon-sprites' | |||
import { requirements, settings } from '~/modules/system' | |||
import { dreamtrack } from '~/modules/services' | |||
import { events } from '~/modules' | |||
export default { | |||
data: () => ({ | |||
unlockedBadTime: settings.achievements.badtime, | |||
isBadTimeAvailable: settings.achievements.badtime, | |||
isBadTime: false, | |||
}), | |||
computed: { | |||
avatar() { | |||
const avatars = new Avatars(sprites, { base64: true }) | |||
return avatars.create(settings.user) | |||
}, | |||
greetings() { | |||
const hours = dayjs().hour() | |||
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' | |||
}, | |||
canNudify() { | |||
return requirements.canNudify | |||
}, | |||
@@ -92,7 +108,17 @@ export default { | |||
mounted() { | |||
events.on('achievements.badtime', () => { | |||
this.unlockedBadTime = true | |||
this.isBadTimeAvailable = true | |||
}) | |||
this.$router.afterEach((to) => { | |||
if (to.path === '/games/badtime') { | |||
this.$dream.name = 'BadDreamTime' | |||
this.isBadTime = true | |||
} else { | |||
this.$dream.name = process.env.npm_package_displayName | |||
this.isBadTime = false | |||
} | |||
}) | |||
}, | |||
@@ -105,6 +131,18 @@ export default { | |||
</script> | |||
<style lang="scss" scoped> | |||
@keyframes logoAnim { | |||
0% { | |||
background-position: 0% 0%; | |||
} | |||
50% { | |||
background-position: 100% 0%; | |||
} | |||
100% { | |||
background-position: 0% 0%; | |||
} | |||
} | |||
@keyframes alertAnim { | |||
0% { | |||
@apply text-danger-500; | |||
@@ -117,9 +155,90 @@ export default { | |||
} | |||
} | |||
.nav { | |||
@apply flex z-10; | |||
@apply h-full bg-dark-500 border-b-2 border-dark-600 shadow; | |||
grid-area: nav; | |||
.nav__left, | |||
.nav__center, | |||
.nav__right { | |||
@apply flex-1 flex; | |||
} | |||
.nav__left { | |||
@apply flex-1; | |||
} | |||
.nav__center { | |||
@apply justify-center; | |||
} | |||
.nav__right { | |||
@apply justify-end items-center; | |||
} | |||
} | |||
.nav__item { | |||
@apply flex items-center; | |||
img { | |||
@apply rounded-full; | |||
height: 30px; | |||
} | |||
&.nav__item--link { | |||
@apply justify-center; | |||
@apply border-b-2 border-transparent text-lg; | |||
width: 100px; | |||
&:hover { | |||
@apply text-primary-500 border-primary-500; | |||
} | |||
} | |||
&.nav__item--button { | |||
@apply justify-center; | |||
@apply rounded-full text-lg mr-3; | |||
width: 40px; | |||
height: 40px; | |||
&:hover { | |||
@apply bg-dark-800; | |||
} | |||
img { | |||
height: 20px; | |||
} | |||
} | |||
&.nav__item--logo { | |||
@apply text-white text-sm font-bold px-6 select-none; | |||
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%; | |||
background-position: 0% 0%; | |||
animation-name: logoAnim; | |||
animation-iteration-count: infinite; | |||
animation-duration: 10s; | |||
animation-timing-function: ease-in-out; | |||
} | |||
&.nav__item--greetings { | |||
@apply text-white text-sm font-light px-3 select-none; | |||
} | |||
} | |||
.layout__navbar { | |||
@apply flex bg-dark-500 z-10; | |||
@apply border-b border-dark-100; | |||
grid-area: nav; | |||
height: 50px; | |||
.navbar__left, |
@@ -0,0 +1,96 @@ | |||
<template> | |||
<div class="titlebar"> | |||
<div class="titlebar__drag" /> | |||
<div class="titlebar__buttons"> | |||
<button id="minimize" type="button" @click="minimize"> | |||
<font-awesome-icon icon="minus" /> | |||
</button> | |||
<button id="maximize" type="button" @click="maximize"> | |||
<font-awesome-icon :icon="['far', 'square']" /> | |||
</button> | |||
<button id="close" type="button" class="close" @click="close"> | |||
<font-awesome-icon icon="times" /> | |||
</button> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
const { getCurrentWindow } = require('electron').remote | |||
const { api } = $provider.util | |||
export default { | |||
methods: { | |||
minimize() { | |||
try { | |||
getCurrentWindow().minimize() | |||
} catch (error) { | |||
throw new Exception('There was a problem trying to minimize the window.', error) | |||
} | |||
}, | |||
maximize() { | |||
try { | |||
getCurrentWindow().maximize() | |||
} catch (error) { | |||
throw new Exception('There was a problem trying to maximize the window.', error) | |||
} | |||
}, | |||
close() { | |||
api.app.quit() | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.titlebar { | |||
@apply flex justify-end; | |||
@apply bg-black text-white; | |||
grid-area: title; | |||
height: 30px; | |||
z-index: 9999999; | |||
.topbar__badtime { | |||
@apply flex items-center justify-center; | |||
@apply lowercase font-bold text-sm; | |||
font-family: "Comic Sans MS", serif; | |||
img { | |||
@apply mr-2; | |||
height: 18px; | |||
} | |||
} | |||
.titlebar__drag { | |||
@apply flex-1; | |||
-webkit-app-region: drag; | |||
} | |||
.titlebar__buttons { | |||
@apply flex; | |||
button { | |||
@apply flex items-center justify-center outline-none; | |||
@apply text-xs; | |||
width: 50px; | |||
height: 30px; | |||
&:hover { | |||
@apply bg-dark-800; | |||
&.close { | |||
@apply bg-danger-500; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -1,180 +0,0 @@ | |||
<template> | |||
<div class="layout__topbar"> | |||
<div class="topbar__left"> | |||
<div class="topbar__logo"> | |||
{{ $dream.name }} {{ $dream.version }} | |||
</div> | |||
<div v-show="!badTime" class="topbar__greetings"> | |||
{{ greetings }} | |||
</div> | |||
<div v-show="badTime" class="topbar__badtime"> | |||
<img src="~/assets/images/games/sans.png"> | |||
i don't like what you are doing. | |||
</div> | |||
</div> | |||
<div class="topbar__buttons"> | |||
<button id="minimize" type="button" @click="minimize"> | |||
<font-awesome-icon icon="minus" /> | |||
</button> | |||
<button id="maximize" type="button" @click="maximize"> | |||
<font-awesome-icon :icon="['far', 'square']" /> | |||
</button> | |||
<button id="close" type="button" class="close" @click="close"> | |||
<font-awesome-icon icon="times" /> | |||
</button> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import dayjs from 'dayjs' | |||
const { getCurrentWindow } = require('electron').remote | |||
const { api } = $provider.util | |||
export default { | |||
data: () => ({ | |||
badTime: false, | |||
}), | |||
computed: { | |||
greetings() { | |||
const hours = dayjs().hour() | |||
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' | |||
}, | |||
}, | |||
mounted() { | |||
this.$router.afterEach((to) => { | |||
if (to.path === '/games/badtime') { | |||
this.$dream.name = 'BadDreamTime' | |||
this.badTime = true | |||
} else { | |||
this.$dream.name = process.env.npm_package_displayName | |||
this.badTime = false | |||
} | |||
}) | |||
}, | |||
methods: { | |||
minimize() { | |||
try { | |||
getCurrentWindow().minimize() | |||
} catch (error) { | |||
throw new Exception('There was a problem trying to minimize the window.', error) | |||
} | |||
}, | |||
maximize() { | |||
try { | |||
getCurrentWindow().maximize() | |||
} catch (error) { | |||
throw new Exception('There was a problem trying to maximize the window.', error) | |||
} | |||
}, | |||
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; | |||
height: 30px; | |||
z-index: 9999999; | |||
.topbar__left { | |||
@apply flex-1 flex; | |||
@apply text-sm; | |||
-webkit-app-region: drag; | |||
} | |||
.topbar__logo { | |||
@apply flex flex-col items-center justify-center mr-4; | |||
@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%; | |||
animation-name: bgAnim; | |||
animation-iteration-count: infinite; | |||
animation-duration: 10s; | |||
animation-timing-function: ease-in-out; | |||
} | |||
.topbar__greetings { | |||
@apply flex items-center justify-center; | |||
@apply font-light; | |||
} | |||
.topbar__badtime { | |||
@apply flex items-center justify-center; | |||
@apply lowercase font-bold text-sm; | |||
font-family: "Comic Sans MS", serif; | |||
img { | |||
@apply mr-2; | |||
height: 18px; | |||
} | |||
} | |||
.topbar__buttons { | |||
@apply flex; | |||
button { | |||
@apply flex items-center justify-center outline-none; | |||
@apply text-xs; | |||
width: 50px; | |||
height: 30px; | |||
&:hover { | |||
@apply bg-gray-700; | |||
&.close { | |||
@apply bg-danger-500; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -1,7 +1,9 @@ | |||
import Vue from 'vue' | |||
import LayoutTopbar from './Topbar' | |||
import LayoutNavbar from './Navbar' | |||
import Titlebar from './Titlebar' | |||
import Navbar from './Navbar' | |||
import Menubar from './Menubar.vue' | |||
Vue.component('LayoutTopbar', LayoutTopbar) | |||
Vue.component('LayoutNavbar', LayoutNavbar) | |||
Vue.component('Titlebar', Titlebar) | |||
Vue.component('Navbar', Navbar) | |||
Vue.component('Menubar', Menubar) |
@@ -2,129 +2,7 @@ | |||
<div id="uploader" class="uploader"> | |||
<!-- Uploader Selection --> | |||
<div class="uploader__selection"> | |||
<div class="selection__menu"> | |||
<select | |||
id="uploader-settings" | |||
v-model="$settings.app.uploadMode" | |||
v-tooltip="{ content: 'Upload mode. What will happen when uploading a photo.', placement: 'right' }" | |||
class="input"> | |||
<option value="add-queue"> | |||
Put in Queue | |||
</option> | |||
<option value="none"> | |||
Put in Pending | |||
</option> | |||
<option value="go-preferences"> | |||
Put in Pending and Open preferences | |||
</option> | |||
</select> | |||
<div id="uploader-methods" class="box box--items"> | |||
<div class="box__content"> | |||
<box-item | |||
label="Instagram" | |||
:icon="['fab', 'instagram']" | |||
:is-link="true" | |||
: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" | |||
:is-link="true" | |||
:class="{'box__item--active': selectionId === 2}" | |||
@click="selectionId = 2" /> | |||
<box-item | |||
label="Folder" | |||
icon="folder-open" | |||
:is-link="true" | |||
:class="{'box__item--active': selectionId === 3}" | |||
@click="selectionId = 3" /> | |||
<box-item | |||
label="Examples" | |||
icon="images" | |||
href="https://time.dreamnet.tech/docs/guide/photos" /> | |||
</div> | |||
</div> | |||
<div class="box uploader__hint"> | |||
<div class="box__content"> | |||
<p> | |||
<font-awesome-icon icon="exclamation-circle" /> | |||
You can drag and drop photos and folders into the application no matter where you are. | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="selection__content"> | |||
<!-- Web Address --> | |||
<div v-show="selectionId === 0" class="selection__content__body"> | |||
<input v-model="webAddress" type="url" class="input mb-2" placeholder="https://" data-private="lipsum"> | |||
<p class="help"> | |||
Enter the web address of a photo that ends in a valid extension. <i>(jpg, png, gif)</i> | |||
</p> | |||
<button class="button" @click="openUrl"> | |||
<span class="icon"><font-awesome-icon icon="globe" /></span> | |||
<span>Submit</span> | |||
</button> | |||
</div> | |||
<!-- Instagram --> | |||
<div v-show="selectionId === 1" class="selection__content__body"> | |||
<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 ID of an Instagram photo. | |||
</p> | |||
<button class="button" @click="openInstagramPhoto"> | |||
<span class="icon"><font-awesome-icon :icon="['fab', 'instagram']" /></span> | |||
<span>Submit</span> | |||
</button> | |||
</div> | |||
<!-- File --> | |||
<div v-show="selectionId === 2" class="selection__content__body"> | |||
<input | |||
v-show="false" | |||
ref="photo" | |||
type="file" | |||
accept="image/jpeg, image/png, image/gif" | |||
multiple | |||
@change="openFile"> | |||
<button class="button" @click.prevent="$refs.photo.click()"> | |||
<span>Open File</span> | |||
</button> | |||
<p class="help"> | |||
Select one or more photos from your computer. | |||
</p> | |||
</div> | |||
<!-- Folder --> | |||
<div v-show="selectionId === 3" class="selection__content__body"> | |||
<button class="button" @click.prevent="openFolder"> | |||
<span>Open folder</span> | |||
</button> | |||
<p class="help"> | |||
Select a folder from your computer. All valid photos inside will be uploaded. | |||
</p> | |||
</div> | |||
</div> | |||
<div class="selection__content" /> | |||
</div> | |||
</div> | |||
</template> | |||
@@ -150,9 +28,7 @@ export default { | |||
}, | |||
data: () => ({ | |||
selectionId: 1, | |||
webAddress: '', | |||
instagramPhoto: '', | |||
}), | |||
watch: { |
@@ -0,0 +1,46 @@ | |||
<template> | |||
<div class="header"> | |||
<div class="header__left"> | |||
<slot /> | |||
</div> | |||
<div v-if="this.$slots.center" class="header__center"> | |||
<slot name="center" /> | |||
</div> | |||
<div v-if="this.$slots.right" class="header__right"> | |||
<slot name="right" /> | |||
</div> | |||
</div> | |||
</template> | |||
<style lang="scss" scoped> | |||
.header { | |||
@apply flex mb-9; | |||
} | |||
.header__left { | |||
@apply flex-1; | |||
&:not(:last-child) { | |||
@apply mr-3; | |||
} | |||
} | |||
.title { | |||
@apply text-lg font-semibold text-generic-100; | |||
.icon { | |||
@apply mr-2; | |||
} | |||
} | |||
.subtitle { | |||
@apply font-thin; | |||
.help { | |||
@apply ml-2; | |||
cursor: help; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,4 @@ | |||
import Vue from 'vue' | |||
import PageHeader from './PageHeader' | |||
Vue.component('PageHeader', PageHeader) |
@@ -0,0 +1,170 @@ | |||
<template> | |||
<div id="queuebar" class="layout__jobbar"> | |||
<section id="queuebar-running"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<span class="icon"><font-awesome-icon icon="running" /></span> | |||
<span>Queue</span> | |||
</div> | |||
<div v-show="$nudify.waiting.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="{placement: 'bottom', content: 'Forget waiting'}" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll('waiting')"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="{placement: 'bottom', content: 'Cancel waiting' }" | |||
class="button button--xs" | |||
@click.prevent="$nudify.cancelAll('waiting')"> | |||
<font-awesome-icon icon="stop" /> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="jobs__list"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.waiting" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
<section id="queuebar-pending"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<span class="icon"><font-awesome-icon icon="clipboard-list" /></span> | |||
<span>Pending</span> | |||
</div> | |||
<div v-show="$nudify.pending.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="'Forget all'" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll()"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="'Start all'" | |||
class="button button--xs" | |||
@click.prevent="$nudify.addAll()"> | |||
<font-awesome-icon icon="play" /> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="jobs__list"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.pending" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
<section id="queuebar-finished"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<span class="icon"><font-awesome-icon icon="clipboard-check" /></span> | |||
<span>Finished</span> | |||
</div> | |||
<div v-show="$nudify.finished.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="'Forget all'" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll('finished')"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="'Rerun all'" | |||
class="button button--xs" | |||
@click.prevent="$nudify.addAll('finished')"> | |||
<font-awesome-icon icon="undo" /> | |||
</button> | |||
</div> | |||
</div> | |||
<div class="jobs__list"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.finished" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.layout__jobbar { | |||
@apply relative flex flex-col; | |||
@apply bg-dark-500 py-2 z-10; | |||
@apply border-l border-dark-100; | |||
} | |||
section { | |||
@apply flex-1 flex flex-col; | |||
@apply overflow-hidden; | |||
height: calc((100vh - 80px) / 3); | |||
&:not(:first-child) { | |||
.section__header { | |||
&::before, &::after { | |||
@apply block border-b; | |||
@apply absolute right-0 pointer-events-none z-0; | |||
content: " "; | |||
left: 100px; | |||
} | |||
&::before { | |||
@apply border-dark-200; | |||
top: 18px; | |||
} | |||
&::after { | |||
@apply border-dark-400; | |||
top: 19px; | |||
} | |||
} | |||
} | |||
.section__header { | |||
@apply px-4 pt-2 text-sm text-white font-semibold; | |||
@apply relative flex items-center; | |||
.icon { | |||
@apply mr-2; | |||
} | |||
.section__title { | |||
@apply flex-1 z-10; | |||
} | |||
} | |||
.section__actions { | |||
@apply flex flex-1 justify-end ml-2 z-10 bg-dark-500; | |||
.button { | |||
@apply ml-2; | |||
} | |||
} | |||
} | |||
.jobs__list { | |||
@apply flex flex-wrap flex-1; | |||
@apply py-2 overflow-y-auto max-h-full; | |||
will-change: scroll-position; | |||
} | |||
</style> |
@@ -1,103 +1,62 @@ | |||
<template> | |||
<div id="queuebar" class="layout__jobbar"> | |||
<section id="queuebar-running"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<div id="queuebar" | |||
class="queue"> | |||
<div class="queue__section queue__section--running"> | |||
<div class="queue__header"> | |||
<p class="title"> | |||
<span class="icon"><font-awesome-icon icon="running" /></span> | |||
<span>Queue</span> | |||
</div> | |||
<div v-show="$nudify.waiting.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="{placement: 'bottom', content: 'Forget waiting'}" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll('waiting')"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="{placement: 'bottom', content: 'Cancel waiting' }" | |||
class="button button--xs" | |||
@click.prevent="$nudify.cancelAll('waiting')"> | |||
<font-awesome-icon icon="stop" /> | |||
</button> | |||
</div> | |||
<span class="separator">ยท</span> | |||
<span>{{ $nudify.waiting.length }}</span> | |||
</p> | |||
</div> | |||
<div class="jobs__list"> | |||
<div class="queue__content"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.waiting" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
</div> | |||
<section id="queuebar-pending"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<div class="queue__section queue__section--pending"> | |||
<div class="queue__header"> | |||
<p class="title"> | |||
<span class="icon"><font-awesome-icon icon="clipboard-list" /></span> | |||
<span>Pending</span> | |||
</div> | |||
<div v-show="$nudify.pending.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="'Forget all'" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll()"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="'Start all'" | |||
class="button button--xs" | |||
@click.prevent="$nudify.addAll()"> | |||
<font-awesome-icon icon="play" /> | |||
</button> | |||
</div> | |||
<span class="separator">ยท</span> | |||
<span>{{ $nudify.pending.length }}</span> | |||
</p> | |||
</div> | |||
<div class="jobs__list"> | |||
<div class="queue__content"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.pending" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
</div> | |||
<section id="queuebar-finished"> | |||
<div class="section__header"> | |||
<div class="section__title"> | |||
<div class="queue__section queue__section--finished"> | |||
<div class="queue__header"> | |||
<p class="title"> | |||
<span class="icon"><font-awesome-icon icon="clipboard-check" /></span> | |||
<span>Finished</span> | |||
</div> | |||
<div v-show="$nudify.finished.length > 0" class="section__actions"> | |||
<button | |||
v-tooltip="'Forget all'" | |||
class="button button--danger button--xs" | |||
@click.prevent="$nudify.forgetAll('finished')"> | |||
<font-awesome-icon icon="trash-alt" /> | |||
</button> | |||
<button | |||
v-tooltip="'Rerun all'" | |||
class="button button--xs" | |||
@click.prevent="$nudify.addAll('finished')"> | |||
<font-awesome-icon icon="undo" /> | |||
</button> | |||
</div> | |||
<span class="separator">ยท</span> | |||
<span>{{ $nudify.finished.length }}</span> | |||
</p> | |||
</div> | |||
<div class="jobs__list"> | |||
<div class="queue__content"> | |||
<QueuePhoto | |||
v-for="(photo, index) of $nudify.finished" | |||
:key="index" | |||
:photo="photo" | |||
data-private /> | |||
</div> | |||
</section> | |||
</div> | |||
</div> | |||
</template> | |||
@@ -108,6 +67,51 @@ export default { | |||
</script> | |||
<style lang="scss" scoped> | |||
.queue { | |||
@apply flex flex-col; | |||
@apply bg-dark-500; | |||
grid-area: queue; | |||
} | |||
.queue__section { | |||
@apply flex-1 flex flex-col; | |||
&.queue__section--running { | |||
} | |||
&.queue__section--pending { | |||
} | |||
&.queue__section--finished { | |||
} | |||
} | |||
.queue__header { | |||
@apply p-3; | |||
.title { | |||
@apply text-sm font-bold; | |||
} | |||
.icon { | |||
@apply mr-2; | |||
} | |||
.separator { | |||
@apply mx-2; | |||
} | |||
} | |||
.queue__content { | |||
@apply flex-1; | |||
@apply overflow-hidden overflow-x-auto whitespace-no-wrap; | |||
.photo { | |||
@apply inline-block; | |||
} | |||
} | |||
/* | |||
.layout__jobbar { | |||
@apply relative flex flex-col; | |||
@apply bg-dark-500 py-2 z-10; | |||
@@ -167,4 +171,5 @@ section { | |||
@apply py-2 overflow-y-auto max-h-full; | |||
will-change: scroll-position; | |||
} | |||
*/ | |||
</style> |
@@ -84,9 +84,8 @@ export default { | |||
<style lang="scss" scoped> | |||
.photo { | |||
@apply w-1/2 relative border-2 border-dark-300; | |||
@apply w-1/2 h-full relative border-2 border-dark-800; | |||
background-image: url("~@/assets/images/curls.png"); | |||
height: 150px; | |||
will-change: transform; | |||
&.photo--running { |
@@ -11,5 +11,5 @@ import Vue from 'vue' | |||
import QueueBar from './QueueBar' | |||
import QueuePhoto from './QueuePhoto' | |||
Vue.component('QueueBar', QueueBar) | |||
Vue.component('Queuebar', QueueBar) | |||
Vue.component('QueuePhoto', QueuePhoto) |
@@ -0,0 +1,159 @@ | |||
<template> | |||
<div class="item" | |||
:class="cssClass" | |||
@click="click"> | |||
<!-- Icon --> | |||
<slot name="icon"> | |||
<div v-if="icon" | |||
class="item__icon"> | |||
<img v-if="isImageIcon" | |||
:src="icon"> | |||
<font-awesome-icon v-else | |||
:icon="icon" /> | |||
</div> | |||
</slot> | |||
<!-- Title & Description --> | |||
<div v-if="label" | |||
class="item__label"> | |||
<span class="item__title" | |||
v-html="label" /> | |||
<slot name="description"> | |||
<span v-if="description" | |||
class="item__description" | |||
v-html="description" /> | |||
</slot> | |||
</div> | |||
<!-- Action --> | |||
<div v-if="$slots.default" | |||
class="item__action"> | |||
<slot /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { isNil, startsWith } from 'lodash' | |||
import { dreamtrack } from '~/modules/services' | |||
const { shell } = $provider.api | |||
export default { | |||
props: { | |||
icon: { | |||
type: [String, Array], | |||
default: undefined, | |||
}, | |||
label: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
description: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
href: { | |||
type: String, | |||
default: undefined, | |||
}, | |||
isLink: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
}, | |||
computed: { | |||
hasIcon() { | |||
return !isNil(this.icon) || !isNil(this.$slots.icon) | |||
}, | |||
isImageIcon() { | |||
return startsWith(this.icon, 'http') || startsWith(this.icon, '/') | |||
}, | |||
cssClass() { | |||
return { | |||
'item--link': !isNil(this.href) || this.isLink, | |||
} | |||
}, | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click') | |||
if (!isNil(this.href)) { | |||
if (startsWith(this.href, '/')) { | |||
this.$router.push(this.href) | |||
} else { | |||
dreamtrack.track('CLICK_LINK', { href: this.href }) | |||
shell.openExternal(this.href) | |||
} | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.item { | |||
@apply flex p-3 rounded; | |||
@apply border-b border-t border-transparent; | |||
@include transition('background-color, color, border-color'); | |||
min-height: 50px; | |||
&.item--active { | |||
@apply bg-dark-400 border-dark-800 text-white; | |||
.item__icon, .item__title { | |||
@apply text-generic-500; | |||
} | |||
} | |||
&.item--link { | |||
@apply cursor-pointer; | |||
&:hover { | |||
@apply bg-dark-400 border-dark-800 text-white; | |||
.item__icon, .item__title { | |||
@apply text-generic-500; | |||
} | |||
} | |||
} | |||
} | |||
.item__icon { | |||
@apply mr-2 flex items-center justify-center; | |||
@apply text-2xl text-generic-500; | |||
width: 42px; | |||
min-width: 42px; | |||
} | |||
.item__label { | |||
@apply flex-1 flex flex-col justify-center; | |||
@apply text-generic-500; | |||
&:not(:last-child) { | |||
@apply mr-6; | |||
} | |||
.item__title { | |||
@apply block font-semibold; | |||
} | |||