mirror of
https://github.com/lucaspalomodevelop/eventcally.git
synced 2026-03-13 00:07:22 +00:00
Custom widgets #345
This commit is contained in:
parent
2ce0dc46b2
commit
daabc2659b
49
messages.pot
49
messages.pot
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-12-08 13:32+0100\n"
|
||||
"POT-Creation-Date: 2021-12-30 14:51+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -193,29 +193,29 @@ msgstr ""
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: project/api/organization/resources.py:364
|
||||
#: project/api/organization/resources.py:371
|
||||
#: project/views/admin_unit_member_invitation.py:85
|
||||
msgid "You have received an invitation"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:311
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:312
|
||||
#: project/views/root.py:37
|
||||
msgid "Terms of service"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:315
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:316
|
||||
#: project/views/root.py:45
|
||||
msgid "Legal notice"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:12 project/templates/_macros.html:1392
|
||||
#: project/templates/layout.html:319
|
||||
#: project/templates/layout.html:320
|
||||
#: project/templates/widget/event_suggestion/create.html:204
|
||||
#: project/views/admin_unit.py:73 project/views/root.py:53
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:323
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:324
|
||||
#: project/views/root.py:61
|
||||
msgid "Privacy"
|
||||
msgstr ""
|
||||
@ -917,7 +917,7 @@ msgstr ""
|
||||
msgid "Distance"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/event_date.py:33 project/forms/planing.py:36
|
||||
#: project/forms/event_date.py:38 project/forms/planing.py:36
|
||||
#: project/templates/widget/event_date/list.html:82
|
||||
msgid "Find"
|
||||
msgstr ""
|
||||
@ -1342,7 +1342,7 @@ msgid "Manage"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/home.html:30 project/templates/security/login_user.html:38
|
||||
#: project/views/widget.py:158
|
||||
#: project/views/widget.py:159
|
||||
msgid "Register for free"
|
||||
msgstr ""
|
||||
|
||||
@ -1366,7 +1366,7 @@ msgstr ""
|
||||
msgid "Planing"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:273
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:274
|
||||
#: project/templates/oauth2_client/list.html:10
|
||||
#: project/templates/oauth2_client/read.html:10
|
||||
#: project/templates/oauth2_token/list.html:10 project/templates/profile.html:4
|
||||
@ -1468,17 +1468,22 @@ msgstr ""
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:272 project/templates/manage/reviews.html:10
|
||||
#: project/templates/layout.html:272
|
||||
#: project/templates/manage/custom_widgets.html:13
|
||||
msgid "Custom widgets"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:273 project/templates/manage/reviews.html:10
|
||||
#: project/templates/manage/widgets.html:5
|
||||
#: project/templates/manage/widgets.html:9
|
||||
msgid "Widgets"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:283
|
||||
#: project/templates/layout.html:284
|
||||
msgid "Switch organization"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:333
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:334
|
||||
#: project/templates/profile.html:29
|
||||
msgid "Developer"
|
||||
msgstr ""
|
||||
@ -1945,7 +1950,7 @@ msgstr ""
|
||||
msgid "Organization successfully updated"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/admin.py:68 project/views/manage.py:331
|
||||
#: project/views/admin.py:68 project/views/manage.py:346
|
||||
msgid "Settings successfully updated"
|
||||
msgstr ""
|
||||
|
||||
@ -2003,27 +2008,27 @@ msgstr ""
|
||||
msgid "Invitation successfully deleted"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:179
|
||||
#: project/views/event.py:178
|
||||
msgid "Event successfully published"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:181
|
||||
#: project/views/event.py:180
|
||||
msgid "Draft successfully saved"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:224
|
||||
#: project/views/event.py:223
|
||||
msgid "Event successfully updated"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:250
|
||||
#: project/views/event.py:249
|
||||
msgid "Event successfully deleted"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:409
|
||||
#: project/views/event.py:408
|
||||
msgid "Referenced event changed"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:432
|
||||
#: project/views/event.py:431
|
||||
msgid "New event report"
|
||||
msgstr ""
|
||||
|
||||
@ -2179,17 +2184,17 @@ msgid ""
|
||||
" the invitation was sent to."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:150
|
||||
#: project/views/widget.py:151
|
||||
msgid "Thank you so much! The event is being verified."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:154
|
||||
#: project/views/widget.py:155
|
||||
msgid ""
|
||||
"For more options and your own calendar of events, you can register for "
|
||||
"free."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:212
|
||||
#: project/views/widget.py:221
|
||||
msgid "New event review"
|
||||
msgstr ""
|
||||
|
||||
|
||||
55
migrations/versions/c5fbefbe9881_.py
Normal file
55
migrations/versions/c5fbefbe9881_.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: c5fbefbe9881
|
||||
Revises: eba21922b9b7
|
||||
Create Date: 2021-12-29 22:35:29.300556
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
from project import dbtypes
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c5fbefbe9881"
|
||||
down_revision = "eba21922b9b7"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"customwidget",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("widget_type", sa.Unicode(length=255), nullable=False),
|
||||
sa.Column("name", sa.Unicode(length=255), nullable=False),
|
||||
sa.Column("admin_unit_id", sa.Integer(), nullable=False),
|
||||
sa.Column("settings", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("created_by_id", sa.Integer(), nullable=True),
|
||||
sa.Column("updated_by_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["admin_unit_id"],
|
||||
["adminunit.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["created_by_id"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["updated_by_id"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("customwidget")
|
||||
# ### end Alembic commands ###
|
||||
@ -125,6 +125,7 @@ scope_list = [
|
||||
"organization:read",
|
||||
"organization:write",
|
||||
"eventlist:write",
|
||||
"customwidget:write",
|
||||
]
|
||||
scopes = {k: get_localized_scope(k) for v, k in enumerate(scope_list)}
|
||||
|
||||
@ -188,6 +189,7 @@ def add_oauth2_scheme_with_transport(insecure: bool):
|
||||
|
||||
marshmallow_plugin.converter.add_attribute_function(enum_to_properties)
|
||||
|
||||
import project.api.custom_widget.resources
|
||||
import project.api.dump.resources
|
||||
import project.api.event.resources
|
||||
import project.api.event_category.resources
|
||||
|
||||
0
project/api/custom_widget/__init__.py
Normal file
0
project/api/custom_widget/__init__.py
Normal file
84
project/api/custom_widget/resources.py
Normal file
84
project/api/custom_widget/resources.py
Normal file
@ -0,0 +1,84 @@
|
||||
from flask import make_response
|
||||
from flask_apispec import doc, marshal_with, use_kwargs
|
||||
|
||||
from project import db
|
||||
from project.access import access_or_401, login_api_user_or_401
|
||||
from project.api import add_api_resource
|
||||
from project.api.custom_widget.schemas import (
|
||||
CustomWidgetPatchRequestSchema,
|
||||
CustomWidgetPostRequestSchema,
|
||||
CustomWidgetSchema,
|
||||
)
|
||||
from project.api.resources import BaseResource, require_api_access
|
||||
from project.models import CustomWidget
|
||||
|
||||
|
||||
class CustomWidgetResource(BaseResource):
|
||||
@doc(summary="Get custom widget", tags=["Custom Widgets"])
|
||||
@marshal_with(CustomWidgetSchema)
|
||||
def get(self, id):
|
||||
return CustomWidget.query.get_or_404(id)
|
||||
|
||||
@doc(
|
||||
summary="Update custom widget",
|
||||
tags=["Custom Widgets"],
|
||||
security=[{"oauth2": ["customwidget:write"]}],
|
||||
)
|
||||
@use_kwargs(CustomWidgetPostRequestSchema, location="json", apply=False)
|
||||
@marshal_with(None, 204)
|
||||
@require_api_access("customwidget:write")
|
||||
def put(self, id):
|
||||
login_api_user_or_401()
|
||||
customwidget = CustomWidget.query.get_or_404(id)
|
||||
access_or_401(customwidget.adminunit, "admin_unit:update")
|
||||
|
||||
customwidget = self.update_instance(
|
||||
CustomWidgetPostRequestSchema, instance=customwidget
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
return make_response("", 204)
|
||||
|
||||
@doc(
|
||||
summary="Patch custom widget",
|
||||
tags=["Custom Widgets"],
|
||||
security=[{"oauth2": ["customwidget:write"]}],
|
||||
)
|
||||
@use_kwargs(CustomWidgetPatchRequestSchema, location="json", apply=False)
|
||||
@marshal_with(None, 204)
|
||||
@require_api_access("customwidget:write")
|
||||
def patch(self, id):
|
||||
login_api_user_or_401()
|
||||
customwidget = CustomWidget.query.get_or_404(id)
|
||||
access_or_401(customwidget.adminunit, "admin_unit:update")
|
||||
|
||||
customwidget = self.update_instance(
|
||||
CustomWidgetPatchRequestSchema, instance=customwidget
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
return make_response("", 204)
|
||||
|
||||
@doc(
|
||||
summary="Delete custom widget",
|
||||
tags=["Custom Widgets"],
|
||||
security=[{"oauth2": ["customwidget:write"]}],
|
||||
)
|
||||
@marshal_with(None, 204)
|
||||
@require_api_access("customwidget:write")
|
||||
def delete(self, id):
|
||||
login_api_user_or_401()
|
||||
customwidget = CustomWidget.query.get_or_404(id)
|
||||
access_or_401(customwidget.adminunit, "admin_unit:update")
|
||||
|
||||
db.session.delete(customwidget)
|
||||
db.session.commit()
|
||||
|
||||
return make_response("", 204)
|
||||
|
||||
|
||||
add_api_resource(
|
||||
CustomWidgetResource,
|
||||
"/custom-widgets/<int:id>",
|
||||
"api_v1_custom_widget",
|
||||
)
|
||||
82
project/api/custom_widget/schemas.py
Normal file
82
project/api/custom_widget/schemas.py
Normal file
@ -0,0 +1,82 @@
|
||||
from marshmallow import fields, validate
|
||||
|
||||
from project.api import marshmallow
|
||||
from project.api.organization.schemas import OrganizationRefSchema
|
||||
from project.api.schemas import (
|
||||
IdSchemaMixin,
|
||||
PaginationRequestSchema,
|
||||
PaginationResponseSchema,
|
||||
SQLAlchemyBaseSchema,
|
||||
TrackableSchemaMixin,
|
||||
WriteIdSchemaMixin,
|
||||
)
|
||||
from project.models import CustomWidget
|
||||
|
||||
|
||||
class CustomWidgetModelSchema(SQLAlchemyBaseSchema):
|
||||
class Meta:
|
||||
model = CustomWidget
|
||||
load_instance = True
|
||||
|
||||
|
||||
class CustomWidgetIdSchema(CustomWidgetModelSchema, IdSchemaMixin):
|
||||
pass
|
||||
|
||||
|
||||
class CustomWidgetDumpIdSchema(CustomWidgetModelSchema, IdSchemaMixin):
|
||||
pass
|
||||
|
||||
|
||||
class CustomWidgetWriteIdSchema(CustomWidgetModelSchema, WriteIdSchemaMixin):
|
||||
pass
|
||||
|
||||
|
||||
class CustomWidgetBaseSchemaMixin(TrackableSchemaMixin):
|
||||
widget_type = marshmallow.auto_field(
|
||||
required=True, validate=validate.Length(min=3, max=255)
|
||||
)
|
||||
name = marshmallow.auto_field(
|
||||
required=True, validate=validate.Length(min=3, max=255)
|
||||
)
|
||||
settings = fields.Dict(keys=fields.Str())
|
||||
|
||||
|
||||
class CustomWidgetSchema(CustomWidgetIdSchema, CustomWidgetBaseSchemaMixin):
|
||||
organization = fields.Nested(OrganizationRefSchema, attribute="adminunit")
|
||||
|
||||
|
||||
class CustomWidgetDumpSchema(CustomWidgetIdSchema, CustomWidgetBaseSchemaMixin):
|
||||
organization_id = fields.Int(attribute="admin_unit_id")
|
||||
|
||||
|
||||
class CustomWidgetRefSchema(CustomWidgetIdSchema):
|
||||
widget_type = marshmallow.auto_field()
|
||||
name = marshmallow.auto_field()
|
||||
|
||||
|
||||
class CustomWidgetListRequestSchema(PaginationRequestSchema):
|
||||
name = fields.Str(
|
||||
metadata={"description": "Looks for name."},
|
||||
)
|
||||
|
||||
|
||||
class CustomWidgetListResponseSchema(PaginationResponseSchema):
|
||||
items = fields.List(
|
||||
fields.Nested(CustomWidgetRefSchema), metadata={"description": "Custom widgets"}
|
||||
)
|
||||
|
||||
|
||||
class CustomWidgetPostRequestSchema(
|
||||
CustomWidgetModelSchema, CustomWidgetBaseSchemaMixin
|
||||
):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.make_post_schema()
|
||||
|
||||
|
||||
class CustomWidgetPatchRequestSchema(
|
||||
CustomWidgetModelSchema, CustomWidgetBaseSchemaMixin
|
||||
):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.make_patch_schema()
|
||||
@ -11,6 +11,12 @@ from project.access import (
|
||||
login_api_user_or_401,
|
||||
)
|
||||
from project.api import add_api_resource
|
||||
from project.api.custom_widget.schemas import (
|
||||
CustomWidgetIdSchema,
|
||||
CustomWidgetListRequestSchema,
|
||||
CustomWidgetListResponseSchema,
|
||||
CustomWidgetPostRequestSchema,
|
||||
)
|
||||
from project.api.event.resources import api_can_read_private_events
|
||||
from project.api.event.schemas import (
|
||||
EventIdSchema,
|
||||
@ -70,6 +76,7 @@ from project.oauth2 import require_oauth
|
||||
from project.services.admin_unit import (
|
||||
get_admin_unit_invitation_query,
|
||||
get_admin_unit_query,
|
||||
get_custom_widget_query,
|
||||
get_event_list_query,
|
||||
get_event_list_status_query,
|
||||
get_organizer_query,
|
||||
@ -422,6 +429,42 @@ class OrganizationEventListStatusListResource(BaseResource):
|
||||
return pagination
|
||||
|
||||
|
||||
class OrganizationCustomWidgetListResource(BaseResource):
|
||||
@doc(
|
||||
summary="List custom widgets of organization",
|
||||
tags=["Organizations", "Custom Widgets"],
|
||||
)
|
||||
@use_kwargs(CustomWidgetListRequestSchema, location=("query"))
|
||||
@marshal_with(CustomWidgetListResponseSchema)
|
||||
def get(self, id, **kwargs):
|
||||
admin_unit = AdminUnit.query.get_or_404(id)
|
||||
name = kwargs["name"] if "name" in kwargs else None
|
||||
|
||||
pagination = get_custom_widget_query(admin_unit.id, name).paginate()
|
||||
return pagination
|
||||
|
||||
@doc(
|
||||
summary="Add new custom widget",
|
||||
tags=["Organizations", "CustomWidgets"],
|
||||
security=[{"oauth2": ["customwidget:write"]}],
|
||||
)
|
||||
@use_kwargs(CustomWidgetPostRequestSchema, location="json", apply=False)
|
||||
@marshal_with(CustomWidgetIdSchema, 201)
|
||||
@require_api_access("customwidget:write")
|
||||
def post(self, id):
|
||||
login_api_user_or_401()
|
||||
admin_unit = get_admin_unit_for_manage_or_404(id)
|
||||
access_or_401(admin_unit, "admin_unit:update")
|
||||
|
||||
custom_widget = self.create_instance(
|
||||
CustomWidgetPostRequestSchema, admin_unit_id=admin_unit.id
|
||||
)
|
||||
db.session.add(custom_widget)
|
||||
db.session.commit()
|
||||
|
||||
return custom_widget, 201
|
||||
|
||||
|
||||
add_api_resource(OrganizationResource, "/organizations/<int:id>", "api_v1_organization")
|
||||
add_api_resource(
|
||||
OrganizationEventDateSearchResource,
|
||||
@ -479,3 +522,8 @@ add_api_resource(
|
||||
"/organizations/<int:id>/organization-invitations",
|
||||
"api_v1_organization_organization_invitation_list",
|
||||
)
|
||||
add_api_resource(
|
||||
OrganizationCustomWidgetListResource,
|
||||
"/organizations/<int:id>/custom-widgets",
|
||||
"api_v1_organization_custom_widget_list",
|
||||
)
|
||||
|
||||
@ -29,6 +29,7 @@ class FindEventDateForm(FlaskForm):
|
||||
choices=distance_choices,
|
||||
)
|
||||
event_list_id = HiddenField(validators=[Optional()])
|
||||
organization_id = HiddenField(validators=[Optional()])
|
||||
s_ft = HiddenField(validators=[Optional()])
|
||||
s_bg = HiddenField(validators=[Optional()])
|
||||
s_pr = HiddenField(validators=[Optional()])
|
||||
|
||||
@ -27,6 +27,7 @@ from sqlalchemy import (
|
||||
func,
|
||||
select,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
@ -443,6 +444,11 @@ class AdminUnit(db.Model, TrackableMixin):
|
||||
cascade="all, delete-orphan",
|
||||
backref=backref("adminunit", lazy=True),
|
||||
)
|
||||
custom_widgets = relationship(
|
||||
"CustomWidget",
|
||||
cascade="all, delete-orphan",
|
||||
backref=backref("adminunit", lazy=True),
|
||||
)
|
||||
location_id = deferred(db.Column(db.Integer, db.ForeignKey("location.id")))
|
||||
location = db.relationship(
|
||||
"Location", uselist=False, single_parent=True, cascade="all, delete-orphan"
|
||||
@ -1143,6 +1149,15 @@ class Analytics(db.Model):
|
||||
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
class CustomWidget(db.Model, TrackableMixin):
|
||||
__tablename__ = "customwidget"
|
||||
id = Column(Integer(), primary_key=True)
|
||||
widget_type = Column(Unicode(255), nullable=False)
|
||||
name = Column(Unicode(255), nullable=False)
|
||||
admin_unit_id = db.Column(db.Integer, db.ForeignKey("adminunit.id"), nullable=False)
|
||||
settings = Column(JSONB)
|
||||
|
||||
|
||||
# Deprecated begin
|
||||
class FeaturedEventReviewStatus(IntEnum):
|
||||
inbox = 1
|
||||
|
||||
@ -8,6 +8,7 @@ from project.models import (
|
||||
AdminUnitMemberInvitation,
|
||||
AdminUnitMemberRole,
|
||||
AdminUnitRelation,
|
||||
CustomWidget,
|
||||
EventEventLists,
|
||||
EventList,
|
||||
EventOrganizer,
|
||||
@ -195,6 +196,16 @@ def get_organizer_query(admin_unit_id, name=None):
|
||||
return query.order_by(func.lower(EventOrganizer.name))
|
||||
|
||||
|
||||
def get_custom_widget_query(admin_unit_id, name=None):
|
||||
query = CustomWidget.query.filter(CustomWidget.admin_unit_id == admin_unit_id)
|
||||
|
||||
if name:
|
||||
like_name = "%" + name + "%"
|
||||
query = query.filter(CustomWidget.name.ilike(like_name))
|
||||
|
||||
return query.order_by(func.lower(CustomWidget.name))
|
||||
|
||||
|
||||
def get_place_query(admin_unit_id, name=None):
|
||||
query = EventPlace.query.filter(EventPlace.admin_unit_id == admin_unit_id)
|
||||
|
||||
|
||||
@ -127,3 +127,6 @@ class EventSearchParams(object):
|
||||
|
||||
if "sort" in request.args:
|
||||
self.sort = request.args["sort"]
|
||||
|
||||
if "organization_id" in request.args:
|
||||
self.admin_unit_id = request.args["organization_id"]
|
||||
|
||||
@ -5,16 +5,17 @@ const CustomTypeahead = {
|
||||
:name="label"
|
||||
:detectInput="false"
|
||||
ref="validationProvider"
|
||||
rules="required"
|
||||
:rules="rules"
|
||||
v-slot="validationContext">
|
||||
<b-form-group :label="label">
|
||||
<vue-typeahead-bootstrap
|
||||
ref="typeahead"
|
||||
v-model="query"
|
||||
:data="suggestions"
|
||||
:minMatchingChars="1"
|
||||
:disableSort="true"
|
||||
:showAllResults="true"
|
||||
:placeholder="$t('shared.autocomplete.instruction')"
|
||||
:placeholder="$attrs.placeholder != null ? $attrs.placeholder : $t('shared.autocomplete.instruction')"
|
||||
:inputClass="getInputClass(validationContext)"
|
||||
@hit="selected = $event"
|
||||
@input="onInput"
|
||||
@ -31,12 +32,27 @@ const CustomTypeahead = {
|
||||
value: {
|
||||
type: null
|
||||
},
|
||||
rules: {
|
||||
type: [Object, String],
|
||||
default: "required"
|
||||
},
|
||||
fetchURL: {
|
||||
type: String
|
||||
},
|
||||
labelKey: {
|
||||
type: String
|
||||
},
|
||||
labelValue: {
|
||||
type: String
|
||||
},
|
||||
validClass: {
|
||||
type: String,
|
||||
default: "is-valid"
|
||||
},
|
||||
invalidClass: {
|
||||
type: String,
|
||||
default: "is-invalid"
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
query: '',
|
||||
@ -45,7 +61,7 @@ const CustomTypeahead = {
|
||||
}),
|
||||
computed: {
|
||||
label() {
|
||||
return this.$t(this.labelKey)
|
||||
return this.labelValue != null ? this.labelValue : this.$t(this.labelKey);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -57,7 +73,7 @@ const CustomTypeahead = {
|
||||
return "";
|
||||
}
|
||||
|
||||
return valid ? "is-valid" : "is-invalid";
|
||||
return valid ? this.validClass : this.invalidClass;
|
||||
},
|
||||
fetchData(query) {
|
||||
const vm = this
|
||||
@ -74,7 +90,16 @@ const CustomTypeahead = {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.validationProvider.syncValue(this.selected)
|
||||
this.$refs.validationProvider.syncValue(this.selected);
|
||||
|
||||
this.$watch(
|
||||
"$refs.typeahead.isFocused",
|
||||
(new_value, old_value) => {
|
||||
if (new_value && this.$refs.typeahead.showOnFocus && this.query == "") {
|
||||
this.fetchData(this.query);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
watch: {
|
||||
selected(newVal) {
|
||||
|
||||
571
project/static/vue/widget-configurator/configurator.vue.js
Normal file
571
project/static/vue/widget-configurator/configurator.vue.js
Normal file
@ -0,0 +1,571 @@
|
||||
const WidgetConfigurator = {
|
||||
template: `
|
||||
<b-container fluid class="h-100 d-flex flex-column">
|
||||
<b-row class="flex-shrink-0 bg-light" align-v="center">
|
||||
<b-col cols="auto">
|
||||
<h1 class="my-3">{{ $t("comp.title") }}</h1>
|
||||
</b-col>
|
||||
<b-col cols="auto">
|
||||
<b-overlay :show="isLoading">
|
||||
<b-form inline>
|
||||
<b-input-group :prepend="$t('shared.models.customWidget.widgetType')" class="mb-2 mr-sm-2 mb-sm-0">
|
||||
<b-form-select v-model="widgetType" :options="widgetTypes"></b-form-select>
|
||||
</b-input-group>
|
||||
<b-input-group :prepend="$t('shared.models.customWidget.name')" class="mb-2 mr-sm-2 mb-sm-0">
|
||||
<b-form-input v-model="name"></b-form-input>
|
||||
</b-input-group>
|
||||
<b-button variant="secondary" @click="goBack" v-bind:disabled="isSubmitting" class="mb-2 mr-sm-2 mb-sm-0">{{ $t("shared.cancel") }}</b-button>
|
||||
<b-button variant="primary" @click.prevent="submitForm()" v-bind:disabled="isSubmitting" class="mb-2 mb-sm-0">
|
||||
<b-spinner small v-if="isSubmitting"></b-spinner>
|
||||
{{ $t("shared.save") }}
|
||||
</b-button>
|
||||
</b-form inline>
|
||||
</b-overlay>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="flex-fill" style="min-height:0;">
|
||||
<b-col sm="3" class="mh-100 py-3" style="overflow-y: scroll;">
|
||||
<b-overlay :show="isLoading">
|
||||
<b-card no-body>
|
||||
<b-tabs card>
|
||||
<b-tab :title="$t('comp.tabSettings')" active>
|
||||
<div v-if="widgetType == 'search'">
|
||||
<custom-typeahead
|
||||
v-model="searchEventList"
|
||||
rules=""
|
||||
validClass=""
|
||||
:fetchURL="eventListFetchUrl"
|
||||
:labelValue="$t('comp.search.eventListId')"
|
||||
:showOnFocus="true"
|
||||
:serializer="i => i.name"
|
||||
/>
|
||||
<b-form-group :label="$t('comp.search.layout')">
|
||||
<b-form-select v-model="form.search.layout" :options="searchLayouts"></b-form-select>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventsPerPage')">
|
||||
<b-form-input v-model="form.search.eventsPerPage"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('comp.search.view')">
|
||||
<b-form-checkbox v-model="form.search.showFilter">
|
||||
{{ $t("comp.search.showFilter") }}
|
||||
</b-form-checkbox>
|
||||
<b-form-checkbox v-model="form.search.showPagination">
|
||||
{{ $t("comp.search.pagination") }}
|
||||
</b-form-checkbox>
|
||||
<b-form-checkbox v-model="form.search.showOvedaLink">
|
||||
{{ $t("comp.search.showOvedaLink") }}
|
||||
</b-form-checkbox>
|
||||
<b-form-checkbox v-model="form.search.showPrintButton">
|
||||
{{ $t("comp.search.printButton") }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('comp.search.iFrameMinHeight')">
|
||||
<b-form-input v-model="form.search.iFrameMinHeight" debounce="500"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.iFrameMaxHeight')">
|
||||
<b-form-input v-model="form.search.iFrameMaxHeight" debounce="500"></b-form-input>
|
||||
</b-form-group>
|
||||
</div>
|
||||
|
||||
<div v-if="widgetType == 'calendar'">
|
||||
<b-form-group :label="$t('comp.calendar.calendarType')">
|
||||
<b-form-select v-model="form.calendar.calendarType" :options="calendarTypes"></b-form-select>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.calendar.iFrameHeight')">
|
||||
<b-form-input v-model="form.calendar.iFrameHeight" debounce="500"></b-form-input>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</b-tab>
|
||||
<b-tab :title="$t('comp.tabStyles')">
|
||||
<div v-if="widgetType == 'search'">
|
||||
<b-form-group :label="$t('comp.generic.fontFamily')">
|
||||
<b-form-input v-model="form.search.fontFamily"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.generic.padding')">
|
||||
<b-form-input v-model="form.search.padding"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.background" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.textColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.generic.linkColor')">
|
||||
<b-form-input v-model="form.search.linkColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<template v-if="form.search.layout == 'card'">
|
||||
<b-form-group :label="$t('comp.search.event') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.eventBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.event') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.eventBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventName') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.eventNameTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventDate') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.eventDateTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventInfo') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.eventInfoTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<b-form-group :label="$t('comp.search.eventBadgeWarning') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.eventBadgeWarningBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventBadgeWarning') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.eventBadgeWarningTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventBadgeInfo') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.eventBadgeInfoBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.eventBadgeInfo') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.eventBadgeInfoTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<template v-if="form.search.showFilter">
|
||||
<b-form-group :label="$t('comp.search.filterLabel') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.filterLabelBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterLabel') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.filterLabelBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterLabel') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.filterLabelTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('comp.search.filterInput') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.filterInputBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterInput') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.filterInputBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterInput') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.filterInputTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('comp.search.filterButton') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.buttonBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterButton') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.buttonBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.filterButton') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.buttonTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<template v-if="form.search.showPagination">
|
||||
<b-form-group :label="$t('comp.search.pagination') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.pagingBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.pagination') + ' ' + $t('comp.generic.color')">
|
||||
<b-form-input v-model="form.search.pagingColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.pagination') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.pagingTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.pagination') + ' ' + $t('comp.generic.disabled') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.pagingDisabledTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.pagination') + ' ' + $t('comp.generic.active') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.pagingActiveTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<template v-if="form.search.showPrintButton">
|
||||
<b-form-group :label="$t('comp.search.printButton') + ' ' + $t('comp.generic.backgroundColor')">
|
||||
<b-form-input v-model="form.search.printButtonBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.printButton') + ' ' + $t('comp.generic.borderColor')">
|
||||
<b-form-input v-model="form.search.printButtonBorderColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('comp.search.printButton') + ' ' + $t('comp.generic.textColor')">
|
||||
<b-form-input v-model="form.search.printButtonTextColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="widgetType == 'calendar'">
|
||||
<b-form-group :label="$t('comp.calendar.eventBackgroundColor')">
|
||||
<b-form-input v-model="form.calendar.eventBackgroundColor" type="color"></b-form-input>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</b-card>
|
||||
</b-overlay>
|
||||
</b-col>
|
||||
<b-col sm="9" class="mh-100 py-3" style="overflow-y: scroll;">
|
||||
<b-container fluid class="h-100 d-flex flex-column px-0">
|
||||
<b-row class="flex-shrink-0">
|
||||
<b-col class="my-2">
|
||||
<b-form inline class="float-sm-right">
|
||||
<b-form-input
|
||||
v-model="previewBackgroundColor"
|
||||
type="color"
|
||||
class="mr-sm-2"
|
||||
style="min-width:40px;"></b-form-input>
|
||||
<b-form-radio-group
|
||||
v-model="previewSize"
|
||||
:options="previewSizes"
|
||||
size="sm"
|
||||
button-variant="outline-secondary"
|
||||
buttons
|
||||
></b-form-radio-group>
|
||||
</b-form>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="flex-fill" style="min-height:0;">
|
||||
<b-col class="mh-100" style="overflow-y: scroll;">
|
||||
<div ref="previewPage" class="h-100 border p-3 m-auto" :style="{width: previewSize, 'background-color': previewBackgroundColor + '!important', 'overflow-y': 'scroll'}">
|
||||
<div class="mb-2 font-weight-bold">{{ $t("comp.preview") }}</div>
|
||||
<iframe v-if="iFrameActive" :key="iFrameCounter" class="preview_iframe" ref="previewIframe" :src="iFrameSource" style="width:100%; height:300px;" frameborder="0"></iframe>
|
||||
</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
`,
|
||||
i18n: {
|
||||
messages: {
|
||||
en: {
|
||||
comp: {
|
||||
title: "Widget configurator",
|
||||
successMessage: "Widget successfully saved",
|
||||
preview: "Preview",
|
||||
tabSettings: "Serrings",
|
||||
tabStyles: "Styles",
|
||||
generic: {
|
||||
fontFamily: "Font family",
|
||||
padding: "Padding",
|
||||
backgroundColor: "Background color",
|
||||
textColor: "Text color",
|
||||
linkColor: "Link color",
|
||||
borderColor: "Border color",
|
||||
color: "Color",
|
||||
disabled: "Deactivated",
|
||||
active: "Activ",
|
||||
},
|
||||
calendar: {
|
||||
iFrameHeight: "Height",
|
||||
eventBackgroundColor: "Event color",
|
||||
calendarType: "Calendar type",
|
||||
},
|
||||
search: {
|
||||
iFrameMinHeight: "Min. Height",
|
||||
iFrameMaxHeight: "Max. Height",
|
||||
eventListId: "Event list",
|
||||
view: "Display",
|
||||
showFilter: "Filter",
|
||||
showOvedaLink: "Oveda link",
|
||||
layout: "Layout",
|
||||
eventsPerPage: "Events per page",
|
||||
event: "Event",
|
||||
eventName: "Event name",
|
||||
eventDate: "Event datum",
|
||||
eventInfo: "Event info",
|
||||
eventBadgeWarning: "Event warning badge",
|
||||
eventBadgeInfo: "Event info badge",
|
||||
filterLabel: "Filter label",
|
||||
filterInput: "Filter input",
|
||||
filterButton: "Filter button",
|
||||
pagination: "Pagination",
|
||||
printButton: "Print button",
|
||||
},
|
||||
},
|
||||
},
|
||||
de: {
|
||||
comp: {
|
||||
title: "Widget Konfigurator",
|
||||
successMessage: "Widget erfolgreich gespeichert",
|
||||
preview: "Vorschau",
|
||||
tabSettings: "Einstellungen",
|
||||
tabStyles: "Styles",
|
||||
generic: {
|
||||
fontFamily: "Schriftart",
|
||||
padding: "Abstand",
|
||||
backgroundColor: "Hintergrundfarbe",
|
||||
textColor: "Textfarbe",
|
||||
linkColor: "Link-Farbe",
|
||||
borderColor: "Rahmen-Farbe",
|
||||
color: "Farbe",
|
||||
disabled: "Deaktiviert",
|
||||
active: "Aktiv",
|
||||
},
|
||||
calendar: {
|
||||
iFrameHeight: "Höhe",
|
||||
eventBackgroundColor: "Event Farbe",
|
||||
calendarType: "Kalender Typ",
|
||||
},
|
||||
search: {
|
||||
iFrameMinHeight: "Min. Höhe",
|
||||
iFrameMaxHeight: "Max. Höhe",
|
||||
eventListId: "Veranstaltungsliste",
|
||||
view: "Anzeige",
|
||||
showFilter: "Filter",
|
||||
showOvedaLink: "Oveda-Link",
|
||||
layout: "Layout",
|
||||
eventsPerPage: "Events pro Seite",
|
||||
event: "Event",
|
||||
eventName: "Event-Name",
|
||||
eventDate: "Event-Datum",
|
||||
eventInfo: "Event-Info",
|
||||
eventBadgeWarning: "Event Warnungszeichen",
|
||||
eventBadgeInfo: "Event Infozeichen",
|
||||
filterLabel: "Filter-Label",
|
||||
filterInput: "Filter-Eingabe",
|
||||
filterButton: "Filter-Button",
|
||||
pagination: "Paginierung",
|
||||
printButton: "Drucken-Button",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
iFrameActive: true,
|
||||
iFrameCounter: 0,
|
||||
previewSize: '100%',
|
||||
previewSizes: [
|
||||
{ text: 'Desktop', value: '100%' },
|
||||
{ text: 'Tablet', value: '768px' },
|
||||
{ text: 'Mobile', value: '400px' },
|
||||
],
|
||||
previewBackgroundColor: '#f8f9fa',
|
||||
isLoading: false,
|
||||
isSubmitting: false,
|
||||
customWidget: null,
|
||||
form: {
|
||||
search: {
|
||||
layout: "card",
|
||||
iFrameMinHeight: 400,
|
||||
iFrameMaxHeight: "Infinity",
|
||||
iFrameAutoResize: true,
|
||||
organizationId: null,
|
||||
eventListId: null,
|
||||
eventsPerPage: 10,
|
||||
showFilter: true,
|
||||
showPagination: true,
|
||||
showPrintButton: false,
|
||||
showOvedaLink: true,
|
||||
fontFamily: "",
|
||||
background: "#ffffff",
|
||||
textColor: "#212529",
|
||||
padding: "1rem",
|
||||
linkColor: "#007bff",
|
||||
buttonBackgroundColor: "#007bff",
|
||||
buttonBorderColor: "#007bff",
|
||||
buttonTextColor: "#ffffff",
|
||||
printButtonBackgroundColor: "#6c757d",
|
||||
printButtonBorderColor: "#6c757d",
|
||||
printButtonTextColor: "#ffffff",
|
||||
filterLabelBackgroundColor: "#e9ecef",
|
||||
filterLabelBorderColor: "#ced4da",
|
||||
filterLabelTextColor: "#495057",
|
||||
filterInputBackgroundColor: "#ffffff",
|
||||
filterInputBorderColor: "#ced4da",
|
||||
filterInputTextColor: "#495057",
|
||||
eventBackgroundColor: "#ffffff",
|
||||
eventBorderColor: "#00000020",
|
||||
eventNameTextColor: "#000000",
|
||||
eventDateTextColor: "#212529",
|
||||
eventInfoTextColor: "#6c757d",
|
||||
pagingBorderColor: "#dee2e6",
|
||||
pagingColor: "#007bff",
|
||||
pagingTextColor: "#007bff",
|
||||
pagingActiveTextColor: "#ffffff",
|
||||
pagingDisabledTextColor: "#6c757d",
|
||||
eventBadgeWarningBackgroundColor: "#ffc107",
|
||||
eventBadgeWarningTextColor: "#212529",
|
||||
eventBadgeInfoBackgroundColor: "#17a2b8",
|
||||
eventBadgeInfoTextColor: "#ffffff",
|
||||
},
|
||||
calendar: {
|
||||
iFrameHeight: 600,
|
||||
iFrameAutoResize: false,
|
||||
organizationId: null,
|
||||
eventListId: null,
|
||||
calendarType: "week",
|
||||
eventBackgroundColor: "#007bff",
|
||||
}
|
||||
},
|
||||
calendarTypes: [
|
||||
{ value: "week", text: 'Woche' },
|
||||
{ value: 'month', text: 'Monat' },
|
||||
],
|
||||
searchLayouts: [
|
||||
{ value: "card", text: 'Karten' },
|
||||
{ value: 'text', text: 'Text' },
|
||||
],
|
||||
widgetType: "search",
|
||||
widgetTypes: [],
|
||||
name: "Widget",
|
||||
searchEventList: null,
|
||||
}),
|
||||
computed: {
|
||||
iFrameSource() {
|
||||
return `${window.location.origin}/static/widget/${this.widgetType}.html`;
|
||||
},
|
||||
organizationId() {
|
||||
return this.$route.params.organization_id;
|
||||
},
|
||||
customWidgetId() {
|
||||
return this.$route.params.custom_widget_id;
|
||||
},
|
||||
settings() {
|
||||
return this.form[this.widgetType];
|
||||
},
|
||||
iFrameResizerOptions() {
|
||||
return {
|
||||
autoResize: this.settings.iFrameAutoResize,
|
||||
minHeight: this.settings.iFrameMinHeight != null ? this.settings.iFrameMinHeight : this.settings.iFrameHeight,
|
||||
maxHeight: this.settings.iFrameMaxHeight != null ? this.settings.iFrameMaxHeight : this.settings.iFrameHeight,
|
||||
scrolling: "omit",
|
||||
};
|
||||
},
|
||||
eventListFetchUrl() {
|
||||
return `/api/v1/organizations/${this.organizationId}/event-lists?name={query}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.isLoading = false;
|
||||
this.customWidget = null;
|
||||
this.widgetTypes = [
|
||||
{ value: "search", text: this.$t("shared.models.customWidget.widgetTypeSearch") },
|
||||
{ value: "calendar", text: this.$t("shared.models.customWidget.widgetTypeCalendar") },
|
||||
]
|
||||
this.form.search.organizationId = this.organizationId;
|
||||
this.form.calendar.organizationId = this.organizationId;
|
||||
|
||||
if (this.customWidgetId == null) {
|
||||
this.initResizer();
|
||||
} else {
|
||||
this.loadFormData();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
settings: {
|
||||
handler: function (val, oldVal) {
|
||||
this.updatePreview();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
iFrameResizerOptions: {
|
||||
handler: function (val, oldVal) {
|
||||
this.reloadIframe();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
searchEventList: function(val) {
|
||||
this.form.search.eventListId = val != null ? val.id : null;
|
||||
},
|
||||
previewSize: function(val) {
|
||||
this.resizePreview();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadIframe() {
|
||||
this.iFrameActive = false;
|
||||
this.iFrameCounter++;
|
||||
this.iFrameActive = true;
|
||||
this.initResizer();
|
||||
},
|
||||
initResizer() {
|
||||
const vm = this;
|
||||
Vue.nextTick(function () {
|
||||
iFrameResize({
|
||||
autoResize: vm.iFrameResizerOptions.autoResize,
|
||||
minHeight: vm.iFrameResizerOptions.minHeight,
|
||||
maxHeight: vm.iFrameResizerOptions.maxHeight,
|
||||
scrolling: vm.iFrameResizerOptions.scrolling,
|
||||
onMessage: function(m) {},
|
||||
onInit: function() { vm.updatePreview() },
|
||||
},
|
||||
'.preview_iframe');
|
||||
});
|
||||
},
|
||||
updatePreview() {
|
||||
const resizer = this.$refs.previewIframe.iFrameResizer;
|
||||
|
||||
if (resizer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
resizer.sendMessage({'type': 'OVEDA_WIDGET_SETTINGS_UPDATE_EVENT', 'data': this.settings});
|
||||
},
|
||||
resizePreview() {
|
||||
const resizer = this.$refs.previewIframe.iFrameResizer;
|
||||
|
||||
if (resizer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
resizer.resize();
|
||||
},
|
||||
loadFormData() {
|
||||
axios
|
||||
.get(`/api/v1/custom-widgets/${this.customWidgetId}`, {
|
||||
withCredentials: true,
|
||||
handleLoading: this.handleLoading,
|
||||
})
|
||||
.then((response) => {
|
||||
this.customWidget = response.data;
|
||||
this.widgetType = this.customWidget.widget_type;
|
||||
this.name = this.customWidget.name;
|
||||
|
||||
for (var key in this.customWidget.settings) {
|
||||
this.settings[key] = this.customWidget.settings[key];
|
||||
}
|
||||
});
|
||||
},
|
||||
handleLoading(isLoading) {
|
||||
this.isLoading = isLoading;
|
||||
},
|
||||
submitForm() {
|
||||
let data = {
|
||||
'widget_type': this.widgetType,
|
||||
'name': this.name,
|
||||
'settings': this.settings,
|
||||
};
|
||||
|
||||
if (this.customWidgetId == null) {
|
||||
axios
|
||||
.post(`/api/v1/organizations/${this.organizationId}/custom-widgets`,
|
||||
data,
|
||||
{
|
||||
withCredentials: true,
|
||||
handleLoading: this.handleSubmitting,
|
||||
})
|
||||
.then(() => {
|
||||
this.$root.makeSuccessToast(this.$t("comp.successMessage"))
|
||||
this.goBack()
|
||||
})
|
||||
} else {
|
||||
axios
|
||||
.put(`/api/v1/custom-widgets/${this.customWidgetId}`,
|
||||
data,
|
||||
{
|
||||
withCredentials: true,
|
||||
handleLoading: this.handleSubmitting,
|
||||
})
|
||||
.then(() => {
|
||||
this.$root.makeSuccessToast(this.$t("comp.successMessage"))
|
||||
this.goBack()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSubmitting(isLoading) {
|
||||
this.isSubmitting = isLoading;
|
||||
},
|
||||
goBack() {
|
||||
window.location.href = `/manage/admin_unit/${this.organizationId}/custom-widgets`;
|
||||
},
|
||||
},
|
||||
};
|
||||
170
project/static/vue/widget-configurator/list.vue.js
Normal file
170
project/static/vue/widget-configurator/list.vue.js
Normal file
@ -0,0 +1,170 @@
|
||||
const WidgetConfiguratorList = {
|
||||
template: `
|
||||
<div>
|
||||
<h1>{{ $t("shared.models.customWidget.listName") }}</h1>
|
||||
|
||||
<div class="my-4">
|
||||
<b-button variant="outline-secondary" @click.prevent="createItem()"><i class="fa fa-plus"></i> {{ $t("comp.addTitle") }}</b-button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" role="alert" v-if="errorMessage">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<b-modal id="custom-widget-installation-modal" title="Installation" size="lg" ok-only>
|
||||
<p>Kopiere den unten stehenden Code und füge ihn auf deiner Website ein.</p>
|
||||
<p>Füge den folgenden Code im <code><head></code> der Seite ein.</p>
|
||||
|
||||
<b-form-textarea rows="3" max-rows="8" size="sm" disabled class="text-monospace" style="font-size: 0.7rem;" v-model="installationHeader"></b-form-textarea>
|
||||
<p class="mt-3">Füge den folgenden Code an der Stelle im <code><body></code> der Seite ein, wo das Widget dargestellt werden soll.</p>
|
||||
<b-form-textarea rows="1" max-rows="8" size="sm" disabled class="text-monospace" style="font-size: 0.7rem;" v-model="installationBody"></b-form-textarea>
|
||||
</b-modal>
|
||||
|
||||
<b-table
|
||||
ref="table"
|
||||
id="main-table"
|
||||
:fields="fields"
|
||||
:items="loadTableData"
|
||||
:current-page="currentPage"
|
||||
:per-page="perPage"
|
||||
primary-key="id"
|
||||
thead-class="d-none"
|
||||
outlined
|
||||
hover
|
||||
responsive
|
||||
show-empty
|
||||
:empty-text="$t('shared.emptyData')"
|
||||
style="min-height:120px"
|
||||
>
|
||||
<template #cell(name)="data">
|
||||
<b-dropdown :id="'item-dropdown-' + data.item.id" :text="data.value" variant="link" toggle-class="m-0 p-0">
|
||||
<b-dropdown-item @click.prevent="installItem(data.item.id)">{{ $t("comp.installation") }}…</b-dropdown-item>
|
||||
<b-dropdown-item @click.prevent="editItem(data.item.id)">{{ $t("shared.edit") }}…</b-dropdown-item>
|
||||
<b-dropdown-item @click.prevent="deleteItem(data.item.id)">{{ $t("shared.delete") }}…</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
<template #cell(widget_type)="data">
|
||||
{{ widgetTypes[data.value] }}
|
||||
</template>
|
||||
</b-table>
|
||||
<b-pagination v-if="totalRows > 0"
|
||||
v-model="currentPage"
|
||||
:total-rows="totalRows"
|
||||
:per-page="perPage"
|
||||
aria-controls="main-table"
|
||||
></b-pagination>
|
||||
</div>
|
||||
`,
|
||||
i18n: {
|
||||
messages: {
|
||||
en: {
|
||||
comp: {
|
||||
addTitle: "Add widget",
|
||||
installation: "Installation",
|
||||
deletedMessage: "Widget successfully deleted",
|
||||
deleteConfirmation: "Do you really want to delete the widget?",
|
||||
},
|
||||
},
|
||||
de: {
|
||||
comp: {
|
||||
addTitle: "Widget hinzufügen",
|
||||
installation: "Installation",
|
||||
deletedMessage: "Widget erfolgreich gelöscht",
|
||||
deleteConfirmation: "Möchtest du das Widget wirklich löschen?",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
errorMessage: null,
|
||||
fields: [
|
||||
{
|
||||
key: "name",
|
||||
label: i18n.t("shared.models.customWidget.name"),
|
||||
},
|
||||
{
|
||||
key: "widget_type",
|
||||
label: i18n.t("shared.models.customWidget.widgetType"),
|
||||
},
|
||||
],
|
||||
totalRows: 0,
|
||||
currentPage: 1,
|
||||
perPage: 10,
|
||||
searchResult: {
|
||||
items: [],
|
||||
},
|
||||
widgetTypes: {
|
||||
"search": "Search",
|
||||
"calendar": "Calendar",
|
||||
},
|
||||
installationWidgetId: 0,
|
||||
}),
|
||||
computed: {
|
||||
organizationId() {
|
||||
return this.$route.params.organization_id;
|
||||
},
|
||||
installationHeader() {
|
||||
return "<!-- Oveda Widget -->\n<script>(function(w,d,s,o,f,js,fjs){w['OvedaWidget']=o;w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};js=d.createElement(s),fjs=d.getElementsByTagName(s)[0];js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);}(window,document,'script','oveda','" + window.location.origin + "/static/widget-loader.js'));</script>\n<!-- End Oveda Widget -->";
|
||||
},
|
||||
installationBody() {
|
||||
return '<div class="oveda-widget" data-widget-id="' + this.installationWidgetId + '"></div>';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.widgetTypes["search"] = this.$t("shared.models.customWidget.widgetTypeSearch");
|
||||
this.widgetTypes["calendar"] = this.$t("shared.models.customWidget.widgetTypeCalendar");
|
||||
},
|
||||
methods: {
|
||||
loadTableData(ctx, callback) {
|
||||
const vm = this;
|
||||
axios
|
||||
.get(`/api/v1/organizations/${this.organizationId}/custom-widgets`, {
|
||||
params: {
|
||||
page: ctx.currentPage,
|
||||
per_page: ctx.perPage,
|
||||
},
|
||||
withCredentials: true,
|
||||
handler: this,
|
||||
})
|
||||
.then((response) => {
|
||||
vm.totalRows = response.data.total;
|
||||
callback(response.data.items);
|
||||
})
|
||||
.catch(() => {
|
||||
callback([]);
|
||||
});
|
||||
return null;
|
||||
},
|
||||
refreshTableData() {
|
||||
this.$refs.table.refresh();
|
||||
},
|
||||
handleRequestStart() {
|
||||
this.errorMessage = null;
|
||||
},
|
||||
handleRequestError(error, message) {
|
||||
this.errorMessage = message;
|
||||
},
|
||||
createItem() {
|
||||
window.location.href = `/manage/admin_unit/${this.organizationId}/custom-widgets/create`;
|
||||
},
|
||||
editItem(id) {
|
||||
window.location.href = `/manage/admin_unit/${this.organizationId}/custom-widgets/${id}/update`;
|
||||
},
|
||||
deleteItem(id) {
|
||||
if (confirm(this.$t("comp.deleteConfirmation"))) {
|
||||
axios
|
||||
.delete(`/api/v1/custom-widgets/${id}`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(() => {
|
||||
this.$root.makeSuccessToast(this.$t("comp.deletedMessage"));
|
||||
this.refreshTableData();
|
||||
});
|
||||
}
|
||||
},
|
||||
installItem(id) {
|
||||
this.installationWidgetId = id;
|
||||
this.$bvModal.show("custom-widget-installation-modal");
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -3,7 +3,7 @@
|
||||
var script = document.currentScript || document.querySelector('script[src*="widget-loader.js"]')
|
||||
var baseUrl = script.src.replace("/static/widget-loader.js", "");
|
||||
|
||||
var iFrameElements = [];
|
||||
var containers = [];
|
||||
var needsResizer = false;
|
||||
|
||||
w.onload = function() {
|
||||
@ -17,30 +17,69 @@
|
||||
function initWidgets() {
|
||||
var elements = d.getElementsByClassName("oveda-widget");
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
initIframeWidget(elements.item(i));
|
||||
initIframeWidget(elements.item(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
function initIframeWidget(element) {
|
||||
var shortName = getWidgetData(element, 'short-name');
|
||||
var width = getWidgetData(element, 'width', '100%');
|
||||
var height = getWidgetData(element, 'height', '400px');
|
||||
var resize = getWidgetBoolData(element, 'resize', false);
|
||||
var googleTagManager = getWidgetBoolData(element, 'google-tag-manager', false);
|
||||
function initIframeWidget(element, index) {
|
||||
var customId = getWidgetData(element, 'id');
|
||||
var customWidgetData = null;
|
||||
var src = null;
|
||||
var resize = true;
|
||||
var minHeight = "400";
|
||||
var maxHeight = "Infinity";
|
||||
var width = "100%";
|
||||
var googleTagManager = false;
|
||||
|
||||
var style = "border: none; width:" + width + ";height:" + height + ";";
|
||||
if (resize) {
|
||||
if (customId != null) {
|
||||
var url = baseUrl + "/api/v1/custom-widgets/" + customId;
|
||||
|
||||
customWidgetData = loadJSON(url);
|
||||
var settings = customWidgetData.settings;
|
||||
src = baseUrl + "/static/widget/" + customWidgetData.widget_type + ".html";
|
||||
|
||||
if (settings.hasOwnProperty('iFrameAutoResize') && settings.iFrameAutoResize != null) {
|
||||
resize = settings.iFrameAutoResize;
|
||||
}
|
||||
|
||||
if (settings.hasOwnProperty('iFrameHeight') && settings.iFrameHeight != null) {
|
||||
minHeight = settings.iFrameHeight;
|
||||
maxHeight = settings.iFrameHeight;
|
||||
}
|
||||
|
||||
if (settings.hasOwnProperty('iFrameMinHeight') && settings.iFrameMinHeight != null) {
|
||||
minHeight = settings.iFrameMinHeight;
|
||||
}
|
||||
|
||||
if (settings.hasOwnProperty('iFrameMaxHeight') && settings.iFrameMaxHeight != null) {
|
||||
maxHeight = settings.iFrameMaxHeight;
|
||||
}
|
||||
|
||||
if (settings.hasOwnProperty('googleTagManager') && settings.googleTagManager != null) {
|
||||
googleTagManager = settings.googleTagManager;
|
||||
}
|
||||
} else {
|
||||
width = getWidgetData(element, 'width', '100%');
|
||||
minHeight = getWidgetData(element, 'height', '400px');
|
||||
resize = getWidgetBoolData(element, 'resize', false);
|
||||
googleTagManager = getWidgetBoolData(element, 'google-tag-manager', false);
|
||||
|
||||
var shortName = getWidgetData(element, 'short-name');
|
||||
var src = baseUrl + "/" + shortName + "/widget/eventdates?";
|
||||
src = addParamToQuery(element, src, 'event-list', 'event_list_id');
|
||||
src = addParamToQuery(element, src, 'font', 's_ft');
|
||||
src = addParamToQuery(element, src, 'background', 's_bg');
|
||||
src = addParamToQuery(element, src, 'primary', 's_pr');
|
||||
src = addParamToQuery(element, src, 'link', 's_li');
|
||||
}
|
||||
|
||||
var style = "border: none; width:" + width + ";height:" + minHeight + ";";
|
||||
if (resize || customWidgetData != null) {
|
||||
style += "min-width:100%;max-width:100%;";
|
||||
}
|
||||
|
||||
var src = baseUrl + "/" + shortName + "/widget/eventdates?";
|
||||
src = addParamToQuery(element, src, 'event-list', 'event_list_id');
|
||||
src = addParamToQuery(element, src, 'font', 's_ft');
|
||||
src = addParamToQuery(element, src, 'background', 's_bg');
|
||||
src = addParamToQuery(element, src, 'primary', 's_pr');
|
||||
src = addParamToQuery(element, src, 'link', 's_li');
|
||||
|
||||
var iFrame = d.createElement("iframe");
|
||||
iFrame.id = "oveda-widget-iframe-" + index;
|
||||
iFrame.class = "oveda-widget-iframe";
|
||||
iFrame.src = src;
|
||||
iFrame.style = style;
|
||||
@ -48,11 +87,20 @@
|
||||
iFrame.allowtransparency = "true";
|
||||
element.appendChild(iFrame);
|
||||
|
||||
if (resize || googleTagManager) {
|
||||
if (resize || googleTagManager || customWidgetData != null) {
|
||||
needsResizer = true;
|
||||
}
|
||||
|
||||
iFrameElements.push(element);
|
||||
var container = {
|
||||
element: element,
|
||||
iFrame: iFrame,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight,
|
||||
resize: resize,
|
||||
googleTagManager: googleTagManager,
|
||||
customWidgetData: customWidgetData
|
||||
};
|
||||
containers.push(container);
|
||||
}
|
||||
|
||||
function addParamToQuery(element, url, attr, param)
|
||||
@ -85,29 +133,40 @@
|
||||
}
|
||||
|
||||
function startIframeResizer() {
|
||||
for (var i = 0; i < iFrameElements.length; i++) {
|
||||
var element = iFrameElements[i];
|
||||
var iFrame = element.getElementsByTagName("iframe")[0];
|
||||
var height = getWidgetData(element, 'height', '400px');
|
||||
var resize = getWidgetBoolData(element, 'resize', false);
|
||||
var googleTagManager = getWidgetBoolData(element, 'google-tag-manager', false);
|
||||
for (var i = 0; i < containers.length; i++) {
|
||||
var container = containers[i];
|
||||
|
||||
if (resize || googleTagManager) {
|
||||
var config = { autoResize: resize };
|
||||
|
||||
if (resize) {
|
||||
config.minHeight = height;
|
||||
} else {
|
||||
config.scrolling = "omit";
|
||||
config.sizeHeight = false;
|
||||
config.sizeWidth = false;
|
||||
}
|
||||
if (container.resize || container.googleTagManager || container.customWidgetData != null) {
|
||||
var config = { };
|
||||
config.autoResize = container.resize;
|
||||
config.scrolling = "omit";
|
||||
config.id = "iFrameResizer" + i;
|
||||
|
||||
config.onMessage = function(messageData) {
|
||||
onIframeMessage(messageData, googleTagManager);
|
||||
onIframeMessage(messageData, container.googleTagManager);
|
||||
}
|
||||
|
||||
iFrameResize(config, iFrame);
|
||||
if (container.customWidgetData == null) {
|
||||
if (container.resize) {
|
||||
config.minHeight = container.minHeight;
|
||||
config.maxHeight = container.maxHeight;
|
||||
} else {
|
||||
config.sizeHeight = false;
|
||||
config.sizeWidth = false;
|
||||
}
|
||||
} else {
|
||||
config.minHeight = container.minHeight;
|
||||
config.maxHeight = container.maxHeight;
|
||||
|
||||
(function(data) {
|
||||
config.onInit = function (iFrame) {
|
||||
iFrame.iFrameResizer.sendMessage({'type': 'OVEDA_WIDGET_SETTINGS_UPDATE_EVENT', 'data': data});
|
||||
}
|
||||
})(container.customWidgetData.settings);
|
||||
}
|
||||
|
||||
var resizers = iFrameResize(config, container.iFrame);
|
||||
container.resizer = resizers[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -144,4 +203,21 @@
|
||||
p.parentNode.insertBefore(element, p);
|
||||
}
|
||||
|
||||
function loadJSON(url) {
|
||||
var json = loadUrlSync(url, "application/json");
|
||||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
function loadUrlSync(url, mimeType)
|
||||
{
|
||||
var xmlhttp=new XMLHttpRequest();
|
||||
xmlhttp.open("GET", url, false);
|
||||
if (mimeType != null && xmlhttp.overrideMimeType) {
|
||||
xmlhttp.overrideMimeType(mimeType);
|
||||
}
|
||||
|
||||
xmlhttp.send();
|
||||
return xmlhttp.responseText;
|
||||
}
|
||||
|
||||
})(window, document);
|
||||
273
project/static/widget/calendar.html
Normal file
273
project/static/widget/calendar.html
Normal file
@ -0,0 +1,273 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.6.2/dist/vuetify.min.css" rel="stylesheet">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<template v-if="widget">
|
||||
<v-app :styles="{'max-height': widget.settings.iFrameHeight}">
|
||||
<v-row class="fill-height" style="margin:0;">
|
||||
<v-col>
|
||||
<v-sheet height="64">
|
||||
<v-toolbar flat>
|
||||
<v-btn fab text small color="grey darken-2" @click="$refs.calendar.prev()">
|
||||
<v-icon small>
|
||||
mdi-chevron-left
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn fab text small color="grey darken-2" @click="$refs.calendar.next()">
|
||||
<v-icon small>
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>
|
||||
{{ title }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
</v-toolbar>
|
||||
</v-sheet>
|
||||
<v-sheet :height="calendarHeight">
|
||||
<v-calendar
|
||||
ref="calendar"
|
||||
v-model="focus"
|
||||
locale="de"
|
||||
:weekdays="[1, 2, 3, 4, 5, 6, 0]"
|
||||
:type="widget.settings.calendarType"
|
||||
:events="events"
|
||||
:event-color="widget.settings.eventBackgroundColor"
|
||||
@click:event="showEvent"
|
||||
@change="calendarChanged"></v-calendar>
|
||||
<v-menu
|
||||
v-model="selectedOpen"
|
||||
:close-on-content-click="false"
|
||||
:activator="selectedElement"
|
||||
offset-x
|
||||
>
|
||||
<v-card flat v-if="selectedEvent">
|
||||
<v-card-title>{{ selectedEvent.name }}</v-card-title>
|
||||
<v-card-subtitle>
|
||||
<v-icon small>mdi-calendar</v-icon> {{ render_event_date_instance(selectedEvent.date.start, selectedEvent.date.allday) }}
|
||||
<event-warning-pills :event="selectedEvent.date.event"></event-warning-pills>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<div><v-icon small>mdi-database</v-icon> {{ selectedEvent.date.event.organization.name }}</div>
|
||||
<div v-if="selectedEvent.date.event.organizer.name != selectedEvent.date.event.organization.name"><v-icon small>mdi-server</v-icon> {{ selectedEvent.date.event.organizer.name }}</div>
|
||||
<div><v-icon small>mdi-map-marker</v-icon> {{ selectedEvent.date.event.place.name }}</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
text
|
||||
color="secondary"
|
||||
@click="selectedOpen = false"
|
||||
>
|
||||
Schließen
|
||||
</v-btn>
|
||||
<v-btn
|
||||
text
|
||||
color="primary"
|
||||
@click="openEventDate(selectedEvent.date)"
|
||||
>
|
||||
Details
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-app>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.6.2/dist/vuetify.js"></script>
|
||||
<script src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js" integrity="sha256-AdQN98MVZs44Eq2yTwtoKufhnU+uZ7v2kXnD5vqzZVo=" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
axios.defaults.baseURL = window.location.origin;
|
||||
moment.locale("de");
|
||||
|
||||
Vue.component('event-warning-pills', {
|
||||
props: ['event'],
|
||||
template: `
|
||||
<span>
|
||||
<v-chip v-if="event.status && event.status != 'scheduled'" small color="yellow">
|
||||
<template v-if="event.status == 'cancelled'">Abgesagt</template>
|
||||
<template v-else-if="event.status == 'movedOnline'">Online verschoben</template>
|
||||
<template v-else-if="event.status == 'postponed'">Verschoben</template>
|
||||
<template v-else-if="event.status == 'rescheduled'">Neu angesetzt</template>
|
||||
</v-chip>
|
||||
<v-chip v-if="event.booked_up" small color="yellow">Ausgebucht</v-chip>
|
||||
<v-chip v-if="event.attendance_mode && event.attendance_mode != 'offline'" small color="blue">
|
||||
<template v-if="event.attendance_mode == 'online'">Online</template>
|
||||
<template v-else-if="event.attendance_mode == 'mixed'">Präsenzveranstaltung und online</template>
|
||||
</v-chip>
|
||||
</span>
|
||||
`
|
||||
});
|
||||
|
||||
var vue_app_data = {
|
||||
widget: null,
|
||||
title: "",
|
||||
focus: '',
|
||||
events: [],
|
||||
selectedEvent: null,
|
||||
selectedElement: null,
|
||||
selectedOpen: false,
|
||||
};
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
vuetify: new Vuetify(),
|
||||
data: vue_app_data,
|
||||
computed: {
|
||||
calendarHeight() {
|
||||
if (!this.widget) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseInt(this.widget.settings.iFrameHeight) - 88;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'widget.settings.organizationId': function(val) {
|
||||
this.parameterChanged();
|
||||
},
|
||||
'widget.settings.eventListId': function(val) {
|
||||
this.parameterChanged();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parameterChanged() {
|
||||
if (this.$refs.calendar) {
|
||||
this.loadEvents(this.$refs.calendar.start, this.$refs.calendar.end);
|
||||
}
|
||||
},
|
||||
calendarChanged({ start, end }) {
|
||||
this.loadEvents(start.date, end.date);
|
||||
},
|
||||
loadEvents(start, end) {
|
||||
if (this.widget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.title = this.$refs.calendar.title;
|
||||
|
||||
let params = {
|
||||
date_from: start,
|
||||
date_to: end,
|
||||
per_page: 50,
|
||||
};
|
||||
|
||||
if (this.widget.settings.eventListId) {
|
||||
params.event_list_id = this.widget.settings.eventListId;
|
||||
}
|
||||
else if (this.widget.settings.organizationId) {
|
||||
params.organization_id = this.widget.settings.organizationId;
|
||||
}
|
||||
|
||||
axios
|
||||
.get(`/api/v1/event-dates/search`, { params: params })
|
||||
.then((response) => {
|
||||
this.events = response.data.items.map(function(date) {
|
||||
return {
|
||||
name: date.event.name,
|
||||
start: moment(date.start).toDate(),
|
||||
end: date.end != null ? moment(date.end).toDate() : null,
|
||||
timed: !date.allday,
|
||||
date: date,
|
||||
};
|
||||
});
|
||||
this.scrollToMinTime();
|
||||
});
|
||||
},
|
||||
scrollToMinTime() {
|
||||
if (!this.$refs.calendar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$refs.calendar.type != 'week') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.events.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const min_event = this.events.reduce(function(prev, curr) {
|
||||
return prev.start.getHours() < curr.start.getHours() ? prev : curr;
|
||||
});
|
||||
this.$refs.calendar.scrollToTime({ hour: min_event.start.getHours(), minute: 0 });
|
||||
},
|
||||
showEvent ({ nativeEvent, event }) {
|
||||
const open = () => {
|
||||
this.selectedEvent = event
|
||||
this.selectedElement = nativeEvent.target
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => this.selectedOpen = true))
|
||||
}
|
||||
|
||||
if (this.selectedOpen) {
|
||||
this.selectedOpen = false
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => open()))
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
|
||||
nativeEvent.stopPropagation()
|
||||
},
|
||||
openEventDate(date) {
|
||||
const url = `${axios.defaults.baseURL}/eventdate/${date.id}`;
|
||||
this.trackAnalyticsEvent({'event':'linkClick', 'url':url});
|
||||
window.open(url);
|
||||
},
|
||||
render_event_date_instance(value, allday, format = "dd. DD.MM.YYYY LT", alldayFormat = "dd. DD.MM.YYYY") {
|
||||
const instance = moment(value);
|
||||
|
||||
if (allday) {
|
||||
return instance.format(alldayFormat);
|
||||
}
|
||||
|
||||
return instance.format(format);
|
||||
},
|
||||
trackAnalyticsEvent(data) {
|
||||
if ('parentIFrame' in window) {
|
||||
parentIFrame.sendMessage({'type': 'OVEDA_ANALYTICS_EVENT', 'data': data});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.iFrameResizer = {
|
||||
onReady: function() {
|
||||
app.trackAnalyticsEvent({'event':'pageView', 'url':document.location.href});
|
||||
},
|
||||
onMessage: function(message) {
|
||||
if (message.type != 'OVEDA_WIDGET_SETTINGS_UPDATE_EVENT') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vue_app_data.widget == null) {
|
||||
vue_app_data.widget = { settings: message.data };
|
||||
return;
|
||||
}
|
||||
|
||||
for (var key in message.data) {
|
||||
vue_app_data.widget.settings[key] = message.data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.contentWindow.min.js" integrity="sha512-14SY6teTzhrLWeL55Q4uCyxr6GQOxF3pEoMxo2mBxXwPRikdMtzKMYWy2B5Lqjr6PHHoGOxZgPaxUYKQrSmu0A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</body>
|
||||
</html>
|
||||
525
project/static/widget/search.html
Normal file
525
project/static/widget/search.html
Normal file
@ -0,0 +1,525 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Oveda Widget</title>
|
||||
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@2.21.2/dist/bootstrap-vue.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
|
||||
<script src="https://unpkg.com/vue-i18n@8.25.0/dist/vue-i18n.min.js"></script>
|
||||
<script src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js" integrity="sha256-AdQN98MVZs44Eq2yTwtoKufhnU+uZ7v2kXnD5vqzZVo=" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap-vue@2.21.2/dist/bootstrap-vue.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<template v-if="widget">
|
||||
<component :is="`style`">
|
||||
body {
|
||||
background-color: {{ widget.settings.background }};
|
||||
color: {{ widget.settings.textColor }};
|
||||
font-family: {{ widget.settings.fontFamily }};
|
||||
padding: {{ widget.settings.padding }};
|
||||
}
|
||||
|
||||
.page-link {
|
||||
background-color: {{ widget.settings.background }};
|
||||
border-color: {{ widget.settings.pagingBorderColor }};
|
||||
color: {{ widget.settings.pagingColor }};
|
||||
}
|
||||
|
||||
.page-link:hover {
|
||||
background-color: {{ widget.settings.pagingColor }};
|
||||
border-color: {{ widget.settings.pagingColor }};
|
||||
color: {{ widget.settings.pagingActiveTextColor }};
|
||||
}
|
||||
|
||||
.page-item.active .page-link {
|
||||
background-color: {{ widget.settings.pagingColor }};
|
||||
border-color: {{ widget.settings.pagingColor }};
|
||||
color: {{ widget.settings.pagingActiveTextColor }};
|
||||
}
|
||||
|
||||
.page-item.disabled .page-link {
|
||||
background-color: {{ widget.settings.background }};
|
||||
border-color: {{ widget.settings.pagingBorderColor }};
|
||||
color: {{ widget.settings.pagingDisabledTextColor }};
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-primary:disabled,
|
||||
.btn-primary:active,
|
||||
.btn-primary:not(:disabled):not(.disabled):active,
|
||||
.btn-primary:focus {
|
||||
background-color: {{ widget.settings.buttonBackgroundColor }};
|
||||
border-color: {{ widget.settings.buttonBorderColor }};
|
||||
color: {{ widget.settings.buttonTextColor }};
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-secondary:disabled,
|
||||
.btn-secondary:hover,
|
||||
.btn-secondary:active,
|
||||
.btn-secondary:not(:disabled):not(.disabled):active,
|
||||
.btn-secondary:focus {
|
||||
background-color: {{ widget.settings.printButtonBackgroundColor }};
|
||||
border-color: {{ widget.settings.printButtonBorderColor }};
|
||||
color: {{ widget.settings.printButtonTextColor }};
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background-color: {{ widget.settings.filterLabelBackgroundColor }};
|
||||
border-color: {{ widget.settings.filterLabelBorderColor }};
|
||||
color: {{ widget.settings.filterLabelTextColor }};
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control.focus,
|
||||
.form-control:focus,
|
||||
.custom-select,
|
||||
.custom-select:focus {
|
||||
background-color: {{ widget.settings.filterInputBackgroundColor }};
|
||||
border-color: {{ widget.settings.filterInputBorderColor }};
|
||||
color: {{ widget.settings.filterInputTextColor }};
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: {{ widget.settings.eventBackgroundColor }};
|
||||
border-color: {{ widget.settings.eventBorderColor }};
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: {{ widget.settings.eventNameTextColor }};
|
||||
}
|
||||
|
||||
.card-subtitle.text-body {
|
||||
color: {{ widget.settings.eventDateTextColor }}!important;
|
||||
}
|
||||
|
||||
.card .text-muted {
|
||||
color: {{ widget.settings.eventInfoTextColor }}!important;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: {{ widget.settings.eventBadgeWarningBackgroundColor }};
|
||||
color: {{ widget.settings.eventBadgeWarningTextColor }};
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background-color: {{ widget.settings.eventBadgeInfoBackgroundColor }};
|
||||
color: {{ widget.settings.eventBadgeInfoTextColor }};
|
||||
}
|
||||
|
||||
a, a:hover {
|
||||
color: {{ widget.settings.linkColor }};
|
||||
}
|
||||
|
||||
a.underlined {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</component>
|
||||
<div v-if="widget.settings.showFilter">
|
||||
<b-form @submit.stop.prevent="loadData" inline class="mb-4" autocomplete="off">
|
||||
<b-input-group prepend="Von" class="mb-2 mr-sm-2">
|
||||
<b-form-datepicker v-model="form.dateFrom" locale="de" start-weekday="1" hide-header :date-format-options="{ year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short' }"></b-form-datepicker>
|
||||
</b-input-group>
|
||||
<b-input-group prepend="bis" class="mb-2 mr-sm-2">
|
||||
<b-form-datepicker v-model="form.dateTo" locale="de" placeholder="Kein Datum gewählt" :min="form.dateFrom" start-weekday="1" hide-header :date-format-options="{ year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short' }"></b-form-datepicker>
|
||||
</b-input-group>
|
||||
<b-input-group prepend="Kategorie" class="mb-2 mr-sm-2">
|
||||
<b-form-select v-model="form.category_id" :options="categories"></b-form-select>
|
||||
</b-input-group>
|
||||
<b-input-group prepend="Stichwort" class="mb-2 mr-sm-2">
|
||||
<b-form-input v-model="form.keyword"></b-form-input>
|
||||
</b-input-group>
|
||||
<b-button variant="primary" class="mb-2" type="submit" :disabled="isLoading">
|
||||
<b-spinner small v-if="isLoading"></b-spinner>
|
||||
Finden
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</b-form>
|
||||
</div>
|
||||
<b-overlay :show="isLoading" variant="transparent">
|
||||
<div v-if="error" class="mb-4">
|
||||
<b-alert show variant="danger">{{ error }}</b-alert>
|
||||
<button type="button" class="btn btn-outline-secondary" @click="loadData()"><i class="fa fa-sync-alt"></i></button>
|
||||
</div>
|
||||
<template v-if="widget.settings.layout == 'card'">
|
||||
<div v-for="date in dates">
|
||||
<!-- Desktop -->
|
||||
<div class="row mb-3 d-none d-sm-block">
|
||||
<div class="col-sm">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h5 class="card-title">{{ date.event.name }} <event-warning-pills :event="date.event"></event-warning-pills></h5>
|
||||
<h6 class="card-subtitle mb-2 text-body"><i class="fa fa-calendar"></i> {{ render_event_date_instance(date.start, date.allday) }}</h6>
|
||||
<p class="card-text" v-if="date.event.description" v-html="date.event.description.truncate(200, true)"></p>
|
||||
<small class="text-muted mr-2"><i class="fa fa-database"></i> {{ date.event.organization.name }}</small>
|
||||
<small v-if="date.event.organizer.name != date.event.organization.name" class="text-muted mr-2"><i class="fa fa-server"></i> {{ date.event.organizer.name }}</small>
|
||||
<small class="text-muted"><i class="fa fa-map-marker"></i> {{ date.event.place.name }}</small>
|
||||
<a href="#" @click.stop.prevent="openEventDate(date)" class="stretched-link"></a>
|
||||
</div>
|
||||
<div class="col-sm-4 text-right">
|
||||
<img v-if="date.event.photo" :src="url_for_image(date.event.photo, 200)" style="object-fit: cover; width: 200px;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile -->
|
||||
<div class="row mb-3 d-sm-none">
|
||||
<div class="col-sm">
|
||||
<div class="card">
|
||||
<div>
|
||||
<img v-if="date.event.photo" :src="url_for_image(date.event.photo, 500)" class="card-img-top" style="object-fit: cover; height: 40vw;" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h5 class="card-title">{{ date.event.name }} <event-warning-pills :event="date.event"></event-warning-pills></h5>
|
||||
<h6 class="card-subtitle mb-2 text-body"><i class="fa fa-calendar"></i> {{ render_event_date_instance(date.start, date.allday) }}</h6>
|
||||
<p class="card-text" v-if="date.event.description" v-html="date.event.description.truncate(100, true)"></p>
|
||||
<small class="text-muted mr-2"><i class="fa fa-database"></i> {{ date.event.organization.name }}</small>
|
||||
<small v-if="date.event.organizer.name != date.event.organization.name" class="text-muted mr-2"><i class="fa fa-server"></i> {{ date.event.organizer.name }}</small>
|
||||
<small class="text-muted"><i class="fa fa-map-marker"></i> {{ date.event.place.name }}</small>
|
||||
<a href="#" @click.stop.prevent="openEventDate(date)" class="stretched-link"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="widget.settings.layout == 'text'">
|
||||
<div class="mb-3">
|
||||
<div v-for="date in dates">
|
||||
{{ render_event_date_instance(date.start, date.allday, 'L', 'L') }} <a href="#" @click.stop.prevent="openEventDate(date)">{{ date.event.name }}</a> <event-warning-pills :event="date.event"></event-warning-pills>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<b-pagination v-if="widget.settings.showPagination && totalRows > perPage"
|
||||
v-model="currentPage"
|
||||
:total-rows="totalRows"
|
||||
:per-page="perPage"
|
||||
></b-pagination>
|
||||
|
||||
<div class="d-print-none">
|
||||
<div v-if="widget.settings.showOvedaLink" class="mb-2">
|
||||
<a href="#" @click.stop.prevent="openSearch()" class="underlined">Veranstaltungen auf oveda.de anzeigen</a>
|
||||
</div>
|
||||
|
||||
<div v-if="widget.settings.showPrintButton">
|
||||
<button @click="printPage()" v-if="totalRows > 0" type="button" class="btn btn-secondary btn-print"><i class="fa fa-print"></i> Drucken</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</b-overlay>
|
||||
</template>
|
||||
</div>
|
||||
<script>
|
||||
axios.defaults.baseURL = window.location.origin;
|
||||
moment.locale("de");
|
||||
|
||||
const localizedMessages = {
|
||||
de: {
|
||||
categories: {
|
||||
Art: "Kunst",
|
||||
Book: "Literatur",
|
||||
Movie: "Film",
|
||||
Family: "Familie",
|
||||
Festival: "Festival",
|
||||
Religious: "Religion",
|
||||
Shopping: "Shopping",
|
||||
Comedy: "Comedy",
|
||||
Music: "Musik",
|
||||
Dance: "Tanz",
|
||||
Nightlife: "Party",
|
||||
Theater: "Theater",
|
||||
Dining: "Dining",
|
||||
Conference: "Konferenz",
|
||||
Meetup: "Networking",
|
||||
Fitness: "Fitness",
|
||||
Sports: "Sport",
|
||||
Other: "Sonstiges",
|
||||
Exhibition: "Ausstellung",
|
||||
Culture: "Kultur",
|
||||
Tour: "Führung",
|
||||
OpenAir: "Open Air",
|
||||
Stage: "Bühne",
|
||||
Lecture: "Vortrag",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: "de",
|
||||
messages: localizedMessages,
|
||||
silentFallbackWarn: true,
|
||||
});
|
||||
|
||||
String.prototype.truncate =
|
||||
String.prototype.truncate ||
|
||||
function (n, useWordBoundary) {
|
||||
if (this.length <= n) {
|
||||
return this;
|
||||
}
|
||||
const subString = this.substr(0, n - 1); // the original check
|
||||
return (
|
||||
(useWordBoundary
|
||||
? subString.substr(0, subString.lastIndexOf(" "))
|
||||
: subString) + "…"
|
||||
);
|
||||
};
|
||||
|
||||
Vue.component('event-warning-pills', {
|
||||
props: ['event'],
|
||||
template: `
|
||||
<span>
|
||||
<span v-if="event.status" class="badge badge-pill badge-warning">
|
||||
<template v-if="event.status == 'cancelled'">Abgesagt</template>
|
||||
<template v-else-if="event.status == 'movedOnline'">Online verschoben</template>
|
||||
<template v-else-if="event.status == 'postponed'">Verschoben</template>
|
||||
<template v-else-if="event.status == 'rescheduled'">Neu angesetzt</template>
|
||||
</span>
|
||||
<span v-if="event.booked_up" class="badge badge-pill badge-warning">Ausgebucht</span>
|
||||
<span v-if="event.attendance_mode" class="badge badge-pill badge-info">
|
||||
<template v-if="event.attendance_mode == 'online'">Online</template>
|
||||
<template v-else-if="event.attendance_mode == 'mixed'">Präsenzveranstaltung und online</template>
|
||||
</span>
|
||||
</span>
|
||||
`
|
||||
});
|
||||
|
||||
var vue_app_data = {
|
||||
widget: null,
|
||||
totalRows: 0,
|
||||
currentPage: 1,
|
||||
dates: [],
|
||||
isLoading: false,
|
||||
isLoadingCategories: false,
|
||||
initialLoaded: false,
|
||||
initialLoadedCategories: false,
|
||||
error: null,
|
||||
form: {
|
||||
dateFrom: moment().toDate(),
|
||||
dateTo: null,
|
||||
keyword: null,
|
||||
category_id: null,
|
||||
},
|
||||
categories: null,
|
||||
};
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
i18n,
|
||||
data: vue_app_data,
|
||||
mounted() {
|
||||
this.isLoading = false;
|
||||
this.error = null;
|
||||
this.dates = [];
|
||||
this.loadData();
|
||||
this.loadCategories();
|
||||
},
|
||||
computed: {
|
||||
perPage() {
|
||||
return this.widget != null ? Math.min(50, this.widget.settings.eventsPerPage) : 10;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentPage: function (val) {
|
||||
this.loadData();
|
||||
},
|
||||
widget: function (val) {
|
||||
this.loadData();
|
||||
this.loadCategories();
|
||||
},
|
||||
'widget.settings.organizationId': function(val) {
|
||||
this.parameterChanged();
|
||||
},
|
||||
'widget.settings.eventListId': function(val) {
|
||||
this.parameterChanged();
|
||||
},
|
||||
'widget.settings.eventsPerPage': function(val) {
|
||||
this.currentPage = 1;
|
||||
this.parameterChanged();
|
||||
},
|
||||
'widget.settings.showFilter': function(val) {
|
||||
if (!this.initialLoadedCategories) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadCategories();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
parameterChanged() {
|
||||
if (!this.initialLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadData();
|
||||
},
|
||||
getSearchParams() {
|
||||
let params = {
|
||||
page: this.currentPage,
|
||||
per_page: this.perPage,
|
||||
date_from: moment(this.form.dateFrom).format("YYYY-MM-DD"),
|
||||
};
|
||||
|
||||
if (this.widget.settings.eventListId) {
|
||||
params.event_list_id = this.widget.settings.eventListId;
|
||||
}
|
||||
else if (this.widget.settings.organizationId) {
|
||||
params.organization_id = this.widget.settings.organizationId;
|
||||
}
|
||||
|
||||
if (this.form.dateTo) {
|
||||
params.date_to = moment(this.form.dateTo).format("YYYY-MM-DD");
|
||||
}
|
||||
|
||||
if (this.form.keyword) {
|
||||
params.keyword = this.form.keyword;
|
||||
}
|
||||
|
||||
if (this.form.category_id && this.form.category_id > 0) {
|
||||
params.category_id = this.form.category_id;
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
loadData() {
|
||||
if (this.widget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
const params = this.getSearchParams();
|
||||
axios
|
||||
.get(`/api/v1/event-dates/search`, { params: params })
|
||||
.then((response) => {
|
||||
this.error = null;
|
||||
this.dates = response.data.items;
|
||||
this.totalRows = response.data.total;
|
||||
this.isLoading = false;
|
||||
this.initialLoaded = true;
|
||||
this.scrollToTop();
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoading = false;
|
||||
this.dates = [];
|
||||
this.totalRows = 0;
|
||||
this.error = error.message;
|
||||
this.scrollToTop();
|
||||
});
|
||||
},
|
||||
loadCategories() {
|
||||
if (this.widget == null ||
|
||||
!this.widget.settings.showFilter ||
|
||||
this.categories != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoadingCategories = true;
|
||||
|
||||
axios
|
||||
.get(`/api/v1/event-categories`)
|
||||
.then((response) => {
|
||||
let categories = response.data.items.map(function(c) {
|
||||
return { value: c.id, text: i18n.t(`categories.${c.name}`) };
|
||||
});
|
||||
categories.sort(function (a, b) {
|
||||
return a.text > b.text ? 1 : -1;
|
||||
})
|
||||
categories.unshift({ value: 0, text: "" });
|
||||
this.categories = categories;
|
||||
this.isLoadingCategories = false;
|
||||
this.initialLoadedCategories = true;
|
||||
})
|
||||
.catch(error => {
|
||||
this.isLoadingCategories = false;
|
||||
});
|
||||
},
|
||||
render_event_date_instance(value, allday, format = "dd. DD.MM.YYYY LT", alldayFormat = "dd. DD.MM.YYYY") {
|
||||
const instance = moment(value);
|
||||
|
||||
if (allday) {
|
||||
return instance.format(alldayFormat);
|
||||
}
|
||||
|
||||
return instance.format(format);
|
||||
},
|
||||
url_for_image(image, size) {
|
||||
return `${axios.defaults.baseURL}${image.image_url}?s=${size}`
|
||||
},
|
||||
scrollToTop() {
|
||||
window.scrollTo(0,0);
|
||||
|
||||
if ('parentIFrame' in window) {
|
||||
parentIFrame.scrollToOffset(0,0);
|
||||
}
|
||||
},
|
||||
printPage() {
|
||||
window.print();
|
||||
},
|
||||
openEventDate(date) {
|
||||
const url = `${axios.defaults.baseURL}/eventdate/${date.id}`;
|
||||
this.trackAnalyticsEvent({'event':'linkClick', 'url':url});
|
||||
window.open(url);
|
||||
},
|
||||
openSearch() {
|
||||
const params = this.getSearchParams();
|
||||
const url = `${axios.defaults.baseURL}/eventdates`;
|
||||
const searchUrl = axios.getUri({ method: "get", url: url, params: params });
|
||||
window.open(searchUrl);
|
||||
},
|
||||
trackAnalyticsEvent(data) {
|
||||
if ('parentIFrame' in window) {
|
||||
parentIFrame.sendMessage({'type': 'OVEDA_ANALYTICS_EVENT', 'data': data});
|
||||
}
|
||||
},
|
||||
updateSettings(settings) {
|
||||
if (this.widget == null) {
|
||||
this.widget = { settings: settings };
|
||||
}
|
||||
|
||||
for (var key in settings) {
|
||||
this.widget.settings[key] = settings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.iFrameResizer = {
|
||||
onReady: function() {
|
||||
app.trackAnalyticsEvent({'event':'pageView', 'url':document.location.href});
|
||||
},
|
||||
onMessage: function(message) {
|
||||
if (message.type != 'OVEDA_WIDGET_SETTINGS_UPDATE_EVENT') {
|
||||
return;
|
||||
}
|
||||
|
||||
app.updateSettings(message.data);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.contentWindow.min.js" integrity="sha512-14SY6teTzhrLWeL55Q4uCyxr6GQOxF3pEoMxo2mBxXwPRikdMtzKMYWy2B5Lqjr6PHHoGOxZgPaxUYKQrSmu0A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -269,6 +269,7 @@
|
||||
<a class="dropdown-item" href="{{ url_for('manage_admin_unit_organization_invitations', id=current_admin_unit.id) }}">{{ _('Organization invitations') }}</a>
|
||||
{% endif %}
|
||||
<a class="dropdown-item" href="{{ url_for('admin_unit_update', id=current_admin_unit.id) }}">{{ _('Settings') }}</a>
|
||||
<a class="dropdown-item" href="{{ url_for('manage_admin_unit_custom_widgets', id=current_admin_unit.id) }}">{{ _('Custom widgets') }}</a>
|
||||
<a class="dropdown-item" href="{{ url_for('manage_admin_unit_widgets', id=current_admin_unit.id) }}">{{ _('Widgets') }}</a>
|
||||
<a class="dropdown-item" href="{{ url_for('organizations', path=current_admin_unit.id) }}">{{ _('Profile') }}</a>
|
||||
</div>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
{% block content %}
|
||||
{% block vue_container %}
|
||||
<div id="vue-container"><router-view></router-view></div>
|
||||
<div id="vue-container"{% block vue_container_attribs %}{% endblock vue_container_attribs %}><router-view></router-view></div>
|
||||
{% endblock %}
|
||||
<script>
|
||||
Vue.component("vue-typeahead-bootstrap", VueTypeaheadBootstrap);
|
||||
@ -76,6 +76,14 @@
|
||||
relationVerify: "Verify new organization",
|
||||
relationVerifyDescription: "If set, events of the new organization are publicly visible.",
|
||||
},
|
||||
customWidget: {
|
||||
className: "Custom widget",
|
||||
listName: "Custom widgets",
|
||||
widgetType: "Type",
|
||||
widgetTypeSearch: "Search",
|
||||
widgetTypeCalendar: "Calendar",
|
||||
name: "Name",
|
||||
},
|
||||
event: {
|
||||
className: "Event",
|
||||
listName: "Events",
|
||||
@ -96,6 +104,7 @@
|
||||
},
|
||||
cancel: "Cancel",
|
||||
decline: "Decline",
|
||||
save: "Save",
|
||||
submit: "Submit",
|
||||
view: "View",
|
||||
edit: "Edit",
|
||||
@ -175,6 +184,14 @@
|
||||
relationVerify: "Neue Organisation verifizieren",
|
||||
relationVerifyDescription: "Wenn gesetzt, sind Veranstaltungen der neuen Organisation öffentlich sichtbar.",
|
||||
},
|
||||
customWidget: {
|
||||
className: "Custom widget",
|
||||
listName: "Custom widgets",
|
||||
widgetTypeSearch: "Suche",
|
||||
widgetTypeCalendar: "Kalender",
|
||||
widgetType: "Typ",
|
||||
name: "Name",
|
||||
},
|
||||
event: {
|
||||
className: "Veranstaltung",
|
||||
listName: "Veranstaltungen",
|
||||
@ -195,6 +212,7 @@
|
||||
},
|
||||
cancel: "Abbrechen",
|
||||
decline: "Ablehnen",
|
||||
save: "Speichern",
|
||||
submit: "Senden",
|
||||
view: "Anzeigen",
|
||||
edit: "Bearbeiten",
|
||||
|
||||
46
project/templates/manage/custom_widgets.html
Normal file
46
project/templates/manage/custom_widgets.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "layout_vue.html" %}
|
||||
|
||||
{% block html_attribs %}{% if full_height %} class="h-100"{% else %}{{ super() }}{% endif %}{% endblock html_attribs %}
|
||||
{% block body_attribs %}{% if full_height %} class="h-100"{% else %}{{ super() }}{% endif %}{% endblock body_attribs %}
|
||||
{% block body_content__attribs %}{% if full_height %} style="height:100%;"{% else %}{{ super() }}{% endif %}{% endblock %}
|
||||
{% block content_container_attribs %}{% if full_height %} class="h-100"{% else %}{{ super() }}{% endif %}{% endblock content_container_attribs %}
|
||||
{% block vue_container_attribs %}{% if full_height %} class="h-100"{% else %}{{ super() }}{% endif %}{% endblock vue_container_attribs %}
|
||||
{% block navbar %}{% if full_height %} {% else %}{{ super() }}{% endif %}{% endblock navbar %}
|
||||
{% block managebar %}{% if full_height %} {% else %}{{ super() }}{% endif %}{% endblock managebar %}
|
||||
{% block footer %}{% if full_height %} {% else %}{{ super() }}{% endif %}{% endblock footer %}
|
||||
|
||||
{%- block title -%}
|
||||
{{ _('Custom widgets') }}
|
||||
{%- endblock -%}
|
||||
|
||||
{% block header_before_site_js %}
|
||||
{{ super() }}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.min.js" integrity="sha512-dnvR4Aebv5bAtJxDunq3eE8puKAJrY9GBJYl9GC6lTOEC76s1dbDfJFcL9GyzpaDW4vlI/UjR8sKbc1j6Ynx6w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
{%- endblock -%}
|
||||
|
||||
{% block component_scripts %}
|
||||
<script src="{{ url_for('static', filename='vue/widget-configurator/list.vue.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='vue/widget-configurator/configurator.vue.js')}}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block component_definitions %}
|
||||
Vue.component("WidgetConfiguratorList", WidgetConfiguratorList);
|
||||
Vue.component("WidgetConfigurator", WidgetConfigurator);
|
||||
{% endblock %}
|
||||
|
||||
{% block vue_routes %}
|
||||
const routes = [
|
||||
{
|
||||
path: "/manage/admin_unit/:organization_id/custom-widgets",
|
||||
component: WidgetConfiguratorList,
|
||||
},
|
||||
{
|
||||
path: "/manage/admin_unit/:organization_id/custom-widgets/create",
|
||||
component: WidgetConfigurator,
|
||||
},
|
||||
{
|
||||
path: "/manage/admin_unit/:organization_id/custom-widgets/:custom_widget_id/update",
|
||||
component: WidgetConfigurator,
|
||||
},
|
||||
];
|
||||
{% endblock %}
|
||||
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-12-08 13:32+0100\n"
|
||||
"POT-Creation-Date: 2021-12-30 14:51+0100\n"
|
||||
"PO-Revision-Date: 2020-06-07 18:51+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: de\n"
|
||||
@ -194,29 +194,29 @@ msgstr "."
|
||||
msgid "message"
|
||||
msgstr "message"
|
||||
|
||||
#: project/api/organization/resources.py:364
|
||||
#: project/api/organization/resources.py:371
|
||||
#: project/views/admin_unit_member_invitation.py:85
|
||||
msgid "You have received an invitation"
|
||||
msgstr "Du hast eine Einladung erhalten"
|
||||
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:311
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:312
|
||||
#: project/views/root.py:37
|
||||
msgid "Terms of service"
|
||||
msgstr "Nutzungsbedingungen"
|
||||
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:315
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:316
|
||||
#: project/views/root.py:45
|
||||
msgid "Legal notice"
|
||||
msgstr "Impressum"
|
||||
|
||||
#: project/forms/admin.py:12 project/templates/_macros.html:1392
|
||||
#: project/templates/layout.html:319
|
||||
#: project/templates/layout.html:320
|
||||
#: project/templates/widget/event_suggestion/create.html:204
|
||||
#: project/views/admin_unit.py:73 project/views/root.py:53
|
||||
msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:323
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:324
|
||||
#: project/views/root.py:61
|
||||
msgid "Privacy"
|
||||
msgstr "Datenschutz"
|
||||
@ -957,7 +957,7 @@ msgstr "Standort"
|
||||
msgid "Distance"
|
||||
msgstr "Distanz"
|
||||
|
||||
#: project/forms/event_date.py:33 project/forms/planing.py:36
|
||||
#: project/forms/event_date.py:38 project/forms/planing.py:36
|
||||
#: project/templates/widget/event_date/list.html:82
|
||||
msgid "Find"
|
||||
msgstr "Finden"
|
||||
@ -1388,7 +1388,7 @@ msgid "Manage"
|
||||
msgstr "Verwaltung"
|
||||
|
||||
#: project/templates/home.html:30 project/templates/security/login_user.html:38
|
||||
#: project/views/widget.py:158
|
||||
#: project/views/widget.py:159
|
||||
msgid "Register for free"
|
||||
msgstr "Kostenlos registrieren"
|
||||
|
||||
@ -1412,7 +1412,7 @@ msgstr "Organisationen"
|
||||
msgid "Planing"
|
||||
msgstr "Planung"
|
||||
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:273
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:274
|
||||
#: project/templates/oauth2_client/list.html:10
|
||||
#: project/templates/oauth2_client/read.html:10
|
||||
#: project/templates/oauth2_token/list.html:10 project/templates/profile.html:4
|
||||
@ -1514,17 +1514,22 @@ msgstr "Organisationseinladungen"
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: project/templates/layout.html:272 project/templates/manage/reviews.html:10
|
||||
#: project/templates/layout.html:272
|
||||
#: project/templates/manage/custom_widgets.html:13
|
||||
msgid "Custom widgets"
|
||||
msgstr "Custom widgets"
|
||||
|
||||
#: project/templates/layout.html:273 project/templates/manage/reviews.html:10
|
||||
#: project/templates/manage/widgets.html:5
|
||||
#: project/templates/manage/widgets.html:9
|
||||
msgid "Widgets"
|
||||
msgstr "Widgets"
|
||||
|
||||
#: project/templates/layout.html:283
|
||||
#: project/templates/layout.html:284
|
||||
msgid "Switch organization"
|
||||
msgstr "Organisation wechseln"
|
||||
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:333
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:334
|
||||
#: project/templates/profile.html:29
|
||||
msgid "Developer"
|
||||
msgstr "Entwickler"
|
||||
@ -1997,7 +2002,7 @@ msgstr "Vorschau"
|
||||
msgid "Organization successfully updated"
|
||||
msgstr "Organisation erfolgreich aktualisiert"
|
||||
|
||||
#: project/views/admin.py:68 project/views/manage.py:331
|
||||
#: project/views/admin.py:68 project/views/manage.py:346
|
||||
msgid "Settings successfully updated"
|
||||
msgstr "Einstellungen erfolgreich aktualisiert"
|
||||
|
||||
@ -2058,27 +2063,27 @@ msgstr "Die eingegebene Email passt nicht zur Email der Einladung"
|
||||
msgid "Invitation successfully deleted"
|
||||
msgstr "Einladung erfolgreich gelöscht"
|
||||
|
||||
#: project/views/event.py:179
|
||||
#: project/views/event.py:178
|
||||
msgid "Event successfully published"
|
||||
msgstr "Veranstaltung erfolgreich veröffentlicht"
|
||||
|
||||
#: project/views/event.py:181
|
||||
#: project/views/event.py:180
|
||||
msgid "Draft successfully saved"
|
||||
msgstr "Entwurf erfolgreich gespeichert"
|
||||
|
||||
#: project/views/event.py:224
|
||||
#: project/views/event.py:223
|
||||
msgid "Event successfully updated"
|
||||
msgstr "Veranstaltung erfolgreich aktualisiert"
|
||||
|
||||
#: project/views/event.py:250
|
||||
#: project/views/event.py:249
|
||||
msgid "Event successfully deleted"
|
||||
msgstr "Veranstaltung erfolgreich gelöscht"
|
||||
|
||||
#: project/views/event.py:409
|
||||
#: project/views/event.py:408
|
||||
msgid "Referenced event changed"
|
||||
msgstr "Empfohlene Veranstaltung wurde geändert"
|
||||
|
||||
#: project/views/event.py:432
|
||||
#: project/views/event.py:431
|
||||
msgid "New event report"
|
||||
msgstr "Neue Meldung zu einer Veranstaltung"
|
||||
|
||||
@ -2242,11 +2247,11 @@ msgstr ""
|
||||
"Die Einladung wurde für einen anderen Nutzer ausgestellt. Melde dich mit "
|
||||
"der Email-Adresse an, an die die Einladung geschickt wurde."
|
||||
|
||||
#: project/views/widget.py:150
|
||||
#: project/views/widget.py:151
|
||||
msgid "Thank you so much! The event is being verified."
|
||||
msgstr "Vielen Dank! Die Veranstaltung wird geprüft."
|
||||
|
||||
#: project/views/widget.py:154
|
||||
#: project/views/widget.py:155
|
||||
msgid ""
|
||||
"For more options and your own calendar of events, you can register for "
|
||||
"free."
|
||||
@ -2254,7 +2259,7 @@ msgstr ""
|
||||
"Für mehr Optionen und einen eigenen Veranstaltungskalender, kannst du "
|
||||
"dich kostenlos registrieren."
|
||||
|
||||
#: project/views/widget.py:212
|
||||
#: project/views/widget.py:221
|
||||
msgid "New event review"
|
||||
msgstr "Neue Veranstaltung zu prüfen"
|
||||
|
||||
|
||||
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2021-12-08 13:32+0100\n"
|
||||
"POT-Creation-Date: 2021-12-30 14:51+0100\n"
|
||||
"PO-Revision-Date: 2021-04-30 15:04+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
@ -194,29 +194,29 @@ msgstr ""
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: project/api/organization/resources.py:364
|
||||
#: project/api/organization/resources.py:371
|
||||
#: project/views/admin_unit_member_invitation.py:85
|
||||
msgid "You have received an invitation"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:311
|
||||
#: project/forms/admin.py:10 project/templates/layout.html:312
|
||||
#: project/views/root.py:37
|
||||
msgid "Terms of service"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:315
|
||||
#: project/forms/admin.py:11 project/templates/layout.html:316
|
||||
#: project/views/root.py:45
|
||||
msgid "Legal notice"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:12 project/templates/_macros.html:1392
|
||||
#: project/templates/layout.html:319
|
||||
#: project/templates/layout.html:320
|
||||
#: project/templates/widget/event_suggestion/create.html:204
|
||||
#: project/views/admin_unit.py:73 project/views/root.py:53
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:323
|
||||
#: project/forms/admin.py:13 project/templates/layout.html:324
|
||||
#: project/views/root.py:61
|
||||
msgid "Privacy"
|
||||
msgstr ""
|
||||
@ -918,7 +918,7 @@ msgstr ""
|
||||
msgid "Distance"
|
||||
msgstr ""
|
||||
|
||||
#: project/forms/event_date.py:33 project/forms/planing.py:36
|
||||
#: project/forms/event_date.py:38 project/forms/planing.py:36
|
||||
#: project/templates/widget/event_date/list.html:82
|
||||
msgid "Find"
|
||||
msgstr ""
|
||||
@ -1343,7 +1343,7 @@ msgid "Manage"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/home.html:30 project/templates/security/login_user.html:38
|
||||
#: project/views/widget.py:158
|
||||
#: project/views/widget.py:159
|
||||
msgid "Register for free"
|
||||
msgstr ""
|
||||
|
||||
@ -1367,7 +1367,7 @@ msgstr ""
|
||||
msgid "Planing"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:273
|
||||
#: project/templates/layout.html:187 project/templates/layout.html:274
|
||||
#: project/templates/oauth2_client/list.html:10
|
||||
#: project/templates/oauth2_client/read.html:10
|
||||
#: project/templates/oauth2_token/list.html:10 project/templates/profile.html:4
|
||||
@ -1469,17 +1469,22 @@ msgstr ""
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:272 project/templates/manage/reviews.html:10
|
||||
#: project/templates/layout.html:272
|
||||
#: project/templates/manage/custom_widgets.html:13
|
||||
msgid "Custom widgets"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:273 project/templates/manage/reviews.html:10
|
||||
#: project/templates/manage/widgets.html:5
|
||||
#: project/templates/manage/widgets.html:9
|
||||
msgid "Widgets"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/layout.html:283
|
||||
#: project/templates/layout.html:284
|
||||
msgid "Switch organization"
|
||||
msgstr ""
|
||||
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:333
|
||||
#: project/templates/developer/read.html:4 project/templates/layout.html:334
|
||||
#: project/templates/profile.html:29
|
||||
msgid "Developer"
|
||||
msgstr ""
|
||||
@ -1946,7 +1951,7 @@ msgstr ""
|
||||
msgid "Organization successfully updated"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/admin.py:68 project/views/manage.py:331
|
||||
#: project/views/admin.py:68 project/views/manage.py:346
|
||||
msgid "Settings successfully updated"
|
||||
msgstr ""
|
||||
|
||||
@ -2004,27 +2009,27 @@ msgstr ""
|
||||
msgid "Invitation successfully deleted"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:179
|
||||
#: project/views/event.py:178
|
||||
msgid "Event successfully published"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:181
|
||||
#: project/views/event.py:180
|
||||
msgid "Draft successfully saved"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:224
|
||||
#: project/views/event.py:223
|
||||
msgid "Event successfully updated"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:250
|
||||
#: project/views/event.py:249
|
||||
msgid "Event successfully deleted"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:409
|
||||
#: project/views/event.py:408
|
||||
msgid "Referenced event changed"
|
||||
msgstr ""
|
||||
|
||||
#: project/views/event.py:432
|
||||
#: project/views/event.py:431
|
||||
msgid "New event report"
|
||||
msgstr ""
|
||||
|
||||
@ -2180,17 +2185,17 @@ msgid ""
|
||||
" the invitation was sent to."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:150
|
||||
#: project/views/widget.py:151
|
||||
msgid "Thank you so much! The event is being verified."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:154
|
||||
#: project/views/widget.py:155
|
||||
msgid ""
|
||||
"For more options and your own calendar of events, you can register for "
|
||||
"free."
|
||||
msgstr ""
|
||||
|
||||
#: project/views/widget.py:212
|
||||
#: project/views/widget.py:221
|
||||
msgid "New event review"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -291,6 +291,21 @@ def manage_admin_unit_event_lists(id, path=None):
|
||||
)
|
||||
|
||||
|
||||
@app.route("/manage/admin_unit/<int:id>/custom-widgets")
|
||||
@app.route("/manage/admin_unit/<int:id>/custom-widgets/<path:path>")
|
||||
@auth_required()
|
||||
def manage_admin_unit_custom_widgets(id, path=None):
|
||||
admin_unit = get_admin_unit_for_manage_or_404(id)
|
||||
set_current_admin_unit(admin_unit)
|
||||
full_height = path is not None and (path == "create" or path.endswith("/update"))
|
||||
|
||||
return render_template(
|
||||
"manage/custom_widgets.html",
|
||||
admin_unit=admin_unit,
|
||||
full_height=full_height,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/manage/admin_unit/<int:id>/widgets", methods=("GET", "POST"))
|
||||
@auth_required()
|
||||
def manage_admin_unit_widgets(id):
|
||||
|
||||
55
tests/api/test_custom_widget.py
Normal file
55
tests/api/test_custom_widget.py
Normal file
@ -0,0 +1,55 @@
|
||||
def test_read(client, seeder, utils):
|
||||
_, admin_unit_id = seeder.setup_base()
|
||||
custom_widget_id = seeder.insert_event_custom_widget(admin_unit_id)
|
||||
|
||||
url = utils.get_url("api_v1_custom_widget", id=custom_widget_id)
|
||||
response = utils.get_json(url)
|
||||
utils.assert_response_ok(response)
|
||||
assert response.json["settings"]["color"] == "black"
|
||||
|
||||
|
||||
def test_put(client, seeder, utils, app):
|
||||
_, admin_unit_id = seeder.setup_api_access()
|
||||
custom_widget_id = seeder.insert_event_custom_widget(admin_unit_id)
|
||||
|
||||
url = utils.get_url("api_v1_custom_widget", id=custom_widget_id)
|
||||
response = utils.put_json(url, {"widget_type": "search", "name": "Neuer Name"})
|
||||
utils.assert_response_no_content(response)
|
||||
|
||||
with app.app_context():
|
||||
from project.models import CustomWidget
|
||||
|
||||
custom_widget = CustomWidget.query.get(custom_widget_id)
|
||||
assert custom_widget.name == "Neuer Name"
|
||||
assert custom_widget.widget_type == "search"
|
||||
|
||||
|
||||
def test_patch(client, seeder, utils, app):
|
||||
_, admin_unit_id = seeder.setup_api_access()
|
||||
custom_widget_id = seeder.insert_event_custom_widget(admin_unit_id)
|
||||
|
||||
url = utils.get_url("api_v1_custom_widget", id=custom_widget_id)
|
||||
response = utils.patch_json(url, {"name": "Neuer Name"})
|
||||
utils.assert_response_no_content(response)
|
||||
|
||||
with app.app_context():
|
||||
from project.models import CustomWidget
|
||||
|
||||
custom_widget = CustomWidget.query.get(custom_widget_id)
|
||||
assert custom_widget.name == "Neuer Name"
|
||||
assert custom_widget.widget_type == "search"
|
||||
|
||||
|
||||
def test_delete(client, seeder, utils, app):
|
||||
_, admin_unit_id = seeder.setup_api_access()
|
||||
custom_widget_id = seeder.insert_event_custom_widget(admin_unit_id)
|
||||
|
||||
url = utils.get_url("api_v1_custom_widget", id=custom_widget_id)
|
||||
response = utils.delete(url)
|
||||
utils.assert_response_no_content(response)
|
||||
|
||||
with app.app_context():
|
||||
from project.models import CustomWidget
|
||||
|
||||
custom_widget = CustomWidget.query.get(custom_widget_id)
|
||||
assert custom_widget is None
|
||||
@ -81,6 +81,9 @@ def test_search(client, seeder, utils):
|
||||
url = utils.get_url("api_v1_event_date_search", organizer_id=organizer_id)
|
||||
response = utils.get_ok(url)
|
||||
|
||||
url = utils.get_url("api_v1_event_date_search", organization_id=admin_unit_id)
|
||||
response = utils.get_ok(url)
|
||||
|
||||
listed_event_id = seeder.create_event(admin_unit_id)
|
||||
event_list_id = seeder.create_event_list(admin_unit_id, listed_event_id)
|
||||
url = utils.get_url("api_v1_event_date_search", event_list_id=event_list_id)
|
||||
|
||||
@ -544,3 +544,41 @@ def test_organization_invitation_list_post(client, app, seeder, utils, mocker):
|
||||
id=invitation_id,
|
||||
)
|
||||
utils.assert_send_mail_called(mail_mock, "invited@test.de", invitation_url)
|
||||
|
||||
|
||||
def test_custom_widgets(client, seeder, utils):
|
||||
_, admin_unit_id = seeder.setup_base()
|
||||
seeder.insert_event_custom_widget(admin_unit_id)
|
||||
|
||||
url = utils.get_url(
|
||||
"api_v1_organization_custom_widget_list", id=admin_unit_id, name="mein"
|
||||
)
|
||||
utils.get_ok(url)
|
||||
|
||||
|
||||
def test_custom_widgets_post(client, seeder, utils, app):
|
||||
user_id, admin_unit_id = seeder.setup_api_access()
|
||||
|
||||
url = utils.get_url("api_v1_organization_custom_widget_list", id=admin_unit_id)
|
||||
response = utils.post_json(
|
||||
url,
|
||||
{
|
||||
"widget_type": "search",
|
||||
"name": "Neues Widget",
|
||||
"settings": {"color": "black"},
|
||||
},
|
||||
)
|
||||
utils.assert_response_created(response)
|
||||
assert "id" in response.json
|
||||
|
||||
with app.app_context():
|
||||
from project.models import CustomWidget
|
||||
|
||||
custom_widget = (
|
||||
CustomWidget.query.filter(CustomWidget.admin_unit_id == admin_unit_id)
|
||||
.filter(CustomWidget.name == "Neues Widget")
|
||||
.first()
|
||||
)
|
||||
assert custom_widget is not None
|
||||
assert custom_widget.widget_type == "search"
|
||||
assert custom_widget.settings["color"] == "black"
|
||||
|
||||
@ -200,6 +200,28 @@ class Seeder(object):
|
||||
|
||||
return organizer_id
|
||||
|
||||
def insert_event_custom_widget(
|
||||
self,
|
||||
admin_unit_id,
|
||||
widget_type="search",
|
||||
name="Mein Widget",
|
||||
settings={"color": "black"},
|
||||
):
|
||||
from project.models import CustomWidget
|
||||
|
||||
with self._app.app_context():
|
||||
custom_widget = CustomWidget()
|
||||
custom_widget.admin_unit_id = admin_unit_id
|
||||
custom_widget.widget_type = widget_type
|
||||
custom_widget.name = name
|
||||
custom_widget.settings = settings
|
||||
|
||||
self._db.session.add(custom_widget)
|
||||
self._db.session.commit()
|
||||
custom_widget_id = custom_widget.id
|
||||
|
||||
return custom_widget_id
|
||||
|
||||
def insert_default_oauth2_client(self, user_id):
|
||||
from project.api import scope_list
|
||||
from project.models import OAuth2Client
|
||||
|
||||
@ -202,3 +202,10 @@ def test_admin_unit_event_lists(client, seeder, utils):
|
||||
|
||||
url = utils.get_url("manage_admin_unit_event_lists", id=admin_unit_id)
|
||||
utils.get_ok(url)
|
||||
|
||||
|
||||
def test_admin_unit_custom_widgets(client, seeder, utils):
|
||||
_, admin_unit_id = seeder.setup_base()
|
||||
|
||||
url = utils.get_url("manage_admin_unit_custom_widgets", id=admin_unit_id)
|
||||
utils.get_ok(url)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user