38 Commits

Author SHA1 Message Date
a-ill
3b8cd8b9f7 Finished groups 2023-08-01 15:56:41 +03:00
a-ill
3bc8dd4239 Interim update 2023-07-30 13:04:15 +03:00
a-ill
e22b0a5fd1 Moved navbar breakpoint 2023-07-28 21:24:08 +03:00
a-ill
033e4f5a41 Reworked navbar 2023-07-28 21:20:07 +03:00
a-ill
4725d8513b Merge branch 'bugfix/ui-fixes' 2023-07-28 18:18:51 +03:00
a-ill
7a12c5e610 Fixed "undefined" in description bug 2023-07-28 17:58:01 +03:00
a-ill
4d3e487011 Update compile_database.jl 2023-07-28 17:51:43 +03:00
a-ill
cc00b78711 Moved all entries to db 2023-07-28 17:49:29 +03:00
a-ill
f8f1b28ea9 Added db table files 2023-07-28 13:07:33 +03:00
a-ill
e2d4d8eba9 Fixed that db entry language depended on the system language 2023-07-25 16:49:40 +03:00
a-ill
7b9333a950 Fixed a grammar issue 2023-07-25 16:49:02 +03:00
a-ill
5f9a3d8e68 Update landing-component.json 2023-07-24 12:59:46 +03:00
a-ill
e228a202c8 Update 2023-07-24 12:59:41 +03:00
a-ill
8f50189da1 Update landing-component.json 2023-07-22 15:57:49 +03:00
a-ill
4613021498 Updated landing page text 2023-07-22 15:57:42 +03:00
a-ill
45e13b6149 Fixed db init 2023-07-22 13:48:47 +03:00
a-ill
80ac081234 Update 2023-07-22 13:29:15 +03:00
a-ill
19a1b8d080 Update 2023-07-22 13:27:43 +03:00
a-ill
2c80026aba Update 2023-07-22 13:22:35 +03:00
a-ill
117e924f66 Update 2023-07-21 18:07:22 +03:00
a-ill
909416fbe3 Update 2023-07-21 17:38:02 +03:00
a-ill
41998a13be Added resizing of input boxes on input 2023-07-20 13:26:26 +03:00
a-ill
ce86b318fd Added a confirmation text 2023-07-20 00:57:20 +03:00
a-ill
35b5865c25 Changed text, fixed locations not showing 2023-07-20 00:44:44 +03:00
a-ill
accda2f16e Debug 2023-07-20 00:37:48 +03:00
a-ill
7a2df1080a Debug 2023-07-20 00:23:40 +03:00
a-ill
a8008dee88 Added a data entry system at /groups-add 2023-07-20 00:15:12 +03:00
a-ill
3277e7ae0a Fixed wrong <b> tag 2023-07-18 22:07:07 +03:00
a-ill
6ca547e802 Update 2023-07-18 21:02:08 +03:00
a-ill
f1ec07a179 Added a pin 2023-07-18 16:45:24 +03:00
a-ill
e4c8e46a49 Update 2023-07-18 16:37:31 +03:00
a-ill
5937afc9a4 Update 2023-07-18 14:49:28 +03:00
a-ill
bc81fc231f Removed one member from Florida 2023-07-18 12:27:33 +03:00
a-ill
41acf38fb5 Added a pin in Bulgaria 2023-07-18 01:31:45 +03:00
a-ill
d688cf79a4 Update 2023-07-17 22:05:43 +03:00
a-ill
2e34212c93 Added a pin to Tallinn 2023-07-17 21:59:43 +03:00
a-ill
e0ecec7d81 Merge pull request #1 from lib-soc/bugfix/ui-fixes
Bugfix/UI fixes
2023-07-17 01:18:49 +03:00
a-ill
dfd90b323c Update 2023-07-17 01:11:32 +03:00
225 changed files with 14340 additions and 1470 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ Server/sessions/
*.jls *.jls
.vscode/settings.json .vscode/settings.json
Server/app/resources/emails/credentials.json Server/app/resources/emails/credentials.json
Server/db/connection.yml Server/db/connection.yml
Server/public/assets/groups.json

View File

