Added trade unions

This commit is contained in:
a-ill 2023-08-17 12:01:26 +03:00
parent 8e88a5a895
commit 73ece83c60
63 changed files with 1402 additions and 55 deletions

View File

@ -0,0 +1,279 @@
module TradeUnionsController
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 = "trade_unions"
dict_layouts = Dict(
:trade_unions => generate_layout_html("main",controller,"trade_unions",libraries=["Leaflet"]),
:trade_unions_add => generate_layout_html("main",controller,"trade_unions_add",libraries=["Leaflet"]),
)
#---Page info-----------------------------------------------------
const trade_unions_info = Dict(
"en" => Dict(
:title => "LibSoc - Trade Unions",
: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 trade_unions()
locale = get_locale()
html(:trade_unions,:trade_unions, layout = dict_layouts[:trade_unions], context = @__MODULE__,
title = trade_unions_info[locale][:title],
description = trade_unions_info[locale][:description]
)
end
function trade_unions_add()
locale = get_locale()
html(:trade_unions,:trade_unions_add, layout = dict_layouts[:trade_unions_add], context = @__MODULE__,
title = trade_unions_info[locale][:title],
description = trade_unions_info[locale][:description]
)
end
function trade_unions_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_trade_unions" => ["*"]], where_data=["user_id" => user_id])
has_group = !isempty(existing_user_group_data)
delete!(data,"group_id")
group_id = insert_into_table("trade_unions",data, "RETURNING id")[1,1]
if has_group
user_trade_unions_id = existing_user_group_data[1,"id"]
prev_group_id = existing_user_group_data[1,"group_id"]
update_table("users_trade_unions",Dict("group_id" => group_id), where_data=["id" => user_trade_unions_id])
members = select_from_table(["trade_unions" => ["members"]], where_data=["id" => prev_group_id])[1,1]
if (members==1)
delete_from_table("trade_unions",["id" => prev_group_id])
else
update_table("trade_unions",Dict("members" => members - 1), where_data=["id" => id])
end
else
dict_users_trade_unions = Dict("user_id" => user.id, "group_id" => group_id)
insert_into_table("users_trade_unions",dict_users_trade_unions)
end
compile("trade_unions")
else
data["status"] = 0
data["user_id"] = user_id
insert_into_table("trade_unions_requests",data)
end
elseif mode==1 # Join
data["user_id"] = user_id
if exist_in_table("users_trade_unions",["group_id" => data["group_id"]])
if exist_in_table("trade_unions_requests",["user_id" => user_id])
delete_from_table("trade_unions_requests",["user_id" => user_id])
end
data["status"] = 0
insert_into_table("trade_unions_requests",data)
else
group_id = data["group_id"]
members = select_from_table("trade_unions" => ["members"], where_data = ["id" => group_id])[1,1]
dict = Dict("members" => members + 1)
update_table("trade_unions",dict, where_data=["id" => group_id])
dict_users_trade_unions = Dict("user_id" => user_id, "group_id" => group_id)
insert_into_table("users_trade_unions",dict_users_trade_unions)
end
elseif mode==2 # Move
existing_user_group_data = select_from_table(["users_trade_unions" => ["*"]], 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("trade_unions",data, where_data=["id" => group_id])
compile("trade_unions")
elseif mode==3 # Leave
existing_user_group_data = select_from_table(["users_trade_unions" => ["*"]], where_data=["user_id" => user_id])
if size(existing_user_group_data,1)==0
if exist_in_table("trade_unions_requests",["user_id" => user_id])
delete_from_table("trade_unions_requests",["user_id" => user_id])
end
else
delete_from_table("users_trade_unions",["user_id" => user_id])
end
end
return nothing
end
function get_user_trade_unions()
local data_dicts
user_id = get_authentication()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
data_dicts = []
if isnothing(group_id)
local data
data = select_from_table("trade_unions_requests" => ["*"], where_data = ["user_id" => user_id,"status" => 0])
if size(data,1)==0
data = select_from_table("trade_unions_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("trade_unions" => ["*"], 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("trade_unions" => ["*"], 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()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
data_dicts = []
if !isnothing(group_id)
user_ids = select_from_table("trade_unions_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()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
members = select_from_table("trade_unions" => ["members"], where_data = ["id" => group_id])[1,1]
dict = Dict("members" => members + 1)
update_table("trade_unions",dict, where_data=["id" => group_id])
update_table("trade_unions_requests",Dict("status" => 1), where_data=["group_id" => group_id, "user_id" => data["user_id"]])
dict_users_trade_unions = Dict("user_id" => data["user_id"], "group_id" => group_id)
insert_into_table("users_trade_unions",dict_users_trade_unions)
compile("trade_unions")
return nothing
end
function reject_request()
data = copy(jsonpayload())
user_id = get_authentication()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
update_table("trade_unions_requests",Dict("status" => 2), where_data=["group_id" => group_id, "user_id" => data["user_id"]])
return nothing
end
function changeMemberCount()
user_id = get_authentication()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
data = copy(jsonpayload())
update_table("trade_unions",data, where_data=["id" => group_id])
compile("trade_unions")
end
function change_group()
user_id = get_authentication()
trade_unions_ids = select_from_table("users_trade_unions" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
group_id = isempty(trade_unions_ids) ? nothing : trade_unions_ids[1]
if !isnothing(group_id)
data = copy(jsonpayload())
data_new = Dict()
ks = keys(data)
for x in ["members","contact"]
if x in ks
data_new[x] = data[x]
end
end
if !isempty(data_new)
update_table("trade_unions",data_new, where_data=["id" => group_id])
compile("trade_unions")
end
end
return nothing
end
end

View File

@ -0,0 +1 @@
<trade-unions-component></trade-unions-component>

View File

@ -0,0 +1 @@
<trade-unions-add-component></trade-unions-add-component>

View File

@ -13,9 +13,9 @@ export function debounce(func, timeout){
}
export function svgFromObject(object) {
var objectDoc = object.contentDocument;
var svgItem = objectDoc.querySelector("path");
return svgItem
var objectDoc = object.contentDocument
var svgItems = objectDoc.querySelectorAll("path")
return svgItems
}
export function rem2px(rem) {

View File

@ -6,7 +6,15 @@ export function addGroupPinContent(g,content,locale) {
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>"
if (g.contact.includes("@") && g.contact.trim().split(" ").length==1) {
text += fieldText + "<a href='mailto:" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (g.contact.includes("http")) {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else {
text += fieldText + g.contact + "<br>"
}
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
@ -176,3 +184,30 @@ export function addPartnersPinContent(g,content,locale) {
}
return {text,coordinates}
}
export function addTradeUnionPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["TradeUnion"]+"</b><br>"
for (let field of ["name","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}
}

View File

@ -1,13 +1,15 @@
{
"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",
"groupsText": "We organize groups for the purposes of education, advocacy, anti-fascist action 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, anti-fascist action 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.",
"communesTitle": "COMMUNES",
"communesText": "We establish communes based on libertarian socialist principles, where commune members have ownership over land, houses, and the means of production as well as make decisions using direct democracy. We are gradually expanding our socialist world, one commune at a time.",
"cooperativesTitle": "COOPERATIVES",
"cooperativesText": "We form worker cooperatives to finance the operations of our groups and communes. Recognizing that economic power influences political power, we consider the establishment of cooperatives to be one of the initial steps towards achieving socialism.",
"cooperativesText": "We form worker cooperatives to finance the operations of our groups and communes. Recognizing that economic power influences political power, we consider the establishment of cooperatives to be a vital activity.",
"partiesTitle": "PARTIES",
"partiesText": "We create political parties in order to push for reforms allowing us to easier further our goals, to move the Overton window as well as to gain popularity. However, we recognize that we cannot achieve libertarian socialism through institutions which act contrary to our goals.",
"tradeUnionsTitle": "TRADE UNIONS",
"tradeUnionsText": "We promote trade unions, which empower laborers to collectively advocate for fair treatment, just wages, and improved working conditions. Struggle at a place of work is an integral part of our strategy to achieve libertarian socialism.",
"findUs": "Find Us",
"whatNow": "What Now?",
"joinUs": "Join Us",

View File

@ -7,6 +7,7 @@
"communes": "Communes",
"cooperatives": "Cooperatives",
"parties": "Parties",
"tradeUnions": "Trade Unions",
"partners": "Partners",
"login": "Login",
"profile": "Profile"

View File

@ -0,0 +1,11 @@
{
"tradeUnions": "Trade Unions",
"p1": "Trade unions play a pivotal role in safeguarding the rights and welfare of workers. Trade unions constitute an integral part of our organization, allowing workers to unite and collectively negotiate for fair wages, better working conditions, and improved labour rights. By fostering solidarity and mobilizing for collective action, trade unions contribute to our overarching mission of dismantling exploitative systems and ushering in a world centered on decentralized decision-making, direct democracy, and worker self-management.",
"subheading1": "Our Trade Unions",
"location": "Location",
"members": "Members",
"contact": "Contact",
"TradeUnion": "Trade union",
"tradeUnion": "trade union",
"map-prompt": "Want to appear on our map? Contact us!"
}

View File

@ -8,6 +8,8 @@
"cooperativesText": "Мы формируем рабочие кооперативы для финансирования операций наших групп и коммун, а также формирования основы новой социалистической экономики. Признавая, что экономическая власть влияет на политическую власть, мы считаем создание кооперативов одним из первых шагов на пути к социализму.",
"partiesTitle": "ПАРТИИ",
"partiesText": "Мы создаем политические партии, чтобы продвигать реформы, которые позволят легче достичь наших целей, сдвигать окно Овертона и увеличивать нашу популярность. Однако мы признаем, что мы не можем достичь либертарианского социализма с помощью институтов, действующих против наших целей.",
"tradeUnionsTitle": "ПРОФСОЮЗЫ",
"tradeUnionsText": "Мы поддерживаем профсоюзы, которые дают возможность работникам коллективно выступать за справедливое обращение, справедливую заработную плату и улучшение условий труда. Борьба на месте работы — неотъемлемая часть нашей стратегии по достижению либертарного социализма.",
"findUs": "Найди нас",
"whatNow": "Что теперь?",
"joinUs": "Присоединяйся",

View File

@ -8,6 +8,7 @@
"cooperatives": "Кооперативы",
"parties": "Партии",
"partners": "Партнеры",
"tradeUnions": "Профсоюзы",
"login": "Войти",
"profile": "Профиль"
}

View File

@ -0,0 +1,11 @@
{
"tradeUnions": "Профсоюзы",
"p1": "Профсоюзы играют ключевую роль в защите прав и благосостояния трудящихся. Профсоюзы составляют неотъемлемую часть нашей организации, позволяя работникам объединяться и вести коллективные переговоры о справедливой заработной плате, лучших условиях труда и улучшенных трудовых правах. Укрепляя солидарность и мобилизуя для коллективных действий, профсоюзы вносят свой вклад в нашу всеобъемлющую миссию по устранению эксплуататорских систем и установлению мира, основанного на децентрализованном принятии решений, прямой демократии и самоуправлении рабочих.",
"subheading1": "Наши профсоюзы",
"location": "Локация",
"members": "Участники",
"contact": "Контакт",
"TradeUnion": "Профсоюз",
"tradeUnion": "профсоюз",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
}

View File

@ -21,6 +21,7 @@
loadLocaleContent(content,"communes-component",loaded)
loadLocaleContent(content,"cooperatives-component",loaded)
loadLocaleContent(content,"parties-component",loaded)
loadLocaleContent(content,"trade-unions-component",loaded)
loadLocaleContent(content,"countries",loaded)
let locale = loadLocaleContent(content,"join-us-component",loaded)
@ -48,6 +49,7 @@
getData("/assets/communes.json",(response) => callback(response,"communes"))
getData("/assets/cooperatives.json",(response) => callback(response,"cooperatives"))
getData("/assets/parties.json",(response) => callback(response,"parties"))
getData("/assets/trade-unions.json",(response) => callback(response,"tradeUnions"))
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
@ -55,15 +57,19 @@
enableCountryGrouping: true,
}
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 tradeUnionsMarkersLayer = addMarkersEntries(entries["tradeUnions"],entriesByCountry["tradeUnions"],map,content,locale,addPartyPinContent,"violet",options)
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue",options)
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red",options)
let overlayMaps = {}
overlayMaps[content.groups] = groupsMarkersLayer
overlayMaps[content.communes] = communesMarkersLayer
overlayMaps[content.cooperatives] = coopsMarkersLayer
overlayMaps[content.parties] = partiesMarkersLayer
overlayMaps[content.tradeUnions] = tradeUnionsMarkersLayer
overlayMaps[content.cooperatives] = coopsMarkersLayer
overlayMaps[content.communes] = communesMarkersLayer
L.control.layers(null, overlayMaps).addTo(map)
}
@ -73,7 +79,7 @@
</script>
{#key $loaded}
{#if $loaded==10}
{#if $loaded==12}
<div id="container">
<div id="text-container">
<h1>{$content.heading}</h1>
@ -97,13 +103,14 @@
<p>{$content.findOur}</p>
<ol id="entities-list">
<li><a href={"/" + locale + "/groups"}>{$content.group}</a>,</li>
<li><a href={"/" + locale + "/communes"}>{$content.commune}</a></li>
<li><a href={"/" + locale + "/cooperatives"}>{$content.cooperative}</a> {$content.or}</li>
<li><a href={"/" + locale + "/parties"}>{$content.party}</a></li>
<li><a href={"/" + locale + "/trade-unions"}>{$content.tradeUnion}</a></li>
<li><a href={"/" + locale + "/cooperatives"}>{$content.cooperative}</a> {$content.or}</li>
<li><a href={"/" + locale + "/communes"}>{$content.commune}</a></li>
</ol>
<p>{$content.nearYou}</p>
</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://discord.gg/Qk8KUk787z" target="_blank" rel=noreferrer>{$content.DiscordServer}</a> {$content.or} <a href="https://chat.whatsapp.com/BhnmUNljUxJ2AjeHUwyTKh" target="_blank" rel=noreferrer>{$content.WhatsAppGroup}</a>{$content.helpStart}</p>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)} colors={["#23AC20","#CA2437","#217BC9","#FFD326"]}></map-component>
<p id="add-prompt">{$content["map-prompt"]}</p>
</div>
@ -138,13 +145,16 @@
background-image: url(https://www.libsoc.org/img/common/markers/marker-green.png);
}
#entities-list li:nth-of-type(2):before {
background-image: url(https://www.libsoc.org/img/common/markers/marker-red.png);
background-image: url(https://www.libsoc.org/img/common/markers/marker-gold.png);
}
#entities-list li:nth-of-type(3):before {
background-image: url(https://www.libsoc.org/img/common/markers/marker-blue.png);
background-image: url(https://www.libsoc.org/img/common/markers/marker-violet.png);
}
#entities-list li:nth-of-type(4):before {
background-image: url(https://www.libsoc.org/img/common/markers/marker-gold.png);
background-image: url(https://www.libsoc.org/img/common/markers/marker-blue.png);
}
#entities-list li:nth-of-type(5):before {
background-image: url(https://www.libsoc.org/img/common/markers/marker-red.png);
}
#entities-list li::marker {

View File

@ -51,11 +51,14 @@
getData("/assets/communes.json",(response) => callback(response,"communes"))
getData("/assets/cooperatives.json",(response) => callback(response,"cooperatives"))
getData("/assets/parties.json",(response) => callback(response,"parties"))
getData("/assets/trade-unions.json",(response) => callback(response,"tradeUnions"))
loadLocaleContent(content,"groups-component",loaded)
loadLocaleContent(content,"communes-component",loaded)
loadLocaleContent(content,"cooperatives-component",loaded)
loadLocaleContent(content,"parties-component",loaded)
loadLocaleContent(content,"trade-unions-component",loaded)
loadLocaleContent(content,"countries",loaded)
let locale = loadLocaleContent(content,"landing-component",loaded,changeWidth)
changeWidth(locale)
@ -66,15 +69,18 @@
enableCountryGrouping: true,
}
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 tradeUnionsMarkersLayer = addMarkersEntries(entries["tradeUnions"],entriesByCountry["tradeUnions"],map,content,locale,addPartyPinContent,"violet",options)
let coopsMarkersLayer = addMarkersEntries(entries["cooperatives"],entriesByCountry["cooperatives"],map,content,locale,addCoopPinContent,"blue",options)
let communesMarkersLayer = addMarkersEntries(entries["communes"],entriesByCountry["communes"],map,content,locale,addCommunePinContent,"red",options)
let overlayMaps = {}
overlayMaps[content.groups] = groupsMarkersLayer
overlayMaps[content.communes] = communesMarkersLayer
overlayMaps[content.cooperatives] = coopsMarkersLayer
overlayMaps[content.parties] = partiesMarkersLayer
overlayMaps[content.tradeUnions] = tradeUnionsMarkersLayer
overlayMaps[content.cooperatives] = coopsMarkersLayer
overlayMaps[content.communes] = communesMarkersLayer
L.control.layers(null, overlayMaps).addTo(map)
}
@ -85,7 +91,7 @@
</script>
{#key $loaded}
{#if $loaded==10}
{#if $loaded==12}
<div id="container">
<picture>
<source srcset="/img/crowd.webp">
@ -105,6 +111,11 @@
<img id="parties-img" src="/img/common/parties.svg" alt="coops">
<p>{$content.partiesText}</p>
</div>
<div>
<a href={"/" + locale + "/trade-unions"}><h2>{$content.tradeUnionsTitle}</h2></a>
<img id="trade-unions-img" src="/img/common/trade-unions.svg" alt="trade unions">
<p>{$content.tradeUnionsText}</p>
</div>
<div>
<a href={"/" + locale + "/coops"}><h2>{$content.cooperativesTitle}</h2></a>
<img id="coops-img" src="/img/common/coops.svg" alt="coops">
@ -134,7 +145,7 @@
</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>
<map-component id="map" callback={(createMap) => mapCallback(createMap,$content,locale)} colors={["#23AC20","#FFD326","#9D35CD","#217BC9","#CA2437"]}></map-component>
<h1>{$content.whatNow}</h1>
<div id="action-container">
<a class="link-button" href={"/" + locale + "/join-us"}>{$content.joinUs}</a>
@ -188,7 +199,7 @@
text-align: center;
}
#groups-img, #communes-img, #coops-img, #parties-img {
#groups-img, #communes-img, #coops-img, #parties-img, #trade-unions-img {
position: absolute;
left: 50%;
transform: translate(-50%);
@ -202,6 +213,11 @@
height: 7.5rem;
}
#trade-unions-img {
margin-top: 0.5rem;
height: 7.5rem;
}
#text-container {
max-width: calc(100vw - 4rem);
margin: auto;
@ -230,13 +246,18 @@
#container-grid {
display: grid;
grid-template-columns: var(--grid-width);
grid-template-rows: var(--grid-width);
grid-template-rows: 100% 100%;
grid-gap: 4rem;
row-gap: 2.5rem;
margin-top: 2rem;
margin-bottom: 1rem;
}
#container-grid>:last-child {
grid-column: 1/span 2;
}
#container-grid > div {
position: relative;
}

View File

@ -115,9 +115,10 @@
<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+"/trade-unions"}>{$content.tradeUnions}</a>
<a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a>
<a href={"/"+locale+"/communes"}>{$content.communes}</a>
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>

View File

@ -115,9 +115,10 @@
<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+"/trade-unions"}>{$content.tradeUnions}</a>
<a href={"/"+locale+"/cooperatives"}>{$content.cooperatives}</a>
<a href={"/"+locale+"/communes"}>{$content.communes}</a>
<a href={"/"+locale+"/partners"}>{$content.partners}</a>
</div>
</li>

View File

@ -15,6 +15,7 @@
import "/js/components/profile-communes.js"
import "/js/components/profile-coops.js"
import "/js/components/profile-parties.js"
import "/js/components/profile-trade-unions.js"
import "/js/components/groups-add-component.js"
// Main code
@ -26,6 +27,7 @@
let communes
let coops
let parties
let tradeUnions
let panes
let groupsAdd
@ -34,6 +36,7 @@
let communesButton
let coopsButton
let partiesButton
let tradeUnionsButton
let buttons
let currentPaneIndex = 0
@ -66,18 +69,21 @@
setTimeout(f,100)
}
else {
let svgItem = svgFromObject(svgObject)
if (svgItem==null) {
let svgItems = svgFromObject(svgObject)
if (svgItems.length==0) {
let f = () => styleField(div,weight,color)
setTimeout(f,100)
}
else {
div.style.fontWeight = weight
svgItem.setAttribute("fill", color)
for (let item of svgItems) {
let fill = item.getAttribute("fill")
if (fill!="#fff" && fill!=null) {
item.setAttribute("fill", color)
}
}
}
}
}
function fillFields() {
@ -98,10 +104,10 @@
function init() {
panes = [general,groups,communes,coops,parties]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton,tradeUnionsButton]
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]
panes = [general,groups,communes,coops,parties,tradeUnions]
buttons = [generalButton,groupsButton,communesButton,coopsButton,partiesButton,tradeUnionsButton]
fillFields()
general.style.display = "initial"
@ -155,6 +161,10 @@
<object id="parties-img" class="icons" type="image/svg+xml" data="/img/common/parties.svg" title="parties"></object>
<span>parties</span>
</button>
<button bind:this={tradeUnionsButton} on:click={() => changePane(tradeUnions,tradeUnionsButton)}>
<object id="trade-unions-img" class="icons" type="image/svg+xml" data="/img/common/trade-unions.svg" title="trade unions"></object>
<span>trade unions</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>
@ -165,9 +175,10 @@
{#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>
<profile-trade-unions bind:this={tradeUnions} style="display: none;"></profile-trade-unions>
<profile-coops bind:this={coops} style="display: none;"></profile-coops>
<profile-communes bind:this={communes} style="display: none;"></profile-communes>
{/if}
{/key}
</div>

View File

@ -0,0 +1,29 @@
<svelte:options tag="profile-trade-unions" />
<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 trade union to be added.</p>
<style>
@import '/css/common.css';
h3 {
text-align: center;
}
</style>

View File

@ -0,0 +1,577 @@
<svelte:options tag="trade-unions-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 { validatePosNumber } from "/js/libraries/miscTools.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/trade-unions.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,"trade-unions-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["TradeUnion"]+"</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 + "/trade-unions-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 trade-union"
}
}
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="number" value={1} on:input={(event) => validatePosNumber(event,membersInput,10000)}>
</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

@ -0,0 +1,178 @@
<svelte:options tag="trade-unions-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 { addTradeUnionPinContent } from "/js/mapFuncs.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,"trade-unions-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/trade-unions.json",callback)
function mapCallback(createMap,content,locale) {
let map = createMap([22, 0],2)
let options = {
enableCountryGrouping: true,
}
addMarkersEntries(entries,entriesByCountry,map,content,locale,addTradeUnionPinContent,"violet",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(() => {
})
</script>
{#key $loaded}
{#if $loaded==3}
<div id="container">
<!--<img src="img/crowd.png" id="crowd" alt="crowd">-->
<div id="text-container">
<h1>{$content.tradeUnions}</h1>
<img id="trade-unions-img" src="/img/common/trade-unions.svg" alt="trade unions">
<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.name}: </b>{entry.name}</p>
<p><b>{$content.location}: </b>{getAddress(entry)}</p>
<p><b>{$content.members}: </b>{entry.members}</p>
{#if entry.contact.includes("@") && entry.contact.trim().split(" ").length==1}
<p><b>{$content.contact}: </b><a href={"mailto:" + entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
{:else if entry.contact.includes("http")}
<p><b>{$content.contact}: </b><a href={entry.contact} target=;_blank; rel=noreferrer>{entry.contact}</a></p>
{:else}
<p><b>{$content.contact}: </b>{entry.contact}</p>
{/if}
</div>
{/each}
</div>
{/each}
</div>
</div>
{/if}
{/key}
<style>
@import '/css/common.css';
.description {
margin-bottom: 1rem;
}
#add-prompt {
margin-bottom: 2rem;
}
#trade-unions-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

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

View File

@ -28,8 +28,6 @@ function up()
add_index(:groups_requests, :user_id)
set_default("groups_requests","added",false)
end
function down()

View File

@ -0,0 +1,38 @@
module CreateTableTradeUnionsRequests
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
include("../../lib/DatabaseSupport.jl")
using .DatabaseSupport
import .DatabaseSupport: add_foreign_key, set_default
function up()
create_table(:trade_unions_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(:members,:integer)
column(:name,:string)
column(:status,:integer)
]
end
add_foreign_key(:trade_unions_requests,:user_id,:users,:id)
add_foreign_key(:trade_unions_requests,:group_id,:trade_unions,:id)
add_index(:trade_unions_requests, :user_id)
end
function down()
drop_table(:trade_unions_requests)
end
end

View File

@ -0,0 +1,24 @@
module CreateTableTradeUnionsUsers
import SearchLight.Migrations: create_table, column, primary_key, add_index, drop_table
function up()
create_table(:trade_unions_users) do
[
primary_key()
column(:user_id, :int)
column(:trade_union_id, :int)
]
end
add_foreign_key(:trade_unions_users,:user_id,:users,:id)
add_foreign_key(:trade_unions_users,:trade_union_id,:trade_unions,:id)
add_index(:trade_unions_users, :user_id)
add_index(:trade_unions_users, :trade_union_id)
end
function down()
drop_table(:trade_unions_users)
end
end

View File

@ -0,0 +1 @@
[]

View File

@ -1,3 +1,3 @@
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-gear-fill" fill="#5B6970" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"/>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-gear-fill" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" fill="#5B6970" d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"/>
</svg>

Before

Width:  |  Height:  |  Size: 837 B

After

Width:  |  Height:  |  Size: 837 B

View File

@ -1 +1 @@
<svg viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg" fill="#5B6970"><path d="m96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112s-50.1-112-112-112-112 50.1-112 112 50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3c-63.6 0-115.2 51.6-115.2 115.2v28.8c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4c-11.6-11.5-27.5-18.6-45.1-18.6h-64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"/></svg>
<svg viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"><path fill="#5B6970" d="m96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112s-50.1-112-112-112-112 50.1-112 112 50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3c-63.6 0-115.2 51.6-115.2 115.2v28.8c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4c-11.6-11.5-27.5-18.6-45.1-18.6h-64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"/></svg>

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 655.51 492.22">
<g>
<path id="path4" style="fill-rule: evenodd;" fill="#5B6970" d="m89.92,237.33c-8.97,2.22-18.14-3.38-20.26-12.35-2.22-8.97,3.38-18.14,12.35-20.26l240.37-58.67c8.97-2.22,18.14,3.38,20.26,12.35,2.22,8.97-3.38,18.14-12.35,20.26l-240.37,58.67Zm484.89,27.21c8.97,2.22,14.57,11.29,12.35,20.26s-11.29,14.57-20.26,12.35l-93.31-22.77c-8.97-2.22-14.57-11.29-12.35-20.26s11.29-14.57,20.26-12.35l93.31,22.77Zm9.75-83.57c9.07,2.12,14.67,11.19,12.54,20.17-2.12,9.07-11.19,14.67-20.17,12.54l-58.38-13.7c-9.07-2.12-14.67-11.19-12.54-20.17,2.12-9.07,11.19-14.67,20.17-12.54l58.38,13.7Zm-504.67-27.02c-9.07,2.12-18.14-3.57-20.17-12.54-2.12-9.07,3.57-18.14,12.54-20.17l169.25-39.66c9.07-2.12,18.14,3.57,20.17,12.54,2.12,9.07-3.57,18.14-12.54,20.17l-169.25,39.66Zm568.65-1.35l-206.6-50.37,20.65,83.18c3.47,13.99.87,27.98-6.08,39.47-6.85,11.29-18.14,19.97-31.94,23.45l-1.83.48c0,.1-.58.19-.77.19-52.3,8.4-96.5,16.79-134.23,24.41l-.19-.68-76.04,18.53,24.61,94.26c14.64,15.58,14.53,15.41,42.91,15.51,24.29-8.42,24.27-8.66,32.54-30.55l-22.77-92.06c1.16,1.06,2.61,1.93,4.25,2.32l305.22,74.4c4.92,1.25,10.04-1.83,11.19-6.85l45.74-184.6c1.35-4.82-1.74-9.94-6.66-11.1ZM7.03,88.34L363.1.24c5.02-1.16,10.04,1.93,11.29,6.95l46.8,188.46c1.25,4.92-1.83,10.04-6.85,11.19L58.27,294.94c-4.92,1.25-10.04-1.83-11.19-6.85L.28,99.63c-1.25-5.02,1.83-10.04,6.75-11.29Zm477.94,262.08l-76.04-18.53-22,107.4c4.23,26.34,3.74,25.6,26.68,38.88,24.82,2.87,25.17,3.26,46.02-13.91l25.34-113.85Z"/>
<path fill="#5B6970" d="m217.55,281.13h91.49v109.1c0,25.25-20.5,45.75-45.75,45.75h0c-25.25,0-45.75-20.5-45.75-45.75v-109.1h0Z" transform="translate(-76.12 71.01) rotate(-13.44)"/>
<path fill="#5B6970" d="m388.68,338.32h81.35v114.18c0,22.45-18.23,40.68-40.68,40.68h0c-22.45,0-40.68-18.23-40.68-40.68v-114.18h0Z" transform="translate(104.8 -86.1) rotate(13.03)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{S as t,i as s,a as e,b as o,s as r,h as a,j as n,n as i,z as d,d as c,o as l}from"./index-0d9f0c09.js";import"../../../../../../../../../js/libraries/authTools.js";function h(t){let s,e,r;return{c(){s=a("h3"),s.textContent="Under development",e=n(),r=a("p"),r.innerHTML='Visit <a href="https://discord.gg/Qk8KUk787z" style="color: #c52a28;">https://discord.gg/Qk8KUk787z</a> and ask for your trade union to be added.',this.c=i,d(r,"position","relative"),d(r,"margin-top","2rem")},m(t,a){o(t,s,a),o(t,e,a),o(t,r,a)},p:i,i:i,o:i,d(t){t&&c(s),t&&c(e),t&&c(r)}}}function p(t){return l((()=>{})),[]}class u extends t{constructor(t){super(),this.shadowRoot.innerHTML="<style>@import '/css/common.css';h3{text-align:center}</style>",s(this,{target:this.shadowRoot,props:e(this.attributes),customElement:!0},p,h,r,{},null),t&&t.target&&o(t.target,this,t.anchor)}}customElements.define("profile-trade-unions",u);export{u as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,9 +13,9 @@ export function debounce(func, timeout){
}
export function svgFromObject(object) {
var objectDoc = object.contentDocument;
var svgItem = objectDoc.querySelector("path");
return svgItem
var objectDoc = object.contentDocument
var svgItems = objectDoc.querySelectorAll("path")
return svgItems
}
export function rem2px(rem) {

View File

@ -6,7 +6,15 @@ export function addGroupPinContent(g,content,locale) {
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>"
if (g.contact.includes("@") && g.contact.trim().split(" ").length==1) {
text += fieldText + "<a href='mailto:" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else if (g.contact.includes("http")) {
text += fieldText + "<a href='" + g.contact + "' target='_blank' rel=noreferrer>" + g.contact + "</a>"
}
else {
text += fieldText + g.contact + "<br>"
}
}
else if (field=="location") {
let location = [g.country,g.state,g.town].filter(x => x!=null && x!=undefined)
@ -176,3 +184,30 @@ export function addPartnersPinContent(g,content,locale) {
}
return {text,coordinates}
}
export function addTradeUnionPinContent(g,content,locale) {
let coordinates
let text = "<b>"+content["TradeUnion"]+"</b><br>"
for (let field of ["name","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}
}

View File

@ -1,13 +1,15 @@
{
"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",
"groupsText": "We organize groups for the purposes of education, advocacy, anti-fascist action 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, anti-fascist action 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.",
"communesTitle": "COMMUNES",
"communesText": "We establish communes based on libertarian socialist principles, where commune members have ownership over land, houses, and the means of production as well as make decisions using direct democracy. We are gradually expanding our socialist world, one commune at a time.",
"cooperativesTitle": "COOPERATIVES",
"cooperativesText": "We form worker cooperatives to finance the operations of our groups and communes. Recognizing that economic power influences political power, we consider the establishment of cooperatives to be one of the initial steps towards achieving socialism.",
"cooperativesText": "We form worker cooperatives to finance the operations of our groups and communes. Recognizing that economic power influences political power, we consider the establishment of cooperatives to be a vital activity.",
"partiesTitle": "PARTIES",
"partiesText": "We create political parties in order to push for reforms allowing us to easier further our goals, to move the Overton window as well as to gain popularity. However, we recognize that we cannot achieve libertarian socialism through institutions which act contrary to our goals.",
"tradeUnionsTitle": "TRADE UNIONS",
"tradeUnionsText": "We promote trade unions, which empower laborers to collectively advocate for fair treatment, just wages, and improved working conditions. Struggle at a place of work is an integral part of our strategy to achieve libertarian socialism.",
"findUs": "Find Us",
"whatNow": "What Now?",
"joinUs": "Join Us",

View File

@ -7,6 +7,7 @@
"communes": "Communes",
"cooperatives": "Cooperatives",
"parties": "Parties",
"tradeUnions": "Trade Unions",
"partners": "Partners",
"login": "Login",
"profile": "Profile"

View File

@ -0,0 +1,11 @@
{
"tradeUnions": "Trade Unions",
"p1": "Trade unions play a pivotal role in safeguarding the rights and welfare of workers. Trade unions constitute an integral part of our organization, allowing workers to unite and collectively negotiate for fair wages, better working conditions, and improved labour rights. By fostering solidarity and mobilizing for collective action, trade unions contribute to our overarching mission of dismantling exploitative systems and ushering in a world centered on decentralized decision-making, direct democracy, and worker self-management.",
"subheading1": "Our Trade Unions",
"location": "Location",
"members": "Members",
"contact": "Contact",
"TradeUnion": "Trade union",
"tradeUnion": "trade union",
"map-prompt": "Want to appear on our map? Contact us!"
}

View File

@ -8,6 +8,8 @@
"cooperativesText": "Мы формируем рабочие кооперативы для финансирования операций наших групп и коммун, а также формирования основы новой социалистической экономики. Признавая, что экономическая власть влияет на политическую власть, мы считаем создание кооперативов одним из первых шагов на пути к социализму.",
"partiesTitle": "ПАРТИИ",
"partiesText": "Мы создаем политические партии, чтобы продвигать реформы, которые позволят легче достичь наших целей, сдвигать окно Овертона и увеличивать нашу популярность. Однако мы признаем, что мы не можем достичь либертарианского социализма с помощью институтов, действующих против наших целей.",
"tradeUnionsTitle": "ПРОФСОЮЗЫ",
"tradeUnionsText": "Мы поддерживаем профсоюзы, которые дают возможность работникам коллективно выступать за справедливое обращение, справедливую заработную плату и улучшение условий труда. Борьба на месте работы — неотъемлемая часть нашей стратегии по достижению либертарного социализма.",
"findUs": "Найди нас",
"whatNow": "Что теперь?",
"joinUs": "Присоединяйся",

View File

@ -8,6 +8,7 @@
"cooperatives": "Кооперативы",
"parties": "Партии",
"partners": "Партнеры",
"tradeUnions": "Профсоюзы",
"login": "Войти",
"profile": "Профиль"
}

View File

@ -0,0 +1,11 @@
{
"tradeUnions": "Профсоюзы",
"p1": "Профсоюзы играют ключевую роль в защите прав и благосостояния трудящихся. Профсоюзы составляют неотъемлемую часть нашей организации, позволяя работникам объединяться и вести коллективные переговоры о справедливой заработной плате, лучших условиях труда и улучшенных трудовых правах. Укрепляя солидарность и мобилизуя для коллективных действий, профсоюзы вносят свой вклад в нашу всеобъемлющую миссию по устранению эксплуататорских систем и установлению мира, основанного на децентрализованном принятии решений, прямой демократии и самоуправлении рабочих.",
"subheading1": "Наши профсоюзы",
"location": "Локация",
"members": "Участники",
"contact": "Контакт",
"TradeUnion": "Профсоюз",
"tradeUnion": "профсоюз",
"map-prompt": "Хочешь оказаться на нашей карте? Напиши нам!"
}

View File

@ -88,6 +88,14 @@ route("/:locale/parties-add/*", PartiesController.parties_add, named = :parties_
route("/:locale/parties-add-post/*", PartiesController.parties_add_post, method = POST, named = :parties_add_post)
#---Trade unions---------------------------------------------------------
route("/:locale/trade-unions/*", TradeUnionsController.trade_unions, named = :trade_unions)
route("/:locale/trade-unions-add/*", TradeUnionsController.trade_unions_add, named = :trade_unions_add)
route("/:locale/trade-unions-add-post/*", TradeUnionsController.trade_unions_add_post, method = POST, named = :trade_unions_add_post)
#---Partners---------------------------------------------------------
route("/:locale/partners/*", PartnersController.partners, named = :partners)