Added admin panel
This commit is contained in:
parent
40f8274d7c
commit
d634cbb65f
|
@ -0,0 +1,145 @@
|
||||||
|
module AdminController
|
||||||
|
|
||||||
|
using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication, DataFrames, GenieAuthorisation
|
||||||
|
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 = "admin"
|
||||||
|
dict_layouts = Dict(
|
||||||
|
:admin_panel => generate_layout_html("main",controller,"admin-panel"),
|
||||||
|
)
|
||||||
|
|
||||||
|
#---Page info-----------------------------------------------------
|
||||||
|
|
||||||
|
const admin_panel_info = Dict(
|
||||||
|
"en" => Dict(
|
||||||
|
:title => "LibSoc - Admin panel",
|
||||||
|
:description => ""
|
||||||
|
),
|
||||||
|
"ru" => Dict(
|
||||||
|
:title => "",
|
||||||
|
: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 admin_panel()
|
||||||
|
@info has_permission(current_user(), "verification")
|
||||||
|
@info current_user()
|
||||||
|
if has_permission(current_user(), "verification")
|
||||||
|
locale = get_locale()
|
||||||
|
html(:admin,:admin_panel, layout = dict_layouts[:admin_panel], context = @__MODULE__,
|
||||||
|
title = admin_panel_info[locale][:title],
|
||||||
|
description = admin_panel_info[locale][:description]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function verify()
|
||||||
|
if has_permission(current_user(), "verification")
|
||||||
|
data = copy(jsonpayload())
|
||||||
|
user_id = data["user_id"]
|
||||||
|
update_table("users",Dict("verified" => true), where_data=["id" => user_id])
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_unverified_users()
|
||||||
|
if has_permission(current_user(), "verification")
|
||||||
|
users = select_from_table("users" => ["id","email"], where_data = ["verified" => false])
|
||||||
|
data = []
|
||||||
|
if size(users,1)!=0
|
||||||
|
for x in eachrow(users)
|
||||||
|
dict = Dict("user_id" => x["id"],"email" => x["email"])
|
||||||
|
push!(data, dict)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return JSON3.write(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function add_verified_groups()
|
||||||
|
if has_permission(current_user(), "admin")
|
||||||
|
groups_create_requests_verified = select_from_table("groups_requests" => ["*"], where_data = ["group_id" => nothing, "status" => 1])
|
||||||
|
if size(groups_create_requests_verified,1)!=0
|
||||||
|
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
|
||||||
|
compile("groups")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
<admin-panel></admin-panel>
|
|
@ -245,23 +245,6 @@ function reject_request()
|
||||||
return nothing
|
return nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
function add_verified_groups()
|
|
||||||
groups_create_requests_verified = select_from_table("groups_requests" => ["*"], where_data = ["group_id" => nothing, "status" => 1])
|
|
||||||
if size(groups_create_requests_verified,1)!=0
|
|
||||||
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
|
|
||||||
compile("groups")
|
|
||||||
end
|
|
||||||
|
|
||||||
function changeMemberCount()
|
function changeMemberCount()
|
||||||
user_id = get_authentication()
|
user_id = get_authentication()
|
||||||
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
|
groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1]
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
<svelte:options tag="admin-panel" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
import "/js/components/pane-aligner.js"
|
||||||
|
|
||||||
|
//Export statements
|
||||||
|
|
||||||
|
// Main code
|
||||||
|
let section
|
||||||
|
let requests_verification = []
|
||||||
|
let loaded = writable(0)
|
||||||
|
let keyRequests = 0
|
||||||
|
let numLoaded = 1
|
||||||
|
let mainPane
|
||||||
|
|
||||||
|
let context = getContext("profile-component")
|
||||||
|
|
||||||
|
function requests_callback(response) {
|
||||||
|
let parsed = JSON.parse(response)
|
||||||
|
requests_verification.push(...parsed)
|
||||||
|
loaded.update((val) => {
|
||||||
|
return val + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getData("/xx/get-unverified-users",requests_callback)
|
||||||
|
|
||||||
|
function approveRequest(ind,user_id) {
|
||||||
|
sendData("/xx/verify",{user_id: user_id})
|
||||||
|
requests_verification.splice(ind,1)
|
||||||
|
keyRequests = keyRequests + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function addVerified() {
|
||||||
|
getData("/xx/add-verified-groups",() => "")
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key $loaded}
|
||||||
|
{#if $loaded==numLoaded}
|
||||||
|
<pane-aligner>
|
||||||
|
<div bind:this={mainPane} slot="main">
|
||||||
|
<h3>User verification</h3>
|
||||||
|
<section bind:this={section} class="entries-section">
|
||||||
|
{#key keyRequests}
|
||||||
|
{#each requests_verification 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="default-button approve-button">Approve</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/key}
|
||||||
|
<button on:click={addVerified} id="add-verified-button" class="default-button">Add verified pins</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</pane-aligner>
|
||||||
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import '/css/common.css';
|
||||||
|
|
||||||
|
.request-button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approve-button {
|
||||||
|
margin-top: -0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-verified-button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---General section-----------------------------------------------------------*/
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-family: var(--sans-serif,sans-serif);
|
||||||
|
font-size: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
|
@ -25,6 +25,9 @@ SearchLight.Configuration.load() |> SearchLight.connect
|
||||||
#SearchLight.Migration.all_up!!(context=Server)
|
#SearchLight.Migration.all_up!!(context=Server)
|
||||||
#SearchLight.Migration.status()
|
#SearchLight.Migration.status()
|
||||||
|
|
||||||
|
|
||||||
|
#---Create tables----------------------------------------------------
|
||||||
|
|
||||||
p = "db/migrations/"
|
p = "db/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)]
|
||||||
|
@ -39,3 +42,23 @@ for f in files
|
||||||
catch
|
catch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#---Initialize Genie Authorization----------------------------------------------------
|
||||||
|
|
||||||
|
using GenieAuthorisation
|
||||||
|
using GenieAuthorisation: findone_or_create, save!, findone
|
||||||
|
|
||||||
|
# Create roles
|
||||||
|
for r in ["admin"]
|
||||||
|
findone_or_create(Role, name = r) |> save!
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create permissions
|
||||||
|
for p in ["verification"]
|
||||||
|
findone_or_create(Permission, name = p) |> save!
|
||||||
|
end
|
||||||
|
|
||||||
|
assign_permission(findone(Role, name = "admin"), findone(Permission, name = "verification"))
|
||||||
|
|
||||||
|
# assign_role(findone(User, email = "user@user"), findone(Role, name = "admin"))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
import{S as t,i as e,a as n,b as o,s,e as r,n as i,d as a,c,g as d,o as l,f as m,h as p,j as u,k as f,l as h,m as g,p as b,t as v}from"./index-0d9f0c09.js";import{w as x}from"./index-1c123138.js";import{getData as j,sendData as w}from"../../../../../../../../../js/libraries/serverTools.js";import"../../../../../../../../../js/components/select-component.js";import"../../../../../../../../../js/components/switch-component.js";import"../../../../../../../../../js/components/pane-aligner.js";function y(t,e,n){const o=t.slice();return o[13]=e[n],o[15]=n,o}function k(t){let e,n,r,i,c,d,l,m,b,v=t[1],x=C(t);return{c(){e=p("pane-aligner"),n=p("div"),r=p("h3"),r.textContent="User verification",i=u(),c=p("section"),x.c(),d=u(),l=p("button"),l.textContent="Add verified pins",f(l,"id","add-verified-button"),f(l,"class","default-button"),f(c,"class","entries-section"),f(n,"slot","main")},m(s,a){o(s,e,a),h(e,n),h(n,r),h(n,i),h(n,c),x.m(c,null),h(c,d),h(c,l),t[9](c),t[10](n),m||(b=g(l,"click",t[7]),m=!0)},p(t,e){2&e&&s(v,v=t[1])?(x.d(1),x=C(t),x.c(),x.m(c,d)):x.p(t,e)},d(n){n&&a(e),x.d(n),t[9](null),t[10](null),m=!1,b()}}}function N(t){let e,n,s,r,i,c,d,l,m,b,x=t[13].email+"";function j(){return t[8](t[15],t[13])}return{c(){e=p("div"),n=p("div"),s=p("span"),r=v(x),i=u(),c=p("div"),d=p("button"),d.textContent="Approve",l=u(),f(d,"class","default-button approve-button"),f(c,"class","request-button-wrapper"),f(n,"class","change-field-line")},m(t,a){o(t,e,a),h(e,n),h(n,s),h(s,r),h(n,i),h(n,c),h(c,d),h(e,l),m||(b=g(d,"click",j),m=!0)},p(e,n){t=e},d(t){t&&a(e),m=!1,b()}}}function C(t){let e,n=t[4],s=[];for(let e=0;e<n.length;e+=1)s[e]=N(y(t,n,e));return{c(){for(let t=0;t<s.length;t+=1)s[t].c();e=r()},m(t,n){for(let e=0;e<s.length;e+=1)s[e].m(t,n);o(t,e,n)},p(t,o){if(80&o){let r;for(n=t[4],r=0;r<n.length;r+=1){const i=y(t,n,r);s[r]?s[r].p(i,o):(s[r]=N(i),s[r].c(),s[r].m(e.parentNode,e))}for(;r<s.length;r+=1)s[r].d(1);s.length=n.length}},d(t){b(s,t),t&&a(e)}}}function q(t){let e,n=t[3]==A&&k(t);return{c(){n&&n.c(),e=r()},m(t,s){n&&n.m(t,s),o(t,e,s)},p(t,o){t[3]==A?n?n.p(t,o):(n=k(t),n.c(),n.m(e.parentNode,e)):n&&(n.d(1),n=null)},d(t){n&&n.d(t),t&&a(e)}}}function z(t){let e,n=t[3],c=q(t);return{c(){c.c(),e=r(),this.c=i},m(t,n){c.m(t,n),o(t,e,n)},p(t,[o]){8&o&&s(n,n=t[3])?(c.d(1),c=q(t),c.c(),c.m(e.parentNode,e)):c.p(t,o)},i:i,o:i,d(t){t&&a(e),c.d(t)}}}let A=1;function E(t,e,n){let o,s,r=[],i=x(0);c(t,i,(t=>n(3,o=t)));let a,p=0;function u(t,e){w("/xx/verify",{user_id:e}),r.splice(t,1),n(1,p+=1)}d("profile-component"),j("/xx/get-unverified-users",(function(t){let e=JSON.parse(t);r.push(...e),i.update((t=>t+1))})),l((()=>{}));return[s,p,a,o,r,i,u,function(){j("/xx/add-verified-groups",(()=>""))},(t,e)=>u(t,e.user_id),function(t){m[t?"unshift":"push"]((()=>{s=t,n(0,s)}))},function(t){m[t?"unshift":"push"]((()=>{a=t,n(2,a)}))}]}class R extends t{constructor(t){super(),this.shadowRoot.innerHTML="<style>@import '/css/common.css';.request-button-wrapper{display:flex;gap:1rem}.default-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}.approve-button{margin-top:-0.45rem}#add-verified-button{margin-top:1rem}.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}.entries-section>div:last-child{padding-bottom:0.75rem;padding-top:0.75rem;border-bottom:0}.change-field-line{display:flex;justify-content:space-between}h3{margin-bottom:0.5rem}span{font-family:var(--sans-serif,sans-serif);font-size:1.15rem}</style>",e(this,{target:this.shadowRoot,props:n(this.attributes),customElement:!0},E,z,s,{},null),t&&t.target&&o(t.target,this,t.anchor)}}customElements.define("admin-panel",R);export{R as default};
|
|
@ -0,0 +1,527 @@
|
||||||
|
|
||||||
|
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
|
||||||
|
function noop() { }
|
||||||
|
function add_location(element, file, line, column, char) {
|
||||||
|
element.__svelte_meta = {
|
||||||
|
loc: { file, line, column, char }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function run(fn) {
|
||||||
|
return fn();
|
||||||
|
}
|
||||||
|
function blank_object() {
|
||||||
|
return Object.create(null);
|
||||||
|
}
|
||||||
|
function run_all(fns) {
|
||||||
|
fns.forEach(run);
|
||||||
|
}
|
||||||
|
function is_function(thing) {
|
||||||
|
return typeof thing === 'function';
|
||||||
|
}
|
||||||
|
function safe_not_equal(a, b) {
|
||||||
|
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
|
||||||
|
}
|
||||||
|
let src_url_equal_anchor;
|
||||||
|
function src_url_equal(element_src, url) {
|
||||||
|
if (!src_url_equal_anchor) {
|
||||||
|
src_url_equal_anchor = document.createElement('a');
|
||||||
|
}
|
||||||
|
src_url_equal_anchor.href = url;
|
||||||
|
return element_src === src_url_equal_anchor.href;
|
||||||
|
}
|
||||||
|
function is_empty(obj) {
|
||||||
|
return Object.keys(obj).length === 0;
|
||||||
|
}
|
||||||
|
function validate_store(store, name) {
|
||||||
|
if (store != null && typeof store.subscribe !== 'function') {
|
||||||
|
throw new Error(`'${name}' is not a store with a 'subscribe' method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function subscribe(store, ...callbacks) {
|
||||||
|
if (store == null) {
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
const unsub = store.subscribe(...callbacks);
|
||||||
|
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
|
||||||
|
}
|
||||||
|
function component_subscribe(component, store, callback) {
|
||||||
|
component.$$.on_destroy.push(subscribe(store, callback));
|
||||||
|
}
|
||||||
|
function append(target, node) {
|
||||||
|
target.appendChild(node);
|
||||||
|
}
|
||||||
|
function insert(target, node, anchor) {
|
||||||
|
target.insertBefore(node, anchor || null);
|
||||||
|
}
|
||||||
|
function detach(node) {
|
||||||
|
node.parentNode.removeChild(node);
|
||||||
|
}
|
||||||
|
function destroy_each(iterations, detaching) {
|
||||||
|
for (let i = 0; i < iterations.length; i += 1) {
|
||||||
|
if (iterations[i])
|
||||||
|
iterations[i].d(detaching);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function element(name) {
|
||||||
|
return document.createElement(name);
|
||||||
|
}
|
||||||
|
function svg_element(name) {
|
||||||
|
return document.createElementNS('http://www.w3.org/2000/svg', name);
|
||||||
|
}
|
||||||
|
function text(data) {
|
||||||
|
return document.createTextNode(data);
|
||||||
|
}
|
||||||
|
function space() {
|
||||||
|
return text(' ');
|
||||||
|
}
|
||||||
|
function empty() {
|
||||||
|
return text('');
|
||||||
|
}
|
||||||
|
function listen(node, event, handler, options) {
|
||||||
|
node.addEventListener(event, handler, options);
|
||||||
|
return () => node.removeEventListener(event, handler, options);
|
||||||
|
}
|
||||||
|
function attr(node, attribute, value) {
|
||||||
|
if (value == null)
|
||||||
|
node.removeAttribute(attribute);
|
||||||
|
else if (node.getAttribute(attribute) !== value)
|
||||||
|
node.setAttribute(attribute, value);
|
||||||
|
}
|
||||||
|
function set_custom_element_data(node, prop, value) {
|
||||||
|
if (prop in node) {
|
||||||
|
node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attr(node, prop, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function to_number(value) {
|
||||||
|
return value === '' ? null : +value;
|
||||||
|
}
|
||||||
|
function children(element) {
|
||||||
|
return Array.from(element.childNodes);
|
||||||
|
}
|
||||||
|
function set_input_value(input, value) {
|
||||||
|
input.value = value == null ? '' : value;
|
||||||
|
}
|
||||||
|
function set_style(node, key, value, important) {
|
||||||
|
if (value === null) {
|
||||||
|
node.style.removeProperty(key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.style.setProperty(key, value, important ? 'important' : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {
|
||||||
|
const e = document.createEvent('CustomEvent');
|
||||||
|
e.initCustomEvent(type, bubbles, cancelable, detail);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
class HtmlTag {
|
||||||
|
constructor(is_svg = false) {
|
||||||
|
this.is_svg = false;
|
||||||
|
this.is_svg = is_svg;
|
||||||
|
this.e = this.n = null;
|
||||||
|
}
|
||||||
|
c(html) {
|
||||||
|
this.h(html);
|
||||||
|
}
|
||||||
|
m(html, target, anchor = null) {
|
||||||
|
if (!this.e) {
|
||||||
|
if (this.is_svg)
|
||||||
|
this.e = svg_element(target.nodeName);
|
||||||
|
else
|
||||||
|
this.e = element(target.nodeName);
|
||||||
|
this.t = target;
|
||||||
|
this.c(html);
|
||||||
|
}
|
||||||
|
this.i(anchor);
|
||||||
|
}
|
||||||
|
h(html) {
|
||||||
|
this.e.innerHTML = html;
|
||||||
|
this.n = Array.from(this.e.childNodes);
|
||||||
|
}
|
||||||
|
i(anchor) {
|
||||||
|
for (let i = 0; i < this.n.length; i += 1) {
|
||||||
|
insert(this.t, this.n[i], anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p(html) {
|
||||||
|
this.d();
|
||||||
|
this.h(html);
|
||||||
|
this.i(this.a);
|
||||||
|
}
|
||||||
|
d() {
|
||||||
|
this.n.forEach(detach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function attribute_to_object(attributes) {
|
||||||
|
const result = {};
|
||||||
|
for (const attribute of attributes) {
|
||||||
|
result[attribute.name] = attribute.value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_component;
|
||||||
|
function set_current_component(component) {
|
||||||
|
current_component = component;
|
||||||
|
}
|
||||||
|
function get_current_component() {
|
||||||
|
if (!current_component)
|
||||||
|
throw new Error('Function called outside component initialization');
|
||||||
|
return current_component;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
|
||||||
|
* It must be called during the component's initialisation (but doesn't need to live *inside* the component;
|
||||||
|
* it can be called from an external module).
|
||||||
|
*
|
||||||
|
* `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api).
|
||||||
|
*
|
||||||
|
* https://svelte.dev/docs#run-time-svelte-onmount
|
||||||
|
*/
|
||||||
|
function onMount(fn) {
|
||||||
|
get_current_component().$$.on_mount.push(fn);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Schedules a callback to run immediately after the component has been updated.
|
||||||
|
*
|
||||||
|
* The first time the callback runs will be after the initial `onMount`
|
||||||
|
*/
|
||||||
|
function afterUpdate(fn) {
|
||||||
|
get_current_component().$$.after_update.push(fn);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Associates an arbitrary `context` object with the current component and the specified `key`
|
||||||
|
* and returns that object. The context is then available to children of the component
|
||||||
|
* (including slotted content) with `getContext`.
|
||||||
|
*
|
||||||
|
* Like lifecycle functions, this must be called during component initialisation.
|
||||||
|
*
|
||||||
|
* https://svelte.dev/docs#run-time-svelte-setcontext
|
||||||
|
*/
|
||||||
|
function setContext(key, context) {
|
||||||
|
get_current_component().$$.context.set(key, context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves the context that belongs to the closest parent component with the specified `key`.
|
||||||
|
* Must be called during component initialisation.
|
||||||
|
*
|
||||||
|
* https://svelte.dev/docs#run-time-svelte-getcontext
|
||||||
|
*/
|
||||||
|
function getContext(key) {
|
||||||
|
return get_current_component().$$.context.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirty_components = [];
|
||||||
|
const binding_callbacks = [];
|
||||||
|
const render_callbacks = [];
|
||||||
|
const flush_callbacks = [];
|
||||||
|
const resolved_promise = Promise.resolve();
|
||||||
|
let update_scheduled = false;
|
||||||
|
function schedule_update() {
|
||||||
|
if (!update_scheduled) {
|
||||||
|
update_scheduled = true;
|
||||||
|
resolved_promise.then(flush);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function add_render_callback(fn) {
|
||||||
|
render_callbacks.push(fn);
|
||||||
|
}
|
||||||
|
// flush() calls callbacks in this order:
|
||||||
|
// 1. All beforeUpdate callbacks, in order: parents before children
|
||||||
|
// 2. All bind:this callbacks, in reverse order: children before parents.
|
||||||
|
// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT
|
||||||
|
// for afterUpdates called during the initial onMount, which are called in
|
||||||
|
// reverse order: children before parents.
|
||||||
|
// Since callbacks might update component values, which could trigger another
|
||||||
|
// call to flush(), the following steps guard against this:
|
||||||
|
// 1. During beforeUpdate, any updated components will be added to the
|
||||||
|
// dirty_components array and will cause a reentrant call to flush(). Because
|
||||||
|
// the flush index is kept outside the function, the reentrant call will pick
|
||||||
|
// up where the earlier call left off and go through all dirty components. The
|
||||||
|
// current_component value is saved and restored so that the reentrant call will
|
||||||
|
// not interfere with the "parent" flush() call.
|
||||||
|
// 2. bind:this callbacks cannot trigger new flush() calls.
|
||||||
|
// 3. During afterUpdate, any updated components will NOT have their afterUpdate
|
||||||
|
// callback called a second time; the seen_callbacks set, outside the flush()
|
||||||
|
// function, guarantees this behavior.
|
||||||
|
const seen_callbacks = new Set();
|
||||||
|
let flushidx = 0; // Do *not* move this inside the flush() function
|
||||||
|
function flush() {
|
||||||
|
const saved_component = current_component;
|
||||||
|
do {
|
||||||
|
// first, call beforeUpdate functions
|
||||||
|
// and update components
|
||||||
|
while (flushidx < dirty_components.length) {
|
||||||
|
const component = dirty_components[flushidx];
|
||||||
|
flushidx++;
|
||||||
|
set_current_component(component);
|
||||||
|
update(component.$$);
|
||||||
|
}
|
||||||
|
set_current_component(null);
|
||||||
|
dirty_components.length = 0;
|
||||||
|
flushidx = 0;
|
||||||
|
while (binding_callbacks.length)
|
||||||
|
binding_callbacks.pop()();
|
||||||
|
// then, once components are updated, call
|
||||||
|
// afterUpdate functions. This may cause
|
||||||
|
// subsequent updates...
|
||||||
|
for (let i = 0; i < render_callbacks.length; i += 1) {
|
||||||
|
const callback = render_callbacks[i];
|
||||||
|
if (!seen_callbacks.has(callback)) {
|
||||||
|
// ...so guard against infinite loops
|
||||||
|
seen_callbacks.add(callback);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render_callbacks.length = 0;
|
||||||
|
} while (dirty_components.length);
|
||||||
|
while (flush_callbacks.length) {
|
||||||
|
flush_callbacks.pop()();
|
||||||
|
}
|
||||||
|
update_scheduled = false;
|
||||||
|
seen_callbacks.clear();
|
||||||
|
set_current_component(saved_component);
|
||||||
|
}
|
||||||
|
function update($$) {
|
||||||
|
if ($$.fragment !== null) {
|
||||||
|
$$.update();
|
||||||
|
run_all($$.before_update);
|
||||||
|
const dirty = $$.dirty;
|
||||||
|
$$.dirty = [-1];
|
||||||
|
$$.fragment && $$.fragment.p($$.ctx, dirty);
|
||||||
|
$$.after_update.forEach(add_render_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const outroing = new Set();
|
||||||
|
function transition_in(block, local) {
|
||||||
|
if (block && block.i) {
|
||||||
|
outroing.delete(block);
|
||||||
|
block.i(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globals = (typeof window !== 'undefined'
|
||||||
|
? window
|
||||||
|
: typeof globalThis !== 'undefined'
|
||||||
|
? globalThis
|
||||||
|
: global);
|
||||||
|
function mount_component(component, target, anchor, customElement) {
|
||||||
|
const { fragment, after_update } = component.$$;
|
||||||
|
fragment && fragment.m(target, anchor);
|
||||||
|
if (!customElement) {
|
||||||
|
// onMount happens before the initial afterUpdate
|
||||||
|
add_render_callback(() => {
|
||||||
|
const new_on_destroy = component.$$.on_mount.map(run).filter(is_function);
|
||||||
|
// if the component was destroyed immediately
|
||||||
|
// it will update the `$$.on_destroy` reference to `null`.
|
||||||
|
// the destructured on_destroy may still reference to the old array
|
||||||
|
if (component.$$.on_destroy) {
|
||||||
|
component.$$.on_destroy.push(...new_on_destroy);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Edge case - component was destroyed immediately,
|
||||||
|
// most likely as a result of a binding initialising
|
||||||
|
run_all(new_on_destroy);
|
||||||
|
}
|
||||||
|
component.$$.on_mount = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
after_update.forEach(add_render_callback);
|
||||||
|
}
|
||||||
|
function destroy_component(component, detaching) {
|
||||||
|
const $$ = component.$$;
|
||||||
|
if ($$.fragment !== null) {
|
||||||
|
run_all($$.on_destroy);
|
||||||
|
$$.fragment && $$.fragment.d(detaching);
|
||||||
|
// TODO null out other refs, including component.$$ (but need to
|
||||||
|
// preserve final state?)
|
||||||
|
$$.on_destroy = $$.fragment = null;
|
||||||
|
$$.ctx = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function make_dirty(component, i) {
|
||||||
|
if (component.$$.dirty[0] === -1) {
|
||||||
|
dirty_components.push(component);
|
||||||
|
schedule_update();
|
||||||
|
component.$$.dirty.fill(0);
|
||||||
|
}
|
||||||
|
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
|
||||||
|
}
|
||||||
|
function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {
|
||||||
|
const parent_component = current_component;
|
||||||
|
set_current_component(component);
|
||||||
|
const $$ = component.$$ = {
|
||||||
|
fragment: null,
|
||||||
|
ctx: [],
|
||||||
|
// state
|
||||||
|
props,
|
||||||
|
update: noop,
|
||||||
|
not_equal,
|
||||||
|
bound: blank_object(),
|
||||||
|
// lifecycle
|
||||||
|
on_mount: [],
|
||||||
|
on_destroy: [],
|
||||||
|
on_disconnect: [],
|
||||||
|
before_update: [],
|
||||||
|
after_update: [],
|
||||||
|
context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
|
||||||
|
// everything else
|
||||||
|
callbacks: blank_object(),
|
||||||
|
dirty,
|
||||||
|
skip_bound: false,
|
||||||
|
root: options.target || parent_component.$$.root
|
||||||
|
};
|
||||||
|
append_styles && append_styles($$.root);
|
||||||
|
let ready = false;
|
||||||
|
$$.ctx = instance
|
||||||
|
? instance(component, options.props || {}, (i, ret, ...rest) => {
|
||||||
|
const value = rest.length ? rest[0] : ret;
|
||||||
|
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
|
||||||
|
if (!$$.skip_bound && $$.bound[i])
|
||||||
|
$$.bound[i](value);
|
||||||
|
if (ready)
|
||||||
|
make_dirty(component, i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
$$.update();
|
||||||
|
ready = true;
|
||||||
|
run_all($$.before_update);
|
||||||
|
// `false` as a special case of no DOM component
|
||||||
|
$$.fragment = create_fragment ? create_fragment($$.ctx) : false;
|
||||||
|
if (options.target) {
|
||||||
|
if (options.hydrate) {
|
||||||
|
const nodes = children(options.target);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
$$.fragment && $$.fragment.l(nodes);
|
||||||
|
nodes.forEach(detach);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
$$.fragment && $$.fragment.c();
|
||||||
|
}
|
||||||
|
if (options.intro)
|
||||||
|
transition_in(component.$$.fragment);
|
||||||
|
mount_component(component, options.target, options.anchor, options.customElement);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
set_current_component(parent_component);
|
||||||
|
}
|
||||||
|
let SvelteElement;
|
||||||
|
if (typeof HTMLElement === 'function') {
|
||||||
|
SvelteElement = class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
const { on_mount } = this.$$;
|
||||||
|
this.$$.on_disconnect = on_mount.map(run).filter(is_function);
|
||||||
|
// @ts-ignore todo: improve typings
|
||||||
|
for (const key in this.$$.slotted) {
|
||||||
|
// @ts-ignore todo: improve typings
|
||||||
|
this.appendChild(this.$$.slotted[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributeChangedCallback(attr, _oldValue, newValue) {
|
||||||
|
this[attr] = newValue;
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
run_all(this.$$.on_disconnect);
|
||||||
|
}
|
||||||
|
$destroy() {
|
||||||
|
destroy_component(this, 1);
|
||||||
|
this.$destroy = noop;
|
||||||
|
}
|
||||||
|
$on(type, callback) {
|
||||||
|
// TODO should this delegate to addEventListener?
|
||||||
|
if (!is_function(callback)) {
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
|
||||||
|
callbacks.push(callback);
|
||||||
|
return () => {
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
if (index !== -1)
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$set($$props) {
|
||||||
|
if (this.$$set && !is_empty($$props)) {
|
||||||
|
this.$$.skip_bound = true;
|
||||||
|
this.$$set($$props);
|
||||||
|
this.$$.skip_bound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatch_dev(type, detail) {
|
||||||
|
document.dispatchEvent(custom_event(type, Object.assign({ version: '3.52.0' }, detail), { bubbles: true }));
|
||||||
|
}
|
||||||
|
function append_dev(target, node) {
|
||||||
|
dispatch_dev('SvelteDOMInsert', { target, node });
|
||||||
|
append(target, node);
|
||||||
|
}
|
||||||
|
function insert_dev(target, node, anchor) {
|
||||||
|
dispatch_dev('SvelteDOMInsert', { target, node, anchor });
|
||||||
|
insert(target, node, anchor);
|
||||||
|
}
|
||||||
|
function detach_dev(node) {
|
||||||
|
dispatch_dev('SvelteDOMRemove', { node });
|
||||||
|
detach(node);
|
||||||
|
}
|
||||||
|
function listen_dev(node, event, handler, options, has_prevent_default, has_stop_propagation) {
|
||||||
|
const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];
|
||||||
|
if (has_prevent_default)
|
||||||
|
modifiers.push('preventDefault');
|
||||||
|
if (has_stop_propagation)
|
||||||
|
modifiers.push('stopPropagation');
|
||||||
|
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });
|
||||||
|
const dispose = listen(node, event, handler, options);
|
||||||
|
return () => {
|
||||||
|
dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });
|
||||||
|
dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function attr_dev(node, attribute, value) {
|
||||||
|
attr(node, attribute, value);
|
||||||
|
if (value == null)
|
||||||
|
dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute });
|
||||||
|
else
|
||||||
|
dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value });
|
||||||
|
}
|
||||||
|
function prop_dev(node, property, value) {
|
||||||
|
node[property] = value;
|
||||||
|
dispatch_dev('SvelteDOMSetProperty', { node, property, value });
|
||||||
|
}
|
||||||
|
function set_data_dev(text, data) {
|
||||||
|
data = '' + data;
|
||||||
|
if (text.wholeText === data)
|
||||||
|
return;
|
||||||
|
dispatch_dev('SvelteDOMSetData', { node: text, data });
|
||||||
|
text.data = data;
|
||||||
|
}
|
||||||
|
function validate_each_argument(arg) {
|
||||||
|
if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) {
|
||||||
|
let msg = '{#each} only iterates over array-like objects.';
|
||||||
|
if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) {
|
||||||
|
msg += ' You can use a spread to convert this iterable into an array.';
|
||||||
|
}
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function validate_slots(name, slot, keys) {
|
||||||
|
for (const slot_key of Object.keys(slot)) {
|
||||||
|
if (!~keys.indexOf(slot_key)) {
|
||||||
|
console.warn(`<${name}> received an unexpected slot "${slot_key}".`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { run_all as A, flush as B, is_function as C, src_url_equal as D, set_custom_element_data as E, prop_dev as F, set_style as G, svg_element as H, HtmlTag as I, afterUpdate as J, to_number as K, set_input_value as L, SvelteElement as S, attribute_to_object as a, insert_dev as b, validate_store as c, dispatch_dev as d, component_subscribe as e, globals as f, getContext as g, empty as h, init as i, detach_dev as j, binding_callbacks as k, validate_each_argument as l, space as m, noop as n, onMount as o, set_data_dev as p, element as q, add_location as r, safe_not_equal as s, text as t, attr_dev as u, validate_slots as v, append_dev as w, listen_dev as x, destroy_each as y, setContext as z };
|
|
@ -0,0 +1,53 @@
|
||||||
|
|
||||||
|
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
|
||||||
|
import { n as noop, s as safe_not_equal } from './index-2620635a.js';
|
||||||
|
|
||||||
|
const subscriber_queue = [];
|
||||||
|
/**
|
||||||
|
* Create a `Writable` store that allows both updating and reading by subscription.
|
||||||
|
* @param {*=}value initial value
|
||||||
|
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
|
||||||
|
*/
|
||||||
|
function writable(value, start = noop) {
|
||||||
|
let stop;
|
||||||
|
const subscribers = new Set();
|
||||||
|
function set(new_value) {
|
||||||
|
if (safe_not_equal(value, new_value)) {
|
||||||
|
value = new_value;
|
||||||
|
if (stop) { // store is ready
|
||||||
|
const run_queue = !subscriber_queue.length;
|
||||||
|
for (const subscriber of subscribers) {
|
||||||
|
subscriber[1]();
|
||||||
|
subscriber_queue.push(subscriber, value);
|
||||||
|
}
|
||||||
|
if (run_queue) {
|
||||||
|
for (let i = 0; i < subscriber_queue.length; i += 2) {
|
||||||
|
subscriber_queue[i][0](subscriber_queue[i + 1]);
|
||||||
|
}
|
||||||
|
subscriber_queue.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function update(fn) {
|
||||||
|
set(fn(value));
|
||||||
|
}
|
||||||
|
function subscribe(run, invalidate = noop) {
|
||||||
|
const subscriber = [run, invalidate];
|
||||||
|
subscribers.add(subscriber);
|
||||||
|
if (subscribers.size === 1) {
|
||||||
|
stop = start(set) || noop;
|
||||||
|
}
|
||||||
|
run(value);
|
||||||
|
return () => {
|
||||||
|
subscribers.delete(subscriber);
|
||||||
|
if (subscribers.size === 0) {
|
||||||
|
stop();
|
||||||
|
stop = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { set, update, subscribe };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { writable as w };
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
using Genie.Router, Genie.Requests, Genie.Renderer.Json, JSON3, GenieAuthentication
|
using Genie.Router, Genie.Requests, Genie.Renderer.Json, JSON3, GenieAuthentication, GenieAuthorisation
|
||||||
using Server.GroupsController
|
using Server.GroupsController, Server.AdminController
|
||||||
|
|
||||||
#---Basic-----------------------------------------------------------
|
#---Basic-----------------------------------------------------------
|
||||||
|
|
||||||
|
@ -12,6 +12,16 @@ route("/:locale/join-us/*", BasicController.join_us, named = :join_us)
|
||||||
|
|
||||||
route("/:locale/political-compass/*", BasicController.political_compass, named = :political_compass)
|
route("/:locale/political-compass/*", BasicController.political_compass, named = :political_compass)
|
||||||
|
|
||||||
|
#---Admin panel------------------------------------------------------
|
||||||
|
|
||||||
|
route("/:locale/bread/*", AdminController.admin_panel, named = :admin_panel)
|
||||||
|
|
||||||
|
route("/:locale/get-unverified-users/*", AdminController.get_unverified_users, named = :get_unverified_users)
|
||||||
|
|
||||||
|
route("/:locale/verify/*", AdminController.verify, method = POST, named=:verify)
|
||||||
|
|
||||||
|
route("/:locale/add-verified-groups/*", AdminController.add_verified_groups, named = :add_verified_groups)
|
||||||
|
|
||||||
#---Authentication and such------------------------------------------
|
#---Authentication and such------------------------------------------
|
||||||
|
|
||||||
route("/:locale/auth/*", AuthenticationController.auth, named = :auth)
|
route("/:locale/auth/*", AuthenticationController.auth, named = :auth)
|
||||||
|
@ -54,8 +64,6 @@ route("/:locale/group-reject-request/*", GroupsController.reject_request, method
|
||||||
|
|
||||||
route("/:locale/group-change/*", GroupsController.change_group, method = POST, named = :group_change)
|
route("/:locale/group-change/*", GroupsController.change_group, method = POST, named = :group_change)
|
||||||
|
|
||||||
route("/:locale/add-verified-groups/*", GroupsController.add_verified_groups, named = :add_verified_groups)
|
|
||||||
|
|
||||||
#---Coops----------------------------------------------------------
|
#---Coops----------------------------------------------------------
|
||||||
|
|
||||||
route("/:locale/cooperatives/*", CooperativesController.cooperatives, named = :cooperatives)
|
route("/:locale/cooperatives/*", CooperativesController.cooperatives, named = :cooperatives)
|
||||||
|
|
Loading…
Reference in New Issue