@@ -1,8 +1,8 @@
# This file is machine-generated - editing it directly is not advised # This file is machine-generated - editing it directly is not advised
julia_version = "1.9.0" julia_version = "1.9.1"
manifest_format = "2.0" manifest_format = "2.0"
project_hash = "829f3e210629ef04542eb44fc4cb5acdb74d23eb" project_hash = "09d33216e2516631ede3cbab2af65d3f95eb0598"
[[deps.Adapt]] [[deps.Adapt]]
deps = ["LinearAlgebra", "Requires"] deps = ["LinearAlgebra", "Requires"]
@@ -62,9 +62,9 @@ version = "1.3.1"
[[deps.CodecZlib]] [[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"] deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "9c209fb7536406834aa938fb149964b985de6c83" git-tree-sha1 = "02aa26a4cf76381be7f66e020a3eddeb27b0a092"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193" uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.1" version = "0.7.2"
[[deps.CommonMark]] [[deps.CommonMark]]
deps = ["Crayons", "JSON", "PrecompileTools", "URIs"] deps = ["Crayons", "JSON", "PrecompileTools", "URIs"]
@@ -74,9 +74,9 @@ version = "0.8.12"
[[deps.Compat]] [[deps.Compat]]
deps = ["UUIDs"] deps = ["UUIDs"]
git-tree-sha1 = "4e88377ae7ebeaf29a047aa1ee40826e0b708a5d" git-tree-sha1 = "5ce999a19f4ca23ea484e92a1774a61b8ca4cf8e"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.7.0" version = "4.8.0"
weakdeps = ["Dates", "LinearAlgebra"] weakdeps = ["Dates", "LinearAlgebra"]
[deps.Compat.extensions] [deps.Compat.extensions]
@@ -89,31 +89,36 @@ version = "1.0.2+0"
[[deps.ConcurrentUtilities]] [[deps.ConcurrentUtilities]]
deps = ["Serialization", "Sockets"] deps = ["Serialization", "Sockets"]
git-tree-sha1 = "96d823b94ba8d187a6d8f0826e731195a74b90e9" git-tree-sha1 = "5372dbbf8f0bdb8c700db5367132925c0771ef7e"
uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
version = "2.2.0" version = "2.2.1"
[[deps.Crayons]] [[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1" version = "4.1.1"
[[deps.DBInterface]]
git-tree-sha1 = "9b0dc525a052b9269ccc5f7f04d5b3639c65bca5"
uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965"
version = "2.5.0"
[[deps.DataAPI]] [[deps.DataAPI]]
git-tree-sha1 = "8da84edb865b0b5b0100c0666a9bc9a0b71c553c" git-tree-sha1 = "8da84edb865b0b5b0100c0666a9bc9a0b71c553c"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.15.0" version = "1.15.0"
[[deps.DataFrames]] [[deps.DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Random", "Reexport", "SentinelArrays", "SnoopPrecompile", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "REPL", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "aa51303df86f8626a962fccb878430cdb0a97eee" git-tree-sha1 = "04c738083f29f86e62c8afc341f0967d8717bdb8"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.5.0" version = "1.6.1"
[[deps.DataStructures]] [[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"] deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "d1fff3a548102f48987a52a2e0d114fa97d730f0" git-tree-sha1 = "cf25ccb972fec4e4817764d01c82386ae94f77b4"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.13" version = "0.18.14"
[[deps.DataValueInterfaces]] [[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
@@ -168,9 +173,9 @@ uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
version = "0.1.9" version = "0.1.9"
[[deps.ExprTools]] [[deps.ExprTools]]
git-tree-sha1 = "c1d06d129da9f55715c6c212866f5b1bddc5fa00" git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.9" version = "0.1.10"
[[deps.EzXML]] [[deps.EzXML]]
deps = ["Printf", "XML2_jll"] deps = ["Printf", "XML2_jll"]
@@ -193,12 +198,6 @@ version = "0.9.20"
[[deps.FileWatching]] [[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
[[deps.Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"
[[deps.Future]] [[deps.Future]]
deps = ["Random"] deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
@@ -257,9 +256,9 @@ version = "1.3.1"
[[deps.HTTP]] [[deps.HTTP]]
deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"]
git-tree-sha1 = "2613d054b0e18a3dea99ca1594e9a3960e025da4" git-tree-sha1 = "cb56ccdd481c0dd7f975ad2b3b62d9eda088f7e2"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "1.9.7" version = "1.9.14"
[[deps.HttpCommon]] [[deps.HttpCommon]]
deps = ["Dates", "Nullables", "Test", "URIParser"] deps = ["Dates", "Nullables", "Test", "URIParser"]
@@ -339,6 +338,12 @@ git-tree-sha1 = "5b62d93f2582b09e469b3099d839c2d2ebf5066d"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.13.1" version = "1.13.1"
[[deps.JWTs]]
deps = ["Base64", "Downloads", "JSON", "MbedTLS", "Random"]
git-tree-sha1 = "a1f3ded6307ef85cc18dec93d9b993814eb4c1a0"
uuid = "d850fbd6-035d-5a70-a269-1ca2e636ac6c"
version = "0.2.2"
[[deps.JuliaFormatter]] [[deps.JuliaFormatter]]
deps = ["CSTParser", "CommonMark", "DataStructures", "Glob", "Pkg", "PrecompileTools", "Tokenize"] deps = ["CSTParser", "CommonMark", "DataStructures", "Glob", "Pkg", "PrecompileTools", "Tokenize"]
git-tree-sha1 = "60567b51bd9e1e19ae2fd8a54dcd6bc5994727f0" git-tree-sha1 = "60567b51bd9e1e19ae2fd8a54dcd6bc5994727f0"
@@ -386,10 +391,10 @@ deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[deps.LibPQ]] [[deps.LibPQ]]
deps = ["CEnum", "Dates", "Decimals", "DocStringExtensions", "FileWatching", "Infinity", "Intervals", "IterTools", "LayerDicts", "LibPQ_jll", "Libdl", "Memento", "OffsetArrays", "SQLStrings", "Tables", "TimeZones", "UTCDateTimes"] deps = ["CEnum", "DBInterface", "Dates", "Decimals", "DocStringExtensions", "FileWatching", "Infinity", "Intervals", "IterTools", "LayerDicts", "LibPQ_jll", "Libdl", "Memento", "OffsetArrays", "SQLStrings", "Tables", "TimeZones", "UTCDateTimes"]
git-tree-sha1 = "114d9d239ab8e1251354ad6bb97ed38622133114" git-tree-sha1 = "d8967f68674aa9ad4b9b3df114e3842f269ac147"
uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
version = "1.15.1" version = "1.16.0"
[[deps.LibPQ_jll]] [[deps.LibPQ_jll]]
deps = ["Artifacts", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Pkg"] deps = ["Artifacts", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Pkg"]
@@ -519,9 +524,9 @@ version = "1.0.0"
[[deps.OffsetArrays]] [[deps.OffsetArrays]]
deps = ["Adapt"] deps = ["Adapt"]
git-tree-sha1 = "82d7c9e310fe55aa54996e6f7f94674e2a38fcb4" git-tree-sha1 = "2ac17d29c523ce1cd38e27785a7d23024853a4bb"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.12.9" version = "1.12.10"
[[deps.OpenBLAS_jll]] [[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
@@ -552,9 +557,9 @@ uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0" version = "0.5.5+0"
[[deps.OrderedCollections]] [[deps.OrderedCollections]]
git-tree-sha1 = "d321bf2de576bf25ec4d3e4360faca399afca282" git-tree-sha1 = "2e73fe17cac3c62ad1aebe70d44c963c3cfdc3e3"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.6.0" version = "1.6.2"
[[deps.Parameters]] [[deps.Parameters]]
deps = ["OrderedCollections", "UnPack"] deps = ["OrderedCollections", "UnPack"]
@@ -564,9 +569,9 @@ version = "0.12.3"
[[deps.Parsers]] [[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"] deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "4b2e829ee66d4218e0cef22c0a64ee37cf258c29" git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.7.1" version = "2.7.2"
[[deps.Pkg]] [[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
@@ -608,10 +613,10 @@ uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.0" version = "1.4.0"
[[deps.PrettyTables]] [[deps.PrettyTables]]
deps = ["Crayons", "Formatting", "LaTeXStrings", "Markdown", "Reexport", "StringManipulation", "Tables"] deps = ["Crayons", "LaTeXStrings", "Markdown", "Printf", "Reexport", "StringManipulation", "Tables"]
git-tree-sha1 = "213579618ec1f42dea7dd637a42785a608b1ea9c" git-tree-sha1 = "ee094908d720185ddbdc58dbe0c1cbe35453ec7a"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "2.2.4" version = "2.2.7"
[[deps.Printf]] [[deps.Printf]]
deps = ["Unicode"] deps = ["Unicode"]
@@ -701,12 +706,6 @@ git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1"
uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7"
version = "1.1.0" version = "1.1.0"
[[deps.SnoopPrecompile]]
deps = ["Preferences"]
git-tree-sha1 = "e760a70afdcd461cf01a575947738d359234665c"
uuid = "66db9d55-30c0-4569-8b51-7e840670fc0c"
version = "1.0.3"
[[deps.Sockets]] [[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc" uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
@@ -739,9 +738,9 @@ version = "1.9.0"
[[deps.StringEncodings]] [[deps.StringEncodings]]
deps = ["Libiconv_jll"] deps = ["Libiconv_jll"]
git-tree-sha1 = "33c0da881af3248dafefb939a21694b97cfece76" git-tree-sha1 = "b765e46ba27ecf6b44faf70df40c57aa3a547dcb"
uuid = "69024149-9ee7-55f6-a4c4-859efe599b68" uuid = "69024149-9ee7-55f6-a4c4-859efe599b68"
version = "0.3.6" version = "0.3.7"
[[deps.StringManipulation]] [[deps.StringManipulation]]
git-tree-sha1 = "46da2434b41f41ac3594ee9816ce5541c6096123" git-tree-sha1 = "46da2434b41f41ac3594ee9816ce5541c6096123"
@@ -765,10 +764,10 @@ uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3" version = "1.0.3"
[[deps.TableShowUtils]] [[deps.TableShowUtils]]
deps = ["DataValues", "Dates", "JSON", "Markdown", "Test"] deps = ["DataValues", "Dates", "JSON", "Markdown", "Unicode"]
git-tree-sha1 = "14c54e1e96431fb87f0d2f5983f090f1b9d06457" git-tree-sha1 = "2a41a3dedda21ed1184a47caab56ed9304e9a038"
uuid = "5e66a065-1f0a-5976-b372-e0b8c017ca10" uuid = "5e66a065-1f0a-5976-b372-e0b8c017ca10"
version = "0.2.5" version = "0.2.6"
[[deps.TableTraits]] [[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"] deps = ["IteratorInterfaceExtensions"]
@@ -810,9 +809,9 @@ version = "1.0.1"
[[deps.TimeZones]] [[deps.TimeZones]]
deps = ["Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "RecipesBase", "Scratch", "Unicode"] deps = ["Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "RecipesBase", "Scratch", "Unicode"]
git-tree-sha1 = "cdaa0c2a4449724aded839550eca7d7240bb6938" git-tree-sha1 = "5b347464bdac31eccfdbe1504d9484c31645cafc"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.10.0" version = "1.11.0"
[[deps.Tokenize]] [[deps.Tokenize]]
git-tree-sha1 = "90538bf898832b6ebd900fa40f223e695970e3a5" git-tree-sha1 = "90538bf898832b6ebd900fa40f223e695970e3a5"
@@ -880,7 +879,7 @@ version = "1.2.13+0"
[[deps.libblastrampoline_jll]] [[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"] deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.7.0+0" version = "5.8.0+0"
[[deps.nghttp2_jll]] [[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"] deps = ["Artifacts", "Libdl"]

View File

@@ -4,6 +4,7 @@ authors = ["a-ill <a_ill@outlook.com>"]
version = "0.1.0" version = "0.1.0"
[deps] [deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
CSVFiles = "5d742f6a-9f54-50ce-8119-2520741973ca" CSVFiles = "5d742f6a-9f54-50ce-8119-2520741973ca"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
@@ -15,6 +16,7 @@ GenieSession = "03cc5b98-4f21-4eb6-99f2-22eced81f962"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
Inflector = "6d011eab-0732-4556-8808-e463c76bf3b6" Inflector = "6d011eab-0732-4556-8808-e463c76bf3b6"
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
JWTs = "d850fbd6-035d-5a70-a269-1ca2e636ac6c"
LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" LibPQ = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"

View File

@@ -25,7 +25,11 @@
<!--<loadscreen-component></loadscreen-component>--> <!--<loadscreen-component></loadscreen-component>-->
<div id="content"> <div id="content">
<navbar-component></navbar-component> <% if authenticated() %>
<navbar-logged></navbar-logged>
<% else %>
<navbar-not-logged></navbar-not-logged>
<% end %>
<%@yield%> <%@yield%>

View File

@@ -0,0 +1,193 @@
module AuthenticationController
using Genie, Genie.Requests, Genie.Renderer, Genie.Renderer.Json, Genie.Renderer.Html, GenieSession, SearchLight, GenieAuthentication, GenieAuthorisation
using Logging
using JSON3, Random, Base64, HTTP, Dates
using Server.Users, Server.EmailSupport, Server.TemplateEditor, Server.Cookies, Server.DatabaseSupport
import Server.TemplateEditor.generate_layout_html
import Server.DatabaseSupport.select_from_table
using JWTs
#---Helpers----------------------------------------------------------
const keyset = JWKSet("https://www.googleapis.com/oauth2/v3/certs")
refresh!(keyset)
current_user() = findone(Users.User, id = get_authentication())
function send_signup_confirmation_email(receiver,confirmation_code)
subject,message = ["Sign-up confirmation","Hello!\r\nYour confirmation code is "*confirmation_code*"\r\n"]
message = "Content-Type: text/html\r\n"*message
return send_email(receiver,subject,message)
end
function register_google()
jws = rawpayload()
jws_split = split(jws,".")
payload_encoded = jws_split[2]
rem = length(payload_encoded)%4
if rem!= 0
payload_encoded = payload_encoded* "="^(4-rem)
end
payload = String(base64decode(payload_encoded))
json = JSON3.read(payload)
sub = json[:sub]
email = json[:email]
user = findone(User, email = email)
if isnothing(user)
# ENABLE WHEN IN PRODUCTION
user = User(email = email,google_id = sub) |> save!
authenticate(user.id, GenieSession.session(params()))
assign_role(user, findone(Role, name = "free"))
save(user)
return true
return 0
else
jwt = JWT(payload="")
jwt.header = jws_split[1]
jwt.payload = jws_split[2]
jwt.signature = jws_split[3]
if validate!(jwt, keyset)
if user.google_id==""
user.google_id = sub
save(user)
authenticate(user.id, GenieSession.session(params()))
return 3
elseif user.google_id==sub
authenticate(user.id, GenieSession.session(params()))
return 3
else
return 0
end
else
return 0
end
end
end
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
const auth_info = Dict(
"en" => Dict(
:title => "LibSoc - Login/Sign Up",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Логин/Регистрация",
:description => ""
)
)
#---Routing functions---------------------------------------------------
controller = "authentication"
const dict_layouts = Dict(
:auth => generate_layout_html("main",controller,"auth",libraries=["GoogleAuth"]),
:profile => generate_layout_html("main",controller,"profile",libraries=["Leaflet"]),
:email_confirmation => generate_layout_html("main",controller,"email_confirmation"),
)
function auth()
locale = get_locale()
set_cookies(params())
html(:authentication,:auth, layout = dict_layouts[:auth], context = @__MODULE__,
title = auth_info[locale][:title],
description = auth_info[locale][:description]
)
end
function profile()
set_cookies(params())
html(:authentication,:profile, layout = dict_layouts[:profile], context = @__MODULE__,
title = "Chiron | Profile",
description = ""
)
end
function email_confirmation()
set_cookies(params())
html(:authentication,:email_confirmation, layout = dict_layouts[:email_confirmation], context = @__MODULE__,
title = "Chiron | Email Confirmation",
description = ""
)
end
function confirm_email()
code = rawpayload()
user = current_user()
if code==user.confirmation_code
GenieAuthorisation.Relationship!(user, findone(Role, name = "unconfirmed")) |> delete
assign_role(user, findone(Role, name = "free"))
return true
else
return false
end
end
function register()
data = jsonpayload()
user = findone(User, email = data["email"])
if isnothing(user)
user = User(email = data["email"],
password = data["password"] |> Users.hash_password,
) |> save!
authenticate(user.id, GenieSession.session(params()))
assign_role(user, findone(Role, name = "free"))
confirmation_code = randstring('0':'9', 5)
user.confirmation_code = confirmation_code
save(user)
#send_signup_confirmation_email(data["email"],confirmation_code)
return true
else
return false
end
end
function login()
data = jsonpayload()
user = findone(User, email = data["email"])
if isnothing(user)
return 0
else
if (user.password==Users.hash_password(data["password"]))
authenticate(user.id, GenieSession.session(params()))
return 2
else
return 1
end
end
end
function logout()
deauthenticate(GenieSession.session(params()))
return
end
function change_user()
data = jsonpayload()
user = findone(Users.User, id = get_authentication())
for (field,value) in data
setfield!(user, Symbol(field), value)
end
save(user)
return JSON3.write(true)
end
function get_user()
try
user = findone(Users.User, id = get_authentication())
return JSON3.write(user)
catch ex
return JSON3.write(false)
end
end
end

View File

@@ -0,0 +1,2 @@
<auth-component></auth-component>

View File

@@ -0,0 +1 @@
<confirmation-component></confirmation-component>

View File

@@ -0,0 +1,2 @@
<profile-component></profile-component>

View File

@@ -1,6 +1,6 @@
module BasicController module BasicController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication
using JSON3 using JSON3
using SearchLight using SearchLight
using Server.DatabaseSupport, Server.TemplateEditor using Server.DatabaseSupport, Server.TemplateEditor
@@ -10,11 +10,6 @@ dict_layouts = Dict(
:landing => generate_layout_html("main",controller,"landing",css=["landing"],libraries=["Leaflet"]), :landing => generate_layout_html("main",controller,"landing",css=["landing"],libraries=["Leaflet"]),
:manifesto => generate_layout_html("main",controller,"manifesto"), :manifesto => generate_layout_html("main",controller,"manifesto"),
:join_us => generate_layout_html("main",controller,"join_us",libraries=["Leaflet"]), :join_us => generate_layout_html("main",controller,"join_us",libraries=["Leaflet"]),
:groups => generate_layout_html("main",controller,"groups",libraries=["Leaflet"]),
:cooperatives => generate_layout_html("main",controller,"cooperatives",libraries=["Leaflet"]),
:communes => generate_layout_html("main",controller,"communes",libraries=["Leaflet"]),
:parties => generate_layout_html("main",controller,"parties",libraries=["Leaflet"]),
:partners => generate_layout_html("main",controller,"partners",libraries=["Leaflet"]),
:compass => generate_layout_html("main",controller,"compass"), :compass => generate_layout_html("main",controller,"compass"),
) )
@@ -53,61 +48,6 @@ const join_us_info = Dict(
), ),
) )
const groups_info = Dict(
"en" => Dict(
:title => "LibSoc - Groups",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Группы",
:description => ""
)
)
const cooperatives_info = Dict(
"en" => Dict(
:title => "LibSoc - Cooperatives",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Кооперативы",
:description => ""
)
)
const communes_info = Dict(
"en" => Dict(
:title => "LibSoc - Communes",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Коммуны",
:description => ""
)
)
const partners_info = Dict(
"en" => Dict(
:title => "LibSoc - Partners",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Партнеры",
:description => ""
)
)
const parties_info = Dict(
"en" => Dict(
:title => "LibSoc - Parties",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Партии",
:description => ""
)
)
const compass_info = Dict( const compass_info = Dict(
"en" => Dict( "en" => Dict(
:title => "LibSoc - Political Compass", :title => "LibSoc - Political Compass",
@@ -146,54 +86,6 @@ function manifesto()
) )
end end
function join_us()
locale = get_locale()
html(:basic,:join_us, layout = dict_layouts[:join_us], context = @__MODULE__,
title = join_us_info[locale][:title],
description = join_us_info[locale][:description]
)
end
function groups()
locale = get_locale()
html(:basic,:groups, layout = dict_layouts[:groups], context = @__MODULE__,
title = groups_info[locale][:title],
description = groups_info[locale][:description]
)
end
function cooperatives()
locale = get_locale()
html(:basic,:cooperatives, layout = dict_layouts[:cooperatives], context = @__MODULE__,
title = cooperatives_info[locale][:title],
description = cooperatives_info[locale][:description]
)
end
function communes()
locale = get_locale()
html(:basic,:communes, layout = dict_layouts[:communes], context = @__MODULE__,
title = communes_info[locale][:title],
description = communes_info[locale][:description]
)
end
function partners()
locale = get_locale()
html(:basic,:partners, layout = dict_layouts[:partners], context = @__MODULE__,
title = partners_info[locale][:title],
description = partners_info[locale][:description]
)
end
function parties()
locale = get_locale()
html(:basic,:parties, layout = dict_layouts[:parties], context = @__MODULE__,
title = parties_info[locale][:title],
description = parties_info[locale][:description]
)
end
function political_compass() function political_compass()
locale = get_locale() locale = get_locale()
html(:basic,:compass, layout = dict_layouts[:compass], context = @__MODULE__, html(:basic,:compass, layout = dict_layouts[:compass], context = @__MODULE__,
@@ -202,4 +94,12 @@ function political_compass()
) )
end end
end function join_us()
locale = get_locale()
html(:basic,:join_us, layout = dict_layouts[:join_us], context = @__MODULE__,
title = join_us_info[locale][:title],
description = join_us_info[locale][:description]
)
end
end

View File

@@ -0,0 +1,59 @@
module CommunesController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication
using JSON3
using SearchLight
using Server.DatabaseSupport, Server.TemplateEditor
controller = "communes"
dict_layouts = Dict(
:communes => generate_layout_html("main",controller,"communes",libraries=["Leaflet"]),
:communes_add => generate_layout_html("main",controller,"communes_add",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const communes_info = Dict(
"en" => Dict(
:title => "LibSoc - Communes",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Коммуны",
:description => ""
)
)
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
#---Functions---------------------------------------------------------
function communes()
locale = get_locale()
html(:communes,:communes, layout = dict_layouts[:communes], context = @__MODULE__,
title = communes_info[locale][:title],
description = communes_info[locale][:description]
)
end
function communes_add()
locale = get_locale()
html(:communes,:communes_add, layout = dict_layouts[:communes_add], context = @__MODULE__,
title = communes_info[locale][:title],
description = communes_info[locale][:description]
)
end
function communes_add_post()
data = jsonpayload()
insert_into_table("communes_requests",data)
end
end

View File

@@ -0,0 +1 @@
<communes-add-component></communes-add-component>

View File

@@ -0,0 +1,60 @@
module CooperativesController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication
using JSON3
using SearchLight
using Server.DatabaseSupport, Server.TemplateEditor
controller = "cooperatives"
dict_layouts = Dict(
:cooperatives => generate_layout_html("main",controller,"cooperatives",libraries=["Leaflet"]),
:cooperatives_add => generate_layout_html("main",controller,"cooperatives_add",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const cooperatives_info = Dict(
"en" => Dict(
:title => "LibSoc - Cooperatives",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Кооперативы",
:description => ""
)
)
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
#---Functions---------------------------------------------------------
function cooperatives_add()
locale = get_locale()
html(:cooperatives,:cooperatives_add, layout = dict_layouts[:cooperatives_add], context = @__MODULE__,
title = cooperatives_info[locale][:title],
description = cooperatives_info[locale][:description]
)
end
function cooperatives()
locale = get_locale()
html(:cooperatives,:cooperatives, layout = dict_layouts[:cooperatives], context = @__MODULE__,
title = cooperatives_info[locale][:title],
description = cooperatives_info[locale][:description]
)
end
function cooperatives_add_post()
data = jsonpayload()
insert_into_table("cooperatives_requests",data)
end
end

View File

@@ -0,0 +1 @@
<cooperatives-add-component></cooperatives-add-component>

View File

@@ -0,0 +1,261 @@
module GroupsController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication, DataFrames
using JSON3
using SearchLight,SearchLightPostgreSQL, LibPQ, JSON3
using Server.DatabaseSupport, Server.TemplateEditor, Server.Users
import Server.DatabaseSupport: select_from_table, insert_into_table, delete_from_table, exist_in_table
controller = "groups"
dict_layouts = Dict(
:groups => generate_layout_html("main",controller,"groups",libraries=["Leaflet"]),
:groups_add => generate_layout_html("main",controller,"groups_add",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const groups_info = Dict(
"en" => Dict(
:title => "LibSoc - Groups",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Группы",
:description => ""
)
)
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
#---Helpers-----------------------------------------------------------
function table_to_json(name,df)
ar = []
for df_row in eachrow(df)
dict = Dict()
for id in names(df_row)
dict[id] = df_row[id]
end
push!(ar,dict)
end
open("public/assets/"*name*".json", "w") do io
JSON3.write(io, ar)
end
end
function compile(name)
df = select_from_table([name => ["*"]])
table_to_json(name,df)
end
function move_requests(name)
df_requests = select_from_table(["$(name)_requests" => ["*"]], where_data=["verified" => true, "added" => false])
df = select_from_table([name => ["*"]])
latitudes = df.latitude
longitudes = df.longitude
for df_row in eachrow(df_requests)
ind_id_given = ismissing(df_row.id_given) ? nothing : findfirst(df_row.id_given.==df.id)
if (!isnothing(ind_id_given))
id = df[ind_id_given,:id]
row_found = df[ind_id_given,Not(:id)]
dict = Dict(zip(names(row_found),values(row_found)))
dict["members"] += 1
update_table(name,dict, where_data=["id" => id])
else
id = df_row.id
dict_update = Dict("added" => true)
update_table("$(name)_requests",dict_update, where_data=["id" => id])
df_row_to_add = df_row[Not(:id_given)]
df_row_to_add = df_row_to_add[Not(:verified)]
df_row_to_add = df_row_to_add[Not(:added)]
df_row_to_add = df_row_to_add[Not(:id)]
dict = Dict(zip(names(df_row_to_add),values(df_row_to_add)))
dict["members"] = 1
insert_into_table(name,dict)
end
end
end
#---Functions---------------------------------------------------------
current_user() = findone(Users.User, id = get_authentication())
function groups()
locale = get_locale()
html(:groups,:groups, layout = dict_layouts[:groups], context = @__MODULE__,
title = groups_info[locale][:title],
description = groups_info[locale][:description]
)
end
function groups_add()
locale = get_locale()
html(:groups,:groups_add, layout = dict_layouts[:groups_add], context = @__MODULE__,
title = groups_info[locale][:title],
description = groups_info[locale][:description]
)
end
function groups_add_post()
data = copy(jsonpayload())
mode = data["mode"]
delete!(data,"mode")
user = current_user()
user_id = user.id
if mode==0 # Create
if user.verified
existing_user_group_data = select_from_table(["users_groups" => ["*"]], where_data=["user_id" => user_id])
has_group = !isempty(existing_user_group_data)
delete!(data,"group_id")
group_id = insert_into_table("groups",data, "RETURNING id")[1,1]
if has_group
user_groups_id = existing_user_group_data[1,"id"]
prev_group_id = existing_user_group_data[1,"group_id"]
update_table("users_groups",Dict("group_id" => group_id), where_data=["id" => user_groups_id])
members = select_from_table(["groups" => ["members"]], where_data=["id" => prev_group_id])[1,1]
if (members==1)
delete_from_table("groups",["id" => prev_group_id])
else
update_table("groups",Dict("members" => members - 1), where_data=["id" => id])
end
else
dict_users_groups = Dict("user_id" => user.id, "group_id" => group_id)
insert_into_table("users_groups",dict_users_groups)
end
compile("groups")
else
data["status"] = 0
data["user_id"] = user_id
insert_into_table("groups_requests",data)
end
elseif mode==1 # Join
data["user_id"] = user_id
if exist_in_table("users_groups",["group_id" => data["group_id"]])
if exist_in_table("groups_requests",["user_id" => user_id])
delete_from_table("groups_requests",["user_id" => user_id])
end
data["status"] = 0
insert_into_table("groups_requests",data)
else
group_id = data["group_id"]
members = select_from_table("groups" => ["members"], where_data = ["id" => group_id])[1,1]
dict = Dict("members" => members + 1)
update_table("groups",dict, where_data=["id" => group_id])
dict_users_groups = Dict("user_id" => user_id, "group_id" => group_id)
insert_into_table("users_groups",dict_users_groups)
end
elseif mode==2 # Move
existing_user_group_data = select_from_table(["users_groups" => ["*"]], where_data=["user_id" => user_id])
group_id = existing_user_group_data[1,"group_id"]
delete!(data,"group_id")
delete!(data,"members")
delete!(data,"contact")
update_table("groups",data, where_data=["id" => group_id])
compile("groups")
elseif mode==3 # Leave
existing_user_group_data = select_from_table(["users_groups" => ["*"]], where_data=["user_id" => user_id])
if size(existing_user_group_data,1)==0
if exist_in_table("groups_requests",["user_id" => user_id])
delete_from_table("groups_requests",["user_id" => user_id])
end
else
delete_from_table("users_groups",["user_id" => user_id])
end
end
return nothing
end
function get_user_groups()
local data_dicts
user_id = get_authentication()
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(groups_ids) ? nothing : groups_ids[1]
data_dicts = []
if isnothing(group_id)
local data
data = select_from_table("groups_requests" => ["*"], where_data = ["user_id" => user_id,"status" => 0])
if size(data,1)==0
data = select_from_table("groups_requests" => ["*"], where_data = ["user_id" => user_id,"status" => 2])
if size(data,1)!=0
data = data[[end],:]
end
end
for row in eachrow(data)
dict = Dict(zip(names(row),values(row)))
if (!ismissing(row["group_id"]))
extra_data = select_from_table("groups" => ["*"], where_data = ["id" => row["group_id"]])
merge!(dict, Dict(zip(names(extra_data[1,:]),values(extra_data[1,:]))))
end
push!(data_dicts, dict)
end
else
group_data = select_from_table("groups" => ["*"], where_data = ["id" => group_id])
ns = names(group_data)
data_dicts = map(x -> Dict(zip(ns,values(x))),eachrow(group_data))
end
return JSON3.write(data_dicts)
end
function get_group_requests()
user_id = get_authentication()
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(groups_ids) ? nothing : groups_ids[1]
data_dicts = []
if !isnothing(group_id)
user_ids = select_from_table("groups_requests" => ["user_id"], where_data = ["group_id" => group_id, "status" => 0])[:,1]
for user2_id in user_ids
email = select_from_table("users" => ["email"], where_data = ["id" => user2_id])[1,1]
push!(data_dicts,Dict("email" => email, "user_id" => user2_id))
end
end
return JSON3.write(data_dicts)
end
function approve_request()
data = copy(jsonpayload())
user_id = get_authentication()
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(groups_ids) ? nothing : groups_ids[1]
members = select_from_table("groups" => ["members"], where_data = ["id" => group_id])[1,1]
dict = Dict("members" => members + 1)
update_table("groups",dict, where_data=["id" => group_id])
update_table("groups_requests",Dict("status" => 1), where_data=["group_id" => group_id, "user_id" => data["user_id"]])
dict_users_groups = Dict("user_id" => data["user_id"], "group_id" => group_id)
insert_into_table("users_groups",dict_users_groups)
return nothing
end
function reject_request()
data = copy(jsonpayload())
user_id = get_authentication()
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(groups_ids) ? nothing : groups_ids[1]
update_table("groups_requests",Dict("status" => 2), where_data=["group_id" => group_id, "user_id" => data["user_id"]])
return nothing
end
function add_verified_groups()
groups_create_requests_verified = select_from_table("groups_requests" => ["*"], where_data = ["group_id" => nothing, "status" => 1])
data = Dict(zip(names(groups_create_requests_verified),groups_create_requests_verified[end,:]))
user_id = data["user_id"]
delete!(data,"group_id")
delete!(data,"user_id")
delete!(data,"id")
delete!(data,"status")
group_id = insert_into_table("groups",data, "RETURNING id")[1,1]
dict_users_groups = Dict("user_id" => user_id, "group_id" => group_id)
insert_into_table("users_groups",dict_users_groups)
delete_from_table("groups_requests",["user_id" => user_id])
end
end

View File

@@ -0,0 +1 @@
<groups-add-component></groups-add-component>

View File

@@ -0,0 +1,60 @@
module PartiesController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication
using JSON3
using SearchLight
using Server.DatabaseSupport, Server.TemplateEditor
controller = "parties"
dict_layouts = Dict(
:parties_add => generate_layout_html("main",controller,"parties_add",libraries=["Leaflet"]),
:parties => generate_layout_html("main",controller,"parties",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const parties_info = Dict(
"en" => Dict(
:title => "LibSoc - Parties",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Партии",
:description => ""
)
)
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
#---Functions---------------------------------------------------------
function parties()
locale = get_locale()
html(:parties,:parties, layout = dict_layouts[:parties], context = @__MODULE__,
title = parties_info[locale][:title],
description = parties_info[locale][:description]
)
end
function parties_add()
locale = get_locale()
html(:parties,:parties_add, layout = dict_layouts[:parties_add], context = @__MODULE__,
title = parties_info[locale][:title],
description = parties_info[locale][:description]
)
end
function parties_add_post()
data = jsonpayload()
insert_into_table("parties_requests",data)
end
end

View File

@@ -0,0 +1 @@
<parties-add-component></parties-add-component>

View File

@@ -0,0 +1,59 @@
module PartnersController
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication
using JSON3
using SearchLight
using Server.DatabaseSupport, Server.TemplateEditor
controller = "partners"
dict_layouts = Dict(
:partners => generate_layout_html("main",controller,"partners",libraries=["Leaflet"]),
:partners_add => generate_layout_html("main",controller,"partners_add",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const partners_info = Dict(
"en" => Dict(
:title => "LibSoc - Partners",
:description => ""
),
"ru" => Dict(
:title => "LibSoc - Партнеры",
:description => ""
)
)
function get_locale()
data = payload()
if :locale in keys(data)
return data[:locale]
else
return "en"
end
end
#---Functions---------------------------------------------------------
function partners()
locale = get_locale()
html(:partners,:partners, layout = dict_layouts[:partners], context = @__MODULE__,
title = partners_info[locale][:title],
description = partners_info[locale][:description]
)
end
function partners_add()
locale = get_locale()
html(:partners,:partners_add, layout = dict_layouts[:partners_add], context = @__MODULE__,
title = partners_info[locale][:title],
description = partners_info[locale][:description]
)
end
function partners_add_post()
data = jsonpayload()
insert_into_table("partners_requests",data)
end
end

View File

@@ -0,0 +1 @@
<partners-add-component></partners-add-component>

View File

@@ -0,0 +1,29 @@
module Users
using SearchLight, SearchLight.Validation, Server.UsersValidator
using SHA
using Random
export User
Base.@kwdef mutable struct User <: AbstractModel
### FIELDS
id::DbId = DbId()
email::String = ""
password::String = ""
google_id::String = ""
confirmation_code::String = ""
verified::Bool = false
end
Validation.validator(u::Type{User}) = ModelValidator([
ValidationRule(:email, UsersValidator.not_empty),
ValidationRule(:email, UsersValidator.unique),
ValidationRule(:password, UsersValidator.not_empty)
])
function hash_password(password::String)
sha256(password) |> bytes2hex
end
end

View File

@@ -0,0 +1,21 @@
module UsersValidator
using SearchLight, SearchLight.Validation, SearchLight.QueryBuilder
function not_empty(field::Symbol, m::T, args::Vararg{Any})::ValidationResult where {T<:AbstractModel}
isempty(getfield(m, field)) && return ValidationResult(invalid, :not_empty, "should not be empty")
ValidationResult(valid)
end
function unique(field::Symbol, m::T, args::Vararg{Any})::ValidationResult where {T<:AbstractModel}
ispersisted(m) && return ValidationResult(valid) # don't validate updates
if SearchLight.count(typeof(m), where("$field = ?", getfield(m, field))) > 0
return ValidationResult(invalid, :unique, "is already used")
end
ValidationResult(valid)
end
end

View File

@@ -0,0 +1,180 @@
label {
font-size: 1.3rem;
font-family: var(--sans-serif);
}
.auth-pane {
position: relative;
padding: 3.4rem;
padding-top: 3.4rem;
padding-bottom: 3.4rem;
width: 30rem;
height: auto;
}
.auth-title {
position: relative;
top: 0.2rem;
margin-bottom: 2.7rem;
}
.auth-label {
display: inline-block;
margin-bottom: 0.3rem;
}
.authEmailInput, .authPasswordInput {
position: relative;
width: 100%;
border-radius: 0.34rem;
color: #353535;
height: 2.73rem;
padding-left: 0.34rem;
}
.authEmailInput {
margin-bottom: 0.682rem;
}
.auth-button {
margin-top: 1.365rem;
height: 3.412rem;
width: 100%;
font-family: var(--sans-serif,sans-serif);
font-size: 1.3rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.512rem;
filter: drop-shadow(0.068rem 0.136rem 0.068rem rgb(0 0 0 / 0.4));
}
.auth-button:active {
background-color: var(--darker-pink);
}
#email-msg,#password-msg {
display: inline;
color:red;
font-family: var(--sans-serif,sans-serif);
}
.auth-line {
margin-top: 1.5rem;
width: 100%;
height: 0.07rem;
border: 0;
border-radius: 0.1rem;
background: black;
}
.auth-methods-group {
display: grid;
grid-template-columns: auto ; /*auto auto*/
justify-content: center;
gap: 2.7rem;
margin-top: 2rem;
}
.auth-methods-group img {
height: auto;
width: 3.4rem;
}
.auth-methods-group> div {
position: relative;
border-radius: 6.8rem;
width: 3.4rem;
height: 3.4rem;
overflow: hidden;
}
#google-btn {
position: absolute;
top: -0.8rem;
left: -0.8rem;
}
#google-logo {
position: relative;
background: white;
pointer-events: none;
}
#google-btn-wrapper {
cursor: pointer;
}
#google-btn div {
position: absolute;
height: 5rem;
width: 5rem;
}
#google-btn iframe {
position: absolute;
height: 10rem;
width: 10rem;
}
.password-field {
position: relative;
}
.eye-icon {
display: block;
position: absolute;
cursor: pointer;
opacity: 0.25;
top: 2.6rem;
right: 0.8rem;
width: 1.7rem;
}
.eye-icon * {
pointer-events: none;
}
#forgot-password {
display: block;
position: relative;
margin-top: 0.5rem;
height: 2rem;
color:#5f5f5f;
margin-left: auto;
width: max-content;
font-family: var(--sans-serif);
font-size: 1.3rem;
}
#remember-me {
position: relative;
display: flex;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
#remember-me-checkbox {
min-height: 1.5rem;
min-width: 1.5rem;
flex: 0;
accent-color: var(--gray);
}
#remember-me label {
position: relative;
margin-top: -0.2rem;
color: #5f5f5f;
}
#content {
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
height: 100%;
min-height: 100vh;
}

View File

@@ -1,21 +1,7 @@
:root { :root {
--light-blue:hsl(195, 67%, 95%); --red:#c52a28;
--darker-pink:hsl(344, 60%, 47%); --gray: #5B6970;
--pink:hsl(344, 73%, 57%);
--dark-green:hsl(176, 63%, 25%);
--green:hsl(147, 33%, 60%);
--orange:hsl(30, 97%, 72%);
--light-orange: hsl(19, 76%, 72%);
--dark-brown:hsl(23, 47%, 20%);
--brown:hsl(23, 47%, 30%);
--light-brown: hsl(23, 47%, 50%);
--dark-pink:hsl(343, 39%, 16%);
--red:hsl(359, 72%, 61%);
--dark-blue:hsl(217, 25%, 16%);
--grey-blue:hsl(223, 13%, 22%);
--cream:hsl(34, 43%, 90%);
--dark-cream:hsl(33, 26%, 84%);
--sans-serif: "OpenSans"; --sans-serif: "OpenSans";
--serif: "Lora"; --serif: "Lora";
} }
@@ -48,12 +34,10 @@ body {
#content { #content {
position: relative; position: relative;
display: flex; display: grid;
flex-direction: column; grid-template-rows: max-content auto max-content;
justify-content: space-between;
height: 100%; height: 100%;
min-height: 100vh; min-height: 100vh;
flex-grow: 1;
} }
/*---Fonts---------------------------------------------------------*/ /*---Fonts---------------------------------------------------------*/
@@ -310,7 +294,7 @@ input[type="text"],input[type="email"],input[type="password"],input[type="number
border-color: black; border-color: black;
border: black solid 0.063rem; border: black solid 0.063rem;
padding-left: 0.5rem; padding-left: 0.5rem;
font: 1.3rem var(--serif, serif); font: 1.15rem var(--serif, serif);
} }
input[type="text"] { input[type="text"] {
@@ -475,9 +459,8 @@ input[type=number]::-webkit-outer-spin-button {
.pane { .pane {
background: white; background: white;
border: 0; border: 0.1rem solid rgb(187, 187, 187);
border-radius: 0.635rem; border-radius: 0.635rem;
box-shadow: 0 0 0.314rem rgb(187, 187, 187);
} }
.pane-container { .pane-container {

View File

@@ -0,0 +1,268 @@
/* Header */
#navbar{
position: relative;
top: 0;
width: min(100%,116rem);
z-index: 1000000000;
height: 5.26rem;
padding-left: 0rem;
padding-right: 0rem;
}
#navbar * {
font-family: var(--sans-serif, sans-serif);
}
/* Logo */
#logo-container {
display: flex;
position: absolute;
margin-left: 1rem;
height: 100%;
max-height: 5.26rem;
color: black;
z-index: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
#navbar-logo {
height: 3.5rem;
width: 3.5rem;
object-fit: contain;
border-radius: 10rem;
}
#navbar-logo-text {
position: relative;
word-wrap: normal;
height: 100%;
line-height: 400%;
font-size: 1.4rem;
color: #292222;
font-family: var(--sans-serif, sans-serif);
font-weight: 400;
padding-left: 1.2rem;
}
/* Nav menu */
#nav {
position: fixed;
width: 100%;
height: 100%;
background-color: white;
overflow: hidden;
z-index: 0;
}
#menu > li > a, .options-button {
display: block;
padding: 1.2rem;
padding-top: 1rem;
padding-bottom: 1rem;
color: black;
font-size: 1.4rem;
}
#menu > li > a:active{
}
#menu li {
list-style-type: none;
}
#nav{
max-height: 0;
/*transition: max-height .5s ease-out;*/
}
/* Menu Icon */
#hamb{
position: absolute;
cursor: pointer;
right: 0rem;
padding: 2.8rem 2rem;
z-index: 9999;
}/* Style label tag */
#hamb-line {
background: black;
display: block;
height: 2px;
position: relative;
width: 24px;
} /* Style span tag */
#hamb-line::before,
#hamb-line::after{
background: black;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
#hamb-line::before{
top: 5px;
}
#hamb-line::after{
top: -5px;
}
#side-menu {
display: none;
} /* Hide checkbox */
/* Toggle menu icon */
#side-menu:checked ~ nav {
display: block;
max-height: 100%;
padding-top: 5.625rem;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb {
position: fixed;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb #hamb-line {
background: transparent;
}
#side-menu:checked ~ #hamb #hamb-line::before {
transform: rotate(-45deg);
top: 0;
}
#side-menu:checked ~ #hamb #hamb-line::after {
transform: rotate(45deg);
top: 0;
}
/* Options */
.options-dropdown {
position: absolute;
display: none;
top: 5.6rem;
right: 1.8rem;
border: #404040 solid 0.1rem;
background-color: white;
z-index: 10;
}
.options-dropdown button, .options-dropdown a {
display: block;
font-family: var(--sans-serif,sans-serif);
font-size: 1.2rem;
width: 100%;
padding: 1rem;
text-align: left;
}
.options-dropdown button:hover, .options-dropdown a:hover {
background-color: var(--red);
color: white;
}
.options-button {
width: 100%;
text-align: left;
}
/* Localization */
#locales {
position: relative;
}
#locales button {
width: 100%;
text-align: left;
height: 4rem;
}
#locales button:hover {
opacity: 0.5;
}
#locales-img {
position: relative;
top: 0rem;
height: 2rem;
margin-left: 1.2rem;
}
/*
#options-dropdown>:first-child {
padding-bottom: 0.5rem;
}
#options-dropdown>:nth-child(2) {
padding-top: 0.5rem;
}
*/
/* Responsiveness */
@media only screen and (min-width: 1200px) {
#navbar {
position: relative;
width: min(100%,116rem);
left: 50%;
transform: translateX(-50%);
padding-right: 4rem;
padding-left: 4rem;
}
#nav {
max-height: none;
top: 0;
position: relative;
float: right;
width: fit-content;
background-color: transparent;
overflow: visible;
}
#side-menu:checked ~ nav {
padding-top: 0;
}
#menu li {
float: left;
}
#menu > li > a:hover, .options-button:hover, #navbar-logo-text:hover {
color: rgb(127, 127, 127);
}
#menu > li > a, .options-button {
padding: 0.9rem;
padding-top: 1.9rem;
padding-bottom: 1.9rem;
}
#hamb {
display: none;
}
#locales {
position: relative;
margin-right: 1.8rem;
}
#locales-img {
top: 0.9rem;
}
}

View File

@@ -0,0 +1,55 @@
/*
#notifications-div>button {
cursor: pointer;
margin-left: 0.341rem;
}
#notifications-div>:nth-child(1) {
width: 1.706rem;
height: 1.706rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>:nth-child(2) {
width: 2.047rem;
height: 2.047rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>:nth-child(3) {
width: 2.389rem;
height: 2.389rem;
background-color: #ccc;
border-radius: 100%;
}
#notifications-div>button>div {
cursor: pointer;
margin: auto;
background-color: white;
border-radius: 100%;
}
#notifications-div>:nth-child(1)>div {
width: 1.228rem;
height: 1.228rem;
}
#notifications-div>:nth-child(2)>div {
width: 1.57rem;
height: 1.57rem;
}
#notifications-div>:nth-child(3)>div {
width: 1.843rem;
height: 1.843rem;
}
*/

View File

