Commit f0535dd0a919de44e6900039da1cfe29882caa5a
Merge pull request #195 from ssbc/image_search
mvp image searchmix irving authored on 5/27/2018, 12:07:08 PM
GitHub committed on 5/27/2018, 12:07:08 PM
Parent: 439c5c1934e4781abb15490399e17e183f4e07db
Parent: 4d1674d8270affecae790df4de91734c0399dc25
Files changed
app/html/modal.mcss | changed |
app/page/imageSearch.js | added |
app/page/imageSearch.mcss | added |
background-process.js | changed |
package-lock.json | changed |
package.json | changed |
router/sync/routes.js | changed |
app/html/modal.mcss | ||
---|---|---|
@@ -7,15 +7,19 @@ | ||
7 | 7 … | height: 100% |
8 | 8 … | text-align: center |
9 | 9 … | top: 0 |
10 | 10 … | left: 0 |
11 | - background: rgba(0,0,0,0.8) | |
11 … | + background: rgba(0,0,0,0.1) | |
12 | 12 … | |
13 | 13 … | div.content { |
14 … | + max-width: 90vw | |
15 … | + max-height: 90vh | |
16 … | + overflow: auto | |
17 … | + | |
18 … | + $backgroundPrimary | |
19 … | + padding: 30px | |
20 … | + box-shadow: rgba(0,0,0,.34) 2px 6px 10px | |
14 | 21 … | margin: auto |
15 | - padding: 30px | |
16 | - border-radius: 5px | |
17 | - $backgroundPrimary | |
18 | 22 … | $dontSelect |
19 | 23 … | |
20 | 24 … | div.dialog { |
21 | 25 … | div.message { |
app/page/imageSearch.js | ||
---|---|---|
@@ -1,0 +1,135 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const { h, Dict, Value, watch, throttle, computed, map, onceTrue } = require('mutant') | |
3 … | + | |
4 … | +exports.gives = nest({ | |
5 … | + 'app.page.imageSearch': true, | |
6 … | + 'app.html.menuItem': true | |
7 … | +}) | |
8 … | + | |
9 … | +exports.needs = nest({ | |
10 … | + 'sbot.obs.connection': 'first', | |
11 … | + 'blob.sync.url': 'first', | |
12 … | + 'app.html.modal': 'first', | |
13 … | + 'app.sync.goTo': 'first', | |
14 … | + 'about.obs.name': 'first', | |
15 … | + 'backlinks.obs.for': 'first' | |
16 … | +}) | |
17 … | + | |
18 … | +exports.create = function (api) { | |
19 … | + return nest({ | |
20 … | + 'app.html.menuItem': menuItem, | |
21 … | + 'app.page.imageSearch': searchPage | |
22 … | + }) | |
23 … | + | |
24 … | + function menuItem () { | |
25 … | + return h('a', { | |
26 … | + style: { order: 0 }, | |
27 … | + 'ev-click': () => api.app.sync.goTo('/imageSearch') | |
28 … | + }, '/imageSearch') | |
29 … | + } | |
30 … | + | |
31 … | + function searchPage (location) { | |
32 … | + const query = Value('') | |
33 … | + const results = Dict({}) | |
34 … | + | |
35 … | + watch(throttle(query, 300), q => { | |
36 … | + if (q && q.length < 3) return | |
37 … | + onceTrue(api.sbot.obs.connection, sbot => { | |
38 … | + sbot.meme.search(q, (err, data) => { | |
39 … | + if (err) return console.error(err) | |
40 … | + results.set(data) | |
41 … | + }) | |
42 … | + }) | |
43 … | + }) | |
44 … | + | |
45 … | + const focusedBlob = Value() | |
46 … | + const modal = Modal({ | |
47 … | + results, | |
48 … | + focusedBlob, | |
49 … | + blobUrl: api.blob.sync.url, | |
50 … | + name: api.about.obs.name, | |
51 … | + goTo: api.app.sync.goTo, | |
52 … | + createModal: api.app.html.modal, | |
53 … | + backlinks: api.backlinks.obs.for | |
54 … | + }) | |
55 … | + | |
56 … | + const page = h('Page -imageSearch', [ | |
57 … | + modal, | |
58 … | + h('section.settings', [ | |
59 … | + h('input', { | |
60 … | + 'placeholder': 'search image by name', | |
61 … | + 'ev-input': ev => query.set(ev.target.value.toLowerCase()) | |
62 … | + }) | |
63 … | + ]), | |
64 … | + h('section.results', computed([results, query], (results, query) => { | |
65 … | + if (!Object.keys(results).length && query.length >= 3) return h('p', '0 results') | |
66 … | + | |
67 … | + return Object.keys(results).map(blob => { | |
68 … | + return h('div', { 'ev-click': () => focusedBlob.set(blob) }, [ | |
69 … | + h('img', { src: api.blob.sync.url(blob) }) | |
70 … | + ]) | |
71 … | + }) | |
72 … | + })) | |
73 … | + ]) | |
74 … | + | |
75 … | + page.title = '/imageSearch' | |
76 … | + | |
77 … | + return page | |
78 … | + } | |
79 … | +} | |
80 … | + | |
81 … | +function Modal ({ results, focusedBlob, blobUrl, name, goTo, createModal, backlinks }) { | |
82 … | + const onClick = (link) => () => { | |
83 … | + isOpen.set(false) | |
84 … | + goTo(link) | |
85 … | + } | |
86 … | + | |
87 … | + const isOpen = Value(false) | |
88 … | + focusedBlob(blob => { | |
89 … | + if (blob) isOpen.set(true) | |
90 … | + }) | |
91 … | + const modalContent = computed([focusedBlob], (blob) => { | |
92 … | + if (!blob) return | |
93 … | + | |
94 … | + const entries = computed(backlinks(blob), msgs => { | |
95 … | + return msgs.reduce((soFar, msg) => { | |
96 … | + const entries = getMentions(msg) | |
97 … | + .filter(mention => mention.link === blob) | |
98 … | + // .filter(mention => mention.name) | |
99 … | + .map(mention => { return { name: mention.name, author: msg.value.author, msg: msg.key, ts: msg.value.timestamp } }) | |
100 … | + return [...soFar, ...entries] | |
101 … | + }, []) | |
102 … | + }) | |
103 … | + | |
104 … | + const imageName = Value('CHOOSE YOUR OWN NAME') | |
105 … | + | |
106 … | + return [ | |
107 … | + h('img', { src: blobUrl(blob) }), | |
108 … | + h('div.md', [ | |
109 … | + 'Copy markdown: ', | |
110 … | + h('pre', ['![', imageName, '](', blob, ')']) | |
111 … | + ]), | |
112 … | + h('table', map(entries, entry => { | |
113 … | + return h('tr', { 'ev-mouseover': () => entry.name && imageName.set(entry.name) }, [ | |
114 … | + h('td', entry.name), | |
115 … | + h('td.msg', h('a', { href: '#', 'ev-click': onClick(entry.msg) }, entry.msg.substring(0, 10) + '...')), | |
116 … | + h('td', h('a', { href: '#', 'ev-click': onClick(entry.author) }, ['@', name(entry.author)])), | |
117 … | + h('td', JSON.stringify(new Date(entry.ts)).substring(1, 11)) // I am a bad human D: | |
118 … | + ]) | |
119 … | + })), | |
120 … | + h('button', { | |
121 … | + 'ev-click': () => { | |
122 … | + focusedBlob.set() | |
123 … | + isOpen.set(false) | |
124 … | + } | |
125 … | + }, 'Close') | |
126 … | + ] | |
127 … | + }) | |
128 … | + return createModal(modalContent, { isOpen }) | |
129 … | +} | |
130 … | + | |
131 … | +function getMentions (msg) { | |
132 … | + if (!msg.value.content.mentions) return [] | |
133 … | + else if (!Array.isArray(msg.value.content.mentions)) return [msg.value.content.mentions] | |
134 … | + else return msg.value.content.mentions | |
135 … | +} |
app/page/imageSearch.mcss | ||
---|---|---|
@@ -1,0 +1,84 @@ | ||
1 … | +Page -imageSearch { | |
2 … | + overflow-y: auto | |
3 … | + | |
4 … | + display: grid | |
5 … | + grid-template-columns: 85vw | |
6 … | + grid-template-rows: 5rem auto | |
7 … | + justify-content: center | |
8 … | + align-items: start | |
9 … | + | |
10 … | + div.Modal { | |
11 … | + div.content { | |
12 … | + display: grid | |
13 … | + justify-items: center | |
14 … | + | |
15 … | + img { | |
16 … | + max-width: 90vw | |
17 … | + max-height: 80vh | |
18 … | + | |
19 … | + margin-bottom: 1rem | |
20 … | + } | |
21 … | + | |
22 … | + div.md { | |
23 … | + pre { | |
24 … | + user-select: all | |
25 … | + padding: .2rem | |
26 … | + padding-right: 2rem | |
27 … | + min-width: 39rem | |
28 … | + background-color: rgba(0,0,0,.1) | |
29 … | + | |
30 … | + text-align: right | |
31 … | + margin-top: .2rem | |
32 … | + display: inline-block | |
33 … | + } | |
34 … | + } | |
35 … | + | |
36 … | + table { | |
37 … | + tr { | |
38 … | + td { | |
39 … | + text-align: left | |
40 … | + padding-right: .5rem | |
41 … | + } | |
42 … | + | |
43 … | + td.msg { | |
44 … | + font-family: monospace | |
45 … | + } | |
46 … | + } | |
47 … | + } | |
48 … | + } | |
49 … | + } | |
50 … | + | |
51 … | + section.settings { | |
52 … | + padding: 1rem 0 | |
53 … | + input { | |
54 … | + min-width: 20rem | |
55 … | + padding: .5rem | |
56 … | + } | |
57 … | + } | |
58 … | + | |
59 … | + section.results { | |
60 … | + display: flex | |
61 … | + align-items: center | |
62 … | + flex-wrap: wrap | |
63 … | + | |
64 … | + div { | |
65 … | + cursor: pointer | |
66 … | + | |
67 … | + min-width: 5rem | |
68 … | + min-height: 5rem | |
69 … | + max-width: 19rem | |
70 … | + max-height: 19rem | |
71 … | + | |
72 … | + display: flex | |
73 … | + justify-content: center | |
74 … | + align-items: center | |
75 … | + | |
76 … | + img { | |
77 … | + max-width: 19rem | |
78 … | + max-height: 19rem | |
79 … | + } | |
80 … | + | |
81 … | + margin: 0 1rem 1rem 0 | |
82 … | + } | |
83 … | + } | |
84 … | +} |
background-process.js | ||
---|---|---|
@@ -16,8 +16,9 @@ | ||
16 | 16 … | .use(require('ssb-friends')) |
17 | 17 … | .use(require('ssb-private')) |
18 | 18 … | .use(require('ssb-blobs')) |
19 | 19 … | .use(require('ssb-search')) |
20 … | + .use(require('ssb-meme')) | |
20 | 21 … | .use(require('ssb-ebt')) |
21 | 22 … | .use(require('ssb-chess-db')) |
22 | 23 … | .use(require('ssb-query')) |
23 | 24 … | .use(require('ssb-ws')) |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 305890 bytes New file size: 317388 bytes |
package.json | ||
---|---|---|
@@ -85,8 +85,9 @@ | ||
85 | 85 … | "ssb-ebt": "^5.2.0", |
86 | 86 … | "ssb-friends": "^2.2.3", |
87 | 87 … | "ssb-horcrux": "^1.0.0", |
88 | 88 … | "ssb-keys": "^7.0.15", |
89 … | + "ssb-meme": "^1.0.0", | |
89 | 90 … | "ssb-mentions": "^0.4.1", |
90 | 91 … | "ssb-mutual": "^0.1.0", |
91 | 92 … | "ssb-private": "^0.1.2", |
92 | 93 … | "ssb-query": "^1.0.2", |
router/sync/routes.js | ||
---|---|---|
@@ -10,8 +10,9 @@ | ||
10 | 10 … | 'private': 'first', |
11 | 11 … | 'notifications': 'first', |
12 | 12 … | 'profile': 'first', |
13 | 13 … | 'search': 'first', |
14 … | + 'imageSearch': 'first', | |
14 | 15 … | 'blob': 'first', |
15 | 16 … | 'thread': 'first', |
16 | 17 … | 'channel': 'first', |
17 | 18 … | 'settings': 'first' |
@@ -32,9 +33,10 @@ | ||
32 | 33 … | [ loc => loc.page === 'notifications', pages.notifications ], |
33 | 34 … | [ loc => loc.page === 'errors', pages.errors ], |
34 | 35 … | [ loc => loc.page === 'profile', () => pages.profile({ feed: myId }) ], |
35 | 36 … | [ loc => loc.page === 'search' && loc.query, pages.search ], |
36 | - [ loc => loc.page === 'settings', pages.settings], | |
37 … | + [ loc => loc.page === 'imageSearch', pages.imageSearch ], | |
38 … | + [ loc => loc.page === 'settings', pages.settings ], | |
37 | 39 … | |
38 | 40 … | [ loc => isBlob(loc.blob), pages.blob ], |
39 | 41 … | [ loc => isPresent(loc.channel), pages.channel ], |
40 | 42 … | [ loc => isFeed(loc.feed), pages.profile ], |
Built with git-ssb-web