diff --git a/README.md b/README.md index 5562831..3530d4d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,73 @@ +> This is a fork of [mokuro](https://github.com/kha-white/mokuro) that adds various improvements and featurs to the manga viewer. + +# Additions: + +## Anki connect integration: + +The reader now has an `Advanced settings` modal where you can specify various new settings, including enabling anki connect integration. + +When anki connect integration is enabled, it will allow you to instantly add the image and sentence to your last created card. You can configure whether or not to crop the image before adding it to the card as well as various other settings + +https://github.com/ZXY101/mokuro/assets/39561296/ade9e655-dcb9-4e99-9244-5df5971cc26b + +## Settings importing/exporting: + +You can now export your mokuro settings as a `.json` file and import them from any mokuro'd file (that was processed with this fork) to instantly carry your settings over. + +This is especially handy so that you do not need to reconfigure your anki connect settings every time you start a new manga. + +## Image preloading: + +When reading from a server mokuro used to only fetch the image as you navigate to it, this would cause momentary flashes of nothing that would dampen the reading experience. + +You can now specify how many pages you would like to preload ahead (up to 10), allowing you to read from a server without worrying about waiting for the page to load. + +## `mobile` flag (Default: True): + +If true, mokuro will generate additional files that are optimised to be viewed on mobile devices, the default files will still be generated. + +In the mobile files: + +- panzoom has been stripped out to allow the default pan/zoom behavior +- panning & zooming is limited to the page size, you can't fly off into the void +- you can now swipe to navigate (can adjust swipe threshold) +- easier to access navigation buttons have been added (can be hidden) +- easier to see page count has been added (can be hidden) + +### usage: + +```bash +$> mokuro my-manga-1 --mobile True --disable_confirmation True +// _ocr, my-manga-1.html, my-manga-1.mobile.html +``` + +`my-manga-1.html` being exactly the same as normal and `my-manga-1.mobile.html` being the mobile friendly version. + +## Misc: + +- You can now set the background color. +- You can now toggle bold font. +- Add new "on page turn" zoom mode (Keep zoom level but scroll to the top) +- Easier navigation for normal files (Clicking to the sides of the page will now navigate) + +# Installation + +```bash +$> pip install git+https://github.com/ZXY101/mokuro.git@master +``` + +(You may need to uninstall mokuro first) + +Or use in [Google colab](https://colab.research.google.com/drive/1i2ESDMmqwjpnOQQZx3vKP8Pd8R_Gtz4W?usp=sharing) + +--- + +### In use with [jidoujisho](https://github.com/lrorpilla/jidoujisho) + +https://user-images.githubusercontent.com/39561296/234891290-dcd79ce3-e215-4c3d-8f74-98c27917ac7f.mp4 + +--- + # mokuro Read Japanese manga with selectable text inside a browser. @@ -10,10 +80,11 @@ https://user-images.githubusercontent.com/22717958/164993274-3e8d1650-9be3-457d- mokuro is aimed towards Japanese learners, who want to read manga in Japanese with a pop-up dictionary like [Yomichan](https://github.com/FooSoft/yomichan). It works like this: + 1. Perform text detection and OCR for each page. -3. After processing a whole volume, generate a HTML file, which you can open in a browser. -4. All processing is done offline (before reading). You can transfer the resulting HTML file together with manga images to -another device (e.g. your mobile phone) and read there. +2. After processing a whole volume, generate a HTML file, which you can open in a browser. +3. All processing is done offline (before reading). You can transfer the resulting HTML file together with manga images to + another device (e.g. your mobile phone) and read there. mokuro uses [comic-text-detector](https://github.com/dmMaze/comic-text-detector) for text detection and [manga-ocr](https://github.com/kha-white/manga-ocr) for OCR. @@ -21,6 +92,7 @@ and [manga-ocr](https://github.com/kha-white/manga-ocr) for OCR. Try running on your manga in Colab: [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/kha-white/mokuro/blob/master/notebooks/mokuro_demo.ipynb) See also: + - [Mokuro2Pdf](https://github.com/Kartoffel0/Mokuro2Pdf), cli Ruby script to generate pdf files with selectable text from Mokuro's html overlay - [Xelieu's guide](https://rentry.co/lazyXel), a comprehensive guide on setting up a reading and mining workflow with manga-ocr/mokuro (and many other useful tips) @@ -68,6 +140,7 @@ For each volume, a separate HTML file will be generated. ## Run on a directory containing multiple volumes If your directory structure looks somewhat like this: + ``` manga_title/ ├─vol1/ @@ -88,9 +161,11 @@ mokuro --parent_dir manga_title/ --force_cpu - disable GPU --as_one_file - generate separate css and js files instead of embedding everything in html --disable_confirmation - run without asking for confirmation +--mobile - also generate a mobile optimised html file ``` # Contact + For any inquiries, please feel free to contact me at kha-white@mail.com # Acknowledgments diff --git a/mokuro/assets/cropper.min.css b/mokuro/assets/cropper.min.css new file mode 100644 index 0000000..e97743a --- /dev/null +++ b/mokuro/assets/cropper.min.css @@ -0,0 +1,9 @@ +/*! + * Cropper.js v1.5.13 + * https://fengyuanchen.github.io/cropperjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2022-11-20T05:30:43.444Z + */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed} \ No newline at end of file diff --git a/mokuro/assets/cropper.min.js b/mokuro/assets/cropper.min.js new file mode 100644 index 0000000..03aed4c --- /dev/null +++ b/mokuro/assets/cropper.min.js @@ -0,0 +1,10 @@ +/*! + * Cropper.js v1.5.13 + * https://fengyuanchen.github.io/cropperjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2022-11-20T05:30:46.114Z + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Cropper=e()}(this,function(){"use strict";function C(e,t){var i,a=Object.keys(e);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(e),t&&(i=i.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,i)),a}function S(a){for(var t=1;tt.length)&&(e=t.length);for(var i=0,a=new Array(e);it.width?3===i?o=t.height*e:h=t.width/e:3===i?h=t.width/e:o=t.height*e,{aspectRatio:e,naturalWidth:n,naturalHeight:a,width:o,height:h});this.canvasData=e,this.limited=1===i||2===i,this.limitCanvas(!0,!0),e.width=Math.min(Math.max(e.width,e.minWidth),e.maxWidth),e.height=Math.min(Math.max(e.height,e.minHeight),e.maxHeight),e.left=(t.width-e.width)/2,e.top=(t.height-e.height)/2,e.oldLeft=e.left,e.oldTop=e.top,this.initialCanvasData=g({},e)},limitCanvas:function(t,e){var i=this.options,a=this.containerData,n=this.canvasData,o=this.cropBoxData,h=i.viewMode,r=n.aspectRatio,s=this.cropped&&o;t&&(t=Number(i.minCanvasWidth)||0,i=Number(i.minCanvasHeight)||0,1=a.width&&(n.minLeft=Math.min(0,r),n.maxLeft=Math.max(0,r)),n.height>=a.height)&&(n.minTop=Math.min(0,t),n.maxTop=Math.max(0,t))):(n.minLeft=-n.width,n.minTop=-n.height,n.maxLeft=a.width,n.maxTop=a.height))},renderCanvas:function(t,e){var i,a,n,o,h=this.canvasData,r=this.imageData;e&&(e={width:r.naturalWidth*Math.abs(r.scaleX||1),height:r.naturalHeight*Math.abs(r.scaleY||1),degree:r.rotate||0},r=e.width,o=e.height,e=e.degree,i=90==(e=Math.abs(e)%180)?{width:o,height:r}:(a=e%90*Math.PI/180,i=Math.sin(a),n=r*(a=Math.cos(a))+o*i,r=r*i+o*a,90h.maxWidth||h.widthh.maxHeight||h.heighte.width?a.height=a.width/i:a.width=a.height*i),this.cropBoxData=a,this.limitCropBox(!0,!0),a.width=Math.min(Math.max(a.width,a.minWidth),a.maxWidth),a.height=Math.min(Math.max(a.height,a.minHeight),a.maxHeight),a.width=Math.max(a.minWidth,a.width*t),a.height=Math.max(a.minHeight,a.height*t),a.left=e.left+(e.width-a.width)/2,a.top=e.top+(e.height-a.height)/2,a.oldLeft=a.left,a.oldTop=a.top,this.initialCropBoxData=g({},a)},limitCropBox:function(t,e){var i,a,n=this.options,o=this.containerData,h=this.canvasData,r=this.cropBoxData,s=this.limited,c=n.aspectRatio;t&&(t=Number(n.minCropBoxWidth)||0,n=Number(n.minCropBoxHeight)||0,i=s?Math.min(o.width,h.width,h.width+h.left,o.width-h.left):o.width,a=s?Math.min(o.height,h.height,h.height+h.top,o.height-h.top):o.height,t=Math.min(t,o.width),n=Math.min(n,o.height),c&&(t&&n?ti.maxWidth||i.widthi.maxHeight||i.height=e.width&&i.height>=e.height?U:P),f(this.cropBox,g({width:i.width,height:i.height},x({translateX:i.left,translateY:i.top}))),this.cropped&&this.limited&&this.limitCanvas(!0,!0),this.disabled||this.output()},output:function(){this.preview(),y(this.element,_,this.getData())}},i={initPreview:function(){var t=this.element,i=this.crossOrigin,e=this.options.preview,a=i?this.crossOriginUrl:this.url,n=t.alt||"The image to preview",o=document.createElement("img");i&&(o.crossOrigin=i),o.src=a,o.alt=n,this.viewBox.appendChild(o),this.viewBoxImage=o,e&&("string"==typeof(o=e)?o=t.ownerDocument.querySelectorAll(e):e.querySelector&&(o=[e]),z(this.previews=o,function(t){var e=document.createElement("img");w(t,m,{width:t.offsetWidth,height:t.offsetHeight,html:t.innerHTML}),i&&(e.crossOrigin=i),e.src=a,e.alt=n,e.style.cssText='display:block;width:100%;height:auto;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation:0deg!important;"',t.innerHTML="",t.appendChild(e)}))},resetPreview:function(){z(this.previews,function(e){var i=Dt(e,m),i=(f(e,{width:i.width,height:i.height}),e.innerHTML=i.html,e),e=m;if(o(i[e]))try{delete i[e]}catch(t){i[e]=void 0}else if(i.dataset)try{delete i.dataset[e]}catch(t){i.dataset[e]=void 0}else i.removeAttribute("data-".concat(Ct(e)))})},preview:function(){var h=this.imageData,t=this.canvasData,e=this.cropBoxData,r=e.width,s=e.height,c=h.width,d=h.height,l=e.left-t.left-h.left,p=e.top-t.top-h.top;this.cropped&&!this.disabled&&(f(this.viewBoxImage,g({width:c,height:d},x(g({translateX:-l,translateY:-p},h)))),z(this.previews,function(t){var e=Dt(t,m),i=e.width,e=e.height,a=i,n=e,o=1;r&&(n=s*(o=i/r)),s&&eMath.abs(a-1)?i:a)&&(t.restore&&(o=this.getCanvasData(),h=this.getCropBoxData()),this.render(),t.restore)&&(this.setCanvasData(z(o,function(t,e){o[e]=t*n})),this.setCropBoxData(z(h,function(t,e){h[e]=t*n}))))},dblclick:function(){var t,e;this.disabled||this.options.dragMode===J||this.setDragMode((t=this.dragBox,e=$,(t.classList?t.classList.contains(e):-1y&&(D.x=y-f);break;case k:p+D.xx&&(D.y=x-v)}}var i,a,o,n=this.options,h=this.canvasData,r=this.containerData,s=this.cropBoxData,c=this.pointers,d=this.action,l=n.aspectRatio,p=s.left,m=s.top,u=s.width,g=s.height,f=p+u,v=m+g,w=0,b=0,y=r.width,x=r.height,M=!0,C=(!l&&t.shiftKey&&(l=u&&g?u/g:1),this.limited&&(w=s.minLeft,b=s.minTop,y=w+Math.min(r.width,h.width,h.left+h.width),x=b+Math.min(r.height,h.height,h.top+h.height)),c[Object.keys(c)[0]]),D={x:C.endX-C.startX,y:C.endY-C.startY};switch(d){case P:p+=D.x,m+=D.y;break;case B:0<=D.x&&(y<=f||l&&(m<=b||x<=v))?M=!1:(e(B),(u+=D.x)<0&&(d=k,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case T:D.y<=0&&(m<=b||l&&(p<=w||y<=f))?M=!1:(e(T),g-=D.y,m+=D.y,g<0&&(d=O,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case k:D.x<=0&&(p<=w||l&&(m<=b||x<=v))?M=!1:(e(k),u-=D.x,p+=D.x,u<0&&(d=B,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case O:0<=D.y&&(x<=v||l&&(p<=w||y<=f))?M=!1:(e(O),(g+=D.y)<0&&(d=T,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case E:if(l){if(D.y<=0&&(m<=b||y<=f)){M=!1;break}e(T),g-=D.y,m+=D.y,u=g*l}else e(T),e(B),!(0<=D.x)||fMath.abs(o)&&(o=i)})}),o),t),M=!1;break;case I:D.x&&D.y?(i=Et(this.cropper),p=C.startX-i.left,m=C.startY-i.top,u=s.minWidth,g=s.minHeight,0 or element.");this.element=t,this.options=g({},mt,u(e)&&e),this.cropped=!1,this.disabled=!1,this.pointers={},this.ready=!1,this.reloading=!1,this.replaced=!1,this.sized=!1,this.sizing=!1,this.init()}var t,e,i;return t=n,i=[{key:"noConflict",value:function(){return window.Cropper=jt,n}},{key:"setDefaults",value:function(t){g(mt,u(t)&&t)}}],(e=[{key:"init",value:function(){var t,e=this.element,i=e.tagName.toLowerCase();if(!e[c]){if(e[c]=this,"img"===i){if(this.isImg=!0,t=e.getAttribute("src")||"",!(this.originalUrl=t))return;t=e.src}else"canvas"===i&&window.HTMLCanvasElement&&(t=e.toDataURL());this.load(t)}}},{key:"load",value:function(t){var e,i,a,n,o,h,r=this;t&&(this.url=t,this.imageData={},e=this.element,(i=this.options).rotatable||i.scalable||(i.checkOrientation=!1),i.checkOrientation&&window.ArrayBuffer?dt.test(t)?lt.test(t)?this.read((h=(h=t).replace(Yt,""),a=atob(h),h=new ArrayBuffer(a.length),z(n=new Uint8Array(h),function(t,e){n[e]=a.charCodeAt(e)}),h)):this.clone():(o=new XMLHttpRequest,h=this.clone.bind(this),this.reloading=!0,(this.xhr=o).onabort=h,o.onerror=h,o.ontimeout=h,o.onprogress=function(){o.getResponseHeader("content-type")!==st&&o.abort()},o.onload=function(){r.read(o.response)},o.onloadend=function(){r.reloading=!1,r.xhr=null},i.checkCrossOrigin&&Nt(t)&&e.crossOrigin&&(t=Lt(t)),o.open("GET",t,!0),o.responseType="arraybuffer",o.withCredentials="use-credentials"===e.crossOrigin,o.send()):this.clone())}},{key:"read",value:function(t){var e=this.options,i=this.imageData,a=Xt(t),n=0,o=1,h=1;1
',o=(n=n.querySelector(".".concat(c,"-container"))).querySelector(".".concat(c,"-canvas")),h=n.querySelector(".".concat(c,"-drag-box")),s=(r=n.querySelector(".".concat(c,"-crop-box"))).querySelector(".".concat(c,"-face")),this.container=a,this.cropper=n,this.canvas=o,this.dragBox=h,this.cropBox=r,this.viewBox=n.querySelector(".".concat(c,"-view-box")),this.face=s,o.appendChild(i),v(t,L),a.insertBefore(n,t.nextSibling),X(i,K),this.initPreview(),this.bind(),e.initialAspectRatio=Math.max(0,e.initialAspectRatio)||NaN,e.aspectRatio=Math.max(0,e.aspectRatio)||NaN,e.viewMode=Math.max(0,Math.min(3,Math.round(e.viewMode)))||0,v(r,L),e.guides||v(r.getElementsByClassName("".concat(c,"-dashed")),L),e.center||v(r.getElementsByClassName("".concat(c,"-center")),L),e.background&&v(n,"".concat(c,"-bg")),e.highlight||v(s,Z),e.cropBoxMovable&&(v(s,G),w(s,d,P)),e.cropBoxResizable||(v(r.getElementsByClassName("".concat(c,"-line")),L),v(r.getElementsByClassName("".concat(c,"-point")),L)),this.render(),this.ready=!0,this.setDragMode(e.dragMode),e.autoCrop&&this.crop(),this.setData(e.data),l(e.ready)&&b(t,"ready",e.ready,{once:!0}),y(t,"ready"))}},{key:"unbuild",value:function(){var t;this.ready&&(this.ready=!1,this.unbind(),this.resetPreview(),(t=this.cropper.parentNode)&&t.removeChild(this.cropper),X(this.element,L))}},{key:"uncreate",value:function(){this.ready?(this.unbuild(),this.ready=!1,this.cropped=!1):this.sizing?(this.sizingImage.onload=null,this.sizing=!1,this.sized=!1):this.reloading?(this.xhr.onabort=null,this.xhr.abort()):this.image&&this.stop()}}])&&A(t.prototype,e),i&&A(t,i),Object.defineProperty(t,"prototype",{writable:!1}),n}();return g(Pt.prototype,t,i,e,Rt,St,At),Pt}); \ No newline at end of file diff --git a/mokuro/common.css b/mokuro/common.css new file mode 100644 index 0000000..a5587c5 --- /dev/null +++ b/mokuro/common.css @@ -0,0 +1,394 @@ +:root { + --sentenceInputDisplay: block; + --connectColor: #6feef7ec; +} + +body { + overflow: hidden; + margin: 0; + background-color: var(--colorBackground); +} + +.notransition { + transition: none !important; +} + +#topMenu *, +.popup * { + font-family: 'Open Sans', sans-serif; +} + +.pageContainer * { + font-family: 'Noto Sans JP', 'Meiryo', 'MS Gothic', sans-serif; + font-weight: var(--textBoxFontWeight); +} + +.pageContainer { + position: relative; + margin: 0 auto; +} + +.pageContainer:hover .textBox { + border: 2px solid var(--textBoxBorderHoverColor); +} + +.textBox { + display: var(--textBoxDisplay); + position: absolute; + padding: 0; + line-height: 1.1em; + font-size: 16pt; + white-space: nowrap; + border: 1px solid rgba(0, 0, 0, 0); +} + +.textBox:hover { + background: rgb(255, 255, 255); + border: 1px solid rgba(0, 0, 0, 0); + z-index: 999 !important; +} + +.textBox p { + display: none; + white-space: nowrap; + letter-spacing: 0.1em; + line-height: 1.1em; + margin: 0; + background-color: rgb(255, 255, 255); +} + +.textBoxFontSizeOverride .textBox p { + font-size: var(--textBoxFontSize) !important; +} + +.textBox:hover p { + display: table; +} + +.connect-button { + display: var(--sentenceConnectButtonDisplay) !important; + opacity: 0; + position: absolute; + margin-right: calc(var(--textBoxFontSize) * -1); + margin-top: calc(var(--textBoxFontSize) * -1); + cursor: pointer; + background-color: var(--connectColor); + border: 1px rgb(0, 0, 0) solid; + border-radius: 50%; + width: calc(var(--textBoxFontSize)); + height: calc(var(--textBoxFontSize)); +} + +.textBox:hover .connect-button { + opacity: 1; + display: block; +} + +.textBox:hover .connect-button svg { + max-height: 1.5em; +} + +.modal { + border-radius: 6px; + border: 1px black solid; + min-width: 40vw; +} + +.dialog-button { + background-color: #323f49; /* Green */ + border: none; + border-radius: 6px; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + cursor: pointer; +} + +#sentence-input { + width: 100%; + resize: none; + display: var(--sentenceInputDisplay); +} + +.dialog-container { + display: flex; + flex-direction: column; + gap: 5px; +} + +#dialog-actions { + display: flex; + justify-content: space-between; + gap: 10px; +} + +#crop-image { + display: block; + max-height: 400px; + max-width: 400px; +} + +.form-item { + display: flex; + justify-content: space-between; + border-bottom: 1px solid black; + padding: 10px 0 10px 0; +} + +.text-input { + width: 50%; +} + +.hovered { + background: rgb(255, 255, 255); + border: 1px solid rgba(0, 0, 0, 0); + z-index: 999 !important; +} + +.hovered p { + display: table; +} + +#pagesContainer.inverted { + -webkit-filter: invert(100%); + filter: invert(100%); +} + +#showMenuA { + display: inline-block; + position: fixed; + left: 0; + top: 0; + width: 3em; + height: 3em; + z-index: 1000; + background-color: rgba(0, 0, 0, 0); +} + +#topMenu { + position: fixed; + z-index: 1000; + top: 0; + left: 0; + margin: 5px; + background: var(--color1); + border-radius: 3px; + box-shadow: 0px 0px 8px 0px var(--color3a); + transition: all 0.5s ease-out, max-width 0s ease-in; + visibility: visible; + opacity: 1; + max-width: 100vw; + white-space: nowrap; +} + +#topMenu.hidden { + max-width: 2.5em; + visibility: hidden; + opacity: 0; + overflow: hidden; + transition: all 1s ease-in, max-width 0.5s ease-out; +} + +#topMenu * { + font-size: 1em; + vertical-align: middle; +} + +#topMenu input { + height: 1.4em; + margin: 0 6px; +} + +#pageIdxDisplay { + margin-left: 0.5em; + margin-right: 0.5em; +} + +.menuButton { + background-color: rgba(0, 0, 0, 0); + color: var(--color3); + border: none; + width: 2.5em; + height: 2.2em; + border-radius: 3px; +} + +.menuButton svg { + max-height: 1.5em; +} + +.menuButton:hover { + background-color: var(--color2); +} + +.dropdown:hover #dropbtn { + background-color: var(--color2); +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background-color: var(--color1); + box-shadow: 0px 0px 8px 0px var(--color3a); + z-index: 1000; + border-radius: 3px; + max-height: 90vh; + max-width: 355px; + overflow: hidden; + overflow-y: auto; +} + +.dropdown-content .buttonRow { + display: flex; +} + +.dropdown-content .menuButton { + flex: 1 1 auto; + align-self: center; +} + +.dropdown-content .dropdown-option { + color: var(--color3); + padding: 10px 10px; + text-decoration: none; + white-space: nowrap; + display: block; + border-radius: 3px; +} + +.dropdown-content .dropdown-option:hover { + background-color: var(--color2); +} + +.dropdown-content .dropdown-option [type='checkbox'] { + vertical-align: middle; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +#dimOverlay { + display: none; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1001; + background-color: rgba(0, 0, 0, 0.5); +} + +.popup { + display: none; + position: fixed; + top: 50vh; + left: 50vw; + width: min(720px, 80vw); + height: min(480px, 80vh); + margin-left: max(-360px, -40vw); + margin-top: max(-240px, -40vh); + z-index: 1002; + background-color: var(--color1); + color: var(--color3); + box-shadow: 0px 0px 10px 4px var(--color3a); + border-radius: 3px; + overflow: auto; + padding: 20px; + box-sizing: border-box; + line-height: 1.5em; +} + +#preload-image { + position: absolute; + width: 0; + height: 0; + overflow: hidden; + z-index: -1; +} + +#snackbar { + visibility: hidden; + min-width: 250px; + margin-left: -125px; + background-color: var(--connectColor); + color: black; + text-align: center; + border-radius: 6px; + padding: 10px; + border: 1px solid black; + position: fixed; + z-index: 1; + left: 50%; + bottom: 30px; +} + +#snackbar.show { + visibility: visible; + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; +} + +@-webkit-keyframes fadein { + from { + bottom: 0; + opacity: 0; + } + to { + bottom: 30px; + opacity: 1; + } +} + +@keyframes fadein { + from { + bottom: 0; + opacity: 0; + } + to { + bottom: 30px; + opacity: 1; + } +} + +@-webkit-keyframes fadeout { + from { + bottom: 30px; + opacity: 1; + } + to { + bottom: 0; + opacity: 0; + } +} + +@keyframes fadeout { + from { + bottom: 30px; + opacity: 1; + } + to { + bottom: 0; + opacity: 0; + } +} + +/*hide arrows from number input*/ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type='number'] { + -moz-appearance: textfield; +} diff --git a/mokuro/common.js b/mokuro/common.js new file mode 100644 index 0000000..959c123 --- /dev/null +++ b/mokuro/common.js @@ -0,0 +1,639 @@ +let pz; + +const preload = document.getElementById('preload-image'); + +let num_pages = -1; +let pc = document.getElementById('pagesContainer'); +let r = document.querySelector(':root'); +let showAboutOnStart = false; + +let storageKey = 'mokuro_' + window.location.pathname; + +function saveState() { + localStorage.setItem(storageKey, JSON.stringify(state)); +} + +function loadState() { + let newState = localStorage.getItem(storageKey); + + if (newState !== null) { + state = JSON.parse(newState); + } + + updateUI(); + updateProperties(); +} + +function getBackgroundImage(page) { + const pageContainer = page?.querySelector('.pageContainer'); + return pageContainer?.style?.backgroundImage + ?.slice(4, -1) + .replace(/['"]/g, ''); +} + +function preloadImage() { + let preloadContent = ''; + + for (let i = 0; i < state.preloadAmount; i++) { + const page = getPage(state.page_idx + i); + const backgroundImageUrl = getBackgroundImage(page); + + if (backgroundImageUrl) { + preloadContent += `url(${backgroundImageUrl}) `; + } + } + preload.style.content = preloadContent; +} + +const connectEnabled = document.getElementById('connect-enabled'); +const editSentence = document.getElementById('edit-sentence-enabled'); +const cropImage = document.getElementById('crop-enabled'); +const overwriteImage = document.getElementById('overwrite-enabled'); +const sentenceField = document.getElementById('sentence-field-input'); +const pictureField = document.getElementById('picture-field-input'); +const settingsDialog = document.getElementById('settings-dialog'); + +connectEnabled.addEventListener('change', () => { + state.connectEnabled = connectEnabled.checked; + saveState(); + updateProperties(); +}); +editSentence.addEventListener('change', () => { + state.editSentence = editSentence.checked; + saveState(); + updateProperties(); +}); +cropImage.addEventListener('change', () => { + state.cropImage = cropImage.checked; + saveState(); + updateProperties(); +}); +overwriteImage.addEventListener('change', () => { + state.overwriteImage = overwriteImage.checked; + saveState(); + updateProperties(); +}); +sentenceField.addEventListener('change', () => { + state.sentenceField = sentenceField.value; + saveState(); + updateProperties(); +}); +pictureField.addEventListener('change', () => { + state.pictureField = pictureField.value; + saveState(); + updateProperties(); +}); + +function initTextBoxes() { + // Add event listeners for toggling ocr text boxes with the toggleOCRTextBoxes option. + let textBoxes = document.querySelectorAll('.textBox'); + for (let i = 0; i < textBoxes.length; i++) { + textBoxes[i].addEventListener('click', function (e) { + if (state.toggleOCRTextBoxes) { + this.classList.add('hovered'); + // Remove hovered state from all other .textBoxes + for (let j = 0; j < textBoxes.length; j++) { + if (i !== j) { + textBoxes[j].classList.remove('hovered'); + } + } + } + }); + } + // When clicking off of a .textBox, remove the hovered state. + document.addEventListener('click', function (e) { + if (state.toggleOCRTextBoxes) { + if (e.target.closest('.textBox') === null) { + let textBoxes = document.querySelectorAll('.textBox'); + for (let i = 0; i < textBoxes.length; i++) { + textBoxes[i].classList.remove('hovered'); + } + } + } + }); +} + +document.getElementById('menuR2l').addEventListener( + 'click', + function () { + state.r2l = document.getElementById('menuR2l').checked; + saveState(); + updatePage(state.page_idx); + }, + false +); + +document.getElementById('menuHasCover').addEventListener( + 'click', + function () { + state.hasCover = document.getElementById('menuHasCover').checked; + saveState(); + updatePage(state.page_idx); + }, + false +); + +document.getElementById('menuTextBoxBorders').addEventListener( + 'click', + function () { + state.textBoxBorders = + document.getElementById('menuTextBoxBorders').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuEditableText').addEventListener( + 'click', + function () { + state.editableText = document.getElementById('menuEditableText').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuDisplayOCR').addEventListener( + 'click', + function () { + state.displayOCR = document.getElementById('menuDisplayOCR').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuFontBold').addEventListener( + 'click', + function () { + state.fontBold = document.getElementById('menuFontBold').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuEInkMode').addEventListener( + 'click', + function () { + state.eInkMode = document.getElementById('menuEInkMode').checked; + saveState(); + updateProperties(); + if (state.eInkMode) { + eInkRefresh(); + } + }, + false +); + +document.getElementById('menuToggleOCRTextBoxes').addEventListener( + 'click', + function () { + state.toggleOCRTextBoxes = document.getElementById( + 'menuToggleOCRTextBoxes' + ).checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuBackgroundColor').addEventListener( + 'input', + function (event) { + state.backgroundColor = event.target.value; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuAdvanced').addEventListener( + 'click', + () => { + settingsDialog.showModal(); + connectEnabled.checked = state.connectEnabled; + editSentence.checked = state.editSentence; + cropImage.checked = state.cropImage; + overwriteImage.checked = state.overwriteImage; + sentenceField.value = state.sentenceField; + pictureField.value = state.pictureField; + pictureField.disabled = false; + sentenceField.disabled = false; + if (pz) { + pz.pause(); + } + }, + false +); + +settingsDialog.addEventListener('close', (e) => { + if (pz) { + pz.resume(); + } +}); + +document.getElementById('menuAbout').addEventListener( + 'click', + function () { + document.getElementById('popupAbout').style.display = 'block'; + document.getElementById('dimOverlay').style.display = 'initial'; + }, + false +); + +document.getElementById('menuReset').addEventListener( + 'click', + function () { + let page_idx = state.page_idx; + state = JSON.parse(JSON.stringify(defaultState)); + updateUI(); + updatePage(page_idx); + updateProperties(); + }, + false +); + +document.getElementById('dimOverlay').addEventListener( + 'click', + function () { + document.getElementById('popupAbout').style.display = 'none'; + document.getElementById('dimOverlay').style.display = 'none'; + if (pz) { + pz.resume(); + } + }, + false +); + +document.getElementById('menuFontSize').addEventListener('change', (e) => { + state.fontSize = e.target.value; + saveState(); + updateProperties(); +}); + +document.getElementById('pageIdxInput').addEventListener('change', (e) => { + updatePage(e.target.value - 1); +}); + +document.getElementById('buttonHideMenu').addEventListener( + 'click', + function () { + document.getElementById('showMenuA').style.display = 'inline-block'; + document.getElementById('topMenu').classList.add('hidden'); + }, + false +); + +document.getElementById('showMenuA').addEventListener( + 'click', + function () { + document.getElementById('showMenuA').style.display = 'none'; + document.getElementById('topMenu').classList.remove('hidden'); + }, + false +); + +document + .getElementById('buttonLeftLeft') + .addEventListener('click', inputLeftLeft, false); +document + .getElementById('buttonLeft') + .addEventListener('click', inputLeft, false); +document + .getElementById('buttonRight') + .addEventListener('click', inputRight, false); +document + .getElementById('buttonRightRight') + .addEventListener('click', inputRightRight, false); + +document.addEventListener('keydown', function onEvent(e) { + switch (e.key) { + case 'PageUp': + prevPage(); + break; + + case 'PageDown': + nextPage(); + break; + + case 'Home': + firstPage(); + break; + + case 'End': + lastPage(); + break; + + case ' ': + nextPage(); + break; + } +}); + +function isPageFirstOfPair(page_idx) { + if (state.singlePageView) { + return true; + } else { + if (state.hasCover) { + return page_idx === 0 || page_idx % 2 === 1; + } else { + return page_idx % 2 === 0; + } + } +} + +function getPage(page_idx) { + return document.getElementById('page' + page_idx); +} + +function firstPage() { + updatePage(0); +} + +function lastPage() { + updatePage(num_pages - 1); +} + +function prevPage() { + updatePage(state.page_idx - (state.singlePageView ? 1 : 2)); +} + +function nextPage() { + updatePage(state.page_idx + (state.singlePageView ? 1 : 2)); +} + +function inputLeftLeft() { + if (state.r2l) { + lastPage(); + } else { + firstPage(); + } +} + +function inputLeft() { + if (state.r2l) { + nextPage(); + } else { + prevPage(); + } +} + +function inputRight() { + if (state.r2l) { + prevPage(); + } else { + nextPage(); + } +} + +function inputRightRight() { + if (state.r2l) { + firstPage(); + } else { + lastPage(); + } +} + +function eInkRefresh() { + pc.classList.add('inverted'); + document.body.style.backgroundColor = 'black'; + setTimeout(function () { + pc.classList.remove('inverted'); + document.body.style.backgroundColor = + r.style.getPropertyValue('--colorBackground'); + }, 300); +} + +const dialog = document.getElementById('dialog'); +const sentenceInput = document.getElementById('sentence-input'); +const confirmBtn = dialog.querySelector('#confirm-btn'); +let cropper; + +dialog.addEventListener('close', (e) => { + if (pz) { + pz.resume(); + } + cropper.destroy(); +}); + +async function updateLast(sentence, img) { + if (state.cropImage) { + const viewportmeta = document.querySelector('meta[name=viewport]'); + + viewportmeta.setAttribute( + 'content', + 'initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' + ); + + const image = document.getElementById('crop-image'); + image.setAttribute('src', img); + sentenceInput.value = sentence; + + cropper = new Cropper(image, { + viewMode: 1, + zoomable: false, + dragMode: 'none', + }); + + dialog.showModal(); + viewportmeta.setAttribute( + 'content', + 'initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0' + ); + if (pz) { + pz.pause(); + } + } else { + const { id } = await getLastCard(); + const url = new URL(img, document.baseURI).href; + const picture = await getImage(url); + + await updateLastCard(id, picture, sentence); + } +} + +function ankiConnect(action, version = 6, params = {}) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.addEventListener('error', () => reject('failed to issue request')); + xhr.addEventListener('load', () => { + try { + const response = JSON.parse(xhr.responseText); + if (Object.getOwnPropertyNames(response).length != 2) { + throw 'response has an unexpected number of fields'; + } + if (!response.hasOwnProperty('error')) { + throw 'response is missing required error field'; + } + if (!response.hasOwnProperty('result')) { + throw 'response is missing required result field'; + } + if (response.error) { + throw response.error; + } + resolve(response.result); + } catch (e) { + reject(e); + } + }); + + xhr.open('POST', 'http://127.0.0.1:8765'); + xhr.send(JSON.stringify({ action, version, params })); + }); +} + +async function getLastCard() { + const notesToday = await ankiConnect('findNotes', 6, { query: 'added:1' }); + const id = notesToday.sort().at(-1); + + return { id }; +} + +function getCroppedImage() { + return cropper + .getCroppedCanvas() + .toDataURL('image/webp') + .split(';base64,')[1]; +} + +function getImage(url) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.addEventListener('load', () => { + const reader = new FileReader(); + reader.addEventListener('load', () => { + resolve(reader.result.split(';base64,')[1]); + }); + reader.readAsDataURL(xhr.response); + }); + + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.overrideMimeType('image/webp'); + xhr.send(); + }); +} + +async function inheritHtml(noteId) { + const htmlTagRegex = RegExp('<[^>]*>(.*?)]*>', 'ig'); + + const [noteInfo] = await ankiConnect('notesInfo', 6, { notes: [noteId] }); + const markedUp = noteInfo?.fields[state.sentenceField]?.value; + const markedUpWithoutBreaklines = markedUp.replace('
', ''); + let inherited = sentenceInput.value; + + while (true) { + const match = htmlTagRegex.exec(markedUpWithoutBreaklines); + + if (match === null || match.length < 2) { + break; + } + + inherited = inherited.replace(match[1], match[0]); + } + + return inherited; +} + +async function updateLastCard(id, picture) { + const timeSinceCardCreated = Math.floor((Date.now() - id) / 60000); + + if (timeSinceCardCreated > 5) { + showSnackbar('Error: Card created over 5 minutes ago'); + return; + } + + const fields = {}; + + if (state.editSentence) { + const sentence = await inheritHtml(id); + if (sentence) { + fields[state.sentenceField] = sentence; + } + } + + if (state.overwriteImage) { + fields[state.pictureField] = ''; + } + + ankiConnect('updateNoteFields', 6, { + note: { + id, + fields, + picture: { + filename: `_${id}.webp`, + data: picture, + fields: [state.pictureField], + }, + }, + }).then(() => { + showSnackbar('Card updated'); + }); +} + +confirmBtn.addEventListener('click', async (event) => { + event.preventDefault(); + + const cropped = getCroppedImage(); + const { id } = await getLastCard(); + + await updateLastCard(id, cropped); + + dialog.close(); +}); + +function exportSettings() { + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify(state) + )}`; + const link = document.createElement('a'); + link.href = jsonString; + link.download = 'settings.json'; + link.click(); +} + +const fileInput = document.getElementById('import-input'); +const exportButton = document.getElementById('export-button'); + +fileInput.addEventListener('change', importSettings); +exportButton.addEventListener('click', exportSettings); + +function importSettings() { + const [file] = fileInput.files; + const reader = new FileReader(); + + reader.addEventListener('load', () => { + const page_idx = state.page_idx; + state = JSON.parse(reader.result); + updateUI(); + updatePage(page_idx); + updateProperties(); + location.reload(); + }); + + if (file) { + reader.readAsText(file); + } +} + +function showSnackbar(message) { + const snackbar = document.getElementById('snackbar'); + snackbar.innerHTML = message; + snackbar.className = 'show'; + + setTimeout(() => { + snackbar.className = snackbar.className.replace('show', ''); + }, 3000); +} + +document.getElementById('menuPreloadAmount').addEventListener( + 'input', + function (event) { + state.preloadAmount = event.target.value; + saveState(); + updateProperties(); + }, + false +); diff --git a/mokuro/overlay_generator.py b/mokuro/overlay_generator.py index 46fd563..9057b8a 100644 --- a/mokuro/overlay_generator.py +++ b/mokuro/overlay_generator.py @@ -15,8 +15,15 @@ SCRIPT_PATH = Path(__file__).parent / 'script.js' STYLES_PATH = Path(__file__).parent / 'styles.css' +COMMON_SCRIPT_PATH = Path(__file__).parent / 'common.js' +COMMON_STYLES_PATH = Path(__file__).parent / 'common.css' +MOBILE_SCRIPT_PATH = Path(__file__).parent / 'script.mobile.js' +MOBILE_STYLES_PATH = Path(__file__).parent / 'styles.mobile.css' + PANZOOM_PATH = ASSETS_PATH / 'panzoom.min.js' ICONS_PATH = ASSETS_PATH / 'icons' +CROPPER_JS_PATH = ASSETS_PATH / 'cropper.min.js' +CROPPER_CSS_PATH = ASSETS_PATH / 'cropper.min.css' ABOUT = f"""

HTML overlay generated with mokuro version {__version__}

@@ -40,6 +47,13 @@

うちの猫’ず日記 © がぁさん

""" +MOBILE_ABOUT = f""" +

This is a temporary fork of mokuro

+

The main aim of this fork is to try and improve the mobile viewing experience

+

This file should only be be viewed on a mobile device since it relies on the browsers default handling for panning & zooming

+

To navigate, you can use the buttons in the top menu; the new buttons at the bottom of the screen or swipe to navigate.

+

Recommended to be used with jidoujisho

+""" class OverlayGenerator: def __init__(self, @@ -55,7 +69,7 @@ def init_models(self): if self.mpocr is None: self.mpocr = MangaPageOcr(self.pretrained_model_name_or_path, self.force_cpu, **self.kwargs) - def process_dir(self, path, as_one_file=True, is_demo=False): + def process_dir(self, path, as_one_file=True, mobile=False, is_demo=False): path = Path(path).expanduser().absolute() assert path.is_dir(), f'{path} must be a directory' if path.stem == '_ocr': @@ -66,10 +80,22 @@ def process_dir(self, path, as_one_file=True, is_demo=False): results_dir = out_dir / '_ocr' / path.name results_dir.mkdir(parents=True, exist_ok=True) + if mobile: + no_media_file_path = path / '.nomedia' + no_media_file = open(no_media_file_path, 'w') + no_media_file.close() + if not as_one_file: + shutil.copy(COMMON_SCRIPT_PATH, out_dir / 'common.js') + shutil.copy(COMMON_STYLES_PATH, out_dir / 'common.css') shutil.copy(SCRIPT_PATH, out_dir / 'script.js') shutil.copy(STYLES_PATH, out_dir / 'styles.css') shutil.copy(PANZOOM_PATH, out_dir / 'panzoom.min.js') + shutil.copy(CROPPER_JS_PATH, out_dir / 'cropper.min.js') + shutil.copy(CROPPER_CSS_PATH, out_dir / 'cropper.min.css') + if mobile: + shutil.copy(MOBILE_SCRIPT_PATH, out_dir / 'script.mobile.js') + shutil.copy(MOBILE_STYLES_PATH, out_dir / 'styles.mobile.css') img_paths = [p for p in path.glob('**/*') if p.is_file() and p.suffix.lower() in ('.jpg', '.jpeg', '.png')] img_paths = natsorted(img_paths) @@ -93,17 +119,40 @@ def process_dir(self, path, as_one_file=True, is_demo=False): title = f'mokuro {__version__} demo' else: title = f'{path.name} | mokuro' - index_html = self.get_index_html(page_htmls, title, as_one_file, is_demo) + index_html = self.get_index_html(page_htmls, title, as_one_file, False, is_demo, ) (out_dir / (path.name + '.html')).write_text(index_html, encoding='utf-8') + if mobile: + index_html = self.get_index_html(page_htmls, title, as_one_file, mobile, is_demo, ) + (out_dir / (path.name + '.mobile.html')).write_text(index_html, encoding='utf-8') - def get_index_html(self, page_htmls, title, as_one_file=True, is_demo=False): + def get_index_html(self, page_htmls=None, title='Mokuro', as_one_file=True, mobile=False, is_demo=False, pages=None, page_count=None): doc, tag, text = Doc().tagtext() + def text_input(id_, text_content): + with tag('label', klass='form-item'): + with tag('label'): + text(text_content) + with tag('input', type='text', id=id_, klass='text-input'): + pass + def toggle_input(id_, text_content): + with tag('label', klass='form-item'): + with tag('label'): + text(text_content) + with tag('input', type='checkbox', id=id_): + pass + def file_input(id_, text_content): + with tag('label', klass='form-item'): + with tag('label'): + text(text_content) + with tag('input', type='file', id=id_, accept='.json'): + pass with tag('html'): doc.asis('') doc.asis('') - doc.asis( - '') + if mobile: + doc.asis('') + else: + doc.asis('') with tag('head'): with tag('title'): @@ -111,13 +160,62 @@ def get_index_html(self, page_htmls, title, as_one_file=True, is_demo=False): if as_one_file: with tag('style'): - doc.asis(STYLES_PATH.read_text()) + if mobile: + doc.asis(MOBILE_STYLES_PATH.read_text()) + else: + doc.asis(STYLES_PATH.read_text()) + with tag('style'): + doc.asis(CROPPER_CSS_PATH.read_text()) + with tag('style'): + doc.asis(COMMON_STYLES_PATH.read_text()) else: - with tag('link', rel='stylesheet', href='styles.css'): + if mobile: + with tag('link', rel='stylesheet', href='styles.mobile.css'): + pass + else: + with tag('link', rel='stylesheet', href='styles.css'): + pass + with tag('link', rel='stylesheet', href='common.css'): pass with tag('body'): - self.top_menu(doc, tag, text, len(page_htmls)) + with tag('dialog', id='dialog', klass='modal'): + with tag('div', klass='dialog-container'): + with tag('h2'): + text('Quick Edit') + with tag('img', id='crop-image'): + pass + with tag('form', id='dialog-actions'): + with tag('button', klass='dialog-button', value='cancel', formmethod='dialog'): + text('Cancel') + with tag('textarea', id='sentence-input'): + pass + with tag('button', klass='dialog-button', id='confirm-btn', formmethod='dialog'): + text('Confirm') + with tag('dialog', id='settings-dialog', klass='modal'): + with tag('div', klass='dialog-container'): + with tag('h2'): + text('Advanced settings') + pass + toggle_input('connect-enabled', 'Enable anki connect integration?') + toggle_input('edit-sentence-enabled', 'Enable sentence grabbing?') + toggle_input('crop-enabled', 'Crop picture before updating note?') + toggle_input('overwrite-enabled', 'Overwrite picture?') + text_input('sentence-field-input', 'Sentence field:') + text_input('picture-field-input', 'Picture field:') + file_input('import-input', 'Import settings:') + with tag('button', id='export-button', klass='dialog-button'): + text('Export settings') + with tag('form', id='dialog-actions'): + with tag('button', klass='dialog-button', value='cancel', formmethod='dialog'): + text('Close') + with tag('div', id='snackbar'): + pass + + if page_count is not None: + self.top_menu(doc, tag, text, page_count, mobile) + else: + self.top_menu(doc, tag, text, len(page_htmls), mobile) with tag('div', id='dimOverlay'): pass @@ -126,37 +224,79 @@ def get_index_html(self, page_htmls, title, as_one_file=True, is_demo=False): if is_demo: doc.asis(ABOUT_DEMO) else: - doc.asis(ABOUT) + if mobile: + doc.asis(MOBILE_ABOUT) + else: + doc.asis(ABOUT) - with tag('a', id='leftAScreen', href='#'): + with tag('div', id='page-num'): pass - with tag('a', id='rightAScreen', href='#'): - pass + if mobile: + with tag('button', id='back', klass='btn'): + text('<') + pass - with tag('div', id='pagesContainer'): - for i, page_html in enumerate(page_htmls): - with tag('div', id=f'page{i}', klass='page'): - doc.asis(page_html) + with tag('button', id='forward', klass='btn'): + text('>') + pass - with tag('a', id='leftAPage', href='#'): + if not mobile: + with tag('a', id='leftAScreen', href='#'): + pass + + with tag('a', id='rightAScreen', href='#'): pass - with tag('a', id='rightAPage', href='#'): + if pages is not None: + doc.asis(pages) + else: + with tag('div', id='preload-image'): pass + with tag('div', id='pagesContainer'): + with tag('button', id='left-nav', klass='nav-btn'): + pass; + for i, page_html in enumerate(page_htmls): + with tag('div', id=f'page{i}', klass='page'): + doc.asis(page_html) + if not mobile: + with tag('button', id=f'connect-{i}', klass='connect'): + pass + if not mobile: + with tag('a', id='leftAPage', href='#'): + pass + with tag('a', id='rightAPage', href='#'): + pass + with tag('button', id='right-nav', klass='nav-btn'): + pass; if as_one_file: with tag('script'): - doc.asis(PANZOOM_PATH.read_text()) - + doc.asis(COMMON_SCRIPT_PATH.read_text()) with tag('script'): - doc.asis(SCRIPT_PATH.read_text()) + doc.asis(CROPPER_JS_PATH.read_text()) + if mobile: + with tag('script'): + doc.asis(MOBILE_SCRIPT_PATH.read_text()) + else: + with tag('script'): + doc.asis(PANZOOM_PATH.read_text()) + + with tag('script'): + doc.asis(SCRIPT_PATH.read_text()) else: - with tag('script', src='panzoom.min.js'): + with tag('script', src='cropper.min.js'): pass - - with tag('script', src='script.js'): + with tag('style', src='cropper.min.css'): pass + if mobile : + with tag('script', src='script.mobile.js'): + pass + else: + with tag('script', src='panzoom.min.js'): + pass + with tag('script', src='script.js'): + pass if is_demo: with tag('script'): @@ -165,7 +305,7 @@ def get_index_html(self, page_htmls, title, as_one_file=True, is_demo=False): html = doc.getvalue() return html - def top_menu(self, doc, tag, text, num_pages): + def top_menu(self, doc, tag, text, num_pages, mobile): with tag('a', id='showMenuA', href='#'): pass @@ -196,9 +336,9 @@ def top_menu(self, doc, tag, text, num_pages): with tag('span', style='color:rgba(255,255,255,0.1);font-size:1px;'): text('。') - self.dropdown_menu(doc, tag, text) + self.dropdown_menu(doc, tag, text, mobile) - def dropdown_menu(self, doc, tag, text): + def dropdown_menu(self, doc, tag, text, mobile): def option_click(id_, text_content): with tag('a', href='#', klass='dropdown-option', id=id_): text(text_content) @@ -206,7 +346,6 @@ def option_click(id_, text_content): def option_toggle(id_, text_content): with tag('label', klass='dropdown-option'): text(text_content) - with tag('input', type='checkbox', id=id_): pass @@ -217,46 +356,64 @@ def option_select(id_, text_content, values): for value in values: with tag('option', value=value): text(value) - + def option_range(id_, text_content, min, max, value): + with tag('label', klass='dropdown-option'): + text(text_content) + with tag('input', type='range', min=min, max=max, value=value, id=id_): + pass def option_color(id_, text_content, value): - with tag('label', klass='dropdown-option'): - text(text_content) - with tag('input', type='color', value=value, id=id_): - pass - + with tag('label', klass='dropdown-option'): + text(text_content) + with tag('input', type='color', value=value, id=id_): + pass with tag('div', klass='dropdown'): with tag('button', id='dropbtn', klass='menuButton'): doc.asis(self.get_icon('menu-hamburger-svgrepo-com')) with tag('div', klass='dropdown-content'): - with tag('div', klass='buttonRow'): - with tag('button', id='menuFitToScreen', klass='menuButton'): - doc.asis(self.get_icon('expand-svgrepo-com')) - with tag('button', id='menuFitToWidth', klass='menuButton'): - doc.asis(self.get_icon('expand-width-svgrepo-com')) - with tag('button', id='menuOriginalSize', klass='menuButton'): - text('1:1') - with tag('button', id='menuFullScreen', klass='menuButton'): - doc.asis(self.get_icon('fullscreen-svgrepo-com')) - - option_select('menuDefaultZoom', 'on page turn: ', [ - 'fit to screen', - 'fit to width', - 'original size', - 'keep zoom level', - ]) + if not mobile: + with tag('div', klass='buttonRow'): + with tag('button', id='menuFitToScreen', klass='menuButton'): + doc.asis(self.get_icon('expand-svgrepo-com')) + with tag('button', id='menuFitToWidth', klass='menuButton'): + doc.asis(self.get_icon('expand-width-svgrepo-com')) + with tag('button', id='menuOriginalSize', klass='menuButton'): + text('1:1') + with tag('button', id='menuFullScreen', klass='menuButton'): + doc.asis(self.get_icon('fullscreen-svgrepo-com')) + + option_select('menuDefaultZoom', 'on page turn: ', [ + 'fit to screen', + 'fit to width', + 'original size', + 'keep zoom level', + 'keep zoom, go to top', + ]) + option_toggle('menuR2l', 'right to left') - option_toggle('menuDoublePageView', 'display two pages ') + if not mobile: + option_toggle('menuDoublePageView', 'display two pages ') option_toggle('menuHasCover', 'first page is cover ') - option_toggle('menuCtrlToPan', 'ctrl+mouse to move ') + if not mobile: + option_toggle('menuCtrlToPan', 'ctrl+mouse to move ') option_toggle('menuDisplayOCR', 'OCR enabled ') option_toggle('menuTextBoxBorders', 'display boxes outlines ') option_toggle('menuEditableText', 'editable text ') option_select('menuFontSize', 'font size: ', ['auto', 9, 10, 11, 12, 14, 16, 18, 20, 24, 32, 40, 48, 60]) + option_toggle('menuFontBold', 'bold font') option_toggle('menuEInkMode', 'e-ink mode ') option_toggle('menuToggleOCRTextBoxes', 'toggle OCR text boxes on click') - option_color('menuBackgroundColor', 'background color', '#C4C3D0') + if mobile: + option_range('menuSwipeThreshold', 'swipe threshold', '10', '90', '25') + option_color('menuBackgroundColor', 'background color', '#000') + option_toggle('menuShowNav', 'show bottom navigation') + option_toggle('menuPageNum', 'show page number') + else: + option_toggle('menuEasyNav', 'enable side navigation') + option_color('menuBackgroundColor', 'background color', '#C4C3D0') + option_range('menuPreloadAmount', 'preload amount (0-10)', '0', '10', '5') + option_click('menuAdvanced', 'advanced settings') option_click('menuReset', 'reset settings') option_click('menuAbout', 'about/help') @@ -276,6 +433,8 @@ def get_page_html(self, result, img_path): for result_blk, z_index in zip(result['blocks'], z_idxs): box_style = self.get_box_style(result_blk, z_index, result['img_width'], result['img_height']) with tag('div', klass='textBox', style=box_style): + with tag('div', ('onclick', f'updateLast("{"".join(result_blk["lines"])}", "{quote(str(img_path.as_posix()))}")'), klass='connect-button', ): + pass for line in result_blk['lines']: with tag('p'): text(line) diff --git a/mokuro/run.py b/mokuro/run.py index 644f7e5..f907a3d 100644 --- a/mokuro/run.py +++ b/mokuro/run.py @@ -12,7 +12,8 @@ def run(*paths, pretrained_model_name_or_path='kha-white/manga-ocr-base', force_cpu=False, as_one_file=True, - disable_confirmation=False, + disable_confirmation=True, + mobile=True ): paths = [Path(p).expanduser().absolute() for p in paths] @@ -22,7 +23,8 @@ def run(*paths, paths.append(p) if len(paths) == 0: - logger.error('Found no paths to process. Did you set the paths correctly?') + logger.error( + 'Found no paths to process. Did you set the paths correctly?') return paths = natsorted(paths) @@ -32,17 +34,19 @@ def run(*paths, print(p) if not disable_confirmation: - inp = input('\nEach of the paths above will be treated as one volume. Continue? [yes/no]\n') + inp = input( + '\nEach of the paths above will be treated as one volume. Continue? [yes/no]\n') if inp.lower() not in ('y', 'yes'): return - ovg = OverlayGenerator(pretrained_model_name_or_path=pretrained_model_name_or_path, force_cpu=force_cpu) + ovg = OverlayGenerator( + pretrained_model_name_or_path=pretrained_model_name_or_path, force_cpu=force_cpu) num_sucessful = 0 for i, path in enumerate(paths): logger.info(f'Processing {i + 1}/{len(paths)}: {path}') try: - ovg.process_dir(path, as_one_file=as_one_file) + ovg.process_dir(path, as_one_file=as_one_file, mobile=mobile) except Exception: logger.exception(f'Error while processing {path}') else: diff --git a/mokuro/script.js b/mokuro/script.js index 03767e2..aeaea25 100644 --- a/mokuro/script.js +++ b/mokuro/script.js @@ -1,558 +1,463 @@ -let num_pages = -1; -let pc = document.getElementById('pagesContainer'); -let r = document.querySelector(':root'); -let pz; -let showAboutOnStart = false; - -let storageKey = "mokuro_" + window.location.pathname; - -let defaultState = { - page_idx: 0, - page2_idx: -1, - hasCover: false, - r2l: true, - singlePageView: false, - ctrlToPan: false, - textBoxBorders: false, - editableText: false, - displayOCR: true, - fontSize: "auto", - eInkMode: false, - defaultZoomMode: "fit to screen", - toggleOCRTextBoxes: false, - backgroundColor: '#C4C3D0', +const defaultState = { + page_idx: 0, + page2_idx: -1, + hasCover: false, + r2l: true, + singlePageView: false, + ctrlToPan: false, + textBoxBorders: false, + editableText: false, + displayOCR: true, + fontSize: 'auto', + eInkMode: false, + defaultZoomMode: 'fit to screen', + toggleOCRTextBoxes: false, + backgroundColor: '#C4C3D0', + menuPreloadAmount: 5, + connectEnabled: false, + editSentence: true, + cropImage: true, + easyNav: true, + overwriteImage: true, + sentenceField: 'Sentence', + pictureField: 'Picture', }; let state = JSON.parse(JSON.stringify(defaultState)); -function saveState() { - localStorage.setItem(storageKey, JSON.stringify(state)); -} - -function loadState() { - let newState = localStorage.getItem(storageKey) - - if (newState !== null) { - state = JSON.parse(newState); - } - - updateUI(); - updateProperties(); -} - function updateUI() { - document.getElementById("menuR2l").checked = state.r2l; - document.getElementById("menuCtrlToPan").checked = state.ctrlToPan; - document.getElementById("menuDoublePageView").checked = !state.singlePageView; - document.getElementById("menuHasCover").checked = state.hasCover; - document.getElementById("menuTextBoxBorders").checked = state.textBoxBorders; - document.getElementById("menuEditableText").checked = state.editableText; - document.getElementById("menuDisplayOCR").checked = state.displayOCR; - document.getElementById('menuFontSize').value = state.fontSize; - document.getElementById('menuEInkMode').checked = state.eInkMode; - document.getElementById('menuDefaultZoom').value = state.defaultZoomMode; - document.getElementById('menuToggleOCRTextBoxes').checked = state.toggleOCRTextBoxes; - document.getElementById('menuBackgroundColor').value = state.backgroundColor; + document.getElementById('menuR2l').checked = state.r2l; + document.getElementById('menuCtrlToPan').checked = state.ctrlToPan; + document.getElementById('menuDoublePageView').checked = !state.singlePageView; + document.getElementById('menuHasCover').checked = state.hasCover; + document.getElementById('menuTextBoxBorders').checked = state.textBoxBorders; + document.getElementById('menuEditableText').checked = state.editableText; + document.getElementById('menuDisplayOCR').checked = state.displayOCR; + document.getElementById('menuFontSize').value = state.fontSize; + document.getElementById('menuFontBold').checked = state.fontBold; + document.getElementById('menuEInkMode').checked = state.eInkMode; + document.getElementById('menuDefaultZoom').value = state.defaultZoomMode; + document.getElementById('menuToggleOCRTextBoxes').checked = + state.toggleOCRTextBoxes; + document.getElementById('menuEasyNav').checked = state.easyNav; + document.getElementById('menuBackgroundColor').value = state.backgroundColor; + document.getElementById('menuPreloadAmount').value = state.preloadAmount; } -document.addEventListener('DOMContentLoaded', function () { +document.addEventListener( + 'DOMContentLoaded', + function () { loadState(); - num_pages = document.getElementsByClassName("page").length; + num_pages = document.getElementsByClassName('page').length; + generateConnectButtons(); pz = panzoom(pc, { - bounds: true, - boundsPadding: 0.05, - maxZoom: 10, - minZoom: 0.1, - zoomDoubleClickSpeed: 1, - enableTextSelection: true, - - beforeMouseDown: function (e) { - let shouldIgnore = disablePanzoomOnElement(e.target) || - (e.target.closest('.textBox') !== null) || - (state.ctrlToPan && !e.ctrlKey); - return shouldIgnore; - }, - - beforeWheel: function (e) { - let shouldIgnore = disablePanzoomOnElement(e.target); - return shouldIgnore; - }, - - onTouch: function (e) { - if (disablePanzoomOnElement(e.target)) { - e.stopPropagation(); - return false; - } - - if (e.touches.length > 1) { - return true; - } else { - return false; - } + bounds: true, + boundsPadding: 0.05, + maxZoom: 10, + minZoom: 0.1, + zoomDoubleClickSpeed: 1, + enableTextSelection: true, + + beforeMouseDown: function (e) { + let shouldIgnore = + disablePanzoomOnElement(e.target) || + e.target.closest('.textBox') !== null || + (state.ctrlToPan && !e.ctrlKey); + return shouldIgnore; + }, + + beforeWheel: function (e) { + let shouldIgnore = disablePanzoomOnElement(e.target); + return shouldIgnore; + }, + + onTouch: function (e) { + if (disablePanzoomOnElement(e.target)) { + e.stopPropagation(); + return false; } + if (e.touches.length > 1) { + return true; + } else { + return false; + } + }, }); updatePage(state.page_idx); initTextBoxes(); if (showAboutOnStart) { - document.getElementById('popupAbout').style.display = 'block'; - document.getElementById('dimOverlay').style.display = 'initial'; - pz.pause(); + document.getElementById('popupAbout').style.display = 'block'; + document.getElementById('dimOverlay').style.display = 'initial'; + pz.pause(); } - -}, false); + }, + false +); function disablePanzoomOnElement(element) { - return document.getElementById('topMenu').contains(element); -} - -function initTextBoxes() { -// Add event listeners for toggling ocr text boxes with the toggleOCRTextBoxes option. - let textBoxes = document.querySelectorAll('.textBox'); - for (let i = 0; i < textBoxes.length; i++) { - textBoxes[i].addEventListener('click', function (e) { - if (state.toggleOCRTextBoxes) { - this.classList.add('hovered'); - // Remove hovered state from all other .textBoxes - for (let j = 0; j < textBoxes.length; j++) { - if (i !== j) { - textBoxes[j].classList.remove('hovered'); - } - } - } - }); - } -// When clicking off of a .textBox, remove the hovered state. - document.addEventListener('click', function (e) { - if (state.toggleOCRTextBoxes) { - if (e.target.closest('.textBox') === null) { - let textBoxes = document.querySelectorAll('.textBox'); - for (let i = 0; i < textBoxes.length; i++) { - textBoxes[i].classList.remove('hovered'); - } - } - } - }); + return document.getElementById('topMenu').contains(element); } function updateProperties() { - if (state.textBoxBorders) { - r.style.setProperty('--textBoxBorderHoverColor', 'rgba(237, 28, 36, 0.3)'); - } else { - r.style.setProperty('--textBoxBorderHoverColor', 'rgba(0, 0, 0, 0)'); - } - - pc.contentEditable = state.editableText; - - if (state.displayOCR) { - r.style.setProperty('--textBoxDisplay', 'initial'); - } else { - r.style.setProperty('--textBoxDisplay', 'none'); - } - - - if (state.fontSize === 'auto') { - pc.classList.remove('textBoxFontSizeOverride'); - } else { - r.style.setProperty('--textBoxFontSize', state.fontSize + 'pt'); - pc.classList.add('textBoxFontSizeOverride'); - } - - if (state.eInkMode) { - document.getElementById('topMenu').classList.add("notransition"); - } else { - document.getElementById('topMenu').classList.remove("notransition"); - } - - if (state.backgroundColor) { - r.style.setProperty('--colorBackground', state.backgroundColor) - } + if (state.textBoxBorders) { + r.style.setProperty('--textBoxBorderHoverColor', 'rgba(237, 28, 36, 0.3)'); + } else { + r.style.setProperty('--textBoxBorderHoverColor', 'rgba(0, 0, 0, 0)'); + } + + pc.contentEditable = state.editableText; + + if (state.displayOCR) { + r.style.setProperty('--textBoxDisplay', 'initial'); + } else { + r.style.setProperty('--textBoxDisplay', 'none'); + } + + if (state.fontSize === 'auto') { + pc.classList.remove('textBoxFontSizeOverride'); + } else { + r.style.setProperty('--textBoxFontSize', state.fontSize + 'pt'); + pc.classList.add('textBoxFontSizeOverride'); + } + + if (state.fontBold) { + r.style.setProperty('--textBoxFontWeight', 'bold'); + } else { + r.style.setProperty('--textBoxFontWeight', 'normal'); + } + + if (state.eInkMode) { + document.getElementById('topMenu').classList.add('notransition'); + } else { + document.getElementById('topMenu').classList.remove('notransition'); + } + + if (state.backgroundColor) { + r.style.setProperty('--colorBackground', state.backgroundColor); + } + + if (state.easyNav) { + r.style.setProperty('--navBtnDisplay', 'block'); + } else { + r.style.setProperty('--navBtnDisplay', 'none'); + } + + if (state.connectEnabled) { + r.style.setProperty('--connectButtonDisplay', 'block'); + } else { + r.style.setProperty('--connectButtonDisplay', 'none'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'none'); + } + + if (state.editSentence && state.connectEnabled) { + r.style.setProperty('--sentenceInputDisplay', 'block'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'block'); + } else { + r.style.setProperty('--sentenceInputDisplay', 'none'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'none'); + } } -document.getElementById('menuR2l').addEventListener('click', function () { - state.r2l = document.getElementById("menuR2l").checked; +document.getElementById('menuCtrlToPan').addEventListener( + 'click', + function () { + state.ctrlToPan = document.getElementById('menuCtrlToPan').checked; saveState(); - updatePage(state.page_idx); -}, false); - -document.getElementById('menuCtrlToPan').addEventListener('click', function () { - state.ctrlToPan = document.getElementById("menuCtrlToPan").checked; - saveState(); -}, false); - -document.getElementById('menuDoublePageView').addEventListener('click', function () { - state.singlePageView = !document.getElementById("menuDoublePageView").checked; + }, + false +); + +document.getElementById('menuDoublePageView').addEventListener( + 'click', + function () { + state.singlePageView = + !document.getElementById('menuDoublePageView').checked; saveState(); updatePage(state.page_idx); -}, false); - -document.getElementById('menuHasCover').addEventListener('click', function () { - state.hasCover = document.getElementById("menuHasCover").checked; - saveState(); - updatePage(state.page_idx); -}, false); - -document.getElementById('menuTextBoxBorders').addEventListener('click', function () { - state.textBoxBorders = document.getElementById("menuTextBoxBorders").checked; + }, + false +); + +document.getElementById('menuEasyNav').addEventListener( + 'click', + function () { + state.easyNav = document.getElementById('menuEasyNav').checked; saveState(); updateProperties(); -}, false); - -document.getElementById('menuEditableText').addEventListener('click', function () { - state.editableText = document.getElementById("menuEditableText").checked; - saveState(); - updateProperties(); -}, false); - -document.getElementById('menuDisplayOCR').addEventListener('click', function () { - state.displayOCR = document.getElementById("menuDisplayOCR").checked; - saveState(); - updateProperties(); -}, false); - -document.getElementById('menuEInkMode').addEventListener('click', function () { - state.eInkMode = document.getElementById("menuEInkMode").checked; - saveState(); - updateProperties(); - if (state.eInkMode) { - eInkRefresh(); - } -}, false); - -document.getElementById('menuToggleOCRTextBoxes').addEventListener('click', function () { - state.toggleOCRTextBoxes = document.getElementById("menuToggleOCRTextBoxes").checked; - saveState(); - updateProperties(); -}, false); - -document.getElementById('menuBackgroundColor').addEventListener( - 'input', - function (event) { - state.backgroundColor = event.target.value; - saveState(); - updateProperties(); - }, - false - ); - -document.getElementById('menuOriginalSize').addEventListener('click', zoomOriginal, false); -document.getElementById('menuFitToWidth').addEventListener('click', zoomFitToWidth, false); -document.getElementById('menuFitToScreen').addEventListener('click', zoomFitToScreen, false); -document.getElementById('menuFullScreen').addEventListener('click', toggleFullScreen, false); - -document.getElementById('menuAbout').addEventListener('click', function () { - document.getElementById('popupAbout').style.display = 'block'; - document.getElementById('dimOverlay').style.display = 'initial'; - pz.pause(); -}, false); - -document.getElementById('menuReset').addEventListener('click', function () { - let page_idx = state.page_idx; - state = JSON.parse(JSON.stringify(defaultState)); - updateUI(); - updatePage(page_idx); - updateProperties(); -}, false); - -document.getElementById('dimOverlay').addEventListener('click', function () { - document.getElementById('popupAbout').style.display = 'none'; - document.getElementById('dimOverlay').style.display = 'none'; - pz.resume(); -}, false); - -document.getElementById('menuFontSize').addEventListener('change', (e) => { - state.fontSize = e.target.value; - saveState(); - updateProperties(); -}); + }, + false +); + +document + .getElementById('menuOriginalSize') + .addEventListener('click', zoomOriginal, false); +document + .getElementById('menuFitToWidth') + .addEventListener('click', zoomFitToWidth, false); +document + .getElementById('menuFitToScreen') + .addEventListener('click', zoomFitToScreen, false); +document + .getElementById('menuFullScreen') + .addEventListener('click', toggleFullScreen, false); document.getElementById('menuDefaultZoom').addEventListener('change', (e) => { - state.defaultZoomMode = e.target.value; - saveState(); + state.defaultZoomMode = e.target.value; + saveState(); }); - -document.getElementById('pageIdxInput').addEventListener('change', (e) => { - updatePage(e.target.value - 1); -}) - -document.getElementById('buttonHideMenu').addEventListener('click', function () { - // document.getElementById('topMenu').style.display = "none"; - document.getElementById('showMenuA').style.display = "inline-block"; - document.getElementById('topMenu').classList.add("hidden"); -}, false); - -document.getElementById('showMenuA').addEventListener('click', function () { - // document.getElementById('topMenu').style.display = "initial"; - document.getElementById('showMenuA').style.display = "none"; - document.getElementById('topMenu').classList.remove("hidden"); -}, false); - -document.getElementById('buttonLeftLeft').addEventListener('click', inputLeftLeft, false); -document.getElementById('buttonLeft').addEventListener('click', inputLeft, false); -document.getElementById('buttonRight').addEventListener('click', inputRight, false); -document.getElementById('buttonRightRight').addEventListener('click', inputRightRight, false); -document.getElementById('leftAPage').addEventListener('click', inputLeft, false); -document.getElementById('leftAScreen').addEventListener('click', inputLeft, false); -document.getElementById('rightAPage').addEventListener('click', inputRight, false); -document.getElementById('rightAScreen').addEventListener('click', inputRight, false); - -document.addEventListener("keydown", function onEvent(e) { - switch (e.key) { - case "PageUp": - prevPage(); - break; - - case "PageDown": - nextPage(); - break; - - case "Home": - firstPage(); - break; - - case "End": - lastPage(); - break; - - case " ": - nextPage(); - break; - - case "0": - zoomDefault(); - break; - } -}); - -function isPageFirstOfPair(page_idx) { - if (state.singlePageView) { - return true; - } else { - if (state.hasCover) { - return (page_idx === 0 || (page_idx % 2 === 1)); - } else { - return page_idx % 2 === 0; - } - } -} - -function getPage(page_idx) { - return document.getElementById("page" + page_idx); -} +document + .getElementById('leftAPage') + .addEventListener('click', inputLeft, false); +document + .getElementById('leftAScreen') + .addEventListener('click', inputLeft, false); +document + .getElementById('rightAPage') + .addEventListener('click', inputRight, false); +document + .getElementById('rightAScreen') + .addEventListener('click', inputRight, false); function getOffsetLeft() { - return 0; + return 0; } function getOffsetTop() { - let offset = 0; - let menu = document.getElementById('topMenu'); - if (!menu.classList.contains("hidden")) { - offset += menu.getBoundingClientRect().bottom + 10; - } - return offset; + let offset = 0; + let menu = document.getElementById('topMenu'); + if (!menu.classList.contains('hidden')) { + offset += menu.getBoundingClientRect().bottom + 10; + } + return offset; } function getOffsetRight() { - return 0; + return 0; } function getOffsetBottom() { - return 0; + return 0; } function getScreenWidth() { - return window.innerWidth - getOffsetLeft() - getOffsetRight(); + return window.innerWidth - getOffsetLeft() - getOffsetRight(); } function getScreenHeight() { - return window.innerHeight - getOffsetTop() - getOffsetBottom(); + return window.innerHeight - getOffsetTop() - getOffsetBottom(); } function panAlign(align_x, align_y) { - let scale = pz.getTransform().scale; - let x; - let y; - - switch (align_x) { - case "left": - x = getOffsetLeft(); - break; - case "center": - x = getOffsetLeft() + (getScreenWidth() - pc.offsetWidth * scale) / 2; - break; - case "right": - x = getOffsetLeft() + (getScreenWidth() - pc.offsetWidth * scale); - break; - } - - switch (align_y) { - case "top": - y = getOffsetTop(); - break; - case "center": - y = getOffsetTop() + (getScreenHeight() - pc.offsetHeight * scale) / 2; - break; - case "bottom": - y = getOffsetTop() + (getScreenHeight() - pc.offsetHeight * scale); - break; - } - - pz.moveTo(x, y); + let scale = pz.getTransform().scale; + let x; + let y; + + switch (align_x) { + case 'left': + x = getOffsetLeft(); + break; + case 'center': + x = getOffsetLeft() + (getScreenWidth() - pc.offsetWidth * scale) / 2; + break; + case 'right': + x = getOffsetLeft() + (getScreenWidth() - pc.offsetWidth * scale); + break; + } + + switch (align_y) { + case 'top': + y = getOffsetTop(); + break; + case 'center': + y = getOffsetTop() + (getScreenHeight() - pc.offsetHeight * scale) / 2; + break; + case 'bottom': + y = getOffsetTop() + (getScreenHeight() - pc.offsetHeight * scale); + break; + } + + pz.moveTo(x, y); } - function zoomOriginal() { - pz.moveTo(0, 0); - pz.zoomTo(0, 0, 1 / pz.getTransform().scale); - panAlign("center", "center"); + pz.moveTo(0, 0); + pz.zoomTo(0, 0, 1 / pz.getTransform().scale); + panAlign('center', 'center'); } function zoomFitToWidth() { - let scale = (1 / pz.getTransform().scale) * (getScreenWidth() / pc.offsetWidth); - pz.moveTo(0, 0); - pz.zoomTo(0, 0, scale); - panAlign("center", "top"); + let scale = + (1 / pz.getTransform().scale) * (getScreenWidth() / pc.offsetWidth); + pz.moveTo(0, 0); + pz.zoomTo(0, 0, scale); + panAlign('center', 'top'); } function zoomFitToScreen() { - let scale_x = getScreenWidth() / pc.offsetWidth; - let scale_y = getScreenHeight() / pc.offsetHeight; - let scale = (1 / pz.getTransform().scale) * Math.min(scale_x, scale_y); - pz.moveTo(0, 0); - pz.zoomTo(0, 0, scale); - panAlign("center", "center"); + let scale_x = getScreenWidth() / pc.offsetWidth; + let scale_y = getScreenHeight() / pc.offsetHeight; + let scale = (1 / pz.getTransform().scale) * Math.min(scale_x, scale_y); + pz.moveTo(0, 0); + pz.zoomTo(0, 0, scale); + panAlign('center', 'center'); +} + +function keepZoomStart() { + panAlign('center', 'top'); } function zoomDefault() { - switch (state.defaultZoomMode) { - case "fit to screen": - zoomFitToScreen(); - break; - case "fit to width": - zoomFitToWidth(); - break; - case "original size": - zoomOriginal(); - break; - } + switch (state.defaultZoomMode) { + case 'fit to screen': + zoomFitToScreen(); + break; + case 'fit to width': + zoomFitToWidth(); + break; + case 'original size': + zoomOriginal(); + break; + case 'keep zoom, go to top': + keepZoomStart(); + break; + } } function updatePage(new_page_idx) { - new_page_idx = Math.min(Math.max(new_page_idx, 0), num_pages - 1); - - getPage(state.page_idx).style.display = "none"; + new_page_idx = Math.min(Math.max(new_page_idx, 0), num_pages - 1); - if (state.page2_idx >= 0) { - getPage(state.page2_idx).style.display = "none"; - } + getPage(state.page_idx).style.display = 'none'; - if (isPageFirstOfPair(new_page_idx)) { - state.page_idx = new_page_idx; - } else { - state.page_idx = new_page_idx - 1; - } + if (state.page2_idx >= 0) { + getPage(state.page2_idx).style.display = 'none'; + } - getPage(state.page_idx).style.display = "inline-block"; - getPage(state.page_idx).style.order = 2; + if (isPageFirstOfPair(new_page_idx)) { + state.page_idx = new_page_idx; + } else { + state.page_idx = new_page_idx - 1; + } - if (!state.singlePageView && state.page_idx < num_pages - 1 && !isPageFirstOfPair(state.page_idx + 1)) { - state.page2_idx = state.page_idx + 1; - getPage(state.page2_idx).style.display = "inline-block"; + getPage(state.page_idx).style.display = 'inline-block'; + getPage(state.page_idx).style.order = 2; - if (state.r2l) { - getPage(state.page2_idx).style.order = 1; - } else { - getPage(state.page2_idx).style.order = 3; - } + if ( + !state.singlePageView && + state.page_idx < num_pages - 1 && + !isPageFirstOfPair(state.page_idx + 1) + ) { + state.page2_idx = state.page_idx + 1; + getPage(state.page2_idx).style.display = 'inline-block'; + if (state.r2l) { + getPage(state.page2_idx).style.order = 1; } else { - state.page2_idx = -1; + getPage(state.page2_idx).style.order = 3; } + } else { + state.page2_idx = -1; + } + + document.getElementById('pageIdxInput').value = state.page_idx + 1; + + page2_txt = state.page2_idx >= 0 ? ',' + (state.page2_idx + 1) : ''; + document.getElementById('pageIdxDisplay').innerHTML = + state.page_idx + 1 + page2_txt + '/' + num_pages; + + saveState(); + zoomDefault(); + preloadImage(); + if (state.eInkMode) { + eInkRefresh(); + } +} - document.getElementById("pageIdxInput").value = state.page_idx + 1; +function toggleFullScreen() { + var doc = window.document; + var docEl = doc.documentElement; + + var requestFullScreen = + docEl.requestFullscreen || + docEl.mozRequestFullScreen || + docEl.webkitRequestFullScreen || + docEl.msRequestFullscreen; + var cancelFullScreen = + doc.exitFullscreen || + doc.mozCancelFullScreen || + doc.webkitExitFullscreen || + doc.msExitFullscreen; + + if ( + !doc.fullscreenElement && + !doc.mozFullScreenElement && + !doc.webkitFullscreenElement && + !doc.msFullscreenElement + ) { + requestFullScreen.call(docEl); + } else { + cancelFullScreen.call(doc); + } +} - page2_txt = (state.page2_idx >= 0) ? ',' + (state.page2_idx + 1) : ""; - document.getElementById("pageIdxDisplay").innerHTML = (state.page_idx + 1) + page2_txt + '/' + num_pages; +document.getElementById('snackbar').addEventListener('click', async () => { + const { id } = await getLastCard(); + await ankiConnect('guiBrowse', 6, { query: `nid:${id}` }); +}); - saveState(); - zoomDefault(); - if (state.eInkMode) { - eInkRefresh(); - } -} +let start; +let end; -function firstPage() { - updatePage(0); -} +const leftNav = document.getElementById('left-nav'); +const rightNav = document.getElementById('right-nav'); -function lastPage() { - updatePage(num_pages - 1); -} +leftNav.addEventListener('mousedown', () => { + start = new Date(); +}); -function prevPage() { - updatePage(state.page_idx - (state.singlePageView ? 1 : 2)); -} +rightNav.addEventListener('mousedown', () => { + start = new Date(); +}); -function nextPage() { - updatePage(state.page_idx + (state.singlePageView ? 1 : 2)); -} +leftNav.addEventListener('mouseup', () => { + end = new Date(); + const clickDuration = end - start; -function inputLeftLeft() { - if (state.r2l) { - lastPage(); - } else { - firstPage(); - } -} + if (clickDuration < 200) { + inputLeft(); + } +}); -function inputLeft() { - if (state.r2l) { - nextPage(); - } else { - prevPage(); - } -} +rightNav.addEventListener('mouseup', () => { + end = new Date(); + const clickDuration = end - start; -function inputRight() { - if (state.r2l) { - prevPage(); - } else { - nextPage(); - } -} + if (clickDuration < 200) { + inputRight(); + } +}); -function inputRightRight() { - if (state.r2l) { - firstPage(); - } else { - lastPage(); - } -} +function generateConnectButtons() { + for (let i = 0; i < num_pages; i++) { + const connectBtn = document.getElementById(`connect-${i}`); -function toggleFullScreen() { - var doc = window.document; - var docEl = doc.documentElement; + connectBtn.addEventListener('mousedown', () => { + start = new Date(); + }); - var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen; - var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen; + connectBtn.addEventListener('mouseup', () => { + end = new Date(); + const clickDuration = end - start; - if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) { - requestFullScreen.call(docEl); - } else { - cancelFullScreen.call(doc); - } + if (clickDuration < 200) { + const page = getPage(i); + const img = getBackgroundImage(page); + updateLast('', img); + } + }); + } } - -function eInkRefresh() { - pc.classList.add("inverted"); - document.body.style.backgroundColor = "black"; - setTimeout(function () { - pc.classList.remove("inverted"); - document.body.style.backgroundColor = r.style.getPropertyValue("--colorBackground"); - }, 300); -} \ No newline at end of file diff --git a/mokuro/script.mobile.js b/mokuro/script.mobile.js new file mode 100644 index 0000000..6579df9 --- /dev/null +++ b/mokuro/script.mobile.js @@ -0,0 +1,348 @@ +const defaultState = { + page_idx: 0, + page2_idx: -1, + hasCover: false, + r2l: false, + singlePageView: true, + textBoxBorders: false, + editableText: false, + displayOCR: true, + fontSize: 'auto', + eInkMode: false, + toggleOCRTextBoxes: false, + swipeThreshold: 35, + backgroundColor: '#000', + menuPreloadAmount: 5, + showNav: true, + showPageNum: true, + connectEnabled: false, + editSentence: true, + cropImage: true, + overwriteImage: true, + sentenceField: 'Sentence', + pictureField: 'Picture', +}; + +let state = JSON.parse(JSON.stringify(defaultState)); + +function fitToScreen() { + const viewportmeta = document.querySelector('meta[name=viewport]'); + const page = getPage(state.page_idx); + const pageContainer = page.querySelector('div'); + const screenWidth = window.innerWidth; + const screenHeight = window.innerHeight; + const elementWidth = pageContainer.style.width.replace('px', ''); + const elementHeight = pageContainer.style.height.replace('px', ''); + + const scaleX = screenWidth / elementWidth; + const scaleY = screenHeight / elementHeight; + const scale = Math.min(scaleX, scaleY); + + const translateX = (screenWidth - elementWidth * scale) / 2; + const translateY = (screenHeight - elementHeight * scale) / 2; + + viewportmeta.setAttribute( + 'content', + 'initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' + ); + + pc.style.transformOrigin = `top left`; + pc.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; + + viewportmeta.setAttribute( + 'content', + 'initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0' + ); +} + +function updateUI() { + document.getElementById('menuR2l').checked = state.r2l; + document.getElementById('menuHasCover').checked = state.hasCover; + document.getElementById('menuTextBoxBorders').checked = state.textBoxBorders; + document.getElementById('menuEditableText').checked = state.editableText; + document.getElementById('menuDisplayOCR').checked = state.displayOCR; + document.getElementById('menuFontSize').value = state.fontSize; + document.getElementById('menuFontBold').checked = state.fontBold; + document.getElementById('menuEInkMode').checked = state.eInkMode; + document.getElementById('menuToggleOCRTextBoxes').checked = + state.toggleOCRTextBoxes; + document.getElementById('menuSwipeThreshold').value = state.swipeThreshold; + document.getElementById('menuPreloadAmount').value = state.preloadAmount; + document.getElementById('menuBackgroundColor').value = state.backgroundColor; + document.getElementById('menuShowNav').checked = state.showNav; + document.getElementById('menuPageNum').checked = state.showPageNum; +} + +window.addEventListener('resize', () => { + fitToScreen(); +}); + +document.addEventListener( + 'DOMContentLoaded', + function () { + loadState(); + fitToScreen(); + num_pages = document.getElementsByClassName('page').length; + + updatePage(state.page_idx); + initTextBoxes(); + + if (showAboutOnStart) { + document.getElementById('popupAbout').style.display = 'block'; + document.getElementById('dimOverlay').style.display = 'initial'; + } + }, + false +); + +function updateProperties() { + if (state.textBoxBorders) { + r.style.setProperty('--textBoxBorderHoverColor', 'rgba(237, 28, 36, 0.3)'); + } else { + r.style.setProperty('--textBoxBorderHoverColor', 'rgba(0, 0, 0, 0)'); + } + + pc.contentEditable = state.editableText; + + if (state.displayOCR) { + r.style.setProperty('--textBoxDisplay', 'initial'); + } else { + r.style.setProperty('--textBoxDisplay', 'none'); + } + + if (state.fontSize === 'auto') { + pc.classList.remove('textBoxFontSizeOverride'); + } else { + r.style.setProperty('--textBoxFontSize', state.fontSize + 'pt'); + pc.classList.add('textBoxFontSizeOverride'); + } + + if (state.fontBold) { + r.style.setProperty('--textBoxFontWeight', 'bold'); + } else { + r.style.setProperty('--textBoxFontWeight', 'normal'); + } + + if (state.eInkMode) { + document.getElementById('topMenu').classList.add('notransition'); + } else { + document.getElementById('topMenu').classList.remove('notransition'); + } + + if (state.backgroundColor) { + r.style.setProperty('--colorBackground', state.backgroundColor); + } + + if (state.showNav) { + r.style.setProperty('--navDisplay', 'initial'); + } else { + r.style.setProperty('--navDisplay', 'none'); + } + + if (state.showPageNum) { + r.style.setProperty('--pageNumOpacity', '1'); + } else { + r.style.setProperty('--pageNumOpacity', '0'); + } + + if (state.connectEnabled) { + r.style.setProperty('--connectButtonDisplay', 'block'); + } else { + r.style.setProperty('--connectButtonDisplay', 'none'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'none'); + } + + if (state.editSentence && state.connectEnabled) { + r.style.setProperty('--sentenceInputDisplay', 'block'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'block'); + } else { + r.style.setProperty('--sentenceInputDisplay', 'none'); + r.style.setProperty('--sentenceConnectButtonDisplay', 'none'); + } +} + +document.getElementById('menuSwipeThreshold').addEventListener( + 'input', + function (event) { + state.swipeThreshold = event.target.value; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuShowNav').addEventListener( + 'click', + function () { + state.showNav = document.getElementById('menuShowNav').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('menuPageNum').addEventListener( + 'click', + function () { + state.showPageNum = document.getElementById('menuPageNum').checked; + saveState(); + updateProperties(); + }, + false +); + +document.getElementById('forward').addEventListener('click', inputRight, false); +document.getElementById('back').addEventListener('click', inputLeft, false); + +function updatePage(new_page_idx) { + new_page_idx = Math.min(Math.max(new_page_idx, 0), num_pages - 1); + + getPage(state.page_idx).style.display = 'none'; + + if (state.page2_idx >= 0) { + getPage(state.page2_idx).style.display = 'none'; + } + + if (isPageFirstOfPair(new_page_idx)) { + state.page_idx = new_page_idx; + } else { + state.page_idx = new_page_idx - 1; + } + + getPage(state.page_idx).style.display = 'inline-block'; + getPage(state.page_idx).style.order = 2; + + if ( + !state.singlePageView && + state.page_idx < num_pages - 1 && + !isPageFirstOfPair(state.page_idx + 1) + ) { + state.page2_idx = state.page_idx + 1; + getPage(state.page2_idx).style.display = 'inline-block'; + + if (state.r2l) { + getPage(state.page2_idx).style.order = 1; + } else { + getPage(state.page2_idx).style.order = 3; + } + } else { + state.page2_idx = -1; + } + + document.getElementById('pageIdxInput').value = state.page_idx + 1; + + page2_txt = state.page2_idx >= 0 ? ',' + (state.page2_idx + 1) : ''; + document.getElementById('pageIdxDisplay').innerHTML = + state.page_idx + 1 + page2_txt + '/' + num_pages; + + document.getElementById('page-num').innerHTML = + state.page_idx + 1 + '/' + num_pages; + + saveState(); + if (state.eInkMode) { + eInkRefresh(); + } + + fitToScreen(); + preloadImage(); +} + +const screenWidth = + window.innerWidth || + document.documentElement.clientWidth || + document.body.clientWidth; +const screenHeight = + window.innerHeight || + document.documentElement.clientHeight || + document.body.clientHeight; + +document.addEventListener('touchstart', handleTouchStart); +document.addEventListener('touchend', handleTouchEnd); +document.addEventListener('touchmove', handleTouchMove); +document.addEventListener('touchcancel', handleTouchCancel); + +let startX; +let startY; +const ongoingTouches = []; +let distanceX; +let distanceY; + +function removeTouch(event) { + const touches = event.changedTouches; + + for (let i = 0; i < touches.length; i++) { + const touch = touches[i]; + const touchIndex = ongoingTouches.indexOf(touch.identifier); + + if (touchIndex >= 0) { + ongoingTouches.splice(touchIndex, 1); + } + } +} + +function handleTouchStart(event) { + const touches = event.changedTouches; + + distanceY = 0; + distanceX = 0; + startX = event.touches[0].clientX; + startY = event.touches[0].clientY; + + ongoingTouches.push(touches[0].identifier); +} + +function handleTouchMove(event) { + const touches = event.changedTouches; + + if (ongoingTouches.length === 1) { + distanceX = Math.floor(touches[0].clientX - startX); + distanceY = Math.floor(touches[0].clientY - startY); + } else { + distanceX = 0; + distanceY = 0; + } +} + +function handleNavigation() { + const swipeThreshold = Math.abs((state.swipeThreshold / 100) * screenWidth); + const isSwipe = distanceY < 100 && distanceY > 200 * -1; + + if (ongoingTouches.length === 1 && isSwipe) { + if (distanceX > swipeThreshold) { + inputLeft(); + } else if (distanceX < swipeThreshold * -1) { + inputRight(); + } + } +} + +let timeout; +let running = false; +function handleTouchEnd(event) { + if (!running) { + running = true; + handleNavigation(); + removeTouch(event); + distanceX = 0; + distanceY = 0; + timeout = setTimeout(() => { + running = false; + }, 100); + } else { + removeTouch(event); + distanceX = 0; + distanceY = 0; + } +} + +function handleTouchCancel(event) { + removeTouch(event); +} + +document.getElementById('page-num').addEventListener('click', () => { + if (state.connectEnabled) { + const page = getPage(state.page_idx); + const img = getBackgroundImage(page); + updateLast('', img); + } +}); diff --git a/mokuro/styles.css b/mokuro/styles.css index ac470dd..3934eee 100644 --- a/mokuro/styles.css +++ b/mokuro/styles.css @@ -1,301 +1,99 @@ :root { - --textBoxDisplay: initial; - --textBoxBorderHoverColor: rgba(237, 28, 36, 0.5); - --textBoxFontSize: 1em; - - --colorBackground: #C4C3D0; - --color1: #f9f9fb; - --color2: #e2e2e9; - --color3: #071013; - --color3a: rgba(7, 16, 19, 0.3); - -} - -body { - overflow: hidden; - margin: 0; - background-color: var(--colorBackground); -} - -.notransition { - transition: none !important; -} - -#topMenu *, .popup * { - font-family: "Open Sans", sans-serif; -} - -.pageContainer * { - font-family: "Noto Sans JP", "Meiryo", "MS Gothic", sans-serif; -} - -.pageContainer { - position: relative; - margin: 0 auto; -} - -.pageContainer:hover .textBox { - border: 2px solid var(--textBoxBorderHoverColor); -} - -.textBox { - display: var(--textBoxDisplay); - position: absolute; - padding: 0; - line-height: 1.1em; - font-size: 16pt; - white-space: nowrap; - border: 1px solid rgba(0, 0, 0, 0); -} - -.textBox:hover { - background: rgb(255, 255, 255); - border: 1px solid rgba(0, 0, 0, 0); - z-index: 999 !important; -} - -.textBox p { - display: none; - white-space: nowrap; - letter-spacing: 0.1em; - line-height: 1.1em; - margin: 0; - background-color: rgb(255, 255, 255); -} - -.textBoxFontSizeOverride .textBox p { - font-size: var(--textBoxFontSize) !important; -} - -.textBox:hover p { - display: table; -} - -.hovered { - background: rgb(255, 255, 255); - border: 1px solid rgba(0, 0, 0, 0); - z-index: 999 !important; -} - -.hovered p { - display: table; + --colorBackground: #c4c3d0; + --connectButtonDisplay: none; + --sentenceConnectButtonDisplay: none; + --navBtnDisplay: block; + /* common */ + --textBoxDisplay: initial; + --textBoxBorderHoverColor: rgba(237, 28, 36, 0.5); + --textBoxFontSize: 1em; + --textBoxFontWeight: normal; + --color1: #f9f9fb; + --color2: #e2e2e9; + --color3: #071013; + --color3a: rgba(7, 16, 19, 0.3); } #pagesContainer { - display: inline-flex; - flex-direction: row; - overflow: visible; -} - -#pagesContainer.inverted { - -webkit-filter: invert(100%); - filter: invert(100%); + display: inline-flex; + flex-direction: row; + overflow: visible; } .page { - display: none; - float: left; - margin: 0 -1px 0 0; + display: none; + float: left; + margin: 0 -1px 0 0; } -#leftAPage, #rightAPage, #leftAScreen, #rightAScreen { - z-index: 1; - background-color: rgba(0, 0, 0, 0); +#leftAPage, +#rightAPage, +#leftAScreen, +#rightAScreen { + z-index: 1; + background-color: rgba(0, 0, 0, 0); } -#leftAPage, #rightAPage { - /*display: inline-block;*/ - display: none; - position: absolute; - top: -5%; - width: 10%; - height: 110%; +#leftAPage, +#rightAPage { + /*display: inline-block;*/ + display: none; + position: absolute; + top: -5%; + width: 10%; + height: 110%; } #leftAPage { - left: -7%; + left: -7%; } #rightAPage { - right: -7%; + right: -7%; } -#leftAScreen, #rightAScreen { - display: inline-block; - position: fixed; - top: 10vh; - width: 5vw; - height: 90vh; +#leftAScreen, +#rightAScreen { + display: inline-block; + position: fixed; + top: 10vh; + width: 5vw; + height: 90vh; } #leftAScreen { - left: 0; + left: 0; } #rightAScreen { - right: 0; -} - -#showMenuA { - display: none; - position: fixed; - left: 0; - top: 0; - width: 3em; - height: 3em; - z-index: 1000; - background-color: rgba(0, 0, 0, 0); -} - -#topMenu { - position: fixed; - z-index: 1000; - top: 0; - left: 0; - margin: 5px; - background: var(--color1); - border-radius: 3px; - box-shadow: 0px 0px 8px 0px var(--color3a); - transition: all 0.5s ease-out, max-width 0s ease-in; - visibility: visible; - opacity: 1; - max-width: 100vw; - white-space: nowrap; -} - - -#topMenu.hidden { - max-width: 2.5em; - visibility: hidden; - opacity: 0; - overflow: hidden; - transition: all 1s ease-in, max-width 0.5s ease-out; -} - -#topMenu * { - font-size: 1em; - vertical-align: middle; -} - -#topMenu input { - height: 1.4em; - margin: 0 6px; -} - -#pageIdxDisplay { - margin-left: 0.5em; - margin-right: 0.5em; -} - -.menuButton { - background-color: rgba(0, 0, 0, 0); - color: var(--color3); - border: none; - width: 2.5em; - height: 2.2em; - border-radius: 3px; -} - -.menuButton svg { - max-height: 1.5em; -} - -.menuButton:hover { - background-color: var(--color2); -} - -.dropdown:hover #dropbtn { - background-color: var(--color2); + right: 0; } -.dropdown { - position: relative; - display: inline-block; +.nav-btn { + display: var(--navBtnDisplay) !important; + position: absolute; + background-color: transparent; + border: none; + cursor: pointer; + height: 100%; + width: 500px; } -.dropdown-content { - display: none; - position: absolute; - right: 0; - background-color: var(--color1); - box-shadow: 0px 0px 8px 0px var(--color3a); - z-index: 1000; - border-radius: 3px; - max-height: 90vh; - overflow: hidden; - overflow-y: auto; +#left-nav { + margin-left: -500px; + left: 0; } -.dropdown-content .buttonRow { - display: flex; -} - -.dropdown-content .menuButton { - flex: 1 1 auto; - align-self: center; -} - - -.dropdown-content .dropdown-option { - color: var(--color3); - padding: 10px 10px; - text-decoration: none; - white-space: nowrap; - display: block; - border-radius: 3px; -} - -.dropdown-content .dropdown-option:hover { - background-color: var(--color2); -} - -.dropdown-content .dropdown-option [type="checkbox"] { - vertical-align: middle; -} - -.dropdown:hover .dropdown-content { - display: block; -} - -#dimOverlay { - display: none; - position: fixed; - width: 100%; - height: 100%; - top: 0; - left: 0; - z-index: 1001; - background-color: rgba(0, 0, 0, 0.5); -} - -.popup { - display: none; - position: fixed; - top: 50vh; - left: 50vw; - width: min(720px, 80vw); - height: min(480px, 80vh); - margin-left: max(-360px, -40vw); - margin-top: max(-240px, -40vh); - z-index: 1002; - background-color: var(--color1); - color: var(--color3); - box-shadow: 0px 0px 10px 4px var(--color3a); - border-radius: 3px; - overflow: auto; - padding: 20px; - box-sizing: border-box; - line-height: 1.5em; -} - - -/*hide arrows from number input*/ -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; +#right-nav { + margin-right: -500px; + right: 0; } -input[type=number] { - -moz-appearance: textfield; +.connect { + display: var(--connectButtonDisplay); + height: 200px; + width: 100%; + background-color: transparent; + cursor: pointer; + border: none; } diff --git a/mokuro/styles.mobile.css b/mokuro/styles.mobile.css new file mode 100644 index 0000000..24e7e61 --- /dev/null +++ b/mokuro/styles.mobile.css @@ -0,0 +1,62 @@ +:root { + --navDisplay: initial; + --pageNumOpacity: 1; + --colorBackground: #000; + --color4: rgba(255, 255, 255, 0.342); + --connectButtonDisplay: none; + --sentenceConnectButtonDisplay: none; + /* common */ + --textBoxDisplay: initial; + --textBoxBorderHoverColor: rgba(237, 28, 36, 0.5); + --textBoxFontSize: 1em; + --textBoxFontWeight: normal; + --color1: #f9f9fb; + --color2: #e2e2e9; + --color3: #071013; + --color3a: rgba(7, 16, 19, 0.3); +} + +#pagesContainer { + flex-direction: row; +} + +.page { + display: none; + margin: 0 -1px 0 0; +} + +.btn { + bottom: 5px; + position: fixed; + background-color: var(--colorBackground); + border: none; + color: var(--color4); + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 32px; + width: 150px; +} + +#forward { + display: var(--navDisplay); + right: 5px; +} + +#back { + display: var(--navDisplay); + left: 5px; +} + +#page-num { + opacity: var(--pageNumOpacity); + left: 50%; + bottom: 18px; + position: fixed; + color: var(--color4); + font-size: x-large; + font-family: 'Noto Sans JP', 'Meiryo', 'MS Gothic', sans-serif; + transform: translate(-50%, 0); + z-index: 10; +}