@@ -1,76 +0,0 @@
export let communes = [
{
location: [["Canada","Montreal"],[45.55541047232767, -73.42859611607271]],
status: "forming",
members: 2,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Denmark"],[55.915625218626275, 9.673445220831253]],
status: "forming",
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Estonia","KohtlaJarve"],[59.409521829709504, 27.288415912535914]],
status: "forming",
members: 2,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Latvia"],[56.934159375258055, 25.269099001330265]],
status: "forming",
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
}
]
export let communesByCountry = {}
for (let c of communes) {
let country = c.location[0][0]
if (country in communesByCountry) {
communesByCountry[country].push(c)
}
else {
communesByCountry[country] = [c]
}
}
export let communesMarkersLayer = L.layerGroup()
export function addMarkersCommunes(map,content) {
for (let g of communes) {
let coordinates
let text = "<b>"+content["Commune"]+"</b><br>"
for (let field in g) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact[0] + "' target='_blank' rel=noreferrer>" + content[g.contact[1]]+ "</a>"
}
else if (field=="location") {
let location = g[field][0]
let locationString = location.map(x => content[x]).join(", ")
text += fieldText + locationString + "<br>"
coordinates = g[field][1]
}
else if (field=="status") {
text += fieldText + content[g[field]] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
var markerIcon = new L.Icon({
iconUrl: 'https://www.libsoc.org/img/common/markers/marker-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
let marker = L.marker(coordinates, {icon: markerIcon})
marker.addTo(communesMarkersLayer).bindPopup(text)
}
communesMarkersLayer.addTo(map)
}

View File

@@ -1,73 +0,0 @@
export let coops = [
{
logo: "chiron_logo",
name: "Chiron Health",
location: [["Estonia","KohtlaJarve"],[59.41038769769602, 27.287802936242034]],
market: "wellnessAndHealth",
workers: 2,
status: "inDevelopment",
website: [ "https://www.chrn.health/", "chrn.health"],
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"],
description: "descriptionChironHealth"
},
{
logo: "kuusk_logo",
name: "Kuusk",
location: [["Estonia","KohtlaJarve"],[59.399947051803004, 27.277159931677055]],
market: "herbalTeas",
workers: 1,
status: "inDevelopment",
website: "-",
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"],
description: "kuuskDescription"
}
]
export let coopsByCountry = {}
for (let g of coops) {
let country = g.location[0][0]
if (country in coopsByCountry) {
coopsByCountry[country].push(g)
}
else {
coopsByCountry[country] = [g]
}
}
export let coopsMarkersLayer = L.layerGroup()
export function addMarkersCoops(map,content) {
for (let g of coops) {
let coordinates
let text = "<b>"+content["Cooperative"]+"</b><br>"
for (let field in g) {
let fieldText
if (field!="logo") {
fieldText = content[field] + ": "
}
if (field=="logo") {
text += "<picture><source srcset=" + "/img/coops/" + g.logo + ".webp><source srcset='/img/coops/" + g.logo + ".png'><img alt='logo' style='position: relative; max-height: 5rem; max-width: 100%; margin: auto;'></picture>" + "<br>"
}
else if (field=="contact") {
text += fieldText + "<a href='https://www." + g.contact[0] + "' target='_blank' rel=noreferrer>" + content[g.contact[1]] + "</a>" + "<br>"
}
else if (field=="website") {
text += fieldText + "<a href='" + g.website[0] + "' target='_blank' rel=noreferrer>" + g.website[1] + "</a>" + "<br>"
}
else if (field=="location") {
let location = g[field][0]
let locationString = location.map(x => content[x]).join(", ")
text += fieldText + locationString + "<br>"
coordinates = g[field][1]
}
else if (field=="market" || field=="status" || field=="description") {
text += fieldText + content[g[field]] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
L.marker(coordinates).addTo(coopsMarkersLayer).bindPopup(text)
}
coopsMarkersLayer.addTo(map)
}

View File

@@ -1,145 +0,0 @@
export let groups = [
{
location: [["Canada","Halifax"],[44.65166135030067, -63.59289968306866]],
members: 2,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Denmark","Copenhagen"],[55.6840661150132, 12.557133959514688]],
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Denmark","Kolding"], [55.49261908652738, 9.470268969851743]],
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Estonia","KohtlaJarve"], [59.40629447076191, 27.280605339416322]],
members: 3,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Germany","Wiesbaden"], [50.07459620869791, 8.234984059337897]],
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Greece","Athens"], [37.94877252621736, 23.677622972996158]],
members: 1,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["Ireland"], [53.280192832733576, -7.688103518964818]],
members: 6,
contact: ["https://discord.gg/4BUau4AZre","DiscordInviteLink"]
},
{
location: [["Latvia","Riga"], [56.94748425529816, 24.003027136431925]],
members: 2,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
},
{
location: [["USA","Florida"], [26.945024427155868, -81.22162645059898]],
members: 2,
contact: ["https://discord.gg/Qk8KUk787z","DiscordInviteLink"]
}
]
export let groupsByCountry = {}
for (let g of groups) {
let country = g.location[0][0]
if (country in groupsByCountry) {
groupsByCountry[country].push(g)
}
else {
groupsByCountry[country] = [g]
}
}
export let groupsMarkersLayer = L.layerGroup()
let groupsMarkersLayerOut = L.layerGroup()
let groupsMarkersLayerIn = L.layerGroup()
let contactGeneral =["https://discord.gg/4BUau4AZre","DiscordInviteLink"]
function addMarkersToLayer(g,layer,content) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field in g) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact[0] + "' target='_blank' rel=noreferrer>" + content[g.contact[1]] + "</a>"
}
else if (field=="location") {
let location = g[field][0]
let locationString = location.map(x => content[x]).join(", ")
text += fieldText + locationString + "<br>"
coordinates = g[field][1]
}
else {
text += fieldText + g[field] + "<br>"
}
}
var markerIcon = new L.Icon({
iconUrl: 'https://www.libsoc.org/img/common/markers/marker-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
let marker = L.marker(coordinates, {icon: markerIcon})
marker.addTo(layer).bindPopup(text)
}
export function addMarkersGroups(map,content) {
for (let g of groups) {
addMarkersToLayer(g,groupsMarkersLayerIn,content)
}
for (let gs of Object.values(groupsByCountry)) {
if (gs.length==1) {
let g = {...gs[0]}
g.location[0][1] = ""
addMarkersToLayer(g,groupsMarkersLayerOut,content)
}
else {
let locationName = [gs[0].location[0][0]]
let locationCoordinates = [0,0]
let members = 0
let contact = gs[0].contact
for (let g of gs) {
locationCoordinates[0] += g.location[1][0]
locationCoordinates[1] += g.location[1][1]
members += g.members
if (g.contact[0]!=gs[0].contact[0]) {
contact = contactGeneral
}
}
locationCoordinates[0] = locationCoordinates[0]/gs.length
locationCoordinates[1] = locationCoordinates[1]/gs.length
let gNew = {
location: [locationName,locationCoordinates],
members: members,
contact: contact
}
addMarkersToLayer(gNew,groupsMarkersLayerOut,content)
}
}
groupsMarkersLayerOut.addTo(groupsMarkersLayer)
groupsMarkersLayer.addTo(map)
map.on("zoomend", () => onZoomEnd(map))
}
function onZoomEnd(map) {
let zoomLevel = map.getZoom()
if (zoomLevel==3) {
groupsMarkersLayer.removeLayer(groupsMarkersLayerIn)
groupsMarkersLayerOut.addTo(groupsMarkersLayer)
}
else if (zoomLevel==4) {
groupsMarkersLayer.removeLayer(groupsMarkersLayerOut)
groupsMarkersLayerIn.addTo(groupsMarkersLayer)
}
}

View File

@@ -0,0 +1,163 @@
import {getData, sendData} from "/js/libraries/serverTools.js"
export function getUser(user,loaded,callbackOuter) {
let callback = function(response) {
Object.assign(user,JSON.parse(response))
if(callbackOuter!=undefined) {
callbackOuter()
}
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-user",callback)
}
export function changeUser(name,value,user) {
if (user[name]!=value && user[name]!=undefined) {
user[name] = value
let data = new Object();
data[name] = value
sendData("/xx/change-user",data)
}
}
export function changePasswordVisibility(button) {
let input = button.previousElementSibling
let type = input.type
if (type=="text") {
input.type = "password";
button.style.opacity = 0.25
}
else {
input.type = "text";
button.style.opacity = 1
}
}
export function checkEmail(email,msg) {
if (email.includes("@")) {
return true
}
else {
msg.innerHTML = "must contain '@'"
return false
}
}
export function checkPassword(password,msg) {
let passwordLength = password.length
if (passwordLength<8) {
msg.innerHTML = "must be 8 characters"
return false
}
let numNumbers = password.match(/\d/g)?.length || 0;
if (numNumbers<1) {
msg.innerHTML = "mush have digits"
return false
}
let numLetters = password.match(/\D/g)?.length || 0;
if (numLetters<2) {
msg.innerHTML = "must have letters"
return false
}
return true
}
export function redirectLogged() {
let callback = function(responseText) {
let response = JSON.parse(responseText)
if (response) {
window.location.href = "/";
}
}
getData("/xx/check-login",callback)
}
export function redirectNotLogged() {
let callback = function(responseText) {
let response = JSON.parse(responseText)
if (!response) {
window.location.href = "/";
}
}
getData("/xx/check-login",callback)
}
// Redirect to the landing page
export function toLandingPage(response) {
if (response!=0) {
window.location.href = "/";
}
}
// Redirect to the dashboard page
export function toDashboard() {
window.location.href = "/";
}
// Process log in
export function processLoginResponse(response,msgs,remember) {
if (response==0) {
msgs.email.innerHTML = "not found"
}
else if (response==1) {
msgs.password.innerHTML = "is wrong"
}
else {
if (remember) {
let date = new Date()
date.setMonth(date.getMonth()+1)
date = date.toUTCString()
document.cookie = "__genierememberme=; expires=" + date + "; path=/;SameSite=Lax";
}
toDashboard()
}
}
// Log in
export function login(msgs,inputs) {
msgs.email.innerHTML = ""
msgs.password.innerHTML = ""
let data = {email: inputs.email.value, password: inputs.password.value, remember: inputs.remember.checked}
sendData('/xx/login-post', data, (response) => processLoginResponse(response,msgs,inputs.remember.checked))
}
// Process sign in
function processSignupResponse(response,msgs) {
if (response) {
toDashboard()
}
else {
msgs.email.innerHTML = "already exists"
}
}
// Sign up
export function signup(msgs,inputs) {
msgs.email.innerHTML = ""
let email = inputs.email.value
let password = inputs.password.value
if (checkEmail(email,msgs.email)==false) {
return
}
if (checkPassword(password,msgs.password)==false) {
return
}
let data = {email: email, password: password}
sendData('/xx/signup-post', data, (response) => processSignupResponse(response,msgs))
}
export function confirmEmail(msg,code,callback) {
msg.innerHTML = ""
sendData('xx/confirm-email',code,callback)
}
// Log out
export function logout() {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "/logout", false ); // false for synchronous request
xmlHttp.send( null );
window.location.href = "/";
}

View File

@@ -0,0 +1,102 @@
let contactGeneral =["https://discord.gg/4BUau4AZre","DiscordInviteLink"]
export function translate(content, x) {
let out = content[x]
if (out==undefined) {
return x
}
else {
return out
}
}
function addMarkersToLayer(g,layer,content,locale,addPinContent,markerColor,options) {
let {text,coordinates} = addPinContent(g,content,locale)
var markerIcon = new L.Icon({
iconUrl: 'https://www.libsoc.org/img/common/markers/marker-' + markerColor + '.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
let marker = L.marker(coordinates, {icon: markerIcon})
marker.id = g.id
marker.members = g.members
marker.contact = g.contact
marker.addTo(layer).bindPopup(text)
if (options.pinCallback!=undefined) {
marker.on('click', (event) => options.pinCallback(marker,event))
}
}
export function addMarkersEntries(entries,entriesByCountry,map,content,locale,addPinContent,markerColor,options) {
let entriesMarkersLayer = L.layerGroup()
let entriesMarkersLayerOut = L.layerGroup()
let entriesMarkersLayerIn = L.layerGroup()
for (let g of entries) {
if (g.country!="Online" && g.country!="Worldwide") {
addMarkersToLayer(g,entriesMarkersLayerIn,content,locale,addPinContent,markerColor,options)
}
}
if (options.enableCountryGrouping) {
for (let gs of Object.values(entriesByCountry)) {
if (gs.length==1) {
let g = {...gs[0]}
g.country = [g.country]
if (g.country!="Online" && g.country!="Worldwide") {
addMarkersToLayer(g,entriesMarkersLayerOut,content,locale,addPinContent,markerColor,options)
}
}
else {
if (gs[0].country!="Online" && gs[0].country!="Worldwide") {
let locationName = gs[0].country
let locationCoordinates = [0,0]
let members = 0
let contact = gs[0].contact
for (let g of gs) {
locationCoordinates[0] += g.latitude
locationCoordinates[1] += g.longitude
members += g.members
if (g.contact[0]!=gs[0].contact[0]) {
contact = contactGeneral
}
}
locationCoordinates[0] = locationCoordinates[0]/gs.length
locationCoordinates[1] = locationCoordinates[1]/gs.length
let gNew = {
country: locationName,
latitude: locationCoordinates[0],
longitude: locationCoordinates[1],
members: members,
contact: contact
}
addMarkersToLayer(gNew,entriesMarkersLayerOut,content,locale,addPinContent,markerColor,options)
}
}
}
entriesMarkersLayerOut.addTo(entriesMarkersLayer)
map.on("zoomend", () => onZoomEnd(map,entriesMarkersLayer,entriesMarkersLayerOut,entriesMarkersLayerIn))
}
else {
entriesMarkersLayerIn.addTo(entriesMarkersLayer)
}
entriesMarkersLayer.addTo(map)
return entriesMarkersLayer
}
function onZoomEnd(map,entriesMarkersLayer,entriesMarkersLayerOut,entriesMarkersLayerIn) {
let zoomLevel = map.getZoom()
if (zoomLevel==3) {
entriesMarkersLayer.removeLayer(entriesMarkersLayerIn)
entriesMarkersLayerOut.addTo(entriesMarkersLayer)
}
else if (zoomLevel==4) {
entriesMarkersLayer.removeLayer(entriesMarkersLayerOut)
entriesMarkersLayerIn.addTo(entriesMarkersLayer)
}
}

View File

@@ -1,17 +1,17 @@
// Get data from server // Get data from server
export function getData(path,callback) { export function getData(path,callback) {
var xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.overrideMimeType("application/json"); xhr.overrideMimeType("application/json")
xhr.open('GET', path, true); xhr.open('GET', path, true)
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == "200") { if (xhr.readyState == 4 && xhr.status == "200") {
if (callback !== undefined) { if (callback !== undefined) {
callback(xhr.responseText); callback(xhr.responseText)
} }
} }
}; };
xhr.send(null); xhr.send()
} }
// Parse JSON from given path into a given variable under a given key // Parse JSON from given path into a given variable under a given key
@@ -35,6 +35,8 @@ export function sendData(route,data,callback) {
callback(xhr.responseText) callback(xhr.responseText)
} }
} else { } else {
callback(false)
console.log("Request gave an error")
// Oh no! There has been an error with the request! // Oh no! There has been an error with the request!
} }
} }
@@ -55,6 +57,8 @@ export function sendText(route,data,callback) {
callback(xhr.responseText) callback(xhr.responseText)
} }
} else { } else {
callback(false)
console.log("Request gave an error")
// Oh no! There has been an error with the request! // Oh no! There has been an error with the request!
} }
} }

View File

@@ -0,0 +1,178 @@
import { translate } from "/js/libraries/mapTools.js"
export function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
export function addCommunePinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Commune"]+"</b><br>"
for (let field of ["location","status","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else if (field=="status") {
text += fieldText + content[g[field]] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
export function addCoopPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Cooperative"]+"</b><br>"
for (let field of ["logo","name","location","workers","status","website","contact","description"]) {
let fieldText
if (field!="logo") {
fieldText = content[field] + ": "
}
if (field=="logo") {
text += "<picture><source srcset=" + "/img/coops/" + g.logo + ".webp><source srcset='/img/coops/" + g.logo + ".png'><img alt='logo' style='position: relative; max-height: 5rem; max-width: 100%; margin: auto;'></picture>" + "<br>"
}
else if (field=="contact") {
text += fieldText + "<a href='https://www." + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>" + "<br>"
}
else if (field=="website") {
text += fieldText + "<a href='" + g.website + "' target='_blank' rel=noreferrer>" + g.website + "</a>" + "<br>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else if (field=="market" || field=="status" || field=="description") {
text += fieldText + g[field] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
export function addPartyPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Cooperative"]+"</b><br>"
for (let field of ["logo","name","location","website","contact","description"]) {
let fieldText
if (field!="logo") {
fieldText = content[field] + ": "
}
if (field=="logo") {
text += "<picture><source srcset=" + "/img/parties/" + g.logo + ".webp><source srcset='/img/parties/" + g.logo + ".png'><img alt='logo' style='position: relative; max-height: 5rem; max-width: 100%; margin: auto;'></picture>" + "<br>"
}
else if (field=="link") {
text += fieldText + "<a href='" + g.link + "' target='_blank' rel=noreferrer>" + g.link + "</a>" + "<br>"
}
else if (field=="website") {
text += fieldText + "<a href='" + g.website + "' target='_blank' rel=noreferrer>" + g.website + "</a>" + "<br>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else if (field=="description") {
text += fieldText + content[g[field]] + "<br>"
}
else if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>" + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
export function addPartnersPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Partner"]+"</b><br>"
for (let field of ["logo","name","location","website","contact","description"]) {
let fieldText
if (field!="logo") {
fieldText = content[field] + ": "
}
if (field=="logo") {
text += "<picture><source srcset=" + "/img/coops/" + g.logo + ".webp><source srcset='/img/coops/" + g.logo + ".png'><img alt='logo' style='position: relative; max-height: 5rem; max-width: 100%; margin: auto;'></picture>" + "<br>"
}
else if (field=="contact") {
text += fieldText + "<a href='https://www." + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>" + "<br>"
}
else if (field=="website") {
text += fieldText + "<a href='" + g.website + "' target='_blank' rel=noreferrer>" + g.website + "</a>" + "<br>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else if (field=="description") {
text += fieldText + g[field] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}

View File

@@ -1,68 +0,0 @@
export let parties = [
{
logo: "roots",
name: "Roots",
location: ["Ireland",[52.98479517270413, -7.649233227534782]],
//members: 6,
link: "https://discord.gg/pSTMacJZsK",
description: "descriptionRoots"
}
]
export let partiesByCountry = {}
for (let g of parties) {
let country = g.location[0]
if (country in partiesByCountry) {
partiesByCountry[country].push(g)
}
else {
partiesByCountry[country] = [g]
}
}
export let partiesMarkersLayer = L.layerGroup()
export function addMarkersParties(map,content) {
for (let g of parties) {
let coordinates
let text = "<b>"+content["Party"]+"</b><br>"
for (let field in g) {
let fieldText
if (field!="logo") {
fieldText = content[field] + ": "
}
if (field=="logo") {
text += "<picture><source srcset=" + "/img/parties/" + g.logo + ".webp><source srcset='/img/parties/" + g.logo + ".png'><img alt='logo' style='position: relative; max-height: 5rem; max-width: 100%; margin: auto;'></picture>" + "<br>"
}
else if (field=="link") {
text += fieldText + "<a href='" + g.link + "' target='_blank' rel=noreferrer>" + g.link + "</a>" + "<br>"
}
else if (field=="website") {
text += fieldText + "<a href='" + g.website + "' target='_blank' rel=noreferrer>" + g.website + "</a>" + "<br>"
}
else if (field=="location") {
let location = g[field][0]
let locationString = location
text += fieldText + locationString + "<br>"
coordinates = g[field][1]
}
else if (field=="description") {
text += fieldText + content[g[field]] + "<br>"
}
else {
text += fieldText + g[field] + "<br>"
}
}
var markerIcon = new L.Icon({
iconUrl: 'https://www.libsoc.org/img/common/markers/marker-gold.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
let marker = L.marker(coordinates, {icon: markerIcon})
marker.addTo(partiesMarkersLayer).bindPopup(text)
}
partiesMarkersLayer.addTo(map)
}

View File

@@ -1,21 +0,0 @@
export let partners = [
{
name: "Gaia's Fall",
type: "typeGaiasFall",
location: [["Online"],[0,0]],
link: "https://discord.libsoc.org/invite/",
description: "descriptionGaiasFall",
logo: "gaias_fall"
}
]
export let partnersByCountry = {}
for (let g of partners) {
let country = g.location[0][0]
if (country in partnersByCountry) {
partnersByCountry[country].push(g)
}
else {
partnersByCountry[country] = [g]
}
}

View File

@@ -10,5 +10,6 @@
"forming": "forming", "forming": "forming",
"WhatsAppInviteLink": "WhatsApp invite link", "WhatsAppInviteLink": "WhatsApp invite link",
"DiscordInviteLink": "Discord invite link", "DiscordInviteLink": "Discord invite link",
"Commune": "Commune" "Commune": "Commune",
"map-prompt": "Want to appear on our map? Contact us!"
} }

View File

@@ -18,6 +18,7 @@
"descriptionChironHealth": "Chiron Health is a health platform providing courses and services on the topics of nutrition, exercise, sleep and mental wellbeing.", "descriptionChironHealth": "Chiron Health is a health platform providing courses and services on the topics of nutrition, exercise, sleep and mental wellbeing.",
"herbalTeas": "herbal teas", "herbalTeas": "herbal teas",
"kuuskDescription": "Kuusk is an online store that sells herbal teas from exclusively local wild plants, as well as an online gathering course.", "kuuskDescription": "Kuusk is an online store that sells herbal teas from exclusively local wild plants, as well as an online gathering course.",
"Cooperative": "Cooperative" "Cooperative": "Cooperative",
"map-prompt": "Want to appear on our map? Contact us!"
} }

View File

@@ -1,21 +1,3 @@
{ {
"map-prompt": "Want to appear on our map? Contact us!",
"Online": "Online",
"Denmark": "Denmark",
"Estonia": "Estonia",
"Greece": "Greece",
"Latvia": "Latvia",
"Canada": "Canada",
"Germany": "Germany",
"Copenhagen": "Copenhagen",
"Ireland": "Ireland",
"USA": "USA",
"Montreal": "Montreal",
"Kolding": "Kolding",
"KohtlaJarve": "Kohtla-Järve",
"Athens": "Athens",
"Riga": "Riga",
"Halifax": "Halifax",
"Wiesbaden": "Wiesbaden",
"Florida": "Florida"
} }

View File

@@ -8,5 +8,6 @@
"contact": "Contact", "contact": "Contact",
"DiscordInviteLink": "Discord invite link", "DiscordInviteLink": "Discord invite link",
"WhatsAppInviteLink": "WhatsApp invite link", "WhatsAppInviteLink": "WhatsApp invite link",
"Group": "Group" "Group": "Group",
"map-prompt": "Want to appear on our map? Contact us!"
} }

View File

@@ -1,5 +1,5 @@
{ {
"top": "Our organization is a decentralized federation build upon the principle of free association. It consists of many groups of people united around a cause of bringing down exploitative politico-economic systems. We aim to replace them with libertarian socialist systems, with the goal of creating an equitable, democratic and sustainable world by stopping exploitation of humans and nature.", "top": "Our organization is a decentralized federation build upon the principle of free association. It consists of many groups of people united around a cause of bringing down exploitative politico-economic systems. We aim to replace them with libertarian socialist systems based on decentralization, direct democracy and worker-ownership of the means of production with the goal of creating an equitable, democratic and sustainable world by stopping exploitation of humans and nature.",
"groupsTitle": "GROUPS", "groupsTitle": "GROUPS",
"groupsText": "We organize groups for the purposes of education, advocacy, and mutual aid. Our objective is to demonstrate how the current politico-economic systems detrimentally impact our well-being, present alternative approaches, and engage in mutual aid to alleviate the challenges of living under capitalism.", "groupsText": "We organize groups for the purposes of education, advocacy, and mutual aid. Our objective is to demonstrate how the current politico-economic systems detrimentally impact our well-being, present alternative approaches, and engage in mutual aid to alleviate the challenges of living under capitalism.",
"communesTitle": "COMMUNES", "communesTitle": "COMMUNES",

View File

@@ -87,7 +87,7 @@ Under capitalism, atomization occurs due to several interconnected factors:
<b>Individualism and Competition:</b> Capitalism places a strong emphasis on individualism and competition, promoting the pursuit of self-interest and personal gain. This focus on individual success leads to a sense of isolation as people prioritize their own needs and goals over collective well-being. The constant competition for resources and opportunities further fragments society, fostering a mindset of "every person for themselves." <b>Individualism and Competition:</b> Capitalism places a strong emphasis on individualism and competition, promoting the pursuit of self-interest and personal gain. This focus on individual success leads to a sense of isolation as people prioritize their own needs and goals over collective well-being. The constant competition for resources and opportunities further fragments society, fostering a mindset of "every person for themselves."
<b>Market Forces and Commodification:</b> In capitalist systems, nearly every aspect of life is subjected to market forces. Commodification occurs when goods, services, and even human relationships are reduced to exchangeable commodities. This commodification mentality erodes social bonds and interpersonal relationships, as people are encouraged to view others primarily as potential buyers or sellers rather than as fellow human beings. <b>Market Forces and Commodification:</b> In capitalist systems, nearly every aspect of life is subjected to market forces. Commodification occurs when goods, services, and even human relationships are reduced to exchangeable commodities. This commodification mentality erodes social bonds and interpersonal relationships, as people are encouraged to view others primarily as potential buyers or sellers rather than as fellow human beings.
<b>Consumer Culture:</b> Capitalism promotes a consumer culture that emphasizes the pursuit of material possessions and immediate gratification. This culture fosters a sense of individualistic consumption, where personal happiness and identity are tied to the acquisition of goods and services. The constant pursuit of consumerism isolates individuals as they prioritize material accumulation over social connections and shared experiences. <b>Consumer Culture:</b> Capitalism promotes a consumer culture that emphasizes the pursuit of material possessions and immediate gratification. This culture fosters a sense of individualistic consumption, where personal happiness and identity are tied to the acquisition of goods and services. The constant pursuit of consumerism isolates individuals as they prioritize material accumulation over social connections and shared experiences.
</b>Social Inequalities:</b> Capitalist systems perpetuate social inequalities based on wealth, class, race, and gender. These inequalities result in marginalization, discrimination, and exclusion of certain groups from full participation in society. Such divisions further contribute to social atomization by creating barriers to collective action and fostering distrust among different social groups. <b>Social Inequalities:</b> Capitalist systems perpetuate social inequalities based on wealth, class, race, and gender. These inequalities result in marginalization, discrimination, and exclusion of certain groups from full participation in society. Such divisions further contribute to social atomization by creating barriers to collective action and fostering distrust among different social groups.
Atomization under capitalism serves as a powerful tool that prevents us from organizing and challenging the systemic inequalities and injustices inherent in the capitalist system. Atomization under capitalism serves as a powerful tool that prevents us from organizing and challenging the systemic inequalities and injustices inherent in the capitalist system.
### Externalities ### Externalities
Externalities refer to the unintended consequences of economic activities that are not reflected in the prices of goods and services. These consequences can be positive or negative and are often borne by individuals or communities who are not directly involved in the economic transactions. Externalities refer to the unintended consequences of economic activities that are not reflected in the prices of goods and services. These consequences can be positive or negative and are often borne by individuals or communities who are not directly involved in the economic transactions.

View File

@@ -2,10 +2,12 @@
"orgName": "Libertarian socialists", "orgName": "Libertarian socialists",
"manifesto": "Manifesto", "manifesto": "Manifesto",
"joinUs": "Join us", "joinUs": "Join us",
"aboutUs": "About us", "initiatives": "Initiatives",
"groups": "Groups", "groups": "Groups",
"communes": "Communes", "communes": "Communes",
"cooperatives": "Cooperatives", "cooperatives": "Cooperatives",
"parties": "Parties", "parties": "Parties",
"partners": "Partners" "partners": "Partners",
"login": "Login",
"profile": "Profile"
} }

View File

@@ -5,8 +5,10 @@
"name": "Name", "name": "Name",
"location": "Location", "location": "Location",
"members": "Members", "members": "Members",
"link": "Link", "website": "Website",
"contact": "Contact",
"description": "Description", "description": "Description",
"descriptionRoots": "We are a left libertarian organization based in Ireland. We have a focus on rebuilding the country's infrastructure, sense of governance, housing, agricultural industries and youth affairs.", "descriptionRoots": "We are a left libertarian organization based in Ireland. We have a focus on rebuilding the country's infrastructure, sense of governance, housing, agricultural industries and youth affairs.",
"Party": "Party" "Party": "Party",
"map-prompt": "Want to appear on our map? Contact us!"
} }

View File

