Skip to content

Commit f828283

Browse files
committed
Feat: Add/Edit/Delete languages.
- Add: fetch langs from db for settings/language view. - get regexes and use them when splitting articles before inserting them - refactor util/word? to use custom regex. Same with not-word. - Add: ability to edit languages (regexes). - Clean: remove old language maps. - Refactor: use `is_not_a_word` to determine styling in component/article-word. UI Changes: - Add[ui]: component/input: add highlight green/red on success/err based on :valid? prop. - Fix[ui]: bottom save changes bar positioning. - Fix[ui]: align open translations button. - Add[ui]: "label" prop to inputs. - Add[ui]: Style button based on type. - Add[ui]: input: on-touched handle validation.
1 parent a6b89c2 commit f828283

17 files changed

+710
-236
lines changed

Diff for: docs/design.afdesign

-28 Bytes
Binary file not shown.

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"clean": "rm -rf resources/public/js/* && rm -rf target",
1414
"pack": "npx electron-builder --dir",
1515
"dist": "DEBUG=electron-builder electron-builder",
16+
"dist-local": "yarn build && DEBUG=electron-builder electron-builder",
1617
"rebuild": "electron-rebuild -f -w better-sqlite3",
1718
"test": "shadow-cljs compile test && node out/node-tests.js"
1819
},

Diff for: src/app/main/db.cljs

+89-38
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
(def db-path (.join path (.getPath app "userData") db-name))
1414
(def db (sqlite. db-path))
1515

16-
1716
;; (defn remove-db-file! []
1817
;; (.unlink fs db-path #(when % (prn "Failed to delete db file") %)))
1918

@@ -27,9 +26,7 @@
2726
;; versions of their actual spelling and ID, making it possible to reconstruct
2827
;; the word from the delimisted string (word_ids) in the article table.
2928

30-
;; FIXME: words table needs new columns
31-
;; TODO - last updated
32-
;; TODO - last seen
29+
;; TODO: rename `article_id` -> `id`; same with `settings_id`
3330
(def db-seed "
3431
3532
CREATE TABLE IF NOT EXISTS words (
@@ -69,10 +66,19 @@
6966
current_page INTEGER DEFAULT 0
7067
);
7168
69+
CREATE TABLE IF NOT EXISTS languages (
70+
id INTEGER PRIMARY KEY,
71+
name TEXT NOT NULL,
72+
iso_639_1 TEXT NOT NULL,
73+
text_splitting_regex TEXT NOT NULL,
74+
word_regex TEXT NOT NULL,
75+
UNIQUE(name, iso_639_1)
76+
);
77+
7278
CREATE TABLE IF NOT EXISTS settings (
7379
settings_id INTEGER PRIMARY KEY,
7480
user TEXT
75-
)
81+
);
7682
7783
")
7884

@@ -103,18 +109,69 @@
103109
js->clj
104110
(get "version")))
105111

106-
107112
(defn read-sample-file
108113
[name]
109114
(-> (.readFileSync ^:export fs (.join path js/__dirname ".." "test/sample_texts/" name) "utf8")))
110115

116+
;; -- DB: Langs ----------------------------------------------------------------
117+
;;
118+
119+
(defn langs-init
120+
"Create the starting languages for the database, if they don't exist yet."
121+
[]
122+
(let [langs (sql {:op :get :stmt "SELECT * FROM languages" :params []})]
123+
(when (= (count langs) 0)
124+
(doseq [[lang lang-map] specs/langs-db]
125+
(sql {:op :run
126+
:params (assoc lang-map :id nil)
127+
:stmt "INSERT INTO languages VALUES (@id, @name, @iso_639_1, @text_splitting_regex, @word_regex)"})))))
128+
129+
(defn langs-all
130+
[]
131+
(sql {:op :all :params [] :stmt "SELECT * FROM languages;"}))
132+
133+
(defn lang-get-by-code
134+
[lang-code]
135+
(sql {:op :get :params [lang-code] :stmt "SELECT * FROM languages where iso_639_1 = ?;"}))
111136

137+
(defn lang-get-text-split-regex
138+
[lang-code]
139+
(get (lang-get-by-code lang-code) :text_splitting_regex))
140+
141+
(defn lang-get-word-regex
142+
[lang-code]
143+
(get (lang-get-by-code lang-code) :word_regex))
144+
145+
(defn lang-update
146+
"Takes a new language and updates it in the database,
147+
then finds all texts (articles) in this language, and resplits and inserts
148+
words based on how it splits up."
149+
[language]
150+
(sql {:op :run
151+
:stmt "UPDATE languages SET word_regex=@word_regex, text_splitting_regex=@text_splitting_regex WHERE id = @id"
152+
:params (select-keys language [:word_regex :text_splitting_regex :id])})
153+
(lang-get-by-code (language :iso_639_1)))
154+
155+
(defn lang-create
156+
"Allows user's to add their own language."
157+
[lang-form-data]
158+
(let [form-data (select-keys lang-form-data [:name :iso_639_1 :word_regex :text_splitting_regex])
159+
form-data (assoc form-data :id nil)]
160+
(sql {:op :run
161+
:params form-data
162+
:stmt "INSERT INTO languages VALUES(@id, @name, @iso_639_1, @word_regex, @text_splitting_regex)"})))
163+
164+
(defn lang-delete
165+
[lang]
166+
(let [id (lang :id)]
167+
(sql {:op :run
168+
:params [id]
169+
:stmt "DELETE FROM languages WHERE id=?"})))
112170

