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.

NudifyMaskPhoto.vue 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <template>
  2. <div class="box mask"
  3. :class="maskClass"
  4. @dragenter="onDragEnter"
  5. @dragover="onDragOver"
  6. @dragleave="onDragLeave"
  7. @drop="onDrop">
  8. <!-- Dragging -->
  9. <div class="mask__dropzone">
  10. <h2>Drop the {{ mask.title }} here!</h2>
  11. </div>
  12. <div class="box__photo mask__photo">
  13. <div v-if="file.exists"
  14. class="mask__photo__preview"
  15. :style="{ backgroundImage: `url('${file.url}')` }"
  16. data-private
  17. @click="openPreview" />
  18. </div>
  19. <div class="box__header">
  20. <span class="title">{{ mask.title }} <AppTip :tooltip="mask.description" /></span>
  21. </div>
  22. <input
  23. v-show="false"
  24. ref="photo"
  25. type="file"
  26. accept="image/png"
  27. @change="change">
  28. <div class="box__footer buttons">
  29. <!--
  30. <button v-if="mask.canShowEdit"
  31. key="edit"
  32. v-tooltip="'Edit with the photo editor.'"
  33. class="button button--sm">
  34. <FontAwesomeIcon icon="paint-brush" />
  35. </button>
  36. -->
  37. <button v-if="mask.canShowUpload"
  38. key="upload"
  39. v-tooltip="'Upload mask.'"
  40. class="button button--sm"
  41. @click="$refs.photo.click()">
  42. <FontAwesomeIcon icon="file-upload" />
  43. </button>
  44. <button v-if="mask.canShowSave"
  45. key="save"
  46. v-tooltip="'Save mask.'"
  47. class="button button--sm"
  48. @click="mask.save()">
  49. <FontAwesomeIcon icon="save" />
  50. </button>
  51. <button v-if="mask.run"
  52. key="button-terminal"
  53. v-tooltip="'View terminal'"
  54. class="button button--sm"
  55. @click.prevent="$refs.terminalDialog.showModal()">
  56. <font-awesome-icon icon="terminal" />
  57. </button>
  58. <button v-if="mask.canShowGenerate && mask.photo.running"
  59. key="stop"
  60. v-tooltip="'Stop Generation.'"
  61. class="button button--danger button--sm"
  62. @click="stop()">
  63. <FontAwesomeIcon icon="stop" />
  64. </button>
  65. <button v-else-if="mask.canShowGenerate && mask.canShowSave"
  66. key="rerun"
  67. v-tooltip="'Rerun.'"
  68. class="button button--info button--sm"
  69. @click="generate()">
  70. <FontAwesomeIcon icon="retweet" />
  71. </button>
  72. <button v-else-if="mask.canShowGenerate && !mask.photo.running"
  73. key="play"
  74. v-tooltip="'Generate.'"
  75. class="button button--success button--sm"
  76. @click="generate()">
  77. <FontAwesomeIcon icon="play" />
  78. </button>
  79. <button v-else-if="mask.isReadOnly && mask.nextMask"
  80. key="play-disabled"
  81. v-tooltip="'This mask will be generated with the next.'"
  82. class="button button--success button--sm button--disabled">
  83. <FontAwesomeIcon icon="play" />
  84. </button>
  85. </div>
  86. <!-- Terminal Dialog -->
  87. <dialog v-if="mask.run" ref="terminalDialog">
  88. <div class="dialog__content">
  89. <div class="terminal">
  90. <li
  91. v-for="(item, index) in mask.run.cli.lines"
  92. :key="index"
  93. :class="item.css">
  94. > {{ item.text }}
  95. </li>
  96. </div>
  97. <div class="dialog__buttons">
  98. <button class="button button--danger" @click="$refs.terminalDialog.close()">
  99. Close
  100. </button>
  101. </div>
  102. </div>
  103. </dialog>
  104. </div>
  105. </template>
  106. <script>
  107. import { File } from '~/modules'
  108. import { DragDropMixin } from '~/mixins'
  109. export default {
  110. mixins: [DragDropMixin],
  111. props: {
  112. mask: {
  113. type: Object,
  114. required: true,
  115. },
  116. },
  117. data: () => ({
  118. renderPhoto: true,
  119. }),
  120. computed: {
  121. file() {
  122. return this.mask.file
  123. },
  124. maskClass() {
  125. return {
  126. 'mask--dragging': this.isDragging,
  127. 'mask--failed': this.mask.run?.failed,
  128. 'mask--running': this.mask.run?.running,
  129. 'mask--finished': this.mask.run?.finished,
  130. }
  131. },
  132. },
  133. mounted() {
  134. if (this.mask.isReadOnly) {
  135. this.isDragEnabled = false
  136. }
  137. this.file.on('loading', this.onLoadingPhoto, this)
  138. this.file.on('loaded', this.onLoadedPhoto, this)
  139. },
  140. beforeDestroy() {
  141. this.file.off('loading', this.onLoadingPhoto, this)
  142. this.file.off('loaded', this.onLoadedPhoto, this)
  143. },
  144. methods: {
  145. async change(event) {
  146. const { files } = event.target
  147. if (files.length === 0) {
  148. return
  149. }
  150. try {
  151. const file = await File.fromPath(files[0].path, { watch: false })
  152. file.validateAs('image/png')
  153. this.file.writeFile(file)
  154. } catch (error) {
  155. // eslint-disable-next-line no-console
  156. console.warn(error)
  157. }
  158. event.target.value = ''
  159. },
  160. async onURL(url) {
  161. try {
  162. const file = await File.fromUrl(url, { watch: false })
  163. file.validateAs('image/png')
  164. this.file.writeFile(file)
  165. } catch (error) {
  166. // eslint-disable-next-line no-console
  167. console.warn(error)
  168. }
  169. },
  170. async onFiles(files) {
  171. try {
  172. const file = await File.fromPath(files[0].path, { watch: false })
  173. file.validateAs('image/png')
  174. this.file.writeFile(file)
  175. } catch (error) {
  176. // eslint-disable-next-line no-console
  177. console.warn(error)
  178. }
  179. },
  180. generate() {
  181. this.mask.photo.generateMask(this.mask.id)
  182. },
  183. stop() {
  184. this.mask.photo.cancel()
  185. },
  186. openPreview() {
  187. this.mask.file.openItem()
  188. },
  189. onLoadingPhoto() {
  190. this.renderPhoto = false
  191. },
  192. onLoadedPhoto() {
  193. this.$nextTick(() => {
  194. this.renderPhoto = true
  195. })
  196. },
  197. },
  198. }
  199. </script>
  200. <style lang="scss" scoped>
  201. .mask {
  202. @apply mb-0 relative border-2 border-transparent;
  203. &.mask--dragging {
  204. .mask__dropzone {
  205. @apply flex opacity-100;
  206. }
  207. }
  208. &.mask--running {
  209. @apply border-primary;
  210. }
  211. &.mask--failed {
  212. @apply border-danger;
  213. }
  214. }
  215. .mask__dropzone {
  216. @apply absolute left-0 right-0 top-0 bottom-0 z-50;
  217. @apply bg-menus-dark-80 items-center justify-center;
  218. @apply hidden opacity-0 pointer-events-none;
  219. backdrop-filter: blur(6px);
  220. transition: opacity 0.2s ease-in-out;
  221. will-change: opacity;
  222. h2 {
  223. @apply text-white font-bold text-xl;
  224. }
  225. }
  226. .mask__photo {
  227. background-image: url('~@/assets/images/repeated-square-dark.png');
  228. will-change: transform;
  229. height: 350px;
  230. }
  231. .mask__photo__preview {
  232. @apply absolute top-0 bottom-0 left-0 right-0 z-10;
  233. @apply bg-contain bg-no-repeat bg-center;
  234. cursor: zoom-in;
  235. }
  236. .box__header {
  237. @apply pb-3;
  238. .tip {
  239. @apply ml-2;
  240. }
  241. }
  242. .buttons {
  243. @apply justify-end;
  244. .button {
  245. max-width: 90px;
  246. }
  247. }
  248. .terminal {
  249. @apply p-2 bg-black overflow-auto rounded;
  250. height: 400px;
  251. li {
  252. @apply font-mono text-xs text-generic-100 mb-3 block;
  253. &.text-danger {
  254. @apply text-danger-500;
  255. }
  256. }
  257. }
  258. </style>