diff --git a/js_conversion.py b/js_conversion.py new file mode 100644 index 0000000..956f83e --- /dev/null +++ b/js_conversion.py @@ -0,0 +1,100 @@ +import server.models.models +from server.models.models import Jsonifiable +from neomodel import StructuredNode, StringProperty, RelationshipManager, \ + UniqueIdProperty, DateProperty, StructuredRel, DateTimeProperty, StructuredRel, \ + Property, RelationshipDefinition +import json + +from typing import Type, Tuple, Dict, List, Optional, Generator + + +def constructor(cls: Type[StructuredNode]) -> str: + return 'constructor(json) {\n' + \ + f' super(json)\n' + \ + ' }' + + +def get_set_attr(name, attr) -> str: + if RelationshipDefinition in attr.__class__.__mro__: + return f""" + get_{name}() {{ + return this.json.{name} + }} + + link_{name}(ref) {{ + return this._link_property('{name}', ref) + }} + + unlink_{name}(ref) {{ + return this._unlink_property('{name}', ref) + }} +""" + else: + return f""" + get_{name}() {{ + return this.json.{name} + }} +""" + f""" + set_{name}(new_val) {{ + if (this.check('{name}', new_val)) {{ + this.json.{name} = new_val + this.changes.{name} = new_val + }} + }} +""" if name != 'uid' else '' + + +def all_attrs(cls): + return '\n'.join([ get_set_attr(name, val) for name, val in get_relevant_attributes(cls)]) + + +def js_representation(cls: Type[StructuredNode]) -> str: + props, rels = get_properties_and_relations(cls) + return f"""class {cls.__name__} extends StructuredNode {{ + {constructor(cls)} + + {all_attrs(cls)} +}} + +StructuredNode.registerClassDefinition({cls.__name__}, {json.dumps(props)}, {json.dumps(rels)}) +""" + + +def get_relevant_attributes(cls: Type[StructuredNode]) -> Generator[None, Tuple[str, Property], None]: + for name, value in vars(cls).items(): + if name[0] == '_' or name[0].isupper() and not isinstance(value, (RelationshipDefinition, Property)): + continue + yield name, value + + +def get_properties_and_relations(cls) -> Tuple[Dict[str, dict], Dict[str, dict]]: + props = dict() + rels = dict() + for name, value in get_relevant_attributes(cls): + if isinstance(value, RelationshipDefinition): + target_cls = value._raw_class.__name__ if not isinstance(value._raw_class, str) else value._raw_class + rels[name] = { + 'field': name, + 'target': target_cls, + 'cardinality': value.manager.__name__ + } + elif isinstance(value, Property): + props[name] = { + 'field': name, + 'type': value.__class__.__name__, + 'required': value.required + } + + return props, rels + + +if __name__ == '__main__': + reps = list() + reps.append("// This file was generated by running js_conversion.py\n" + + "// It converts the neomodel declaration of this element into js classes.\n") + + for name, cls in vars(server.models.models).items(): + if hasattr(cls, '__mro__') and StructuredNode in cls.__mro__ and StructuredNode != cls.__mro__[0]: + reps.append(js_representation(cls)) + + print('\n\n\n'.join(reps)) diff --git a/server/abstract_api.py b/server/abstract_api.py new file mode 100644 index 0000000..55ba8dc --- /dev/null +++ b/server/abstract_api.py @@ -0,0 +1,221 @@ +import sys +from server.models.models import Jsonifiable +from flask import Flask, request, Response, jsonify +from typing import Type, Final, List, Optional +from datetime import datetime +from neomodel import StructuredNode, RelationshipManager, RelationshipDefinition +from neomodel.cardinality import ZeroOrOne, ZeroOrMore, One, OneOrMore +import json + +""" +This document declares some abstract api functions that will help you implement a REST api +for you StructuredNodes fast and efficiently. + +The simplest way to achieve this is by using the register_api_object(cls: Type[StructuredNode]) +method. + +Example: + +given the node type: (note that you need to add the mixin Jsonifiable) + +class Person(StructuredNode, Jsonifiable): + uid = UniqueIdProperty() + name = StringProperty(required=True) + children = RelationshipTo('Person', 'CHILDREN') + +# you can register the endpoints /api/person and /api/person/ by calling +register_api_object(Person) + + +# if you want to preotect some attributes from being set this way, simply include them in the +BLOCKED_PROPERTIES global variable in this module +""" + +BLOCKED_PROPERTIES: Final[List[str]] = ['id', 'uid', 'password'] + + +class ApiError(Exception): + """Defines an error that occurred during the handling of an API request""" + def __init__(self, msg, status=400, data=None): + super().__init__() + self.msg = msg + self.status = status + self.data = data + + def __str__(self): + return json.dumps(dict(msg=self.msg, status=self.status)) + + def to_response(self) -> Response: + """Converts the error into a response""" + resp: Response = jsonify(msg=self.msg, data=self.data) + resp.status_code = self.status + return resp + + @staticmethod + def not_found(cls: Type[StructuredNode], uid=None): + return ApiError(f"Cannot find {cls.__name__}" + (f" with uid {uid}" if uid else "") + "!", status=404) + + @staticmethod + def bad_request(msg: str, data=None): + return ApiError(msg, status=400, data=data) + + +def _connect_callback(attr, target): + """returns a callback to connect attr with target, this is wrapped + because we need to get attr and target into a separate closure""" + return lambda: attr.connect(target) + + +def _get_target_class(rel: RelationshipDefinition) -> Type[StructuredNode]: + target_cls = rel._raw_class + return getattr(sys.modules[rel.module_name], target_cls) if isinstance(target_cls, str) else target_cls + + +def construct_object_from_request(cls: Type[StructuredNode], uid=None): + """Construct an obect of class cls from request data""" + # properties that you do not want to initialize or change this way + if uid: + obj: Final = cls.nodes.get_or_none(uid=uid) + + if not obj: + raise ApiError.not_found(cls, uid) + else: + obj: Final = cls() + + relationship_attach_callbacks = list() + + for name, value in cls.__dict__.items(): + if name[0] == '_' or name in BLOCKED_PROPERTIES or not name[0].islower(): + continue + + cls_name = value.__class__.__name__ + convert_with = None + + if cls_name == 'StringProperty': + convert_with = str + elif cls_name == 'DateProperty': + convert_with = datetime.fromisoformat + elif cls_name == 'IntegerProperty': + convert_with = int + elif cls_name == 'FloatProperty': + convert_with = float + elif cls_name == 'RelationshipDefinition': + if name not in request.form: + continue + + target_cls: Type[StructuredNode] = _get_target_class(value) + for val in request.form.getlist(name): + target = target_cls.nodes.first_or_none(uid=val) + if not target: + raise ApiError(f'Cannot find referenced object uid={val} of type {target_cls.__name__}!') + + relationship_attach_callbacks.append(_connect_callback(getattr(obj, name), target)) + if not convert_with: + # skip this property + continue + + if name in request.form: + setattr(obj, name, convert_with(request.form.get(name))) + + return obj, lambda: [c() for c in relationship_attach_callbacks] + + +def handle_object_api_request(cls: Type[StructuredNode]): + """basic method for handling (GET|POST) /api/type calls""" + if request.method == 'GET': + return jsonify([obj.json(include_relations=False) for obj in cls.nodes.all()]) + if request.method == 'POST': + obj, attach_relationships = construct_object_from_request(cls) + obj.save() + attach_relationships() + return jsonify(obj.json()) + + +def handle_object_api_request_id(cls: Type[StructuredNode], uid: str): + """basic method for handling (GET|PUT|DELETE) /api/type/uid calls""" + if request.method == 'GET': + obj = cls.nodes.get_or_none(uid=uid) + if not obj: + raise ApiError.not_found(cls, uid) + return jsonify(obj.json()) + if request.method == 'PUT': + obj, attach_relationships = construct_object_from_request(cls, uid=uid) + obj.save() + attach_relationships() + return jsonify(obj.json()) + if request.method == 'DELETE': + obj = cls.nodes.get_or_none(uid=uid) + if not obj: + raise ApiError.not_found(cls, uid) + + +def handle_object_api_request_for_relation(cls: Type[StructuredNode], uid, relation, reluid): + obj = cls.nodes.get_or_none(uid=uid) + + if not obj: + raise ApiError.not_found(cls, uid) + + rel = getattr(cls, relation, None) + + if not (rel and isinstance(rel, RelationshipDefinition)): + raise ApiError.bad_request(f'Object of type {cls.__name__} has no relation {relation}!') + + target_cls = _get_target_class(rel) + + target_obj = target_cls.nodes.get_or_none(uid=reluid) + + if not target_obj: + raise ApiError.not_found(target_cls, reluid) + + if request.method == 'DELETE': + getattr(obj, relation).disconnect(target_obj) + elif request.method == 'POST': + if isinstance(rel.manager, (ZeroOrOne, One)): + getattr(obj, relation).disconnect_all() + getattr(obj, relation).connect(target_obj) + + return jsonify(obj) + + +def register_api_object(app: Flask, cls: Type[StructuredNode], name: Optional[str] = None): + """Register api paths for the object type cls + + routes registered: + GET /api/ + POST /api/ + GET /api// + PUT /api// + DELETE /api// + POST /api//// connect relationships + DELETE /api//// disconnect relationships + + where defaults to the lowercase class name of cls + + internally it registers the three view functions api., api..with-id and api..rel + """ + name = name or cls.__name__.lower() + + print(app.view_functions) + + def func_handle(): + return handle_object_api_request(cls) + + def func_handle_id(uid): + return handle_object_api_request_id(cls, uid) + + def func_handle_rel(uid, rel, reluid): + return handle_object_api_request_for_relation(cls, uid, rel, reluid) + + app.view_functions[f'api.{name}'] = func_handle + app.view_functions[f'api.{name}.wth-id'] = func_handle_id + app.view_functions[f'api.{name}.rel'] = func_handle_rel + + app.add_url_rule(f'/api/{name}', + f'api.{name}', func_handle, + methods=['GET', 'POST']) + app.add_url_rule(f'/api/{name}/', + f'api.{name}.with-id', func_handle_id, + methods=['GET', 'PUT', 'DELETE']) + app.add_url_rule(f'/api/{name}///', + f'api.{name}.rel', func_handle_rel, + methods=['POST', 'DELETE']) diff --git a/server/api.py b/server/api.py index 17f7fa7..ead0925 100644 --- a/server/api.py +++ b/server/api.py @@ -1,120 +1,18 @@ -import sys from .models import * -from flask import Flask, request, jsonify -from typing import Dict, Type, List, Final -from datetime import datetime - - -class ApiError(BaseException): - def __init__(self, msg, status=400): - super().__init__() - self.msg = msg - self.status = status - - -def connect_callback(attr, target): - """returns a callback to connect attr with target""" - return lambda: attr.connect(target) - - -def construct_object_from_request(cls, uid=None): - if uid: - obj: Final = cls.nodes.get_or_none(uid=uid) - - if not obj: - raise ApiError(f"No {cls.__name__} found with uid {request.form.get('uid')}!", status=404) - else: - obj: Final = cls() - - relationship_attach_callbacks = list() - - for name, value in cls.__dict__.items(): - if name[0] == '_' or not name[0].islower(): - continue - - cls_name = value.__class__.__name__ - convert_with = None - - if cls_name == 'StringProperty': - convert_with = str - elif cls_name == 'DateProperty': - convert_with = datetime.fromisoformat - elif cls_name == 'IntegerProperty': - convert_with = int - elif cls_name == 'FloatProperty': - convert_with = float - elif cls_name == 'RelationshipDefinition': - if name not in request.form: - continue - - target_cls = value._raw_class - # convert class string to class by loading it from module object - if isinstance(target_cls, str): - target_cls = getattr(cls.__module__, target_cls) - - for val in request.form.getlist(name): - target = target_cls.nodes.first_or_none(uid=val) - if not target: - raise ApiError(f'Cannot find referenced object uid={val} of type {target_cls.__name__}!') - - relationship_attach_callbacks.append(connect_callback(getattr(obj, name), target)) - if not convert_with: - # skip this property - continue - - if name in request.form: - setattr(obj, name, convert_with(request.form.get(name))) - - return obj, lambda: [c() for c in relationship_attach_callbacks] - - -def func_api_obj(cls: Type): - """basic method for handling /api/type calls""" - if request.method == 'GET': - return jsonify([p.json(include_relations=False) for p in cls.nodes.all()]) - if request.method == 'POST': - p, attach_relationships = construct_object_from_request(cls) - p.save() - attach_relationships() - return jsonify(p.json()) - - -def func_api_obj_id(cls: Type, uid: str): - if request.method == 'GET': - person = Person.nodes.get_or_none(uid=uid) - if not person: - return "Not found", 404 - return jsonify(person.json()) - if request.method == 'PUT': - p, attach_relationships = construct_object_from_request(cls, uid=uid) - p.save() - attach_relationships() - return jsonify(p.json()) +from server.abstract_api import ApiError, register_api_object +from flask import Flask def attach_to_flask(app: Flask): - @app.route('/api/person', methods=['POST', 'GET']) - def func_api_person(): - return func_api_obj(Person) - - @app.route('/api/person/', methods=['PUT', 'GET']) - def func_api_person_id(uid): - return func_api_obj_id(Person, uid) - - @app.route('/api/event', methods=['POST', 'GET']) - def func_api_event(): - return func_api_obj(Event) - - @app.route('/api/event/', methods=['PUT', 'GET']) - def func_api_event_id(uid): - return func_api_obj_id(Event, uid) - - @app.route('/api/place', methods=['POST', 'GET']) - def func_api_place(): - return func_api_obj(Place) - - @app.route('/api/place/', methods=['PUT', 'GET']) - def func_api_place_id(uid): - return func_api_obj_id(Place, uid) + @app.errorhandler(ApiError) + def handle_api_error(err: ApiError): + return err.to_response() + + # register these as api accessible + register_api_object(app, Person) + register_api_object(app, Event) + register_api_object(app, Place) + register_api_object(app, Image) + register_api_object(app, VisitRel) diff --git a/server/models/models.py b/server/models/models.py index a618072..171a05f 100644 --- a/server/models/models.py +++ b/server/models/models.py @@ -66,6 +66,3 @@ class Image(StructuredNode, Jsonifiable): place = RelationshipTo(Place, 'TAKEN_AT') date = DateProperty() - - - diff --git a/static/api.js b/static/api.js new file mode 100644 index 0000000..ff5f620 --- /dev/null +++ b/static/api.js @@ -0,0 +1,194 @@ +class StructuredNode { + constructor(json) { + if (this.constructor === StructuredNode) { + throw new TypeError('Cannot instantiate object of abstract type StructuredNode!') + } + + this.json = json + this.changes = {} + this.definition = StructuredNode.classes[this.constructor.name] + this.hooks = [] + this.deleted = false + } + + check(name, type, new_value) { + + } + + _link_property(name, elm) { + const relation = this.definition.rels[name] + + if (!relation) { + throw new ApiError(`Cannot find realation ${name} of object ${this.constructor.name}!`, 404, {obj: this, elm: elm}) + } + const type = StructuredNode.classes[relation.target] + + if (typeof elm == "string") { + elm = type.by_id(elm) + } + if (elm instanceof type) { + if (!elm.exists() || !this.exists()) { + throw new ApiError("Cannot connect object before it exists on the backend!") + } + + // make results available immediately + if (relation.cardinality.endsWith('OrMore')) { + if (!this.json[name].contains(elm)) { + this.json[name].push(elm) + } + } else { + this.json[name] = elm + } + + return Api.post(`/${this.constructor.name}/${this.json.uid}/${name}/${elm.json.uid}`).then(json => { + this.json = json + this.update('link') + return this + }) + } + } + + _unlink_property(name, type, elm) { + this.json[name] = this.json[name].filter(elm => elm.json.uid === (elm.uid || elm)) + + } + + static by_id(uid) { + return Api.get(this.constructor.name + '/' + uid).then(x => x.map(elm => new this.constructor(elm))) + } + + static find(attributes, settings) { + + } + + save() { + if (this.deleted) { + throw new ApiError("Cannot save deleted object!", {obj: this}) + } + + if (this.json.uid) { + return Api.put(`/${this.constructor.name}/${this.json.uid}`, this.changes).then(json => { + this.json = json + this.changes = {} + this.update('save') + return this + }) + } else { + return Api.post('/' + this.constructor.name, this.json).then(json => { + this.json = json + this.changes = {} + this.update('created') + return this + }) + } + } + + delete() { + if (!this.exists()) { + throw new ApiError("Cannot delete object that does not exist yet!", {obj: this}) + } + this.deleted = true + Api.delete(`/${this.constructor.name}/${this.json.uid}`).then(r => { + this.update('deleted') + return this + }) + } + + exists() { + return !this.deleted && !!this.json.id + } + + onupdate(callback) { + this.hooks.push(callback) + } + + update(event) { + this.hooks.forEach(cb => { + try { + cb(this, event) + } catch (e) { + console.error(`Error while running update on ${this.constructor.name} update function ${cb}: ${e}`) + console.error(cb, event, e) + } + }) + } + + static registerClassDefinition(klass, props, rels) { + StructuredNode.classes[klass.constructor.name] = {klass, props, rels} + } + +} +StructuredNode.classes = {} + +class Api { + static set_auth(token) { + Api.token = token; + } + + static get(url) { + fetch('/api' + url, {headers: Api.headers()}).then(r => { + if (r.ok) { + return r.json + } else { + // evaluate response and raise error + return ApiError.fromResponse(r) + } + }) + } + static post(url, body="") { + fetch('/api' + url, {headers: Api.headers(), body: JSON.stringify(body), method: 'POST'}).then(r => { + if (r.ok) { + return r.json + } else { + // evaluate response and raise error + return ApiError.fromResponse(r) + } + }) + } + static put(url, body) { + fetch('/api' + url, {headers: Api.headers(), body: JSON.stringify(body), method: 'PUT'}).then(r => { + if (r.ok) { + return r.json + } else { + // evaluate response and raise error + return ApiError.fromResponse(r) + } + }) + } + static delete(url) { + fetch('/api' + url, {headers: Api.headers(), method: 'DELETE'}).then(r => { + if (r.ok) { + return r + } else { + // evaluate response and raise error + return ApiError.fromResponse(r) + } + }) + } + + static headers() { + return { + 'Content-Type': 'application/json' + } + } +} + +class ApiError { + constructor(msg, code, data) { + this.msg = msg + this.code = code + this.data = data + } + + static fromResponse(r) { + if (r.headers.get('Content-Type') === 'application/json') { + return r.json().then(json => { + throw new ApiError(json.msg, r.status, json.data) + }) + } else { + return r.text().then(text => { + throw new ApiError(text, r.status) + }) + } + } +} \ No newline at end of file diff --git a/static/models.js b/static/models.js new file mode 100644 index 0000000..7b1731a --- /dev/null +++ b/static/models.js @@ -0,0 +1,312 @@ +// This file was generated by running js_conversion.py +// It converts the neomodel declaration of this element into js classes. + + + +class Person extends StructuredNode { + constructor(json) { + super(json) + } + + + + get_name() { + return this.json.name + } + + set_name(new_val) { + if (this.check('name', new_val)) { + this.json.name = new_val + this.changes.name = new_val + } + } + + + get_father() { + return this.json.father + } + + link_father(ref) { + return this._link_property('father', ref) + } + + unlink_father(ref) { + return this._unlink_property('father', ref) + } + + + get_mother() { + return this.json.mother + } + + link_mother(ref) { + return this._link_property('mother', ref) + } + + unlink_mother(ref) { + return this._unlink_property('mother', ref) + } + + + get_birthdate() { + return this.json.birthdate + } + + set_birthdate(new_val) { + if (this.check('birthdate', new_val)) { + this.json.birthdate = new_val + this.changes.birthdate = new_val + } + } + + + get_deceased() { + return this.json.deceased + } + + set_deceased(new_val) { + if (this.check('deceased', new_val)) { + this.json.deceased = new_val + this.changes.deceased = new_val + } + } + + + get_description() { + return this.json.description + } + + set_description(new_val) { + if (this.check('description', new_val)) { + this.json.description = new_val + this.changes.description = new_val + } + } + + + get_picture() { + return this.json.picture + } + + set_picture(new_val) { + if (this.check('picture', new_val)) { + this.json.picture = new_val + this.changes.picture = new_val + } + } + + + get_places() { + return this.json.places + } + + link_places(ref) { + return this._link_property('places', ref) + } + + unlink_places(ref) { + return this._unlink_property('places', ref) + } + +} + +StructuredNode.registerClassDefinition(Person, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "birthdate": {"field": "birthdate", "type": "DateProperty", "required": false}, "deceased": {"field": "deceased", "type": "DateProperty", "required": false}, "description": {"field": "description", "type": "StringProperty", "required": false}, "picture": {"field": "picture", "type": "StringProperty", "required": false}}, {"father": {"field": "father", "target": "Person", "cardinality": "ZeroOrOne"}, "mother": {"field": "mother", "target": "Person", "cardinality": "ZeroOrOne"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}}) + + + +class Event extends StructuredNode { + constructor(json) { + super(json) + } + + + + get_name() { + return this.json.name + } + + set_name(new_val) { + if (this.check('name', new_val)) { + this.json.name = new_val + this.changes.name = new_val + } + } + + + get_participants() { + return this.json.participants + } + + link_participants(ref) { + return this._link_property('participants', ref) + } + + unlink_participants(ref) { + return this._unlink_property('participants', ref) + } + + + get_places() { + return this.json.places + } + + link_places(ref) { + return this._link_property('places', ref) + } + + unlink_places(ref) { + return this._unlink_property('places', ref) + } + + + get_date() { + return this.json.date + } + + set_date(new_val) { + if (this.check('date', new_val)) { + this.json.date = new_val + this.changes.date = new_val + } + } + + + get_end_date() { + return this.json.end_date + } + + set_end_date(new_val) { + if (this.check('end_date', new_val)) { + this.json.end_date = new_val + this.changes.end_date = new_val + } + } + + + get_related_events() { + return this.json.related_events + } + + link_related_events(ref) { + return this._link_property('related_events', ref) + } + + unlink_related_events(ref) { + return this._unlink_property('related_events', ref) + } + +} + +StructuredNode.registerClassDefinition(Event, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "date": {"field": "date", "type": "DateProperty", "required": false}, "end_date": {"field": "end_date", "type": "DateProperty", "required": false}}, {"participants": {"field": "participants", "target": "Person", "cardinality": "ZeroOrMore"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}, "related_events": {"field": "related_events", "target": "Event", "cardinality": "ZeroOrMore"}}) + + + +class Place extends StructuredNode { + constructor(json) { + super(json) + } + + + + get_name() { + return this.json.name + } + + set_name(new_val) { + if (this.check('name', new_val)) { + this.json.name = new_val + this.changes.name = new_val + } + } + + + get_description() { + return this.json.description + } + + set_description(new_val) { + if (this.check('description', new_val)) { + this.json.description = new_val + this.changes.description = new_val + } + } + + + get_visitors() { + return this.json.visitors + } + + link_visitors(ref) { + return this._link_property('visitors', ref) + } + + unlink_visitors(ref) { + return this._unlink_property('visitors', ref) + } + +} + +StructuredNode.registerClassDefinition(Place, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}}, {"visitors": {"field": "visitors", "target": "Person", "cardinality": "ZeroOrMore"}}) + + + +class Image extends StructuredNode { + constructor(json) { + super(json) + } + + + + get_description() { + return this.json.description + } + + set_description(new_val) { + if (this.check('description', new_val)) { + this.json.description = new_val + this.changes.description = new_val + } + } + + + get_pictured() { + return this.json.pictured + } + + link_pictured(ref) { + return this._link_property('pictured', ref) + } + + unlink_pictured(ref) { + return this._unlink_property('pictured', ref) + } + + + get_place() { + return this.json.place + } + + link_place(ref) { + return this._link_property('place', ref) + } + + unlink_place(ref) { + return this._unlink_property('place', ref) + } + + + get_date() { + return this.json.date + } + + set_date(new_val) { + if (this.check('date', new_val)) { + this.json.date = new_val + this.changes.date = new_val + } + } + +} + +StructuredNode.registerClassDefinition(Image, {"uid": {"field": "uid", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}, "date": {"field": "date", "type": "DateProperty", "required": false}}, {"pictured": {"field": "pictured", "target": "Person", "cardinality": "ZeroOrMore"}, "place": {"field": "place", "target": "Place", "cardinality": "ZeroOrMore"}}) + diff --git a/static/site.js b/static/site.js index 8bb0b3e..e69de29 100644 --- a/static/site.js +++ b/static/site.js @@ -1,20 +0,0 @@ -const $ = sel => document.querySelector(sel) -const $$ = sel => Array.from(document.querySelectorAll(sel)) -const create_elm = str => { - const div = document.createElement('div') - div.innerHTML = str - return Array.from(div.childNodes) -} - -const flash_msg = (message) => { - const elm = create_elm('
' + message + ' ×
')[0] - $('main .container').prepend(elm) - elm.querySelector('.close-btn').addEventListener('click', e => elm.remove()) -} - -onload(() => { - // remove flash messages when close button is clicked - $$('.flash-msg .close-btn').forEach(elm => elm.addEventListener('click', e => { - elm.parentElement.remove() - })) -}) \ No newline at end of file diff --git a/static/socket.js b/static/socket.js index 4d95d24..e69de29 100644 --- a/static/socket.js +++ b/static/socket.js @@ -1,32 +0,0 @@ -onload(() => { - const socket = io.connect(document.location.host); - - socket.on('flash_msg', flash_msg); - - socket.on('player_state', (data) => { - if (!data.secret) { - // our secret seems to be to old - localStorage.removeItem('secret'); - return register(); - } - //flash_msg("Logged in!"); - localStorage.setItem('secret', data.secret); - localStorage.setItem('name', data.username); - document.cookie = `secret=${data.secret}`; - window.username = data.username; - $('.username').innerText = 'playing as ' + window.username; - }); - - function register() { - const name = localStorage.getItem('name') || prompt("Whats your username?"); - socket.emit('signup', name) - } - - if (!localStorage.getItem('secret')) { - register() - } else { - socket.emit('resume', localStorage.getItem('secret')) - } - - window.socket = socket; -}); \ No newline at end of file diff --git a/static/style.css b/static/style.css index f0cc707..ee5c124 100644 --- a/static/style.css +++ b/static/style.css @@ -68,16 +68,3 @@ a:hover { text-decoration: underline; } -.flash-msg { - border: 1px solid; - padding: 8px 16px; - border-radius: 3px; - margin: 16px; - margin-top: -16px; - margin-bottom: 32px; - background: rgb(48, 50, 51) -} - -.flash-msg .close-btn { - cursor: pointer; -} \ No newline at end of file