Skip to content

Commit c20477b

Browse files
committed
track recently used emoji! fixes #3
1 parent 54c64ae commit c20477b

File tree

1 file changed

+110
-34
lines changed

1 file changed

+110
-34
lines changed

src/Qmoji.re

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ let setTimeout = (fn, time) => setTimeout(UnitTracker.track(fn), time);
2020

2121
external toggleMenuItem: (Fluid.App.menuItem, bool) => unit = "qmenu_toggleMenuItem";
2222

23-
let fuzzyEmoji = (text, emoji) => {
23+
let fuzzyEmoji = (text, recentlyUsedMap, emoji) => {
2424
let score = Fuzzy.fuzzyScore(~exactWeight=1000, text, emoji.name);
2525
let best = emoji.keywords->Belt.Array.reduce(score, (score, kwd) => {
2626
Fuzzy.maxScore(score, Fuzzy.fuzzyScore(text, kwd))
2727
});
2828
if (best.full) {
29-
Some((best, emoji))
29+
Some((best, emoji, recentlyUsedMap->Belt.Map.String.get(emoji.name)))
3030
} else {
3131
None
3232
}
@@ -58,8 +58,11 @@ let dimsForIndex = (~padding=0., index) => {
5858
}
5959
};
6060

61-
let drawEmoji = (dims, {top, left, width, height}, emoji, isSelected) =>
61+
let drawEmoji = (dims, {top, left, width, height}, emoji, used, isSelected) =>
6262
if (dims.top +. size >= top && dims.top <= top +. height) {
63+
if (used != None) {
64+
Fluid.Draw.fillRect(dims, {r: 0.6, g: 0.6, b: 0.8, a: 0.2});
65+
};
6366
if (isSelected) {
6467
Fluid.Draw.rect(dims, {r: 0.4, g: 0.4, b: 0.4, a: 0.5});
6568
};
@@ -90,19 +93,32 @@ module Config = {
9093
showAtCursor: bool,
9194
checkForUpdates: bool,
9295
lastCheckedTag: string,
96+
recentlyUsed: Belt.Map.String.t((int, float))
9397
};
94-
let default = {showAtCursor: false, checkForUpdates: true, lastCheckedTag: "-"};
98+
let m = Unix.gettimeofday();
99+
let default = {showAtCursor: false, checkForUpdates: true, lastCheckedTag: "-", recentlyUsed: Belt.Map.String.empty};
95100
let ofJson = json => {
96101
open Json.Infix;
97102
let showAtCursor = json |> Json.get("showAtCursor") |?> Json.bool |? false;
98103
let checkForUpdates = json |> Json.get("checkForUpdates") |?> Json.bool |? true;
99104
let lastCheckedTag = json |> Json.get("lastCheckedTag") |?> Json.string |? "-";
100-
{showAtCursor, checkForUpdates, lastCheckedTag}
105+
let recentlyUsed = json |> Json.get("recentlyUsed") |?> Json.array |?>> Belt.List.keepMap(_, item => {
106+
switch ((item |> Json.get("name") |?> Json.string, item |> Json.get("count") |?> Json.number, item |> Json.get("date") |?> Json.number)) {
107+
| (Some(name), Some(count), Some(date)) => Some((name, (int_of_float(count), date)))
108+
| _ => None
109+
}
110+
}) |?>> Belt.List.toArray |?>> Belt.Map.String.fromArray |? Belt.Map.String.empty;
111+
{showAtCursor, checkForUpdates, lastCheckedTag, recentlyUsed}
101112
};
102-
let toJson = ({showAtCursor, checkForUpdates, lastCheckedTag}) => Json.Object([
113+
let toJson = ({showAtCursor, checkForUpdates, lastCheckedTag, recentlyUsed}) => Json.Object([
103114
("showAtCursor", showAtCursor ? Json.True : Json.False),
104115
("checkForUpdates", checkForUpdates ? Json.True : Json.False),
105-
("lastCheckedTag", Json.String(lastCheckedTag))
116+
("lastCheckedTag", Json.String(lastCheckedTag)),
117+
("recentlyUsed", Json.Array(recentlyUsed->Belt.Map.String.toArray->Belt.List.fromArray->Belt.List.map(((name, (count, date))) => Json.Object([
118+
("name", Json.String(name)),
119+
("count", Json.Number(float_of_int(count))),
120+
("date", Json.Number(date)),
121+
]))))
106122
]);
107123
let load = path => switch (Json.parse(Files.readFileExn(path))) {
108124
| exception _ => default
@@ -121,10 +137,36 @@ module Config = {
121137
current := fn(current^);
122138
save(current^, configPath);
123139
};
140+
141+
let maxRecentlyUsed = 20;
142+
143+
let removeEmojiUse = name => update(({recentlyUsed} as config) => {...config, recentlyUsed: Belt.Map.String.remove(recentlyUsed, name)});
144+
145+
let useEmoji = name => {
146+
update(({recentlyUsed} as config) => {
147+
let recentlyUsed = switch (Belt.Map.String.get(recentlyUsed, name)) {
148+
| Some((count, date)) => Belt.Map.String.set(recentlyUsed, name, (count + 1, Unix.gettimeofday()))
149+
| None =>
150+
if (Belt.Map.String.size(recentlyUsed) < maxRecentlyUsed) {
151+
Belt.Map.String.set(recentlyUsed, name, (1, Unix.gettimeofday()))
152+
} else {
153+
let least = recentlyUsed->Belt.Map.String.reduce(None, (least, key, (count, date)) => switch least {
154+
| None => Some((key, count, date))
155+
| Some((okey, ocount, odate)) when ocount > count || (ocount == count && odate > date) => Some((key, count, date))
156+
| _ => least
157+
});
158+
switch least {
159+
| None => recentlyUsed
160+
| Some((least, _, _)) => Belt.Map.String.remove(recentlyUsed, least)->Belt.Map.String.set(name, (1, Unix.gettimeofday()))
161+
}
162+
}
163+
};
164+
{...config, recentlyUsed}
165+
});
166+
};
124167
};
125168

126169
let checkVersion = (assetsDir, onDone) => {
127-
/* print_endline("Checking version"); */
128170
switch (Files.readFile(assetsDir->Filename.concat("git-head"))) {
129171
| None => onDone(None)
130172
| Some(gitHead) =>
@@ -193,11 +235,31 @@ let startChecking = assetsDir => {
193235
check()
194236
};
195237

238+
let compareUsed = (a, b) => switch (a, b) {
239+
| (Some((acount, adate)), Some((bcount, bdate))) =>
240+
if (acount != bcount) {
241+
bcount - acount
242+
} else {
243+
int_of_float(bdate) - int_of_float(adate)
244+
}
245+
| (Some(_), _) => -1
246+
| (_, Some(_)) => 1
247+
| _ => 0
248+
};
249+
196250
let main = (~assetsDir, ~emojis, ~onDone, hooks) => {
197251
let%hook (text, setText) = useState("");
198252
let%hook (selection, setSelection) = useState(0);
199253
let%hook (hasNewVersion, setHasNewVersion) = useState(hasNewVersion^);
200254

255+
let onSelect = emoji => {
256+
Config.useEmoji(emoji.name);
257+
setSelection(0);
258+
setText("");
259+
onDone(Some(emoji.char))
260+
};
261+
let onCancel = () => onDone(None);
262+
201263
onHasNewVersion := setHasNewVersion;
202264

203265
let%hook rightClickMenu = useMemo(() => {
@@ -215,18 +277,25 @@ let main = (~assetsDir, ~emojis, ~onDone, hooks) => {
215277
);
216278
}, ());
217279

218-
let filtered = text == "" ? emojis : {
219-
emojis->Belt.List.keepMap(fuzzyEmoji(text))->Belt.List.sort(
220-
((ascore, amoji), (bscore, bmoji)) => Fuzzy.compareScores(ascore, bscore)
221-
)->Belt.List.map(snd);
280+
let filtered = text == "" ? emojis->Belt.List.map(em => (em, Config.current^.recentlyUsed->Belt.Map.String.get(em.name)))->Belt.List.sort(((amoji, aused), (bmoji, bused)) => compareUsed(aused, bused)) : {
281+
emojis->Belt.List.keepMap(fuzzyEmoji(text, Config.current^.recentlyUsed))->Belt.List.sort(
282+
((ascore, amoji, aused), (bscore, bmoji, bused)) => {
283+
let uc = compareUsed(aused, bused);
284+
if (uc == 0) {
285+
Fuzzy.compareScores(ascore, bscore)
286+
} else {
287+
uc
288+
}
289+
}
290+
)->Belt.List.map(((_, emoji, used)) => (emoji, used));
222291
};
223292

224293
let%hook prev = useRef(None);
225294

226295
let invalidated = switch (prev.contents) {
227296
| None => `Full
228-
| Some((prevText, prevSelection)) =>
229-
if (prevText != text) {
297+
| Some((prevText, prevSelection, prevUsed)) =>
298+
if (prevText != text || prevUsed !== Config.current^.recentlyUsed) {
230299
`Full
231300
} else if (prevSelection != selection) {
232301
`Partial([
@@ -238,7 +307,7 @@ let main = (~assetsDir, ~emojis, ~onDone, hooks) => {
238307
}
239308
};
240309

241-
prev.contents = Some((text, selection));
310+
prev.contents = Some((text, selection, Config.current^.recentlyUsed));
242311

243312
let rows = ceil(float_of_int(List.length(filtered)) /. rowf)->int_of_float;
244313

@@ -249,33 +318,39 @@ let main = (~assetsDir, ~emojis, ~onDone, hooks) => {
249318
}
250319
}, filtered)
251320

321+
let%hook rightMouseDown = useCallback((pos) => {
322+
let index = indexForPos(pos);
323+
switch (filtered->Belt.List.get(index)) {
324+
| None => ()
325+
| Some((emoji, _)) =>
326+
Config.removeEmojiUse(emoji.name);
327+
setText(text);
328+
}
329+
}, (text, filtered))
330+
252331
let%hook mouseDown = useCallback((pos) => {
253332
let index = indexForPos(pos);
254333
switch (filtered->Belt.List.get(index)) {
255334
| None => ()
256-
| Some({char}) =>
257-
setText("")
258-
setSelection(0);
259-
onDone(Some(char));
335+
| Some((emoji, _)) =>
336+
onSelect(emoji);
260337
}
261338
}, filtered)
262339

263340
let%hook draw = useCallback((bounds) => {
264-
filtered->Belt.List.forEachWithIndex((index, emoji) => drawEmoji(dimsForIndex(index), bounds, emoji, index == selection));
265-
}, (text, selection));
341+
filtered->Belt.List.forEachWithIndex((index, (emoji, used)) => drawEmoji(dimsForIndex(index), bounds, emoji, used, index == selection));
342+
}, (filtered, selection));
266343

267344
let%hook onEnter = useCallback(text => {
268345
switch (Belt.List.get(filtered, selection)) {
269-
| None => onDone(None)
270-
| Some({char}) => onDone(Some(char))
346+
| None => onCancel()
347+
| Some((emoji, _)) => onSelect(emoji)
271348
};
272-
setSelection(0);
273-
setText("");
274349
}, (filtered, selection));
275350

276351
let%hook onEscape = useCallback({() => {
277352
if (text == "") {
278-
onDone(None)
353+
onCancel()
279354
} else {
280355
setSelection(0);
281356
setText("");
@@ -322,20 +397,21 @@ let main = (~assetsDir, ~emojis, ~onDone, hooks) => {
322397
<view layout={
323398
Layout.style(~paddingHorizontal=10., ~alignSelf=AlignStretch, ())
324399
}>
325-
<custom
326-
invalidated
327-
layout={Layout.style(~alignSelf=AlignStretch, ~height=(float_of_int(rows) *. size), ())}
328-
onMouseDown={mouseDown}
329-
onMouseMove={mouseMove}
330-
draw={draw}
331-
/>
400+
<custom
401+
invalidated
402+
layout={Layout.style(~alignSelf=AlignStretch, ~height=(float_of_int(rows) *. size), ())}
403+
onMouseDown={mouseDown}
404+
onMouseMove={mouseMove}
405+
onRightMouseDown={rightMouseDown}
406+
draw={draw}
407+
/>
332408
</view>
333409
],
334410
()
335411
)}
336412
{switch (filtered->Belt.List.get(selection)) {
337413
| None => <view />
338-
| Some(emoji) => <ShowEmoji emoji />
414+
| Some((emoji, _)) => <ShowEmoji emoji />
339415
}}
340416
{switch (hasNewVersion) {
341417
| None => <view />

0 commit comments

Comments
 (0)