Merged in develop (pull request #52)

Develop
This commit is contained in:
Ramon Wenger 2020-03-30 18:48:22 +00:00
commit 4cf8a6fe27
139 changed files with 3564 additions and 17253 deletions

View File

@ -10,6 +10,7 @@ python_version = '3.6'
awscli = "*" awscli = "*"
ipdb = "*" ipdb = "*"
coverage = "*" coverage = "*"
django-silk = "*"
[packages] [packages]
factory-boy = "==2.11.0" factory-boy = "==2.11.0"

177
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e7807798e8b5c39d7c1bc57d61520f2c888da08d2b6061f07758e00b490fdbd6" "sha256": "ca4f635dc983134e4569df2af8f0d73f488211bc2a97d11c194f76279f942977"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -48,18 +48,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:27e221d3868f35687807e5c920f7e8d4872f722f64196a7fd274a06ad65beec0", "sha256:629ce3be236b6e0aed52358146eea9ffa7679d6cd1cc9b3e12332226270d6499",
"sha256:8ff4e3d9e5d6a26dd7494afc68dc96afe6b7bda88130cca84cd58702d888ed27" "sha256:b1351e62136fae29be8fcbb1c4890f1d72017d57e33051d435a8bf9f71212fde"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.11.10" "version": "==1.11.14"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e", "sha256:34ad4d73e6bef5c8ad956c66354611628cdebd431fe2927261ed9c068b9cfb7a",
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d" "sha256:6570f2ba046956d5b548ab2346d32f713ecf8b8cb098a839d73fcf832ccfa223"
], ],
"version": "==1.14.10" "version": "==1.14.14"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -535,10 +535,10 @@
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a", "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c" "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
], ],
"version": "==0.3.2" "version": "==0.3.3"
}, },
"sendgrid": { "sendgrid": {
"hashes": [ "hashes": [
@ -603,6 +603,7 @@
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
], ],
"markers": "python_version != '3.4'",
"version": "==1.25.8" "version": "==1.25.8"
}, },
"wagtail": { "wagtail": {
@ -652,13 +653,26 @@
} }
}, },
"develop": { "develop": {
"asgiref": {
"hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
],
"version": "==3.2.3"
},
"autopep8": {
"hashes": [
"sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
],
"version": "==1.5"
},
"awscli": { "awscli": {
"hashes": [ "hashes": [
"sha256:4c49f085fb827ca1aeba5e6e5e39f6005110a0059b5c772aeb1d51c4f33c4028", "sha256:2748af4e77728ced50d7d5bda0fa980449bd71eedff90ee643bee86ed4283d2f",
"sha256:9459ac705c2a5d8724057492800c52084df714b624853eb3331087ecf8726a22" "sha256:9118015f4bbab1c671d9c9927d07b6f7eadb7e1e8bbb2b06dc849c3de578d692"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.9" "version": "==1.17.14"
}, },
"backcall": { "backcall": {
"hashes": [ "hashes": [
@ -669,17 +683,32 @@
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:cf3144994191847e30ef76781af867009bdc233b3f1f4736615e5330687a891e", "sha256:34ad4d73e6bef5c8ad956c66354611628cdebd431fe2927261ed9c068b9cfb7a",
"sha256:f11ff8616f46ca04697df031e622c9ed50931b9d649d4e719f961e9d80771e8d" "sha256:6570f2ba046956d5b548ab2346d32f713ecf8b8cb098a839d73fcf832ccfa223"
], ],
"version": "==1.14.10" "version": "==1.14.14"
},
"certifi": {
"hashes": [
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
],
"version": "==2019.11.28"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
], ],
"version": "==0.4.1" "markers": "python_version != '3.4'",
"version": "==0.4.3"
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
@ -725,6 +754,22 @@
], ],
"version": "==4.4.1" "version": "==4.4.1"
}, },
"django": {
"hashes": [
"sha256:48522428f4a285cf265af969f4744c5ebb027c7f41958ba48b639ace2068ffe7",
"sha256:a794f7a2f4b7c928eecfbc4ebad03712ff27fb545abe269bf01aa8500781eb1c"
],
"index": "pypi",
"version": "==2.1.15"
},
"django-silk": {
"hashes": [
"sha256:9d5d66628689230288d1020de186b86e6f38583f611b5dd796ec988bb6a6333e",
"sha256:b0033ec3713882a5abb8b3db2b4392a746b83ce164ab7be568f0eeec4ba78a98"
],
"index": "pypi",
"version": "==4.0.0"
},
"docutils": { "docutils": {
"hashes": [ "hashes": [
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
@ -733,6 +778,19 @@
], ],
"version": "==0.15.2" "version": "==0.15.2"
}, },
"gprof2dot": {
"hashes": [
"sha256:b43fe04ebb3dfe181a612bbfc69e90555b8957022ad6a466f0308ed9c7f22e99"
],
"version": "==2019.11.30"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"ipdb": { "ipdb": {
"hashes": [ "hashes": [
"sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd" "sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd"
@ -762,6 +820,13 @@
], ],
"version": "==0.16.0" "version": "==0.16.0"
}, },
"jinja2": {
"hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
],
"version": "==2.11.1"
},
"jmespath": { "jmespath": {
"hashes": [ "hashes": [
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
@ -769,6 +834,44 @@
], ],
"version": "==0.9.4" "version": "==0.9.4"
}, },
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"parso": { "parso": {
"hashes": [ "hashes": [
"sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57",
@ -812,6 +915,13 @@
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pycodestyle": {
"hashes": [
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
],
"version": "==2.5.0"
},
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
@ -826,6 +936,13 @@
], ],
"version": "==2.8.1" "version": "==2.8.1"
}, },
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"version": "==2019.3"
},
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
@ -842,6 +959,14 @@
], ],
"version": "==5.2" "version": "==5.2"
}, },
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
"index": "pypi",
"version": "==2.22.0"
},
"rsa": { "rsa": {
"hashes": [ "hashes": [
"sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5",
@ -851,10 +976,10 @@
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:2525bae2a530195576da53671bae8ca8c55ee8e33bc2225a65e804476611ea5a", "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
"sha256:4924e10451cc37901945806423d16c2c2040a6530645a614ed87e995ccec764c" "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
], ],
"version": "==0.3.2" "version": "==0.3.3"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -863,6 +988,13 @@
], ],
"version": "==1.14.0" "version": "==1.14.0"
}, },
"sqlparse": {
"hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
],
"version": "==0.3.0"
},
"traitlets": { "traitlets": {
"hashes": [ "hashes": [
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
@ -875,6 +1007,7 @@
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
], ],
"markers": "python_version != '3.4'",
"version": "==1.25.8" "version": "==1.25.8"
}, },
"wcwidth": { "wcwidth": {

View File

@ -190,8 +190,5 @@ There is a schema.json in the fixtures folder. For now it has been generated onc
To generate a new schema, use the management command To generate a new schema, use the management command
``` ```
python manage.py graphql_schema --schema api.schema.schema --out schema.json --indent 4 python manage.py export_schema_for_cypress
``` ```
Then, remove the `data` property from the generated `schema.json`, so the `__schema` property is on the top level.
Also remove the two objects with `"name": "__debug"` from the JSON file.

View File

@ -0,0 +1,36 @@
{
"me": {
"id": "VXNlck5vZGU6NQ==",
"pk": 5,
"username": "rahel.cueni",
"email": "rahel.cueni@skillbox.example",
"firstName": "Rahel",
"lastName": "Cueni",
"avatarUrl": "",
"isTeacher": false,
"lastModule": {
"id": "TW9kdWxlTm9kZToxNw==",
"slug": "lohn-und-budget",
"__typename": "ModuleNode"
},
"selectedClass": {
"id": "U2Nob29sQ2xhc3NOb2RlOjI=",
"__typename": "SchoolClassNode"
},
"schoolClasses": {
"edges": [
{
"node": {
"id": "U2Nob29sQ2xhc3NOb2RlOjE=",
"name": "FLID2018a",
"__typename": "SchoolClassNode"
},
"__typename": "SchoolClassNodeEdge"
}
],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "UserNode",
"permissions": []
}
}

View File

@ -0,0 +1,19 @@
{
"me": {
"id": "VXNlck5vZGU6NQ==",
"pk": 5,
"username": "hansli",
"email": "hansli@skillbox.example",
"firstName": "Hansli",
"lastName": "Alleini",
"avatarUrl": "",
"lastModule": null,
"selectedClass": null,
"schoolClasses": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "UserNode",
"permissions": []
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,30 @@
{
"me": {
"id": "VXNlck5vZGU6Mg==",
"isTeacher": false,
"selectedClass": {
"id": "U2Nob29sQ2xhc3NOb2RlOjE=",
"name": "Moordale",
"members": [
{
"id": "VXNlck5vZGU6Mw==",
"firstName": "Otis",
"lastName": "Milburn",
"isTeacher": false,
"active": true,
"__typename": "ClassMemberNode"
},
{
"id": "VXNlck5vZGU6NA==",
"firstName": "Maeve",
"lastName": "Wiley",
"isTeacher": false,
"active": true,
"__typename": "ClassMemberNode"
}
],
"__typename": "SchoolClassNode"
},
"__typename": "UserNode"
}
}

View File

@ -1,4 +1,4 @@
describe('Survey', () => { describe('Bookmarks', () => {
beforeEach(() => { beforeEach(() => {
// todo: mock all the graphql queries and mutations // todo: mock all the graphql queries and mutations
cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress"); cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress");
@ -27,7 +27,6 @@ describe('Survey', () => {
cy.get('@interviewContent').within(() => { cy.get('@interviewContent').within(() => {
cy.get('.bookmark-actions__edit-note').click(); cy.get('.bookmark-actions__edit-note').click();
}); });
cy.get('[data-cy=bookmark-note]').within(() => { cy.get('[data-cy=bookmark-note]').within(() => {

View File

@ -1,73 +0,0 @@
describe('Change Password Page', () => {
const validNewPassword = 'Abcd1234!';
const validOldPassword = 'test';
const validationTooShort = 'Das neue Passwort muss mindestens 8 Zeichen lang sein';
const validationErrorMsg = 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten';
const validationOldWrongMsg = 'Die Eingabe ist falsch';
beforeEach(function () {
// todo: mock all the graphql queries and mutations
cy.clearCookies();
cy.visit('/me/profile');
cy.login('rahel.cueni', 'test');
});
after(function () {
cy.exec("python ../server/manage.py reset_testuser_password rahel.cueni");
});
it('shows an empty form', () => {
cy.get('[data-cy=password-change-success]').should('not.exist');
cy.get('[data-cy=old-password]').should('have.value', '');
cy.get('[data-cy=new-password]').should('have.value', '');
});
it('shows errors if old password is not entered', () => {
cy.changePassword('', validNewPassword);
cy.get('[data-cy=old-password-local-errors]').should('contain', 'Dein aktuelles Passwort fehlt')
});
it('shows errors if new password is not entered', () => {
cy.changePassword(validOldPassword, '');
cy.get('[data-cy=new-password-local-errors]').should('contain', 'Dein neues Passwort fehlt')
});
it('shows errors if new password is too short', () => {
cy.changePassword(validOldPassword, 'Abc1!');
cy.get('[data-cy=new-password-local-errors]').should('contain', validationTooShort)
});
it('shows errors if new password has no uppercase letter', () => {
cy.changePassword(validOldPassword, 'aabdddedddbc1!');
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
});
it('shows errors if new password has no lowercase letter', () => {
cy.changePassword(validOldPassword, 'ABCDDD334551!');
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
});
it('shows errors if new password has no digit', () => {
cy.changePassword(validOldPassword, 'AbcdEEDE!');
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
});
it('shows errors if new password has no special character', () => {
cy.changePassword(validOldPassword, 'AbcdEEDE09877');
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
});
it('shows errors if old password does not match', () => {
cy.changePassword('test12345', validNewPassword);
cy.get('[data-cy=old-password-remote-errors]').should('contain', validationOldWrongMsg)
});
it('shows success if change was successful', () => {
cy.changePassword(validOldPassword, validNewPassword);
cy.get('[data-cy=password-change-success]').should('contain', 'Dein Password wurde erfolgreich geändert.');
cy.get('[data-cy=old-password]').should('have.value', '');
cy.get('[data-cy=new-password]').should('have.value', '');
});
});

View File

@ -44,4 +44,22 @@ describe('The Login Page', () => {
cy.get('body').contains('Berufliche Grundbildung'); cy.get('body').contains('Berufliche Grundbildung');
}); });
it.only('logs out then logs in again', () => {
cy.viewport('macbook-15');
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/me/my-class');
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
});
cy.get('[data-cy=logout]').click();
cy.login('rahel.cueni', 'test');
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
});
cy.get('.profile-sidebar').should('be.visible');
});
}) })

View File

@ -0,0 +1,52 @@
const schema = require('../fixtures/schema.json');
const me = require('../fixtures/me.new-student.json');
describe('New student', () => {
it('shows "Enter Code" page and adds the user to a class', () => {
cy.server();
cy.mockGraphql({
schema: schema,
});
cy.apolloLogin('hansli', 'test');
const __typename = "SchoolClassNode";
const name = "KF1A";
const id = "U2Nob29sQ2xhc3NOb2RlOjI=";
cy.mockGraphqlOps({
operations: {
MeQuery: me,
JoinClass: {
joinClass: {
success: true,
schoolClass: {
id,
name,
__typename
}
}
},
MySchoolClassQuery: {
me: {
...me.me,
selectedClass: {
__typename,
name,
id,
members: []
}
}
}
}
});
cy.visit('/');
cy.get('[data-cy=join-class-title]').should('contain', 'Zugangscode');
cy.get('[data-cy=input-class-code]').type('XXXX');
cy.get('[data-cy=join-class]').click();
cy.get('[data-cy=class-list-title]').should('contain', 'Klassenliste');
});
});

View File

@ -0,0 +1,320 @@
const schema = require('../fixtures/schema.json');
const me = require('../fixtures/me.join-class.json');
const selectedClass = require('../fixtures/selected-school-class.json');
describe('Class Management', () => {
beforeEach(() => {
cy.server();
cy.mockGraphql({
schema: schema,
});
cy.viewport('macbook-15');
cy.apolloLogin('rahel.cueni', 'test');
});
// fixme: cache misbehaves with mequery, but only for test
// it('should join class', () => {
// const name = 'KF1A';
// const id = 'U2Nob29sQ2xhc3NOb2RlOjI=';
// const __typename = 'SchoolClassNode';
//
// let localMe = {
// ...me
// };
//
// cy.mockGraphqlOps({
// operations: {
// MeQuery: localMe,
// // JoinClass() {
// // // fixme: is this necessary? the cache somehow does not seem to do anything for the MeQuery
// // let schoolClass = {
// // id,
// // name,
// // // __typename
// // };
// // // localMe.me.schoolClasses.edges.push({
// // // node: schoolClass,
// // // __typename: 'SchoolClassNodeEdge'
// // // });
// // return {
// // joinClass: {
// // success: true,
// // schoolClass
// // }
// // }
// // },
// JoinClass: {
// joinClass: {
// success: true,
// schoolClass: {
// name,
// id
// }
// }
// },
// MySchoolClassQuery: {
// me: {
// ...selectedClass.me,
// selectedClass: {
// __typename,
// name,
// id,
// members: []
// }
// }
// }
// }
// });
//
// cy.visit('/me/profile');
//
// cy.get('[data-cy=header-user-widget]').within(() => {
// cy.get('[data-cy=user-widget-avatar]').click();
// });
// //
// cy.get('[data-cy=class-selection]').click();
// cy.get('[data-cy=class-selection-entry]').should('have.length', 1);
// cy.get('[data-cy=class-selection]').click();
//
// cy.get('[data-cy=join-class-link]').click();
//
// cy.get('[data-cy=input-class-code]').type('XXXX');
// cy.get('[data-cy=join-class]').click();
//
// cy.get('[data-cy=school-class-name]').should('contain', name);
// cy.get('[data-cy=current-class-name]').should('contain', name);
//
// cy.get('[data-cy=header-user-widget]').within(() => {
// cy.get('[data-cy=user-widget-avatar]').click();
// });
//
// cy.get('[data-cy=class-selection]').click();
// cy.get('[data-cy=class-selection-entry]').should('have.length', 2);
// //
// });
it('should not be able to leave class', () => {
cy.mockGraphqlOps({
operations: {
MeQuery: me,
MySchoolClassQuery: selectedClass
}
});
cy.visit('/me/my-class');
cy.get('[data-cy=school-class-member]').should('have.length', 2);
cy.get('[data-cy=remove-from-class]').should('have.length', 0);
cy.get('[data-cy=add-to-class]').should('have.length', 0);
});
it('should leave and re-join class', () => {
// todo: re-enable after the deactivation of users is re-enabled again
// const teacher = {
// me: {
// ...me.me,
// isTeacher: true
// }
// };
// const teacherSelectedClass = {
// me: {
// ...selectedClass.me,
// isTeacher: true
// }
// };
// cy.mockGraphqlOps({
// operations: {
// MeQuery: teacher,
// AddRemoveMember: {
// addRemoveMember: {
// success: true
// }
// },
// MySchoolClassQuery: teacherSelectedClass
// }
// });
//
// cy.visit('/me/my-class');
// cy.get('[data-cy=active-class-members-list]').within(() => {
// cy.get('[data-cy=school-class-member]').should('have.length', 2)
// });
// cy.get('[data-cy=inactive-class-members-list]').should('not.exist');
//
// cy.get('[data-cy=remove-from-class]').first().click();
//
// cy.get('[data-cy=modal-save-button]').click();
//
// cy.get('[data-cy=active-class-members-list]').within(() => {
// cy.get('[data-cy=school-class-member]').should('have.length', 1)
// });
// cy.get('[data-cy=inactive-class-members-list]').within(() => {
// cy.get('[data-cy=school-class-member]').should('have.length', 1)
// });
//
// cy.get('[data-cy=add-to-class]').first().click();
//
// cy.get('[data-cy=active-class-members-list]').within(() => {
// cy.get('[data-cy=school-class-member]').should('have.length', 2)
// });
// cy.get('[data-cy=inactive-class-members-list]').should('not.exist');
});
it('should display old classes', () => {
let oldClasses = me.me.schoolClasses;
let OldClassesQuery = {
me: {
...me.me,
oldClasses
},
};
cy.mockGraphqlOps({
operations: {
MeQuery: me,
OldClassesQuery
}
});
cy.visit('/me/old-classes');
cy.get('[data-cy=old-class-item]').should('have.length', 1);
});
});
describe('Teacher Class Management', () => {
beforeEach(() => {
cy.server();
cy.mockGraphql({
schema: schema,
});
cy.viewport('macbook-15');
cy.apolloLogin('nico.zickgraf', 'test');
});
it('changes class name', () => {
let className = 'Gotta have class';
let localMe = {
me: {
...me.me,
isTeacher: true
}
};
cy.mockGraphqlOps({
operations: {
MeQuery: localMe,
MySchoolClassQuery: {
me: {
...selectedClass.me,
isTeacher: true
}
},
UpdateSchoolClass: {
updateSchoolClass: {
success: true,
schoolClass: {
name: className,
__typename: 'SchoolClassNode'
},
__typename: 'UpdateSchoolClassPayload'
}
}
}
});
cy.visit('/me/my-class');
cy.get('[data-cy=edit-class-name-link]').click();
cy.get('[data-cy=edit-class-name-input] input').type('{selectall}{backspace}').type(className);
cy.get('[data-cy=modal-save-button]').click();
cy.get('[data-cy=school-class-name]').should('contain', className);
});
// // fixme: cache misbehaves with mequery, but only for test
//
// it.only('creates a new class', () => {
// const name = 'Moordale';
// const id = 'U2Nob29sQ2xhc3NOb2RlOjI=';
// const __typename = "SchoolClassNode";
// let localMe = {
// me: {
// ...me.me,
// isTeacher: true
// }
// };
//
// cy.mockGraphqlOps({
// operations: {
// MeQuery: () => {
// return localMe;
// },
// CreateSchoolClass() {
// // fixme: is this necessary? the cache somehow does not seem to do anything for the MeQuery
// let schoolClass = {
// id,
// name,
// __typename
// };
// // localMe.me.schoolClasses.edges.push({
// // node: schoolClass,
// // __typename: 'SchoolClassNodeEdge'
// // });
// return {
// createSchoolClass: {
// success: true,
// schoolClass
// }
// }
// },
// MySchoolClassQuery: {
// me: {
// ...selectedClass.me,
// selectedClass: {
// __typename,
// name,
// id,
// members: []
// }
// }
// }
// }
// });
//
// cy.visit('/me/my-class');
//
// cy.get('h1').should('exist');
//
// cy.get('[data-cy=header-user-widget]').within(() => {
// cy.get('[data-cy=user-widget-avatar]').click();
// });
//
// cy.get('[data-cy=class-selection]').click();
// cy.get('[data-cy=class-selection-entry]').should('have.length', 1);
//
//
// cy.get('[data-cy=create-class-link]').click();
//
// cy.get('[data-cy=input-class-name]').type(name);
//
// cy.get('[data-cy=create-class]').click();
//
// cy.get('[data-cy=school-class-name]').should('contain', name);
// cy.get('[data-cy=current-class-name]').should('contain', name);
//
// cy.get('[data-cy=header-user-widget]').within(() => {
// cy.get('[data-cy=user-widget-avatar]').click();
// });
//
// cy.get('[data-cy=class-selection]').click();
// cy.get('[data-cy=class-selection-entry]').should('have.length', 2);
// });
});

View File

@ -10,7 +10,6 @@ describe('Survey', () => {
}); });
cy.viewport('macbook-15'); cy.viewport('macbook-15');
cy.apolloLogin('rahel.cueni', 'test'); cy.apolloLogin('rahel.cueni', 'test');
}); });

590
client/package-lock.json generated
View File

@ -2460,7 +2460,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -2492,7 +2492,7 @@
}, },
"onetime": { "onetime": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true "dev": true
}, },
@ -2766,6 +2766,15 @@
"@types/yargs": "^12.0.9" "@types/yargs": "^12.0.9"
} }
}, },
"@samverschueren/stream-to-observable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
"integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==",
"dev": true,
"requires": {
"any-observable": "^0.3.0"
}
},
"@types/babel__core": { "@types/babel__core": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz",
@ -3185,6 +3194,12 @@
"integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=",
"dev": true "dev": true
}, },
"any-observable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
"integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
"dev": true
},
"anymatch": { "anymatch": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@ -5699,13 +5714,10 @@
} }
}, },
"cachedir": { "cachedir": {
"version": "1.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-1.3.0.tgz", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz",
"integrity": "sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg==", "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==",
"dev": true, "dev": true
"requires": {
"os-homedir": "^1.0.1"
}
}, },
"caffeine-eight": { "caffeine-eight": {
"version": "2.5.9", "version": "2.5.9",
@ -6066,9 +6078,9 @@
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE="
}, },
"ci-info": { "ci-info": {
"version": "1.6.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true "dev": true
}, },
"cipher-base": { "cipher-base": {
@ -6166,6 +6178,17 @@
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz",
"integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==" "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg=="
}, },
"cli-table3": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
"integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
"dev": true,
"requires": {
"colors": "^1.1.2",
"object-assign": "^4.1.0",
"string-width": "^2.1.1"
}
},
"cli-truncate": { "cli-truncate": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
@ -7009,61 +7032,65 @@
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
}, },
"cypress": { "cypress": {
"version": "3.8.1", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.8.1.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz",
"integrity": "sha512-eLk5OpL/ZMDfQx9t7ZaDUAGVcvSOPTi7CG1tiUnu9BGk7caBiDhuFi3Tz/D5vWqH/Dl6Uh4X+Au4W+zh0xzbXw==", "integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cypress/listr-verbose-renderer": "0.4.1", "@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/xvfb": "1.2.4", "@cypress/xvfb": "1.2.4",
"@types/sizzle": "2.3.2", "@types/sizzle": "2.3.2",
"arch": "2.1.1", "arch": "2.1.1",
"bluebird": "3.5.0", "bluebird": "3.7.2",
"cachedir": "1.3.0", "cachedir": "2.3.0",
"chalk": "2.4.2", "chalk": "2.4.2",
"check-more-types": "2.24.0", "check-more-types": "2.24.0",
"commander": "2.15.1", "cli-table3": "0.5.1",
"commander": "4.1.0",
"common-tags": "1.8.0", "common-tags": "1.8.0",
"debug": "3.2.6", "debug": "4.1.1",
"execa": "0.10.0", "eventemitter2": "4.1.2",
"execa": "1.0.0",
"executable": "4.1.1", "executable": "4.1.1",
"extract-zip": "1.6.7", "extract-zip": "1.6.7",
"fs-extra": "5.0.0", "fs-extra": "8.1.0",
"getos": "3.1.1", "getos": "3.1.4",
"is-ci": "1.2.1", "is-ci": "2.0.0",
"is-installed-globally": "0.1.0", "is-installed-globally": "0.1.0",
"lazy-ass": "1.6.0", "lazy-ass": "1.6.0",
"listr": "0.12.0", "listr": "0.14.3",
"lodash": "4.17.15", "lodash": "4.17.15",
"log-symbols": "2.2.0", "log-symbols": "3.0.0",
"minimist": "1.2.0", "minimist": "1.2.2",
"moment": "2.24.0", "moment": "2.24.0",
"ramda": "0.24.1", "ospath": "1.2.2",
"request": "2.88.0", "pretty-bytes": "5.3.0",
"ramda": "0.26.1",
"request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"request-progress": "3.0.0", "request-progress": "3.0.0",
"supports-color": "5.5.0", "supports-color": "7.1.0",
"tmp": "0.1.0", "tmp": "0.1.0",
"untildify": "3.0.3", "untildify": "4.0.0",
"url": "0.11.0", "url": "0.11.0",
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "6.10.2", "version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^2.0.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1", "json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
"bluebird": { "bluebird": {
"version": "3.5.0", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true "dev": true
}, },
"chalk": { "chalk": {
@ -7075,12 +7102,23 @@
"ansi-styles": "^3.2.1", "ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0" "supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
} }
}, },
"commander": { "commander": {
"version": "2.15.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==",
"dev": true "dev": true
}, },
"cross-spawn": { "cross-spawn": {
@ -7097,22 +7135,22 @@
} }
}, },
"debug": { "debug": {
"version": "3.2.6", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true, "dev": true,
"requires": { "requires": {
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"execa": { "execa": {
"version": "0.10.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"dev": true, "dev": true,
"requires": { "requires": {
"cross-spawn": "^6.0.0", "cross-spawn": "^6.0.0",
"get-stream": "^3.0.0", "get-stream": "^4.0.0",
"is-stream": "^1.1.0", "is-stream": "^1.1.0",
"npm-run-path": "^2.0.0", "npm-run-path": "^2.0.0",
"p-finally": "^1.0.0", "p-finally": "^1.0.0",
@ -7121,11 +7159,20 @@
} }
}, },
"fast-deep-equal": { "fast-deep-equal": {
"version": "2.0.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
"dev": true "dev": true
}, },
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -7162,10 +7209,19 @@
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true "dev": true
}, },
"log-symbols": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
"dev": true,
"requires": {
"chalk": "^2.4.2"
}
},
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.2",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==",
"dev": true "dev": true
}, },
"ms": { "ms": {
@ -7180,16 +7236,19 @@
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true "dev": true
}, },
"punycode": { "pump": {
"version": "1.4.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true "dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
}, },
"request": { "request": {
"version": "2.88.0", "version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true, "dev": true,
"requires": { "requires": {
"aws-sign2": "~0.7.0", "aws-sign2": "~0.7.0",
@ -7199,7 +7258,7 @@
"extend": "~3.0.2", "extend": "~3.0.2",
"forever-agent": "~0.6.1", "forever-agent": "~0.6.1",
"form-data": "~2.3.2", "form-data": "~2.3.2",
"har-validator": "~5.1.0", "har-validator": "~5.1.3",
"http-signature": "~1.2.0", "http-signature": "~1.2.0",
"is-typedarray": "~1.0.0", "is-typedarray": "~1.0.0",
"isstream": "~0.1.2", "isstream": "~0.1.2",
@ -7209,7 +7268,7 @@
"performance-now": "^2.1.0", "performance-now": "^2.1.0",
"qs": "~6.5.2", "qs": "~6.5.2",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3", "tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0", "tunnel-agent": "^0.6.0",
"uuid": "^3.3.2" "uuid": "^3.3.2"
} }
@ -7224,12 +7283,20 @@
} }
}, },
"supports-color": { "supports-color": {
"version": "5.5.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"has-flag": "^3.0.0" "has-flag": "^4.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
}
} }
}, },
"tmp": { "tmp": {
@ -7242,13 +7309,13 @@
} }
}, },
"tough-cookie": { "tough-cookie": {
"version": "2.4.3", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true, "dev": true,
"requires": { "requires": {
"psl": "^1.1.24", "psl": "^1.1.28",
"punycode": "^1.4.1" "punycode": "^2.1.1"
} }
} }
} }
@ -8280,6 +8347,12 @@
"es5-ext": "~0.10.14" "es5-ext": "~0.10.14"
} }
}, },
"eventemitter2": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
"integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=",
"dev": true
},
"eventemitter3": { "eventemitter3": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
@ -8988,16 +9061,22 @@
} }
}, },
"fs-extra": { "fs-extra": {
"version": "5.0.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true, "dev": true,
"requires": { "requires": {
"graceful-fs": "^4.1.2", "graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0", "jsonfile": "^4.0.0",
"universalify": "^0.1.0" "universalify": "^0.1.0"
}, },
"dependencies": { "dependencies": {
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"dev": true
},
"jsonfile": { "jsonfile": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@ -9606,12 +9685,20 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
}, },
"getos": { "getos": {
"version": "3.1.1", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.1.tgz", "resolved": "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz",
"integrity": "sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==", "integrity": "sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==",
"dev": true, "dev": true,
"requires": { "requires": {
"async": "2.6.1" "async": "^3.1.0"
},
"dependencies": {
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==",
"dev": true
}
} }
}, },
"getpass": { "getpass": {
@ -10507,12 +10594,12 @@
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
}, },
"is-ci": { "is-ci": {
"version": "1.2.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
"dev": true, "dev": true,
"requires": { "requires": {
"ci-info": "^1.5.0" "ci-info": "^2.0.0"
} }
}, },
"is-data-descriptor": { "is-data-descriptor": {
@ -10638,6 +10725,15 @@
} }
} }
}, },
"is-observable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
"integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
"dev": true,
"requires": {
"symbol-observable": "^1.1.0"
}
},
"is-path-cwd": { "is-path-cwd": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
@ -11540,7 +11636,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -11568,6 +11665,7 @@
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -11582,7 +11680,8 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -11593,7 +11692,8 @@
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -11710,7 +11810,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -11722,6 +11823,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -11736,6 +11838,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -11743,12 +11846,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -11767,6 +11872,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -11847,7 +11953,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -11859,6 +11966,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -11944,7 +12052,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -11980,6 +12089,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -11999,6 +12109,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -12042,12 +12153,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -13167,114 +13280,26 @@
} }
}, },
"listr": { "listr": {
"version": "0.12.0", "version": "0.14.3",
"resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
"integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^1.1.3", "@samverschueren/stream-to-observable": "^0.3.0",
"cli-truncate": "^0.2.1", "is-observable": "^1.1.0",
"figures": "^1.7.0",
"indent-string": "^2.1.0",
"is-promise": "^2.1.0", "is-promise": "^2.1.0",
"is-stream": "^1.1.0", "is-stream": "^1.1.0",
"listr-silent-renderer": "^1.1.1", "listr-silent-renderer": "^1.1.1",
"listr-update-renderer": "^0.2.0", "listr-update-renderer": "^0.5.0",
"listr-verbose-renderer": "^0.4.0", "listr-verbose-renderer": "^0.5.0",
"log-symbols": "^1.0.2", "p-map": "^2.0.0",
"log-update": "^1.0.2", "rxjs": "^6.3.3"
"ora": "^0.2.3",
"p-map": "^1.1.1",
"rxjs": "^5.0.0-beta.11",
"stream-to-observable": "^0.1.0",
"strip-ansi": "^3.0.1"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "p-map": {
"version": "2.2.1", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"cli-cursor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
"dev": true,
"requires": {
"restore-cursor": "^1.0.1"
}
},
"cli-spinners": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz",
"integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=",
"dev": true
},
"figures": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5",
"object-assign": "^4.1.0"
}
},
"log-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
"integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
"dev": true,
"requires": {
"chalk": "^1.0.0"
}
},
"onetime": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
"ora": {
"version": "0.2.3",
"resolved": "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
"integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
"dev": true,
"requires": {
"chalk": "^1.1.1",
"cli-cursor": "^1.0.2",
"cli-spinners": "^0.1.2",
"object-assign": "^4.0.1"
}
},
"restore-cursor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
"dev": true,
"requires": {
"exit-hook": "^1.0.0",
"onetime": "^1.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true "dev": true
} }
} }
@ -13286,9 +13311,9 @@
"dev": true "dev": true
}, },
"listr-update-renderer": { "listr-update-renderer": {
"version": "0.2.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
"integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^1.1.3", "chalk": "^1.1.3",
@ -13297,7 +13322,7 @@
"figures": "^1.7.0", "figures": "^1.7.0",
"indent-string": "^3.0.0", "indent-string": "^3.0.0",
"log-symbols": "^1.0.2", "log-symbols": "^1.0.2",
"log-update": "^1.0.2", "log-update": "^2.3.0",
"strip-ansi": "^3.0.1" "strip-ansi": "^3.0.1"
}, },
"dependencies": { "dependencies": {
@ -13309,7 +13334,7 @@
}, },
"chalk": { "chalk": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -13354,77 +13379,15 @@
} }
}, },
"listr-verbose-renderer": { "listr-verbose-renderer": {
"version": "0.4.1", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
"integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^1.1.3", "chalk": "^2.4.1",
"cli-cursor": "^1.0.2", "cli-cursor": "^2.1.0",
"date-fns": "^1.27.2", "date-fns": "^1.27.2",
"figures": "^1.7.0" "figures": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"cli-cursor": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
"dev": true,
"requires": {
"restore-cursor": "^1.0.1"
}
},
"figures": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5",
"object-assign": "^4.1.0"
}
},
"onetime": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true
},
"restore-cursor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
"dev": true,
"requires": {
"exit-hook": "^1.0.0",
"onetime": "^1.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
}
} }
}, },
"load-json-file": { "load-json-file": {
@ -13649,44 +13612,39 @@
} }
}, },
"log-update": { "log-update": {
"version": "1.0.2", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
"integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-escapes": "^1.0.0", "ansi-escapes": "^3.0.0",
"cli-cursor": "^1.0.2" "cli-cursor": "^2.0.0",
"wrap-ansi": "^3.0.1"
}, },
"dependencies": { "dependencies": {
"ansi-escapes": { "ansi-regex": {
"version": "1.4.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true "dev": true
}, },
"cli-cursor": { "strip-ansi": {
"version": "1.0.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true, "dev": true,
"requires": { "requires": {
"restore-cursor": "^1.0.1" "ansi-regex": "^3.0.0"
} }
}, },
"onetime": { "wrap-ansi": {
"version": "1.1.0", "version": "3.0.1",
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=",
"dev": true
},
"restore-cursor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
"dev": true, "dev": true,
"requires": { "requires": {
"exit-hook": "^1.0.0", "string-width": "^2.1.1",
"onetime": "^1.0.0" "strip-ansi": "^4.0.0"
} }
} }
} }
@ -14917,6 +14875,12 @@
"os-tmpdir": "^1.0.0" "os-tmpdir": "^1.0.0"
} }
}, },
"ospath": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz",
"integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=",
"dev": true
},
"p-defer": { "p-defer": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@ -17089,6 +17053,12 @@
} }
} }
}, },
"pretty-bytes": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",
"integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==",
"dev": true
},
"pretty-error": { "pretty-error": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
@ -17255,9 +17225,9 @@
"integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw=="
}, },
"ramda": { "ramda": {
"version": "0.24.1", "version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
"integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=", "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
"dev": true "dev": true
}, },
"randomatic": { "randomatic": {
@ -17874,20 +17844,12 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "5.5.12", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"symbol-observable": "1.0.1" "tslib": "^1.9.0"
},
"dependencies": {
"symbol-observable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
"dev": true
}
} }
}, },
"safe-buffer": { "safe-buffer": {
@ -18678,12 +18640,6 @@
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
}, },
"stream-to-observable": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz",
"integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=",
"dev": true
},
"strict-uri-encode": { "strict-uri-encode": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@ -19456,9 +19412,9 @@
} }
}, },
"untildify": { "untildify": {
"version": "3.0.3", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true "dev": true
}, },
"upath": { "upath": {

View File

@ -108,7 +108,7 @@
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0", "babel-jest": "^24.8.0",
"canvas": "^2.5.0", "canvas": "^2.5.0",
"cypress": "3.8.1", "cypress": "^4.2.0",
"jest": "^24.8.0", "jest": "^24.8.0",
"jest-serializer-vue": "^2.0.2", "jest-serializer-vue": "^2.0.2",
"jest-transform-graphql": "^2.1.0", "jest-transform-graphql": "^2.1.0",

View File

@ -1,5 +1,6 @@
<template> <template>
<div :class="{'no-scroll': showModal || showMobileNavigation}" class="app" id="app"> <div :class="{'no-scroll': showModal || showMobileNavigation}" class="app" id="app">
<component :is="showModalDeprecated" v-if="showModalDeprecated"></component>
<component :is="showModal" v-if="showModal"></component> <component :is="showModal" v-if="showModal"></component>
<component :is="layout"></component> <component :is="layout"></component>
<mobile-navigation v-if="showMobileNavigation"></mobile-navigation> <mobile-navigation v-if="showMobileNavigation"></mobile-navigation>
@ -18,16 +19,16 @@
import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard'; import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard';
import NewRoomEntryWizard from '@/components/rooms/room-entries/NewRoomEntryWizard'; import NewRoomEntryWizard from '@/components/rooms/room-entries/NewRoomEntryWizard';
import EditRoomEntryWizard from '@/components/rooms/room-entries/EditRoomEntryWizard'; import EditRoomEntryWizard from '@/components/rooms/room-entries/EditRoomEntryWizard';
import NewObjectiveGroupWizard from '@/components/objective-groups/NewObjectiveGroupWizard';
import EditObjectiveGroupWizard from '@/components/objective-groups/EditObjectiveGroupWizard';
import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard'; import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard';
import EditProjectEntryWizard from '@/components/portfolio/EditProjectEntryWizard'; import EditProjectEntryWizard from '@/components/portfolio/EditProjectEntryWizard';
import NewObjectiveWizard from '@/components/objective-groups/NewObjectiveWizard'; import NewObjectiveWizard from '@/components/objective-groups/NewObjectiveWizard';
import NewNoteWizard from '@/components/notes/NewNoteWizard'; import NewNoteWizard from '@/components/notes/NewNoteWizard';
import EditNoteWizard from '@/components/notes/EditNoteWizard'; import EditNoteWizard from '@/components/notes/EditNoteWizard';
import EditClassNameWizard from '@/components/school-class/EditClassNameWizard';
import FullscreenImage from '@/components/FullscreenImage'; import FullscreenImage from '@/components/FullscreenImage';
import FullscreenInfographic from '@/components/FullscreenInfographic'; import FullscreenInfographic from '@/components/FullscreenInfographic';
import FullscreenVideo from '@/components/FullscreenVideo'; import FullscreenVideo from '@/components/FullscreenVideo';
import DeactivatePerson from '@/components/profile/DeactivatePerson';
import {mapGetters} from 'vuex'; import {mapGetters} from 'vuex';
@ -46,24 +47,29 @@
EditContentBlockWizard, EditContentBlockWizard,
NewRoomEntryWizard, NewRoomEntryWizard,
EditRoomEntryWizard, EditRoomEntryWizard,
// todo: remove
NewObjectiveGroupWizard,
EditObjectiveGroupWizard,
NewProjectEntryWizard, NewProjectEntryWizard,
EditProjectEntryWizard, EditProjectEntryWizard,
NewObjectiveWizard, NewObjectiveWizard,
NewNoteWizard, NewNoteWizard,
EditNoteWizard, EditNoteWizard,
EditClassNameWizard,
FullscreenImage, FullscreenImage,
FullscreenInfographic, FullscreenInfographic,
FullscreenVideo FullscreenVideo,
DeactivatePerson
}, },
computed: { computed: {
layout() { layout() {
return (this.$route.meta.layout || 'default') + '-layout'; return (this.$route.meta.layout || 'default') + '-layout';
}, },
...mapGetters(['showModal', 'showMobileNavigation']) ...mapGetters({
showModalDeprecated: 'showModal', // don't use this any more todo: remove this
showMobileNavigation: 'showMobileNavigation'
}),
showModal() {
return this.$modal.state.component;
}
}, },
mounted() { mounted() {

View File

@ -1,65 +0,0 @@
<template>
<div class="add-objective-group-button" @click="addObjectiveGroup()">
<add-icon class="add-objective-group-button__icon"></add-icon>
<div class="add-objective-group-button__text">Zusätzlich Lernziele für «{{typeDescription}}» erfassen</div>
</div>
</template>
<script>
import AddIcon from '@/components/icons/AddIcon';
export default {
props: ['type', 'module'],
components: {
AddIcon
},
computed: {
typeDescription() {
if (this.type === 'society') {
return 'Gesellschaft'
}
return 'Sprache & Kommunikation';
}
},
methods: {
addObjectiveGroup() {
this.$store.dispatch('addObjectiveGroup', {
module: this.module,
type: this.type
});
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.add-objective-group-button {
display: none;
grid-template-columns: 45px auto;
align-items: center;
margin-top: -20px;
margin-bottom: 35px;
cursor: pointer;
@include desktop {
display: grid;
}
&__icon {
width: 25px;
height: 25px;
fill: $color-silver-dark;
}
&__text {
color: $color-silver-dark;
font-family: $sans-serif-font-family;
}
}
</style>

View File

@ -1,123 +0,0 @@
<template>
<div class="class-selection" v-if="isTeacher">
<div class="class-selection__selected-class selected-class" @click="showPopover = !showPopover">
<p class="selected-class__text">Klasse: {{currentClassSelection.name}}</p>
</div>
<widget-popover v-if="showPopover"
@hide-me="showPopover = false"
:mobile="mobile"
class="class-selection__popover">
<li class="popover-links__link popover-links__link--large" v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
:label="schoolClass.name"
:item="schoolClass"
@click="updateFilter(schoolClass)">
{{schoolClass.name}}
</li>
</widget-popover>
</div>
</template>
<script>
import WidgetPopover from '@/components/WidgetPopover';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
export default {
components: {
WidgetPopover
},
props: {
mobile: {
type: Boolean,
default: false
}
},
apollo: {
me: {
query: ME_QUERY
}
},
data() {
return {
me: {
selectedClass: {
id: ''
},
permissions: []
},
showPopover: false
}
},
methods: {
updateFilter(selectedClass) {
this.$apollo.mutate({
mutation: UPDATE_USER_SETTING,
variables: {
input: {
id: selectedClass.id
}
},
update(store, data) {
let meData = store.readQuery({query: ME_QUERY});
meData.me.selectedClass = selectedClass
store.writeQuery({query: ME_QUERY, data: meData});
}
}).catch((error) => {
console.log('fail', error)
});
this.showPopover = false;
}
},
computed: {
currentClassSelection() {
let currentClass = this.schoolClasses.find(schoolClass => {
return schoolClass.id === this.me.selectedClass.id
})
return currentClass || this.schoolClasses[0];
},
schoolClasses() {
return this.$getRidOfEdges(this.me.schoolClasses);
},
isTeacher() {
return this.me.permissions.includes('users.can_manage_school_class_content');
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.class-selection {
position: relative;
cursor: pointer;
margin-right: $large-spacing;
&__popover {
white-space: nowrap;
top: 40px;
}
}
.selected-class {
&__text {
line-height: $large-spacing;
@include regular-text;
color: $color-silver-dark;
}
}
.popover-links__link {
cursor: pointer;
}
</style>

View File

@ -59,15 +59,8 @@
display: flex; display: flex;
&__link { &__link {
font-size: 1.0625rem;
padding: 0 24px; padding: 0 24px;
font-family: $sans-serif-font-family; @include default-link;
font-weight: $font-weight-regular;
color: $color-silver-dark;
&--active {
color: $color-brand;
}
} }
$parent: &; $parent: &;
@ -96,9 +89,11 @@
order: 3; order: 3;
border-bottom: 0; border-bottom: 0;
} }
&:nth-child(2) { &:nth-child(2) {
order: 1; order: 1;
} }
&:nth-child(3) { &:nth-child(3) {
order: 2; order: 2;
} }

View File

@ -1,12 +1,13 @@
<template> <template>
<header class="header-bar"> <header class="header-bar">
<top-navigation></top-navigation> <content-navigation></content-navigation>
<router-link to="/" class="header-bar__logo" data-cy="home-link"> <router-link to="/" class="header-bar__logo" data-cy="home-link">
<logo></logo> <logo></logo>
</router-link> </router-link>
<div class="user-header"> <div class="user-header">
<class-selection-widget /> <a class="user-header__sidebar-link" @click="openSidebar()"><current-class class="user-header__current-class"/></a>
<user-widget v-bind="me"></user-widget>
<user-widget data-cy="header-user-widget" v-bind="me"></user-widget>
</div> </div>
<book-navigation v-if="showSubnavigation"> <book-navigation v-if="showSubnavigation">
</book-navigation> </book-navigation>
@ -14,23 +15,24 @@
</template> </template>
<script> <script>
import TopNavigation from '@/components/TopNavigation.vue'; import ContentNavigation from '@/components/ContentNavigation.vue';
import BookNavigation from '@/components/book-navigation/BookNavigation'; import BookNavigation from '@/components/book-navigation/BookNavigation';
import UserWidget from '@/components/UserWidget.vue'; import UserWidget from '@/components/UserWidget.vue';
import LogoutWidget from '@/components/LogoutWidget.vue';
import Logo from '@/components/icons/Logo'; import Logo from '@/components/icons/Logo';
import ClassSelectionWidget from '@/components/ClassSelectionWidget'; import CurrentClass from '@/components/school-class/CurrentClass';
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import openSidebar from '@/mixins/open-sidebar';
import me from '@/mixins/me';
export default { export default {
mixins: [openSidebar, me],
components: { components: {
TopNavigation, ContentNavigation,
UserWidget, UserWidget,
LogoutWidget,
BookNavigation, BookNavigation,
Logo, Logo,
ClassSelectionWidget CurrentClass
}, },
computed: { computed: {
@ -38,18 +40,6 @@
return this.$route.meta.subnavigation; return this.$route.meta.subnavigation;
} }
}, },
data() {
return {
me: {}
}
},
apollo: {
me: {
query: ME_QUERY,
},
},
} }
</script> </script>
@ -58,7 +48,8 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.header-bar { .header-bar {
display: -ms-grid; display: flex;
flex-direction: row;
@supports (display: grid) { @supports (display: grid) {
display: none; display: none;
@ -67,7 +58,7 @@
} }
} }
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-between;
background-color: $color-white; background-color: $color-white;
grid-auto-rows: 50px; grid-auto-rows: 50px;
width: 100%; width: 100%;
@ -127,5 +118,13 @@
.user-header { .user-header {
display: flex; display: flex;
&__current-class {
margin-right: $large-spacing;
}
&__sidebar-link {
cursor: pointer;
}
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="logout-widget"> <div class="logout-widget">
<button class="logout-widget__logout" data-cy="logout" @click="logout()">Logout</button> <a class="logout-widget__logout" data-cy="logout" @click="logout()">Logout</a>
</div> </div>
</template> </template>
@ -8,7 +8,6 @@
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql'; import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
export default { export default {
methods: { methods: {
logout() { logout() {
this.$apollo.mutate({ this.$apollo.mutate({
@ -23,6 +22,7 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.logout-widget { .logout-widget {
color: $color-silver-dark; color: $color-silver-dark;
@ -30,15 +30,8 @@
align-items: center; align-items: center;
&__logout { &__logout {
font-family: $sans-serif-font-family; @include regular-text;
line-height: 16px;
margin: 0 15px 0 $large-spacing;
background: none;
color: inherit;
border: none;
padding: 0;
cursor: pointer; cursor: pointer;
outline: inherit;
} }
} }
</style> </style>

View File

@ -1,24 +1,31 @@
<template> <template>
<div class="mobile-header"> <div class="mobile-header">
<router-link to="/" data-cy="mobile-home-link">
<logo></logo>
</router-link>
<a @click="showMobileNavigation"> <a @click="showMobileNavigation">
<hamburger class="mobile-header__hamburger"></hamburger> <hamburger class="mobile-header__hamburger"></hamburger>
</a> </a>
<router-link to="/" data-cy="mobile-home-link">
<logo></logo>
</router-link>
<user-widget v-bind="me"></user-widget>
</div> </div>
</template> </template>
<script> <script>
import Logo from '@/components/icons/Logo'; import Logo from '@/components/icons/Logo';
import Hamburger from '@/components/icons/Hamburger'; import Hamburger from '@/components/icons/Hamburger';
import UserWidget from '@/components/UserWidget';
import me from '@/mixins/me';
export default { export default {
mixins: [me],
components: { components: {
Logo, Logo,
Hamburger Hamburger,
UserWidget
}, },
methods: { methods: {

View File

@ -1,36 +1,17 @@
<template> <template>
<div class="user-widget" :class="{'user-widget--is-profile': isProfile}"> <div class="user-widget" :class="{'user-widget--is-profile': isProfile}">
<div class="user-widget__avatar" @click="toggleShowPopover()"> <div class="user-widget__avatar" data-cy="user-widget-avatar" @click="openSidebar()">
<avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile"/> <avatar :avatar-url="avatarUrl" :icon-highlighted="isProfile"/>
</div> </div>
<widget-popover v-if="showPopover && showMenu"
@hide-me="showPopover = false"
:mobile="mobile"
class="user-widget__popover ">
<li class="popover-links__link popover-links__link--large popover-links__link--emph">{{firstName}} {{lastName}}
</li>
<li class="popover-links__link popover-links__link--large">
<router-link to="/me/activity" @click="toggleShowPopover()">Aktivität</router-link>
</li>
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
<router-link to="/me/profile">Profil</router-link>
</li>
<li class="popover-links__link popover-links__link--large" @click="toggleShowPopover()">
<router-link to="/me/myclasses">Klassenliste</router-link>
</li>
<li class="popover-links__link popover-links__link--large" data-cy="logout" @click="logout()">
<a>Logout</a>
</li>
</widget-popover>
</div> </div>
</template> </template>
<script> <script>
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
import Avatar from '@/components/profile/Avatar'; import Avatar from '@/components/profile/Avatar';
import WidgetPopover from '@/components/WidgetPopover'; import openSidebar from '@/mixins/open-sidebar';
export default { export default {
// todo: clean up unneeded props
props: { props: {
firstName: { firstName: {
type: String type: String
@ -51,31 +32,10 @@
} }
}, },
data() { mixins: [openSidebar],
return {
showPopover: false
}
},
methods: {
toggleShowPopover() {
if (this.showMenu) {
this.showPopover = !this.showPopover;
}
},
logout() {
this.$apollo.mutate({
mutation: LOGOUT_MUTATION,
}).then(({data}) => {
if (data.logout.success) {
location.replace('/logout')
}
});
}
},
components: { components: {
Avatar, WidgetPopover Avatar
}, },
computed: { computed: {
isProfile() { isProfile() {

View File

@ -18,51 +18,5 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.widget-popover {
position: absolute;
right: 0;
display: flex;
flex-direction: column;
background-color: $color-white;
padding: 20px;
z-index: 100;
@include widget-shadow;
&--mobile {
left: 0;
right: inherit;
}
}
.popover-links {
list-style: none;
display: grid;
&__link {
& > a {
display: inline-block;
color: $color-silver-dark;
font-family: $sans-serif-font-family;
font-size: toRem(14px);
line-height: 1.5;
padding: 5px 0;
cursor: pointer;
}
&--large {
line-height: 40px;
& > a, & {
@include small-text;
}
}
&--emph {
@include regular-text;
font-weight: 600;
}
}
}
</style> </style>

View File

@ -1,32 +1,25 @@
<template> <template>
<div class="mobile-navigation"> <div class="mobile-navigation">
<top-navigation class="mobile-navigation__main" :mobile="true"></top-navigation> <content-navigation class="mobile-navigation__main" :mobile="true"></content-navigation>
<div class="mobile-navigation__close-button" @click="hideMobileNavigation"> <div class="mobile-navigation__close-button" @click="hideMobileNavigation">
<cross class="mobile-navigation__close-icon"></cross> <cross class="mobile-navigation__close-icon"></cross>
</div> </div>
<div class="mobile-navigation__subnavigation"></div>
<div class="mobile-navigation__secondary">
<class-selection-widget :mobile="true" />
<user-widget class="mobile-navigation__user-widget" v-bind="me" :mobile="true"></user-widget>
</div>
</div> </div>
</template> </template>
<script> <script>
import Cross from '@/components/icons/Cross'; import Cross from '@/components/icons/Cross';
import UserWidget from '@/components/UserWidget'; import UserWidget from '@/components/UserWidget';
import LogoutWidget from '@/components/LogoutWidget'; import ContentNavigation from '@/components/ContentNavigation';
import TopNavigation from '@/components/TopNavigation'; import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
import ClassSelectionWidget from '@/components/ClassSelectionWidget';
import {meQuery} from '@/graphql/queries'; import {meQuery} from '@/graphql/queries';
export default { export default {
components: { components: {
TopNavigation, ContentNavigation,
Cross, Cross,
UserWidget, UserWidget,
LogoutWidget,
ClassSelectionWidget ClassSelectionWidget
}, },
@ -101,20 +94,5 @@
opacity: 0.5; opacity: 0.5;
fill: $color-white; fill: $color-white;
} }
&__secondary {
grid-area: s;
padding: $medium-spacing;
display: flex;
flex-direction: column;
}
&__user-widget {
margin-bottom: $small-spacing;
}
&__logout-widget {
margin-left: -$large-spacing;
}
} }
</style> </style>

View File

@ -42,30 +42,3 @@
} }
} }
</script> </script>
<style lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.book-subnavigation {
&__item {
@include small-text;
margin-bottom: $small-spacing;
cursor: pointer;
color: $color-silver-dark;
&--mobile {
color: $color-white;
}
&:last-of-type {
margin-bottom: 0;
}
&--active {
color: $color-brand;
}
}
}
</style>

View File

@ -32,6 +32,7 @@
import ThinglinkBlock from '@/components/content-blocks/ThinglinkBlock'; import ThinglinkBlock from '@/components/content-blocks/ThinglinkBlock';
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock'; import GeniallyBlock from '@/components/content-blocks/GeniallyBlock';
import SubtitleBlock from '@/components/content-blocks/SubtitleBlock'; import SubtitleBlock from '@/components/content-blocks/SubtitleBlock';
import SectionTitleBlock from '@/components/content-blocks/SectionTitleBlock';
import ContentListBlock from '@/components/content-blocks/ContentListBlock'; import ContentListBlock from '@/components/content-blocks/ContentListBlock';
import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug'; import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug';
import Assignment from '@/components/content-blocks/assignment/Assignment'; import Assignment from '@/components/content-blocks/assignment/Assignment';
@ -56,6 +57,7 @@
'infogram_block': InfogramBlock, 'infogram_block': InfogramBlock,
'genially_block': GeniallyBlock, 'genially_block': GeniallyBlock,
'subtitle': SubtitleBlock, 'subtitle': SubtitleBlock,
'section_title': SectionTitleBlock,
'content_list': ContentListBlock, 'content_list': ContentListBlock,
'module_room_slug': ModuleRoomSlug, 'module_room_slug': ModuleRoomSlug,
'thinglink_block': ThinglinkBlock, 'thinglink_block': ThinglinkBlock,
@ -102,6 +104,7 @@
.content-component { .content-component {
position: relative; position: relative;
&--bookmarked { &--bookmarked {
} }

View File

@ -50,8 +50,7 @@
data() { data() {
return { return {
height: 0, height: 0
// title: 'Zahlungsmittel'
} }
} }
} }
@ -61,16 +60,9 @@
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
.infogram-block { .infogram-block {
/*padding: 8px 0;*/ margin-bottom: $large-spacing;
/*font-family: $sans-serif-font-family;*/
/*font-size: 13px !important;*/
/*line-height: 15px !important;*/
/*text-align: center;*/
/*border-top: 1px solid #dadada;*/
/*margin: 0 30px;*/
&__link { &__link {
/*color:#989898;*/
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
} }

View File

@ -89,7 +89,6 @@
spellcheckText() { spellcheckText() {
if (!this.spellcheckLoading) { if (!this.spellcheckLoading) {
return 'Rechtschreibung prüfen' return 'Rechtschreibung prüfen'
} else { } else {
return 'Wird geprüft...' return 'Wird geprüft...'
} }

View File

@ -0,0 +1,63 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400">
<defs>
<clipPath id="avatar-clip-path">
<circle class="cls-1" cx="200" cy="200" r="197"/>
</clipPath>
</defs>
<g id="bg">
<circle class="cls-2" cx="200" cy="200" r="197"/>
</g>
<g id="objects">
<g class="cls-3">
<path class="cls-4"
d="M380.03229,346.82485l-99.89952-29.68554-75.91247-7.58045h-2.40707l-78.31955,7.58045-103.526,30.44984s75.62737,95.55272,183.04906,95.55272S380.03229,346.82485,380.03229,346.82485Z"/>
<path class="cls-5"
d="M272.15765,65.53589a86.01591,86.01591,0,0,0-20.524-14.33295,103.61294,103.61294,0,0,0-49.8204-11.92284,103.61271,103.61271,0,0,0-49.8198,11.92255A86.01706,86.01706,0,0,0,131.4688,65.53589c-47.89186,45.957-59.684,136.053-53.17653,195.51559,22.30221,28.20465,70.43168,35.98046,103.97675,29.59277-1.699-28.98109-18.28465-55.36789-18.09824-84.40807,16.82179-9.95623,9.42329-59.11938,10.702-81.59916l26.94039-6.97556,26.9404,6.97556c1.27875,22.47978-6.11974,71.64293,10.70211,81.59916.18642,29.04018-16.39928,55.427-18.09824,84.40807,33.545,6.38769,81.67447-1.38812,103.97668-29.59277C331.84167,201.58886,320.04951,111.49293,272.15765,65.53589Z"/>
<path class="cls-6"
d="M237.057,266.23162H166.56944c0,34.9025-27.34,45.73431-43.07576,50.90769,15.73577,67.03868,142.10688,67.03868,156.63909,0C260.78642,311.96593,241.52987,305.94826,237.057,266.23162Z"/>
<ellipse class="cls-7" cx="202.35227" cy="179.47219" rx="79.23803" ry="97.02616"/>
<path class="cls-8"
d="M117.61519,154.166c10.8764-3.25836,22.30778,5.29762,25.51206,19.09451,3.20424,13.7965-3.02464,27.64251-13.90108,30.90087-10.87644,3.25678-22.30919-5.29877-25.51339-19.09389-3.20425-13.79708,3.026-27.64273,13.90241-30.90149Z"/>
<path class="cls-5"
d="M158.77973,88.35379c34.35007,53.44875,83.71361,66.59188,126.44529,82.83956C322.20435,145.19484,244.7734,11.96688,158.77973,88.35379Z"/>
<path class="cls-5"
d="M181.50651,94.88021c-10.83147,33.057-30.31262,48.8307-55.51284,58.59347C109.4863,115.38906,134.25941,72.625,181.50651,94.88021Z"/>
</g>
</g>
</svg>
</template>
<style scoped>
.cls-1 {
fill: none;
}
.cls-2 {
fill: #d1eee7;
}
.cls-3 {
clip-path: url(#avatar-clip-path);
}
.cls-4 {
fill: #17a887;
}
.cls-4, .cls-5, .cls-6, .cls-8 {
fill-rule: evenodd;
}
.cls-5 {
fill: #102d24;
}
.cls-6 {
fill: #e5f5f2;
}
.cls-7, .cls-8 {
fill: #fff;
}
</style>

View File

@ -31,7 +31,6 @@
<script> <script>
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue'; import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
import ObjectiveGroupControl from '@/components/objective-groups/ObjectiveGroupControl.vue'; import ObjectiveGroupControl from '@/components/objective-groups/ObjectiveGroupControl.vue';
import AddObjectiveGroupButton from '@/components/AddObjectiveGroupButton';
import Chapter from '@/components/Chapter.vue'; import Chapter from '@/components/Chapter.vue';
import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql'; import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql';
@ -49,7 +48,6 @@
BookmarkActions, BookmarkActions,
ObjectiveGroups, ObjectiveGroups,
ObjectiveGroupControl, ObjectiveGroupControl,
AddObjectiveGroupButton,
Chapter Chapter
}, },

View File

@ -164,7 +164,7 @@
display: none; display: none;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
z-index: 90; z-index: 11;
background-color: $color-white; background-color: $color-white;
border-top: 1px solid $color-silver; border-top: 1px solid $color-silver;

View File

@ -1,107 +0,0 @@
<template>
<objective-group-form
:title="title"
:objectives="objectives"
@save="saveObjectiveGroup"
@hide="hideModal"
></objective-group-form>
</template>
<script>
import Modal from '@/components/Modal';
import ObjectiveGroupForm from '@/components/objective-groups/ObjectiveGroupForm';
import AddContentElement from '@/components/AddContentElement';
import UPDATE_OBJECTIVE_GROUP_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroup.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import OBJECTIVE_GROUP_QUERY from '@/graphql/gql/objectiveGroupQuery.gql';
export default {
components: {
AddContentElement,
Modal,
ObjectiveGroupForm
},
computed: {
title() {
if (this.$store.state.objectiveGroupType === 'society') {
return 'Gesellschaft';
}
return 'Sprache & Kommunikation';
},
objectives() {
return this.objectiveGroup.objectives.edges.map(edge => edge.node)
}
},
methods: {
saveObjectiveGroup(objectives) {
const objectiveGroup = {
id: this.$store.state.currentObjectiveGroup,
objectives,
};
this.$apollo.mutate({
mutation: UPDATE_OBJECTIVE_GROUP_MUTATION,
variables: {
input: {
objectiveGroup
}
},
// todo: make update work
// update: (store, {data: {addObjectiveGroup: {objectiveGroup}}}) => {
// const query = MODULE_DETAILS_QUERY;
// const variables = {slug: this.$route.params.slug};
// const data = store.readQuery({query, variables});
// debugger;
// if (data.module && data.module.objectiveGroups) {
// data.module.objectiveGroups.edges.push({
// node: objectiveGroup,
// __typename: 'ObjectiveGroupNode'
// });
// store.writeQuery({query, variables, data});
// }
//
// }
refetchQueries: [{
query: MODULE_DETAILS_QUERY,
variables: {
slug: this.$route.params.slug
}
}]
}).then(() => {
this.hideModal();
});
},
hideModal() {
this.$store.dispatch('hideModal');
},
},
apollo: {
objectiveGroup() {
return {
query: OBJECTIVE_GROUP_QUERY,
variables: {
id: this.$store.state.currentObjectiveGroup
}
}
}
},
data() {
return {
objectiveGroup: {
objectives: {
edges: []
}
}
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,91 +0,0 @@
<template>
<objective-group-form
:title="title"
:objectives="objectives"
@save="saveObjectiveGroup"
@hide="hideModal"
></objective-group-form>
</template>
<script>
import Modal from '@/components/Modal';
import ObjectiveGroupForm from '@/components/objective-groups/ObjectiveGroupForm';
import AddContentElement from '@/components/AddContentElement';
import NEW_OBJECTIVE_GROUP_MUTATION from '@/graphql/gql/mutations/addObjectiveGroup.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
export default {
components: {
AddContentElement,
Modal,
ObjectiveGroupForm
},
computed: {
title() {
if (this.$store.state.objectiveGroupType === 'society') {
return 'Gesellschaft';
}
return 'Sprache & Kommunikation';
}
},
methods: {
saveObjectiveGroup(objectives) {
const objectiveGroup = {
title: this.$store.state.objectiveGroupType,
module: this.$store.state.parentModule,
objectives,
};
this.$apollo.mutate({
mutation: NEW_OBJECTIVE_GROUP_MUTATION,
variables: {
input: {
objectiveGroup
}
},
// todo: make update work
// update: (store, {data: {addObjectiveGroup: {objectiveGroup}}}) => {
// const query = MODULE_DETAILS_QUERY;
// const variables = {slug: this.$route.params.slug};
// const data = store.readQuery({query, variables});
// debugger;
// if (data.module && data.module.objectiveGroups) {
// data.module.objectiveGroups.edges.push({
// node: objectiveGroup,
// __typename: 'ObjectiveGroupNode'
// });
// store.writeQuery({query, variables, data});
// }
//
// }
refetchQueries: [{
query: MODULE_DETAILS_QUERY,
variables: {
slug: this.$route.params.slug
}
}]
}).then(() => {
this.hideModal();
});
},
hideModal() {
this.$store.dispatch('hideModal');
},
},
data() {
return {
objectives: [
{},
]
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -56,12 +56,6 @@
return this.me.selectedClass; return this.me.selectedClass;
}, },
}, },
methods: {
editObjectiveGroup() {
this.$store.dispatch('editObjectiveGroup', this.group.id);
}
}
} }
</script> </script>

View File

@ -1,64 +0,0 @@
<template>
<modal class="objective-group-form">
<template slot="header">
<h4 class="objective-group-form__heading">Lernziele: {{title}}</h4>
</template>
<objective-form
:objective="objective"
v-for="(objective, index) in initialObjectives"
@input="updateObjective($event, index)"
@delete="removeObjective(index)"
:key="index"></objective-form>
<add-content-element @add-element="addObjective"></add-content-element>
<div slot="footer">
<a class="button button--primary" data-cy="modal-save-button"
@click="$emit('save', initialObjectives)">Speichern</a>
<a class="button" @click="$emit('hide')">Abbrechen</a>
</div>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
import ObjectiveForm from '@/components/objective-groups/ObjectiveForm';
import AddContentElement from '@/components/AddContentElement';
export default {
props: ['title', 'objectives'],
components: {
AddContentElement,
Modal,
ObjectiveForm
},
methods: {
addObjective() {
this.initialObjectives.push({});
},
updateObjective(text, index) {
this.initialObjectives.splice(index, 1, {text});
},
removeObjective(index) {
this.initialObjectives.splice(index, 1);
}
},
data() {
return {
initialObjectives: this.objectives
}
}
}
</script>
<style scoped lang="scss">
.objective-group-form {
&__heading {
margin-bottom: 0;
}
}
</style>

View File

@ -1,28 +1,49 @@
<template> <template>
<div class="avatar"> <div class="avatar">
<transition name="fade"> <transition name="fade">
<user-icon v-show="!isAvatarLoaded" class="avatar__placeholder" :class="{'avatar__placeholder--highlighted': iconHighlighted}"></user-icon> <default-avatar v-show="!isAvatarLoaded" class="avatar__placeholder"
:class="{'avatar__placeholder--highlighted': iconHighlighted}"></default-avatar>
</transition> </transition>
<transition name="show"> <transition name="show">
<div v-show="isAvatarLoaded" class="avatar__image" ref="avatarImage" :style="{'background-image': `url(${this.avatarUrl})`}"></div> <div v-show="isAvatarLoaded" class="avatar__image" ref="avatarImage"
:style="{'background-image': `url(${this.avatarUrl})`}"></div>
</transition> </transition>
<img class="avatar__fake-image" :src="avatarUrl" ref="fakeImage"/> <img class="avatar__fake-image" :src="avatarUrl" ref="fakeImage"/>
<div class="avatar__edit" v-if="editable" @click="closeSidebar">
<router-link :to="{name: 'profile'}">
<pen-icon></pen-icon>
</router-link>
</div>
</div> </div>
</template> </template>
<script> <script>
import DefaultAvatar from '@/components/icons/DefaultAvatar';
import PenIcon from '@/components/icons/PenIcon';
import UserIcon from '@/components/icons/UserIcon'; import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
export default { export default {
props: ['avatarUrl', 'iconHighlighted'], props: {
components: {UserIcon}, avatarUrl: {
data () { type: String
},
iconHighlighted: {},
editable: {
default: false
}
},
components: {
DefaultAvatar,
PenIcon
},
data() {
return { return {
isAvatarLoaded: false isAvatarLoaded: false
} }
}, },
mounted () { mounted() {
if (this.avatarUrl !== '') { if (this.avatarUrl !== '') {
this.$refs.fakeImage.addEventListener('load', () => { this.$refs.fakeImage.addEventListener('load', () => {
if (this.$refs.fakeImage) { if (this.$refs.fakeImage) {
@ -30,7 +51,17 @@
this.isAvatarLoaded = true; this.isAvatarLoaded = true;
} }
}); });
}; }
},
methods: {
closeSidebar() {
this.$apollo.mutate({
mutation: TOGGLE_SIDEBAR,
variables: {
open: false
}
})
}
} }
} }
</script> </script>
@ -45,11 +76,11 @@
width: $max-width; width: $max-width;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
border-radius: 50%;
&__placeholder { &__placeholder {
height: $max-width; height: $max-width;
fill: $color-silver-dark; fill: $color-silver-dark;
border-radius: 50%;
&--highlighted { &--highlighted {
fill: $color-brand; fill: $color-brand;
@ -57,14 +88,14 @@
} }
&__image { &__image {
background-size: cover;
background-position: center center;
height: 100%;
width: 100%;
border: 0;
border-radius: 50%;
background-size: cover; &--landscape {
background-position: center center;
height: 100%;
width: 100%;
border: 0;
&--landscape {
width: auto; width: auto;
height: $max-width; height: $max-width;
} }
@ -75,10 +106,25 @@
height: 0; height: 0;
} }
.fade-leave-active, .show-enter-active { &__edit {
position: absolute;
box-sizing: border-box;
width: 34px;
height: 34px;
display: block;
left: 50%;
bottom: -7px;
transform: translateX(50%);
background-color: $color-white;
border-radius: 50%;
padding: 6px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
}
.fade-leave-active, .show-enter-active {
transition: opacity .5s; transition: opacity .5s;
} }
.fade-leave-to, .show-enter { .fade-leave-to, .show-enter {
opacity: 0; opacity: 0;
} }

View File

@ -0,0 +1,109 @@
<template>
<div class="school-class">
<h2 class="school-class__heading"><span class="school-class__name" data-cy="school-class-name">{{name}}</span>
<edit-class-name v-if="teacher" @edit="editClassName"></edit-class-name>
</h2>
<div class="school-class__members school-class-members">
<ul class="school-class-members__list simple-list simple-list--active" data-cy="active-class-members-list">
<li
class="simple-list__item member-item"
data-cy="school-class-member"
v-for="member in activeMembers"
:key="member.id">
<span class="member-item__name">{{fullName(member)}}</span>
<span class="member-item__role">{{role(member)}}</span>
<!-- <a-->
<!-- class="member-item__action simple-list__action"-->
<!-- data-cy="remove-from-class"-->
<!-- v-if="teacher"-->
<!-- @click="$emit('remove', member)">Deaktivieren</a>-->
</li>
</ul>
<!-- <template v-if="inactiveMembers.length">-->
<!-- <h3 class="school-class__inactive-heading">Deaktivierte Personen</h3>-->
<!-- <ul data-cy="inactive-class-members-list" class="simple-list simple-list&#45;&#45;inactive">-->
<!-- <li-->
<!-- class="simple-list__item member-item"-->
<!-- data-cy="school-class-member"-->
<!-- v-for="member in inactiveMembers"-->
<!-- :key="member.id">-->
<!-- <span class="member-item__name">{{fullName(member)}}</span>-->
<!-- <span class="member-item__role">{{role(member)}}</span>-->
<!-- <a-->
<!-- class="member-item__action simple-list__action"-->
<!-- data-cy="add-to-class"-->
<!-- v-if="teacher"-->
<!-- @click="$emit('add', member)">Aktivieren</a>-->
<!-- </li>-->
<!-- </ul>-->
<!-- </template>-->
</div>
</div>
</template>
<script>
import EditClassName from '@/components/school-class/EditClassName';
export default {
props: ['members', 'name', 'teacher', 'id'],
components: {
EditClassName
},
methods: {
fullName(member) {
return `${member.firstName} ${member.lastName}`;
},
role({isTeacher}) {
return isTeacher ? 'Lehrperson' : 'Schüler';
},
editClassName() {
this.$store.dispatch('editClassName');
}
},
computed: {
activeMembers() {
return this.members.filter(member => member.active)
},
inactiveMembers() {
return this.members.filter(member => !member.active)
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.school-class {
&__inactive-heading {
@include heading-4;
margin-bottom: $small-spacing;
}
&__name {
@include heading-2;
}
}
.member-item {
&__name {
font-family: $sans-serif-font-family;
font-weight: $font-weight-bold;
flex: 2 1 auto;
}
&__role {
flex: 0 1 110px;
text-align: right;
}
&__action {
flex: 0 1 110px;
padding-left: $large-spacing;
}
}
</style>

View File

@ -1,59 +0,0 @@
<template>
<div class="schoolclass">
<h2 class="schoolclass__name">{{name}}</h2>
<div class="schoolclass__members schoolclass-members">
<ul class="schoolclass-members__list members-list">
<li v-for="user in users" :key="user.id" class="members-list__item">
<p class="member-item"><span class="member-item__name">{{fullName(user)}}</span> <span class="member-item__role">{{role(user)}}</span></p>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: ['users', 'name'],
methods: {
fullName (user) {
return `${user.firstName} ${user.lastName}`;
},
role ({permissions}) {
return permissions.indexOf('users.can_manage_school_class_content') > -1 ? 'Lehrperson' : '';
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
$height: 52px;
.members-list {
&__item {
line-height: $height;
height: $height;
border-bottom: 1px solid $color-silver-dark;
}
}
.member-item {
line-height: $height;
height: $height;
display: flex;
flex-direction: row;
justify-content: space-between;
&__name {
font-family: $sans-serif-font-family;
font-weight: $font-weight-bold;
}
&__role {
padding-right: $medium-spacing;
}
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<modal class="deactivate-user" :hide-header="true" :small="true">
<h3 class="deactivate-user__heading">
<template v-if="myself">
Mich
</template>
<template v-else>
Person
</template>
deaktivieren
</h3>
<p class="deactivate-user__text deactivate-user__paragraph">
<template v-if="myself">
Möchten Sie die Klasse <strong class="deactivate-user__text--strong">{{schoolClass}}</strong> verlassen?
</template>
<template v-else>
Möchten Sie <strong
class="deactivate-user__text--strong">{{name}}</strong> in der
Klasse
<strong class="deactivate-user__text--strong">{{schoolClass}}</strong> deaktivieren?
</template>
</p>
<ul class="deactivate-user__list">
<li class="deactivate-user__text deactivate-user__list-item">
<template v-if="myself">
Sie können
</template>
<template v-else>
Diese Person kann
</template>
in Zukunft keine Inhalte mehr erfassen, bearbeiten und teilen.
</li>
<li class="deactivate-user__text deactivate-user__list-item">
<template v-if="myself">
Sie können
</template>
<template v-else>
Diese Person kann
</template>
weiterhin Module und Instrumente lesen.
</li>
<li class="deactivate-user__text deactivate-user__list-item">
<template v-if="myself">
Sie können der Klasse jederzeit wieder beitreten.
</template>
<template v-else>
Sie können diese Person jederzeit wieder aktivieren.
</template>
</li>
</ul>
<div slot="footer">
<a class="button button--primary" data-cy="modal-save-button" v-on:click="confirm">Speichern</a>
<a class="button" v-on:click="cancel">Abbrechen</a>
</div>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
export default {
components: {
Modal
},
computed: {
myself() {
return this.$modal.state.payload.myself;
},
name() {
return this.$modal.state.payload.name;
},
schoolClass() {
return this.$modal.state.payload.className;
}
},
methods: {
confirm() {
this.$modal.confirm();
},
cancel() {
this.$modal.cancel();
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.deactivate-user {
&__heading {
margin-bottom: $medium-spacing;
}
&__list {
padding-left: $medium-spacing;
}
&__list-item {
list-style: disc;
}
&__text {
@include regular-text;
margin-bottom: $medium-spacing;
&--strong {
font-weight: 600;
}
}
}
</style>

View File

@ -1,4 +1,5 @@
<template> <template>
<!-- Not currently in use, but keeping the file in case it's needed again -->
<div class="password-reset"> <div class="password-reset">
<h2 class="password-reset__header">Passwort ändern</h2> <h2 class="password-reset__header">Passwort ändern</h2>
<div v-if="showSuccess" class="success-message"> <div v-if="showSuccess" class="success-message">
@ -7,7 +8,7 @@
<password-change-form <password-change-form
@passwordSubmited="resetPassword" @passwordSubmited="resetPassword"
:oldPasswordErrors="oldPasswordErrors" :oldPasswordErrors="oldPasswordErrors"
:newPasswordErrors="newPasswordErrors" /> :newPasswordErrors="newPasswordErrors"/>
</div> </div>
</template> </template>
@ -29,7 +30,7 @@
} }
}, },
methods: { methods: {
resetPassword (passwords) { resetPassword(passwords) {
this.$apollo.mutate({ this.$apollo.mutate({
mutation: UPDATE_PASSWORD_MUTATION, mutation: UPDATE_PASSWORD_MUTATION,
variables: { variables: {
@ -37,7 +38,7 @@
passwordInput: passwords passwordInput: passwords
} }
} }
}).then(({ data }) => { }).then(({data}) => {
if (data.updatePassword.success) { if (data.updatePassword.success) {
this.oldPasswordErrors = []; this.oldPasswordErrors = [];
this.newPasswordErrors = []; this.newPasswordErrors = [];
@ -56,14 +57,14 @@
console.log('fail', error) console.log('fail', error)
}); });
}, },
handleOldPasswordError (error) { handleOldPasswordError(error) {
this.oldPasswordErrors = error.errors.map((fieldError) => { this.oldPasswordErrors = error.errors.map((fieldError) => {
if (fieldError.code === 'invalid') { if (fieldError.code === 'invalid') {
return 'Die Eingabe ist falsch' return 'Die Eingabe ist falsch'
} }
}); });
}, },
handleNewPasswordError (error) { handleNewPasswordError(error) {
this.newPasswordErrors = error.errors.map((fieldError) => { this.newPasswordErrors = error.errors.map((fieldError) => {
if (fieldError.code === 'invalid') { if (fieldError.code === 'invalid') {
return 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten' return 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten'

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="profile"> <div class="profile">
<h1 class="profile__header">Profil</h1> <h1 class="profile__header">Profilbild</h1>
<h2 class="profile__avatar profile-avatar">Profilbild</h2>
<div class="profile-avatar" v-if="me.avatarUrl" > <div class="profile-avatar" v-if="me.avatarUrl" >
<div class="profile-avatar__image"> <div class="profile-avatar__image">
<avatar :avatarUrl="me.avatarUrl" /> <avatar :avatarUrl="me.avatarUrl" />
@ -11,22 +10,18 @@
</a> </a>
</div> </div>
<avatar-upload-form v-else @avatarUpdate="updateAvatar"/> <avatar-upload-form v-else @avatarUpdate="updateAvatar"/>
<password-change />
</div> </div>
</template> </template>
<script> <script>
import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql'; import UPDATE_AVATAR_QUERY from '@/graphql/gql/mutations/updateAvatarUrl.gql';
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import ME_QUERY from '@/graphql/gql/meQuery.gql';
import PasswordChange from '@/components/profile/PasswordChange';
import AvatarUploadForm from '@/components/profile/AvatarUploadForm'; import AvatarUploadForm from '@/components/profile/AvatarUploadForm';
import Avatar from '@/components/profile/Avatar'; import Avatar from '@/components/profile/Avatar';
import TrashIcon from '@/components/icons/TrashIcon'; import TrashIcon from '@/components/icons/TrashIcon';
export default { export default {
components: { components: {
PasswordChange,
AvatarUploadForm, AvatarUploadForm,
Avatar, Avatar,
TrashIcon TrashIcon

View File

@ -0,0 +1,112 @@
<template>
<div class="profile-sidebar" v-if="sidebar.open" v-click-outside="closeSidebar">
<a class="profile-sidebar__close-link" @click="closeSidebar">
<cross class="profile-sidebar__close-icon"></cross>
</a>
<profile-widget class="profile-sidebar__item"></profile-widget>
<div class="profile-sidebar__item" @click="closeSidebar">
<router-link to="/me/activity" class="profile-sidebar__link">Meine Aktivitäten</router-link>
</div>
<div class="profile-sidebar__item">
<h3 class="profile-sidebar__subtitle">Klasse</h3>
<class-selection-widget></class-selection-widget>
<div @click="closeSidebar">
<router-link :to="{name: 'my-class'}" class="profile-sidebar__link">Klassenliste anzeigen</router-link>
</div>
</div>
<div class="profile-sidebar__item" @click="closeSidebar">
<router-link :to="{name:'join-class'}" data-cy="join-class-link" class="profile-sidebar__link">Zugangscode
eingeben
</router-link>
</div>
<div class="profile-sidebar__item">
<logout-widget></logout-widget>
</div>
<p class="profile-sidebar__support">
Supportanfragen: rahel.wenger@hep-verlag.ch
</p>
</div>
</template>
<script>
import ProfileWidget from '@/components/profile/ProfileWidget';
import Cross from '@/components/icons/Cross';
import ClassSelectionWidget from '@/components/school-class/ClassSelectionWidget';
import sidebarMixin from '@/mixins/sidebar';
import LogoutWidget from '@/components/LogoutWidget';
export default {
components: {
LogoutWidget,
ClassSelectionWidget,
ProfileWidget,
Cross
},
mixins: [sidebarMixin],
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.profile-sidebar {
padding: $large-spacing 0;
box-sizing: border-box;
position: fixed;
right: 0;
top: 0;
bottom: 0;
height: 100vh;
background-color: $color-white;
z-index: 15;
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
overflow-y: scroll;
width: 100%;
@include desktop {
width: 333px;
}
display: flex;
flex-direction: column;
&__item {
border-bottom: 1px solid $color-silver-light;
padding: $large-spacing $medium-spacing;
}
&__subtitle {
@include small-text;
margin: 0;
margin-bottom: $small-spacing;
}
&__link {
@include default-link;
display: block;
}
&__close-link {
position: absolute;
right: $small-spacing;
top: $small-spacing;
cursor: pointer;
}
&__close-icon {
width: 40px;
height: 40px;
}
&__support {
padding: $small-spacing;
@include regular-text;
color: $color-silver-dark;
}
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<div class="profile-widget">
<div class="profile-widget__avatar">
<avatar :avatar-url="me.avatarUrl" :editable="true"></avatar>
</div>
<h3 class="profile-widget__name">{{me.firstName}} {{me.lastName}}</h3>
</div>
</template>
<script>
import Avatar from '@/components/profile/Avatar';
import {meQuery} from '@/graphql/queries';
export default {
components: {
Avatar
},
apollo: {
me: meQuery
},
data: () => ({
me: {}
})
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.profile-widget {
display: flex;
flex-direction: column;
align-items: center;
&__name {
@include heading-3;
text-align: center;
margin-bottom: $small-spacing;
}
&__avatar {
display: flex;
justify-content: center;
margin-bottom: $medium-spacing;
width: 80px;
height: 80px;
position: relative;
}
}
</style>

View File

@ -0,0 +1,129 @@
<template>
<div class="class-selection" v-if="currentClassSelection">
<div data-cy="class-selection" class="class-selection__selected-class selected-class"
@click="showPopover = !showPopover">
<current-class class="selected-class__text"></current-class>
<chevron-down class="selected-class__dropdown-icon"></chevron-down>
</div>
<widget-popover v-if="showPopover"
@hide-me="showPopover = false"
:mobile="mobile"
class="class-selection__popover">
<li data-cy="class-selection-entry" class="popover-links__link popover-links__link--large"
v-for="schoolClass in me.schoolClasses"
:key="schoolClass.id"
:label="schoolClass.name"
:item="schoolClass"
@click="updateSelectedClassAndHidePopover(schoolClass)">
{{schoolClass.name}}
</li>
<li class="popover-links__link popover-links__link--large popover-links__divider"
v-if="me.isTeacher"
data-cy="create-class-link" @click="closeSidebar">
<router-link tag="span" class="popover-links__link-with-icon" :to="{name: 'create-class'}">
<add-icon class="popover-links__icon"/>
<span>Klasse erfassen</span>
</router-link>
</li>
<!-- <li class="popover-links__link popover-links__link&#45;&#45;large popover-links__divider" @click="closeSidebar">-->
<!-- <router-link tag="span" :to="{name: 'old-classes'}">Alte Klassen anzeigen</router-link>-->
<!-- </li>-->
</widget-popover>
</div>
</template>
<script>
import WidgetPopover from '@/components/WidgetPopover';
import ChevronDown from '@/components/icons/ChevronDown';
import CurrentClass from '@/components/school-class/CurrentClass';
import AddIcon from '@/components/icons/AddIcon';
import updateSelectedClassMixin from '@/mixins/updateSelectedClass';
import sidebarMixin from '@/mixins/sidebar';
import meMixin from '@/mixins/me';
export default {
components: {
WidgetPopover,
ChevronDown,
CurrentClass,
AddIcon
},
props: {
mobile: {
type: Boolean,
default: false
}
},
mixins: [updateSelectedClassMixin, sidebarMixin, meMixin],
data() {
return {
showPopover: false
}
},
methods: {
updateSelectedClassAndHidePopover(selectedClass) {
this.updateSelectedClass(selectedClass);
this.showPopover = false;
this.closeSidebar();
}
},
computed: {
currentClassSelection() {
let currentClass = this.me.schoolClasses.find(schoolClass => {
return schoolClass.id === this.me.selectedClass.id
});
return currentClass || this.me.schoolClasses[0];
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.class-selection {
position: relative;
cursor: pointer;
margin-bottom: $medium-spacing;
border: 1px solid $color-silver;
border-radius: 4px;
&__popover {
white-space: nowrap;
top: 100%;
left: 0;
transform: translateY($small-spacing);
}
}
.selected-class {
width: 100%;
box-sizing: border-box;
padding: $small-spacing $medium-spacing;
display: flex;
align-items: center;
justify-content: space-between;
&__text {
line-height: $large-spacing;
@include regular-text;
color: $color-silver-dark;
}
&__dropdown-icon {
width: 20px;
height: 20px;
fill: $color-brand;
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<span class="current-class" data-cy="current-class-name">{{currentClassName}}</span>
</template>
<script>
import me from '@/mixins/me';
export default {
mixins: [me],
computed: {
currentClassName() {
let currentClass = this.me.schoolClasses.find(schoolClass => {
return schoolClass.id === this.me.selectedClass.id
});
return currentClass ? currentClass.name : this.me.schoolClasses.length ? this.me.schoolClasses[0].name : '';
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.current-class {
line-height: $large-spacing;
@include regular-text;
color: $color-silver-dark;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<a class="edit-class-name" @click="$emit('edit')" data-cy="edit-class-name-link">
<pen-icon class="edit-class-name__icon"></pen-icon>
</a>
</template>
<script>
import PenIcon from '@/components/icons/PenIcon';
export default {
components: {
PenIcon
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.edit-class-name {
&__icon {
width: 20px;
height: 20px;
fill: $color-brand;
}
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<modal :hide-header="false" :small="true" title="Hello">
<h4 slot="header">Klasse bearbeiten</h4>
<modal-input v-on:input="name = $event"
placeholder="Klassenname"
data-cy="edit-class-name-input"
:value="name"
></modal-input>
<div slot="footer">
<a class="button button--primary" data-cy="modal-save-button"
@click="save">Speichern</a>
<a class="button" @click="hide">Abbrechen</a>
</div>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
import ModalInput from '@/components/ModalInput';
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
import UPDATE_SCHOOL_CLASS_MUTATION from '@/graphql/gql/mutations/updateSchoolClass.gql';
export default {
components: {
Modal,
ModalInput
},
methods: {
save() {
this.$apollo.mutate({
mutation: UPDATE_SCHOOL_CLASS_MUTATION,
variables: {
input: {
name: this.name,
id: this.schoolClass.id
}
},
update(store, {data: {updateSchoolClass: {schoolClass: {name}}}}) {
let query = MY_SCHOOL_CLASS_QUERY;
let data = store.readQuery({query});
data.me.selectedClass.name = name;
store.writeQuery({query, data});
}
});
this.hide();
},
hide() {
this.$store.dispatch('hideModal');
}
},
apollo: {
schoolClass: {
query: MY_SCHOOL_CLASS_QUERY,
update(data) {
return this.$getRidOfEdges(data).me.selectedClass
}
}
},
mounted() {
this.name = this.schoolClass ? this.schoolClass.name : '';
},
data() {
return {
name: ''
}
}
}
</script>

View File

@ -7,6 +7,22 @@ import fetch from 'unfetch'
import {typeDefs} from '@/graphql/typedefs'; import {typeDefs} from '@/graphql/typedefs';
import {resolvers} from '@/graphql/resolvers'; import {resolvers} from '@/graphql/resolvers';
const writeLocalCache = cache => {
// we use the cache as our local state
cache.writeData({
data: {
scrollPosition: {
__typename: 'ScrollPosition',
scrollTo: ''
},
sidebar: {
__typename: 'Sidebar',
open: false
}
}
});
};
export default function (uri) { export default function (uri) {
const httpLink = createHttpLink({ const httpLink = createHttpLink({
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/', // uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
@ -96,23 +112,19 @@ export default function (uri) {
} }
}; };
// we use the cache as our local state writeLocalCache(cache);
cache.writeData({
data: {
scrollPosition: {
__typename: 'ScrollPosition',
scrollTo: ''
}
}
});
// Create the apollo client // Create the apollo client
return new ApolloClient({ const client = new ApolloClient({
link: composedLink, link: composedLink,
// link: httpLink, // link: httpLink,
cache, cache,
connectToDevTools: true, connectToDevTools: true,
typeDefs, typeDefs,
resolvers resolvers
}) });
client.onResetStore(() => {
writeLocalCache(cache);
});
return client;
} }

View File

@ -0,0 +1,3 @@
mutation($open: Boolean!) {
toggleSidebar(open: $open) @client
}

View File

@ -0,0 +1,5 @@
query Sidebar {
sidebar @client {
open
}
}

View File

@ -3,6 +3,7 @@
query MeQuery { query MeQuery {
me { me {
...UserParts ...UserParts
isTeacher
permissions permissions
} }
} }

View File

@ -1,16 +0,0 @@
#import "../fragments/objectiveGroupParts.gql"
mutation AddObjectiveGroup($input: AddObjectiveGroupInput!) {
addObjectiveGroup(input: $input) {
objectiveGroup {
...ObjectiveGroupParts
objectives {
edges {
node {
id
text
}
}
}
}
}
}

View File

@ -0,0 +1,5 @@
mutation AddRemoveMember($input: AddRemoveMemberInput!) {
addRemoveMember(input: $input) {
success
}
}

View File

@ -0,0 +1,9 @@
mutation CreateSchoolClass($input: CreateSchoolClassInput!) {
createSchoolClass(input: $input) {
success
schoolClass {
id
name
}
}
}

View File

@ -0,0 +1,9 @@
mutation JoinClass($input: JoinClassInput!) {
joinClass(input: $input) {
success
schoolClass {
id
name
}
}
}

View File

@ -1,16 +0,0 @@
#import "../fragments/objectiveGroupParts.gql"
mutation UpdateObjectiveGroup($input: UpdateObjectiveGroupInput!) {
updateObjectiveGroup(input: $input) {
objectiveGroup {
...ObjectiveGroupParts
objectives {
edges {
node {
id
text
}
}
}
}
}
}

View File

@ -0,0 +1,8 @@
mutation UpdateSchoolClass($input: UpdateSchoolClassInput!) {
updateSchoolClass(input: $input) {
success
schoolClass {
name
}
}
}

View File

@ -0,0 +1,18 @@
query MySchoolClassQuery {
me {
id
isTeacher
selectedClass {
id
name
code
members {
id
firstName
lastName
isTeacher
active
}
}
}
}

View File

@ -1,23 +0,0 @@
query {
me {
id
schoolClasses {
edges {
node {
id
name
users {
edges {
node {
id
firstName
lastName
permissions
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,13 @@
query OldClassesQuery {
me {
id
oldClasses {
edges {
node {
id
name
}
}
}
}
}

View File

@ -1,4 +1,5 @@
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql'; import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
export const resolvers = { export const resolvers = {
Mutation: { Mutation: {
@ -7,6 +8,12 @@ export const resolvers = {
data.scrollPosition.scrollTo = scrollTo; data.scrollPosition.scrollTo = scrollTo;
cache.writeQuery({query: SCROLL_POSITION, data}); cache.writeQuery({query: SCROLL_POSITION, data});
return data.scrollPosition; return data.scrollPosition;
},
toggleSidebar: (_, {open}, {cache}) => {
const data = cache.readQuery({query: SIDEBAR});
data.sidebar.open = open;
cache.writeQuery({query: SIDEBAR, data});
return data.sidebar;
} }
} }
}; };

View File

@ -5,6 +5,10 @@ export const typeDefs = gql`
scrollTo: String! scrollTo: String!
} }
type Sidebar {
open: Boolean!
}
type Mutation { type Mutation {
scrollTo(scrollTo: String!): ScrollPosition scrollTo(scrollTo: String!): ScrollPosition
} }

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="blank-layout"> <div class="blank-layout">
<profile-sidebar></profile-sidebar>
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
@ -14,9 +15,9 @@
</style> </style>
<script> <script>
import ProfileSidebar from '@/components/profile/ProfileSidebar';
export default { export default {
components: {}, components: {ProfileSidebar},
} }
</script> </script>

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="container skillbox" :class="specialContainerClass"> <div class="container skillbox" :class="specialContainerClass">
<profile-sidebar></profile-sidebar>
<header-bar class="header skillbox__header"> <header-bar class="header skillbox__header">
</header-bar> </header-bar>
@ -13,11 +14,13 @@
<script> <script>
import HeaderBar from '@/components/HeaderBar'; import HeaderBar from '@/components/HeaderBar';
import MobileHeader from '@/components/MobileHeader'; import MobileHeader from '@/components/MobileHeader';
import ProfileSidebar from '@/components/profile/ProfileSidebar';
export default { export default {
components: { components: {
HeaderBar, HeaderBar,
MobileHeader MobileHeader,
ProfileSidebar
}, },
computed: { computed: {

View File

@ -17,32 +17,13 @@ import {dateFilter} from './filters/date-filter';
import autoGrow from '@/directives/auto-grow' import autoGrow from '@/directives/auto-grow'
import clickOutside from '@/directives/click-outside' import clickOutside from '@/directives/click-outside'
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import ME_QUERY from '@/graphql/gql/meQuery.gql';
import VueModal from '@/plugins/modal';
import VueRemoveEdges from '@/plugins/edges';
Vue.config.productionTip = false; Vue.config.productionTip = false;
// TODO: Move into a separate project as a plugin Vue.use(VueModal);
// Vue.use(VueRemoveEdges);
function getRidOfEdges(collection) {
if (typeof collection === 'object' && collection && !Array.isArray(collection)) {
let newObj = {};
for (const k in collection) {
if (k === 'edges') {
return collection.edges.map(edge => getRidOfEdges(edge.node));
} else {
newObj[k] = getRidOfEdges(collection[k]);
if (newObj[k]) {
// delete newObj[k]['__typename']
}
}
}
return newObj
} else {
return collection
}
}
Object.defineProperty(Vue.prototype, '$getRidOfEdges', {value: getRidOfEdges});
Vue.use(VueApollo); Vue.use(VueApollo);
Vue.use(VueAxios, axios); Vue.use(VueAxios, axios);
Vue.use(VueVimeoPlayer); Vue.use(VueVimeoPlayer);
@ -141,6 +122,7 @@ router.beforeEach(async (to, from, next) => {
// handle logout // handle logout
if (to.path === '/logout') { if (to.path === '/logout') {
privateApolloClient.resetStore(); privateApolloClient.resetStore();
publicApolloClient.resetStore();
next({name: 'login'}); next({name: 'login'});
return return
} }
@ -151,8 +133,8 @@ router.beforeEach(async (to, from, next) => {
return return
} }
if (to.name !== 'noClass' && loginRequired(to) && await redirectStudentsWithoutClass()) { if (to.name !== 'join-class' && loginRequired(to) && await redirectStudentsWithoutClass()) {
next({name: 'noClass'}) next({name: 'join-class'})
return return
} }

View File

@ -0,0 +1,27 @@
import ME_QUERY from '@/graphql/gql/meQuery';
export default {
methods: {
addSchoolClass(store, schoolClass) {
const query = ME_QUERY;
if (schoolClass) {
console.log('updating school class');
const data = store.readQuery({query});
if (data) {
data.me.schoolClasses.edges = [
...data.me.schoolClasses.edges,
{
node: schoolClass,
__typename: 'SchoolClassNodeEdge'
}
];
data.me.selectedClass = {
id: schoolClass.id,
__typename: 'SchoolClassNode'
};
store.writeQuery({query, data});
}
}
},
}
}

27
client/src/mixins/me.js Normal file
View File

@ -0,0 +1,27 @@
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default {
data() {
return {
me: {
selectedClass: {
id: ''
},
permissions: [],
schoolClasses: [],
isTeacher: false
},
showPopover: false
}
},
apollo: {
me: {
query: ME_QUERY,
update(data) {
return this.$getRidOfEdges(data).me;
},
fetchPolicy: 'cache-first'
},
},
}

View File

@ -0,0 +1,16 @@
import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
export default {
methods: {
openSidebar() {
this.$nextTick(() => { // we don't want this to happen instantly, only almost instantly. Otherwise the click-outside-directive won't work
this.$apollo.mutate({
mutation: TOGGLE_SIDEBAR,
variables: {
open: true
}
});
});
},
},
}

View File

@ -0,0 +1,24 @@
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
export default {
apollo: {
me: {
query: MY_SCHOOL_CLASS_QUERY,
update(data) {
return this.$getRidOfEdges(data).me
}
}
},
data() {
return {
me: {
isTeacher: false,
selectedClass: {
name: '',
members: []
}
}
}
}
}

View File

@ -0,0 +1,29 @@
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
import TOGGLE_SIDEBAR from '@/graphql/gql/local/mutations/toggleSidebar.gql';
export default {
methods: {
closeSidebar() {
if (this.sidebar.open) {
this.$apollo.mutate({
mutation: TOGGLE_SIDEBAR,
variables: {
open: false
}
});
}
},
},
apollo: {
sidebar: {
query: SIDEBAR
}
},
data: () => ({
sidebar: {
open: false
}
})
}

View File

@ -0,0 +1,30 @@
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import UPDATE_USER_SETTING from '@/graphql/gql/mutations/updateUserSetting.gql';
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
export default {
methods: {
updateSelectedClass(selectedClass) {
return this.$apollo.mutate({
mutation: UPDATE_USER_SETTING,
variables: {
input: {
id: selectedClass.id
}
},
update(store, data) {
let meData = store.readQuery({query: ME_QUERY});
meData.me.selectedClass = selectedClass;
store.writeQuery({query: ME_QUERY, data: meData});
},
refetchQueries: [{
query: MY_SCHOOL_CLASS_QUERY
}]
}).catch((error) => {
console.warn('failed to update selected class', error)
});
}
},
}

View File

@ -0,0 +1,83 @@
<template>
<div class="create-class">
<h1 class="create-class__title">Klasse erfassen</h1>
<div>
<div class="skillboxform-input">
<label for="class-name" class="skillboxform-input__label">Name</label>
<input
id="class-name"
class="skillbox-input skillboxform-input__input"
:class="{'skillboxform-input__input--error': error}"
data-cy="input-class-name"
:value="name"
@input="updateName">
<small
v-if="error"
class="skillboxform-input__error"
data-cy="email-local-errors"
>{{ error }}
</small>
</div>
<div>
<a class="button button--primary button--big" data-cy="create-class" @click="createClass(name)">Klasse
erfassen</a>
<a class="button button--big" data-cy="create-class-cancel" @click="cancel">Abbrechen</a>
</div>
</div>
</div>
</template>
<script>
import addSchoolClassMixin from '@/mixins/add-school-class';
import CREATE_CLASS_MUTATION from '@/graphql/gql/mutations/createClass.gql';
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
export default {
mixins: [addSchoolClassMixin],
data: () => ({
name: '',
error: ''
}),
methods: {
updateName(event) {
this.name = event.target.value;
this.error = '';
},
createClass(name) {
let self = this;
this.$apollo.mutate({
mutation: CREATE_CLASS_MUTATION,
variables: {
input: {
name
}
},
update(store, {data: {createSchoolClass: {schoolClass}}}) {
self.addSchoolClass(store, schoolClass);
self.$router.push({
name: 'my-class'
});
},
refetchQueries: [{
query: MY_SCHOOL_CLASS_QUERY
}]
});
},
cancel() {
this.$router.go(-1);
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.create-class {
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div>
<h1 data-cy="join-class-title">Zugangscode eingeben</h1>
<div>
<div class="skillboxform-input">
<label for="join-code" class="skillboxform-input__label">Zugangscode</label>
<input
id="join-code"
class="skillbox-input skillboxform-input__input"
:class="{'skillboxform-input__input--error': error}"
data-cy="input-class-code"
:value="code"
@input="updateCode">
<small
v-if="error"
class="skillboxform-input__error"
data-cy="email-local-errors"
>{{ error }}
</small>
</div>
<div>
<a class="button button--primary button--big" data-cy="join-class" @click="joinClass(code)">Klasse beitreten</a>
<a class="button button--big" data-cy="join-class-cancel" @click="cancel">Abbrechen</a>
</div>
</div>
</div>
</template>
<script>
import JOIN_CLASS_MUTATION from '@/graphql/gql/mutations/joinClass.gql';
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
import addSchoolClassMixin from '@/mixins/add-school-class';
export default {
mixins: [addSchoolClassMixin],
data: () => ({
code: '',
error: ''
}),
methods: {
updateCode(event) {
this.code = event.target.value;
this.error = '';
},
joinClass(code) {
let self = this;
this.$apollo.mutate({
mutation: JOIN_CLASS_MUTATION,
variables: {
input: {
code
}
},
update(store, {data: {joinClass: {schoolClass}}}) {
self.addSchoolClass(store, schoolClass);
self.$router.push({name: 'my-class'});
},
refetchQueries: [{query: MY_SCHOOL_CLASS_QUERY}]
})
.then(() => {
})
.catch(e => {
console.debug(e);
if (e.message.indexOf('[CAJ]') > -1) {
this.error = 'Sie sind dieser Klasse bereits beigetreten.';
} else {
this.error = 'Dieser Zugangscode ist nicht gültig.';
}
})
},
cancel() {
this.$router.go(-1);
}
}
}
</script>

View File

@ -19,7 +19,7 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.module-page { .module-page {
display: -ms-grid; display: flex;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
} }

View File

@ -25,7 +25,7 @@
<script> <script>
import MODULE_ROOM_ENTRIES_QUERY from '@/graphql/gql/moduleRoomEntryQuery.gql'; import MODULE_ROOM_ENTRIES_QUERY from '@/graphql/gql/moduleRoomEntryQuery.gql';
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import ME_QUERY from '@/graphql/gql/meQuery.gql';
import roomMixin from '@/components/mixins/room' import roomMixin from '@/mixins/room'
export default { export default {
props: ['slug'], props: ['slug'],

View File

@ -0,0 +1,105 @@
<template>
<div class="my-class">
<h1 class="my-class__header" data-cy="class-list-title">Klassenliste</h1>
<router-link class="my-class__code-link button button--primary"
v-if="me.isTeacher"
:to="{name: 'show-code'}">Zugangscode anzeigen
</router-link>
<class-list
class="my-class__class"
:name="me.selectedClass.name"
:members="me.selectedClass.members"
:teacher="me.isTeacher"
:id="me.selectedClass.id"
@remove="remove"
@add="add"
></class-list>
</div>
</template>
<script>
import ClassList from '@/components/profile/ClassList';
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass.gql';
import ADD_REMOVE_MEMBER_MUTATION from '@/graphql/gql/mutations/addRemoveMember.gql';
import selectedClassMixin from '@/mixins/selected-class';
export default {
mixins: [selectedClassMixin],
components: {
ClassList
},
methods: {
changeMember(member, active) {
this.$apollo.mutate({
mutation: ADD_REMOVE_MEMBER_MUTATION,
variables: {
input: {
member: member.id,
schoolClass: this.me.selectedClass.id,
active
}
},
update(store, {data: {addRemoveMember: {success}}}) {
if (success) {
const query = MY_SCHOOL_CLASS_QUERY;
const data = store.readQuery({query});
let memberIndex = data.me.selectedClass.members.findIndex(m => m.id === member.id);
data.me.selectedClass.members = [
...data.me.selectedClass.members.slice(0, memberIndex),
{...member, active},
...data.me.selectedClass.members.slice(memberIndex + 1),
];
store.writeQuery({query, data});
}
}
});
},
add(member) {
this.changeMember(member, true);
},
remove(member) {
this.$modal.open('deactivate-person', {
myself: member.id === this.me.id,
name: `${member.firstName} ${member.lastName}`,
className: this.me.selectedClass.name,
})
.then(() => {
this.changeMember(member, false);
})
.catch(() => {
});
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.my-class {
display: grid;
grid-template-columns: auto auto;
grid-template-rows: auto auto;
grid-template-areas: "h b" "c c";
&__header {
grid-area: h;
}
&__code-link {
grid-area: b;
justify-self: end;
align-self: center;
}
&__class {
grid-area: c;
width: 100%;
margin-bottom: $large-spacing;
}
}
</style>

View File

@ -1,44 +0,0 @@
<template>
<div class="myclasses">
<h1 class="myclasses__header">Klassenliste</h1>
<classlist v-for="schoolClass in schoolClasses" v-bind="schoolClass" :key="schoolClass.name" class="myclasses__class"></classlist>
</div>
</template>
<script>
import MY_SCHOOL_CLASSES_QUERY from '@/graphql/gql/mySchoolClasses.gql';
import Classlist from '@/components/profile/Classlist';
export default {
components: {
Classlist
},
apollo: {
schoolClasses: {
query: MY_SCHOOL_CLASSES_QUERY,
update(data) {
return this.$getRidOfEdges(data).me.schoolClasses
}
}
},
data() {
return {
schoolClasses: []
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.myclasses {
&__class {
margin-bottom: $large-spacing;
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<div class="old-classes">
<h1 class="old-classes__title">Alte Klassen</h1>
<ul class="old-classes__list simple-list">
<li class="simple-list__item" v-for="schoolClass in me.oldClasses" :key="schoolClass.id" data-cy="old-class-item"><span
class="old-classes__class-name">{{schoolClass.name}}</span> <a
class="simple-list__action" @click="updateSelectedClassAndGoToClassList(schoolClass)">Anzeigen</a>
</li>
</ul>
</div>
</template>
<script>
import OLD_CLASSES_QUERY from '@/graphql/gql/oldClasses.gql';
import updateSelectedClassMixin from '@/mixins/updateSelectedClass';
export default {
mixins: [updateSelectedClassMixin],
methods: {
updateSelectedClassAndGoToClassList(selectedClass) {
this.updateSelectedClass(selectedClass).then(() => {
this.$router.push({name: 'my-class'});
});
}
},
// todo: test this in front- and backend
apollo: {
me: {
query: OLD_CLASSES_QUERY,
update(data) {
return this.$getRidOfEdges(data).me;
}
}
},
data: () => ({
me: {
oldClasses: []
}
})
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.old-classes {
&__class-name {
font-family: $sans-serif-font-family;
}
}
</style>

View File

@ -1,16 +1,5 @@
<template> <template>
<div class="profile"> <div class="profile">
<nav class="top-navigation profile-submenu profile__submenu">
<router-link to="/me/activity" active-class="top-navigation__link--active"
class="top-navigation__link profile-submenu__item submenu-item">Aktivität
</router-link>
<router-link to="/me/myclasses" active-class="top-navigation__link--active"
class="top-navigation__link profile-submenu__item submenu-item">Klassenliste
</router-link>
<router-link to="/me/profile" active-class="top-navigation__link--active"
class="top-navigation__link profile-submenu__item submenu-item">Profil
</router-link>
</nav>
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
@ -21,26 +10,15 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.profile { .profile {
padding: $medium-spacing; padding: $large-spacing;
max-width: 640px; max-width: 640px;
margin: 0 auto; margin: 0 auto;
width: 100%;
box-sizing: border-box;
@include desktop { @include desktop {
max-width: 1024px; max-width: 800px;
margin: 0; margin: 0 auto;
}
&__submenu {
margin-bottom: $medium-spacing;
margin-left: -$medium-spacing;
} }
} }
.profile-submenu {
&__item {
font-family: $sans-serif-font-family;
font-size: toRem(14px);
}
}
</style> </style>

View File

@ -25,7 +25,7 @@
<script> <script>
import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql'; import ROOM_ENTRIES_QUERY from '@/graphql/gql/roomEntriesQuery.gql';
import roomMixin from '@/components/mixins/room' import roomMixin from '@/mixins/room'
export default { export default {
props: ['slug'], props: ['slug'],

View File

@ -0,0 +1,32 @@
<template>
<div class="show-code">
<h2 class="show-code__title">Zugangscode Klasse {{me.selectedClass.name}}</h2>
<h1 class="show-code__code">{{me.selectedClass.code}}</h1>
</div>
</template>
<script>
import selectedClassMixin from '@/mixins/selected-class';
export default {
mixins: [selectedClassMixin],
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.show-code {
&__title {
@include regular-text;
margin-bottom: 2*$large-spacing;
}
&__code {
font-size: toRem(120px);
letter-spacing: toRem(20px);
font-weight: 600;
}
}
</style>

View File

@ -40,9 +40,9 @@
<news-teaser date="11. März 2020" title="Brexit" <news-teaser date="11. März 2020" title="Brexit"
url="https://myskillbox-abu-news.webflow.io/brexit"></news-teaser> url="https://myskillbox-abu-news.webflow.io/brexit"></news-teaser>
<news-teaser date="20. Dezember 2019" title="Blockchain" <news-teaser date="20. Dezember 2019" title="Blockchain"
url="http://abunews-blockchain.webflow.io/"></news-teaser> url="https://myskillbox-abu-news.webflow.io/blockchain"></news-teaser>
<news-teaser date="9. September 2019" title="Klima was sonst?" <news-teaser date="9. September 2019" title="Klima was sonst?"
url="https://abunews-1f178193a10edaabf-4caff67b27d10.webflow.io/"></news-teaser> url="https://myskillbox-abu-news.webflow.io/klima"></news-teaser>
<!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"--> <!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
<!--url="https://abunews.webflow.io/"></news-teaser>--> <!--url="https://abunews.webflow.io/"></news-teaser>-->
<div class="news__more">Mehr...</div> <div class="news__more">Mehr...</div>

View File

@ -3,7 +3,7 @@
<h1 class="survey-page__title">{{title}}</h1> <h1 class="survey-page__title">{{title}}</h1>
<survey :survey='survey'></survey> <survey :survey='survey'></survey>
<solution :value="solution" v-if="module.solutionsEnabled || isTeacher"></solution> <solution :value="solution" v-if="showSolution"></solution>
<div v-if="surveyComplete"> <div v-if="surveyComplete">
<a class="button button--primary" @click="reopen">Übung bearbeiten</a> <a class="button button--primary" @click="reopen">Übung bearbeiten</a>
</div> </div>
@ -50,6 +50,9 @@
surveyComplete() { surveyComplete() {
return this.survey && this.survey.isCompleted return this.survey && this.survey.isCompleted
}, },
showSolution() {
return (module.solutionsEnabled || isTeacher) && !this.survey.isCompleted
},
solution() { solution() {
return { return {
text: this.answers.reduce((previous, answer) => { text: this.answers.reduce((previous, answer) => {

View File

@ -0,0 +1,28 @@
const getRidOfEdges = (collection) => {
if (typeof collection === 'object' && collection && !Array.isArray(collection)) {
let newObj = {};
for (const k in collection) {
if (collection.hasOwnProperty(k)) {
if (k === 'edges') {
return collection.edges.map(edge => getRidOfEdges(edge.node));
} else {
newObj[k] = getRidOfEdges(collection[k]);
if (newObj[k]) {
// delete newObj[k]['__typename']
}
}
}
}
return newObj
} else {
return collection
}
};
const EdgesPlugin = {
install(Vue, options) {
Vue.prototype.$getRidOfEdges = getRidOfEdges;
}
};
export default EdgesPlugin;

View File

@ -0,0 +1,57 @@
// adapted from
// https://stackoverflow.com/questions/41791193/vuejs-reactive-binding-for-a-plugin-how-to/41801107#41801107
import Vue from 'vue';
class ModalStore {
constructor(data = {}) {
this.vm = new Vue({
data: () => ({
component: '',
payload: {}
})
});
}
get state() {
return this.vm.$data;
}
}
const ModalPlugin = {
install(Vue, options) {
const store = new ModalStore({});
const reset = () => {
store.state.component = '';
store.state.payload = {};
};
Vue.prototype.$modal = {
state: store.state,
component: store.state.component,
payload: store.state.payload,
open: (component, payload) => {
store.state.payload = payload;
store.state.component = component;
return new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
},
confirm: () => {
reset();
this._resolve();
},
cancel: () => {
reset();
this._reject();
},
_resolve: () => {
},
_reject: () => {
},
};
}
};
export default ModalPlugin;

View File

@ -19,7 +19,7 @@ import portfolio from '@/pages/portfolio'
import project from '@/pages/project' import project from '@/pages/project'
import profilePage from '@/pages/profile' import profilePage from '@/pages/profile'
import profile from '@/components/profile/Profile' import profile from '@/components/profile/Profile'
import myClasses from '@/pages/myClasses' import myClass from '@/pages/myClass'
import activity from '@/pages/activity' import activity from '@/pages/activity'
import Router from 'vue-router' import Router from 'vue-router'
import editProject from '@/pages/editProject' import editProject from '@/pages/editProject'
@ -30,6 +30,10 @@ import moduleRoom from '@/pages/moduleRoom'
import login from '@/pages/login' import login from '@/pages/login'
import registration from '@/pages/registration' import registration from '@/pages/registration'
import waitForClass from '@/pages/waitForClass' import waitForClass from '@/pages/waitForClass'
import joinClass from '@/pages/joinClass'
import oldClasses from '@/pages/oldClasses';
import createClass from '@/pages/createClass';
import showCode from '@/pages/showCode';
import store from '@/store/index'; import store from '@/store/index';
@ -107,11 +111,20 @@ const routes = [
component: profilePage, component: profilePage,
children: [ children: [
{path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}}, {path: 'profile', name: 'profile', component: profile, meta: {isProfile: true}},
{path: 'myclasses', name: 'my-classes', component: myClasses, meta: {isProfile: true}}, {path: 'my-class', name: 'my-class', component: myClass, meta: {isProfile: true}},
{path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}}, {path: 'activity', name: 'activity', component: activity, meta: {isProfile: true}},
{path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}}, {path: '', name: 'profile-activity', component: activity, meta: {isProfile: true}},
{
path: 'old-classes',
name: 'old-classes',
component: oldClasses,
meta: {isProfile: true}
},
{path: 'create-class', name: 'create-class', component: createClass, meta: {layout: 'simple'}},
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
] ]
}, },
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
{ {
path: '/survey/:id', path: '/survey/:id',
component: surveyPage, component: surveyPage,

View File

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex);
// WARNING fixme todo: please do not use this anymore, use the local GraphQL cache // WARNING fixme todo: please do not use this anymore, use the local GraphQL cache
export default new Vuex.Store({ export default new Vuex.Store({
@ -19,7 +19,6 @@ export default new Vuex.Store({
currentRoomEntry: '', currentRoomEntry: '',
parentRoom: null, parentRoom: null,
parentModule: '', parentModule: '',
objectiveGroupType: '',
currentObjectiveGroup: '', currentObjectiveGroup: '',
parentProject: null, parentProject: null,
currentNote: null, currentNote: null,
@ -34,7 +33,10 @@ export default new Vuex.Store({
scrollToAssignmentId: '', scrollToAssignmentId: '',
scrollToAssignmentReady: false, scrollToAssignmentReady: false,
scrollingToAssignment: false, scrollingToAssignment: false,
editModule: false editModule: false,
modulePayload: [],
modalResolve: () => {},
modalReject: () => {},
}, },
getters: { getters: {
@ -51,12 +53,21 @@ export default new Vuex.Store({
currentNote: state => state.currentNote, currentNote: state => state.currentNote,
currentNoteParent: state => state.currentNoteParent, currentNoteParent: state => state.currentNoteParent,
noteType: state => state.noteType, noteType: state => state.noteType,
modulePayload: state => state.modulePayload
}, },
actions: { actions: {
setSpecialContainerClass({commit}, payload) { setSpecialContainerClass({commit}, payload) {
commit('setSpecialContainerClass', payload); commit('setSpecialContainerClass', payload);
}, },
confirmModal({dispatch, state}) {
dispatch('hideModal');
state.modalResolve();
},
cancelModal({dispatch, state}) {
dispatch('hideModal');
state.modalReject();
},
hideModal({commit, dispatch}) { hideModal({commit, dispatch}) {
document.body.classList.remove('no-scroll'); // won't get at the body any other way document.body.classList.remove('no-scroll'); // won't get at the body any other way
commit('setModal', false); commit('setModal', false);
@ -70,8 +81,6 @@ export default new Vuex.Store({
commit('setContentBlockPosition', {}); commit('setContentBlockPosition', {});
commit('setParentRoom', null); commit('setParentRoom', null);
commit('setParentModule', ''); commit('setParentModule', '');
// todo: remove
commit('setObjectiveGroupType', '');
commit('setCurrentObjectiveGroup', ''); commit('setCurrentObjectiveGroup', '');
commit('setParentProject', null); commit('setParentProject', null);
commit('setCurrentProjectEntry', null); commit('setCurrentProjectEntry', null);
@ -83,6 +92,7 @@ export default new Vuex.Store({
commit('setVimeoId', null); commit('setVimeoId', null);
commit('setCurrentNote', null); commit('setCurrentNote', null);
commit('setNoteType', ''); commit('setNoteType', '');
commit('setModulePayload', []);
}, },
resetContentBlockPosition({commit}) { resetContentBlockPosition({commit}) {
commit('setContentBlockPosition', {}); commit('setContentBlockPosition', {});
@ -110,19 +120,14 @@ export default new Vuex.Store({
commit('setCurrentRoomEntry', payload); commit('setCurrentRoomEntry', payload);
dispatch('showModal', 'edit-room-entry-wizard'); dispatch('showModal', 'edit-room-entry-wizard');
}, },
// todo: remove
addObjectiveGroup({commit, dispatch}, {module, type}) {
commit('setParentModule', module);
commit('setObjectiveGroupType', type);
dispatch('showModal', 'new-objective-group-wizard');
},
editObjectiveGroup({commit, dispatch}, payload) {
commit('setCurrentObjectiveGroup', payload);
dispatch('showModal', 'edit-objective-group-wizard');
},
showModal({commit}, payload) { showModal({commit}, payload) {
document.body.classList.add('no-scroll'); // won't get at the body any other way document.body.classList.add('no-scroll'); // won't get at the body any other way
commit('setModal', payload); commit('setModal', payload);
return new Promise((resolve, reject) => {
commit('setModalResolve', resolve);
commit('setModalReject', reject);
})
}, },
addProjectEntry({commit, dispatch}, payload) { addProjectEntry({commit, dispatch}, payload) {
commit('setParentProject', payload); commit('setParentProject', payload);
@ -179,6 +184,13 @@ export default new Vuex.Store({
}, },
editModule({commit}, payload) { editModule({commit}, payload) {
commit('setEditModule', payload) commit('setEditModule', payload)
},
editClassName({dispatch}, payload) {
dispatch('showModal', 'edit-class-name-wizard');
},
deactivateUser({commit, dispatch}, payload) {
commit('setModulePayload', payload);
return dispatch('showModal', 'deactivate-person');
} }
}, },
@ -216,10 +228,6 @@ export default new Vuex.Store({
setParentModule(state, payload) { setParentModule(state, payload) {
state.parentModule = payload; state.parentModule = payload;
}, },
// todo: remove
setObjectiveGroupType(state, payload) {
state.objectiveGroupType = payload;
},
setCurrentObjectiveGroup(state, payload) { setCurrentObjectiveGroup(state, payload) {
state.currentObjectiveGroup = payload; state.currentObjectiveGroup = payload;
}, },
@ -258,6 +266,15 @@ export default new Vuex.Store({
}, },
setNoteType(state, payload) { setNoteType(state, payload) {
state.noteType = payload; state.noteType = payload;
},
setModulePayload(state, payload) {
state.modulePayload = payload;
},
setModalResolve(state, payload) {
state.modalResolve = payload;
},
setModalReject(state, payload) {
state.modalReject = payload;
} }
} }
}) })

View File

@ -0,0 +1,21 @@
.book-subnavigation {
&__item {
@include small-text;
margin-bottom: $small-spacing;
cursor: pointer;
color: $color-silver-dark;
&--mobile {
color: $color-white;
}
&:last-of-type {
margin-bottom: 0;
}
&--active {
color: $color-brand;
}
}
}

View File

@ -4,6 +4,11 @@
.skillbox { .skillbox {
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
/*
* For IE10+
*/
display: flex;
flex-direction: column;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
} }
@ -34,39 +39,15 @@
-ms-grid-column: 1; -ms-grid-column: 1;
} }
/*
* For IE10+
*/
display: -ms-grid;
-ms-grid-rows: 50px 30px auto; // 1 extra row for gap
-ms-grid-columns: 1fr;
@include skillbox-colors; @include skillbox-colors;
&__header { &__header {
grid-area: h; grid-area: h;
-ms-grid-row: 1;
} }
&__content {
-ms-grid-row: 3;
-ms-grid-column: 1;
}
&__footer { &__footer {
grid-area: f; grid-area: f;
display: none; display: none;
} }
/*
* For IE10+
*/
& > :nth-child(2) {
}
& > :nth-child(3) {
-ms-grid-row: 4;
-ms-grid-column: 1;
}
} }

View File

@ -141,6 +141,18 @@
font-size: toRem(18px); font-size: toRem(18px);
} }
@mixin default-link {
font-size: toRem(18px);
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-silver-dark;
cursor: pointer;
&--active {
color: $color-brand;
}
}
@mixin page-form-input-heading { @mixin page-form-input-heading {
display: block; display: block;
margin-bottom: $medium-spacing; margin-bottom: $medium-spacing;

View File

@ -0,0 +1,24 @@
@import "variables";
.simple-list {
border-top: 1px solid $color-silver-dark;
&--active {
margin-bottom: 2*$large-spacing;
}
&__item {
line-height: $list-height;
height: $list-height;
border-bottom: 1px solid $color-silver-dark;
display: flex;
flex-direction: row;
justify-content: space-between;
}
&__action {
@include default-link;
color: $color-brand;
}
}

View File

@ -1,8 +1,10 @@
@import "variables";
.uploadcare--panel { .uploadcare--panel {
background: $color-silver-light; background: $color-white;
height: 200px; height: 230px;
border: none; border: 1px dashed $color-silver;
border-radius: 0; border-radius: 12px;
} }
.uploadcare--menu { .uploadcare--menu {

View File

@ -56,6 +56,9 @@ $red: #FA5F5F;
$green: #6DD79A; $green: #6DD79A;
$brown: #EB9E77; $brown: #EB9E77;
$list-height: 52px;
$default-border-radius: 13px; $default-border-radius: 13px;
$input-border-radius: 3px; $input-border-radius: 3px;

View File

@ -0,0 +1,67 @@
@import "variables";
@import "mixins";
.widget-popover {
position: absolute;
right: 0;
display: flex;
flex-direction: column;
background-color: $color-white;
padding: 0;
z-index: 100;
@include widget-shadow;
&--mobile {
left: 0;
right: inherit;
}
}
.popover-links {
list-style: none;
display: grid;
&__icon {
width: 25px;
height: 25px;
margin-right: $small-spacing;
}
&__link-with-icon {
display: flex;
align-items: center;
}
&__link {
cursor: pointer;
padding: 0 $medium-spacing;
& > a {
display: inline-block;
color: $color-silver-dark;
font-family: $sans-serif-font-family;
font-size: toRem(14px);
line-height: 1.5;
padding: 5px 0;
cursor: pointer;
}
&--large {
line-height: 40px;
padding: $small-spacing $medium-spacing;
& > a, & {
@include small-text;
}
}
&--emph {
@include regular-text;
font-weight: 600;
}
}
&__divider {
border-top: 1px solid $color-silver-dark;
}
}

View File

@ -22,3 +22,6 @@
@import "public-page"; @import "public-page";
@import "student-submission"; @import "student-submission";
@import "module-activity"; @import "module-activity";
@import "book-subnavigation";
@import "simple-list";
@import "widget-popover";

View File

@ -46,6 +46,12 @@ def get_graphql_query(filename):
def get_graphql_mutation(filename): def get_graphql_mutation(filename):
"""
Get the GraphQL mutation from the mutation directory
:param filename: the filename of the mutation
:return: the mutation
"""
from django.conf import settings from django.conf import settings
with io.open(os.path.join(settings.GRAPHQL_MUTATIONS_DIR, filename)) as f: with io.open(os.path.join(settings.GRAPHQL_MUTATIONS_DIR, filename)) as f:

View File

@ -13,6 +13,7 @@ from graphql_relay import to_global_id
from api.test_utils import create_client, DefaultUserTestCase from api.test_utils import create_client, DefaultUserTestCase
from assignments.models import Assignment, StudentSubmission from assignments.models import Assignment, StudentSubmission
from users.factories import SchoolClassFactory from users.factories import SchoolClassFactory
from users.models import SchoolClassMember
from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory from ..factories import AssignmentFactory, StudentSubmissionFactory, SubmissionFeedbackFactory
@ -25,13 +26,16 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
) )
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk) self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, final=False) self.student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
final=False)
self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk) self.student_submission_id = to_global_id('StudentSubmissionNode', self.student_submission.pk)
school_class = SchoolClassFactory() school_class = SchoolClassFactory()
school_class.users.add(self.student1) for user in [self.student1, self.teacher, self.teacher2]:
school_class.users.add(self.teacher) SchoolClassMember.objects.create(
school_class.users.add(self.teacher2) user=user,
school_class=school_class
)
def _create_submission_feedback(self, user, final, text, student_submission_id): def _create_submission_feedback(self, user, final, text, student_submission_id):
mutation = ''' mutation = '''
@ -122,19 +126,17 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
}) })
def test_teacher_can_create_feedback(self): def test_teacher_can_create_feedback(self):
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id) result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertIsNotNone(result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id')) self.assertIsNotNone(
result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
def test_student_cannot_create_feedback(self): def test_student_cannot_create_feedback(self):
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id) result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.get('errors'))
def test_teacher_can_update_feedback(self): def test_teacher_can_update_feedback(self):
assignment = AssignmentFactory( assignment = AssignmentFactory(
owner=self.teacher owner=self.teacher
) )
@ -148,13 +150,13 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback') submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get(
'updatedSubmissionFeedback')
self.assertTrue(submission_feedback_response.get('final')) self.assertTrue(submission_feedback_response.get('final'))
self.assertEqual(submission_feedback_response.get('text'), 'Some') self.assertEqual(submission_feedback_response.get('text'), 'Some')
def test_rogue_teacher_cannot_update_feedback(self): def test_rogue_teacher_cannot_update_feedback(self):
assignment = AssignmentFactory( assignment = AssignmentFactory(
owner=self.teacher owner=self.teacher
) )
@ -169,14 +171,12 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.get('errors'))
def test_student_does_not_see_non_final_feedback(self): def test_student_does_not_see_non_final_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission) SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1) result = self._fetch_assignment_student(self.student1)
self.assertIsNone(result.get('data').get('submissionFeedback')) self.assertIsNone(result.get('data').get('submissionFeedback'))
def test_student_does_see_final_feedback(self): def test_student_does_see_final_feedback(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True, submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
student_submission=self.student_submission) student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1) result = self._fetch_assignment_student(self.student1)
@ -195,7 +195,7 @@ class SubmissionFeedbackTestCase(DefaultUserTestCase):
def test_rogue_teacher_cannot_see_feedback(self): def test_rogue_teacher_cannot_see_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False, SubmissionFeedbackFactory(teacher=self.teacher, final=False,
student_submission=self.student_submission) student_submission=self.student_submission)
self.student_submission.final = True self.student_submission.final = True
self.student_submission.save() self.student_submission.save()

Some files were not shown because too many files have changed in this diff Show More