@@ -5,10 +5,11 @@
"subheading2": "Online", "subheading2": "Online",
"name": "Name", "name": "Name",
"location": "Location", "location": "Location",
"type": "Type", "website": "Website",
"link": "Link", "contact": "Contact",
"description": "Description", "description": "Description",
"typeGaiasFall": "a place for discussions", "typeGaiasFall": "a place for discussions",
"descriptionGaiasFall": "Gaia's Fall is a server that promotes Solarpunk ideals, environmentalism, anarchism, and anti-capitalism. We encourage civil debates, discussions of theories and possibilities, and the creation of communities focused on shaping a better world." "descriptionGaiasFall": "Gaia's Fall is a server that promotes Solarpunk ideals, environmentalism, anarchism, and anti-capitalism. We encourage civil debates, discussions of theories and possibilities, and the creation of communities focused on shaping a better world.",
"map-prompt": "Want to appear on our map? Contact us!"
} }

View File

@@ -9,5 +9,6 @@
"forming": "формируется", "forming": "формируется",
"WhatsAppInviteLink": "WhatsApp ссылка", "WhatsAppInviteLink": "WhatsApp ссылка",
"DiscordInviteLink": "Discord ссылка", "DiscordInviteLink": "Discord ссылка",
"Commune": "Коммуна" "Commune": "Коммуна",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -18,5 +18,6 @@
"descriptionChironHealth": "Chiron Health — это платформа о здоровье, предлагающая курсы и услуги по вопросам питания, физических упражнений, сна и психического благополучия", "descriptionChironHealth": "Chiron Health — это платформа о здоровье, предлагающая курсы и услуги по вопросам питания, физических упражнений, сна и психического благополучия",
"herbalTeas": "травяные чаи", "herbalTeas": "травяные чаи",
"kuuskDescription": "Kuusk — интернет-магазин, в котором продаются травяные чаи исключительно из местных дикорастущих растений, а также онлайн-курс по собирательству.", "kuuskDescription": "Kuusk — интернет-магазин, в котором продаются травяные чаи исключительно из местных дикорастущих растений, а также онлайн-курс по собирательству.",
"Cooperative": "Кооператив" "Cooperative": "Кооператив",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -1,21 +1,3 @@
{ {
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!",
"Online": "Онлайн",
"Denmark": "Дания",
"Estonia": "Эстония",
"Greece": "Греция",
"Latvia": "Латвия",
"Canada": "Канада",
"Ireland": "Ирландия",
"Germany": "Германия",
"USA": "CША",
"Copenhagen": "Копенгаген",
"Kolding": "Колдинг",
"KohtlaJarve": "Кохтла-Ярве",
"Athens": "Афины",
"Riga": "Рига",
"Halifax": "Галифакс",
"Montreal": "Монреаль",
"Wiesbaden": "Висбаден",
"Florida": "Флорида"
} }

View File

@@ -7,5 +7,6 @@
"contact": "Контакт", "contact": "Контакт",
"DiscordInviteLink": "Discord ссылка", "DiscordInviteLink": "Discord ссылка",
"WhatsAppInviteLink": "WhatsApp ссылка", "WhatsAppInviteLink": "WhatsApp ссылка",
"Group": "Группа" "Group": "Группа",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -30,5 +30,6 @@
"market": "Рынок", "market": "Рынок",
"workers": "Работники", "workers": "Работники",
"description": "Описание", "description": "Описание",
"website": "Вебсайт" "website": "Вебсайт",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -1,5 +1,5 @@
{ {
"top": "Наша организация представляет собой децентрализованную федерацию, основанную на принципе свободного объединения. Она состоит из многих групп людей, объединенных вокруг цели уничтожения эксплуататорских политико-экономических систем. Мы стремимся заменить их либертарными социалистическими системами с целью создания справедливого, демократического и устойчивого мира путем прекращения эксплуатации людей и природы.", "top": "Наша организация представляет собой децентрализованную федерацию, основанную на принципе свободного объединения. Она состоит из многих групп людей, объединенных вокруг цели свержения эксплуататорских политико-экономических систем. Мы стремимся заменить их либертарными социалистическими системами, основанными на децентрализации, прямой демократии и собственности рабочих на средства производства, с целью создания справедливого, демократического и устойчивого мира путем прекращения эксплуатации людей и природы.",
"groupsTitle": "ГРУППЫ", "groupsTitle": "ГРУППЫ",
"groupsText": "Мы организуем группы в целях образования, защиты наших интересов и взаимопомощи. Наша цель — продемонстрировать людям, как нынешние политико-экономические системы пагубно влияют на наше благополучие, представить им альтернативные варианты и заниматься взаимопомощью, чтобы сделать жизнь при капитализме легче.", "groupsText": "Мы организуем группы в целях образования, защиты наших интересов и взаимопомощи. Наша цель — продемонстрировать людям, как нынешние политико-экономические системы пагубно влияют на наше благополучие, представить им альтернативные варианты и заниматься взаимопомощью, чтобы сделать жизнь при капитализме легче.",
"communesTitle": "КОММУНЫ", "communesTitle": "КОММУНЫ",
@@ -11,5 +11,6 @@
"findUs": "Найди нас", "findUs": "Найди нас",
"whatNow": "Что теперь?", "whatNow": "Что теперь?",
"joinUs": "Присоединяйся", "joinUs": "Присоединяйся",
"talkWithUs": "Напиши нам" "talkWithUs": "Напиши нам",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -2,7 +2,7 @@
"orgName": "Либертарные социалисты", "orgName": "Либертарные социалисты",
"manifesto": "Манифест", "manifesto": "Манифест",
"joinUs": "Присоединяйся", "joinUs": "Присоединяйся",
"aboutUs": "О нас", "initiatives": "Инициативы",
"groups": "Группы", "groups": "Группы",
"communes": "Коммуны", "communes": "Коммуны",
"cooperatives": "Кооперативы", "cooperatives": "Кооперативы",

View File

@@ -5,9 +5,11 @@
"name": "Имя", "name": "Имя",
"location": "Локация", "location": "Локация",
"members": "Участники", "members": "Участники",
"link": "Ссылка", "website": "Вебсайт",
"contact": "Контакт",
"description": "Описание", "description": "Описание",
"ireland": "Ирландия", "ireland": "Ирландия",
"descriptionRoots": "Мы — левая либертарная организация, базирующаяся в Ирландии. Мы уделяем особое внимание восстановлению инфраструктуры страны, самоуправлению, жилищному строительству, сельскохозяйственной промышленности и делам молодежи.", "descriptionRoots": "Мы — левая либертарная организация, базирующаяся в Ирландии. Мы уделяем особое внимание восстановлению инфраструктуры страны, самоуправлению, жилищному строительству, сельскохозяйственной промышленности и делам молодежи.",
"Party": "Партия" "Party": "Партия",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -6,8 +6,10 @@
"name": "Название", "name": "Название",
"location": "Локация", "location": "Локация",
"type": "Тип", "type": "Тип",
"link": "Ссылка", "website": "Вебсайт",
"contact": "Контакт",
"description": "Описание", "description": "Описание",
"typeGaiasFall": "место для общения", "typeGaiasFall": "место для общения",
"descriptionGaiasFall": "Gaia's Fall — это сервер, который продвигает идеалы соларпанка, защиту окружающей среды, анархизм и антикапитализм. Мы поощряем гражданские дебаты, обсуждение теорий и возможностей, а также создание сообществ, сосредоточенных на формировании лучшего мира." "descriptionGaiasFall": "Gaia's Fall — это сервер, который продвигает идеалы соларпанка, защиту окружающей среды, анархизм и антикапитализм. Мы поощряем гражданские дебаты, обсуждение теорий и возможностей, а также создание сообществ, сосредоточенных на формировании лучшего мира.",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
} }

View File

@@ -31,7 +31,6 @@ function serve() {
} }
}; };
} }
const walkSync = require("walk-sync"); const walkSync = require("walk-sync");
const paths = walkSync("./src", {globs: ["**/*.svelte"]}).map(x => "src/"+x) const paths = walkSync("./src", {globs: ["**/*.svelte"]}).map(x => "src/"+x)

View File

@@ -0,0 +1,129 @@
<svelte:options tag="auth-component" />
<script>
// Import statements
import { onMount, setContext,getContext } from 'svelte'
import { sendText } from "/js/libraries/serverTools.js"
import * as AuthTools from "/js/libraries/authTools.js"
import "/js/components/login-component.js"
import "/js/components/signup-component.js"
// Main code
AuthTools.redirectLogged()
let loginComponent
let signupComponent
let context = {
googleInit: false
}
setContext("auth",context)
function switchFocus(component) {
if (component==loginComponent) {
loginComponent.focused = true
signupComponent.focused = false
}
else {
loginComponent.focused = false
signupComponent.focused = true
}
}
function callbackGoogle(data) {
console.log(data)
sendText("/signup-google",data.credential,(response) => AuthTools.processLoginResponse(response,context.msgs,context.remember.checked))
}
function initGoogle() {
if (typeof google != 'undefined') {
google.accounts.id.initialize({
client_id: '93612176787-sr8qjqem4e3kok4msrnj8s1illt85a9g.apps.googleusercontent.com',
callback: callbackGoogle,
auto_select: true,
context: "signin"
})
context.googleInit = true
}
else {
setTimeout(initGoogle,100)
}
}
initGoogle()
</script>
<div id="auth-group">
<div id="auth-grid-group">
<login-component bind:this={loginComponent} on:click={() => switchFocus(loginComponent)} on:keydown={() => ""}></login-component>
<signup-component bind:this={signupComponent} on:click={() => switchFocus(signupComponent)} on:keydown={() => ""}></signup-component>
</div>
<div id="auth-or" class="pane">
<span>OR</span>
</div>
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
span {
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
}
#auth-group {
margin: auto;
width: auto;
margin-bottom: 3rem;
}
#auth-grid-group {
display: grid;
grid-template-columns: 30rem 30rem;
justify-content: center;
gap: 1.37rem;
width: 100%;
}
#auth-or {
display: flex;
position: absolute;
margin: auto;
top: 40%;/*40%;*/
left: 50%;
transform: translate(-50%, -50%);
width: 5.4rem;
height: 5.4rem;
border-radius: 6.8rem;
background-color: white;
align-items:center;
justify-content:center;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
}
@media only screen and (max-width: 1200px) {
#auth-grid-group {
display: grid;
grid-template-columns: 30rem;
grid-template-rows: auto auto;
justify-content: center;
gap: 1.37rem;
width: 100%;
}
#auth-or {
top: 40rem;/*46.4rem;*/
}
#auth-group {
margin-top: 2rem;
margin-bottom: 3rem;
}
}
</style>

View File

@@ -0,0 +1,131 @@
<svelte:options tag="confirmation-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
// Main code
let confirmationInputs = []
let confirmationMsg
let confirmationButton
function onlyNumberKey(ind,evt) {
// Only ASCII character in that range allowed
var value = evt.data
if (value in ["0","1","2","3","4","5","6","7","8","9"]) {
if (ind<4) {
confirmationInputs[ind+1].focus()
}
else {
AuthTools.confirmEmail(confirmationMsg,getCode(),callback)
}
}
else {
confirmationInputs[ind].value = ""
}
}
function getCode() {
let code = ""
for (let input of confirmationInputs) {
code += input.value
}
return parseInt(code)
}
function callback(response) {
if (response=="true") {
AuthTools.toDashboard()
}
else {
confirmationMsg.innerHTML = "Wrong code"
}
}
onMount(() => {
})
</script>
<div class="pane auth-pane">
<h2 class="auth-title title-highlight">CONFIRMATION CODE</h2>
<div id="confirmationInputs">
<input bind:this={confirmationInputs[0]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(0,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[1]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(1,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[2]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(2,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[3]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(3,evt)}><span class="dash">-</span>
<input bind:this={confirmationInputs[4]} class="authConfirmationInput" type="text" maxlength="1" on:input={(evt) => onlyNumberKey(4,evt)}>
</div>
<span bind:this={confirmationMsg} id="confirmation-msg"></span>
<button bind:this={confirmationButton} class="auth-button" on:click="{() => AuthTools.confirmEmail(confirmationMsg,getCode(),callback)}">Confirm</button>
</div>
<style>
@import '/css/common.css';
.auth-pane {
position: relative;
padding: 3.4rem;
padding-top: 5.5rem;
padding-bottom: 5.5rem;
width: 33rem;
height: auto;
margin: auto;
}
.auth-title {
position: relative;
left: 0.7rem;
top: 0.2rem;
margin-bottom: 1.4rem;
}
.authConfirmationInput {
position: relative;
width: 3.16rem;
font-family: var(--serif,serif);
font-size: 3rem;
border-radius: 0.34rem;
margin-bottom: 0.7rem;
text-align: center;
padding-left: 0;
padding-bottom: 0.3 rem;
}
.dash {
display: block;
font-size: 3rem;
font-family: var(--serif,serif);
}
#confirmationInputs {
margin: auto;
display: grid;
justify-content: space-between;
grid-auto-flow: column;
}
.auth-button {
margin-top: 1.4rem;
height: 3.4rem;
width: 100%;
font-family: var(--sans-serif,sans-serif);
font-size: 1.6rem;
color: white;
background-color: var(--pink);
border-color: var(--pink);
border-radius: 0.5rem;
filter: drop-shadow(0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#confirmation-msg {
display: inline;
color:red;
}
</style>

View File

@@ -0,0 +1,110 @@
<svelte:options tag="login-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
export let focused = false
// Main code
let emailInput
let passwordInput
let inputs
let passwordVisibilityButton
let emailMsg
let passwordMsg
let msgs
let rememberMe
let googleButton
let parentProps = getContext("auth")
function renderGoogle() {
if (parentProps.googleInit) {
google.accounts.id.renderButton(googleButton,{
theme: 'outline',
size: 'large'
})
let iframe = googleButton.getElementsByTagName('iframe')[0]
iframe.style.height = "5rem"
iframe.style.width = "5rem"
}
else {
setTimeout(renderGoogle,100)
}
}
onMount(() => {
rememberMe.checked = true
inputs = {email: emailInput, password: passwordInput, remember: rememberMe}
msgs = {email: emailMsg, password: passwordMsg}
parentProps.msgs = msgs
parentProps.remember = rememberMe
parentProps.loginGoogle = googleButton
document.addEventListener("keypress", function(event) {
if (event.code == "Enter") {
if (focused) {
AuthTools.login(msgs,inputs)
}
}
})
renderGoogle()
})
</script>
<div id="login-group"class="pane auth-pane">
<h2 class="auth-title">LOG IN</h2>
<label class="auth-label" for="emailInput">Email&nbsp;</label><span bind:this={emailMsg} id="email-msg"></span>
<input bind:this={emailInput} id="emailInput" class="authEmailInput" type="email">
<div class="password-field">
<label class="auth-label" for="passwordInput">Password&nbsp;</label><span bind:this={passwordMsg} id="password-msg"></span>
<input bind:this={passwordInput} id="passwordInput" class="authPasswordInput" type="password">
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye icon"></object>
</button>
</div>
<div id="remember-me">
<input bind:this={rememberMe} type="checkbox" id="remember-me-checkbox"><label id="remember-me-label" for="passwordInput">remember me</label>
</div>
<button class="auth-button" on:click="{() => AuthTools.login(msgs,inputs)}">Log in</button>
<a id="forgot-password" href="forgot-password">Forgot password?</a>
<!--
<hr class="auth-line">
<div class="auth-methods-group">
<div id="google-btn-wrapper">
<div bind:this={googleButton} id="google-btn"></div>
<img src="/img/auth/google_icon.svg" id="google-logo" alt="google icon">
</div>
<button on:click={openGoogleWindow}>
<img src="img/auth/google_icon.svg" id="navbar-logo" alt="google icon">
</button>
<button onclick="">
<img src="img/auth/facebook_icon.svg" id="navbar-logo" alt="facebook icon">
</button>
<button onclick="">
<img src="img/auth/linkedin_icon.svg" id="navbar-logo" alt="linkedin icon">
</button>
</div>
-->
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
</style>

View File

@@ -0,0 +1,243 @@
<svelte:options tag="signup-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Export statements
export let focused = false
// Main code
let signupGroup
let emailInput
let passwordInput
let passwordVisibilityButton
let inputs
let googleButton
let emailMsg
let passwordMsg
let msgs
let rememberMe
let dialog
let signUp
let signUpField
let parentProps = getContext("auth")
function removeMsg(msg) {
if (msg.innerHTML!="") {
msg.innerHTML = ""
}
}
function showDialog() {
dialog.style.display = "block"
}
function hide() {
if (dialog!=null) {
dialog.style.display = "none";
}
}
function sendEmail() {
let email = signUpField.value
if (email.includes("@")) {
sendText("/get-email",email)
signUpField.value = ""
signUpField.placeholder = "Subscribed!"
signUpField.style.setProperty("--c", "hsl(147, 33%, 60%)")
}
else {
signUpField.value = ""
signUpField.placeholder = "must contain '@'"
signUpField.style.setProperty("--c", "hsl(0, 100%, 60%)")
}
}
function clearField() {
signUpField.placeholder = ""
}
function renderGoogle() {
if (parentProps.googleInit) {
google.accounts.id.renderButton(googleButton,{
theme: 'outline',
size: 'large'
})
}
else {
setTimeout(renderGoogle,100)
}
}
onMount(() => {
rememberMe.checked = true
inputs = {email: emailInput, password: passwordInput}
msgs = {email: emailMsg, password: passwordMsg}
document.addEventListener("keypress", function(event) {
if (event.code == "Enter") {
if (focused) {
AuthTools.signup(msgs,inputs,toLandingPage)
}
}
})
//renderGoogle()
})
</script>
<div id="signup-group" class="pane auth-pane" bind:this={signupGroup}>
<h2 class="auth-title">SIGN UP</h2>
<label class="auth-label" for="emailInput">Email&nbsp;</label><span bind:this={emailMsg} id="email-msg" on:change={() => removeMsg(emailMsg)}></span>
<input bind:this={emailInput} id="emailInput" class="authEmailInput" type="email">
<div class="password-field">
<label class="auth-label" for="emailInput">Password&nbsp;</label><span bind:this={passwordMsg} id="password-msg"></span>
<input bind:this={passwordInput} id="passwordInput" class="authPasswordInput" type="password" on:change={() => removeMsg(passwordMsg)}>
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye-icon"></object>
</button>
</div>
<div id="remember-me">
<input bind:this={rememberMe} type="checkbox" id="remember-me-checkbox"><label id="remember-me-label" for="passwordInput">remember me</label>
</div>
<button class="auth-button" on:click="{showDialog}">Sign up</button> <!--() => AuthTools.signup(msgs,inputs,AuthTools.toLandingPage)-->
<p id="forgot-password"></p>
<!--
<hr class="auth-line">
<div class="auth-methods-group">
<button on:click="{showDialog}">
<img src="/img/auth/google_icon.svg" id="navbar-logo" alt="google icon">
</button>
<button onclick="">
<img src="img/auth/facebook_icon.svg" id="navbar-logo" alt="facebook icon">
</button>
<button onclick="">
<img src="img/auth/linkedin_icon.svg" id="navbar-logo" alt="linkedin icon">
</button>
</div>
-->
</div>
<div bind:this={dialog} id="dialog">
<button id="shadow" on:click={hide}></button>
<div id="wrapper" class="pane">
<h2>Registration is closed</h2>
<p>We are still in the process of opening.</p>
<p>Sign up for updates to know when it becomes available:</p>
<div id="newsletter-container">
<input bind:this={signUpField} on:click={clearField} id="newsletterEmailInput" type="text">
<button bind:this={signUp} on:click={sendEmail} id="newsletterEmailButton">sign up</button>
</div>
<button id="no-button" on:click={hide}>No thanks</button>
</div>
</div>
<style>
@import '/css/common.css';
@import '/css/auth.css';
#dialog {
display: none;
}
#wrapper p {
text-align: justify;
}
#wrapper h2 {
text-align: center;
margin-bottom: 1rem;
}
#shadow {
position: fixed;
cursor: default;
top: 50%; right: 50%;
transform: translate(50%,-50%);
width: 100vw;
height: 100vh;
background:rgb(0, 0, 0, 0.2);
z-index: 999999;
}
#newsletter-container {
position: relative;
height: 3rem;
width: 100%;
margin-top: 1rem;
display: flex;
flex-direction: row;
}
#newsletterEmailInput {
height: 2.5rem;
border-radius: 0.2rem 0 0 0.2rem;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
flex-grow: 1;
}
#newsletterEmailInput::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--c,gray);
opacity: 1; /* Firefox */
}
#newsletterEmailButton {
width: 6.8rem;
height: 2.5rem;
background: var(--pink);
color: #ffffff;
font-family: var(--sans-serif,sans-serif);
font-size: 1.4rem;
border-radius: 0 0.2rem 0.2rem 0;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#newsletterEmailButton:active {
background: var(--darker-pink);
}
#wrapper {
top: 50%; right: 50%;
transform: translate(50%,-50%);
position: fixed;
max-width: 36rem;
width: 90vw;
padding: 2rem 4rem;
z-index: 1999999;
}
#wrapper * {
font-family: var(--sans-serif);
}
#no-button {
position: relative;
left: 50%;
transform: translateX(-50%);
width: 13rem;
height: 3rem;
margin-top: 2rem;
margin-bottom: 0.5rem;
background: #ffffff;
border: 0.2rem solid var(--pink);
font-family: var(--sans-serif,sans-serif);
font-size: 1.4rem;
border-radius: 0.5rem;
filter: drop-shadow( 0.07rem 0.14rem 0.07rem rgb(0 0 0 / 0.4));
}
#no-button:active {
background: hsl(343, 23%, 82%);
}
</style>

View File

