├── .gitignore ├── README.md ├── gulpfile.coffee ├── horesase.icns ├── index.html ├── main.js ├── package.json ├── screenshot.png ├── src ├── images │ ├── clippy.svg │ └── splash │ │ ├── 1.gif │ │ ├── 2.gif │ │ ├── 3.gif │ │ └── 4.gif ├── javascripts │ └── app.js └── stylesheets │ └── app.scss └── webpack.config.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .module-cache 3 | *.map 4 | /build 5 | /node_modules 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Horesase Desktop 2 | 3 | All horesase meigens on your desktop. 4 | 5 | ![screenshot](https://raw.githubusercontent.com/june29/horesase-desktop/master/screenshot.png) 6 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | gulp = require "gulp" 2 | webpack = require "webpack-stream" 3 | $ = do require "gulp-load-plugins" 4 | 5 | gulp.task "webpack", -> 6 | gulp.src "./src/" 7 | .pipe webpack require "./webpack.config.coffee" 8 | .pipe gulp.dest "./build/" 9 | 10 | gulp.task "default", -> 11 | $.watch "./src/**/*.(js|scss)", -> 12 | gulp.start "webpack" 13 | -------------------------------------------------------------------------------- /horesase.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/horesase.icns -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Horesase Desktop 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var App = require("app"); 4 | var BrowserWindow = require("browser-window"); 5 | var CrashReporter = require("crash-reporter"); 6 | 7 | CrashReporter.start(); 8 | 9 | App.on("window-all-closed", function() { 10 | if (process.platform != "darwin") { 11 | App.quit(); 12 | } 13 | }); 14 | 15 | var mainWindow = null; 16 | 17 | App.on("ready", function() { 18 | mainWindow = new BrowserWindow({ width: 1000, height: 800, resizable: false }); 19 | mainWindow.loadURL("file://" + __dirname + "/index.html"); 20 | 21 | if (process.env.DEBUG) { 22 | mainWindow.webContents.openDevTools(); 23 | } 24 | 25 | mainWindow.on("closed", function() { 26 | mainWindow = null; 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "horesase-desktop", 3 | "version": "0.0.1", 4 | "description": "All horesase meigens on your desktop", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/horesase/horesase-desktop.git" 12 | }, 13 | "keywords": [ 14 | "horesase" 15 | ], 16 | "author": "june29", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/horesase/horesase-desktop/issues" 20 | }, 21 | "homepage": "https://github.com/horesase/horesase-desktop", 22 | "devDependencies": { 23 | "gulp": "^3.9.0", 24 | "gulp-coffee": "^2.3.1", 25 | "gulp-load-plugins": "^1.1.0", 26 | "gulp-watch": "^4.3.5", 27 | "webpack": "^1.12.6", 28 | "webpack-stream": "^2.1.1", 29 | "babel-core": "^6.1.21", 30 | "babel-loader": "^6.1.0", 31 | "babel-preset-es2015": "^6.1.18", 32 | "babel-preset-react": "^6.1.18", 33 | "node-sass": "^3.4.2", 34 | "css-loader": "^0.23.0", 35 | "sass-loader": "^3.1.1", 36 | "style-loader": "^0.13.0", 37 | "image-webpack-loader": "^1.6.2" 38 | }, 39 | "dependencies": { 40 | "react": "^0.14.3", 41 | "react-dom": "^0.14.3", 42 | "lodash": "^3.10.1", 43 | "react-copy-to-clipboard": "^3.0.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/screenshot.png -------------------------------------------------------------------------------- /src/images/clippy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/splash/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/src/images/splash/1.gif -------------------------------------------------------------------------------- /src/images/splash/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/src/images/splash/2.gif -------------------------------------------------------------------------------- /src/images/splash/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/src/images/splash/3.gif -------------------------------------------------------------------------------- /src/images/splash/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horesase/horesase-desktop/ffa20342f50045bda28ef2524f950d9d8a7021a9/src/images/splash/4.gif -------------------------------------------------------------------------------- /src/javascripts/app.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | 3 | var React = require("react"); 4 | var ReactDOM = require("react-dom"); 5 | var CopyToClipboard = require("react-copy-to-clipboard"); 6 | var HTTP = require("http"); 7 | var _ = require("lodash"); 8 | 9 | // Stylesheets 10 | 11 | require("../stylesheets/app.scss"); 12 | 13 | // Images 14 | 15 | var imgClippyPath = "build/" + require("../images/clippy.svg"); 16 | var splashNumber = parseInt((Math.floor(Math.random() * 3) + 1)); 17 | var imgSplashPath = "build/" + require("../images/splash/" + splashNumber + ".gif") 18 | 19 | // Components 20 | 21 | var MeigenList = React.createClass({ 22 | propTypes: { 23 | meigens: React.PropTypes.array.isRequired, 24 | hasMoreMeigens: React.PropTypes.bool.isRequired, 25 | loadMoreMeigens: React.PropTypes.func.isRequired, 26 | selectMeigen: React.PropTypes.func.isRequired 27 | }, 28 | 29 | onClick() { 30 | this.props.loadMoreMeigens(); 31 | }, 32 | 33 | render() { 34 | var list = this.props.meigens.map((m) => { 35 | return 36 | }); 37 | 38 | return( 39 |
40 | 43 | {this.props.hasMoreMeigens ?
Load more meigens
: null} 44 |
45 | ); 46 | } 47 | }); 48 | 49 | var Meigen = React.createClass({ 50 | propTypes: { 51 | id: React.PropTypes.number.isRequired, 52 | title: React.PropTypes.string.isRequired, 53 | image: React.PropTypes.string.isRequired, 54 | character: React.PropTypes.string.isRequired, 55 | cid: React.PropTypes.number.isRequired, 56 | eid: React.PropTypes.number.isRequired, 57 | body: React.PropTypes.string.isRequired, 58 | selectMeigen: React.PropTypes.func.isRequired 59 | }, 60 | 61 | onClick() { 62 | this.props.selectMeigen(this.props.id); 63 | }, 64 | 65 | render() { 66 | return ( 67 |
  • 68 | ); 69 | } 70 | }); 71 | 72 | var MeigenPopup = React.createClass({ 73 | getInitialState() { 74 | return { 75 | copied_markdown: false, 76 | copied_html: false, 77 | copied_url: false 78 | } 79 | }, 80 | 81 | propTypes: { 82 | id: React.PropTypes.number.isRequired, 83 | title: React.PropTypes.string.isRequired, 84 | image: React.PropTypes.string.isRequired, 85 | character: React.PropTypes.string.isRequired, 86 | cid: React.PropTypes.number.isRequired, 87 | eid: React.PropTypes.number.isRequired, 88 | unselectMeigen: React.PropTypes.func.isRequired 89 | }, 90 | 91 | onClick(event) { 92 | if (event.target.id == "popup-container") { 93 | this.props.unselectMeigen(); 94 | } 95 | }, 96 | 97 | onFocus(event) { 98 | event.target.select(); 99 | }, 100 | 101 | onCopy(from) { 102 | if (from == "markdown") { 103 | this.setState({ copied_markdown: true }); 104 | setTimeout(() => { this.setState({ copied_markdown: false }); }, 1000); 105 | } else if (from == "html") { 106 | this.setState({ copied_html: true }); 107 | setTimeout(() => { this.setState({ copied_html: false }); }, 1000); 108 | } else if (from == "url") { 109 | this.setState({ copied_url: true }); 110 | setTimeout(() => { this.setState({ copied_url: false }); }, 1000); 111 | } 112 | }, 113 | 114 | render() { 115 | var entryTitle = `惚れさせ${this.props.id} 「${this.props.title}」`; 116 | var entryURL = `http://jigokuno.com/eid_${this.props.eid}.html`; 117 | 118 | var paster = { 119 | markdown: `[![${entryTitle}](${this.props.image})](${entryURL})`, 120 | html: `${entryTitle}`, 121 | url: this.props.image 122 | } 123 | 124 | return( 125 | 159 | ); 160 | } 161 | }); 162 | 163 | var CharacterList = React.createClass({ 164 | propTypes: { 165 | characters: React.PropTypes.array.isRequired, 166 | currentCharacterID: React.PropTypes.number.isRequired, 167 | selectCharacter: React.PropTypes.func.isRequired 168 | }, 169 | 170 | render() { 171 | var list = this.props.characters.map((c) => { 172 | if (c.id == this.props.currentCharacterID) { 173 | return 174 | } else { 175 | return 176 | } 177 | }); 178 | 179 | if (this.props.currentCharacterID == 0) { 180 | list.unshift(); 181 | } else { 182 | list.unshift(); 183 | } 184 | 185 | return( 186 |
      187 | {list} 188 |
    189 | ); 190 | } 191 | }); 192 | 193 | var Character = React.createClass({ 194 | propTypes: { 195 | id: React.PropTypes.number.isRequired, 196 | name: React.PropTypes.string.isRequired, 197 | selected: React.PropTypes.bool.isRequired, 198 | selectCharacter: React.PropTypes.func.isRequired 199 | }, 200 | 201 | onClick() { 202 | this.props.selectCharacter(this.props.id); 203 | }, 204 | 205 | render() { 206 | if (this.props.selected) { 207 | return
  • {this.props.name}
  • 208 | } else { 209 | return
  • {this.props.name}
  • 210 | } 211 | } 212 | }); 213 | 214 | var Searcher = React.createClass({ 215 | propTypes: { 216 | updateQuery: React.PropTypes.func.isRequired 217 | }, 218 | 219 | onChange(event) { 220 | this.props.updateQuery(event.target.value); 221 | }, 222 | 223 | render() { 224 | return( 225 | 226 | ); 227 | } 228 | }); 229 | 230 | var Horesase = React.createClass({ 231 | propTypes: { 232 | initialDisplayCount: React.PropTypes.number.isRequired, 233 | }, 234 | 235 | getInitialState() { 236 | return { 237 | meigens: null, 238 | displayCount: this.props.initialDisplayCount, 239 | query: "", 240 | currentCharacterID: 0, 241 | selectedMeigenID: null 242 | } 243 | }, 244 | 245 | selectMeigen(meigenID) { 246 | this.setState({ selectedMeigenID: meigenID }); 247 | }, 248 | 249 | unselectMeigen() { 250 | this.setState({ selectedMeigenID: null }); 251 | }, 252 | 253 | loadMoreMeigens() { 254 | this.setState({ displayCount: this.state.displayCount + this.props.initialDisplayCount * 2 }); 255 | }, 256 | 257 | selectCharacter(characterID) { 258 | this.setState({ currentCharacterID: characterID, displayCount: this.props.initialDisplayCount }); 259 | }, 260 | 261 | updateQuery(newQuery) { 262 | this.setState({ query: newQuery, displayCount: this.props.initialDisplayCount }); 263 | }, 264 | 265 | render() { 266 | if (this.state.meigens == null) { 267 | HTTP.request({ host: "horesase.github.io", path: "/horesase-boys/meigens.json" }, (response) => { 268 | var result = ""; 269 | response.on("data", (chunk) => { result += chunk; }); 270 | response.on("end", () => { 271 | setTimeout(() => { 272 | this.setState({ meigens: JSON.parse(result) }) 273 | }, 3000); 274 | }); 275 | }).end(); 276 | 277 | return ( 278 |
    279 |

    280 | 281 |

    282 |

    283 | Loading... 284 |

    285 |
    286 | ) 287 | } 288 | 289 | var characters = _.uniq(this.state.meigens.map((m) => { 290 | return { id: m.cid, name: m.character } 291 | }), "id"); 292 | 293 | var popup = null; 294 | 295 | if (this.state.selectedMeigenID) { 296 | var meigen = this.state.meigens.filter((m) => { return m.id == this.state.selectedMeigenID })[0]; 297 | popup = 298 | } 299 | 300 | var filtered = this.state.meigens; 301 | 302 | if (this.state.currentCharacterID != 0) { 303 | filtered = this.state.meigens.filter((m) => { return m.cid == this.state.currentCharacterID }); 304 | } 305 | 306 | if (this.state.query.length > 0) { 307 | filtered = filtered.filter((m) => { 308 | var re = new RegExp(this.state.query); 309 | return m.title.match(re) || m.body.match(re) || m.character.match(re); 310 | }); 311 | } 312 | 313 | var hasMoreMeigens = filtered.length > this.state.displayCount; 314 | 315 | filtered = _.take(_.sortBy(filtered, (m) => { return m.id * -1 }), this.state.displayCount); 316 | 317 | return( 318 |
    319 |
    320 | 321 |
    322 |
    323 | 324 | 325 |
    326 | {popup} 327 |
    328 | ); 329 | } 330 | }); 331 | 332 | ReactDOM.render( 333 | , 334 | document.getElementById("app") 335 | ); 336 | -------------------------------------------------------------------------------- /src/stylesheets/app.scss: -------------------------------------------------------------------------------- 1 | @keyframes fade-in { 2 | from { opacity: 0; } 3 | to { opacity: 1; } 4 | } 5 | 6 | @keyframes fade-out { 7 | from { opacity: 1; } 8 | to { opacity: 0; } 9 | } 10 | 11 | * { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | body { 17 | box-sizing: border-box; 18 | font-size: 14px; 19 | font-family: "Hiragino Kaku Gothic ProN", Meiryo, fantasy; 20 | } 21 | 22 | #app { 23 | width: 1002px; 24 | margin: 0 auto; 25 | } 26 | 27 | #splash { 28 | text-align: center; 29 | 30 | .image { 31 | img { 32 | max-width: 100%; 33 | } 34 | } 35 | 36 | .message { 37 | font-size: 30px; 38 | } 39 | } 40 | 41 | #splash.splash-1, #splash.splash-4 { 42 | .image { 43 | height: 700px; 44 | 45 | img { 46 | max-height: 670px; 47 | } 48 | } 49 | } 50 | 51 | #splash.splash-2, #splash.splash-3 { 52 | .image { 53 | height: 670px; 54 | padding-top: 30px; 55 | 56 | img { 57 | max-height: 700px; 58 | } 59 | } 60 | } 61 | 62 | #horesase { 63 | animation: fade-in 2s; 64 | } 65 | 66 | #searcher-container { 67 | padding: 20px; 68 | background-color: #e8e6e8; 69 | border-bottom: 1px solid #c2c0c2; 70 | 71 | input[type="search"] { 72 | box-sizing: border-box; 73 | width: 100%; 74 | font-size: 200%; 75 | } 76 | } 77 | 78 | #list-container { 79 | position: relative; 80 | 81 | ul { 82 | box-sizing: border-box; 83 | height: 100%; 84 | overflow-y: scroll; 85 | list-style-type: none; 86 | } 87 | 88 | ul#character-list { 89 | position: absolute; 90 | top: 0; 91 | left: 0; 92 | width: 200px; 93 | padding: 10px; 94 | border-right: 1px solid #ddd; 95 | background-color: #f5f5f4; 96 | border-bottom-left-radius: 6px; 97 | 98 | li { 99 | padding: 3px 10px 2px; 100 | font-size: 13px; 101 | color: #333; 102 | cursor: pointer; 103 | 104 | &:hover { 105 | background-color: #e2e2e2; 106 | } 107 | 108 | &.selected { 109 | background-color: #e2e2e2; 110 | font-weight: bold; 111 | } 112 | } 113 | } 114 | 115 | #meigen-list-wrapper { 116 | margin-left: 200px; 117 | padding: 10px; 118 | 119 | ul#meigen-list { 120 | li.meigen { 121 | display: inline-block; 122 | width: 240px; 123 | margin: 10px 10px; 124 | cursor: pointer; 125 | 126 | img { 127 | width: 240px; 128 | } 129 | } 130 | } 131 | 132 | #loader { 133 | margin: 10px; 134 | padding: 5px 0; 135 | border: 1px solid #ccc; 136 | text-align: center; 137 | border-radius: 4px; 138 | cursor: pointer; 139 | } 140 | } 141 | } 142 | 143 | #popup-container { 144 | position: fixed; 145 | top: 0; 146 | right: 0; 147 | bottom: 0; 148 | left: 0; 149 | width: 100%; 150 | height: 100%; 151 | background-color: rgba(0, 0, 0, 0.5); 152 | 153 | #popup { 154 | position: absolute; 155 | top: 0; 156 | right: 0; 157 | bottom: 0; 158 | left: 0; 159 | width: 542px; 160 | height: 362px; 161 | margin: auto; 162 | background-color: #fff; 163 | border-radius: 6px; 164 | 165 | .image { 166 | position: absolute; 167 | top: 20px; 168 | left: 20px; 169 | width: 242px; 170 | height: 322px; 171 | 172 | img { 173 | width: 240px; 174 | border: 1px solid #ccc; 175 | } 176 | } 177 | 178 | .info { 179 | position: absolute; 180 | top: 20px; 181 | right: 20px; 182 | width: 240px; 183 | height: 322px; 184 | 185 | h2 { 186 | font-size: 16px; 187 | } 188 | 189 | .copy-boards { 190 | position: absolute; 191 | right: 0; 192 | bottom: 0; 193 | width: 100%; 194 | 195 | .copy-board { 196 | position: relative; 197 | 198 | h3 { 199 | margin: 0 0 5px; 200 | font-size: 12px; 201 | } 202 | 203 | p { 204 | margin: 0 0 15px; 205 | 206 | input[type="text"] { 207 | box-sizing: border-box; 208 | width: 100%; 209 | line-height: 22px; 210 | padding: 0 5px; 211 | } 212 | } 213 | 214 | .copy-button { 215 | position: absolute; 216 | right: 1px; 217 | bottom: 1px; 218 | width: 14px; 219 | height: 16px; 220 | padding: 4px 8px 4px 10px; 221 | background-color: #eee; 222 | cursor: pointer; 223 | 224 | &:hover { 225 | background-color: #ddd; 226 | } 227 | } 228 | 229 | .status { 230 | position: absolute; 231 | top: 0; 232 | right: 0; 233 | font-size: 12px; 234 | font-weight: bold; 235 | color: green; 236 | animation: fade-out 1s; 237 | animation-fill-mode: forwards; 238 | } 239 | } 240 | 241 | .copy-board:last-child { 242 | p { 243 | margin-bottom: 0; 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /webpack.config.coffee: -------------------------------------------------------------------------------- 1 | path = require("path") 2 | 3 | module.exports = { 4 | entry: { 5 | app: [path.resolve(__dirname, "src/javascripts/app.js")] 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, "build"), 9 | filename: "bundle.js" 10 | }, 11 | target: "atom", 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | loader: "babel", 18 | query: 19 | { 20 | presets:["es2015", "react"] 21 | } 22 | }, 23 | { 24 | test: /\.scss$/, 25 | loader: "style!css!sass" 26 | }, 27 | { 28 | test: /\.(jpe?g|png|gif|svg)$/i, 29 | loaders: [ 30 | "file?hash=sha512&digest=hex&name=[name]-[hash].[ext]", 31 | "image-webpack" 32 | ] 33 | } 34 | ] 35 | }, 36 | } 37 | --------------------------------------------------------------------------------