113171
;; -- DB: Settings -------------------------------------------------------------
114172
;; Settings are handled in JSON. For better or worse: ¯\_(ツ)_/¯
115173
;; All settings updates/inserts need to be jsonified.
116174

117-
118175
(defn- settings->json
119176
[s]
120177
(-> s clj->js js/JSON.stringify))
@@ -133,23 +190,22 @@
133190
sometimes handle side-effectful things, so we diff the new settings
134191
against the old, and run 'hooks' based on what changed."
135192
[new-settings-from-fe]
136-
(let [old-settings (settings-get)
193+
(let [old-settings (settings-get)
137194
;; normalize new-settings to look like it came from the db before we diff it.
138-
new-settings (-> new-settings-from-fe settings->json settings->edn)
139-
[old-diff new-diff both] (data/diff new-settings old-settings)]
195+
new-settings (-> new-settings-from-fe settings->json settings->edn)
196+
[_ new-diff _] (data/diff new-settings old-settings)]
140197
(cond
141198
(contains? new-diff "page-size")
142199
(sql {:op :run :params [] :stmt "UPDATE articles SET current_page = 0"})
143200

144-
:else nil
145-
)))
201+
:else nil)))
146202

147203
(defn settings-update
148204
[settings]
149-
(settings-hook! settings)
150-
(sql {:op :run
151-
:stmt "UPDATE settings SET user = ? WHERE settings_id = 1"
152-
:params [(settings->json settings)]}))
205+
(settings-hook! settings)
206+
(sql {:op :run
207+
:stmt "UPDATE settings SET user = ? WHERE settings_id = 1"
208+
:params [(settings->json settings)]}))
153209

154210
(defn settings-init
155211
"If there are no rows in the settings table, initialize it."
@@ -205,14 +261,13 @@
205261
:params [word-id]
206262
:op :get})]
207263
(swap! words-out conj res)))
208-
(assoc article :word-data @words-out :total-pages (js/Math.ceil total-pages)))
209-
)
264+
(assoc article :word-data @words-out :total-pages (js/Math.ceil total-pages))))
210265