@@ -0,0 +1,415 @@
<svelte:options tag="communes-add-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import { writable } from 'svelte/store'
import { loadLocaleContent, getData, sendData } from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
import "/js/components/select-component.js"
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/communes.json",callback)
let confirmationMsg
let addressInput
let contactInput
let statusInput
let addressVec
let userPinLat = 0
let userPinLng = 0
let userPin = createPin(0,0)
userPin.setOpacity(0)
let locale = loadLocaleContent(content,"communes-component",loaded)
loadLocaleContent(content,"countries",loaded)
function createPin(lat,lng) {
let markerIcon = new L.Icon({
iconUrl: '/img/common/markers/marker-black.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
return L.marker([lat,lng], {icon: markerIcon})
}
function updatePin(marker,lat,lng) {
let newLatLng = L.latLng(lat, lng); // Replace with the desired coordinates
marker.setLatLng(newLatLng)
}
function reverseGeocodeLocal(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressInput.value = fullAddress
resizeInput(addressInput)
}
getData(url,callback)
}
function reverseGeocode(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2&accept-language=en`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
getData(url,callback)
}
function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
userPin.addTo(map)
map.on('click', function(event) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinLat = lat
userPinLng = lng
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
})
}
function updateConfirmationMsg(response) {
if (response!==false) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
confirmationMsg.style.color = "green"
}
else {
confirmationMsg.innerHTML = "Something went wrong."
confirmationMsg.style.color = "red"
}
}
function submitLocation() {
if (addressVec!=undefined) {
let data = {
country: addressVec[0],
state: addressVec[1],
town: addressVec[2],
latitude: userPinLat,
longitude: userPinLng,
contact: contactInput.value,
status: statusInput.value
}
if (data.state=="") {
data.state = null
}
if (data.town=="") {
data.town = null
}
if (data.contact=="") {
data.contact = null
}
let url = "/" + locale + "/communes-add-post/"
sendData(url,data,updateConfirmationMsg)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
onMount(() => {
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>Add a Commune</h1>
<img id="communes-img" src="/img/common/communes.svg" alt="communes">
<p class="description">If there are no communes near you who you can join and you want to start your own then do the following:</p>
<ol>
<li>Click on the map to show us where you are located;</li>
<li>Add a way to contact you or leave blank for a pin to point to our discord;</li>
<li>Press "Submit" to add yourself to our map;</li>
<li>Verify yourself by having a chat with us at our Discord server to show on the map;</li>
</ol>
<div id="address-input-wrapper" class="input-label-wrapper">
<label for="address-input">Location: </label>
<div class="input-wrapper">
<input bind:this={addressInput} on:input={() => resizeInput(addressInput)} id="address-input" type="text" readonly>
<div class="ghost-input"></div>
</div>
</div>
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
<div id="status-input-wrapper" class="input-label-wrapper">
<label for="contact-input">Status: </label>
<div class="input-wrapper">
<select-component bind:this={statusInput} id="status-input" options={["forming","growing","not growing"]}></select-component>
<div class="ghost-input"></div>
</div>
</div>
<button id="submit-button" on:click={submitLocation}>Submit</button>
<p id="confirmation-msg" bind:this={confirmationMsg}></p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
#confirmation-msg {
margin-top: 0.5rem;
margin-bottom: 2rem;
}
ol li {
margin-left: 3rem;
margin-bottom: 0.5rem;
}
label {
display: inline-block;
font-family: var(--serif,serif);
font-size: 1.15rem;
line-height: 160%;
color: #222222;
width: 5.5rem;
}
input, .ghost-input {
font-size: 1.15rem;
font-family: var(--serif,serif);
}
input {
height: 2.5rem;
display: inline-block;
position: relative;
}
#address-input, #contact-input {
width: 100%;
}
#address-input-wrapper {
margin-top: 2rem;
margin-bottom: 1rem;
}
#status-input {
--width: 100%;
--height: 2.5rem;
--display: inline-block;
--position: relative;
--background-color: white;
--border-radius: 0.4rem;
--border: black solid 0.063rem;
--padding-left: 0.5rem;
--font-size: 1.15rem;
--font-family: var(--serif, serif);
}
#status-input-wrapper {
margin-top: 1rem;
margin-bottom: 0rem;
}
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 5.5rem);
min-width: min(20rem, calc(100% - 5.5rem));
height: 2.5rem;
}
.input-label-wrapper {
display: flex;
justify-content: start;
}
.input-label-wrapper label {
position: relative;
top: 0.3rem;
}
.description {
margin-bottom: 1rem;
}
#submit-button {
display: block;
margin: auto;
margin-top: 2rem;
padding: 1rem 2rem;
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
border: 0rem solid black;
border-radius: 0.5rem;
background: #cb1816;
color: white;
}
#communes-img {
position: absolute;
width: 11.5rem;
left: 50%;
transform: translate(-50%);
z-index: 0;
opacity: 0.2;
}
#text-container>:nth-child(3) {
margin-top: 8rem;
}
#map {
--height: 30rem;
--width: 100%;
--margin-top: 2rem;
--margin-bottom: 0.5rem;
}
#text-container {
position: relative;
max-width: calc(100vw - 4rem);
margin: auto;
}
h1 {
margin-bottom: 1rem;
font-size: 2.2rem;
text-align: center;
}
#container {
margin: auto;
max-width: 800px;
margin-top: 1rem;
margin-bottom: 4rem;
}
#container p {
text-align: justify;
}
</style>

View File

@@ -3,23 +3,59 @@
<script> <script>
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store'
import { communesByCountry, addMarkersCommunes } from '/js/communes.js' import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { loadLocaleContent } from "/js/libraries/serverTools.js" import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addCommunePinContent } from "/js/mapFuncs.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries
let entriesByCountry
loadLocaleContent(content,"countries",loaded)
let locale = loadLocaleContent(content,"communes-component",loaded) let locale = loadLocaleContent(content,"communes-component",loaded)
loadLocaleContent(content,"countries",loaded)
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/communes.json",callback)
function mapCallbackCommunes(createMap,content) { function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2) let map = createMap([22, 0],2)
addMarkersCommunes(map,content) let options = {
enableCountryGrouping: true,
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCommunePinContent,"red",options)
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
} }
onMount(() => { onMount(() => {
@@ -28,25 +64,27 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==2} {#if $loaded==3}
<div id="container"> <div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">--> <!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container"> <div id="text-container">
<h1>{$content.communes}</h1> <h1>{$content.communes}</h1>
<img id="commune-img" src="/img/common/commune.svg" alt="commune"> <img id="communes-img" src="/img/common/communes.svg" alt="commune">
<p class="description">{$content.p1}</p> <p class="description">{$content.p1}</p>
<h3>{$content.subheading1}</h3> <h3>{$content.subheading1}</h3>
<map-component id="map" callback={(createMap) => mapCallbackCommunes(createMap,$content,locale)}></map-component> <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p> <p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(communesByCountry) as [name,communes]} {#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{$content[name]}</h4> <h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block"> <div class="country-block">
{#each communes as commune} {#each entries as entry}
<div class="location-info"> <div class="location-info">
<p><b>{$content.location}: </b>{commune.location[0].map(x => $content[x]).join(", ")}</p> <p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.status}: </b>{$content[commune.status]}</p> <p><b>{$content.status}: </b>{$content[entry.status]}</p>
<p><b>{$content.members}: </b>{commune.members}</p> <p><b>{$content.members}: </b>{entry.members}</p>
<p><b>{$content.contact}: </b><a href={commune.contact[0]} target=;_blank; rel=noreferrer>{$content[commune.contact[1]]}</a></p> <p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div> </div>
{/each} {/each}
</div> </div>
@@ -67,7 +105,7 @@
margin-bottom: 2rem; margin-bottom: 2rem;
} }
#commune-img { #communes-img {
position: absolute; position: absolute;
width: 11.5rem; width: 11.5rem;
left: 50%; left: 50%;

View File

@@ -9,6 +9,7 @@
// Export statements // Export statements
export let callback = null export let callback = null
export let colors = null export let colors = null
export let map = null
// Main code // Main code
let mapContainer let mapContainer
@@ -50,6 +51,8 @@
width: var(--width,100%); width: var(--width,100%);
margin-top: var(--margin-top,0); margin-top: var(--margin-top,0);
margin-bottom: var(--margin-bottom,0); margin-bottom: var(--margin-bottom,0);
position: relative;
z-index: 0;
} }
</style> </style>

View File

@@ -71,17 +71,17 @@
<div bind:this={root} id="root" class="pane-centering"> <div bind:this={root} id="root" class="pane-centering">
<div class="pane-container"> <div class="pane-container">
<div id="sidebars-left" class="sidebar"> <div id="sidebars-left" class="sidebar">
<div bind:this={sidebarLeft} id="sidebar-left" class="pane"> <div bind:this={sidebarLeft} id="sidebar-left">
<slot name="sidebar-left"></slot> <slot name="sidebar-left"></slot>
</div> </div>
<div bind:this={sidebarLeft2} id="sidebar-left2" class="pane"> <div bind:this={sidebarLeft2} id="sidebar-left2">
<slot name="sidebar-left2"></slot> <slot name="sidebar-left2"></slot>
</div> </div>
</div> </div>
<div bind:this={sidebarRight} id="sidebar-right" class="pane sidebar"> <div bind:this={sidebarRight} id="sidebar-right" class="pane sidebar">
<slot name="sidebar-right"></slot> <slot name="sidebar-right"></slot>
</div> </div>
<div bind:this={mainPane} id="main-pane" class="pane"> <div bind:this={mainPane} id="main-pane">
<slot name="main" id="main-slot"></slot> <slot name="main" id="main-slot"></slot>
</div> </div>
</div> </div>
@@ -95,7 +95,10 @@
} }
#root { #root {
position: relative;
margin-top: auto;
min-height: var(--min-height,auto); min-height: var(--min-height,auto);
height: 100%;
} }
#main-pane { #main-pane {
@@ -105,10 +108,8 @@
padding-top: var(--padding-top,0rem); padding-top: var(--padding-top,0rem);
padding-bottom: var(--padding-bottom,0rem); padding-bottom: var(--padding-bottom,0rem);
text-align: justify; text-align: justify;
background: var(--background,white);
box-shadow: var(--box-shadow,0 0 0.314rem rgb(187, 187, 187));
margin: auto; margin: auto;
height: min-content; height: 100%;
max-width: var(--width-main,66rem); max-width: var(--width-main,66rem);
width: var(--width-main,66rem); width: var(--width-main,66rem);
z-index: 1; z-index: 1;
@@ -124,13 +125,14 @@
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
margin-left: calc(-1*var(--width-left,22.5rem) - 1rem - 4rem); margin-left: calc(-1*var(--width-left,22.5rem) - 1rem - 4rem);
width: calc(var(--width-left,22.5rem) + 4rem); width: max-content;
max-width: 30rem;
} }
#sidebar-left,#sidebar-left2 { #sidebar-left,#sidebar-left2 {
position: relative; position: relative;
background-color: white; background-color: white;
padding: 2rem 2rem; padding: 0rem 0rem;
} }
#sidebar-left { #sidebar-left {
@@ -149,16 +151,16 @@
padding: 2rem 2rem; padding: 2rem 2rem;
} }
@media only screen and (max-width: 1880px) { @media only screen and (max-width: 1340px) {
#main-pane { #main-pane {
max-width: initial; max-width: initial;
width: 100%; width: 100%;
max-width: var(--width-main,66rem); max-width: var(--width-main,66rem);
padding-left: var(--padding-left-mobile,1.8rem); padding-left: var(--padding-left-mobile,0rem);
padding-right: var(--padding-right-mobile,1.8rem); padding-right: var(--padding-right-mobile,0rem);
padding-top: var(--padding-top-mobile,1.8rem); padding-top: var(--padding-top-mobile,0rem);
padding-bottom: var(--padding-bottom-mobile,1.8rem); padding-bottom: var(--padding-bottom-mobile,0rem);
} }
#sidebars-left, #sidebar-right { #sidebars-left, #sidebar-right {

View File

@@ -194,6 +194,7 @@
.select span { .select span {
position: relative; position: relative;
padding-top: 0.5rem; padding-top: 0.5rem;
padding-left: 0.3rem;
white-space: nowrap; white-space: nowrap;
} }

View File

@@ -0,0 +1,413 @@
<svelte:options tag="cooperatives-add-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import { writable } from 'svelte/store';
import { loadLocaleContent, getData, sendData } from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/groups.json",callback)
let confirmationMsg
let addressInput
let contactInput
let addressVec
let userPinLat = 0
let userPinLng = 0
let userPin = createPin(0,0)
userPin.setOpacity(0)
let locale = loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"countries",loaded)
function createPin(lat,lng) {
let markerIcon = new L.Icon({
iconUrl: '/img/common/markers/marker-black.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
return L.marker([lat,lng], {icon: markerIcon})
}
function updatePin(marker,lat,lng) {
let newLatLng = L.latLng(lat, lng); // Replace with the desired coordinates
marker.setLatLng(newLatLng)
}
function reverseGeocodeLocal(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressInput.value = fullAddress
resizeInput(addressInput)
}
getData(url,callback)
}
function reverseGeocode(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2&accept-language=en`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
getData(url,callback)
}
function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
userPin.addTo(map)
map.on('click', function(event) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinLat = lat
userPinLng = lng
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
})
}
function updateConfirmationMsg(response) {
if (response!==false) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
confirmationMsg.style.color = "green"
}
else {
confirmationMsg.innerHTML = "Something went wrong."
confirmationMsg.style.color = "red"
}
}
function submitLocation() {
if (addressVec!=undefined) {
let data = {
country: addressVec[0],
state: addressVec[1],
town: addressVec[2],
latitude: userPinLat,
longitude: userPinLng,
contact: contactInput.value
}
if (data.state=="") {
data.state = null
}
if (data.town=="") {
data.town = null
}
if (data.contact=="") {
data.contact = null
}
let url = "/" + locale + "/groups-add-post/"
sendData(url,data,updateConfirmationMsg)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
onMount(() => {
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>Add a Group</h1>
<img id="groups-img" src="/img/common/groups.svg" alt="groups">
<p class="description">If there are no groups in your town with whom you can organize then do the following:</p>
<ol>
<li>Click on the map to show us where you are located;</li>
<li>Add a way to contact you or leave blank for a pin to point to our discord;</li>
<li>Press "Submit" to add yourself to our map;</li>
<li>Verify yourself by having a chat with us at our Discord server to show on the map;</li>
</ol>
<div id="address-input-wrapper" class="input-label-wrapper">
<label for="address-input">Location: </label>
<div class="input-wrapper">
<input bind:this={addressInput} on:input={() => resizeInput(addressInput)} id="address-input" type="text" readonly>
<div class="ghost-input"></div>
</div>
</div>
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
<button id="submit-button" on:click={submitLocation}>Submit</button>
<p id="confirmation-msg" bind:this={confirmationMsg}></p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
#confirmation-msg {
margin-top: 0.5rem;
margin-bottom: 2rem;
}
ol li {
margin-left: 3rem;
margin-bottom: 0.5rem;
}
label {
display: inline-block;
font-family: var(--serif,serif);
font-size: 1.15rem;
line-height: 160%;
color: #222222;
width: 5.5rem;
}
input, .ghost-input {
font-size: 1.15rem;
font-family: var(--serif,serif);
}
input {
height: 2.5rem;
display: inline-block;
position: relative;
}
#address-input, #contact-input {
width: 100%;
}
#address-input-wrapper {
margin-top: 2rem;
margin-bottom: 1rem;
}
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 5.5rem);
min-width: min(20rem, calc(100% - 5.5rem));
height: 2.5rem;
}
.input-label-wrapper {
display: flex;
justify-content: start;
}
.input-label-wrapper label {
position: relative;
top: 0.3rem;
}
.description {
margin-bottom: 1rem;
}
#submit-button {
display: block;
margin: auto;
margin-top: 2rem;
padding: 1rem 2rem;
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
border: 0rem solid black;
border-radius: 0.5rem;
background: #cb1816;
color: white;
}
#add-prompt {
margin-bottom: 2rem;
}
#groups-img {
position: absolute;
width: 14rem;
left: 50%;
transform: translate(-50%);
z-index: 0;
opacity: 0.2;
}
#text-container>:nth-child(3) {
margin-top: 8rem;
}
.country-name {
margin-bottom: 0.5rem;
}
.country-block {
margin-bottom: 2rem;
}
.location-info {
margin-bottom: 0.75rem;
}
.location-info p {
margin-bottom: 0;
}
a {
color: #DD1C1A;
}
#map {
--height: 30rem;
--width: 100%;
--margin-top: 2rem;
--margin-bottom: 0.5rem;
}
#text-container {
position: relative;
max-width: calc(100vw - 4rem);
margin: auto;
}
h1 {
margin-bottom: 1rem;
font-size: 2.2rem;
text-align: center;
}
h3 {
margin-bottom: 1rem;
}
#container {
margin: auto;
max-width: 800px;
margin-top: 1rem;
margin-bottom: 4rem;
}
#container p {
text-align: justify;
}
</style>

View File

