You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Upload.vue 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <div class="c-uploader">
  3. <div class="uploader__settings box box--items">
  4. <div class="box__content">
  5. <box-item
  6. label="On Upload"
  7. description="Select what should be done when uploading a new photo.">
  8. <select v-model="$settings.app.uploadMode" class="input">
  9. <option value="none">
  10. Stay
  11. </option>
  12. <option value="add-queue">
  13. Start photo transformation
  14. </option>
  15. <option value="go-preferences">
  16. Change photo preferences
  17. </option>
  18. </select>
  19. </box-item>
  20. </div>
  21. </div>
  22. <!-- Dropzone -->
  23. <div
  24. class="uploader__dropzone"
  25. :class="{'is-dragging': isDragging}"
  26. @dragenter="onDragEnter"
  27. @dragover="onDragOver"
  28. @dragleave="onDragLeave"
  29. @drop="openDrop">
  30. <p class="dropzone-hint">
  31. <font-awesome-icon icon="camera" />
  32. Drop the photo(s)/folder here!
  33. </p>
  34. </div>
  35. <div class="uploader__alt">
  36. <!-- File -->
  37. <div class="box">
  38. <div class="box__header">
  39. <h2 class="title">
  40. <span class="icon"><font-awesome-icon icon="image" /></span>
  41. <span>File.</span>
  42. </h2>
  43. <h3 class="subtitle">
  44. Select a file from your computer.
  45. </h3>
  46. </div>
  47. <div class="box__content">
  48. <input
  49. v-show="false"
  50. ref="photo"
  51. type="file"
  52. accept="image/jpeg, image/png"
  53. multiple
  54. @change="openFile">
  55. <button class="button" @click.prevent="$refs.photo.click()">
  56. <span>open file</span>
  57. </button>
  58. </div>
  59. </div>
  60. <!-- Folder -->
  61. <div class="box">
  62. <div class="box__header">
  63. <h2 class="title">
  64. <span class="icon"><font-awesome-icon icon="folder-open" /></span>
  65. <span>Folder.</span>
  66. </h2>
  67. <h3 class="subtitle">
  68. Select a folder from your computer. All valid photos will be transformed.
  69. </h3>
  70. </div>
  71. <div class="box__content">
  72. <button class="button" @click.prevent="openFolder">
  73. <span>import folder</span>
  74. </button>
  75. </div>
  76. </div>
  77. <!-- Web Address -->
  78. <div class="box">
  79. <div class="box__header">
  80. <h2 class="title">
  81. <span class="icon"><font-awesome-icon icon="globe" /></span>
  82. <span>Web Address.</span>
  83. </h2>
  84. <h3 class="subtitle">
  85. Enter the web address of a photo. It must end in a valid extension (jpg, png, gif)
  86. </h3>
  87. </div>
  88. <div class="box__content">
  89. <input v-model="webAddress" type="url" class="input mb-2" placeholder="https://">
  90. <button class="button" @click="openUrl">
  91. Go!
  92. </button>
  93. </div>
  94. </div>
  95. <!-- Web Address -->
  96. <div class="box">
  97. <div class="box__header">
  98. <h2 class="title">
  99. <span class="icon"><font-awesome-icon :icon="['fab', 'instagram']" /></span>
  100. <span>Instagram photo.</span>
  101. </h2>
  102. <h3 class="subtitle">
  103. Enter the web address or Media ID of an Instagram photo.
  104. </h3>
  105. </div>
  106. <div class="box__content">
  107. <input v-model="instagramPhoto" type="url" class="input mb-2" placeholder="https://www.instagram.com/p/dU4fHDw-Ho/">
  108. <button class="button" @click="openInstagramPhoto">
  109. Go!
  110. </button>
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. </template>
  116. <script>
  117. /* eslint-disable no-param-reassign */
  118. import {
  119. isNil, isEmpty, startsWith,
  120. map, isArray,
  121. } from 'lodash'
  122. import Swal from 'sweetalert2'
  123. import { Nudify } from '~/modules/nudify'
  124. const { nucleus } = $provider.services
  125. const { instagram } = $provider.tools
  126. const { dialog } = $provider.api
  127. export default {
  128. props: {
  129. model: {
  130. type: String,
  131. default: undefined,
  132. },
  133. },
  134. data: () => ({
  135. webAddress: '',
  136. instagramPhoto: '',
  137. isDragging: false,
  138. }),
  139. created() {
  140. },
  141. methods: {
  142. /**
  143. * File selected, start a new transformation process
  144. */
  145. addFile(file) {
  146. if (isNil(file)) {
  147. return
  148. }
  149. Nudify.addFile(file.path)
  150. },
  151. async addFiles(files) {
  152. if (!isArray(files)) {
  153. return
  154. }
  155. Swal.fire({
  156. title: 'Importing files...',
  157. text: 'One moment, please.',
  158. showConfirmButton: false,
  159. allowOutsideClick: false,
  160. allowEscapeKey: false,
  161. })
  162. await Nudify.addFiles(files)
  163. Swal.close()
  164. },
  165. /**
  166. *
  167. */
  168. openFile(event) {
  169. const { files } = event.target
  170. if (files.length === 0) {
  171. return
  172. }
  173. const paths = map(files, 'path')
  174. nucleus.track('UPLOAD_FILE')
  175. this.addFiles(paths)
  176. event.target.value = ''
  177. },
  178. /**
  179. *
  180. */
  181. openFolder() {
  182. const paths = dialog.showOpenDialogSync({
  183. properties: ['openDirectory'],
  184. })
  185. this.addFiles(paths)
  186. },
  187. /**
  188. *
  189. */
  190. openUrl() {
  191. if (isEmpty(this.webAddress) || (!startsWith(this.webAddress, 'http://') && !startsWith(this.webAddress, 'https://'))) {
  192. throw new AppError('Please enter a valid web address.', { title: 'Upload failed.', level: 'warning' })
  193. }
  194. nucleus.track('UPLOAD_URL')
  195. Nudify.addUrl(this.webAddress)
  196. this.webAddress = ''
  197. },
  198. /**
  199. *
  200. */
  201. async openInstagramPhoto() {
  202. if (isEmpty(this.instagramPhoto)) {
  203. throw new AppError('Please enter a valid Instagram photo.', { title: 'Upload failed.', level: 'warning' })
  204. }
  205. const post = await instagram.getPost(this.instagramPhoto)
  206. if (post.isVideo) {
  207. throw new AppError('The videos are not supported yet.', { title: 'Upload failed.', level: 'warning' })
  208. }
  209. Nudify.addUrl(post.downloadUrl)
  210. this.instagramPhoto = ''
  211. },
  212. /**
  213. *
  214. */
  215. onDragEnter(event) {
  216. event.dataTransfer.dropEffect = 'copy'
  217. this.isDragging = true
  218. },
  219. /**
  220. *
  221. */
  222. onDragLeave() {
  223. this.isDragging = false
  224. },
  225. /**
  226. *
  227. */
  228. onDragOver(event) {
  229. event.preventDefault()
  230. event.stopPropagation()
  231. event.dataTransfer.dropEffect = 'copy'
  232. this.isDragging = true
  233. },
  234. /**
  235. *
  236. */
  237. openDrop(event) {
  238. event.preventDefault()
  239. event.stopPropagation()
  240. this.isDragging = false
  241. const { files } = event.dataTransfer
  242. const url = event.dataTransfer.getData('url')
  243. if (url.length > 0) {
  244. nucleus.track('UPLOAD_DROP_URL')
  245. Nudify.addUrl(url)
  246. } else if (files.length > 0) {
  247. const paths = map(files, 'path')
  248. this.addFiles(paths)
  249. nucleus.track('UPLOAD_DROP')
  250. }
  251. },
  252. },
  253. }
  254. </script>
  255. <style lang="scss" scoped>
  256. .c-uploader {
  257. @apply w-full relative;
  258. .uploader__alt {
  259. @apply flex flex-wrap justify-between;
  260. .box {
  261. @apply flex flex-col;
  262. width: calc(1/2*100% - (1 - 1/2)*1rem);
  263. min-height: 200px;
  264. .box__header {
  265. h2 {
  266. @apply text-lg font-bold;
  267. }
  268. h3 {
  269. @apply text-sm mb-4 font-light;
  270. }
  271. .help {
  272. @apply text-xs align-text-top font-bold underline;
  273. cursor: help;
  274. }
  275. }
  276. .box__content {
  277. @apply flex-1 flex flex-col justify-center items-center;
  278. }
  279. }
  280. }
  281. .uploader__dropzone {
  282. @apply flex items-center justify-center;
  283. @apply bg-dark-500 mb-6;
  284. @apply rounded border-2 border-dashed border-dark-100;
  285. height: 200px;
  286. transition: all 0.1s linear;
  287. &.is-dragging {
  288. @apply bg-dark-700 border-dark-200;
  289. .dropzone-hint {
  290. @apply text-white text-xl;
  291. }
  292. }
  293. .dropzone-hint {
  294. @apply text-generic-300 uppercase;
  295. transition: all 0.1s linear;
  296. }
  297. }
  298. .upload-url {
  299. @apply mb-6 flex;
  300. .input {
  301. @apply flex-1 mr-4;
  302. }
  303. }
  304. &.is-dragging {
  305. @apply border-white border-dotted;
  306. .dragging-overlay {
  307. //display: block;
  308. }
  309. }
  310. .dragging-overlay {
  311. @apply bg-white absolute top-0 left-0 right-0 bottom-0 hidden;
  312. opacity: 0.3;
  313. }
  314. .fu-hint {
  315. @apply text-sm text-gray-600;
  316. }
  317. }
  318. </style>