211266
(defn article-insert
212267
"Creates a new article. Requirements:
213268
-> words for article are already in words table.
214-
-> words from article have been re-queries
215-
-> requiriesed words' ids have been made into a delimited string with $."
269+
-> words from article have been queried
270+
-> queried words' ids have been made into a delimited string with $."
216271
[{:keys [article title source word_ids language]}]
217272
(sql {:op :run
218273
:params [article word_ids title source (js/Date.now) language]
@@ -264,7 +319,8 @@
264319
"Before inserting an article, we need to get the id for each word in the db
265320
then we can build a delimited string that will get stored under the `word_ids` column"
266321
[{:keys [article language]}]
267-
(let [article-str-vec (u/split-article article)
322+
(let [split-regex (lang-get-text-split-regex language)
323+
article-str-vec (u/split-article article split-regex)
268324
word-ids (atom [])
269325
query "SELECT id FROM words WHERE slug = ? AND name = ? AND language = ?"]
270326
(doseq [word article-str-vec]
@@ -280,15 +336,17 @@
280336
The sql placeholder is a string with many question marks because we are doing a bulk insert?
281337
"
282338
[{:keys [article language]}]
283-
(let [words (u/split-article article)
339+
(let [split-regex (lang-get-text-split-regex language)
340+
word-regex (lang-get-word-regex language)
341+
words (u/split-article article split-regex)
284342
placeholders (str/join ", " (map (fn [_] "(?, ?, ?, ?)") words)) ;; this is annoying
285343
params (->> words
286344
(map (fn [word]
287345
;; THIS is the param list!
288346
(vector
289347
word
290348
(u/slug-word word)
291-
(bool->int (not (u/word? word)))
349+
(bool->int (not (u/word? word word-regex)))
292350
language)))
293351
flatten
294352
(apply array))
@@ -302,8 +360,7 @@
302360
(sql {:op :run
303361
:params phrase
304362
:stmt "INSERT INTO phrases VALUES (@id, @word_ids, @name, @slug, @comfort, @translation, @first_word_slug, @last_word_slug, @language)
305-
ON CONFLICT(name, language) DO UPDATE SET comfort=@comfort, name=@name, translation=@translation"
306-
}))
363+
ON CONFLICT(name, language) DO UPDATE SET comfort=@comfort, name=@name, translation=@translation"}))
307364

308365
(defn phrase-get
309366
[id]
@@ -343,20 +400,15 @@
343400
article
344401
(assoc article :word-data @new-word-data))))
345402

346-
347-
348403
;; ----------------------------------------------------------------------------------------
349404
;; Seed fns
350405

351-
352406
(defn seed-article
353407
[]
354-
(let [
355-
data {:article (read-sample-file "fr_compte2.txt") :title "Compte, Ch 2", :source "..", :language "fr"}
408+
(let [data {:article (read-sample-file "fr_compte2.txt") :title "Compte, Ch 2", :source "..", :language "fr"}
356409
_ (words-insert data)
357410
word-ids-str (words-get-ids-for-article data)
358-
inserted-article (article-insert (merge data {:word_ids word-ids-str}))])
359-
)
411+
inserted-article (article-insert (merge data {:word_ids word-ids-str}))]))
360412

361413
(defn run-seeds
362414

@@ -368,7 +420,6 @@
368420
(when no-content-yet
369421
(seed-article))))
370422

371-
372423
;; Wipe / Init -----------------------------------------------------------------------------
373424

374425
(defn wipe!
@@ -377,11 +428,10 @@
377428
(.exec db "DROP TABLE words;
378429
DROP TABLE articles;
379430
DROP TABLE phrases;
431+
DROP TABLE languages;
380432
DROP TABLE settings;" #(println %))
381433
(.relaunch app)
382-
(.quit app)
383-
)
384-
434+
(.quit app))
385435

386436
(defn init
387437
[]
@@ -390,7 +440,8 @@
390440
(when err
391441
(throw (js/Error. (str "Failed db" err))))))
392442
(settings-init)
393-
(when u/debug? (run-seeds))
394-
)
443+
(langs-init)
444+
445+
(when u/debug? (run-seeds)))
395446

396447
;; FIXME: when do I run "db.close()"?

Diff for: src/app/main/ipc.cljs

+35-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
(defn reply!
1616
"Sends data back to renderer from IPC back end.
1717
Data going over ipc MUST be in JS, so it is converted here.
18+
;; TODO: note that the use of `name` here causes errors if the key doesn't exist.
1819
"
1920
([electron-event msg]
2021
(js/electron-event.reply (name (s-ev :ipc-success)) msg))
@@ -78,7 +79,38 @@
7879
(reply-err! event "Failed to get article." e)))
7980
)
8081

81-
;; -- WORDS HANDLERS ---------------------------
82+
;; -- language handlers -----------------------------------------------------
83+
(s-ev :languages-get)
84+
(fn [event _]
85+
(try
86+
(let [res (db/langs-all)]
87+
(reply! event (s-ev :languages-got) res))
88+
(catch js/Error e reply-err! event "Failed to fetch languages" e)))
89+
90+
(s-ev :language-create)
91+
(fn [event data]
92+
(try
93+
(let [_ (db/lang-create data)
94+
langs (db/langs-all)]
95+
(reply! event (s-ev :language-created) langs))
96+
(catch js/Error e reply-err! event "Failed to create a language" e)))
97+
98+
(s-ev :language-delete)
99+
(fn [event data]
100+
(try
101+
(let [_ (db/lang-delete data)
102+
langs (db/langs-all)]
103+
(reply! event (s-ev :language-deleted) langs))
104+
(catch js/Error e reply-err! event "Failed to create a language" e)))
105+
106+
(s-ev :language-update)
107+
(fn [event data]
108+
(try
109+
(let [updated-lang (db/lang-update data)]
110+
(reply! event (s-ev :language-updated) updated-lang))
111+
(catch js/Error e reply-err! event "Failed to update language" e)))
112+
113+
;; -- words handlers --------------------------------------------------------
82114

83115
(s-ev :words-get)
84116
(fn [event data]
@@ -111,10 +143,10 @@
111143
(s-ev :phrase-update)
112144
(fn [event data]
113145
(try
114-
(let [res (db/phrase-upsert data)
146+
(let [res (db/phrase-upsert data)
115147
is-insert (-> data :id nil?) ;no id yet == insert.
116148
lastInsertRowid (or (data :id) (res :lastInsertRowid))
117-
updated-phrase (db/phrase-get lastInsertRowid )]
149+
updated-phrase (db/phrase-get lastInsertRowid )]
118150
(if is-insert
119151
(reply! event (s-ev :phrase-inserted) updated-phrase)
120152
(reply! event (s-ev :phrase-updated) updated-phrase)))

0 commit comments

Comments
 (0)