@@ -4,8 +4,9 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { coopsByCountry, addMarkersCoops } from '/js/coops.js' import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { loadLocaleContent } from "/js/libraries/serverTools.js" import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addCoopPinContent } from "/js/mapFuncs.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
@@ -13,13 +14,48 @@
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries
let entriesByCountry
loadLocaleContent(content,"countries",loaded)
let locale = loadLocaleContent(content,"cooperatives-component",loaded) let locale = loadLocaleContent(content,"cooperatives-component",loaded)
loadLocaleContent(content,"countries",loaded)
function mapCallbackCoops(createMap,content) { let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/cooperatives.json",callback)
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2) let map = createMap([22, 0],2)
addMarkersCoops(map,content) let options = {
enableCountryGrouping: true,
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addCoopPinContent,"blue",options)
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
} }
onMount(() => { onMount(() => {
@@ -28,37 +64,38 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==2} {#if $loaded==3}
<div id="container"> <div id="container">
<div id="text-container"> <div id="text-container">
<h1>{$content.cooperatives}</h1> <h1>{$content.cooperatives}</h1>
<img id="coops-img" src="/img/common/coops.svg" alt="coops"> <img id="coops-img" src="/img/common/coops.svg" alt="coops">
<p class="description">{$content.p1}</p> <p class="description">{$content.p1}</p>
<h3>{$content.subheading1}</h3> <h3>{$content.subheading1}</h3>
<map-component id="map" callback={(createMap) => mapCallbackCoops(createMap,$content,locale)}></map-component> <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p> <p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(coopsByCountry) as [name,coops]} {#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{$content[name]}</h4> <h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block"> <div class="country-block">
{#each coops as coop} {#each entries as entry}
{console.log(entry)}
<div class="location-info"> <div class="location-info">
<div class="img-general-info"> <div class="img-general-info">
<div> <div>
<p><b>{$content.name}: </b>{coop.name}</p> <p><b>{$content.name}: </b>{entry.name}</p>
<p><b>{$content.location}: </b>{coop.location[0].map(x => $content[x]).join(", ")}</p> <p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.market}: </b>{$content[coop.market]}</p> <p><b>{$content.market}: </b>{entry.market}</p>
<p><b>{$content.workers}: </b>{coop.workers}</p> <p><b>{$content.workers}: </b>{entry.workers}</p>
<p><b>{$content.status}: </b>{$content[coop.status]}</p> <p><b>{$content.status}: </b>{entry.status}</p>
<p><b>{$content.website}: </b><a href={"https://www."+coop.website} target="_blank" rel=noreferrer>{coop.website}</a></p> <p><b>{$content.website}: </b><a href={entry.website} target="_blank" rel=noreferrer>{entry.website}</a></p>
<p><b>{$content.contact}: </b><a href={coop.contact[0]} target=;_blank; rel=noreferrer>{$content[coop.contact[1]]}</a></p> <p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div> </div>
<picture> <picture>
<source srcset={"/img/coops/"+coop.logo+".webp"}> <source srcset={"/img/coops/"+entry.logo+".webp"}>
<source srcset={"/img/coops/"+coop.logo+".png"}> <source srcset={"/img/coops/"+entry.logo+".png"}>
<img class="coop-logo" alt="logo"> <img class="coop-logo" alt="logo">
</picture> </picture>
</div> </div>
<p><b>{$content.description}: </b>{$content[coop.description]}</p> <p><b>{$content.description}: </b>{entry.description}</p>
</div> </div>
{/each} {/each}
</div> </div>

View File

@@ -32,8 +32,8 @@
<button on:click={() => {location.href='#'}} id="footer-up" aria-label="go up"> <button on:click={() => {location.href='#'}} id="footer-up" aria-label="go up">
<svg xmlns="http://www.w3.org/2000/svg" width="42.545" height="72.601" viewBox="0 0 42.545 72.601"> <svg xmlns="http://www.w3.org/2000/svg" width="42.545" height="72.601" viewBox="0 0 42.545 72.601">
<g id="Group_268" data-name="Group 268" transform="translate(-6.177 -2.399)"> <g id="Group_268" data-name="Group 268" transform="translate(-6.177 -2.399)">
<rect id="Rectangle_146" data-name="Rectangle 146" width="11" height="51" rx="5.5" transform="translate(22 24)" fill="#cb1816"/> <rect id="Rectangle_146" data-name="Rectangle 146" width="11" height="51" rx="5.5" transform="translate(22 24)" fill="var(--red)"/>
<path id="Path_1145" data-name="Path 1145" d="M23.814,4.021a5,5,0,0,1,7.372,0l16.134,17.6c2.94,3.207,1.046,10.4-3.686,8.379S28.02,14.081,28.391,13.524,16.544,27.976,11.366,30,4.741,24.828,7.68,21.621Z" fill="#DD1C1A"/> <path id="Path_1145" data-name="Path 1145" d="M23.814,4.021a5,5,0,0,1,7.372,0l16.134,17.6c2.94,3.207,1.046,10.4-3.686,8.379S28.02,14.081,28.391,13.524,16.544,27.976,11.366,30,4.741,24.828,7.68,21.621Z" fill="var(--red)"/>
</g> </g>
</svg> </svg>
</button> </button>
@@ -54,8 +54,8 @@ footer {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: auto; height: auto;
background: #5B6970;/*var(--dark-green);*/ background: var(--gray);
border-top: #cb1816 solid 0.5rem; border-top: var(--red) solid 0.5rem;
} }
footer p, footer a { footer p, footer a {

View File

@@ -0,0 +1,576 @@
<svelte:options tag="groups-add-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store';
import { loadLocaleContent, getData, sendData } from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
// Export statements
export let map = null
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let userData
let buttonsGroupMember
let buttonsNotGroupMember
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/groups.json",callback)
let confirmationMsg
let addressInput
let contactInput
let membersInput
let addressVec = ["","",""]
let userPinData = {
}
let userPinLng = 0
let userPin = createPin(0,0)
userPin.setOpacity(0)
let modeButtons = []
let context = getContext("profile-component")
let closeGroupsAdd = context.closeGroupsAdd
let maps = context.maps
let onLoadedGroups = context.onLoadedGroups
let userGroups = context.userGroups
let user = context.user
let has_group = userGroups.length!=0
let mode = has_group ? 2 : 0
let pendingGroup
if (has_group) {
pendingGroup= userGroups[0].status!=undefined
if (pendingGroup) {
mode = 3
}
}
let locale = loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"countries",loaded)
function createPin(lat,lng) {
let markerIcon = new L.Icon({
iconUrl: '/img/common/markers/marker-black.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
return L.marker([lat,lng], {icon: markerIcon})
}
function updatePin(marker,lat,lng) {
let newLatLng = L.latLng(lat, lng); // Replace with the desired coordinates
marker.setLatLng(newLatLng)
}
function reverseGeocodeLocal(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressInput.value = fullAddress
resizeInput(addressInput)
}
getData(url,callback)
}
function reverseGeocode(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2&accept-language=en`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
if (address!=undefined) {
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
}
getData(url,callback)
}
function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
map = createMap([22, 0],2)
maps["groupsAdd"] = map
let options = {
enableCountryGrouping: false,
pinCallback: pinCallback
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green",options)
userPin.addTo(map)
map.on('click', function(event) {
if (mode==0) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinData["latitude"] = lat
userPinData["longitude"] = lng
userPinData["id"] = null
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
}
})
}
function updateConfirmationMsg(response) {
if (response!==false) {
if (mode==0 && !user.verified) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
}
else {
confirmationMsg.innerHTML = "Success!"
}
confirmationMsg.style.color = "green"
if (mode==0 || mode==1) {
userGroups[0] = {}
}
userGroups[0].country = addressVec[0]=="" ? null : addressVec[0]
userGroups[0].state = addressVec[1]=="" ? null : addressVec[1]
userGroups[0].town = addressVec[2]=="" ? null : addressVec[2]
userGroups[0].members = userPinData["members"]
onLoadedGroups()
}
else {
confirmationMsg.innerHTML = "Something went wrong."
confirmationMsg.style.color = "red"
}
}
function submitLocation() {
if (addressVec[0]!="" || mode==3) {
let membersVal, contactVal
if (mode==0) { // Create
membersVal = membersInput.value
contactVal = contactInput.value
}
else if (mode==1) { // Join
contactVal = contactInput.value
}
else if (mode==2 || mode==3) { // Move
membersVal = ""
contactVal = ""
}
else if (mode==3) { // Leave
membersVal = ""
contactVal = ""
addressVec = [null,null,null]
userPinData["latitude"] = null
userPinData["longitude"] = null
}
userData = {
country: addressVec[0],
state: addressVec[1],
town: addressVec[2],
latitude: userPinData["latitude"],
longitude: userPinData["longitude"],
contact: contactVal=="" ? null : contactVal,
members: membersVal=="" ? null : parseInt(membersVal),
group_id: userPinData["id"],
mode: mode
}
if (userData.state=="") {
userData.state = null
}
if (userData.town=="") {
userData.town = null
}
let url = "/" + locale + "/groups-add-post/"
sendData(url,userData,updateConfirmationMsg)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
function pinCallback(marker,event) {
if (mode==1) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinData["latitude"] = lat
userPinData["longitude"] = lng
userPinData["id"] = marker.id
userPinData["members"] = marker.members
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
}
}
function chooseButton(index) {
for (let b of modeButtons) {
if (b!=undefined) {
b.style.background = "rgba(197, 43, 40, 0.319)"
b.style.color = "black"
}
}
modeButtons[index].style.background = "rgb(197, 43, 40)"
modeButtons[index].style.color = "white"
mode = index
}
function getAddress(g) {
if (g!=undefined) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
else {
return "Create or join group"
}
}
function onLoaded() {
if ($loaded==3) {
chooseButton(mode)
if (mode==2 || mode==3) {
addressInput.value = getAddress(userGroups[0])
}
}
else {
let f = () => onLoaded()
setTimeout(f, 100)
}
}
onMount(() => {
onLoaded()
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<button class="close-button" on:click={closeGroupsAdd}></button>
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
{#if !has_group}
<div bind:this={buttonsNotGroupMember} id="button-line">
<button bind:this={modeButtons[0]} on:click={() => chooseButton(0)}>Create</button>
<button bind:this={modeButtons[1]} on:click={() => chooseButton(1)}>Join</button>
</div>
{:else if has_group && !pendingGroup}
<div bind:this={buttonsGroupMember} id="button-line">
<button bind:this={modeButtons[2]} on:click={() => chooseButton(2)} style={"display: " + (pendingGroup ? "none" : "initial")}>Move</button>
<button bind:this={modeButtons[3]} on:click={() => chooseButton(3)}>Leave</button>
</div>
{:else}
<div bind:this={buttonsGroupMember} id="button-line">
<button bind:this={modeButtons[3]} on:click={() => chooseButton(3)}>Leave</button>
</div>
{/if}
<div id="address-input-wrapper" class="input-label-wrapper">
<label for="address-input">Location: </label>
<div class="input-wrapper">
<input bind:this={addressInput} on:input={() => resizeInput(addressInput)} id="address-input" type="text" readonly>
<div class="ghost-input"></div>
</div>
</div>
{#key mode}
{#if mode==0}
<div id="members-input-wrapper" class="input-label-wrapper">
<label for="members-input">Members: </label>
<div class="input-wrapper">
<input bind:this={membersInput} id="members-input" type="text" value={1}>
</div>
</div>
{/if}
{#if mode==0 || mode==1}
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
{/if}
{/key}
<button id="submit-button" on:click={submitLocation}>Submit</button>
<p id="confirmation-msg" bind:this={confirmationMsg}></p>
{#if !(has_group && pendingGroup)}
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
{/if}
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
#button-line {
position: relative;
width: fit-content;
margin: auto;
margin-top: 1.5rem;
}
#button-line button{
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
padding: 1rem 0;
width: 7rem;
}
#button-line :first-child {
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
margin-right: 0.1rem;
}
#button-line :last-child {
margin-left: 0.1rem;
border-top-right-radius: 1rem;
border-bottom-right-radius: 1rem;
}
.close-button {
position: absolute;
top: 2rem;
right: 0rem;
width: 2rem;
height: 2rem;
border: none;
cursor: pointer;
z-index: 2;
}
.close-button:hover {
opacity: 0.7;
}
.close-button::before,
.close-button::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0.2rem;
height: 2rem;
background-color: #000;
border-radius: 1rem;
}
.close-button::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.close-button::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
#confirmation-msg {
margin-top: 0.5rem;
margin-bottom: 2rem;
}
ol li {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
label {
display: inline-block;
font-family: var(--serif,serif);
font-size: 1.15rem;
line-height: 160%;
color: #222222;
width: 6rem;
}
input, .ghost-input {
font-size: 1.15rem;
font-family: var(--serif,serif);
}
input {
height: 2.5rem;
display: inline-block;
position: relative;
}
#address-input, #contact-input {
width: 100%;
}
#address-input-wrapper {
margin-top: 2rem;
margin-bottom: 1rem;
}
#members-input-wrapper {
margin-bottom: 1rem;
}
#members-input {
width: 5rem;
}
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 5.5rem);
min-width: min(20rem, calc(100% - 5.5rem));
height: 2.5rem;
}
.input-label-wrapper {
display: flex;
justify-content: start;
}
.input-label-wrapper label {
position: relative;
top: 0.3rem;
}
.description {
margin-bottom: 1rem;
}
#submit-button {
display: block;
margin: auto;
margin-top: 2rem;
padding: 1rem 2rem;
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
border: 0rem solid black;
border-radius: 0.5rem;
background: #cb1816;
color: white;
}
#map {
--height: 30rem;
--width: 100%;
--margin-top: 2rem;
--margin-bottom: 0.5rem;
}
#text-container {
position: relative;
max-width: calc(100vw - 4rem);
margin: auto;
}
#container {
margin: auto;
max-width: 800px;
margin-top: 1rem;
margin-bottom: 4rem;
}
#container p {
text-align: justify;
}
</style>

View File

@@ -4,8 +4,9 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { groupsByCountry, addMarkersGroups } from '/js/groups.js' import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { loadLocaleContent} from "/js/libraries/serverTools.js" import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addGroupPinContent } from "/js/mapFuncs.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
@@ -13,22 +14,54 @@
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries
let entriesByCountry
loadLocaleContent(content,"groups-component",loaded) let locale = loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"countries",loaded) loadLocaleContent(content,"countries",loaded)
function mapCallbackGroups(createMap,content) { let callback = (response) => {
let map = createMap([22, 0],2) entries = JSON.parse(response)
addMarkersGroups(map,content) entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
} }
getData("/assets/groups.json",callback)
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
onMount(() => { onMount(() => {
// {console.log(loaded)}
}) })
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==2} {#if $loaded==3}
<div id="container"> <div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">--> <!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container"> <div id="text-container">
@@ -36,16 +69,16 @@
<img id="groups-img" src="/img/common/groups.svg" alt="groups"> <img id="groups-img" src="/img/common/groups.svg" alt="groups">
<p class="description">{$content.p1}</p> <p class="description">{$content.p1}</p>
<h3>{$content.subheading1}</h3> <h3>{$content.subheading1}</h3>
<map-component id="map" callback={(createMap) => mapCallbackGroups(createMap,$content)}></map-component> <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p> <p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(groupsByCountry) as [name,groups]} {#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{$content[name]}</h4> <h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block"> <div class="country-block">
{#each groups as group} {#each entries as entry}
<div class="location-info"> <div class="location-info">
<p><b>{$content.location}: </b>{group.location[0].map(x => $content[x]).join(", ")}</p> <p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.members}: </b>{group.members}</p> <p><b>{$content.members}: </b>{entry.members}</p>
<p><b>{$content.contact}: </b><a href={group.contact[0]} target=;_blank; rel=noreferrer>{$content[group.contact[1]]}</a></p> <p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div> </div>
{/each} {/each}
</div> </div>
@@ -102,7 +135,7 @@
#map { #map {
--height: 30rem; --height: 30rem;
--width: 100%; --width: 100%;
--margin-bottom: 3rem; --margin-bottom: 0.5rem;
} }
#text-container { #text-container {

View File

@@ -4,11 +4,9 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { addMarkersGroups, groupsMarkersLayer } from '/js/groups.js' import { loadLocaleContent, getData } from "/js/libraries/serverTools.js"
import { addMarkersCoops, coopsMarkersLayer } from '/js/coops.js' import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addMarkersCommunes, communesMarkersLayer } from '/js/communes.js' import { addGroupPinContent, addCommunePinContent, addCoopPinContent, addPartyPinContent } from "/js/mapFuncs.js"
import { addMarkersParties, partiesMarkersLayer } from '/js/parties.js'
import { loadLocaleContent } from "/js/libraries/serverTools.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
@@ -16,6 +14,8 @@
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries = {}
let entriesByCountry ={}
loadLocaleContent(content,"groups-component",loaded) loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"communes-component",loaded) loadLocaleContent(content,"communes-component",loaded)
@@ -24,19 +24,43 @@
loadLocaleContent(content,"countries",loaded) loadLocaleContent(content,"countries",loaded)
let locale = loadLocaleContent(content,"join-us-component",loaded) let locale = loadLocaleContent(content,"join-us-component",loaded)
function mapCallback(createMap,content) {
let map = createMap([22, 0],2) let callback = (response,name) => {
addMarkersGroups(map,content) entries[name] = JSON.parse(response)
addMarkersCommunes(map,content) entriesByCountry[name] = {}
addMarkersCoops(map,content) for (let g of entries[name]) {
addMarkersParties(map,content) let country = g.country
if (g.contact==null) {
let overlayMaps = { g.contact = "https://discord.gg/Qk8KUk787z"
"Groups": groupsMarkersLayer, }
"Communes": communesMarkersLayer, if (country in entriesByCountry[name]) {
"Coops": coopsMarkersLayer, entriesByCountry[name][country].push(g)
"Parties": partiesMarkersLayer, }
else {
entriesByCountry[name][country] = [g]
}
} }
loaded.update((val) => {
return val + 1
})
}
getData("/assets/groups.json",(response) => callback(response,"groups"))
getData("/assets/communes.json",(response) => callback(response,"communes"))
getData("/assets/cooperatives.json",(response) => callback(response,"cooperatives"))
getData("/assets/parties.json",(response) => callback(response,"parties"))
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
let groupsMarkersLayer = addMarkersEntries(entries["groups"],entriesByCountry["groups"],map,content,locale,addGroupPinContent,"green")
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red")
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue")
let partiesMarkersLayer = addMarkersEntries(entries["parties"],entriesByCountry["parties"],map,content,locale,addPartyPinContent,"gold")
let overlayMaps = {}
overlayMaps[content.groups] = groupsMarkersLayer
overlayMaps[content.communes] = communesMarkersLayer
overlayMaps[content.cooperatives] = coopsMarkersLayer
overlayMaps[content.parties] = partiesMarkersLayer
L.control.layers(null, overlayMaps).addTo(map) L.control.layers(null, overlayMaps).addTo(map)
} }
@@ -46,7 +70,7 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==6} {#if $loaded==10}
<div id="container"> <div id="container">
<div id="text-container"> <div id="text-container">
<h1>{$content.heading}</h1> <h1>{$content.heading}</h1>
@@ -77,7 +101,7 @@
<p>{$content.nearYou}</p> <p>{$content.nearYou}</p>
</div> </div>
<p>{$content.noneNear} <a href="https://chat.whatsapp.com/BhnmUNljUxJ2AjeHUwyTKh" target="_blank" rel=noreferrer>{$content.WhatsAppGroup}</a> {$content.or} <a href="https://discord.gg/Qk8KUk787z" target="_blank" rel=noreferrer>{$content.DiscordServer}</a>{$content.helpStart}</p> <p>{$content.noneNear} <a href="https://chat.whatsapp.com/BhnmUNljUxJ2AjeHUwyTKh" target="_blank" rel=noreferrer>{$content.WhatsAppGroup}</a> {$content.or} <a href="https://discord.gg/Qk8KUk787z" target="_blank" rel=noreferrer>{$content.DiscordServer}</a>{$content.helpStart}</p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content)}></map-component> <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)} colors={["#23AC20","#CA2437","#217BC9","#FFD326"]}></map-component>
</div> </div>
</div> </div>
{/if} {/if}

View File

@@ -4,11 +4,9 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { addMarkersGroups, groupsMarkersLayer } from '/js/groups.js' import { loadLocaleContent, getData } from "/js/libraries/serverTools.js"
import { addMarkersCoops, coopsMarkersLayer } from '/js/coops.js' import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addMarkersCommunes, communesMarkersLayer } from '/js/communes.js' import { addGroupPinContent, addCommunePinContent, addCoopPinContent, addPartyPinContent } from "/js/mapFuncs.js"
import { addMarkersParties, partiesMarkersLayer } from '/js/parties.js'
import { loadLocaleContent } from "/js/libraries/serverTools.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
@@ -18,6 +16,8 @@
let gridWidth let gridWidth
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries = {}
let entriesByCountry ={}
function changeWidth(locale) { function changeWidth(locale) {
if (locale=="ru") { if (locale=="ru") {
@@ -28,6 +28,30 @@
} }
} }
let callback = (response,name) => {
entries[name] = JSON.parse(response)
entriesByCountry[name] = {}
for (let g of entries[name]) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry[name]) {
entriesByCountry[name][country].push(g)
}
else {
entriesByCountry[name][country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/groups.json",(response) => callback(response,"groups"))
getData("/assets/communes.json",(response) => callback(response,"communes"))
getData("/assets/cooperatives.json",(response) => callback(response,"cooperatives"))
getData("/assets/parties.json",(response) => callback(response,"parties"))
loadLocaleContent(content,"groups-component",loaded) loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"communes-component",loaded) loadLocaleContent(content,"communes-component",loaded)
loadLocaleContent(content,"cooperatives-component",loaded) loadLocaleContent(content,"cooperatives-component",loaded)
@@ -36,12 +60,15 @@
let locale = loadLocaleContent(content,"landing-component",loaded,changeWidth) let locale = loadLocaleContent(content,"landing-component",loaded,changeWidth)
changeWidth(locale) changeWidth(locale)
function mapCallback(createMap,content) { function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2) let map = createMap([22, 0],2)
addMarkersGroups(map,content) let options = {
addMarkersCommunes(map,content) enableCountryGrouping: true,
addMarkersCoops(map,content) }
addMarkersParties(map,content) let groupsMarkersLayer = addMarkersEntries(entries["groups"],entriesByCountry["groups"],map,content,locale,addGroupPinContent,"green",options)
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red",options)
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue",options)
let partiesMarkersLayer = addMarkersEntries(entries["parties"],entriesByCountry["parties"],map,content,locale,addPartyPinContent,"gold",options)
let overlayMaps = {} let overlayMaps = {}
overlayMaps[content.groups] = groupsMarkersLayer overlayMaps[content.groups] = groupsMarkersLayer
@@ -58,14 +85,13 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==6} {#if $loaded==10}
<div id="container"> <div id="container">
<picture> <picture>
<source srcset="/img/crowd.webp"> <source srcset="/img/crowd.webp">
<source srcset="/img/crowd.png"> <source srcset="/img/crowd.png">
<img id="crowd" alt="crowd"> <img id="crowd" alt="crowd">
</picture> </picture>
<div id="text-container"> <div id="text-container">
<p>{$content.top}</p> <p>{$content.top}</p>
<div bind:this={grid} id="container-grid" style="--grid-width: {gridWidth}"> <div bind:this={grid} id="container-grid" style="--grid-width: {gridWidth}">
@@ -75,9 +101,9 @@
<p>{$content.groupsText}</p> <p>{$content.groupsText}</p>
</div> </div>
<div> <div>
<a href={"/" + locale + "/communes"}><h2>{$content.communesTitle}</h2></a> <a href={"/" + locale + "/parties"}><h2>{$content.partiesTitle}</h2></a>
<img id="communes-img" src="/img/common/commune.svg" alt="communes"> <img id="parties-img" src="/img/common/parties.svg" alt="coops">
<p>{$content.communesText}</p> <p>{$content.partiesText}</p>
</div> </div>
<div> <div>
<a href={"/" + locale + "/coops"}><h2>{$content.cooperativesTitle}</h2></a> <a href={"/" + locale + "/coops"}><h2>{$content.cooperativesTitle}</h2></a>
@@ -85,13 +111,30 @@
<p>{$content.cooperativesText}</p> <p>{$content.cooperativesText}</p>
</div> </div>
<div> <div>
<a href={"/" + locale + "/parties"}><h2>{$content.partiesTitle}</h2></a> <a href={"/" + locale + "/communes"}><h2>{$content.communesTitle}</h2></a>
<img id="parties-img" src="/img/common/parties.svg" alt="coops"> <img id="communes-img" src="/img/common/communes.svg" alt="communes">
<p>{$content.partiesText}</p> <p>{$content.communesText}</p>
</div> </div>
</div> </div>
<h1>{$content.findUs}</h1> <!--
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content)} colors={["#23AC20","#CA2437","#217BC9","#FFD326"]}></map-component> <div id="values-container">
<h1>Our Values</h1>
<h2>Environmentalism</h2>
<p>We are committed to environmental preservation and the urgent need to safeguard our planet's biodiversity. We embrace sustainable practices, such as permaculture, as essential components of our vision for the future. We emphasize the harmonious integration of landscapes, ecosystems, and human settlements. We strive to create resilient and self-sustaining systems that promote biodiversity, enhance soil fertility, conserve water resources, and foster ecological balance.</p>
<h2>Against animal exploitation</h2>
<p>We embrace a compassionate approach to our relationship with animals, recognizing the inherent value and welfare of all sentient beings. We strive to build a more harmonious coexistence between humans and animals, promoting sustainable and cruelty-free practices that benefit both the planet and its inhabitants. This commitment reflects our deep understanding of the interconnectedness of all living beings.</p>
<h2>Queer liberation</h2>
<p>We strive for a society free from the constraints and hierarchies imposed by gender or sexual norms. We envision a world where the word “queer” becomes meaningless because individuals are not defined or limited by their assigned gender at birth, where the binary constructs of male and female are dismantled, and where everyone can freely express their authentic selves without fear of discrimination or marginalization.</p>
<h2>Racial Justice</h2>
<p>We actively challenge and dismantle systemic racism, discrimination, and inequality in all its forms. We advocate for a society that recognizes and celebrates the diversity of human experiences, irrespective of their race or ethnicity. We strive to address the historical and ongoing impacts of racism, dismantle racist structures, and uplift marginalized communities.</p>
<h2>Anti-Imperialism</h2>
<p>We stand in solidarity with indigenous peoples and support their right to self-determination, land sovereignty, and cultural preservation. We challenge global systems that exploit and subjugate nations and communities.</p>
<h2>Peace and non-violence</h2>
<p>We promote peaceful resolutions to conflicts and oppose war, militarization, and violence. We advocate for non-violent strategies that address social issues and create lasting positive change.</p>
</div>
-->
<h1 id="find-us">{$content.findUs}</h1>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)} colors={["#23AC20","#CA2437","#217BC9","#FFD326"]}></map-component>
<h1>{$content.whatNow}</h1> <h1>{$content.whatNow}</h1>
<div id="action-container"> <div id="action-container">
<a class="link-button" href={"/" + locale + "/join-us"}>{$content.joinUs}</a> <a class="link-button" href={"/" + locale + "/join-us"}>{$content.joinUs}</a>
@@ -105,6 +148,10 @@
<style> <style>
@import '/css/common.css'; @import '/css/common.css';
#values-container {
margin-bottom: 3rem;
}
#action-container { #action-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -119,7 +166,7 @@
font-family: var(--sans-serif,sans-serif); font-family: var(--sans-serif,sans-serif);
width: 14rem; width: 14rem;
line-height: 4rem; line-height: 4rem;
background: #cb1816; background: var(--red);
color: white; color: white;
text-align: center; text-align: center;
} }
@@ -134,6 +181,7 @@
h1 { h1 {
font-size: 2rem; font-size: 2rem;
text-align: center; text-align: center;
margin-bottom: 1rem;
} }
#container-grid > div > a > h2 { #container-grid > div > a > h2 {
@@ -184,9 +232,9 @@
grid-template-columns: var(--grid-width); grid-template-columns: var(--grid-width);
grid-template-rows: var(--grid-width); grid-template-rows: var(--grid-width);
grid-gap: 4rem; grid-gap: 4rem;
row-gap: 3rem; row-gap: 2.5rem;
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 2rem; margin-bottom: 1rem;
} }
#container-grid > div { #container-grid > div {

View File

@@ -1,446 +0,0 @@
<svelte:options tag="navbar-component" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { loadLocaleContent, locales } from "/js/libraries/serverTools.js"
// Main code
let hambInput
let navbar
let localesDropdown;
let loaded = writable(0)
let content = writable({})
let logoText
let locale = loadLocaleContent(content,"navbar-component",loaded)
function changeNavbar() {
if (hambInput.checked) {
navbar.style.background = "white"
//navbar.style.boxShadow = "0 0 0.314rem rgb(187, 187, 187)"
}
else {
setTimeout(()=> {
navbar.style.position = "relative"
navbar.style.background = ""
navbar.style.boxShadow = ""
}
,510)
}
}
function showLocales() {
if (localesDropdown.style.display=="block") {
localesDropdown.style.display = "none"
}
else {
localesDropdown.style.display = "block"
}
}
function changeLocale(lang) {
localStorage.setItem("locale",lang)
let locSplit = location.href.split("/")
let localesSymbols = Object.keys(locales)
locSplit = locSplit.filter(x => !localesSymbols.includes(x))
let loc = locSplit.slice(0,locSplit.length-1).join("/") + "/" + lang + "/" + locSplit[locSplit.length-1]
location.href = loc
}
function fixHeading() {
if (locale=="ru") {
let func = () => {
if (logoText==undefined) {
setTimeout(func,100)
}
else {
if (window.innerWidth < 400 && logoText.style.lineHeight!="100%") {
logoText.style.lineHeight = "120%"
logoText.style.top = "1rem"
logoText.style.width = "16rem"
}
else if (window.innerWidth > 400 && logoText.style.lineHeight!="400%") {
logoText.style.lineHeight = "400%"
logoText.style.top = "0rem"
logoText.style.width = "auto"
}
}
}
func()
addEventListener("resize", func)
}
}
onMount(() => {
})
</script>
<!-- Navigation bar -->
{#key loaded}
{#if Object.keys($content).length!=0}
{fixHeading()}{console.log($content)}
<header bind:this={navbar} id="navbar">
<!-- Hamburger icon -->
<input bind:this={hambInput} type="checkbox" id="side-menu" on:click={changeNavbar}>
<label id="hamb" for="side-menu"><span id="hamb-line"></span></label>
<!-- Logo -->
<a id=logo-container href={"/" + locale + "/"}>
<img src="/img/common/flag.png" id="navbar-logo" alt="logo">
<span bind:this={logoText} id="navbar-logo-text" >{@html $content.orgName}</span>
</a>
<!-- Menu -->
<nav id="nav">
<ul id="menu">
<li><a href={"/"+locale+"/manifesto"}>{$content.manifesto}</a></li>
<li><a href={"/"+locale+"/join-us"}>{$content.joinUs}</a></li>
<!-- Options dropdown -->
<!-- A list of links pointing to different pages of the website. Implemented as a div opened on :hover-->
<li id="options-container">
<button id="options-button">{$content.aboutUs}</button>
<div id="options-dropdown">
<ul id="options-list">
<li><a href={"/"+locale+"/groups"}>{$content.groups}</a></li>
<li><a href={"/"+locale+"/communes"}>{$content.communes}</a></li>
<li><a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a></li>
<li><a href={"/"+locale+"/parties"}>{$content.parties}</a></li>
<li><a href={"/"+locale+"/partners"}>{$content.partners}</a></li>
</ul>
</div>
</li>
<li id="locales">
<button on:click={showLocales}>
<picture>
<source srcset="/img/common/globe.webp">
<source srcset="/img/common/globe.png">
<img id="locales-img" alt="globe">
</picture>
</button>
</li>
<div bind:this={localesDropdown} id="locales-dropdown">
{#each Object.entries(locales) as [loc,name]}
<button on:click={() => changeLocale(loc)}>{name}</button>
{/each}
</div>
</ul>
</nav>
</header>
{/if}
{/key}
<style>
@import '/css/common.css';
/* Header */
#navbar{
position: relative;
top: 0;
width: min(100%,116rem);
z-index: 1000000000;
height: 5.26rem;
padding-left: 4rem;
padding-right: 4rem;
}
#navbar * {
font-family: var(--sans-serif, sans-serif);
}
/* Logo */
#logo-container {
display: flex;
position: absolute;
margin-left: 1rem;
height: 100%;
max-height: 5.26rem;
color: black;
z-index: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
}
#navbar-logo {
height: 3.5rem;
width: 3.5rem;
object-fit: contain;
border-radius: 10rem;
}
#navbar-logo-text {
position: relative;
word-wrap: normal;
height: 100%;
line-height: 400%;
font-size: 1.4rem;
color: #292222;
font-family: var(--sans-serif, sans-serif);
font-weight: 400;
padding-left: 1.2rem;
}
/* Nav menu */
#nav {
position: fixed;
width: 100%;
height: 100%;
background-color: white;
overflow: hidden;
z-index: 0;
}
#menu a, #options-button {
display: block;
padding: 1.2rem;
padding-top: 1rem;
padding-bottom: 1rem;
color: black;
font-size: 1.4rem;
}
#menu a:hover {
background-color: rgb(220, 220, 220);
}
#menu a:active{
background-color: #f7aec0;
}
#menu li {
list-style-type: none;
}
#nav{
max-height: 0;
/*transition: max-height .5s ease-out;*/
}
/* Menu Icon */
#hamb{
position: absolute;
cursor: pointer;
right: 0rem;
padding: 2.8rem 2rem;
z-index: 9999;
}/* Style label tag */
#hamb-line {
background: black;
display: block;
height: 2px;
position: relative;
width: 24px;
} /* Style span tag */
#hamb-line::before,
#hamb-line::after{
background: black;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
#hamb-line::before{
top: 5px;
}
#hamb-line::after{
top: -5px;
}
#side-menu {
display: none;
} /* Hide checkbox */
/* Toggle menu icon */
#side-menu:checked ~ nav {
display: block;
max-height: 100%;
padding-top: 5.625rem;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb {
position: fixed;
}
#side-menu:checked ~ #logo-container {
position: fixed;
}
#side-menu:checked ~ #hamb #hamb-line {
background: transparent;
}
#side-menu:checked ~ #hamb #hamb-line::before {
transform: rotate(-45deg);
top: 0;
}
#side-menu:checked ~ #hamb #hamb-line::after {
transform: rotate(45deg);
top: 0;
}
/* Options */
#options-dropdown {
position: absolute;
display: none;
border: #404040 solid 0.1rem;
/*padding: 0.5rem;*/
background-color: white;
overflow: auto;
width: 35%;
}
#options-list > li a {
padding: 1rem;
font-size: 1.1rem;
}
#options-list > li:hover {
background-color: rgb(187 53 52 / 96%);
width: 100%;
}
#options-list > li:hover > a{
color: white;
}
#options-container:hover #options-dropdown {
display: block;
}
/*Localization*/
#locales {
position: relative;
}
#locales button {
width: 100%;
text-align: left;
height: 4rem;
}
#locales button:hover {
opacity: 0.5;
}
#locales-img {
position: relative;
top: 0rem;
height: 2rem;
margin-left: 1.2rem;
}
#locales-dropdown {
position: absolute;
display: none;
top: 5.6rem;
right: 1.8rem;
border: #404040 solid 0.1rem;
padding: 1.4rem;
background-color: white;
}
#locales-dropdown button {
display: block;
font-family: var(--sans-serif,sans-serif);
font-size: 1.3rem;
width: 100%;
}
#locales-dropdown button:hover {
color: rgb(127, 127, 127);
}
#locales-dropdown>:first-child {
padding-bottom: 0.5rem;
}
#locales-dropdown>:nth-child(2) {
padding-top: 0.5rem;
}
/* Responsiveness */
@media only screen and (min-width: 1500px) {
#navbar {
position: relative;
width: min(100%,116rem);
left: 50%;
transform: translateX(-50%);
padding-right: 4rem;
padding-left: 4rem;
}
#nav {
max-height: none;
top: 0;
position: relative;
float: right;
width: fit-content;
background-color: transparent;
overflow: visible;
}
#side-menu:checked ~ nav {
padding-top: 0;
}
#menu li {
float: left;
}
#menu a:hover, #options-button:hover {
background-color: transparent;
color: rgb(127, 127, 127);
}
#menu a, #options-button {
padding: 1.2rem;
padding-top: 1.9rem;
padding-bottom: 1.9rem;
}
#hamb {
display: none;
}
#locales {
position: relative;
margin-right: 1.8rem;
}
#locales-img {
top: 0.9rem;
}
#locales-dropdown {
top: 5,7rem;
}
}
</style>

View File

@@ -0,0 +1,151 @@
<svelte:options tag="navbar-logged" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { loadLocaleContent, locales } from "/js/libraries/serverTools.js"
// Main code
let hambInput
let navbar
let localesDropdown
let initiativesDropdown
let loaded = writable(0)
let content = writable({})
let logoText
let locale = loadLocaleContent(content,"navbar-component",loaded)
function changeNavbar() {
if (hambInput.checked) {
navbar.style.background = "white"
//navbar.style.boxShadow = "0 0 0.314rem rgb(187, 187, 187)"
}
else {
setTimeout(()=> {
navbar.style.position = "relative"
navbar.style.background = ""
navbar.style.boxShadow = ""
}
,510)
}
}
function showDropdown(dropdown) {
let state = dropdown.style.display
initiativesDropdown.style.display = "none"
localesDropdown.style.display = "none"
if (state=="block") {
dropdown.style.display = "none"
}
else {
dropdown.style.display = "block"
}
}
function changeLocale(lang) {
localStorage.setItem("locale",lang)
let locSplit = location.href.split("/")
let localesSymbols = Object.keys(locales)
locSplit = locSplit.filter(x => !localesSymbols.includes(x))
let loc = locSplit.slice(0,locSplit.length-1).join("/") + "/" + lang + "/" + locSplit[locSplit.length-1]
location.href = loc
}
function fixHeading() {
if (locale=="ru") {
let func = () => {
if (logoText==undefined) {
setTimeout(func,100)
}
else {
if (((window.innerWidth < 1700 && window.innerWidth > 1400) || window.innerWidth < 400) && logoText.style.lineHeight!="100%") {
logoText.style.lineHeight = "120%"
logoText.style.top = "1rem"
logoText.style.width = "16rem"
}
else if (((window.innerWidth > 1700) || (window.innerWidth > 400 && window.innerWidth < 1400)) && logoText.style.lineHeight!="400%") {
logoText.style.lineHeight = "400%"
logoText.style.top = "0rem"
logoText.style.width = "auto"
}
}
}
func()
addEventListener("resize", func)
}
}
function hide(dropdown) {
let callback = () => {
dropdown.style.display = "none"
}
setTimeout(callback,100)
}
onMount(() => {
fixHeading()
})
</script>
<!-- Navigation bar -->
{#key loaded}
{#if Object.keys($content).length!=0}
<header bind:this={navbar} id="navbar">
<!-- Hamburger icon -->
<input bind:this={hambInput} type="checkbox" id="side-menu" on:click={changeNavbar}>
<label id="hamb" for="side-menu"><span id="hamb-line"></span></label>
<!-- Logo -->
<a id=logo-container href={"/" + locale + "/"}>
<img src="/img/common/flag.png" id="navbar-logo" alt="logo">
<span bind:this={logoText} id="navbar-logo-text" >{@html $content.orgName}</span>
</a>
<!-- Menu -->
<nav id="nav">
<ul id="menu">
<li><a href={"/"+locale+"/join-us"}>{$content.joinUs}</a></li>
<li><a href={"/"+locale+"/manifesto"}>{$content.manifesto}</a></li>
<!-- Options dropdown -->
<!-- A list of links pointing to different pages of the website. Implemented as a div opened on :hover-->
<li id="options-container">
<button on:click={() => showDropdown(initiativesDropdown)} on:focusout={() => hide(initiativesDropdown)} class="options-button">{$content.initiatives}</button>
<div bind:this={initiativesDropdown} class="options-dropdown">
<a href={"/"+locale+"/groups"}>{$content.groups}</a>
<a href={"/"+locale+"/communes"}>{$content.communes}</a>
<a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a>
<a href={"/"+locale+"/parties"}>{$content.parties}</a>
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>
<li><a href={"/"+locale+"/profile"}>{$content.profile}</a></li>
<li id="locales">
<button on:click={() => showDropdown(localesDropdown)} on:focusout={() => hide(localesDropdown)}>
<picture>
<source srcset="/img/common/globe.webp">
<source srcset="/img/common/globe.png">
<img id="locales-img" alt="globe">
</picture>
</button>
</li>
<div bind:this={localesDropdown} class="options-dropdown">
{#each Object.entries(locales) as [loc,name]}
<button on:click={() => changeLocale(loc)}>{name}</button>
{/each}
</div>
</ul>
</nav>
</header>
{/if}
{/key}
<style>
@import '/css/common.css';
@import '/css/navbar.css';
</style>

View File

@@ -0,0 +1,151 @@
<svelte:options tag="navbar-not-logged" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { loadLocaleContent, locales } from "/js/libraries/serverTools.js"
// Main code
let hambInput
let navbar
let localesDropdown
let initiativesDropdown
let loaded = writable(0)
let content = writable({})
let logoText
let locale = loadLocaleContent(content,"navbar-component",loaded)
function changeNavbar() {
if (hambInput.checked) {
navbar.style.background = "white"
//navbar.style.boxShadow = "0 0 0.314rem rgb(187, 187, 187)"
}
else {
setTimeout(()=> {
navbar.style.position = "relative"
navbar.style.background = ""
navbar.style.boxShadow = ""
}
,510)
}
}
function showDropdown(dropdown) {
let state = dropdown.style.display
initiativesDropdown.style.display = "none"
localesDropdown.style.display = "none"
if (state=="block") {
dropdown.style.display = "none"
}
else {
dropdown.style.display = "block"
}
}
function changeLocale(lang) {
localStorage.setItem("locale",lang)
let locSplit = location.href.split("/")
let localesSymbols = Object.keys(locales)
locSplit = locSplit.filter(x => !localesSymbols.includes(x))
let loc = locSplit.slice(0,locSplit.length-1).join("/") + "/" + lang + "/" + locSplit[locSplit.length-1]
location.href = loc
}
function fixHeading() {
if (locale=="ru") {
let func = () => {
if (logoText==undefined) {
setTimeout(func,100)
}
else {
if (((window.innerWidth < 1700 && window.innerWidth > 1400) || window.innerWidth < 400) && logoText.style.lineHeight!="100%") {
logoText.style.lineHeight = "120%"
logoText.style.top = "1rem"
logoText.style.width = "16rem"
}
else if (((window.innerWidth > 1700) || (window.innerWidth > 400 && window.innerWidth < 1400)) && logoText.style.lineHeight!="400%") {
logoText.style.lineHeight = "400%"
logoText.style.top = "0rem"
logoText.style.width = "auto"
}
}
}
func()
addEventListener("resize", func)
}
}
function hide(dropdown) {
let callback = () => {
dropdown.style.display = "none"
}
setTimeout(callback,100)
}
onMount(() => {
fixHeading()
})
</script>
<!-- Navigation bar -->
{#key loaded}
{#if Object.keys($content).length!=0}
<header bind:this={navbar} id="navbar">
<!-- Hamburger icon -->
<input bind:this={hambInput} type="checkbox" id="side-menu" on:click={changeNavbar}>
<label id="hamb" for="side-menu"><span id="hamb-line"></span></label>
<!-- Logo -->
<a id=logo-container href={"/" + locale + "/"}>
<img src="/img/common/flag.png" id="navbar-logo" alt="logo">
<span bind:this={logoText} id="navbar-logo-text" >{@html $content.orgName}</span>
</a>
<!-- Menu -->
<nav id="nav">
<ul id="menu">
<li><a href={"/"+locale+"/join-us"}>{$content.joinUs}</a></li>
<li><a href={"/"+locale+"/manifesto"}>{$content.manifesto}</a></li>
<!-- Options dropdown -->
<!-- A list of links pointing to different pages of the website. Implemented as a div opened on :hover-->
<li id="options-container">
<button on:click={() => showDropdown(initiativesDropdown)} on:focusout={() => hide(initiativesDropdown)} class="options-button">{$content.initiatives}</button>
<div bind:this={initiativesDropdown} class="options-dropdown">
<a href={"/"+locale+"/groups"}>{$content.groups}</a>
<a href={"/"+locale+"/communes"}>{$content.communes}</a>
<a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a>
<a href={"/"+locale+"/parties"}>{$content.parties}</a>
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>
<li><a href={"/"+locale+"/auth"}>{$content.login}</a></li>
<li id="locales">
<button on:click={() => showDropdown(localesDropdown)} on:focusout={() => hide(localesDropdown)}>
<picture>
<source srcset="/img/common/globe.webp">
<source srcset="/img/common/globe.png">
<img id="locales-img" alt="globe">
</picture>
</button>
</li>
<div bind:this={localesDropdown} class="options-dropdown">
{#each Object.entries(locales) as [loc,name]}
<button on:click={() => changeLocale(loc)}>{name}</button>
{/each}
</div>
</ul>
</nav>
</header>
{/if}
{/key}
<style>
@import '/css/common.css';
@import '/css/navbar.css';
</style>

View File

@@ -0,0 +1,413 @@
<svelte:options tag="parties-add-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import { writable } from 'svelte/store';
import { loadLocaleContent, getData, sendData } from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/parties.json",callback)
let confirmationMsg
let addressInput
let contactInput
let addressVec
let userPinLat = 0
let userPinLng = 0
let userPin = createPin(0,0)
userPin.setOpacity(0)
let locale = loadLocaleContent(content,"parties-component",loaded)
loadLocaleContent(content,"countries",loaded)
function createPin(lat,lng) {
let markerIcon = new L.Icon({
iconUrl: '/img/common/markers/marker-black.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
return L.marker([lat,lng], {icon: markerIcon})
}
function updatePin(marker,lat,lng) {
let newLatLng = L.latLng(lat, lng); // Replace with the desired coordinates
marker.setLatLng(newLatLng)
}
function reverseGeocodeLocal(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressInput.value = fullAddress
resizeInput(addressInput)
}
getData(url,callback)
}
function reverseGeocode(latitude, longitude) {
let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=jsonv2&accept-language=en`;
let callback = (response) => {
// Parse the response JSON
response = JSON.parse(response)
// Extract the address information from the response
let address = response.address
let city = address.city || address.town || address.village || address.hamlet
let state = address.state
let country = address.country
let fullAddress = country
if (state!=undefined) {
fullAddress += ", " + state
}
else {
state = ""
}
if (city!=undefined) {
fullAddress += ", " + city
}
else {
city = ""
}
addressVec = [country,state,city]
}
getData(url,callback)
}
function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
userPin.addTo(map)
map.on('click', function(event) {
let lat = event.latlng.lat;
let lng = event.latlng.lng;
userPinLat = lat
userPinLng = lng
updatePin(userPin,lat,lng)
userPin.setOpacity(1)
reverseGeocodeLocal(lat, lng)
reverseGeocode(lat, lng)
})
}
function updateConfirmationMsg(response) {
if (response!==false) {
confirmationMsg.innerHTML = "You have been added to our database! Now go to our Discord to verify yourself."
confirmationMsg.style.color = "green"
}
else {
confirmationMsg.innerHTML = "Something went wrong."
confirmationMsg.style.color = "red"
}
}
function submitLocation() {
if (addressVec!=undefined) {
let data = {
country: addressVec[0],
state: addressVec[1],
town: addressVec[2],
latitude: userPinLat,
longitude: userPinLng,
contact: contactInput.value
}
if (data.state=="") {
data.state = null
}
if (data.town=="") {
data.town = null
}
if (data.contact=="") {
data.contact = null
}
let url = "/" + locale + "/parties-add-post/"
sendData(url,data,updateConfirmationMsg)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
onMount(() => {
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>Add a Party</h1>
<img id="parties-img" src="/img/common/parties.svg" alt="parties">
<p class="description">If there are no parties in your country which you can join then do the following:</p>
<ol>
<li>Click on the map to show us where you are located;</li>
<li>Add a way to contact you or leave blank for a pin to point to our discord;</li>
<li>Press "Submit" to add yourself to our map;</li>
<li>Verify yourself by having a chat with us at our Discord server to show on the map;</li>
</ol>
<div id="address-input-wrapper" class="input-label-wrapper">
<label for="address-input">Location: </label>
<div class="input-wrapper">
<input bind:this={addressInput} on:input={() => resizeInput(addressInput)} id="address-input" type="text" readonly>
<div class="ghost-input"></div>
</div>
</div>
<div class="input-label-wrapper">
<label for="contact-input">Contact: </label>
<div class="input-wrapper">
<input bind:this={contactInput} on:input={() => resizeInput(contactInput)} id="contact-input" type="text">
<div class="ghost-input"></div>
</div>
</div>
<button id="submit-button" on:click={submitLocation}>Submit</button>
<p id="confirmation-msg" bind:this={confirmationMsg}></p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
#confirmation-msg {
margin-top: 0.5rem;
margin-bottom: 2rem;
}
ol li {
margin-left: 3rem;
margin-bottom: 0.5rem;
}
label {
display: inline-block;
font-family: var(--serif,serif);
font-size: 1.15rem;
line-height: 160%;
color: #222222;
width: 5.5rem;
}
input, .ghost-input {
font-size: 1.15rem;
font-family: var(--serif,serif);
}
input {
height: 2.5rem;
display: inline-block;
position: relative;
}
#address-input, #contact-input {
width: 100%;
}
#address-input-wrapper {
margin-top: 2rem;
margin-bottom: 1rem;
}
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 5.5rem);
min-width: min(20rem, calc(100% - 5.5rem));
height: 2.5rem;
}
.input-label-wrapper {
display: flex;
justify-content: start;
}
.input-label-wrapper label {
position: relative;
top: 0.3rem;
}
.description {
margin-bottom: 1rem;
}
#submit-button {
display: block;
margin: auto;
margin-top: 2rem;
padding: 1rem 2rem;
font-size: 1.4rem;
font-family: var(--sans-serif,sans-serif);
border: 0rem solid black;
border-radius: 0.5rem;
background: #cb1816;
color: white;
}
#add-prompt {
margin-bottom: 2rem;
}
#parties-img {
position: absolute;
width: 14rem;
left: 50%;
transform: translate(-50%);
z-index: 0;
opacity: 0.2;
}
#text-container>:nth-child(3) {
margin-top: 8rem;
}
.country-name {
margin-bottom: 0.5rem;
}
.country-block {
margin-bottom: 2rem;
}
.location-info {
margin-bottom: 0.75rem;
}
.location-info p {
margin-bottom: 0;
}
a {
color: #DD1C1A;
}
#map {
--height: 30rem;
--width: 100%;
--margin-top: 2rem;
--margin-bottom: 0.5rem;
}
#text-container {
position: relative;
max-width: calc(100vw - 4rem);
margin: auto;
}
h1 {
margin-bottom: 1rem;
font-size: 2.2rem;
text-align: center;
}
h3 {
margin-bottom: 1rem;
}
#container {
margin: auto;
max-width: 800px;
margin-top: 1rem;
margin-bottom: 4rem;
}
#container p {
text-align: justify;
}
</style>

