diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5547e82 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,82 @@ +name: CI/CD + +on: + pull_request_target: + branches: + - '**' + +jobs: + backend-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Check formatting with Black + run: | + # Stop the build if there are any formatting issues picked up by Black + black --check . + - name: Tests + env: + SECRET_KEY: ${{ secrets.DJANGO_SECRET_KEY }} + DEBUG: 0 + run: python manage.py test --settings=hackathon_site.settings.ci + + template-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Install dependencies + run: yarn install + - name: Formatting check + run: yarn run prettier-check + + dashboard-checks: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hackathon_site/dashboard/frontend + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Install dependencies + run: yarn install + - name: Formatting check + run: yarn prettier --check 'src/**/*.(js|ts|tsx|scss)' + - name: Typescript check + run: yarn run tsc + - name: Tests + run: yarn test --watchAll=false + - name: Build frontend + run: yarn run build diff --git a/deployment/entrypoint.sh b/deployment/entrypoint.sh index 2f33e35..704de48 100644 --- a/deployment/entrypoint.sh +++ b/deployment/entrypoint.sh @@ -18,4 +18,4 @@ then else echo "Failed to connect to database" exit 1 -fi \ No newline at end of file +fi diff --git a/hackathon_site/dashboard/frontend/src/api/helpers.ts b/hackathon_site/dashboard/frontend/src/api/helpers.ts index 70f0067..53836f5 100644 --- a/hackathon_site/dashboard/frontend/src/api/helpers.ts +++ b/hackathon_site/dashboard/frontend/src/api/helpers.ts @@ -72,7 +72,6 @@ export const teamOrderListSerialization = ( id: order.id, hardwareInOrder: returnedHardware, }); - const hardwareInTableRow = Object.values(hardwareItems); if (hardwareInTableRow.length > 0) (order.status === "Submitted" || order.status === "Ready for Pickup" diff --git a/hackathon_site/dashboard/frontend/src/components/dashboard/ItemTable/ItemTable.test.tsx b/hackathon_site/dashboard/frontend/src/components/dashboard/ItemTable/ItemTable.test.tsx index 3b15219..2f031e2 100644 --- a/hackathon_site/dashboard/frontend/src/components/dashboard/ItemTable/ItemTable.test.tsx +++ b/hackathon_site/dashboard/frontend/src/components/dashboard/ItemTable/ItemTable.test.tsx @@ -14,7 +14,7 @@ import { mockReturnedOrdersInTable, } from "testing/mockData"; import { ReturnOrderInTable } from "api/types"; -import { getAllByText } from "@testing-library/react"; +import { getAllByText, queryAllByText } from "@testing-library/react"; describe("", () => { it("Shows pending items and status chip", () => { diff --git a/hackathon_site/event/admin.py b/hackathon_site/event/admin.py index eadbf4b..58a7f70 100644 --- a/hackathon_site/event/admin.py +++ b/hackathon_site/event/admin.py @@ -4,7 +4,7 @@ from import_export import resources from import_export.admin import ExportMixin -from event.models import Profile, Team as EventTeam, User +from event.models import Profile, Team as EventTeam, User, UserActivity from hardware.admin import OrderInline admin.site.unregister(User) @@ -96,5 +96,28 @@ def get_members_count(self, obj): return obj.members_count +@admin.register(UserActivity) +class UserActivityAdmin(ExportMixin, admin.ModelAdmin): + list_display = ( + "get_user_name", + "sign_in", + "lunch1", + "dinner1", + "breakfast2", + "lunch2", + ) + + def get_user_name(self, obj): + return f"{obj.user.first_name} {obj.user.last_name}" + + get_user_name.short_description = "Name" + + def get_queryset(self, request): + return super().get_queryset(request).select_related("user") + + def get_export_queryset(self, request): + return super().get_queryset(request).select_related("user") + + # Register your models here. admin.site.register(Profile) diff --git a/hackathon_site/event/jinja2/event/admin_qr_scanner.html b/hackathon_site/event/jinja2/event/admin_qr_scanner.html new file mode 100644 index 0000000..853b6df --- /dev/null +++ b/hackathon_site/event/jinja2/event/admin_qr_scanner.html @@ -0,0 +1,128 @@ +{% extends "event/base.html" %} + +{% block nav_links %} +
  • Dashboard
  • +
  • Change Password
  • +{% endblock %} + +{% block body %} +
    +
    +
    +
    + + {% if get_messages(request) %} + {% for message in get_messages(request) %} + + {% endfor %} + {% endif %} + +

    QR Scanner for Sign-In

    + {% if get_curr_sign_in_time(true) %} + + {% else %} + + {% endif %} + + + +
    + + {% if not sign_in_form %} +

    {{ hackathon_name }} is not happening now

    + {% else %} +
    +

    Student Information

    +
    + + + + + + + + + +
    Name
    Email + {{ csrf_input }} +
    + {{ sign_in_form.email }} + + {% if sign_in_form.email.errors %} + + {% for error in sign_in_form.email.errors %} + {{ error }} +
    + {% endfor %} +
    + {% endif %} +
    +
    + {% if get_curr_sign_in_time() %} + + {% endif %} +
    +
    + {% endif %} + +
    + + + + + + + + + {% for event in sign_in_times %} + + + + + + + {% endfor %} +
    EventTimeSign In IntervalScanned
    +
    +
    +
    +
    +{% endblock %} + +{% block scripts %} + + +{% endblock %} \ No newline at end of file diff --git a/hackathon_site/event/jinja2/event/base.html b/hackathon_site/event/jinja2/event/base.html index e999cc2..fb93050 100644 --- a/hackathon_site/event/jinja2/event/base.html +++ b/hackathon_site/event/jinja2/event/base.html @@ -19,6 +19,7 @@ + {% block title %}{{ hackathon_name }} {{ localtime(event_start_date).strftime("%Y") }}{% endblock %} @@ -72,4 +73,4 @@ {% block scripts %}{% endblock %} - \ No newline at end of file + diff --git a/hackathon_site/event/jinja2/event/change_password.html b/hackathon_site/event/jinja2/event/change_password.html index 1cf2c5b..377cd31 100644 --- a/hackathon_site/event/jinja2/event/change_password.html +++ b/hackathon_site/event/jinja2/event/change_password.html @@ -33,4 +33,4 @@
    Something wrong? Contact us.
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/event/jinja2/event/change_password_done.html b/hackathon_site/event/jinja2/event/change_password_done.html index f232f73..780b291 100644 --- a/hackathon_site/event/jinja2/event/change_password_done.html +++ b/hackathon_site/event/jinja2/event/change_password_done.html @@ -22,4 +22,4 @@

    Success

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/event/jinja2/event/dashboard_admin.html b/hackathon_site/event/jinja2/event/dashboard_admin.html new file mode 100644 index 0000000..64fab44 --- /dev/null +++ b/hackathon_site/event/jinja2/event/dashboard_admin.html @@ -0,0 +1,66 @@ +{% extends "event/base.html" %} + +{% block nav_links %} +
  • Dashboard
  • +
  • Change Password
  • +{% endblock %} + +{% block body %} +
    +
    +
    +
    +

    Admin Dashboard

    + +

    Administrative Actions

    + + +
    + +

    Export Data to Google Sheets

    +

    Clicking any of the below buttons will export the respective data to google sheets in this folder. Files will not be replaced, a new file will be created any time new data is exported.

    +

    (Work In Progress)

    +
    +
    + + + + +
    +
    + +
    +

    Analytics

    + + + + + + + + + + + + + +
    Total number of sign-ups:345
    Total number of completed applications:215
    Total number of completed reviews:0
    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/hackathon_site/event/jinja2/event/dashboard_base.html b/hackathon_site/event/jinja2/event/dashboard_base.html index 86b95e5..6ea5e1b 100644 --- a/hackathon_site/event/jinja2/event/dashboard_base.html +++ b/hackathon_site/event/jinja2/event/dashboard_base.html @@ -16,7 +16,8 @@

    Dashboard

    {% if application is none %} {% if not is_registration_open() %} -

    Applications are not yet open

    +

    Applications have closed

    +

    Unfortunately, the deadline to apply for {{ hackathon_name }} was {{ localtime(registration_close_date).strftime("%B %-d, %Y") }}.


    If you have any questions or concerns, please contact us.

    {% else %} @@ -34,10 +35,82 @@

    Complete your application

    {% elif application is not none and review is not none %}
    {% if review.status == "Accepted" %} -

    Congratulations! You've been accepted into {{ hackathon_name }}!

    -

    Make sure you read the participant package for all the info regarding + {% if application.rsvp and using_rsvp %} +

    Thanks for RSVP'ing! See you at {{ hackathon_name }}!

    + {% elif application.rsvp is sameas false and using_rsvp%} +

    Thanks for letting us know. Hope to see you next year at {{ hackathon_name }}

    + {% else %} +

    Congratulations! You've been accepted into {{ hackathon_name }}!

    + {% endif %} + + {% if using_rsvp %} +

    + Status:  + {% if application.rsvp is sameas false %} + + highlight_off + + {% elif application.rsvp %} + + check_circle_outline + + {% endif %} + {{ status }} +

    +
    + {% if not rsvp_passed %} + {% if application.rsvp is none %} +

    To confirm your attendance at {{ hackathon_name }}, please RSVP using the accept or decline buttons below.


    +

    RSVP by {{ rsvp_deadline }}, otherwise your spot will be given to other applicants. + Alternatively you can decline the offer. You can change your response at any time before {{ rsvp_deadline }}. +


    + {% else %} +

    You can change your response at any time before {{ rsvp_deadline }}.


    + {% endif %} + {% endif %} + {% endif %} + + {% if (application.rsvp and using_rsvp) or not using_rsvp %} +

    Make sure you read the participant package for all the info regarding the event, and join our {{ chat_room_name }}. Stay tuned for more updates regarding detailed event logistics, and we hope to see you soon!


    +
    +

    Please show the QR code below to the front desk to sign-in. .

    + +
    + {% endif %}

    If you have questions, read the FAQ, or feel free to contact us.


    + + {% if using_rsvp %} + {% if not rsvp_passed %} +
    + {% if application.rsvp is sameas false or application.rsvp is none %} + + Will Attend (Accept) + + {% endif %} + {% if application.rsvp or application.rsvp is none %} + + Cannot Attend (Decline) + + {% endif %} +
    + + {% elif rsvp_passed and application.rsvp %} +

    The RSVP deadline has passed. Thanks for confirming your position at {{ hackathon_name }}! We look forward to seeing you there. Note that you cannot change your RSVP at this time.

    + {% elif rsvp_passed and application.rsvp is sameas false %} +

    The RSVP deadline has passed. We regret to see that you will not be joining us this year at {{ hackathon_name }}. Unfortunately you cannot change your RSVP at this time.

    + {% else %} +

    It appears you haven't RSVPed. Unfortunately the RSVP deadline has passed and you cannot change your RSVP at this time.

    + {% endif %} + {% endif %} + {% elif review.status == "Waitlisted" %}

    You've been waitlisted for {{ hackathon_name }}

    The {{ hackathon_name }} team has reviewed your application, and have decided not to grant you a guaranteed spot @@ -69,6 +142,67 @@

    Your application has been submitted!

    {% endif %} + {% if using_teams and (is_registration_open() or application is not none) %} +
    +

    Apply as a team

    +

    Have friends you want to work with? Create a team with up to 4 people and we’ll review your applications together.

    +
    + {% if application is none %} +

    You must complete your application before you can form a team. Return here once you've submitted your application.

    + {% else %} +

    Your team code is: {{ application.team.team_code }}

    + {% set num_members = application.team.applications.count() %} + {% if is_registration_open() %} +

    + {% if join_team_form and num_members < application.team.MAX_MEMBERS%} + Share your team code with your teammates, or join their team instead. + {% endif %} +

    + {% endif %} + +
    +

    Team members ({{ num_members }}/{{ application.team.MAX_MEMBERS }})

    + + {% if join_team_form %} + + Leave team + +
    +
    +

    Join a different team

    +
    + {{ csrf_input }} +
    + {{ join_team_form.team_code }} + {{ join_team_form.team_code.label_tag() }} + + {% if join_team_form.team_code.errors %} + + {% for error in join_team_form.team_code.errors %} + {{ error }} +
    + {% endfor %} +
    + {% endif %} +
    +
    + +
    +
    + {% endif %} + + {% endif %} +
    + {% endif %} +

    Application FAQs

    @@ -90,4 +224,15 @@

    Application FAQs

    +{% endblock %} + +{% block scripts %} + + {% endblock %} \ No newline at end of file diff --git a/hackathon_site/event/jinja2/event/form_base.html b/hackathon_site/event/jinja2/event/form_base.html index 9eebd65..0c29131 100644 --- a/hackathon_site/event/jinja2/event/form_base.html +++ b/hackathon_site/event/jinja2/event/form_base.html @@ -80,4 +80,4 @@

    {% block form_title %}{% endblock %}

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/event/jinja2/event/landing.html b/hackathon_site/event/jinja2/event/landing.html index a627d3d..26b5f8e 100644 --- a/hackathon_site/event/jinja2/event/landing.html +++ b/hackathon_site/event/jinja2/event/landing.html @@ -397,4 +397,4 @@

    Contact Us

    {% block scripts %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/event/jinja2/event/login.html b/hackathon_site/event/jinja2/event/login.html index 428cf20..51f66d7 100644 --- a/hackathon_site/event/jinja2/event/login.html +++ b/hackathon_site/event/jinja2/event/login.html @@ -77,4 +77,4 @@
    Something wrong? Contact us.
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/event/migrations/0008_useractivity.py b/hackathon_site/event/migrations/0008_useractivity.py new file mode 100644 index 0000000..89c7e5c --- /dev/null +++ b/hackathon_site/event/migrations/0008_useractivity.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.15 on 2023-10-01 22:22 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("event", "0007_hss_test_users"), + ] + + operations = [ + migrations.CreateModel( + name="UserActivity", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("sign_in", models.DateTimeField(null=True)), + ("lunch1", models.DateTimeField(null=True)), + ("dinner1", models.DateTimeField(null=True)), + ("breakfast2", models.DateTimeField(null=True)), + ("lunch2", models.DateTimeField(null=True)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/hackathon_site/event/models.py b/hackathon_site/event/models.py index 8ecec5c..e745eb1 100644 --- a/hackathon_site/event/models.py +++ b/hackathon_site/event/models.py @@ -44,3 +44,12 @@ def save(self, *args, **kwargs): def __str__(self): return f"{self.id} | {self.user.first_name} {self.user.last_name}" + + +class UserActivity(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + sign_in = models.DateTimeField(null=True) + lunch1 = models.DateTimeField(null=True) + dinner1 = models.DateTimeField(null=True) + breakfast2 = models.DateTimeField(null=True) + lunch2 = models.DateTimeField(null=True) diff --git a/hackathon_site/event/serializers.py b/hackathon_site/event/serializers.py index d7849cc..4d656cc 100644 --- a/hackathon_site/event/serializers.py +++ b/hackathon_site/event/serializers.py @@ -104,11 +104,11 @@ def create(self, validated_data): ) if not is_test_user: try: - application = Application.objects.get(user=current_user) - # if not rsvp_status: - # raise serializers.ValidationError( - # "User has not RSVP'd to the hackathon. Please RSVP to access the Hardware Signout Site" - # ) + rsvp_status = Application.objects.get(user=current_user).rsvp + if not rsvp_status and settings.RSVP: + raise serializers.ValidationError( + "User has not RSVP'd to the hackathon. Please RSVP to access the Hardware Signout Site" + ) except Application.DoesNotExist: raise serializers.ValidationError( "User has not completed their application to the hackathon. Please do so to access the Hardware Signout Site" diff --git a/hackathon_site/event/static/event/images/landing.svg b/hackathon_site/event/static/event/images/landing.svg new file mode 100644 index 0000000..e978b00 --- /dev/null +++ b/hackathon_site/event/static/event/images/landing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/hackathon_site/event/static/event/js/qr-scanner-worker.min.js b/hackathon_site/event/static/event/js/qr-scanner-worker.min.js new file mode 100644 index 0000000..b31d7f9 --- /dev/null +++ b/hackathon_site/event/static/event/js/qr-scanner-worker.min.js @@ -0,0 +1,106 @@ +export const createWorker = () => + new Worker( + URL.createObjectURL( + new Blob([ + `class x{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new x(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;fa||32this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0>8-c<>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0>c<>c, +this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var B,C=B||(B={});C.Numeric="numeric";C.Alphanumeric="alphanumeric";C.Byte="byte";C.Kanji="kanji";C.ECI="eci";C.StructuredAppend="structuredappend";var D,E=D||(D={});E[E.Terminator=0]="Terminator";E[E.Numeric=1]="Numeric";E[E.Alphanumeric=2]="Alphanumeric";E[E.Byte=4]="Byte";E[E.Kanji=8]="Kanji";E[E.ECI=7]="ECI";E[E.StructuredAppend=3]="StructuredAppend";let F="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split(""); +function ca(a,b){let c=[],d="";b=a.readBits([8,16,16][b]);for(let e=0;e\`%\${("0"+e.toString(16)).substr(-2)}\`).join(""))}catch(e){}return{bytes:c,text:d}} +function da(a,b){a=new ba(a);let c=9>=b?0:26>=b?1:2;for(b={text:"",bytes:[],chunks:[],version:b};4<=a.available();){var d=a.readBits(4);if(d===D.Terminator)return b;if(d===D.ECI)0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(7)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(14)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(21)}):b.chunks.push({type:B.ECI,assignmentNumber:-1});else if(d===D.Numeric){var e=a,f=[];d="";for(var g= +e.readBits([10,12,14][c]);3<=g;){var h=e.readBits(10);if(1E3<=h)throw Error("Invalid numeric value above 999");var k=Math.floor(h/100),m=Math.floor(h/10)%10;h%=10;f.push(48+k,48+m,48+h);d+=k.toString()+m.toString()+h.toString();g-=3}if(2===g){g=e.readBits(7);if(100<=g)throw Error("Invalid numeric value above 99");e=Math.floor(g/10);g%=10;f.push(48+e,48+g);d+=e.toString()+g.toString()}else if(1===g){e=e.readBits(4);if(10<=e)throw Error("Invalid numeric value above 9");f.push(48+e);d+=e.toString()}b.text+= +d;b.bytes.push(...f);b.chunks.push({type:B.Numeric,text:d})}else if(d===D.Alphanumeric){e=a;f=[];d="";for(g=e.readBits([9,11,13][c]);2<=g;)m=e.readBits(11),k=Math.floor(m/45),m%=45,f.push(F[k].charCodeAt(0),F[m].charCodeAt(0)),d+=F[k]+F[m],g-=2;1===g&&(e=e.readBits(6),f.push(F[e].charCodeAt(0)),d+=F[e]);b.text+=d;b.bytes.push(...f);b.chunks.push({type:B.Alphanumeric,text:d})}else if(d===D.Byte)d=ca(a,c),b.text+=d.text,b.bytes.push(...d.bytes),b.chunks.push({type:B.Byte,bytes:d.bytes,text:d.text}); +else if(d===D.Kanji){f=a;d=[];e=f.readBits([8,10,12][c]);for(g=0;gk?k+33088:k+49472,d.push(k>>8,k&255);f=(new TextDecoder("shift-jis")).decode(Uint8Array.from(d));b.text+=f;b.bytes.push(...d);b.chunks.push({type:B.Kanji,bytes:d,text:f})}else d===D.StructuredAppend&&b.chunks.push({type:B.StructuredAppend,currentSequence:a.readBits(4),totalSequence:a.readBits(4),parity:a.readBits(8)})}if(0===a.available()||0===a.readBits(a.available()))return b} +class G{constructor(a,b){if(0===b.length)throw Error("No coefficients.");this.field=a;let c=b.length;if(1a.length&&([b,a]=[a,b]);let c=new Uint8ClampedArray(a.length),d=a.length-b.length;for(var e=0;ea)throw Error("Invalid degree less than 0");if(0===b)return this.field.zero;let c=this.coefficients.length;a=new Uint8ClampedArray(c+a);for(let d=0;d{b^=d}),b;b=this.coefficients[0];for(let d=1;d=this.size&&(a=(a^this.primitive)&this.size-1);for(a=0;aa)throw Error("Invalid monomial degree less than 0");if(0===b)return this.zero;a=new Uint8ClampedArray(a+1);a[0]=b;return new G(this,a)}log(a){if(0===a)throw Error("Can't take log(0)");return this.logTable[a]}exp(a){return this.expTable[a]}} +function fa(a,b,c,d){b.degree()=d/2;){var g=b;let h=e;b=c;e=f;if(b.isZero())return null;c=g;f=a.zero;g=b.getCoefficient(b.degree());for(g=a.inverse(g);c.degree()>=b.degree()&&!c.isZero();){let k=c.degree()-b.degree(),m=a.multiply(c.getCoefficient(c.degree()),g);f=f.addOrSubtract(a.buildMonomial(k,m));c=c.addOrSubtract(b.multiplyByMonomial(k,m))}f=f.multiplyPoly(e).addOrSubtract(h);if(c.degree()>=b.degree())return null}d=f.getCoefficient(0); +if(0===d)return null;a=a.inverse(d);return[f.multiply(a),c.multiply(a)]} +function ha(a,b){let c=new Uint8ClampedArray(a.length);c.set(a);a=new ea(285,256,0);var d=new G(a,c),e=new Uint8ClampedArray(b),f=!1;for(var g=0;gf)return null;c[f]^=d[e]}return c} +let I=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1, +dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18, +ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]}, +{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6, +34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18, +ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38}, +{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36}, +{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4, +dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1, +dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]}, +{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]}, +{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115}, +{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22, +ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50, +74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749, +versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17, +dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2, +dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]}, +{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24}, +{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22}, +{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24}, +{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24}, +{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30, +ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13, +dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19, +dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]}, +{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117}, +{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30, +ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6, +26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]}, +{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15}, +{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19, +dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]}, +{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44, +dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]}, +{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6, +dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]}, +{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4, +dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166], +errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017, +versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15}, +{numBlocks:61,dataCodewordsPerBlock:16}]}]}];function J(a,b){a^=b;for(b=0;a;)b++,a&=a-1;return b}function K(a,b){return b<<1|a} +let ia=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0, +dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054, +formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2, +dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],ja=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y% +2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2]; +function ka(a,b,c){c=ja[c.dataMask];let d=a.height;var e=17+4*b.versionNumber;let f=x.createEmpty(e,e);f.setRegion(0,0,9,9,!0);f.setRegion(e-8,0,8,9,!0);f.setRegion(0,e-8,9,8,!0);for(var g of b.alignmentPatternCenters)for(var h of b.alignmentPatternCenters)6===g&&6===h||6===g&&h===e-7||g===e-7&&6===h||f.setRegion(g-2,h-2,5,5,!0);f.setRegion(6,9,1,e-17,!0);f.setRegion(9,6,e-17,1,!0);6n;n++){let q=k-n;if(!f.get(q,l)){h++;let r=a.get(q,l);c({y:l,x:q})&&(r=!r);g=g<<1|r;8===h&&(b.push(g),g=h=0)}}}e=!e}return b} +function la(a){var b=a.height,c=Math.floor((b-17)/4);if(6>=c)return I[c-1];c=0;for(var d=5;0<=d;d--)for(var e=b-9;e>=b-11;e--)c=K(a.get(e,d),c);d=0;for(e=5;0<=e;e--)for(let g=b-9;g>=b-11;g--)d=K(a.get(e,g),d);a=Infinity;let f;for(let g of I){if(g.infoBits===c||g.infoBits===d)return g;b=J(c,g.infoBits);b=a)return f} +function ma(a){let b=0;for(var c=0;8>=c;c++)6!==c&&(b=K(a.get(c,8),b));for(c=7;0<=c;c--)6!==c&&(b=K(a.get(8,c),b));var d=a.height;c=0;for(var e=d-1;e>=d-7;e--)c=K(a.get(8,e),c);for(e=d-8;e=a?d:null} +function na(a,b,c){let d=b.errorCorrectionLevels[c],e=[],f=0;d.ecBlocks.forEach(h=>{for(let k=0;ke+f.numDataCodewords,0);c=new Uint8ClampedArray(c);a=0;for(let e of d){d=ha(e.codewords,e.codewords.length-e.numDataCodewords);if(!d)return null;for(let f=0;f{const p=g*r+m*u+q;return{x:(e*r+h*u+l)/p,y:(f*r+k*u+n)/p}};for(let r=0;rMath.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));function O(a){return a.reduce((b,c)=>b+c)} +function qa(a,b,c){let d=N(a,b),e=N(b,c),f=N(a,c),g,h,k;e>=d&&e>=f?[g,h,k]=[b,a,c]:f>=e&&f>=d?[g,h,k]=[a,b,c]:[g,h,k]=[a,c,b];0>(k.x-h.x)*(g.y-h.y)-(k.y-h.y)*(g.x-h.x)&&([g,k]=[k,g]);return{bottomLeft:g,topLeft:h,topRight:k}} +function ra(a,b,c,d){d=(O(P(a,c,d,5))/7+O(P(a,b,d,5))/7+O(P(c,a,d,5))/7+O(P(b,a,d,5))/7)/4;if(1>d)throw Error("Invalid module size");b=Math.round(N(a,b)/d);a=Math.round(N(a,c)/d);a=Math.floor((b+a)/2)+7;switch(a%4){case 0:a++;break;case 2:a--}return{dimension:a,moduleSize:d}} +function Q(a,b,c,d){let e=[{x:Math.floor(a.x),y:Math.floor(a.y)}];var f=Math.abs(b.y-a.y)>Math.abs(b.x-a.x);if(f){var g=Math.floor(a.y);var h=Math.floor(a.x);a=Math.floor(b.y);b=Math.floor(b.x)}else g=Math.floor(a.x),h=Math.floor(a.y),a=Math.floor(b.x),b=Math.floor(b.y);let k=Math.abs(a-g),m=Math.abs(b-h),l=Math.floor(-k/2),n=g{d+=Math.pow(a[f]-e*c,2)});return{averageSize:c,error:d}} +function S(a,b,c){try{let d=P(a,{x:-1,y:a.y},c,b.length),e=P(a,{x:a.x,y:-1},c,b.length),f=P(a,{x:Math.max(0,a.x-a.y)-1,y:Math.max(0,a.y-a.x)-1},c,b.length),g=P(a,{x:Math.min(c.width,a.x+a.y)+1,y:Math.min(c.height,a.y+a.x)+1},c,b.length),h=R(d,b),k=R(e,b),m=R(f,b),l=R(g,b),n=(h.averageSize+k.averageSize+m.averageSize+l.averageSize)/4;return Math.sqrt(h.error*h.error+k.error*k.error+m.error*m.error+l.error*l.error)+(Math.pow(h.averageSize-n,2)+Math.pow(k.averageSize-n,2)+Math.pow(m.averageSize-n,2)+ +Math.pow(l.averageSize-n,2))/n}catch(d){return Infinity}}function T(a,b){for(var c=Math.round(b.x);a.get(c,Math.round(b.y));)c--;for(var d=Math.round(b.x);a.get(d,Math.round(b.y));)d++;c=(c+d)/2;for(d=Math.round(b.y);a.get(Math.round(c),d);)d--;for(b=Math.round(b.y);a.get(Math.round(c),b);)b++;return{x:c,y:(d+b)/2}} +function sa(a){var b=[],c=[];let d=[];var e=[];for(let p=0;p<=a.height;p++){var f=0,g=!1;let t=[0,0,0,0,0];for(let v=-1;v<=a.width;v++){var h=a.get(v,p);if(h===g)f++;else{t=[t[1],t[2],t[3],t[4],f];f=1;g=h;var k=O(t)/7;k=Math.abs(t[0]-k)y>=w.bottom.startX&& +y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5y>=w.bottom.startX&&y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5v.bottom.y!==p&&2<=v.bottom.y-v.top.y));c=c.filter(v=>v.bottom.y===p);d.push(...e.filter(v=>v.bottom.y!==p));e=e.filter(v=>v.bottom.y===p)}b.push(...c.filter(p=>2<=p.bottom.y-p.top.y));d.push(...e);c=[];for(var l of b)2>l.bottom.y-l.top.y||(b=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4,e=(l.top.y+l.bottom.y+1)/2,a.get(Math.round(b),Math.round(e))&&(f=[l.top.endX-l.top.startX,l.bottom.endX-l.bottom.startX,l.bottom.y-l.top.y+ +1],f=O(f)/f.length,g=S({x:Math.round(b),y:Math.round(e)},[1,1,3,1,1],a),c.push({score:g,x:b,y:e,size:f})));if(3>c.length)return null;c.sort((p,t)=>p.score-t.score);l=[];for(b=0;bp.score-t.score);l.push({points:[e,f[0],f[1]],score:e.score+f[0].score+f[1].score})}l.sort((p,t)=>p.score-t.score);let {topRight:q,topLeft:r,bottomLeft:u}=qa(...l[0].points); +l=U(a,d,q,r,u);n=[];l&&n.push({alignmentPattern:{x:l.alignmentPattern.x,y:l.alignmentPattern.y},bottomLeft:{x:u.x,y:u.y},dimension:l.dimension,topLeft:{x:r.x,y:r.y},topRight:{x:q.x,y:q.y}});l=T(a,q);b=T(a,r);c=T(a,u);(a=U(a,d,l,b,c))&&n.push({alignmentPattern:{x:a.alignmentPattern.x,y:a.alignmentPattern.y},bottomLeft:{x:c.x,y:c.y},topLeft:{x:b.x,y:b.y},topRight:{x:l.x,y:l.y},dimension:a.dimension});return 0===n.length?null:n} +function U(a,b,c,d,e){let f,g;try{({dimension:f,moduleSize:g}=ra(d,c,e,a))}catch(l){return null}var h=c.x-d.x+e.x,k=c.y-d.y+e.y;c=(N(d,e)+N(d,c))/2/g;e=1-3/c;let m={x:d.x+e*(h-d.x),y:d.y+e*(k-d.y)};b=b.map(l=>{const n=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4;l=(l.top.y+l.bottom.y+1)/2;if(a.get(Math.floor(n),Math.floor(l))){var q=S({x:Math.floor(n),y:Math.floor(l)},[1,1,1],a)+N({x:n,y:l},m);return{x:n,y:l,score:q}}}).filter(l=>!!l).sort((l,n)=>l.score-n.score);return{alignmentPattern:15<= +c&&b.length?b[0]:m,dimension:f}} +function V(a){var b=sa(a);if(!b)return null;for(let e of b){b=pa(a,e);var c=b.matrix;if(null==c)c=null;else{var d=L(c);if(d)c=d;else{for(d=0;d{a[c]=b[c]})} +function X(a,b,c,d={}){let e=Object.create(null);W(e,ta);W(e,d);d="onlyInvert"===e.inversionAttempts||"invertFirst"===e.inversionAttempts;var f="attemptBoth"===e.inversionAttempts||d;var g=e.greyScaleWeights,h=e.canOverwriteImage,k=b*c;if(a.length!==4*k)throw Error("Malformed data passed to binarizer.");var m=0;if(h){var l=new Uint8ClampedArray(a.buffer,m,k);m+=k}l=new A(b,c,l);if(g.useIntegerApproximation)for(var n=0;n>8)}else for(n=0;nv;v++)for(let w=0;8>w;w++){let aa=l.get(8*r+w,8*q+v);p=Math.min(p,aa);t=Math.max(t,aa)}v=(p+t)/2;v=Math.min(255,1.11*v);24>=t-p&&(v=p/2,0a?2:a>c?c:a;h=n-3;h=2>b?2:b>h?h:b;k=0;for(m=-2;2>=m;m++)for(p=-2;2>=p;p++)k+=u.get(c+m,h+p);c=k/25;for(h=0;8>h;h++)for(k=0;8>k;k++)m=8*a+h,p=8*b+k,t=l.get(m,p),q.set(m,p,t<=c),f&&r.set(m,p,!(t<=c))}f=f?{binarized:q,inverted:r}:{binarized:q};let {binarized:z,inverted:y}=f;(f=V(d? +y:z))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||(f=V(d?z:y));return f}X.default=X;let Y="dontInvert",Z={red:77,green:150,blue:29,useIntegerApproximation:!0}; +self.onmessage=a=>{let b=a.data.id,c=a.data.data;switch(a.data.type){case "decode":(a=X(c.data,c.width,c.height,{inversionAttempts:Y,greyScaleWeights:Z}))?self.postMessage({id:b,type:"qrResult",data:a.data,cornerPoints:[a.location.topLeftCorner,a.location.topRightCorner,a.location.bottomRightCorner,a.location.bottomLeftCorner]}):self.postMessage({id:b,type:"qrResult",data:null});break;case "grayscaleWeights":Z.red=c.red;Z.green=c.green;Z.blue=c.blue;Z.useIntegerApproximation=c.useIntegerApproximation; +break;case "inversionMode":switch(c){case "original":Y="dontInvert";break;case "invert":Y="onlyInvert";break;case "both":Y="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}} +`, + ]), + { type: "application/javascript" } + ) + ); //# sourceMappingURL=qr-scanner-worker.min.js.map diff --git a/hackathon_site/event/static/event/js/qr-scanner.umd.min.js b/hackathon_site/event/static/event/js/qr-scanner.umd.min.js new file mode 100644 index 0000000..0bcdd4a --- /dev/null +++ b/hackathon_site/event/static/event/js/qr-scanner.umd.min.js @@ -0,0 +1,683 @@ +"use strict"; +(function (e, a) { + "object" === typeof exports && "undefined" !== typeof module + ? (module.exports = a()) + : "function" === typeof define && define.amd + ? define(a) + : ((e = "undefined" !== typeof globalThis ? globalThis : e || self), + (e.QrScanner = a())); +})(this, function () { + class e { + constructor(a, b, c, d, f) { + this._legacyCanvasSize = e.DEFAULT_CANVAS_SIZE; + this._preferredCamera = "environment"; + this._maxScansPerSecond = 25; + this._lastScanTimestamp = -1; + this._destroyed = this._flashOn = this._paused = this._active = !1; + this.$video = a; + this.$canvas = document.createElement("canvas"); + c && "object" === typeof c + ? (this._onDecode = b) + : (c || d || f + ? console.warn( + "You're using a deprecated version of the QrScanner constructor which will be removed in the future" + ) + : console.warn( + "Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true." + ), + (this._legacyOnDecode = b)); + b = "object" === typeof c ? c : {}; + this._onDecodeError = + b.onDecodeError || ("function" === typeof c ? c : this._onDecodeError); + this._calculateScanRegion = + b.calculateScanRegion || + ("function" === typeof d ? d : this._calculateScanRegion); + this._preferredCamera = b.preferredCamera || f || this._preferredCamera; + this._legacyCanvasSize = + "number" === typeof c + ? c + : "number" === typeof d + ? d + : this._legacyCanvasSize; + this._maxScansPerSecond = b.maxScansPerSecond || this._maxScansPerSecond; + this._onPlay = this._onPlay.bind(this); + this._onLoadedMetaData = this._onLoadedMetaData.bind(this); + this._onVisibilityChange = this._onVisibilityChange.bind(this); + this._updateOverlay = this._updateOverlay.bind(this); + a.disablePictureInPicture = !0; + a.playsInline = !0; + a.muted = !0; + let h = !1; + a.hidden && ((a.hidden = !1), (h = !0)); + document.body.contains(a) || (document.body.appendChild(a), (h = !0)); + c = a.parentElement; + if (b.highlightScanRegion || b.highlightCodeOutline) { + d = !!b.overlay; + this.$overlay = b.overlay || document.createElement("div"); + f = this.$overlay.style; + f.position = "absolute"; + f.display = "none"; + f.pointerEvents = "none"; + this.$overlay.classList.add("scan-region-highlight"); + if (!d && b.highlightScanRegion) { + this.$overlay.innerHTML = + ''; + try { + this.$overlay.firstElementChild.animate( + { transform: ["scale(.98)", "scale(1.01)"] }, + { + duration: 400, + iterations: Infinity, + direction: "alternate", + easing: "ease-in-out", + } + ); + } catch (m) {} + c.insertBefore(this.$overlay, this.$video.nextSibling); + } + b.highlightCodeOutline && + (this.$overlay.insertAdjacentHTML( + "beforeend", + '' + ), + (this.$codeOutlineHighlight = this.$overlay.lastElementChild)); + } + this._scanRegion = this._calculateScanRegion(a); + requestAnimationFrame(() => { + let m = window.getComputedStyle(a); + "none" === m.display && + (a.style.setProperty("display", "block", "important"), (h = !0)); + "visible" !== m.visibility && + (a.style.setProperty("visibility", "visible", "important"), + (h = !0)); + h && + (console.warn( + "QrScanner has overwritten the video hiding style to avoid Safari stopping the playback." + ), + (a.style.opacity = "0"), + (a.style.width = "0"), + (a.style.height = "0"), + this.$overlay && + this.$overlay.parentElement && + this.$overlay.parentElement.removeChild(this.$overlay), + delete this.$overlay, + delete this.$codeOutlineHighlight); + this.$overlay && this._updateOverlay(); + }); + a.addEventListener("play", this._onPlay); + a.addEventListener("loadedmetadata", this._onLoadedMetaData); + document.addEventListener("visibilitychange", this._onVisibilityChange); + window.addEventListener("resize", this._updateOverlay); + this._qrEnginePromise = e.createQrEngine(); + } + static set WORKER_PATH(a) { + console.warn( + "Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions." + ); + } + static async hasCamera() { + try { + return !!(await e.listCameras(!1)).length; + } catch (a) { + return !1; + } + } + static async listCameras(a = !1) { + if (!navigator.mediaDevices) return []; + let b = async () => + (await navigator.mediaDevices.enumerateDevices()).filter( + (d) => "videoinput" === d.kind + ), + c; + try { + a && + (await b()).every((d) => !d.label) && + (c = await navigator.mediaDevices.getUserMedia({ + audio: !1, + video: !0, + })); + } catch (d) {} + try { + return (await b()).map((d, f) => ({ + id: d.deviceId, + label: d.label || (0 === f ? "Default Camera" : `Camera ${f + 1}`), + })); + } finally { + c && + (console.warn( + "Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream" + ), + e._stopVideoStream(c)); + } + } + async hasFlash() { + let a; + try { + if (this.$video.srcObject) { + if (!(this.$video.srcObject instanceof MediaStream)) return !1; + a = this.$video.srcObject; + } else a = (await this._getCameraStream()).stream; + return "torch" in a.getVideoTracks()[0].getSettings(); + } catch (b) { + return !1; + } finally { + a && + a !== this.$video.srcObject && + (console.warn( + "Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream" + ), + e._stopVideoStream(a)); + } + } + isFlashOn() { + return this._flashOn; + } + async toggleFlash() { + this._flashOn ? await this.turnFlashOff() : await this.turnFlashOn(); + } + async turnFlashOn() { + if ( + !this._flashOn && + !this._destroyed && + ((this._flashOn = !0), this._active && !this._paused) + ) + try { + if (!(await this.hasFlash())) throw "No flash available"; + await this.$video.srcObject + .getVideoTracks()[0] + .applyConstraints({ advanced: [{ torch: !0 }] }); + } catch (a) { + throw ((this._flashOn = !1), a); + } + } + async turnFlashOff() { + this._flashOn && ((this._flashOn = !1), await this._restartVideoStream()); + } + destroy() { + this.$video.removeEventListener("loadedmetadata", this._onLoadedMetaData); + this.$video.removeEventListener("play", this._onPlay); + document.removeEventListener("visibilitychange", this._onVisibilityChange); + window.removeEventListener("resize", this._updateOverlay); + this._destroyed = !0; + this._flashOn = !1; + this.stop(); + e._postWorkerMessage(this._qrEnginePromise, "close"); + } + async start() { + if (this._destroyed) + throw Error( + "The QR scanner can not be started as it had been destroyed." + ); + if (!this._active || this._paused) + if ( + ("https:" !== window.location.protocol && + console.warn( + "The camera stream is only accessible if the page is transferred via https." + ), + (this._active = !0), + !document.hidden) + ) + if (((this._paused = !1), this.$video.srcObject)) + await this.$video.play(); + else + try { + let { stream: a, facingMode: b } = + await this._getCameraStream(); + !this._active || this._paused + ? e._stopVideoStream(a) + : (this._setVideoMirror(b), + (this.$video.srcObject = a), + await this.$video.play(), + this._flashOn && + ((this._flashOn = !1), + this.turnFlashOn().catch(() => {}))); + } catch (a) { + if (!this._paused) throw ((this._active = !1), a); + } + } + stop() { + this.pause(); + this._active = !1; + } + async pause(a = !1) { + this._paused = !0; + if (!this._active) return !0; + this.$video.pause(); + this.$overlay && (this.$overlay.style.display = "none"); + let b = () => { + this.$video.srcObject instanceof MediaStream && + (e._stopVideoStream(this.$video.srcObject), + (this.$video.srcObject = null)); + }; + if (a) return b(), !0; + await new Promise((c) => setTimeout(c, 300)); + if (!this._paused) return !1; + b(); + return !0; + } + async setCamera(a) { + a !== this._preferredCamera && + ((this._preferredCamera = a), await this._restartVideoStream()); + } + static async scanImage(a, b, c, d, f = !1, h = !1) { + let m, + n = !1; + b && + ("scanRegion" in b || + "qrEngine" in b || + "canvas" in b || + "disallowCanvasResizing" in b || + "alsoTryWithoutScanRegion" in b || + "returnDetailedScanResult" in b) + ? ((m = b.scanRegion), + (c = b.qrEngine), + (d = b.canvas), + (f = b.disallowCanvasResizing || !1), + (h = b.alsoTryWithoutScanRegion || !1), + (n = !0)) + : b || c || d || f || h + ? console.warn( + "You're using a deprecated api for scanImage which will be removed in the future." + ) + : console.warn( + "Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true." + ); + b = !!c; + try { + let p, k; + [c, p] = await Promise.all([c || e.createQrEngine(), e._loadImage(a)]); + [d, k] = e._drawToCanvas(p, m, d, f); + let q; + if (c instanceof Worker) { + let g = c; + b || e._postWorkerMessageSync(g, "inversionMode", "both"); + q = await new Promise((l, v) => { + let w, + u, + r, + y = -1; + u = (t) => { + t.data.id === y && + (g.removeEventListener("message", u), + g.removeEventListener("error", r), + clearTimeout(w), + null !== t.data.data + ? l({ + data: t.data.data, + cornerPoints: e._convertPoints( + t.data.cornerPoints, + m + ), + }) + : v(e.NO_QR_CODE_FOUND)); + }; + r = (t) => { + g.removeEventListener("message", u); + g.removeEventListener("error", r); + clearTimeout(w); + v( + "Scanner error: " + + (t ? t.message || t : "Unknown Error") + ); + }; + g.addEventListener("message", u); + g.addEventListener("error", r); + w = setTimeout(() => r("timeout"), 1e4); + let x = k.getImageData(0, 0, d.width, d.height); + y = e._postWorkerMessageSync(g, "decode", x, [x.data.buffer]); + }); + } else + q = await Promise.race([ + new Promise((g, l) => + window.setTimeout(() => l("Scanner error: timeout"), 1e4) + ), + (async () => { + try { + var [g] = await c.detect(d); + if (!g) throw e.NO_QR_CODE_FOUND; + return { + data: g.rawValue, + cornerPoints: e._convertPoints(g.cornerPoints, m), + }; + } catch (l) { + g = l.message || l; + if (/not implemented|service unavailable/.test(g)) + return ( + (e._disableBarcodeDetector = !0), + e.scanImage(a, { + scanRegion: m, + canvas: d, + disallowCanvasResizing: f, + alsoTryWithoutScanRegion: h, + }) + ); + throw `Scanner error: ${g}`; + } + })(), + ]); + return n ? q : q.data; + } catch (p) { + if (!m || !h) throw p; + let k = await e.scanImage(a, { + qrEngine: c, + canvas: d, + disallowCanvasResizing: f, + }); + return n ? k : k.data; + } finally { + b || e._postWorkerMessage(c, "close"); + } + } + setGrayscaleWeights(a, b, c, d = !0) { + e._postWorkerMessage(this._qrEnginePromise, "grayscaleWeights", { + red: a, + green: b, + blue: c, + useIntegerApproximation: d, + }); + } + setInversionMode(a) { + e._postWorkerMessage(this._qrEnginePromise, "inversionMode", a); + } + static async createQrEngine(a) { + a && + console.warn( + "Specifying a worker path is not required and not supported anymore." + ); + a = () => + import("./qr-scanner-worker.min.js").then((c) => c.createWorker()); + if ( + !( + !e._disableBarcodeDetector && + "BarcodeDetector" in window && + BarcodeDetector.getSupportedFormats && + (await BarcodeDetector.getSupportedFormats()).includes("qr_code") + ) + ) + return a(); + let b = navigator.userAgentData; + return b && + b.brands.some(({ brand: c }) => /Chromium/i.test(c)) && + /mac ?OS/i.test(b.platform) && + (await b + .getHighEntropyValues(["architecture", "platformVersion"]) + .then( + ({ architecture: c, platformVersion: d }) => + /arm/i.test(c || "arm") && 13 <= parseInt(d || "13") + ) + .catch(() => !0)) + ? a() + : new BarcodeDetector({ formats: ["qr_code"] }); + } + _onPlay() { + this._scanRegion = this._calculateScanRegion(this.$video); + this._updateOverlay(); + this.$overlay && (this.$overlay.style.display = ""); + this._scanFrame(); + } + _onLoadedMetaData() { + this._scanRegion = this._calculateScanRegion(this.$video); + this._updateOverlay(); + } + _onVisibilityChange() { + document.hidden ? this.pause() : this._active && this.start(); + } + _calculateScanRegion(a) { + let b = Math.round((2 / 3) * Math.min(a.videoWidth, a.videoHeight)); + return { + x: Math.round((a.videoWidth - b) / 2), + y: Math.round((a.videoHeight - b) / 2), + width: b, + height: b, + downScaledWidth: this._legacyCanvasSize, + downScaledHeight: this._legacyCanvasSize, + }; + } + _updateOverlay() { + requestAnimationFrame(() => { + if (this.$overlay) { + var a = this.$video, + b = a.videoWidth, + c = a.videoHeight, + d = a.offsetWidth, + f = a.offsetHeight, + h = a.offsetLeft, + m = a.offsetTop, + n = window.getComputedStyle(a), + p = n.objectFit, + k = b / c, + q = d / f; + switch (p) { + case "none": + var g = b; + var l = c; + break; + case "fill": + g = d; + l = f; + break; + default: + ("cover" === p ? k > q : k < q) + ? ((l = f), (g = l * k)) + : ((g = d), (l = g / k)), + "scale-down" === p && + ((g = Math.min(g, b)), (l = Math.min(l, c))); + } + var [v, w] = n.objectPosition.split(" ").map((r, y) => { + const x = parseFloat(r); + return r.endsWith("%") ? ((y ? f - l : d - g) * x) / 100 : x; + }); + n = this._scanRegion.width || b; + q = this._scanRegion.height || c; + p = this._scanRegion.x || 0; + var u = this._scanRegion.y || 0; + k = this.$overlay.style; + k.width = `${(n / b) * g}px`; + k.height = `${(q / c) * l}px`; + k.top = `${m + w + (u / c) * l}px`; + c = /scaleX\(-1\)/.test(a.style.transform); + k.left = `${ + h + (c ? d - v - g : v) + ((c ? b - p - n : p) / b) * g + }px`; + k.transform = a.style.transform; + } + }); + } + static _convertPoints(a, b) { + if (!b) return a; + let c = b.x || 0, + d = b.y || 0, + f = b.width && b.downScaledWidth ? b.width / b.downScaledWidth : 1; + b = b.height && b.downScaledHeight ? b.height / b.downScaledHeight : 1; + for (let h of a) (h.x = h.x * f + c), (h.y = h.y * b + d); + return a; + } + _scanFrame() { + !this._active || + this.$video.paused || + this.$video.ended || + ("requestVideoFrameCallback" in this.$video + ? this.$video.requestVideoFrameCallback.bind(this.$video) + : requestAnimationFrame)(async () => { + if (!(1 >= this.$video.readyState)) { + var a = Date.now() - this._lastScanTimestamp, + b = 1e3 / this._maxScansPerSecond; + a < b && (await new Promise((d) => setTimeout(d, b - a))); + this._lastScanTimestamp = Date.now(); + try { + var c = await e.scanImage(this.$video, { + scanRegion: this._scanRegion, + qrEngine: this._qrEnginePromise, + canvas: this.$canvas, + }); + } catch (d) { + if (!this._active) return; + this._onDecodeError(d); + } + !e._disableBarcodeDetector || + (await this._qrEnginePromise) instanceof Worker || + (this._qrEnginePromise = e.createQrEngine()); + c + ? (this._onDecode + ? this._onDecode(c) + : this._legacyOnDecode && + this._legacyOnDecode(c.data), + this.$codeOutlineHighlight && + (clearTimeout( + this._codeOutlineHighlightRemovalTimeout + ), + (this._codeOutlineHighlightRemovalTimeout = void 0), + this.$codeOutlineHighlight.setAttribute( + "viewBox", + `${this._scanRegion.x || 0} ` + + `${this._scanRegion.y || 0} ` + + `${ + this._scanRegion.width || + this.$video.videoWidth + } ` + + `${ + this._scanRegion.height || + this.$video.videoHeight + }` + ), + this.$codeOutlineHighlight.firstElementChild.setAttribute( + "points", + c.cornerPoints + .map(({ x: d, y: f }) => `${d},${f}`) + .join(" ") + ), + (this.$codeOutlineHighlight.style.display = ""))) + : this.$codeOutlineHighlight && + !this._codeOutlineHighlightRemovalTimeout && + (this._codeOutlineHighlightRemovalTimeout = setTimeout( + () => + (this.$codeOutlineHighlight.style.display = + "none"), + 100 + )); + } + this._scanFrame(); + }); + } + _onDecodeError(a) { + a !== e.NO_QR_CODE_FOUND && console.log(a); + } + async _getCameraStream() { + if (!navigator.mediaDevices) throw "Camera not found."; + let a = /^(environment|user)$/.test(this._preferredCamera) + ? "facingMode" + : "deviceId", + b = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}], + c = b.map((d) => + Object.assign({}, d, { [a]: { exact: this._preferredCamera } }) + ); + for (let d of [...c, ...b]) + try { + let f = await navigator.mediaDevices.getUserMedia({ + video: d, + audio: !1, + }), + h = + this._getFacingMode(f) || + (d.facingMode + ? this._preferredCamera + : "environment" === this._preferredCamera + ? "user" + : "environment"); + return { stream: f, facingMode: h }; + } catch (f) {} + throw "Camera not found."; + } + async _restartVideoStream() { + let a = this._paused; + (await this.pause(!0)) && !a && this._active && (await this.start()); + } + static _stopVideoStream(a) { + for (let b of a.getTracks()) b.stop(), a.removeTrack(b); + } + _setVideoMirror(a) { + this.$video.style.transform = "scaleX(" + ("user" === a ? -1 : 1) + ")"; + } + _getFacingMode(a) { + return (a = a.getVideoTracks()[0]) + ? /rear|back|environment/i.test(a.label) + ? "environment" + : /front|user|face/i.test(a.label) + ? "user" + : null + : null; + } + static _drawToCanvas(a, b, c, d = !1) { + c = c || document.createElement("canvas"); + let f = b && b.x ? b.x : 0, + h = b && b.y ? b.y : 0, + m = b && b.width ? b.width : a.videoWidth || a.width, + n = b && b.height ? b.height : a.videoHeight || a.height; + d || + ((d = b && b.downScaledWidth ? b.downScaledWidth : m), + (b = b && b.downScaledHeight ? b.downScaledHeight : n), + c.width !== d && (c.width = d), + c.height !== b && (c.height = b)); + b = c.getContext("2d", { alpha: !1 }); + b.imageSmoothingEnabled = !1; + b.drawImage(a, f, h, m, n, 0, 0, c.width, c.height); + return [c, b]; + } + static async _loadImage(a) { + if (a instanceof Image) return await e._awaitImageLoad(a), a; + if ( + a instanceof HTMLVideoElement || + a instanceof HTMLCanvasElement || + a instanceof SVGImageElement || + ("OffscreenCanvas" in window && a instanceof OffscreenCanvas) || + ("ImageBitmap" in window && a instanceof ImageBitmap) + ) + return a; + if ( + a instanceof File || + a instanceof Blob || + a instanceof URL || + "string" === typeof a + ) { + let b = new Image(); + b.src = + a instanceof File || a instanceof Blob + ? URL.createObjectURL(a) + : a.toString(); + try { + return await e._awaitImageLoad(b), b; + } finally { + (a instanceof File || a instanceof Blob) && + URL.revokeObjectURL(b.src); + } + } else throw "Unsupported image type."; + } + static async _awaitImageLoad(a) { + (a.complete && 0 !== a.naturalWidth) || + (await new Promise((b, c) => { + let d = (f) => { + a.removeEventListener("load", d); + a.removeEventListener("error", d); + f instanceof ErrorEvent ? c("Image load error") : b(); + }; + a.addEventListener("load", d); + a.addEventListener("error", d); + })); + } + static async _postWorkerMessage(a, b, c, d) { + return e._postWorkerMessageSync(await a, b, c, d); + } + static _postWorkerMessageSync(a, b, c, d) { + if (!(a instanceof Worker)) return -1; + let f = e._workerMessageId++; + a.postMessage({ id: f, type: b, data: c }, d); + return f; + } + } + e.DEFAULT_CANVAS_SIZE = 400; + e.NO_QR_CODE_FOUND = "No QR code found"; + e._disableBarcodeDetector = !1; + e._workerMessageId = 0; + return e; +}); +//# sourceMappingURL=qr-scanner.umd.min.js.map diff --git a/hackathon_site/event/static/event/styles/scss/styles.scss b/hackathon_site/event/static/event/styles/scss/styles.scss index 3cc320a..7513840 100644 --- a/hackathon_site/event/static/event/styles/scss/styles.scss +++ b/hackathon_site/event/static/event/styles/scss/styles.scss @@ -39,10 +39,18 @@ h3 { text-transform: uppercase; } +h1, +h2, +h3, +h4, +h5 { + font-family: font(header); +} + body { - font-family: font(body), sans-serif; - font-size: 18px; + font-size: size(lg); color: color(font); + font-family: font(body); } strong { @@ -50,111 +58,147 @@ strong { } // End of general HTML stuff -// Navbar stuff -.logoNav100 { - height: 100%; - padding: 5px 0; +// Colors +.backgroundColor { + // Change the background colors of each page here! + &Landing { + background-color: color(backgroundColor); + } + &FormBase { + background-color: color(backgroundColor); + } + &Dashboard { + background-color: color(backgroundColor); + } + + &Landing, + &FormBase, + &Dashboard { + min-height: 100vh; + } } -.logoNav { - height: 200px; - transition: height 200ms linear; - padding: 10px 0; +.primaryColor { + background-color: color(primary) !important; +} - &Scrolled { - padding: 5px 0; - height: 100%; - } +.secondaryColor { + background-color: color(secondary) !important; +} - &Side { - padding: 30px; - height: 150px; +.primaryText { + color: color(primary) !important; +} - a, - img { - height: 100% !important; - padding: 0 !important; +.secondaryText { + color: color(secondary) !important; +} - &:hover { - background-color: transparent !important; - } - } - } +.errorText { + color: color(error); +} - @include responsive(lg-down) { - height: 64px; - padding: 5px 0; +.successText { + color: color(success); +} + +.colorBtn { + font-weight: 600 !important; + text-align: center; + text-transform: uppercase; + color: color(font) !important; + background-color: color(secondary) !important; + + &:hover { + background-color: color(secondaryOnHover) !important; } +} - @include responsive(md-down) { - height: 100%; +.redBtn { + font-weight: bold !important; + text-align: center; + text-transform: uppercase; + color: color(white) !important; + background-color: color(error) !important; + + &:hover { + background-color: color(errorOnHover) !important; } } +.active { + color: color(secondary); +} +// End of colors -.sidenav { - background: linear-gradient(180deg, #1a093e 30%, #2f1e52 60%, #4b3676 80%); +// Formatting stuff +.flexColCenter { + @include flexPosition($alIte: center, $dir: column); +} - li { - & > a { - color: #fff !important; - } +.longText { + padding-right: 200px; - & > .colorBtn { - color: #000 !important; - } + @include responsive(sm-down) { + padding-right: 0; } +} - li > a:hover, - nav ul a:hover { - background-color: rgba(0, 0, 0, 0.25); +.hoverLink { + font-weight: 600; + + &:hover { + text-decoration: underline; } } -.navbar { - position: sticky; - top: 0; - background-color: transparent; - box-shadow: none; - transition: background-color 200ms linear, top 200ms ease-in-out; - z-index: 998; +.rowMarginPadding { + margin-left: 0 !important; + margin-right: 0 !important; + padding: 0 !important; +} - &Scrolled { - background-color: rgba(39, 22, 72, 0.9); - box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), - 0 1px 5px 0 rgba(0, 0, 0, 0.2); +.formPadding { + @include responsive(sm-down) { + padding: 0 !important; } } -// End of navbar stuff +// End of formatting stuff -// Landing section -#landing { - position: relative; - z-index: 2; +// Navbar stuff +.logoNav { + height: 100%; + padding: 5px; } -.purpleLanding { - background-image: url("../../images/purple_landing.svg"); - background-repeat: no-repeat; - background-size: auto 100%; - background-position: right top; - padding: 30px 0 50px; - margin-top: -64px; +.logoNavSide { + padding: 30px; + height: 150px; - @media only screen and (min-width: 2600px) { - background-position: left top; - } + a, + img { + height: 100% !important; + padding: 0 !important; - @include responsive(sm-down) { - background-image: url("../../images/purple_landing_mobile.svg"); - background-position: center bottom; - padding: 0 0 220px; - margin-top: -56px; + &:hover { + background-color: transparent !important; + } } } +.navbar { + position: sticky; + top: 0; + z-index: 1100; +} + +.sidenav-trigger { + margin-left: 0 !important; +} +// End of navbar stuff + // Landing section .landingImg { - @include flexPosition($jusCon: center); + @include flexPosition($jusCon: center, $alIte: center); img { width: 100%; @@ -167,30 +211,13 @@ strong { // End of landing section // "About" section -#about { - position: relative; - z-index: 2; - - @include responsive(sm-down) { - margin-top: -100px; +#aboutText { + p { + margin: 20px 0; } } - -.aboutText p { - margin: 20px 0; -} - -.skylineImg { - position: absolute; - bottom: -5px; - right: 0; - width: 100%; -} - .countdown { - display: flex; - align-items: center; - justify-content: center; + @include flexPosition($jusCon: center, $alIte: center); &Days { margin-top: 20px; @@ -204,14 +231,11 @@ strong { font-weight: bold; font-size: 106px; text-align: center; - color: color(font); - font-family: font(header), sans-serif; - display: flex; - flex-direction: column; background-color: color(secondary); @include flexPosition($dir: column); margin: 0 5px; padding: 0 !important; + font-family: font(header); } &WhiteLine { @@ -234,7 +258,7 @@ strong { margin: 10px 0 30px; p { - font-size: 20px; + font-size: size(xl); margin: 0; } @@ -249,7 +273,7 @@ strong { @include responsive(md-down) { p { - font-size: 18px; + font-size: size(lg); } &Time { width: 70px; @@ -287,10 +311,9 @@ strong { // End of "Hackathon stats" section // "FAQ" section - .collapsible .active { // color: inherit; - color: color(white); + color: color(primary); } .active .collapsible-header { @@ -304,14 +327,8 @@ strong { box-shadow: none; margin: 0; } - .collapsible-header { background-color: transparent; - color: color(white); -} - -.collapsible-body { - color: color(light); } // End of "FAQ" section @@ -341,22 +358,6 @@ strong { } // End of video section -// Past projects section -#past-projects { - display: flex; - justify-content: center; - padding-bottom: 40px; - - a { - color: #dad1e9; - } - - a:hover { - text-decoration: underline; - } -} -// End of past projects section - // Carousel section .carousel { height: 333px !important; @@ -387,7 +388,7 @@ strong { width: 100%; // Manipulate the filter to change the color of the icon. // e.g. invert(0) is black and invert(1) is white - filter: invert(0); + filter: invert(1); } &:hover { @@ -397,67 +398,11 @@ strong { } // End of section -// Colors -.primaryColor { - background-color: color(primary); -} - -.secondaryColor { - background-color: color(secondary) !important; -} - -.primaryText { - color: color(primary); -} - -.secondaryText { - color: color(secondary); -} - -.darkText { - color: color(font); -} - -.lightText { - color: color(light); -} - -.colorBtn { - font-weight: bold !important; - text-align: center; - text-transform: uppercase; - color: color(font) !important; - font-family: font(header), sans-serif; - background-color: color(secondary); - - &:hover, - &:focus { - background-color: #d2a300 !important; - } -} - -.redBtn { - font-weight: bold !important; - text-align: center; - text-transform: uppercase; - color: #f3c7c7 !important; - background-color: #b40000 !important; - - &:hover { - background-color: #870000 !important; - } -} - -.active { - color: color(secondary); -} -// End of colors - // Login stuff .login { &Div { display: flex; - background-color: #fff; + background-color: color(white); border-top: solid 10px color(secondary); border-radius: 3px 3px 0 0; padding: 50px 30px 30px; @@ -479,10 +424,9 @@ strong { } &Link { - display: flex; - justify-content: center; + @include flexPosition($jusCon: center, $alIte: center); margin: 10px 0 15px; - font-size: size(md); + font-size: size(sm); padding: 0 !important; i { @@ -497,23 +441,17 @@ strong { text-align: center; text-transform: uppercase; margin: 0 auto 30px; - color: color(dark); font-size: 50px; } &H2 { font-weight: normal; - color: color(dark); font-size: 30px; margin: 0 0 15px; } &H3 { font-weight: normal; - color: color(dark); - font-size: 24px; - text-transform: initial; - margin: 0 0 10px; } @include responsive(md-down) { @@ -525,7 +463,7 @@ strong { font-size: 24px; } &H3 { - font-size: 20px; + font-size: size(xl); } } @@ -534,19 +472,15 @@ strong { } &FieldError { - font-size: 12px; - color: #f44336; + font-size: size(xs); + color: color(error); display: block; } &Error { - color: #f44336; + color: color(error); display: block; } - - &AddTopMargin { - margin-top: 9px; - } } .input-field { @@ -580,9 +514,8 @@ strong { // Dashboard stuff .borderTopDiv { - display: flex; - flex-direction: column; - background-color: #fff; + background-color: color(white); + @include flexPosition($dir: column); border-top: solid 10px color(secondary); border-radius: 3px 3px 0 0; padding: 40px 30px 30px; @@ -605,83 +538,38 @@ strong { margin: 5px 0 20px; } } -// End of dashboard stuff -// Background colors -.ombreBackgroundLight { - position: relative; - background: color(secondary); - background: linear-gradient( - 180deg, - color(secondary) 25%, - rgba(230, 186, 98, 1) 40%, - rgba(139, 83, 102, 1) 60%, - #3e2c64 85% - ); +.banner { + width: 100%; + padding: 10px; + text-align: center; + border-radius: 5px; + border: 2px; + margin: 10px 0; - @include responsive(md-down) { - background: linear-gradient( - 180deg, - color(secondary) 15%, - rgba(230, 186, 98, 1) 20%, - rgba(139, 83, 102, 1) 40%, - #3e2c64 65% - ); + &success { + border: 1px solid color(success); + background-color: color(successBackground); + color: color(success); } -} - -.ombreBackgroundDark1 { - background: linear-gradient(180deg, color(dark) 40%, #0c0618 90%); - margin-top: -10px; -} -.ombreBackgroundDark2 { - background: linear-gradient(180deg, #0d061a 2%, #0a011a 45%); - margin-top: -10px; -} - -.ombreBackgroundDark3 { - background: linear-gradient(180deg, #271648 60%, #0c0618 90%); - min-height: 100vh; -} -// End of background colors - -// Formatting stuff -.flexColCenter { - display: flex; - flex-direction: column; - align-items: center; -} - -.longText { - padding-right: 200px; - - @include responsive(sm-down) { - padding-right: 0; + &error { + border: 1px solid color(error); + background-color: color(errorBackground); + color: color(error); } -} - -.hoverLink { - font-weight: 600; - &:hover { - text-decoration: underline; + &info { + border: 1px solid color(info); + background-color: color(infoBackground); + color: color(info); } -} - -.rowMarginPadding { - margin-left: 0 !important; - margin-right: 0 !important; - padding: 0 !important; -} -.formPadding { - @include responsive(sm-down) { - padding: 0 !important; + &warning { + border: 1px solid color(warning); + background-color: color(warningBackground); + color: color(warning); } } -.marginTop0 { - margin-top: 0 !important; -} -// End of formatting stuff +// End of dashboard stuff diff --git a/hackathon_site/event/test_api.py b/hackathon_site/event/test_api.py index 404b87c..75d8d04 100644 --- a/hackathon_site/event/test_api.py +++ b/hackathon_site/event/test_api.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib.auth.models import Group from django.urls import reverse from rest_framework import status @@ -159,17 +160,6 @@ def check_can_leave_cancelled_or_returned(self): self.assertNotEqual(old_team.pk, self.user.profile.team.pk) self.assertFalse(Team.objects.filter(team_code=old_team.team_code).exists()) - def check_can_leave_cancelled(self): - old_team = self.profile.team - sample_team = self._make_event_team(self_users=False, num_users=2) - response = self.client.post(self._build_view(sample_team.team_code)) - self.user.refresh_from_db() - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["id"], self.user.profile.team.pk) - self.assertNotEqual(old_team.pk, self.user.profile.team.pk) - self.assertFalse(Team.objects.filter(team_code=old_team.team_code).exists()) - def test_cannot_leave_with_order(self): """ check user cannot join another team when there @@ -570,7 +560,7 @@ def test_user_not_logged_in(self): self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_user_can_have_profile(self): - self._review(application=self._apply_as_user(self.user)) + self._review(application=self._apply_as_user(self.user, rsvp=True)) self._login() response = self.client.post(self.view, self.request_body) data = response.json() @@ -587,7 +577,7 @@ def test_user_can_have_profile(self): self.assertEqual(self.expected_response, data) def test_not_including_required_fields(self): - self._review(application=self._apply_as_user(self.user)) + self._review(application=self._apply_as_user(self.user, rsvp=True)) self._login() response = self.client.post(self.view, {"e_signature": "user signature",}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -595,7 +585,7 @@ def test_not_including_required_fields(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_acknowledge_rules_is_false(self): - self._review(application=self._apply_as_user(self.user)) + self._review(application=self._apply_as_user(self.user, rsvp=True)) self._login() response = self.client.post( self.view, {"e_signature": "user signature", "acknowledge_rules": False,}, @@ -607,7 +597,7 @@ def test_acknowledge_rules_is_false(self): ) def test_e_signature_is_empty(self): - self._review(application=self._apply_as_user(self.user)) + self._review(application=self._apply_as_user(self.user, rsvp=True)) self._login() response = self.client.post( self.view, {"e_signature": "", "acknowledge_rules": True,}, @@ -636,25 +626,53 @@ def test_user_has_not_completed_application(self): "User has not completed their application to the hackathon. Please do so to access the Hardware Signout Site", ) - def test_user_has_not_been_reviewed(self): + def test_user_has_not_rsvp(self): self._apply_as_user(self.user) self._login() response = self.client.post(self.view, self.request_body) data = response.json() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + data[0], + "User has not RSVP'd to the hackathon. Please RSVP to access the Hardware Signout Site", + ) + + def test_user_has_not_been_reviewed(self): + self._apply_as_user(self.user, rsvp=True) + self._login() + response = self.client.post(self.view, self.request_body) + data = response.json() + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + data[0], + "User has not been reviewed yet, Hardware Signout Site cannot be accessed until reviewed", + ) + + def test_user_has_been_reviewed_but_not_sent(self): + self._review( + application=self._apply_as_user(self.user, rsvp=True), + decision_sent_date=None, + ) + self._login() + response = self.client.post(self.view, self.request_body) + data = response.json() + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( data[0], "User has not been reviewed yet, Hardware Signout Site cannot be accessed until reviewed", ) def test_user_review_rejected(self): - self._review(application=self._apply_as_user(self.user), status="Rejected") + self._review( + application=self._apply_as_user(self.user, rsvp=True), status="Rejected" + ) self._login() response = self.client.post(self.view, self.request_body) data = response.json() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( - data[0], "User has not been accepted to participate in NewHacks" + data[0], + f"User has not been accepted to participate in {settings.HACKATHON_NAME}", ) @@ -860,8 +878,10 @@ def setUp(self, **kwargs): self.permissions = Permission.objects.filter( Q(content_type__app_label="event", codename="view_team") + | Q(content_type__app_label="event", codename="change_team") | Q(content_type__app_label="event", codename="delete_team"), ) + super().setUp() def _build_view(self, team_code): @@ -914,6 +934,19 @@ def test_team_delete_with_returned_orders(self): response = self.client.delete(self._build_view(self.team3.team_code)) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + def test_team_patch_not_login(self): + response = self.client.patch( + self._build_view("56ABD"), data={"project_description": "New description"} + ) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_team_patch_no_permissions(self): + self._login() + response = self.client.patch( + self._build_view("56ABD"), data={"project_description": "New description"} + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + class TeamOrderDetailViewTestCase(SetupUserMixin, APITestCase): def setUp(self): diff --git a/hackathon_site/event/urls.py b/hackathon_site/event/urls.py index 2482c8f..9b1f7ed 100644 --- a/hackathon_site/event/urls.py +++ b/hackathon_site/event/urls.py @@ -1,6 +1,6 @@ from django.contrib.auth import views as auth_views from django.urls import path, reverse_lazy -from event.views import IndexView, DashboardView +from event.views import IndexView, DashboardView, QRScannerView from event.forms import ( PasswordChangeForm, PasswordResetForm, @@ -21,6 +21,7 @@ ), path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout",), path("dashboard/", DashboardView.as_view(), name="dashboard"), + path("dashboard/qrscan/", QRScannerView.as_view(), name="qr-scanner"), path( "accounts/change_password/", auth_views.PasswordChangeView.as_view( diff --git a/hackathon_site/event/views.py b/hackathon_site/event/views.py index f457f18..1891ced 100644 --- a/hackathon_site/event/views.py +++ b/hackathon_site/event/views.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.shortcuts import redirect @@ -7,20 +8,23 @@ from django.views.generic.base import TemplateView from django.views.generic.edit import FormView - from django.conf import settings from django_filters import rest_framework as filters from rest_framework import generics, mixins from rest_framework.filters import SearchFilter - -from hackathon_site.utils import is_registration_open -from registration.forms import JoinTeamForm -from registration.models import Team as RegistrationTeam - - -from event.models import Team as EventTeam +from pprint import pprint +from hackathon_site.utils import ( + is_registration_open, + is_hackathon_happening, + NoEventOccurringException, + get_curr_sign_in_time, +) +from registration.forms import JoinTeamForm, SignInForm +from registration.models import Team as RegistrationTeam, User, Application + +from event.models import Team as EventTeam, UserActivity from event.serializers import TeamSerializer from event.api_filters import TeamFilter from event.permissions import FullDjangoModelPermissions @@ -47,10 +51,14 @@ def get_context_data(self, **kwargs): class DashboardView(LoginRequiredMixin, FormView): - template_name = "event/dashboard_base.html" # Form submits should take the user back to the dashboard success_url = reverse_lazy("event:dashboard") + def get_template_names(self): + if self.request.user.is_staff: + return "event/dashboard_admin.html" + return "event/dashboard_base.html" + def get_form(self, form_class=None): """ The dashboard can have different forms, but not at the same time: @@ -117,6 +125,19 @@ def get_context_data(self, **kwargs): review = self.request.user.application.review context["review"] = review + if settings.RSVP: + context[ + "rsvp_passed" + ] = _now().date() > review.decision_sent_date + timedelta( + days=settings.RSVP_DAYS + ) + rsvp_deadline = datetime.combine( + review.decision_sent_date + timedelta(days=settings.RSVP_DAYS), + datetime.max.time(), # 11:59PM + ) + context["rsvp_deadline"] = settings.TZ_INFO.localize( + rsvp_deadline + ).strftime("%B %-d, %Y, %-I:%M %p %Z") else: context["review"] = None @@ -134,6 +155,12 @@ def get_context_data(self, **kwargs): and self.request.user.application.review.decision_sent_date is None ): context["status"] = "Application Complete" + elif ( + hasattr(self.request.user.application, "review") + and self.request.user.application.review.status == "Accepted" + and self.request.user.application.rsvp is None + ): + context["status"] = "Accepted, awaiting RSVP" elif ( hasattr(self.request.user.application, "review") and self.request.user.application.review.status == "Waitlisted" @@ -144,6 +171,10 @@ def get_context_data(self, **kwargs): and self.request.user.application.review.status == "Rejected" ): context["status"] = "Rejected" + elif self.request.user.application.rsvp: + context["status"] = "Will Attend (Accepted)" + elif not self.request.user.application.rsvp: + context["status"] = "Cannot Attend (Declined)" else: context["status"] = "Unknown" @@ -165,6 +196,103 @@ def post(self, request, *args, **kwargs): return super().post(request, *args, **kwargs) +class QRScannerView(LoginRequiredMixin, FormView): + success_url = reverse_lazy("event:qr-scanner") + + def get_template_names(self): + if self.request.user.is_staff: + return "event/admin_qr_scanner.html" + return Exception("You do not have permission to view this page.") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + if isinstance(context["form"], SignInForm): + context["sign_in_form"] = context["form"] + + # Get the total number of users who have signed in in each event + # Find the all rows that the sign in event is not null + # Then count the number of rows for each event + context["sign_in_counts"] = { + event: UserActivity.objects.filter(**{f"{event}__isnull": False}).count() + for event in ["breakfast2", "dinner1", "lunch1", "lunch2", "sign_in"] + } + + return context + + def get_form(self, form_class=None): + if form_class is not None: + return form_class(**self.get_form_kwargs()) + + if is_hackathon_happening(): + return SignInForm(**self.get_form_kwargs()) + + return None + + def form_valid(self, form): + if isinstance(form, SignInForm): + try: + user = User.objects.get(email__exact=form.cleaned_data["email"]) + sign_in_event = get_curr_sign_in_time(False, True) + now = datetime.now().replace(tzinfo=settings.TZ_INFO) + application = Application.objects.get(user__exact=user) + + try: + user_activity = UserActivity.objects.get(user__exact=user) + if getattr(user_activity, sign_in_event, None) is not None: + messages.error( + self.request, + f'User {form.cleaned_data["email"]} has already signed in!', + ) + return redirect(self.get_success_url()) + else: + setattr(user_activity, sign_in_event, now) + user_activity.save() + except UserActivity.DoesNotExist: + sign_in_obj = {} + sign_in_obj[sign_in_event] = now + UserActivity.objects.create(user=user, **sign_in_obj) + + # Return the info on a new line + # TODO: use once these fields are added to Application Model + # if application.specific_dietary_requirement != "": + # return_string = ( + # (user.first_name).capitalize() + # + " successfully signed in. 👕T-shirt: " + # + application.tshirt_size + # + " 🍉 Dietary Restrictions: " + # + application.dietary_restrictions + # + " ⭐️Special Diet Requirement:" + # + application.specific_dietary_requirement + # ) + # else: + # return_string = ( + # (user.first_name).capitalize() + # + " successfully signed in. 👕T-shirt: " + # + application.tshirt_size + # + " 🍉 Dietary Restrictions: " + # + application.dietary_restrictions + # ) + return_string = ( + user.first_name + ).capitalize() + " successfully signed in." + + messages.success(self.request, return_string) + except NoEventOccurringException as e: + messages.info(self.request, str(e)) + except Exception as e: + messages.error( + self.request, + f'User {form.cleaned_data["email"]} could not sign in due to: {str(e)}', + ) + + return redirect(self.get_success_url()) + + @transaction.atomic + def post(self, request, *args, **kwargs): + return super().post(request, *args, **kwargs) + + class TeamListView(mixins.ListModelMixin, generics.GenericAPIView): queryset = EventTeam.objects.all() serializer_class = TeamSerializer diff --git a/hackathon_site/hackathon_site/jinja2.py b/hackathon_site/hackathon_site/jinja2.py index b874c68..4ab2243 100644 --- a/hackathon_site/hackathon_site/jinja2.py +++ b/hackathon_site/hackathon_site/jinja2.py @@ -1,10 +1,16 @@ +from django.contrib import messages from django.contrib.staticfiles.storage import staticfiles_storage from django.conf import settings from django.urls import reverse from django.utils.timezone import template_localtime from jinja2 import Environment -from hackathon_site.utils import is_registration_open +from hackathon_site.utils import ( + is_registration_open, + get_sign_in_interval, + get_curr_sign_in_time, +) + # In testing, nothing in this file can be overwritten using the # @patch or @override_settings decorators, because it is evaluated before @@ -21,6 +27,9 @@ def environment(**options): "url": reverse, "localtime": template_localtime, "is_registration_open": is_registration_open, + "get_messages": messages.get_messages, + "get_sign_in_interval": get_sign_in_interval, + "get_curr_sign_in_time": get_curr_sign_in_time, # Variables "hackathon_name": settings.HACKATHON_NAME, "hss_url": settings.HSS_URL, @@ -36,6 +45,8 @@ def environment(**options): "chat_room_name": settings.CHAT_ROOM[0], "chat_room_link": settings.CHAT_ROOM[1], "using_teams": settings.TEAMS, + "using_rsvp": settings.RSVP, + "sign_in_times": settings.SIGN_IN_TIMES, } ) return env diff --git a/hackathon_site/hackathon_site/settings/__init__.py b/hackathon_site/hackathon_site/settings/__init__.py index dd3e7ee..c9064d1 100644 --- a/hackathon_site/hackathon_site/settings/__init__.py +++ b/hackathon_site/hackathon_site/settings/__init__.py @@ -112,6 +112,7 @@ "django_filters", "client_side_image_cropping", "captcha", + "qrcode", "dashboard", "registration", "event", @@ -166,6 +167,7 @@ LOGIN_REDIRECT_URL = reverse_lazy("event:dashboard") LOGOUT_REDIRECT_URL = reverse_lazy("event:index") +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases @@ -321,36 +323,7 @@ HARDWARE_SIGN_OUT_START_DATE = datetime(2023, 10, 28, 23, 59, 0, tzinfo=TZ_INFO) HARDWARE_SIGN_OUT_END_DATE = EVENT_END_DATE -# Registration user requirements -MINIMUM_AGE = 18 - -# Registration settings -ACCOUNT_ACTIVATION_DAYS = 7 - -# Team requirements -MIN_MEMBERS = 2 -MAX_MEMBERS = 4 - -# The time at which waitlisted people will start being accepted into -# the event. This usually happens an hour or two after the start of -# the event. -WAITLISTED_ACCEPTANCE_START_TIME = EVENT_START_DATE + timedelta(hours=1) - -# The date at which applications will be reviewed at the latest. -FINAL_REVIEW_RESPONSE_DATE = REGISTRATION_CLOSE_DATE + timedelta(days=7) - -# Links -PARTICIPANT_PACKAGE_LINK = "https://docs.google.com/document/d/1JvPlvxwMze9dqjv_5PIUVu8bHTiEZz8AtviHRMEsWQQ/edit?usp=sharing" - -# Note this is in the form (chat_room_name, chat_room_link) -# Chat room name is such as the following: Slack, Discord -CHAT_ROOM = ("Discord", "https://discord.gg/BQg4Upq3pm") - -# Enable/Disable certain Features -TEAMS = True - -# HSS Testing -TEST_USER_GROUP = "HSS Test Users" +RSVP_DAYS = 5 # sign in times must be between EVENT_START_DATE and EVENT_END_DATE and in chronological order # the number of sign in times MUST MATCH the number of columns in UserActivityTable @@ -382,3 +355,35 @@ "time": datetime(2023, 2, 19, 13, 0, 0, tzinfo=TZ_INFO), # Oct 11th @ 12pm }, ] + +# Registration user requirements +MINIMUM_AGE = 18 + +# Registration settings +ACCOUNT_ACTIVATION_DAYS = 7 + +# Team requirements +MIN_MEMBERS = 2 +MAX_MEMBERS = 4 + +# The time at which waitlisted people will start being accepted into +# the event. This usually happens an hour or two after the start of +# the event. +WAITLISTED_ACCEPTANCE_START_TIME = EVENT_START_DATE + timedelta(hours=1) + +# The date at which applications will be reviewed at the latest. +FINAL_REVIEW_RESPONSE_DATE = REGISTRATION_CLOSE_DATE + timedelta(days=7) + +# Links +PARTICIPANT_PACKAGE_LINK = "https://docs.google.com/document/d/1JvPlvxwMze9dqjv_5PIUVu8bHTiEZz8AtviHRMEsWQQ/edit?usp=sharing" + +# Note this is in the form (chat_room_name, chat_room_link) +# Chat room name is such as the following: Slack, Discord +CHAT_ROOM = ("Discord", "https://discord.gg/BQg4Upq3pm") + +# Enable/Disable certain Features +TEAMS = True +RSVP = True + +# HSS Testing +TEST_USER_GROUP = "HSS Test Users" diff --git a/hackathon_site/hackathon_site/utils.py b/hackathon_site/hackathon_site/utils.py index 209f65b..0db0a8c 100644 --- a/hackathon_site/hackathon_site/utils.py +++ b/hackathon_site/hackathon_site/utils.py @@ -1,8 +1,16 @@ from datetime import datetime +from dateutil.relativedelta import relativedelta from django.conf import settings +class NoEventOccurringException(Exception): + def __init__(self): + super().__init__( + "There is currently no event happening for the user to sign in." + ) + + def is_registration_open(): """ Determine whether registration is currently open @@ -15,3 +23,35 @@ def is_registration_open(): # is configured to match TIME_ZONE. We then make the datetime object timezone-aware. now = datetime.now().replace(tzinfo=settings.TZ_INFO) return settings.REGISTRATION_OPEN_DATE <= now < settings.REGISTRATION_CLOSE_DATE + + +def is_hackathon_happening(): + if settings.IN_TESTING or settings.DEBUG: + return True + + now = datetime.now().replace(tzinfo=settings.TZ_INFO) + return settings.EVENT_START_DATE <= now < settings.EVENT_END_DATE + + +def get_curr_sign_in_time(use_description=False, return_exception=False): + now = datetime.now().replace(tzinfo=settings.TZ_INFO) + for event in settings.SIGN_IN_TIMES: + start_interval = event["time"] - relativedelta(hours=1) + end_interval = event["time"] + relativedelta(hours=1) + if start_interval <= now <= end_interval: + return event["description"] if use_description else event["name"] + + if use_description or not return_exception: + return None + raise NoEventOccurringException() + + +# assumes interval won't overlap between different months or years +def get_sign_in_interval(time): + start_interval = time - relativedelta(hours=1) + end_interval = time + relativedelta(hours=1) + + if start_interval.day == end_interval.day: + return f"{start_interval.strftime('%H:%M')} - {end_interval.strftime('%H:%M')}, {start_interval.strftime('%b %d')}" + + return f"{start_interval.strftime('%H:%M, %b %d')} - {end_interval.strftime('%H:%M, %b %d')}" diff --git a/hackathon_site/registration/admin.py b/hackathon_site/registration/admin.py index 6dff7ea..f946ff4 100644 --- a/hackathon_site/registration/admin.py +++ b/hackathon_site/registration/admin.py @@ -57,6 +57,7 @@ class Meta: "email_agree", "resume_sharing", "review__status", + "rsvp", "created_at", "updated_at", ) @@ -84,6 +85,7 @@ class Meta: "email_agree", "resume_sharing", "review__status", + "rsvp", "created_at", "updated_at", ) @@ -104,6 +106,7 @@ def get_export_headers(self): "study_level", "graduation_year", "review_status", + "rsvp", "program", "resume", "linkedin", @@ -113,7 +116,6 @@ def get_export_headers(self): "logistics_agree", "email_agree", "resume_sharing", - "review__status", "created_at", "updated_at", ] diff --git a/hackathon_site/registration/forms.py b/hackathon_site/registration/forms.py index b914bb4..a234c6b 100644 --- a/hackathon_site/registration/forms.py +++ b/hackathon_site/registration/forms.py @@ -8,9 +8,10 @@ from django_registration import validators from django.conf import settings -from hackathon_site.utils import is_registration_open +from hackathon_site.utils import is_registration_open, is_hackathon_happening from registration.models import Application, Team, User from registration.widgets import MaterialFileInput +from review.models import Review class SignUpForm(UserCreationForm): @@ -247,3 +248,50 @@ def clean_team_code(self): raise forms.ValidationError(_(f"Team {team_code} is full.")) return team_code + + +class SignInForm(forms.Form): + email = forms.EmailField() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.label_suffix = "" + self.error_css_class = "invalid" + + def clean(self): + if not is_hackathon_happening(): + raise forms.ValidationError( + _("You cannot sign in outside of the hackathon period."), + code="invalid_sign_in_time", + ) + + return super().clean() + + def clean_email(self): + email = self.cleaned_data["email"] + + try: + user = User.objects.get(email__exact=email) + application = Application.objects.get(user__exact=user) + review = Review.objects.get(application__exact=application) + if review.status == "Accepted": + if settings.RSVP and application.rsvp is None: + raise forms.ValidationError( + _(f"User {email} has not RSVP'd to the hackathon") + ) + else: + raise forms.ValidationError( + _( + f"User {email} has not been Accepted to attend {settings.HACKATHON_NAME}" + ) + ) + except User.DoesNotExist: + raise forms.ValidationError(_(f"User {email} does not exist.")) + except Application.DoesNotExist: + raise forms.ValidationError( + _(f"User {email} has not applied to {settings.HACKATHON_NAME}") + ) + except Exception as e: + raise e + + return email diff --git a/hackathon_site/registration/jinja2/registration/application.html b/hackathon_site/registration/jinja2/registration/application.html index 775ee4a..eaeb47f 100644 --- a/hackathon_site/registration/jinja2/registration/application.html +++ b/hackathon_site/registration/jinja2/registration/application.html @@ -174,4 +174,4 @@

    Questions

    {% endif %} }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hackathon_site/registration/migrations/0012_application_rsvp.py b/hackathon_site/registration/migrations/0012_application_rsvp.py new file mode 100644 index 0000000..dc7a474 --- /dev/null +++ b/hackathon_site/registration/migrations/0012_application_rsvp.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.15 on 2023-10-15 22:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registration", "0011_auto_20230627_2303"), + ] + + operations = [ + migrations.AddField( + model_name="application", name="rsvp", field=models.BooleanField(null=True), + ), + ] diff --git a/hackathon_site/registration/models.py b/hackathon_site/registration/models.py index c914be7..b610545 100644 --- a/hackathon_site/registration/models.py +++ b/hackathon_site/registration/models.py @@ -186,6 +186,7 @@ class Application(models.Model): null=True, default=False, ) + rsvp = models.BooleanField(null=True) created_at = models.DateTimeField(auto_now_add=True, null=False) updated_at = models.DateTimeField(auto_now=True, null=False) diff --git a/hackathon_site/registration/urls.py b/hackathon_site/registration/urls.py index 001a407..2be8472 100644 --- a/hackathon_site/registration/urls.py +++ b/hackathon_site/registration/urls.py @@ -24,4 +24,5 @@ ), path("application/", views.ApplicationView.as_view(), name="application"), path("leave_team/", views.LeaveTeamView.as_view(), name="leave-team"), + re_path("rsvp/(?Pyes|no)/$", views.RSVPView.as_view(), name="rsvp"), ] diff --git a/hackathon_site/registration/views.py b/hackathon_site/registration/views.py index 6255e08..574a801 100644 --- a/hackathon_site/registration/views.py +++ b/hackathon_site/registration/views.py @@ -209,3 +209,72 @@ def get(self, request, *args, **kwargs): response["Content-Encoding"] = encoding return response + + +class RSVPView(LoginRequiredMixin, View): + """ + This page checks if the RSVP deadline has passed and then does one of + the following: + 1. If the deadline has passed, redirect to the dashboard + 2. Otherwise if their decision was a 'yes', we note that and + create a profile for them + 3. Otherwise if their decision was a 'no', we note that and remove + their profile and team if they had one. + + Finally we redirect to the dashboard. + """ + + def get(self, request, *args, **kwargs): + decision = kwargs["rsvp"] + + # Check for nulls + user = self.request.user + if not hasattr(user, "application"): + return HttpResponseBadRequest( + "You have not submitted an application.".encode(encoding="utf-8") + ) + + application = user.application + if not hasattr(application, "review"): + return HttpResponseBadRequest( + "Your application has not yet been reviewed.".encode(encoding="utf-8") + ) + + review = application.review + if not review.status == "Accepted": + return HttpResponseBadRequest( + "You cannot RSVP since your application has not yet been accepted.".encode( + encoding="utf-8" + ) + ) + + # Check if RSVP deadline has passed + if _now().date() > review.decision_sent_date + timedelta( + days=settings.RSVP_DAYS + ): + return redirect(reverse_lazy("event:dashboard")) + + # Check decision + else: + if decision == "yes" and not application.rsvp: + application.rsvp = True + application.save() + + # TODO: decide whether to create profile when rsvp + # profile = Profile(user=user, team=EventTeam.objects.create()) + # profile.save() + + elif decision == "no" and (application.rsvp or application.rsvp is None): + application.rsvp = False + application.save() + + # Delete the profile + if hasattr(request.user, "profile"): + team = user.profile.team + user.profile.delete() + + # Delete the team if it is empty + if not team.profiles.exists(): + team.delete() + + return redirect(reverse_lazy("event:dashboard")) diff --git a/hackathon_site/requirements.txt b/hackathon_site/requirements.txt index d499015..f3ad7eb 100644 --- a/hackathon_site/requirements.txt +++ b/hackathon_site/requirements.txt @@ -32,6 +32,7 @@ python-dateutil==2.8.2 pyparsing==2.4.7 pytz==2020.1 PyYAML==5.4 +qrcode==7.3.1 redis==3.5.3 regex==2021.4.4 requests==2.25.1 diff --git a/hackathon_site/review/jinja2/review/emails/accepted_email_body.html b/hackathon_site/review/jinja2/review/emails/accepted_email_body.html index 7717d40..e8242d9 100644 --- a/hackathon_site/review/jinja2/review/emails/accepted_email_body.html +++ b/hackathon_site/review/jinja2/review/emails/accepted_email_body.html @@ -2,10 +2,10 @@

    Hello {{ user.first_name|striptags }},

    -

    The {{ hackathon_name }} team has reviewed your application, and we’re excited to welcome you to {{ hackathon_name }}! +

    The {{ hackathon_name }} team has reviewed your application, and we’re excited to welcome you to {{ hackathon_name }}!

    -

    Read our participant package for all the info regarding the event, and join our {{ chat_room_name }}. Stay tuned for more updates regarding detailed event logistics, and we hope to see you soon! If you have questions, read the FAQ on your dashboard or feel free to contact us.

    +

    Read our participant package for all the info regarding the event, and join our {{ chat_room_name }}. Stay tuned for more updates regarding detailed event logistics, and we hope to see you soon! If you have questions, read the FAQ on your dashboard or feel free to contact us.

    Happy hacking,
    The {{ hackathon_name }} Team -

    \ No newline at end of file +

    diff --git a/hackathon_site/review/views.py b/hackathon_site/review/views.py index 3d25ef8..809612c 100644 --- a/hackathon_site/review/views.py +++ b/hackathon_site/review/views.py @@ -61,6 +61,9 @@ def form_valid(self, form, **kwargs): render_to_string_context = { "user": review.application.user, "request": self.request, + "rsvp_deadline": ( + current_date + timedelta(days=settings.RSVP_DAYS) + ).strftime("%B %-d %Y"), } review.application.user.email_user(