Compare commits
1 Commits
master
...
some-branc
Author | SHA1 | Date | |
---|---|---|---|
|
aba286cc4e |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,3 @@ node_modules/
|
||||
!.yarn/versions
|
||||
|
||||
.parcel-cache
|
||||
.aider*
|
||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1743964447,
|
||||
"narHash": "sha256-nEo1t3Q0F+0jQ36HJfbJtiRU4OI+/0jX/iITURKe3EE=",
|
||||
"lastModified": 1693250523,
|
||||
"narHash": "sha256-y3up5gXMTbnCsXrNEB5j+7TVantDLUYyQLu/ueiXuyg=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "063dece00c5a77e4a0ea24e5e5a5bd75232806f8",
|
||||
"rev": "3efb0f6f404ec8dae31bdb1a9b17705ce0d6986e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
58
flake.nix
58
flake.nix
@ -1,42 +1,40 @@
|
||||
{
|
||||
description = "Kucinako — Wordbook of Arzhanai languages";
|
||||
description = "Kucinako - Wordbook of Arzhanai languages";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
frontend = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "kucinako";
|
||||
version = "0.1";
|
||||
node-modules = pkgs.mkYarnPackage {
|
||||
name = "node-modules";
|
||||
src = ./.;
|
||||
yarnOfflineCache = pkgs.fetchYarnDeps {
|
||||
yarnLock = finalAttrs.src + "/yarn.lock";
|
||||
hash = "sha256-g5g2xlwDxH8O8zaLJ4meO1+DQdJIomVPqd6RXTAhDuE=";
|
||||
};
|
||||
nativeBuildInputs = with pkgs; [
|
||||
yarnConfigHook
|
||||
yarnBuildHook
|
||||
# Needed for executing package.json scripts
|
||||
nodejs
|
||||
];
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
mv dist/* $out
|
||||
'';
|
||||
});
|
||||
in {
|
||||
packages = {
|
||||
default = frontend;
|
||||
};
|
||||
}
|
||||
frontend = pkgs.stdenv.mkDerivation {
|
||||
name = "frontend";
|
||||
src = ./.;
|
||||
buildInputs = [pkgs.yarn node-modules pkgs.lmdb];
|
||||
buildPhase = ''
|
||||
ln -s ${node-modules}/libexec/kucinako/node_modules node_modules
|
||||
${pkgs.yarn}/bin/yarn build
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
mv dist $out/dist
|
||||
'';
|
||||
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
node-modules = node-modules;
|
||||
default = frontend;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
<link rel="shortcut icon" href="./favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
YOLO SWAGG
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
|
4
justfile
4
justfile
@ -1,9 +1,5 @@
|
||||
default:
|
||||
just --list
|
||||
|
||||
# Run local instance
|
||||
run-local:
|
||||
yarn start
|
||||
|
||||
copy-to-marjvena:
|
||||
rsync --progress -r dist greg@marjvena.lan:/home/greg/
|
||||
|
233
src/App.scss
233
src/App.scss
@ -1,18 +1,7 @@
|
||||
// Color variables
|
||||
$background-color: #f0f0b8;
|
||||
$primary-color: #b2a336;
|
||||
$primary-color-dark: #a69530;
|
||||
$text-color: #333;
|
||||
$accent-color: #a63333;
|
||||
$accent-color-dark: #6a3131;
|
||||
$white: #fff;
|
||||
|
||||
body {
|
||||
background-color: $background-color;
|
||||
background-color: #f0f0b8;
|
||||
font-size: 18pt;
|
||||
font-family: "Biwa";
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
@ -21,13 +10,11 @@ input {
|
||||
|
||||
main {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
div .container {
|
||||
max-width: 62rem;
|
||||
margin: auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
div .results {
|
||||
@ -35,74 +22,30 @@ div .results {
|
||||
}
|
||||
|
||||
div .textInput {
|
||||
max-width: 100%;
|
||||
max-width: 70%;
|
||||
margin: auto;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $primary-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.searchDropdown {
|
||||
font-size: 22px;
|
||||
font-family: "Biwa";
|
||||
padding: 10px 15px;
|
||||
border: 2px solid $primary-color;
|
||||
border-radius: 8px;
|
||||
background-color: $white;
|
||||
color: $text-color;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
$encoded-primary-color: str-slice("#{$primary-color}", 2);
|
||||
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23#{$encoded-primary-color}%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 15px top 50%;
|
||||
background-size: 12px auto;
|
||||
padding-right: 35px;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.searchDropdown:hover, .searchDropdown:focus {
|
||||
border-color: $primary-color-dark;
|
||||
box-shadow: 0 0 0 3px rgba($primary-color, 0.25);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
padding: 10px 15px;
|
||||
margin: 10px 5px;
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
font-size: 22px;
|
||||
font-family: "Biwa";
|
||||
background-color: $primary-color;
|
||||
color: $white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.1s;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.searchButton:hover {
|
||||
background-color: $primary-color-dark;
|
||||
}
|
||||
|
||||
.searchButton:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.searchResult {
|
||||
border-bottom: 1px solid $primary-color;
|
||||
padding: 10px 5px;
|
||||
border-bottom: 1px solid #b2a336;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.searchResultHeader {
|
||||
@ -110,172 +53,18 @@ input {
|
||||
}
|
||||
|
||||
.additionalNotes {
|
||||
color: $accent-color;
|
||||
color: #a63333;
|
||||
}
|
||||
|
||||
.semField {
|
||||
font-variant: small-caps;
|
||||
color: $accent-color-dark;
|
||||
color: #6a3131;
|
||||
}
|
||||
|
||||
.saimiarNounMorpho {
|
||||
color: $accent-color;
|
||||
color: #a63333;
|
||||
i {
|
||||
color: $accent-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.passwordDialog {
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $primary-color;
|
||||
background-color: $background-color;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.passwordDialog h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.passwordDialog input {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialogButtons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.passwordDialog button {
|
||||
margin: 5px;
|
||||
padding: 8px 15px;
|
||||
background-color: $primary-color;
|
||||
color: $white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.passwordDialog button:hover {
|
||||
background-color: $primary-color-dark;
|
||||
}
|
||||
|
||||
.searchControls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.buttonGroup {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdownContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dropdownLabel {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
color: $accent-color-dark;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Responsive styles for mobile devices */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
div .textInput {
|
||||
max-width: 100%;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.searchDropdown {
|
||||
font-size: 18px;
|
||||
padding: 12px 15px;
|
||||
margin: 10px auto;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search button {
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.searchControls {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttonGroup {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdownContainer {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dropdownLabel {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
font-size: 18px;
|
||||
padding: 12px 15px;
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
div .container {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.searchDropdown {
|
||||
font-size: 16px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
font-size: 16px;
|
||||
padding: 10px 12px;
|
||||
color: #6a3131;
|
||||
|
||||
}
|
||||
}
|
||||
|
79
src/App.tsx
79
src/App.tsx
@ -25,17 +25,9 @@ const PasswordDialog = (_props) => {
|
||||
|
||||
return (
|
||||
<dialog className="passwordDialog">
|
||||
<h3>Enter Password</h3>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="enter password"
|
||||
value={password}
|
||||
onChange={ (evt) => setPasswordStr(evt.target.value) }
|
||||
/>
|
||||
<div className="dialogButtons">
|
||||
<button onClick={save}>Save</button>
|
||||
<button onClick={cancel}>Cancel</button>
|
||||
</div>
|
||||
<input type="password" placeholder="enter password" value={password} onChange={ (evt) => setPasswordStr(evt.target.value) } />
|
||||
<button onClick={save}>Save</button>
|
||||
<button onClick={cancel}>Cancel</button>
|
||||
</dialog>);
|
||||
};
|
||||
|
||||
@ -95,7 +87,7 @@ const Results = (props: ResultsProps) => {
|
||||
const num = props.searchResults.length;
|
||||
|
||||
const renderedName = renderConlang(conlang);
|
||||
const searchType = (props.direction === SearchDirection.ToConlang) ? `English → ${renderedName}` : `${renderedName} → English`;
|
||||
const searchType = (props.direction === SearchDirection.ToConlang) ? `English -> ${renderedName}` : `${renderedName} -> English`;
|
||||
const result = num === 1 ? "result" : "results";
|
||||
const header = (
|
||||
<div className="searchResultHeader" key="header">
|
||||
@ -143,22 +135,15 @@ const App = (_props) => {
|
||||
};
|
||||
|
||||
const handleSearch = (direction: SearchDirection) => {
|
||||
// First convert any shorthand notation
|
||||
let processedSearchTerm = direction === SearchDirection.ToEnglish ?
|
||||
convertSearchBoxShorthand(searchBoxInput, conlang) :
|
||||
searchBoxInput;
|
||||
|
||||
// Then normalize to lowercase
|
||||
processedSearchTerm = processedSearchTerm.toLowerCase();
|
||||
|
||||
if (processedSearchTerm === "") {
|
||||
const searchTerm = direction === SearchDirection.ToEnglish ? convertSearchBoxShorthand(searchBoxInput, conlang) : searchBoxInput;
|
||||
if (searchTerm === "") {
|
||||
setSearchResults(null);
|
||||
setSearchTerm(null);
|
||||
setDirection(null);
|
||||
} else {
|
||||
searchEntry(processedSearchTerm, conlang, direction, (json) => {
|
||||
searchEntry(searchTerm, conlang, direction, (json) => {
|
||||
setSearchResults(json);
|
||||
setSearchTerm(processedSearchTerm);
|
||||
setSearchTerm(searchTerm);
|
||||
setDirection(direction);
|
||||
});
|
||||
}
|
||||
@ -172,22 +157,9 @@ const App = (_props) => {
|
||||
|
||||
const conlangs = [Conlang.Saimiar, Conlang.Elesu, Conlang.Tukvaysi, Conlang.Juteyuji];
|
||||
const langSelectDropdown = (
|
||||
<div className="dropdownContainer">
|
||||
<label htmlFor="languageSelect" className="dropdownLabel">Select Language:</label>
|
||||
<select
|
||||
id="languageSelect"
|
||||
className="searchDropdown"
|
||||
value={conlang}
|
||||
onChange={handleLangChange}
|
||||
aria-label="Select language"
|
||||
>
|
||||
{conlangs.map((conlang) => (
|
||||
<option value={conlang} key={conlang}>
|
||||
{renderConlang(conlang)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<select className="searchDropdown" value={conlang} onChange={ handleLangChange }>
|
||||
{conlangs.map((conlang) => <option value={conlang} key={conlang}>{renderConlang(conlang)}</option>)}
|
||||
</select>
|
||||
);
|
||||
|
||||
const showPasswordBox = () => {
|
||||
@ -201,32 +173,23 @@ const App = (_props) => {
|
||||
<PasswordDialog />
|
||||
<div className="container">
|
||||
<div className="search">
|
||||
<h1>Kucinako — Wordbook of Arzhanai languages</h1>
|
||||
<h1>Kucinako - Wordbook of Arzhanai languages</h1>
|
||||
<p><b>Kucinako</b> (<i>Saimiar</i> "word-book") is a dictionary of words in various languages of the world Arzhanø, and their English
|
||||
equivalents.</p>
|
||||
<div className="textInput">
|
||||
<input
|
||||
className="textInput"
|
||||
type="text"
|
||||
value={ searchBoxInput }
|
||||
onChange={ (evt) => {
|
||||
setSearchBoxInput(evt.target.value);
|
||||
}}
|
||||
placeholder="Enter search term (case-insensitive)"
|
||||
/>
|
||||
</div>
|
||||
<div className="searchControls">
|
||||
{ langSelectDropdown }
|
||||
<div className="buttonGroup">
|
||||
<button onClick={ () => handleSearch(SearchDirection.ToEnglish) } className="searchButton">{renderConlang(conlang)}</button>
|
||||
<button onClick={ () => handleSearch(SearchDirection.ToConlang) } className="searchButton">English</button>
|
||||
<button onClick={ showPasswordBox } className="searchButton">Password</button>
|
||||
</div>
|
||||
<input className="textInput" type="text" value={ searchBoxInput } onChange={ (evt) => {
|
||||
setSearchBoxInput(evt.target.value);
|
||||
} } />
|
||||
</div>
|
||||
<br/>
|
||||
{ langSelectDropdown }
|
||||
<button onClick={ () => handleSearch(SearchDirection.ToEnglish) } className="searchButton">{renderConlang(conlang)}</button>
|
||||
<button onClick={ () => handleSearch(SearchDirection.ToConlang) } className="searchButton">English</button>
|
||||
<button onClick={ showPasswordBox } className="searchButton">Password</button>
|
||||
</div>
|
||||
<Results
|
||||
searchResults={ searchResults }
|
||||
searchTerm={ searchTerm }
|
||||
searchTerm= { searchTerm }
|
||||
conlang={ conlang }
|
||||
direction={ direction }
|
||||
/>
|
||||
|
@ -20,7 +20,6 @@ const EntryBase = (props: BaseProps) => {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
};
|
||||
|
||||
const controlStyle = {
|
||||
@ -28,20 +27,14 @@ const EntryBase = (props: BaseProps) => {
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
minWidth: "20%",
|
||||
marginTop: "10px",
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
updateEntry(props.conlang, props.id, english);
|
||||
};
|
||||
|
||||
const engTranslation = editing ?
|
||||
<input
|
||||
type="text"
|
||||
value={ english }
|
||||
onChange={ (evt) => setEnglish(evt.target.value) }
|
||||
style={{ width: "100%", marginTop: "5px" }}
|
||||
/> : english;
|
||||
const engTranslation = editing ? <input type="text" value={ english } onChange={ (evt) => setEnglish(evt.target.value) }/>
|
||||
: english;
|
||||
|
||||
const EditControls = ({onSave}: { onSave: () => void }) => {
|
||||
const cancel = () => setEditing(false);
|
||||
@ -88,18 +81,11 @@ const SaiEntry = (props: {entry: SaiEntryProps}) => {
|
||||
if (isNominal) {
|
||||
const decl = declineSaimiar(entry);
|
||||
if (decl) {
|
||||
morphology = (
|
||||
<table className="saimiarNounMorpho">
|
||||
<tr>
|
||||
<td>Abs: <i>{decl.abs}</i></td><td>Erg: <i>{decl.erg}</i></td><td>Adp: <i>{decl.adp}</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>All: <i>{decl.all}</i></td><td>Loc: <i>{decl.loc}</i></td><td>Ell: <i>{decl.ell}</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Inst: <i>{decl.inst}</i></td><td>Rel: <i>{decl.rel}</i></td><td></td>
|
||||
</tr>
|
||||
</table>);
|
||||
morphology = (<span className="saimiarNounMorpho">
|
||||
Abs: <i>{decl.abs}</i>, Erg: <i>{decl.erg}</i>, Adp: <i>{decl.adp}</i>,
|
||||
All: <i>{decl.all}</i>, Loc: <i>{decl.loc}</i>, Ell: <i>{decl.ell}</i>,
|
||||
Inst: <i>{decl.inst}</i>, Rel: <i>{decl.rel}</i>
|
||||
</span>);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2020", "dom"],
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
"lib": ["ES2020", "dom"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user