View File

@@ -4,8 +4,9 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { partiesByCountry, addMarkersParties } from '/js/parties.js' import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { loadLocaleContent } from "/js/libraries/serverTools.js" import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addPartyPinContent } from "/js/mapFuncs.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
@@ -13,13 +14,45 @@
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries
let entriesByCountry
let locale = loadLocaleContent(content,"parties-component",loaded)
loadLocaleContent(content,"countries",loaded) loadLocaleContent(content,"countries",loaded)
loadLocaleContent(content,"parties-component",loaded)
function mapCallbackParties(createMap,content) { let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/parties.json",callback)
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2) let map = createMap([22, 0],2)
addMarkersParties(map,content) addMarkersEntries(entries,entriesByCountry,map,content,locale,addPartyPinContent,"gold")
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
} }
onMount(() => { onMount(() => {
@@ -28,7 +61,7 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==2} {#if $loaded==3}
<div id="container"> <div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">--> <!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container"> <div id="text-container">
@@ -36,26 +69,27 @@
<img id="party-img" src="/img/common/parties.svg" alt="party"> <img id="party-img" src="/img/common/parties.svg" alt="party">
<p class="description">{$content.p1}</p> <p class="description">{$content.p1}</p>
<h3>{$content.subheading1}</h3> <h3>{$content.subheading1}</h3>
<map-component id="map" callback={(createMap) => mapCallbackParties(createMap,$content)}></map-component> <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p> <p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(partiesByCountry) as [name,parties]} {#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{$content[name]}</h4> <h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block"> <div class="country-block">
{#each parties as party} {#each entries as entry}
<div class="location-info"> <div class="location-info">
<div class="img-general-info"> <div class="img-general-info">
<picture> <picture>
<source srcset={"/img/parties/"+party.logo+".webp"}> <source srcset={"/img/parties/"+entry.logo+".webp"}>
<source srcset={"/img/parties/"+party.logo+".jpg"}> <source srcset={"/img/parties/"+entry.logo+".jpg"}>
<img class="party-logo" alt="logo"> <img class="party-logo" alt="logo">
</picture> </picture>
<div> <div>
<p><b>{$content.name}: </b>{party.name}</p> <p><b>{$content.name}: </b>{entry.name}</p>
<p><b>{$content.location}: </b>{$content[party.location[0]]}</p> <p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.link}: </b><a href={party.link} target=;_blank; rel=noreferrer>{party.link}</a></p> <p><b>{$content.website}: </b><a href={entry.website} target=;_blank; rel=noreferrer>{entry.website}</a></p>
<p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div> </div>
</div> </div>
<p><b>{$content.description}: </b>{$content[party.description]}</p> <p><b>{$content.description}: </b>{entry.description}</p>
</div> </div>
{/each} {/each}
</div> </div>
@@ -126,7 +160,7 @@
.party-logo { .party-logo {
position: relative; position: relative;
right: 0; right: 0;
max-height: 5.5rem; max-height: 6.5rem;
max-width: 100%; max-width: 100%;
border-radius: 1rem; border-radius: 1rem;
} }

View File

@@ -0,0 +1,195 @@
<svelte:options tag="partners-component" />
<script>
// Import statements
import { onMount } from 'svelte'
import { writable } from 'svelte/store';
import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
// Import components
import "/js/components/map-component.js"
// Main code
let loaded = writable(0)
let content = writable({})
let entries
let entriesByCountry
let locale = loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"countries",loaded)
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/groups.json",callback)
function addGroupPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["Group"]+"</b><br>"
for (let field of ["location","members","contact"]) {
let fieldText = content[field] + ": "
if (field=="contact") {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
let locationString
if (locale=="en") {
locationString = location.map(x => x).join(", ")
}
else {
locationString = location.map(x => translate(content, x)).join(", ")
}
text += fieldText + locationString + "<br>"
coordinates = [g.latitude,g.longitude]
}
else {
text += fieldText + g[field] + "<br>"
}
}
return {text,coordinates}
}
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addGroupPinContent,"green")
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
onMount(() => {
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>{$content.groups}</h1>
<img id="groups-img" src="/img/common/groups.svg" alt="groups">
<p class="description">{$content.p1}</p>
<h3>{$content.subheading1}</h3>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block">
{#each entries as entry}
<div class="location-info">
<p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.members}: </b>{entry.members}</p>
<p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div>
{/each}
</div>
{/each}
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
.description {
margin-bottom: 1rem;
}
#add-prompt {
margin-bottom: 2rem;
}
#groups-img {
position: absolute;
width: 14rem;
left: 50%;
transform: translate(-50%);
z-index: 0;
opacity: 0.2;
}
#text-container>:nth-child(3) {
margin-top: 8rem;
}
.country-name {
margin-bottom: 0.5rem;
}
.country-block {
margin-bottom: 2rem;
}
.location-info {
margin-bottom: 0.75rem;
}
.location-info p {
margin-bottom: 0;
}
a {
color: #DD1C1A;
}
#map {
--height: 30rem;
--width: 100%;
--margin-bottom: 0.5rem;
}
#text-container {
position: relative;
max-width: calc(100vw - 4rem);
margin: auto;
}
h1 {
margin-bottom: 1rem;
font-size: 2.2rem;
text-align: center;
}
h3 {
margin-bottom: 1rem;
}
#container {
margin: auto;
max-width: 800px;
margin-top: 1rem;
margin-bottom: 4rem;
}
#container p {
text-align: justify;
}
</style>

View File

@@ -4,18 +4,56 @@
// Import statements // Import statements
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { loadLocaleContent } from "/js/libraries/serverTools.js" import { loadLocaleContent, getData} from "/js/libraries/serverTools.js"
import { partnersByCountry } from '/js/partners.js' import { addMarkersEntries, translate } from "/js/libraries/mapTools.js"
import { addPartnersPinContent } from "/js/mapFuncs.js"
// Import components // Import components
import "/js/components/map-component.js" import "/js/components/map-component.js"
// Main code // Main code
let loaded = writable(0) let loaded = writable(0)
let content = writable({}) let content = writable({})
let entries
let entriesByCountry
let locale = loadLocaleContent(content,"partners-component",loaded)
loadLocaleContent(content,"countries",loaded) loadLocaleContent(content,"countries",loaded)
loadLocaleContent(content,"partners-component",loaded)
let callback = (response) => {
entries = JSON.parse(response)
entriesByCountry = {}
for (let g of entries) {
let country = g.country
if (g.contact==null) {
g.contact = "https://discord.gg/Qk8KUk787z"
}
if (country in entriesByCountry) {
entriesByCountry[country].push(g)
}
else {
entriesByCountry[country] = [g]
}
}
loaded.update((val) => {
return val + 1
})
}
getData("/assets/partners.json",callback)
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
addMarkersEntries(entries,entriesByCountry,map,content,locale,addPartnersPinContent,"blue")
}
function getCountry(x) {
return locale=="en" ? x : translate($content,x)
}
function getAddress(g) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
onMount(() => { onMount(() => {
@@ -23,7 +61,7 @@
</script> </script>
{#key $loaded} {#key $loaded}
{#if $loaded==2} {#if $loaded==3}
<div id="container"> <div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">--> <!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container"> <div id="text-container">
@@ -31,25 +69,27 @@
<img id="hands-img" src="/img/common/handshake.svg" alt="hands"> <img id="hands-img" src="/img/common/handshake.svg" alt="hands">
<p>{$content.p1}</p> <p>{$content.p1}</p>
<h3>{$content.subheading1}</h3> <h3>{$content.subheading1}</h3>
{#each Object.entries(partnersByCountry) as [name,partners]} <map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)}></map-component>
<h4 class="country-name">{$content[name]}</h4> <p id="add-prompt">{$content["map-prompt"]}</p>
{#each Object.entries(entriesByCountry) as [name,entries]}
<h4 class="country-name">{getCountry(name)}</h4>
<div class="country-block"> <div class="country-block">
{#each partners as partner} {#each entries as entry}
<div class="location-info"> <div class="location-info">
<div class="img-general-info"> <div class="img-general-info">
<picture> <picture>
<source srcset={"/img/partners/"+partner.logo+".webp"}> <source srcset={"/img/partners/"+entry.logo+".webp"}>
<source srcset={"/img/partners/"+partner.logo+".jpg"}> <source srcset={"/img/partners/"+entry.logo+".jpg"}>
<img class="partner-logo" alt="logo"> <img class="partner-logo" alt="logo">
</picture> </picture>
<div> <div>
<p><b>{$content.name}: </b>{partner.name}</p> <p><b>{$content.name}: </b>{entry.name}</p>
<p><b>{$content.type}: </b>{$content[partner.type]}</p> <p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.location}: </b>{$content[partner.location[0][0]] + (partner.location[0][1]=="" ? "" : ", " + $content[partner.location[0][1]])}</p> <p><b>{$content.website}: </b><a href={entry.website} target=;_blank; rel=noreferrer>{entry.website}</a></p>
<p><b>{$content.link}: </b><a href={partner.link} target=;_blank; rel=noreferrer>{partner.link}</a></p> <p><b>{$content.contact}: </b><a href={entry.website} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
</div> </div>
</div> </div>
<p><b>{$content.description}: </b>{$content[partner.description]}</p> <p><b>{$content.description}: </b>{entry.description}</p>
</div> </div>
{/each} {/each}
</div> </div>
@@ -125,7 +165,7 @@
#map { #map {
--height: 30rem; --height: 30rem;
--width: 100%; --width: 100%;
--margin-bottom: 3rem; --margin-bottom: 0.5rem;
} }
#text-container { #text-container {
@@ -140,7 +180,7 @@
} }
h3 { h3 {
margin-bottom: 2rem; margin-bottom: 1rem;
} }
#container { #container {

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-communes" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your commune to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@@ -0,0 +1,284 @@
<svelte:options tag="profile-component" />
<script>
// Import libraries
import { onMount, afterUpdate, setContext } from 'svelte'
import { writable } from 'svelte/store'
import * as AuthTools from "/js/libraries/authTools.js"
import {svgFromObject} from "/js/libraries/miscTools.js"
//Import components
import "/js/components/pane-aligner.js"
import "/js/components/profile-general.js"
import "/js/components/profile-groups.js"
import "/js/components/profile-communes.js"
import "/js/components/profile-coops.js"
import "/js/components/profile-parties.js"
import "/js/components/groups-add-component.js"
// Main code
AuthTools.redirectNotLogged()
let root
let general
let groups
let communes
let coops
let parties
let panes
let groupsAdd
let generalButton
let groupsButton
let communesButton
let coopsButton
let partiesButton
let buttons
let currentPaneIndex = 0
let locationPopup
let maps = {}
let user = {}
let loaded = writable(0)
let reloadTriggerVal = writable(0)
AuthTools.getUser(user,loaded)
function changePane(pane,button) {
for (let p of panes) {
p.style.display = "none"
}
for (let b of buttons) {
styleField(b,400,"#636363")
}
pane.style.display = "initial"
styleField(button,500,"#c52a28")
}
function styleField(div,weight,color) {
let svgObject = div.querySelector("object")
if (svgObject==null) {
let f = () => styleField(div,weight,color)
setTimeout(f,100)
}
else {
let svgItem = svgFromObject(svgObject)
if (svgItem==null) {
let f = () => styleField(div,weight,color)
setTimeout(f,100)
}
else {
div.style.fontWeight = weight
svgItem.setAttribute("fill", color)
}
}
}
function fillFields() {
if (Object.keys(user).length!=0 && root!=undefined) {
for (let b of buttons) {
styleField(b,400,"#636363")
}
styleField(buttons[currentPaneIndex],500,"#c52a28")
}
else {
setTimeout(fillFields, 100)
}
}
function valid(el) {
return (el!=undefined) && (el!=null)
}
function init() {
panes = [general,groups,communes,coops,parties]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton]
if ($loaded==1 && panes.every(x => valid(x)) && buttons.every(x => valid(x))) {
panes = [general,groups,communes,coops,parties]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton]
fillFields()
general.style.display = "initial"
}
else {
let f = () => init()
setTimeout(f,100)
}
}
function reloadTrigger() {
reloadTriggerVal.update((val) => {
return val + 1
})
}
setContext("profile-component",{user,maps,reloadTrigger})
onMount(() => {
init()
})
</script>
<!--
<div bind:this={locationPopup} class="overlay" style="display: none">
<div id="location-overlay-content">
</div>
<button class="overlay-button" on:click={() => locationPopup.style.display = "none"}></button>
</div>
-->
<pane-aligner>
<div id="left-column" class="pane" slot="sidebar-left" bind:this={root}>
<button bind:this={generalButton} on:click={() => changePane(general,generalButton)}>
<object id="general-img" class="icons" type="image/svg+xml" data="/img/profile/icons/general.svg" title="general"></object>
<span>general</span>
</button>
<button bind:this={groupsButton} on:click={() => changePane(groups,groupsButton)}>
<object id="groups-img" class="icons" type="image/svg+xml" data="/img/common/groups.svg" title="groups"></object>
<span>groups</span>
</button>
<button bind:this={communesButton} on:click={() => changePane(communes,communesButton)}>
<object id="communes-img" class="icons" type="image/svg+xml" data="/img/common/communes.svg" title="communes"></object>
<span>communes</span>
</button>
<button bind:this={coopsButton} on:click={() => changePane(coops,coopsButton)}>
<object id="coops-img" class="icons" type="image/svg+xml" data="/img/common/coops.svg" title="coops"></object>
<span>cooperatives</span>
</button>
<button bind:this={partiesButton} on:click={() => changePane(parties,partiesButton)}>
<object id="parties-img" class="icons" type="image/svg+xml" data="/img/common/parties.svg" title="parties"></object>
<span>parties</span>
</button>
<button on:click={AuthTools.logout} id="logout-button">
<object id="logout-img" class="icons" type="image/svg+xml" data="/img/profile/icons/logout.svg" title=""></object>
<span>logout</span>
</button>
</div>
<div id="main-column" slot="main">
{#key $loaded}
{#if $loaded==1}
<profile-general bind:this={general} style="display: none;"></profile-general>
<profile-groups bind:this={groups} style="display: none;"></profile-groups>
<profile-communes bind:this={communes} style="display: none;"></profile-communes>
<profile-coops bind:this={coops} style="display: none;"></profile-coops>
<profile-parties bind:this={parties} style="display: none;"></profile-parties>
{/if}
{/key}
</div>
</pane-aligner>
<style>
@import '/css/common.css';
#general-img {
top: 0rem;
}
#groups-img {
top: 0.3rem;
}
#coops-img {
top: 0rem;
}
#parties-img {
top: 0rem;
}
#logout-img {
width: 1.5rem;
}
#logout-button {
padding-top: 1rem;
padding-left: 0.1rem;
}
#left-column {
position: relative;
display: flex;
flex-direction: column;
width: 15.2rem;
padding: 2rem;
border-radius: 0.64rem 0.64rem 0.64rem 0.64rem;
gap: 1rem;
}
.icons {
position: relative;
width: 1.8rem;
}
#left-column button span {
position: absolute;
padding-left: 3.4rem;
margin-top: 0rem;
font-family: var(--sans-serif,sans-serif);
}
#left-column button {
display: flex;
flex-direction: row;
}
#main-column {
padding: 1rem 2rem 1rem 2rem;
height: 100%;
width: 100%;
border-radius: 0 0.64rem 0.64rem 0;
flex-grow: 1;
flex-shrink: 1;
min-height: 20rem;
}
pane-aligner {
--width-main: 800px;
--width-left: 10.5rem;
}
@media only screen and (max-width: 1340px) {
#left-column {
position: relative;
margin-left: 0rem;
width: 100%;
border-radius: 0.64rem 0.64rem 0rem 0;
}
#main-column {
border-radius: 0.64rem;
padding: 3rem 0.5rem;
padding-bottom: 1.5rem;
border-radius: 0rem 0rem 0.64rem 0.64rem;
width: 100%;
}
#logout-button {
position: relative;
bottom: 0;
}
#left-column button {
margin-left: auto;
margin-right: auto;
width: 10rem;
}
#logout-button {
padding-top: 1rem;
margin-bottom: 0rem;
}
}
</style>

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-coops" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your cooperative to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@@ -0,0 +1,433 @@
<svelte:options tag="profile-general" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
//Import components
import "/js/components/select-component.js"
import "/js/components/switch-component.js"
//Export statements
// Main code
let emailInput
let section
let saveEmailButton
let changePasswordInputDiv
let changePasswordMsg
let savePasswordButton
let passwordInput
let changePasswordDiv
let passwordVisibilityButton
let emailMsg
let passwordDiv
let emailDiv
let emailInputDiv
let prevEmail
let context = getContext("profile-component")
let user = context.user
function showSaveButton(button) {
prevEmail = emailInput.value
button.style.display = "initial"
emailMsg.style.display = "inline"
let windowWidth = window.innerWidth
if (windowWidth<1100) {
emailInputDiv.style.marginTop = "1rem"
emailDiv.style.flexDirection = "column"
}
else {
//emailInput.style.width = "19rem"
}
}
function saveEmail() {
let email = emailInput.value
if (AuthTools.checkEmail(email,emailMsg)) {
if (email!=user.email) {
AuthTools.changeUser("email",email,user)
}
resetEmailField()
}
}
function resetEmailField() {
if (prevEmail!=undefined) {
emailInput.value = prevEmail
}
emailInput.style.width = "100%"
emailMsg.style.display = "none"
emailDiv.style.flexDirection = "row"
emailInputDiv.style.marginTop = "0rem"
saveEmailButton.style.display = "none"
emailMsg.innerHTML = ""
}
function launchChangePassword() {
let windowWidth = window.innerWidth
if (windowWidth<1100) {
changePasswordInputDiv.style.display = "flex";
}
else {
changePasswordInputDiv.style.display = "initial";
}
changePasswordDiv.style.display = "none";
passwordInput.focus()
}
function savePassword() {
let password = passwordInput.value
if (AuthTools.checkPassword(password,changePasswordMsg)) {
if (password!=user.password) {
AuthTools.changeUser("password",password,user)
}
changePasswordMsg.innerHTML = ""
resetPasswordField()
}
}
function resetPasswordField() {
changePasswordInputDiv.style.display = "none";
changePasswordDiv.style.display = "initial";
changePasswordMsg.innerHTML = ""
}
function fillFields() {
if (user!=null && Object.keys(user).length!=0 && section!=undefined) {
emailInput.value = user.email
}
else {
setTimeout(fillFields, 10)
}
}
function resizeInput(el) {
el.nextElementSibling.innerHTML = el.value
}
onMount(() => {
fillFields()
document.addEventListener("click", function(event) {
if (passwordDiv.focused) {
resetEmailField()
}
else if (emailDiv.focused) {
resetPasswordField()
}
else {
resetEmailField()
resetPasswordField()
}
})
})
</script>
<section bind:this={section} id="general-section">
<h2 class="title-highlight">General</h2>
<div bind:this={emailDiv} on:mouseenter={emailDiv.focused=true} on:mouseleave={emailDiv.focused=false}>
<div class="title-msg">
<span>Email:</span>
<span bind:this={emailMsg} id="signup-email-msg"></span>
</div>
<div bind:this={emailInputDiv} id="emailInputDiv">
<button bind:this={saveEmailButton} id="save-email" class="save-button" on:click={saveEmail}>save</button>
<div class="input-wrapper">
<input bind:this={emailInput} id="emailInput" class="text-input" type="text" on:click={() => showSaveButton(saveEmailButton)} on:input={() => resizeInput(emailInput)}>
<div class="ghost-input"></div>
</div>
</div>
</div>
<div bind:this={passwordDiv} on:mouseenter={passwordDiv.focused=true} on:mouseleave={passwordDiv.focused=false} id="change-password-line-wrapper">
<div id="change-password-line">
<div class="title-msg">
<span>Password:</span>
<span bind:this={changePasswordMsg} id="signup-password-msg"></span>
</div>
<div bind:this={changePasswordDiv} id="change-password-div">
<button id="change-password" on:click={launchChangePassword}>change
<object type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon"></object>
</button>
</div>
</div>
<div bind:this={changePasswordInputDiv} id="change-password-input-div">
<button bind:this={savePasswordButton} id="save-password" class="save-button" on:click={savePassword}>save</button>
<input bind:this={passwordInput} id="passwordInput" class="text-input" type="password">
<button bind:this={passwordVisibilityButton} class="eye-icon" on:click="{() => AuthTools.changePasswordVisibility(passwordVisibilityButton)}">
<object type="image/svg+xml" data="/img/auth/eye_icon.svg" title="eye icon"></object>
</button>
</div>
</div>
<div>
<div id="verifiedDiv">
<span>Verified:</span>
<span style="color: {user.verified ? "green" : "red"}">{user.verified}</span>
</div>
</div>
</section>
<style>
@import '/css/common.css';
#verifiedDiv {
display: inline;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 2rem;
width: 100%;
}
/*---General section-----------------------------------------------------------*/
.ghost-input {
display: block;
visibility: hidden;
height: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.input-wrapper {
display: inline-block;
max-width: calc(100% - 10rem);
min-width: 0rem;
height: 2.5rem;
position: relative;
right: 0
}
span {
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
}
#general-section {
display: flex;
flex-direction: column;
}
#general-section h2 {
margin: auto;
margin-top: 0;
margin-bottom: 0;
}
#general-section >div {
height: 3.5rem;
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0.14rem solid;
border-color: #cdcdcd;
}
#general-section >div >:first-child {
font-family: var(--sans-serif,sans-serif);
}
/* add padding to every line to center the diving line*/
#general-section >div:last-child {
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0;
}
#general-section >div div,
#general-section >div input,
#general-section >div :not(:first-child) input {
font-weight: 500;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
color: #292222;
border: 0;
}
#general-section >div>:last-child {
padding-right: 1.35rem;
}
.text-input {
position: relative;
width: 20.475rem;
direction: rtl;
border: 0;
outline: none;
bottom: 0.341rem;
}
/*---Email-------------------------------------------------------------------*/
#emailInput {
position: relative;
right: 0;
top: 0.1rem;
width: 100%;
}
#save-email {
display: none;
margin-top: 0.5rem;
}
#signup-email-msg,
#signup-password-msg {
position: relative;
display: inline-block;
color:red;
font-weight: 400;
white-space: nowrap;
}
#signup-email-msg {
display: none;
}
#general-section >div:nth-child(2) {
display: flex;
flex-direction: row;
}
#emailInputDiv {
display: flex;
flex-direction: row;
justify-content: right;
align-items: center;
height: 2rem;
width: 100%;
}
.title-msg {
display: flex;
gap: 0.5rem;
}
.title-msg * {
text-align: left;
}
/*---Change password-------------------------------------------------------------------*/
#change-password-line {
display: flex;
justify-content: space-between;
}
#change-password-div {
width: 9.3rem;
left: 0;
}
#change-password {
position: absolute;
cursor: pointer;
width: 8rem;
height: 2.73rem;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
text-align: right;
padding-right: 2rem;
margin-top: -0.55rem;
background-color: transparent;
}
#change-password > object {
pointer-events: none;
position: absolute;
width: 1.5rem;
right: 0.0rem;
}
#change-password-input-div {
display: none;
float: right;
position: relative;
margin-top: -1.7rem;
}
#passwordInput {
width: 15rem;
right: 0.65rem;
margin-left: 1.5rem;
}
.save-button {
position: relative;
bottom: 0.34rem;
margin-right: 0.6rem;
height: 2.73rem;
width: 4.778rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.512rem;
}
#save-password {
bottom: 0.6rem
}
.eye-icon {
display: inline-block;
position: relative;
cursor: pointer;
opacity: 0.25;
height: 2.2rem;
width: 1.7rem;
}
.eye-icon * {
pointer-events: none;
}
@media only screen and (max-width: 1100px) {
#change-password-line-wrapper {
display: flex;
flex-direction: column;
height: auto;
min-height: 4rem;
}
#change-password-input-div {
margin-top: 1rem;
justify-content: flex-end;
}
#general-section >div:nth-child(2) {
height: auto;
min-height: 4rem;
position: relative;
}
#passwordInput {
width: 100%;
bottom: 0;
}
#emailInput {
width: 100%;
}
#save-password {
bottom: 0rem
}
}
</style>

