From 520ff56041ba43d075a99901b9b2d4256d933771 Mon Sep 17 00:00:00 2001 From: Mustafa Abdulrahman Date: Sun, 1 Oct 2023 18:49:54 -0400 Subject: [PATCH 1/4] Copy scanner code and changes from template repo --- .github/workflows/main.yml | 82 +++ deployment/entrypoint.sh | 2 +- .../dashboard/frontend/src/api/helpers.ts | 1 - .../dashboard/ItemTable/ItemTable.test.tsx | 2 +- hackathon_site/event/admin.py | 25 +- .../event/jinja2/event/admin_qr_scanner.html | 127 ++++ hackathon_site/event/jinja2/event/base.html | 3 +- .../event/jinja2/event/change_password.html | 2 +- .../jinja2/event/change_password_done.html | 2 +- .../event/jinja2/event/dashboard_admin.html | 66 ++ .../event/jinja2/event/dashboard_base.html | 76 ++ .../event/jinja2/event/form_base.html | 2 +- .../event/jinja2/event/landing.html | 2 +- hackathon_site/event/jinja2/event/login.html | 2 +- .../event/migrations/0008_useractivity.py | 28 + hackathon_site/event/models.py | 8 + .../event/static/event/images/landing.svg | 4 + .../static/event/js/qr-scanner-worker.min.js | 106 +++ .../static/event/js/qr-scanner.umd.min.js | 683 ++++++++++++++++++ .../static/event/styles/scss/styles.scss | 435 +++++------ hackathon_site/event/test_api.py | 4 +- hackathon_site/event/urls.py | 3 +- hackathon_site/event/views.py | 81 ++- hackathon_site/hackathon_site/jinja2.py | 12 +- .../hackathon_site/settings/__init__.py | 64 +- hackathon_site/hackathon_site/utils.py | 40 + hackathon_site/registration/forms.py | 49 +- .../jinja2/registration/application.html | 2 +- hackathon_site/requirements.txt | 1 + .../review/emails/accepted_email_body.html | 6 +- 30 files changed, 1591 insertions(+), 329 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 hackathon_site/event/jinja2/event/admin_qr_scanner.html create mode 100644 hackathon_site/event/jinja2/event/dashboard_admin.html create mode 100644 hackathon_site/event/migrations/0008_useractivity.py create mode 100644 hackathon_site/event/static/event/images/landing.svg create mode 100644 hackathon_site/event/static/event/js/qr-scanner-worker.min.js create mode 100644 hackathon_site/event/static/event/js/qr-scanner.umd.min.js 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..cbf7540 --- /dev/null +++ b/hackathon_site/event/jinja2/event/admin_qr_scanner.html @@ -0,0 +1,127 @@ +{% 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 %} +
    +
    + +
    +
    + {% endif %} + +
    + + + + + + + + {% for event in sign_in_times %} + + + + + + {% endfor %} +
    EventTimeSign In Interval
    {{ event.description }} {{ event.time.strftime("%H:%M, %b %d") }} {{ get_sign_in_interval(event.time) }}
    +
    +
    +
    +
    +{% 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..481d6c7 --- /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..5469876 100644 --- a/hackathon_site/event/jinja2/event/dashboard_base.html +++ b/hackathon_site/event/jinja2/event/dashboard_base.html @@ -37,6 +37,10 @@

    Complete your application

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

    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. .

    + +

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


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

    You've been waitlisted for {{ hackathon_name }}

    @@ -69,6 +73,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 }})

    +
      + {% for application in application.team.applications.all() %} +
    • {{ application.user.first_name }} {{ application.user.last_name }}
    • + {% endfor %} +
    + {% 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 +155,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..a0617b2 --- /dev/null +++ b/hackathon_site/event/migrations/0008_useractivity.py @@ -0,0 +1,28 @@ +# 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..c9658d3 100644 --- a/hackathon_site/event/models.py +++ b/hackathon_site/event/models.py @@ -44,3 +44,11 @@ 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) \ No newline at end of file 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..a4ce92d 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,24 +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) { &H1 { @@ -525,7 +462,7 @@ strong { font-size: 24px; } &H3 { - font-size: 20px; + font-size: size(xl); } } @@ -534,19 +471,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 +513,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 +537,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..9efe850 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 @@ -654,7 +655,8 @@ def test_user_review_rejected(self): 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}", ) 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..2cf3f74 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 @@ -15,12 +16,16 @@ 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 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 +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 +52,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: @@ -165,6 +174,66 @@ 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"] + + 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() + now = datetime.now().replace(tzinfo=settings.TZ_INFO) + + try: + user_activity = UserActivity.objects.get(user__exact=user) + 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) + + messages.success( + self.request, + f'User {form.cleaned_data["email"]} successfully signed in', + ) + 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..ffb30fe 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,7 @@ def environment(**options): "chat_room_name": settings.CHAT_ROOM[0], "chat_room_link": settings.CHAT_ROOM[1], "using_teams": settings.TEAMS, + "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..5aea507 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,37 +323,6 @@ 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" - # 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 # TODO: modify sign in times when available @@ -382,3 +353,34 @@ "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 + +# 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..5c8d549 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(useDescription=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 useDescription else event["name"] + + if useDescription: + 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/forms.py b/hackathon_site/registration/forms.py index b914bb4..66175bc 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,49 @@ 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 \ No newline at end of file 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/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 +

    From 634c3e8165cc945009c81ad71ffb97b33c448973 Mon Sep 17 00:00:00 2001 From: Mustafa Abdulrahman Date: Sun, 15 Oct 2023 20:06:11 -0400 Subject: [PATCH 2/4] Add rsvp features and fix merge issues --- .../event/jinja2/event/admin_qr_scanner.html | 23 ++--- .../event/jinja2/event/dashboard_admin.html | 2 +- .../event/jinja2/event/dashboard_base.html | 83 +++++++++++++++++-- .../event/migrations/0008_useractivity.py | 32 +++++-- hackathon_site/event/models.py | 3 +- hackathon_site/event/serializers.py | 10 +-- hackathon_site/event/views.py | 77 +++++++++++++++-- hackathon_site/hackathon_site/jinja2.py | 1 + .../hackathon_site/settings/__init__.py | 3 + hackathon_site/hackathon_site/utils.py | 6 +- hackathon_site/registration/admin.py | 4 +- hackathon_site/registration/forms.py | 3 +- .../migrations/0012_application_rsvp.py | 16 ++++ hackathon_site/registration/models.py | 1 + hackathon_site/registration/urls.py | 1 + hackathon_site/registration/views.py | 69 +++++++++++++++ hackathon_site/review/views.py | 3 + 17 files changed, 289 insertions(+), 48 deletions(-) create mode 100644 hackathon_site/registration/migrations/0012_application_rsvp.py diff --git a/hackathon_site/event/jinja2/event/admin_qr_scanner.html b/hackathon_site/event/jinja2/event/admin_qr_scanner.html index cbf7540..853b6df 100644 --- a/hackathon_site/event/jinja2/event/admin_qr_scanner.html +++ b/hackathon_site/event/jinja2/event/admin_qr_scanner.html @@ -6,7 +6,7 @@ {% endblock %} {% block body %} -
    +
    @@ -58,10 +58,13 @@

    Student Information

    - + {% if get_curr_sign_in_time() %} + + {% endif %}
    {% endif %} @@ -73,12 +76,14 @@

    Student Information

    Event Time Sign In Interval + Scanned {% for event in sign_in_times %} - + {{ event.description }} {{ event.time.strftime("%H:%M, %b %d") }} {{ get_sign_in_interval(event.time) }} + {{ sign_in_counts[event.name] }} {% endfor %} @@ -89,15 +94,11 @@

    Student Information

    {% endblock %} {% block scripts %} - +