├── .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 | 
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 |
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: `[](${entryURL})`,
120 | html: `
`,
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 |
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 |
--------------------------------------------------------------------------------