View File

@@ -0,0 +1,455 @@
<svelte:options tag="profile-groups" />
<script>
// Import statements
import { onMount, getContext } from 'svelte'
import { writable } from 'svelte/store'
import { getData, sendData } from "/js/libraries/serverTools.js"
//Import components
import "/js/components/select-component.js"
import "/js/components/switch-component.js"
//Export statements
// Main code
let section
let userGroups = []
let groupsRequests = []
let content = writable({})
let loaded = writable(0)
let keyRequests = 0
let numLoaded = 2
let mainPane
let groupsAdd
let membersInput
let saveMembersButton
let membersInputDiv
let contactInput
let saveContactButton
let contactInputDiv
let locale = "en"
let inputLocation
let inputContact
let inputMembers
let pencilMembers
let pencilContact
let pencilButtonMembers
let pencilButtonContact
let myGroupLocation
let myGroupStatus
let context = getContext("profile-component")
let maps = context.maps
function groups_callback(response) {
userGroups = JSON.parse(response)
context["userGroups"] = userGroups
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-user-groups",groups_callback)
function requests_callback(response) {
let parsed = JSON.parse(response)
groupsRequests.push(...parsed)
loaded.update((val) => {
return val + 1
})
}
getData("/xx/get-group-requests",requests_callback)
function getAddress(g) {
if (g!=undefined) {
let location = [g.country,g.state,g.town].filter(x => x!=null)
return location.map(x => locale=="en" ? x : translate($content,x)).join(", ")
}
else {
return "Create or join group"
}
}
function getContact(c) {
if (c==null) {
return "https://discord.gg/Qk8KUk787z"
}
else {
return c
}
}
function launchChangeLocation() {
showLocationOverlay()
}
function launchChangeMembers() {
}
function showSaveButton(button,input) {
if (!input.readOnly) {
button.style.display = "initial"
}
}
function resetMembersField() {
saveMembersButton.style.display = "none"
}
function resetContactField() {
saveContactButton.style.display = "none"
}
function saveMembers() {
let email = emailInput.value
if (AuthTools.checkEmail(email,emailMsg)) {
if (email!=user.email) {
AuthTools.changeUser("email",email,user)
}
resetMembersField()
}
}
function saveContact() {}
function updateUserGroup(newInfo) {
if (newInfo!=undefined) {
myGroupLocation.innerHTML = getAddress(newInfo)
}
}
function onLoadedGroups() {
let els = [saveMembersButton,saveContactButton,membersInputDiv,contactInputDiv]
if ($loaded==numLoaded && els.every(x => x!=undefined && x!=null)) {
document.addEventListener("click", function(event) {
let activeEl
let shadowRoot = this.activeElement.shadowRoot
if (shadowRoot!=null) {
activeEl = shadowRoot.activeElement
shadowRoot = activeEl.shadowRoot
if (shadowRoot!=null) {
activeEl = shadowRoot.activeElement
}
}
if (activeEl == membersInput || activeEl == saveMembersButton) {
resetContactField()
}
else if (activeEl == contactInput || activeEl == saveContactButton) {
resetMembersField()
}
else {
resetMembersField()
resetContactField()
}
})
context["updateUserGroup"] = updateUserGroup
inputLocation = getAddress(userGroups[0])
if (userGroups.length==0) {
inputContact = ""
inputMembers = ""
}
else {
let group = userGroups[0]
inputContact = getContact(group.contact)
inputMembers = group.members
let status = group.status
if (status!=undefined) {
if (status==0) {
myGroupStatus.innerHTML = "(pending)"
myGroupStatus.style.color = "#FFC90E"
}
else if (status==2) {
myGroupStatus.innerHTML = "(rejected)"
myGroupStatus.style.color = "#c52a28"
}
pencilMembers.style.display = "none"
pencilContact.style.display = "none"
pencilButtonContact.style.cursor = "default"
pencilButtonMembers.style.cursor = "default"
membersInput.readOnly = true
contactInput.readOnly = true
}
}
}
else {
let f = () => onLoadedGroups()
setTimeout(f, 100)
}
}
function focus(el) {
el.focus()
el.click()
}
function approveRequest(ind,user_id) {
sendData("/xx/group-approve-request",{user_id: user_id})
groupsRequests.splice(ind,1)
keyRequests = keyRequests + 1
}
function rejectRequest(ind,user_id) {
sendData("/xx/group-reject-request",{user_id: user_id})
groupsRequests.splice(ind,1)
keyRequests = keyRequests + 1
}
function launchGroupsAdd() {
groupsAdd.style.display = "block"
mainPane.style.display = "none"
if (maps["groupsAdd"]!=undefined) {
maps["groupsAdd"].invalidateSize()
}
}
function closeGroupsAdd() {
groupsAdd.style.display = "none"
mainPane.style.display = "block"
}
context["onLoadedGroups"] = onLoadedGroups
context["launchGroupsAdd"] = launchGroupsAdd
context["closeGroupsAdd"] = closeGroupsAdd
onMount(() => {
onLoadedGroups()
})
</script>
{#key $loaded}
{#if $loaded==numLoaded}
<div bind:this={mainPane}>
<h2>Groups</h2>
<div>
<h3 class="group-heading">My group</h3>
<span bind:this={myGroupStatus} class="status"></span>
</div>
<section bind:this={section} class="entries-section">
<div>
<div class="change-field-line">
<span>Location:</span>
<div class="change-field-div">
<button class="change-field-button" bind:this={myGroupLocation} on:click={launchGroupsAdd}>{inputLocation}
<object type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
<div>
<div class="change-field-line">
<span>Members:</span>
<div bind:this={membersInputDiv} class="change-field-div input-pencil">
<div class="save-button-wrapper">
<button bind:this={saveMembersButton} on:click={saveMembers} class="save-button" style="display: none">save</button>
</div>
<input bind:this={membersInput} id="membersInput" class="text-input" type="text" bind:value={inputMembers} on:click={() => showSaveButton(saveMembersButton,membersInput)}>
<button bind:this={pencilButtonMembers} class="text-input-pencil-button" on:click={() => {focus(membersInput)}}>
<object bind:this={pencilMembers} type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
<div>
<div class="change-field-line">
<span>Contact:</span>
<div bind:this={contactInputDiv} class="change-field-div input-pencil">
<div class="save-button-wrapper">
<button bind:this={saveContactButton} on:click={saveContact} class="save-button" style="display: none">save</button>
</div>
<input bind:this={contactInput} id="contactInput" class="text-input" type="text" bind:value={inputContact} on:click={() => showSaveButton(saveContactButton,contactInput)}>
<button bind:this={pencilButtonContact} class="text-input-pencil-button" on:click={focus(contactInput)}>
<object bind:this={pencilContact} type="image/svg+xml" data="/img/profile/icons/pencil.svg" title="pencil-icon" class="pencil"></object>
</button>
</div>
</div>
</div>
</section>
<h3>Requests</h3>
<section bind:this={section} class="entries-section">
{#key keyRequests}
{#each groupsRequests as req,ind}
<div>
<div class="change-field-line">
<span>{req.email}</span>
<div class="request-button-wrapper">
<button on:click={() => approveRequest(ind,req.user_id)} class="approve-button">approve</button>
<button on:click={() => rejectRequest(ind,req.user_id)} class="approve-button" style="display:visible">reject</button>
</div>
</div>
</div>
{/each}
{/key}
</section>
</div>
<!--Helper panes-->
<groups-add-component bind:this={groupsAdd} style="display: none;"></groups-add-component>
{/if}
{/key}
<style>
@import '/css/common.css';
.request-button-wrapper {
display: flex;
gap: 1rem;
}
.approve-button {
height: 2.7rem;
padding: 0rem 1rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.5rem;
margin-top: -0.5rem;
}
.group-heading {
display: inline-block;
}
.status {
display: inline-block;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
margin-left: 0.5rem;
}
input {
font-family: var(--sans-serif,sans-serif)
}
.text-input-pencil-button {
display: inline-block;
position: relative;
height: 2.7rem;
width: 2rem;
}
.text-input-pencil-button object {
top: 0rem;
}
.pencil {
pointer-events: none;
position: absolute;
width: 1.5rem;
right: 0.0rem;
}
.change-field-div input.text-input {
position: relative;
width: 20.475rem;
direction: rtl;
border: 0;
outline: none;
height: 2.7rem;
font-style: var(--sans-serif,sans-serif);
background: transparent;
margin-top: -0.5rem;
}
#membersInput {
width: 5rem;
}
#contactInput {
max-width: 18rem;
}
.save-button {
position: absolute;
right: 0;
top: -0.4rem;
margin-right: 0.6rem;
height: 2.7rem;
width: 5rem;
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
color: white;
background-color: var(--red);
border-color: var(--red);
border-radius: 0.5rem;
}
.save-button-wrapper {
display: inline-block;
position: relative;
height: 2rem;
}
h2 {
text-align: center;
margin-bottom: 0.5rem;
}
.entries-section {
margin-bottom: 1rem;
}
.entries-section >div {
height: 3.5rem;
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0.14rem solid;
border-color: #cdcdcd;
}
/* add padding to every line to center the diving line*/
.entries-section >div:last-child {
padding-bottom: 0.75rem;
padding-top: 0.75rem;
border-bottom: 0;
}
/*---Change field-------------------------------------------------------------------*/
.change-field-line {
display: flex;
justify-content: space-between;
}
.change-field-div {
width: max-content;
position: relative;
display: flex;
}
.change-field-button {
position: relative;
cursor: pointer;
height: 2.7rem;
font-size: 1.15rem;
font-family: var(--sans-serif,sans-serif);
font-weight: 500;
text-align: right;
padding-right: 1.9rem;
margin-top: -0.55rem;
background-color: transparent;
width: 100%;
}
/*---General section-----------------------------------------------------------*/
h3 {
margin-bottom: 0.5rem;
}
span {
font-family: var(--sans-serif,sans-serif);
font-size: 1.15rem;
}
</style>

View File

@@ -0,0 +1,29 @@
<svelte:options tag="profile-parties" />
<script>
// Import statements
import { onMount } from 'svelte'
import * as AuthTools from "/js/libraries/authTools.js"
// Main code
onMount(() => {
})
</script>
<h3>Under development</h3>
<p style=" position: relative; margin-top: 2rem;">Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your party to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@@ -12,8 +12,8 @@ server {
add_header Pragma public; add_header Pragma public;
add_header Cache-Control "public"; add_header Cache-Control "public";
} }
location ~* \.(?:css|js|json)$ { location ~* \.(?:css|js|json|txt)$ {
expires 1d; expires 1h;
add_header Pragma public; add_header Pragma public;
add_header Cache-Control "public"; add_header Cache-Control "public";
} }
@@ -22,6 +22,7 @@ server {
add_header Pragma public; add_header Pragma public;
add_header Cache-Control "public"; add_header Cache-Control "public";
} }
location /js/ {} location /js/ {}
location /css {} location /css {}
location /img/ {} location /img/ {}
@@ -29,16 +30,29 @@ server {
location /fonts/ {} location /fonts/ {}
location /favicon.ico {} location /favicon.ico {}
location /robots.txt {} location /robots.txt {}
location /sitemap.txt {}
location ~ /loaderio-58f125137ee61345d68285d88016ce2a {} location ~ /loaderio-58f125137ee61345d68285d88016ce2a {}
location / { map $request_uri $language_redirect {
proxy_pass http://127.0.0.1:8001/; ~^/(check-login|login-post|logout|signup-post|signup-google|change-user|get-user|confirm-email) ""; # Excluded URIs, no redirection
~^/([a-zA-Z]{2})/ ""; # Matches URIs that start with a two-letter language code and sets it to an empty string
default /en; # Redirects all other URIs to the /en prefix
} }
location / {
rewrite ^ $language_redirect$request_uri? permanent;
proxy_pass http://127.0.0.1:8001;
}
rewrite /en/communities /en/communes permanent;
rewrite /en/coops /en/cooperatives permanent;
rewrite /en/affiliates /en/partners permanent;
listen 443 http3; listen 443 http3;
listen 443 ssl http2; listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/libsoc.org/fullchain.pem; # managed by Certbot ssl_certificate /etc/letsencrypt/live/libsoc.org-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/libsoc.org/privkey.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/libsoc.org-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
@@ -49,6 +63,7 @@ server {
resolver 1.1.1.1 1.0.0.1 valid=300s; resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s; resolver_timeout 5s;
} }
server { server {
@@ -57,17 +72,20 @@ server {
server_name libsoc.org; server_name libsoc.org;
listen 443 ssl http2; # managed by Certbot listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/libsoc.org/fullchain.pem; # managed by Certbot ssl_certificate /etc/letsencrypt/live/libsoc.org-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/libsoc.org/privkey.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/libsoc.org-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
} }
server { server {
return 301 https://www.libsoc.org$request_uri;
listen 80; return 301 https://www.libsoc.org$request_uri;
listen [::]:80;
listen 80;
listen [::]:80;
server_name www.libsoc.org libsoc.org;
server_name www.libsoc.org libsoc.org;
} }

View File

@@ -25,8 +25,7 @@ SearchLight.Configuration.load() |> SearchLight.connect
#SearchLight.Migration.all_up!!(context=Server) #SearchLight.Migration.all_up!!(context=Server)
#SearchLight.Migration.status() #SearchLight.Migration.status()
cd(@__DIR__) p = "db/migrations/"
p = "migrations/"
files = readdir(p) files = readdir(p)
files = files[map(x -> x[end-1:end].=="jl", files)] files = files[map(x -> x[end-1:end].=="jl", files)]
inds = map(x -> parse(Int64,split(x,"_")[1]), files) inds = map(x -> parse(Int64,split(x,"_")[1]), files)
@@ -35,7 +34,7 @@ files = files[inds_sorted]
for f in files for f in files
try try
m = include(joinpath(p,f)) m = include(joinpath("migrations",f))
m.up() m.up()
catch catch
end end

View File

@@ -8,12 +8,9 @@ function up()
primary_key() primary_key()
column(:email, :string) column(:email, :string)
column(:password, :string, limit = 100) column(:password, :string, limit = 100)
column(:name, :string)
column(:profile_picture, :int)
column(:country, :int)
column(:newsletter, :bool)
column(:notifications, :int)
column(:confirmation_code, :string) column(:confirmation_code, :string)
column(:google_id, :string)
column(:verified, :bool)
] ]
end end

View File

@@ -0,0 +1,31 @@
module CreateTableGroups
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
import .DatabaseSupport: add_foreign_key, add_index
function up()
create_table(:groups) do
[
primary_key()
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:members, :int)
column(:user_id, :int)
]
end
add_foreign_key(:groups,:user_id,:users,:id)
add_index(:groups, :user_id)
end
function down()
drop_table(:groups)
end
end

View File

@@ -0,0 +1,25 @@
module CreateTableCommunes
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
function up()
create_table(:communes) do
[
primary_key()
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:members, :int)
column(:status, :string)
]
end
end
function down()
drop_table(:communes)
end
end

View File

@@ -0,0 +1,29 @@
module CreateTableCooperatives
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
function up()
create_table(:cooperatives) do
[
primary_key()
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:workers, :int)
column(:status, :string)
column(:logo, :string)
column(:name, :string)
column(:market, :string)
column(:website, :string)
]
end
end
function down()
drop_table(:cooperatives)
end
end

View File

@@ -0,0 +1,26 @@
module CreateTableParties
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
function up()
create_table(:parties) do
[
primary_key()
column(:country, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:logo, :string)
column(:name, :string)
column(:website, :string)
column(:contact, :string)
column(:description, :string)
column(:members, :string)
]
end
end
function down()
drop_table(:parties)
end
end

View File

@@ -0,0 +1,27 @@
module CreateTablePartners
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
function up()
create_table(:partners) do
[
primary_key()
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:logo, :string)
column(:name, :string)
column(:website, :string)
column(:description, :string)
]
end
end
function down()
drop_table(:partners)
end
end

View File

@@ -0,0 +1,41 @@
module CreateTableGroupsRequests
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
using .DatabaseSupport
import .DatabaseSupport: add_foreign_key, add_index, set_default
function up()
create_table(:groups_requests) do
[
primary_key()
column(:group_id, :integer)
column(:user_id, :integer)
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:longitude, :float)
column(:members,:integer)
column(:added, :bool)
column(:status,:Integer)
]
end
add_foreign_key(:groups_requests,:user_id,:users,:id)
add_foreign_key(:groups_requests,:group_id,:groups,:id)
add_index(:groups_requests, :user_id)
set_default("groups_requests","added",false)
end
function down()
drop_table(:groups_requests)
end
end

View File

@@ -0,0 +1,34 @@
module CreateTablePartiesRequests
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
using .DatabaseSupport
import .DatabaseSupport: set_default
function up()
create_table(:parties_requests) do
[
primary_key()
column(:id_given, :integer)
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:status, :string)
column(:verified, :bool)
column(:added, :bool)
]
end
set_default("parties_requests","verified",false)
set_default("parties_requests","added",false)
end
function down()
drop_table(:parties_requests)
end
end

View File

@@ -0,0 +1,32 @@
module CreateTableGroupsRequests
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
using .DatabaseSupport
function up()
create_table(:groups_requests) do
[
primary_key()
column(:id_given, :integer)
column(:country, :string)
column(:state, :string)
column(:town, :string)
column(:contact, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:verified, :bool)
column(:added, :bool)
]
end
set_default("groups_requests","verified",false)
set_default("groups_requests","added",false)
end
function down()
drop_table(:groups_requests)
end
end

View File

@@ -0,0 +1,32 @@
module CreateTablePartiesRequests
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
using .DatabaseSupport
import .DatabaseSupport: set_default
function up()
create_table(:parties_requests) do
[
primary_key()
column(:id_given, :integer)
column(:name, :string)
column(:country, :string)
column(:link, :string)
column(:latitude, :float)
column(:longitude, :float)
column(:verified, :bool)
column(:added, :bool)
]
end
set_default("parties_requests","verified",false)
set_default("parties_requests","added",false)
end
function down()
drop_table(:parties_requests)
end
end

Some files were not shown because too many files have changed in this diff Show More