merge develop into feature/pw-reset

This commit is contained in:
Christian Cueni 2019-04-11 08:14:08 +02:00
commit f8c1f372cb
121 changed files with 2841 additions and 648 deletions

2
.gitignore vendored
View File

@ -40,3 +40,5 @@ server/media/
# pyenv # pyenv
.python-version .python-version
.coverage

View File

@ -35,3 +35,5 @@ django-libsass = "*"
bleach = "*" bleach = "*"
newrelic = "*" newrelic = "*"
sentry-sdk = "==0.7.2" sentry-sdk = "==0.7.2"
"django-sendgrid-v5" = "*"
coverage = "*"

259
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "61e358e30d3f66b4b69ac4ad424c24401d367fc483aaffeac28d970a418066aa" "sha256": "97ff5ca56ac835d40353e34e32ec8333ccb23822bcf971644b7641429d9774e1"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -41,25 +41,25 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:0bed0db8c10b88b3daa042adaa1fb6c3262caed39d28086e8548015405c71744", "sha256:3927beac97e5467f869d63d60920b83c2d39964f69fbf944bc1db724116bfe1a",
"sha256:70e71e0192a68f65754ab9d2a335be3c6856a1e8a15f3bd6263ea12e2f442bc7" "sha256:be88cae6f16bb9fe3b850b6c8259b297f60b46855175cadae57594c9a403c582"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.9.93" "version": "==1.9.124"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:4df39ef9bcd7766e3a71a9e7f976ca6c9e926f451914a9c073aa50e9519436ca", "sha256:bb756a8da2c6e3ccf42dccb0ac71c1df2e07844db339183da06f4e0285b251d0",
"sha256:d3cea95919892eac30e2ff8c5a8908022d5a93f917df3cff4ed06a6926dcc0e5" "sha256:fc7560a2676df2f0bab4ef0638277b86e5a00944c2ce1c3bb124b3066e6d3d2a"
], ],
"version": "==1.12.93" "version": "==1.12.124"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
], ],
"version": "==2018.11.29" "version": "==2019.3.9"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
@ -75,6 +75,43 @@
], ],
"version": "==7.0" "version": "==7.0"
}, },
"coverage": {
"hashes": [
"sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9",
"sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74",
"sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390",
"sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8",
"sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe",
"sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf",
"sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e",
"sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741",
"sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09",
"sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd",
"sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034",
"sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420",
"sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c",
"sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab",
"sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba",
"sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e",
"sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609",
"sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2",
"sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49",
"sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b",
"sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d",
"sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce",
"sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9",
"sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4",
"sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773",
"sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723",
"sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c",
"sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f",
"sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1",
"sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260",
"sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"
],
"index": "pypi",
"version": "==4.5.3"
},
"dj-database-url": { "dj-database-url": {
"hashes": [ "hashes": [
"sha256:7f4c78d2a090df8dfaf56d5d3ff7bbee17360436e4879558317e2314424864cd" "sha256:7f4c78d2a090df8dfaf56d5d3ff7bbee17360436e4879558317e2314424864cd"
@ -92,10 +129,10 @@
}, },
"django-appconf": { "django-appconf": {
"hashes": [ "hashes": [
"sha256:6a4d9aea683b4c224d97ab8ee11ad2d29a37072c0c6c509896dd9857466fb261", "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3",
"sha256:ddab987d14b26731352c01ee69c090a4ebfc9141ed223bef039d79587f22acd9" "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"
], ],
"version": "==1.0.2" "version": "==1.0.3"
}, },
"django-compressor": { "django-compressor": {
"hashes": [ "hashes": [
@ -143,6 +180,14 @@
], ],
"version": "==4.3" "version": "==4.3"
}, },
"django-sendgrid-v5": {
"hashes": [
"sha256:471f718ae02c775f0f2ba8f901d5abb486b016805af8b2ac543ac9ff2f4164ec",
"sha256:c69ba0171b260c25def8048b69d931b46fbd8360b9aff4eb5477a284260fce0c"
],
"index": "pypi",
"version": "==0.7.1"
},
"django-storages": { "django-storages": {
"hashes": [ "hashes": [
"sha256:8e35d2c7baeda5dc6f0b4f9a0fc142d25f9a1bf72b8cebfcbc5db4863abc552d", "sha256:8e35d2c7baeda5dc6f0b4f9a0fc142d25f9a1bf72b8cebfcbc5db4863abc552d",
@ -153,10 +198,10 @@
}, },
"django-taggit": { "django-taggit": {
"hashes": [ "hashes": [
"sha256:a21cbe7e0879f1364eef1c88a2eda89d593bf000ebf51c3f00423c6927075dce", "sha256:710b4d15ec1996550cc68a0abbc41903ca7d832540e52b1336e6858737e410d8",
"sha256:db4430ec99265341e05d0274edb0279163bd74357241f7b4d9274bdcb3338b17" "sha256:bb8f27684814cd1414b2af75b857b5e26a40912631904038a7ecacd2bfafc3ac"
], ],
"version": "==0.23.0" "version": "==0.24.0"
}, },
"django-treebeard": { "django-treebeard": {
"hashes": [ "hashes": [
@ -196,10 +241,16 @@
}, },
"faker": { "faker": {
"hashes": [ "hashes": [
"sha256:16342dca4d92bfc83bab6a7daf6650e0ab087605a66bc38f17523fdb01757910", "sha256:00b7011757c4907546f17d0e47df098b542ea2b04c966ee0e80a493aae2c13c8",
"sha256:d871ea315b2dcba9138b8344f2c131a76ac62d6227ca39f69b0c889fec97376c" "sha256:745ac8b9c9526e338696e07b7f2e206e5e317e5744e22fdd7c2894bf19af41f1"
], ],
"version": "==1.0.2" "version": "==1.0.4"
},
"future": {
"hashes": [
"sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"
],
"version": "==0.17.1"
}, },
"graphene": { "graphene": {
"hashes": [ "hashes": [
@ -253,38 +304,38 @@
}, },
"jmespath": { "jmespath": {
"hashes": [ "hashes": [
"sha256:6a81d4c9aa62caf061cb517b4d9ad1dd300374cd4706997aff9cd6aedd61fc64", "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
"sha256:f11b4461f425740a1d908e9a3f7365c3d2e569f6ca68a2ff8bc5bcd9676edd63" "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
], ],
"version": "==0.9.3" "version": "==0.9.4"
}, },
"libsass": { "libsass": {
"hashes": [ "hashes": [
"sha256:0da943e00e028211cb4bb91496a20becab9fe82407bb75266ec4212af04acb45", "sha256:2ae3b061a7d250fb47e5fdad1a8191600ca15dc604e76b109b6d3bf8e08fd2ed",
"sha256:107591ba2c0d173bb1705bef0e9fd04a5b6f482f3584f4ea51b28ab8b137fbb2", "sha256:2ee186aa682a035a53c557b7e61ce562a1114f1a1a992d0ba962cbc3e82c490c",
"sha256:1aeadc155594af23879e27792667dc06e7f248c2c599c40ff2a7335193abdf05", "sha256:366f4fd5a5eab4a519beb583e9fa78718cf2c0f40e92ed835d7ed23b82e5d954",
"sha256:411833c623288138744865d882f5226f6db52afce1e19f42722c416df9d308dc", "sha256:5511b3c62e8d97daf929c63bd516b794f0a06acd09dd261445d864e48290551b",
"sha256:4bf7a80a956da9de9715436b85343a179da4ff399a6e9a1694e70bff93d43099", "sha256:7462da168c8fb997b31cb4dc3ee5adb9af2d106f7b92c2d57a1c68a56ae5a3a0",
"sha256:53be1c6cea9458fc0b59fafff5307d63cbde4d6f8a4413fb52ae467566273357", "sha256:84a16ec5cf7842ff5bc2caed2c032ed624d587699797bc2a4d4a8e41f579b6e7",
"sha256:55b77204cfa363142ab02c49ee871321a396b8e51f6361ebc226c3953c780541", "sha256:8fc0360ee99224f7a3cb09987e641171d34180759f467ba3d15934102ade396f",
"sha256:66e3062ff508c81928e35c66702f0cc4f70fb12eb76ba23eeb0ff87a6340cc13", "sha256:a6c5535a21a07d769151453270bc6a8373b821d1d2fd9810d84fccfe315ab188",
"sha256:747e1cb3624b25ce9104315cf98b080246c5112d008cba6536a7dd2edb16fcc2", "sha256:b375bfbf3c86ec0f4a27f266b44b2753a4b8cab7e73649eed7afcad84bc56257",
"sha256:75605a97f4b2f47fafc5a372f09efec210c7f33908c6de726362f85489fd53aa", "sha256:b548af46c1a606aed93da2566901146005d6065f73fefc63d256ba62ba1f803d",
"sha256:7c7a531b8cd786c35170e97338be2e73a74806f95539366a8ee837df94b8a8cf", "sha256:bb30fc7125350c64925a98cb90da7979f76bb0ea1a0157e8aeb268f8da38e296",
"sha256:953ebe810f09d81b84ccafdca0fb6171d1b58c8f0147cb650184a41e124e296f", "sha256:c2f386677514f9fc758631328bd318dd3e9d839ad7b6e248ec4535a191bfd271",
"sha256:a19041e78d5bb7c5d72e010e893c29119693628b6ee06025503ab2584cf24edd", "sha256:d1f301637ad5768aecc81d17dcf40a68f2e11b7ca8b427dbb9f8972c150d303e",
"sha256:b3e4abf50ad3a6bec25acd0c67495301cab6137aa79b8640364276f7f3712586", "sha256:e0cf54dddf2cc6e373005bed6e46ccdce1f3a77bd169ab505c3a8ad9023eee5f",
"sha256:bf6b7ad08f287695338f050c80f79d258a405e5c349cdaeb9be5d5376c09e37a", "sha256:e8941881063691d50f9cc8b8d6d8fd7bec86a8c461b2a4fc87188a5fc44d6ba4",
"sha256:fcbc861a001ffd68c4df00164b41c6152d5451185d06c654ac240d811be9f7e2" "sha256:f4b29b0c70d753c754a58aaad7c31ad3309ca4a26f9aa64e695157251f6832ad"
], ],
"version": "==0.17.0" "version": "==0.18.0"
}, },
"newrelic": { "newrelic": {
"hashes": [ "hashes": [
"sha256:8fdc94350bfe69e4f70dc8a3e7ead6c6a1905e8f161ce24de7ee3de4f69196ad" "sha256:1d08248dee0d33116a145de723a2ae86e57a10674145ce4c8af3c316423bd140"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.12.0.113" "version": "==4.16.0.116"
}, },
"pillow": { "pillow": {
"hashes": [ "hashes": [
@ -377,6 +428,12 @@
"index": "pypi", "index": "pypi",
"version": "==0.7.1" "version": "==0.7.1"
}, },
"python-http-client": {
"hashes": [
"sha256:7e430f4b9dd2b621b0051f6a362f103447ea8e267594c602a5c502a0c694ee38"
],
"version": "==3.1.0"
},
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
@ -425,6 +482,13 @@
], ],
"version": "==0.2.0" "version": "==0.2.0"
}, },
"sendgrid": {
"hashes": [
"sha256:97eb356ea44a18a03e6fe99f02b7942445be306a50f73a7dd35471fe15cc0094",
"sha256:9e9d3d75602b8853f174ec959d1af1ac168cd361cbcafc81285fa8c183f7a505"
],
"version": "==5.6.0"
},
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:131e3b9ac11dffd86fe4f1f5d388d3dab372fc9e30d6611d1fc87096a1d67359", "sha256:131e3b9ac11dffd86fe4f1f5d388d3dab372fc9e30d6611d1fc87096a1d67359",
@ -515,11 +579,11 @@
"develop": { "develop": {
"awscli": { "awscli": {
"hashes": [ "hashes": [
"sha256:11a18d6f42469366920ad96d483a64804f17ea63e21e5f4a66d8492d7031aa14", "sha256:87258e4719978f51dae8c62e15cd0486a778ddcb530645f3bc035239b800f184",
"sha256:21ad9141041834dfc135c731d32fdde674642f33d19b21e2ac9c696a8370d717" "sha256:fbd9dc00ecd7060f36e5768122c9293672b82748fa224cb13e22e6322532d8db"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.16.103" "version": "==1.16.134"
}, },
"backcall": { "backcall": {
"hashes": [ "hashes": [
@ -530,10 +594,10 @@
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:4df39ef9bcd7766e3a71a9e7f976ca6c9e926f451914a9c073aa50e9519436ca", "sha256:bb756a8da2c6e3ccf42dccb0ac71c1df2e07844db339183da06f4e0285b251d0",
"sha256:d3cea95919892eac30e2ff8c5a8908022d5a93f917df3cff4ed06a6926dcc0e5" "sha256:fc7560a2676df2f0bab4ef0638277b86e5a00944c2ce1c3bb124b3066e6d3d2a"
], ],
"version": "==1.12.93" "version": "==1.12.124"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
@ -544,47 +608,47 @@
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
"sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9",
"sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74",
"sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390",
"sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8",
"sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe",
"sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf",
"sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e",
"sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741",
"sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09",
"sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd",
"sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034",
"sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420",
"sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c",
"sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab",
"sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba",
"sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e",
"sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609",
"sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2",
"sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49",
"sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b",
"sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d",
"sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce",
"sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9",
"sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4",
"sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773",
"sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723",
"sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c",
"sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f",
"sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1",
"sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260",
"sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.5.2" "version": "==4.5.3"
}, },
"decorator": { "decorator": {
"hashes": [ "hashes": [
"sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e", "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de",
"sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b" "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"
], ],
"version": "==4.3.2" "version": "==4.4.0"
}, },
"docutils": { "docutils": {
"hashes": [ "hashes": [
@ -596,18 +660,17 @@
}, },
"ipdb": { "ipdb": {
"hashes": [ "hashes": [
"sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a" "sha256:dce2112557edfe759742ca2d0fee35c59c97b0cc7a05398b791079d78f1519ce"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.11" "version": "==0.12"
}, },
"ipython": { "ipython": {
"hashes": [ "hashes": [
"sha256:6a9496209b76463f1dec126ab928919aaf1f55b38beb9219af3fe202f6bbdd12", "sha256:b038baa489c38f6d853a3cfc4c635b0cda66f2864d136fe8f40c1a6e334e2a6b",
"sha256:f69932b1e806b38a7818d9a1e918e5821b685715040b48e59c657b3c7961b742" "sha256:f5102c1cd67e399ec8ea66bcebe6e3968ea25a8977e53f012963e5affeb1fe38"
], ],
"markers": "python_version >= '3.3'", "version": "==7.4.0"
"version": "==7.2.0"
}, },
"ipython-genutils": { "ipython-genutils": {
"hashes": [ "hashes": [
@ -618,17 +681,17 @@
}, },
"jedi": { "jedi": {
"hashes": [ "hashes": [
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd", "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191" "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
], ],
"version": "==0.13.2" "version": "==0.13.3"
}, },
"jmespath": { "jmespath": {
"hashes": [ "hashes": [
"sha256:6a81d4c9aa62caf061cb517b4d9ad1dd300374cd4706997aff9cd6aedd61fc64", "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
"sha256:f11b4461f425740a1d908e9a3f7365c3d2e569f6ca68a2ff8bc5bcd9676edd63" "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
], ],
"version": "==0.9.3" "version": "==0.9.4"
}, },
"parso": { "parso": {
"hashes": [ "hashes": [
@ -654,11 +717,11 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:88002cc618cacfda8760c4539e76c3b3f148ecdb7035a3d422c7ecdc90c2a3ba", "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780",
"sha256:c6655a12e9b08edb8cf5aeab4815fd1e1bdea4ad73d3bbf269cf2e0c4eb75d5e", "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1",
"sha256:df5835fb8f417aa55e5cafadbaeb0cf630a1e824aad16989f9f0493e679ec010" "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"
], ],
"version": "==2.0.8" "version": "==2.0.9"
}, },
"ptyprocess": { "ptyprocess": {
"hashes": [ "hashes": [

View File

@ -36,7 +36,7 @@ aliases:
- echo "This pipeline rules!" - echo "This pipeline rules!"
- *setup-tests - *setup-tests
- npm install --prefix client - npm install --prefix client
# - npm run "install:cypress" --prefix client # - npm run "install:cypress" --prefix client
- psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME" - psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME"
- python server/manage.py dummy_data - python server/manage.py dummy_data
- python server/manage.py runserver & - python server/manage.py runserver &
@ -55,6 +55,7 @@ pipelines:
branches: branches:
master: master:
- step: *unittest-python - step: *unittest-python
- step: *cypress-test
develop: develop:
- step: *unittest-python - step: *unittest-python
@ -67,4 +68,5 @@ pipelines:
custom: custom:
prod: prod:
- step: *unittest-python - step: *unittest-python
- step: *cypress-test
- step: *deploy-prod - step: *deploy-prod

View File

@ -9,7 +9,7 @@ describe('The Login Page', () => {
cy.get('#id_password').type(`${password}{enter}`); cy.get('#id_password').type(`${password}{enter}`);
cy.getCookie('sessionid').should('exist'); cy.getCookie('sessionid').should('exist');
cy.get('.start-page__title').should('contain', 'skillbox') cy.get('.start-page__header').should('exist')
}); });
// it('logs in programmatically without using the UI', () => { // it('logs in programmatically without using the UI', () => {
// cy.visit('/accounts/login/'); // have to get a csrf token by getting the base page first // cy.visit('/accounts/login/'); // have to get a csrf token by getting the base page first

View File

@ -0,0 +1,14 @@
describe('New project', () => {
it('creates a new project and displays it', () => {
cy.viewport('macbook-15');
cy.login('rahel.cueni', 'test');
cy.visit('/portfolio');
cy.get('[data-cy=add-project-button]').click();
cy.get('[data-cy=page-form-input-titel]').type('Some random title');
cy.get('[data-cy=page-form-input-beschreibung]').type('This description rocks');
cy.get('[data-cy=page-form-input-ziele]').type('Git gud');
cy.get('[data-cy=save-project-button]').click();
cy.get('.project-widget:first').contains('random');
})
});

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
<title>skillbox</title> <title>skillbox</title>
<link href='https://fonts.googleapis.com/css?family=Material+Icons' rel="stylesheet" type="text/css"> <link href='https://fonts.googleapis.com/css?family=Material+Icons' rel="stylesheet" type="text/css">

View File

@ -3042,6 +3042,12 @@
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
}, },
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=",
"dev": true
},
"supports-color": { "supports-color": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz",
@ -7191,10 +7197,9 @@
} }
}, },
"moment": { "moment": {
"version": "2.22.2", "version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
"dev": true
}, },
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -51,6 +51,7 @@
"graphql-tag": "^2.9.2", "graphql-tag": "^2.9.2",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"moment": "^2.24.0",
"node-notifier": "^5.1.2", "node-notifier": "^5.1.2",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"optimize-css-assets-webpack-plugin": "^3.2.0", "optimize-css-assets-webpack-plugin": "^3.2.0",

View File

@ -1,7 +1,8 @@
<template> <template>
<div :class="{'no-scroll': showModal}"> <div :class="{'no-scroll': showModal || showMobileNavigation}" class="app">
<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>
</div> </div>
</template> </template>
@ -10,6 +11,7 @@
import SimpleLayout from '@/layouts/SimpleLayout'; import SimpleLayout from '@/layouts/SimpleLayout';
import BlankLayout from '@/layouts/BlankLayout'; import BlankLayout from '@/layouts/BlankLayout';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import MobileNavigation from '@/components/MobileNavigation';
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard'; import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
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';
@ -19,6 +21,9 @@
import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard'; import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard';
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 {mapGetters} from 'vuex';
export default { export default {
name: 'App', name: 'App',
@ -28,6 +33,7 @@
SimpleLayout, SimpleLayout,
BlankLayout, BlankLayout,
Modal, Modal,
MobileNavigation,
NewContentBlockWizard, NewContentBlockWizard,
EditContentBlockWizard, EditContentBlockWizard,
NewRoomEntryWizard, NewRoomEntryWizard,
@ -36,16 +42,15 @@
EditObjectiveGroupWizard, EditObjectiveGroupWizard,
NewProjectEntryWizard, NewProjectEntryWizard,
FullscreenImage, FullscreenImage,
FullscreenInfographic FullscreenInfographic,
FullscreenVideo
}, },
computed: { computed: {
layout() { layout() {
return (this.$route.meta.layout || 'default') + '-layout'; return (this.$route.meta.layout || 'default') + '-layout';
}, },
showModal() { ...mapGetters(['showModal', 'showMobileNavigation'])
return this.$store.state.showModal
}
}, },
mounted() { mounted() {
@ -56,6 +61,18 @@
<style lang="scss"> <style lang="scss">
@import "styles/main.scss"; @import "styles/main.scss";
body {
overflow: hidden;
height: 100vh;
}
.app {
overflow-y: auto;
height: 100vh;
/*for IE10+*/
display: flex;
}
.no-scroll { .no-scroll {
overflow-y: hidden; overflow-y: hidden;
} }

View File

@ -37,15 +37,20 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.add-objective-group-button { .add-objective-group-button {
display: grid; display: none;
grid-template-columns: 45px auto; grid-template-columns: 45px auto;
align-items: center; align-items: center;
margin-top: -20px; margin-top: -20px;
margin-bottom: 35px; margin-bottom: 35px;
cursor: pointer; cursor: pointer;
@include desktop {
display: grid;
}
&__icon { &__icon {
width: 25px; width: 25px;
height: 25px; height: 25px;

View File

@ -1,5 +1,6 @@
<template> <template>
<component :is="component" v-bind="properties" class="add-widget" @click="$emit('click')" :class="{ 'add-widget--reverse': reverse }"> <component :is="component" v-bind="properties" class="add-widget" @click="$emit('click')"
:class="{ 'add-widget--reverse': reverse }">
<add-icon class="add-widget__add"></add-icon> <add-icon class="add-widget__add"></add-icon>
</component> </component>
</template> </template>
@ -47,12 +48,16 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.add-widget { .add-widget {
display: flex; display: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@include widget-shadow; @include widget-shadow;
cursor: pointer; cursor: pointer;
@include desktop {
display: flex;
}
&__add { &__add {
width: 80px; width: 80px;
fill: $color-grey; fill: $color-grey;

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="room-colors"> <div class="color-chooser">
<div v-for="(color, index) in colors" <div v-for="(color, index) in colors"
:key="index" :key="index"
class="room-colors__color-wrapper" class="color-chooser__color-wrapper"
@click="$emit('input', color.name)" @click="$emit('input', color.name)"
:class="{'room-colors__color-wrapper--selected': selectedColor === color.name}"> :class="{'color-chooser__color-wrapper--selected': selectedColor === color.name}">
<div class="room-colors__color" :class="'room-colors__color--' + color.name"> <div class="color-chooser__color" :class="'color-chooser__color--' + color.name">
<tick class="room-colors__selected-icon" v-if="selectedColor === color.name"></tick> <tick class="color-chooser__selected-icon" v-if="selectedColor === color.name"></tick>
</div> </div>
</div> </div>
</div> </div>
@ -47,7 +47,7 @@
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.room-colors { .color-chooser {
display: flex; display: flex;
&__color-wrapper { &__color-wrapper {
@ -69,7 +69,11 @@
width: 46px; width: 46px;
height: 46px; height: 46px;
border-radius: 23px; border-radius: 23px;
display: grid; display: flex;
justify-content: center;
@supports (display: grid) {
display: grid
}
justify-items: center; justify-items: center;
align-items: center; align-items: center;

View File

@ -0,0 +1,41 @@
<template>
<modal class="fullscreen-video" :hide-header="true" :fullscreen="true">
<iframe :src="src"
width="2000"
height="1000"
class="fullscreen-video__embed"
frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen></iframe>
</modal>
</template>
<script>
import Modal from '@/components/Modal';
export default {
components: {
Modal
},
computed: {
vimeoId() {
return this.$store.state.vimeoId;
},
src() {
return `https://player.vimeo.com/video/${this.vimeoId}`;
}
}
}
</script>
<style scoped lang="scss">
.fullscreen-video {
&__embed {
max-width: 100%;
width: 100%;
height: 95vh;
vertical-align: bottom;
}
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<header class="header-bar">
<top-navigation></top-navigation>
<router-link to="/" class="header-bar__logo"><logo></logo></router-link>
<div class="user-header">
<user-widget v-bind="me"></user-widget>
<logout-widget></logout-widget>
</div>
<book-navigation v-if="showSubnavigation">
</book-navigation>
</header>
</template>
<script>
import TopNavigation from '@/components/TopNavigation.vue';
import BookNavigation from '@/components/book-navigation/BookNavigation';
import UserWidget from '@/components/UserWidget.vue';
import LogoutWidget from '@/components/LogoutWidget.vue';
import Logo from '@/components/icons/Logo';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default {
components: {
TopNavigation,
UserWidget,
LogoutWidget,
BookNavigation,
Logo
},
computed: {
showSubnavigation() {
return this.$route.meta.subnavigation;
}
},
data() {
return {
me: {}
}
},
apollo: {
me: {
query: ME_QUERY,
},
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.header-bar {
display: -ms-grid;
@supports (display: grid) {
display: none;
@include desktop {
display: grid;
}
}
align-items: center;
justify-content: space-around;
background-color: $color-white;
grid-auto-rows: 50px;
width: 100%;
@include desktop {
grid-template-columns: 1fr 1fr 1fr;
}
/*
* For IE10+
*/
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 50px 50px;
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-column: 1;
-ms-grid-row-align: center;
}
/*
* For IE10+
*/
& > :nth-child(3) {
-ms-grid-column: 3;
-ms-grid-row-align: center;
-ms-grid-column-align: end;
justify-self: end;
}
& > :nth-child(4) {
-ms-grid-row: 2;
-ms-grid-column: 1;
-ms-grid-column-span: 3;
}
&__logo {
color: #17A887;
font-size: 36px;
font-weight: 800;
font-family: $sans-serif-font-family;
display: flex;
justify-self: center;
/*
* For IE10+
*/
-ms-grid-column: 2;
-ms-grid-row-align: center;
-ms-grid-column-align: center;
}
}
.user-header {
display: flex;
}
</style>

View File

@ -32,7 +32,7 @@
&__logout { &__logout {
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
line-height: 16px; line-height: 16px;
margin: 0 15px 0 20px; margin: 0 15px 0 $large-spacing;
background: none; background: none;
color: inherit; color: inherit;
border: none; border: none;

View File

@ -0,0 +1,54 @@
<template>
<div class="mobile-header">
<router-link to="/">
<logo></logo>
</router-link>
<a @click="showMobileNavigation">
<hamburger class="mobile-header__hamburger"></hamburger>
</a>
</div>
</template>
<script>
import Logo from '@/components/icons/Logo';
import Hamburger from '@/components/icons/Hamburger';
export default {
components: {
Logo,
Hamburger
},
methods: {
showMobileNavigation() {
this.$store.dispatch('showMobileNavigation', true);
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.mobile-header {
justify-content: space-between;
align-items: center;
display: flex;
@include desktop {
display: none;
}
padding: 0 $medium-spacing;
&__hamburger {
width: 30px;
height: 30px;
fill: $color-grey;
}
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<div class="mobile-navigation">
<top-navigation class="mobile-navigation__main" :mobile="true"></top-navigation>
<div class="mobile-navigation__close-button" @click="hideMobileNavigation">
<cross class="mobile-navigation__close-icon"></cross>
</div>
<div class="mobile-navigation__subnavigation"></div>
<div class="mobile-navigation__secondary">
<user-widget class="mobile-navigation__user-widget" v-bind="me"></user-widget>
<logout-widget class="mobile-navigation__logout-widget"></logout-widget>
</div>
</div>
</template>
<script>
import Cross from '@/components/icons/Cross';
import UserWidget from '@/components/UserWidget';
import LogoutWidget from '@/components/LogoutWidget';
import TopNavigation from '@/components/TopNavigation';
import {meQuery} from '@/graphql/queries';
export default {
components: {
TopNavigation,
Cross,
UserWidget,
LogoutWidget
},
methods: {
hideMobileNavigation() {
this.$store.dispatch('showMobileNavigation', false);
}
},
apollo: {
me: meQuery
},
data() {
return {
me: {}
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.mobile-navigation {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: white;
z-index: 20;
display: grid;
grid-template-columns: 1fr 50px;
grid-template-rows: 50px 100px auto 100px;
grid-template-areas: "m m" "m m" "s s";
&--with-subnavigation {
grid-template-areas: "m m" "m m" "sub sub" "s s";
}
height: 100vh;
overflow-y: auto;
@include desktop {
display: none;
}
&__main {
background-color: $color-brand;
padding: $medium-spacing;
grid-area: m;
}
&__main-link {
}
&__close-button {
grid-row: 1;
grid-column: 2;
align-self: center;
justify-self: center;
}
&__close-icon {
width: 30px;
height: 30px;
opacity: 0.5;
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>

View File

@ -59,9 +59,13 @@
border-radius: 12px; border-radius: 12px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
border: 1px solid $color-lightgrey; border: 1px solid $color-lightgrey;
display: grid; display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr 65px; grid-template-rows: auto 1fr 65px;
grid-template-areas: "header" "body" "footer"; grid-template-areas: "header" "body" "footer";
-ms-grid-rows: auto 1fr 65px;
position: relative; position: relative;
&--hide-header { &--hide-header {
@ -81,6 +85,7 @@
width: 95vw; width: 95vw;
height: auto; height: auto;
grid-template-rows: 1fr; grid-template-rows: 1fr;
-ms-grid-rows: 1fr;
grid-template-areas: "body"; grid-template-areas: "body";
overflow: hidden; overflow: hidden;
} }
@ -107,7 +112,11 @@
} }
&__backdrop { &__backdrop {
display: grid; display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@ -119,12 +128,14 @@
&__header { &__header {
grid-area: header; grid-area: header;
-ms-grid-row: 1;
padding: 10px $modal-lateral-padding; padding: 10px $modal-lateral-padding;
border-bottom: 1px solid $color-lightgrey; border-bottom: 1px solid $color-lightgrey;
} }
&__body { &__body {
grid-area: body; grid-area: body;
-ms-grid-row: 2;
padding: 10px $modal-lateral-padding; padding: 10px $modal-lateral-padding;
overflow: auto; overflow: auto;
box-sizing: border-box; box-sizing: border-box;
@ -150,6 +161,7 @@
&__footer { &__footer {
grid-area: footer; grid-area: footer;
-ms-grid-row: 3;
border-top: 1px solid $color-lightgrey; border-top: 1px solid $color-lightgrey;
padding: 16px $modal-lateral-padding; padding: 16px $modal-lateral-padding;
} }

View File

@ -4,8 +4,8 @@
<a :href="url" target="_blank" class="teaser__title">{{title}}</a> <a :href="url" target="_blank" class="teaser__title">{{title}}</a>
</h4> </h4>
<a :href="url" target="_blank" class="teaser__date"> <a :href="url" target="_blank" class="teaser__date">
{{date}} {{date}}
</a> </a>
</div> </div>
</template> </template>
@ -18,10 +18,21 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_functions.scss"; @import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
.news-teaser { .news-teaser {
display: flex; display: flex;
border-left: 1px solid $color-lightgrey; border-bottom: 1px solid $color-lightgrey;
padding-bottom: $large-spacing;
text-align: center;
@include desktop {
border-bottom: 0;
border-left: 1px solid $color-lightgrey;
padding-bottom: 0;
text-align: left;
}
padding-left: $medium-spacing; padding-left: $medium-spacing;
flex-direction: column; flex-direction: column;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<nav class="top-navigation"> <nav class="top-navigation" :class="{'top-navigation--mobile': mobile}">
<router-link to="/book/topic/geld-und-kauf" active-class="top-navigation__link--active" <router-link to="/book/topic/geld-und-kauf" active-class="top-navigation__link--active"
:class="{'top-navigation__link--active': isActive('book')}" :class="{'top-navigation__link--active': isActive('book')}"
class="top-navigation__link">Inhalte class="top-navigation__link">Inhalte
@ -13,6 +13,12 @@
<script> <script>
export default { export default {
props: {
mobile: {
default: false
}
},
methods: { methods: {
isActive(linkName) { isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1; return linkName === 'book' && this.$route.path.indexOf('module') > -1;
@ -22,4 +28,39 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.top-navigation {
display: flex;
&__link {
font-size: 1.0625rem;
padding: 0 24px;
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-grey;
&--active {
color: $color-brand;
}
}
$parent: &;
&--mobile {
flex-direction: column;
#{$parent}__link {
color: rgba($color-white, 0.6);
@include heading-4;
line-height: 2em;
padding: 0;
&--active {
color: $color-white;
}
}
}
}
</style> </style>

View File

@ -12,10 +12,15 @@
import UserIcon from '@/components/icons/UserIcon'; import UserIcon from '@/components/icons/UserIcon';
export default { export default {
props: ['firstName', 'lastName', 'avatar', 'date', 'isProfile'], props: ['firstName', 'lastName', 'avatar', 'date'],
components: { components: {
UserIcon UserIcon
},
computed: {
isProfile() {
return this.$route.meta.isProfile;
}
} }
} }
</script> </script>

View File

@ -1,14 +1,13 @@
<template> <template>
<div class="widget-footer"> <div class="widget-footer">
<a @click="showMenu = !showMenu" class="widget-footer__more-link"> <a @click="toggleMenu"
class="widget-footer__more-link">
<ellipses></ellipses> <ellipses></ellipses>
</a> </a>
<widget-popover :entity="entity" <widget-popover v-if="showMenu"
@delete="onDelete" @hide-me="showMenu = false">
@hide-me="showMenu = false" <slot :hide="toggleMenu"></slot>
@edit="onEdit" </widget-popover>
:id="id"
v-if="showMenu"></widget-popover>
</div> </div>
</template> </template>
@ -17,7 +16,6 @@
import WidgetPopover from '@/components/rooms/WidgetPopover'; import WidgetPopover from '@/components/rooms/WidgetPopover';
export default { export default {
props: ['on-delete', 'on-edit', 'id', 'entity'],
components: { components: {
Ellipses, Ellipses,
@ -28,12 +26,19 @@
return { return {
showMenu: false showMenu: false
} }
},
methods: {
toggleMenu: function () {
this.showMenu = !this.showMenu
}
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.widget-footer { .widget-footer {
background-color: $color-grey--lighter; background-color: $color-grey--lighter;
@ -43,6 +48,11 @@
position: relative; position: relative;
border-bottom-left-radius: $default-border-radius; border-bottom-left-radius: $default-border-radius;
border-bottom-right-radius: $default-border-radius; border-bottom-right-radius: $default-border-radius;
visibility: hidden;
@include desktop {
visibility: visible;
}
/* /*
* For IE10+ * For IE10+

View File

@ -1,10 +1,16 @@
<template> <template>
<img :src="value.path" alt="" class="image-block"> <img :src="value.path" alt="" class="image-block" @click="openFullscreen">
</template> </template>
<script> <script>
export default { export default {
props: ['value'] props: ['value'],
methods: {
openFullscreen() {
this.$store.dispatch('showFullscreenImage', this.value.path);
}
}
} }
</script> </script>

View File

@ -66,8 +66,12 @@
@import "@/styles/_functions.scss"; @import "@/styles/_functions.scss";
.content-block-element-chooser-widget { .content-block-element-chooser-widget {
display: grid; display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-template-columns: repeat(6, 1fr); grid-template-columns: repeat(6, 1fr);
-ms-grid-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-column-gap: 0px; grid-column-gap: 0px;
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
text-align: center; text-align: center;
@ -78,6 +82,31 @@
position: relative; position: relative;
margin-bottom: 20px; margin-bottom: 20px;
/*IE10+*/
& > :nth-child(1) {
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-column: 3;
}
& > :nth-child(4) {
-ms-grid-column: 4;
}
& > :nth-child(5) {
-ms-grid-column: 5;
}
& > :nth-child(6) {
-ms-grid-column: 6;
}
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;

View File

@ -3,7 +3,7 @@
<h3 class="text-form-with-help-text__heading"><span class="text-form-with-help-text__title">{{title}}</span> <h3 class="text-form-with-help-text__heading"><span class="text-form-with-help-text__title">{{title}}</span>
<info-icon class="text-form-with-help-text__icon"></info-icon> <info-icon class="text-form-with-help-text__icon"></info-icon>
</h3> </h3>
<text-form @text-change-value="$emit('change', $event.target.value)" :value="v"></text-form> <text-form @text-change-value="$emit('change', $event)" :value="v"></text-form>
</div> </div>
</template> </template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M15,50a2.48,2.48,0,0,0,2.49,2.5h65a2.5,2.5,0,0,0,0-5h-65A2.5,2.5,0,0,0,15,50Z"/>
<path d="M15,20.5A2.48,2.48,0,0,0,17.49,23h65a2.5,2.5,0,0,0,0-5h-65A2.5,2.5,0,0,0,15,20.5Z"/>
<path d="M15,79.5A2.48,2.48,0,0,0,17.49,82h65a2.5,2.5,0,0,0,0-5h-65A2.5,2.5,0,0,0,15,79.5Z"/>
</svg>
</template>

View File

@ -0,0 +1,37 @@
<template>
<svg class="logo" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1350 250">
<path
d="M304.4,242.15a60,60,0,0,1-19.59-3.1,64.2,64.2,0,0,1-17.6-9.63l-2.94-2.22,21.17-34,3.58,3.21a21.91,21.91,0,0,0,6,4,15.21,15.21,0,0,0,5.81,1.09c4,0,6.51-1.44,8.08-4.68l1.15-2.19L263.73,85.72H313.8L334.27,143l17.38-57.3h48.8L353,208.39c-4.53,11.34-10.91,19.87-19,25.41h0C326,239.34,316,242.15,304.4,242.15Zm-29.33-17a53.63,53.63,0,0,0,12.38,6.3,51.94,51.94,0,0,0,17,2.66c10,0,18.42-2.33,25.12-6.94h0c6.71-4.62,12.1-11.92,16-21.71L388.67,93.79h-31l-22.74,75-26.79-75H275.94L319,195l-2.88,5.47c-2.87,5.92-8.18,9.11-15.29,9.11a23.28,23.28,0,0,1-8.88-1.69,24.83,24.83,0,0,1-4.58-2.53Z"
style="fill:#36c0a1"/>
<path
d="M458.66,113a12.63,12.63,0,0,0-6.43,1.39,4.55,4.55,0,0,0-2.36,4.18q0,3.22,4.4,5.25a93.59,93.59,0,0,0,14,4.61,178.08,178.08,0,0,1,21.33,7.29,40.28,40.28,0,0,1,14.79,11q6.32,7.39,6.33,19,0,17.8-14,28.19t-37.19,10.4A102.76,102.76,0,0,1,430,200.15a84.64,84.64,0,0,1-25.4-12.33l13.29-27.22a97.33,97.33,0,0,0,21.76,10.72A64.21,64.21,0,0,0,460.16,175a14.94,14.94,0,0,0,7.07-1.39,4.33,4.33,0,0,0,2.57-4q0-3.22-4.18-5.25a84.51,84.51,0,0,0-13.83-4.61A157.5,157.5,0,0,1,431,152.67a40,40,0,0,1-14.58-10.93q-6.22-7.29-6.22-18.86,0-18,13.72-28.51t36-10.5q26.79,0,51.23,14.15l-14.36,27.22Q473,113,458.66,113Z"
style="fill:#36c0a1"/>
<path d="M604.69,202.4l-21.22-40.51-8.79,9.22v31.3h-43.3V43.35h43.3v77.38l32.15-34.94h48.87l-42.66,45,42.87,71.6Z"
style="fill:#36c0a1"/>
<path
d="M712.25,36.49q6.22,6.22,6.22,16.08t-6.22,16.08q-6.22,6.22-16.08,6.22T680,68.64q-6.33-6.21-6.32-16.08T680,36.49q6.32-6.21,16.18-6.22T712.25,36.49Zm-37.51,49.3H718V202.4h-43.3Z"
style="fill:#36c0a1"/>
<path d="M748.47,43.35h43.3V202.4h-43.3Z" style="fill:#36c0a1"/>
<path d="M823.5,43.35h43.3V202.4H823.5Z" style="fill:#36c0a1"/>
<path
d="M1002.06,91.79A50.33,50.33,0,0,1,1021,113q6.75,13.72,6.75,31.73,0,17.8-6.54,31.19a48.35,48.35,0,0,1-18.54,20.69q-12,7.29-27.87,7.29A44,44,0,0,1,956.19,200a40.21,40.21,0,0,1-14.36-11.15v13.5h-43.3V43.35h43.3V99.29a38.85,38.85,0,0,1,13.93-11.15,41.53,41.53,0,0,1,18-3.86Q989.85,84.29,1002.06,91.79Zm-23.8,70.63q5.79-7.18,5.79-18.76t-5.79-18.76a18.82,18.82,0,0,0-15.43-7.18,18.59,18.59,0,0,0-15.22,7.18q-5.79,7.19-5.79,18.76t5.79,18.76a18.58,18.58,0,0,0,15.22,7.18A18.8,18.8,0,0,0,978.27,162.42Z"
style="fill:#36c0a1"/>
<path
d="M1142.8,91.69a54.24,54.24,0,0,1,22.62,20.9q8,13.5,8,31.51,0,17.8-8,31.4a54,54,0,0,1-22.62,21q-14.58,7.4-34.08,7.4t-34.19-7.4a53.86,53.86,0,0,1-22.73-21q-8-13.61-8-31.4,0-18,8-31.51a54.08,54.08,0,0,1,22.73-20.9q14.68-7.4,34.19-7.4T1142.8,91.69Zm-49.52,34.08q-5.79,7.18-5.79,18.76,0,11.79,5.79,18.86a18.92,18.92,0,0,0,15.43,7.07,18.7,18.7,0,0,0,15.22-7.07q5.79-7.07,5.79-18.86,0-11.58-5.79-18.76a18.6,18.6,0,0,0-15.22-7.18A18.81,18.81,0,0,0,1093.28,125.77Z"
style="fill:#36c0a1"/>
<path
d="M1176.45,85.79h49.73L1242.26,116l18-30.23h47.16L1271,142.6l39,59.81h-49.73l-18-33-20.58,33h-47.59l39-59.59Z"
style="fill:#36c0a1"/>
<path
d="M245,105.8A38.35,38.35,0,0,0,229.9,89.74h0a46.56,46.56,0,0,0-46.21,1.09A41.77,41.77,0,0,0,171.45,103a38.76,38.76,0,0,0-11.67-12,42.9,42.9,0,0,0-24.06-6.82,44.09,44.09,0,0,0-21.4,5.16,41.05,41.05,0,0,0-8.13,5.83v-9.4H58V201.83h48.19V144.37c0-5.32,1.23-9.42,3.77-12.55a11.7,11.7,0,0,1,9.27-4.46,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V144.37c0-5.26,1.24-9.49,3.69-12.59a11.44,11.44,0,0,1,9.15-4.43,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V129.28A51.17,51.17,0,0,0,245,105.8Zm-2.87,88h-32v-53c0-6.25-1.7-11.41-5-15.33a17.51,17.51,0,0,0-14-6.18h0a19.38,19.38,0,0,0-15.37,7.49c-3.61,4.55-5.44,10.48-5.44,17.6v49.39h-32v-53c0-6.25-1.7-11.41-5-15.33a17.53,17.53,0,0,0-14-6.18h0a19.66,19.66,0,0,0-15.44,7.45c-3.69,4.56-5.57,10.49-5.57,17.63v49.39h-32v-100h32v18.16h5.19l2.25-3.54a34.63,34.63,0,0,1,12.63-12,36.13,36.13,0,0,1,17.53-4.17,35,35,0,0,1,19.62,5.49,31.51,31.51,0,0,1,12.17,15.38l.46,1.18h6.52l.45-1a36.75,36.75,0,0,1,50.91-16.55,30,30,0,0,1,11.93,12.74,43.2,43.2,0,0,1,4.33,19.81Z"
style="fill:#36c0a1"/>
</svg>
</template>
<style scoped lang="scss">
.logo {
width: 250px;
height: 48px;
}
</style>

View File

@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M50,0a50,50,0,1,0,50,50A50,50,0,0,0,50,0ZM38,74V26L70,50Z" style="fill:#17a887"/>
</svg>
</template>

View File

@ -137,7 +137,7 @@
width: 640px; width: 640px;
} }
flex-direction: column; flex-direction: column;
padding: 0 15px; padding: $large-spacing 15px;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<nav class="module-navigation"> <nav v-if="false" class="module-navigation">
<div class="module-navigation__module-content"> <div class="module-navigation__module-content">
<router-link <router-link
tag="h3" tag="h3"

View File

@ -0,0 +1,64 @@
<template>
<div class="page-form__page">
<form class="page-form" @submit.prevent="$emit('save')">
<div class="page-form__content">
<h1 class="page-form__heading">{{title}}</h1>
<slot></slot>
</div>
<div class="page-form__footer">
<slot name="footer"></slot>
</div>
</form>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.page-form {
width: 710px;
min-height: 760px;
max-height: 100%;
box-sizing: border-box;
background-color: $color-white;
border-radius: $default-border-radius;
display: flex;
flex-direction: column;
@supports (display: grid) {
display: grid;
}
grid-template-rows: 1fr 55px;
&__page {
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
justify-items: center;
align-items: center;
height: 100%;
}
&__content {
padding: $medium-spacing;
}
&__heading {
@include heading-2;
}
&__footer {
border-top: 1px solid $color-lightgrey;
padding: $small-spacing $medium-spacing;
}
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="page-form-input">
<label class="page-form-input__label" :for="id">{{label}}</label>
<component :is="type" :class="classes"
:value.prop="value"
:data-cy="cyId"
:id="id" @input="$emit('input', $event.target.value)"></component>
</div>
</template>
<script>
export default {
props: {
label: {
type: String
},
type: {
type: String,
default: 'input'
},
value: {
type: String
}
},
created() {
this.id = this._uid;
},
computed: {
classes() {
return `page-form-input__${this.type} skillbox-${this.type}`;
},
cyId() {
return `page-form-input-${this.label.toLowerCase()}`
}
},
data() {
return {
id: null
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.page-form-input {
&__label {
@include page-form-input-heading;
}
&__input, &__textarea {
width: 100%;
margin-bottom: $large-spacing;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<add-widget route="/new-project"></add-widget> <add-widget route="/new-project" data-cy="add-project-button"></add-widget>
</template> </template>
<script> <script>

View File

@ -6,14 +6,15 @@
import AddWidget from '@/components/AddWidget'; import AddWidget from '@/components/AddWidget';
export default { export default {
props: ['project'],
components: { components: {
AddWidget AddWidget
}, },
methods: { methods: {
addProjectEntry() { addProjectEntry() {
console.log('click'); this.$store.dispatch('addProjectEntry', this.project);
this.$store.dispatch('addProjectEntry', '');
} }
} }
} }

View File

@ -0,0 +1,41 @@
<template>
<project-form
:project="project"
@save="updateProject"
></project-form>
</template>
<script>
import ProjectForm from '@/components/portfolio/ProjectForm';
import UPDATE_PROJECT_MUTATION from '@/graphql/gql/mutations/updateProject.gql';
export default {
props: ['project'],
components: {
ProjectForm
},
methods: {
updateProject(project) {
this.$apollo.mutate({
mutation: UPDATE_PROJECT_MUTATION,
variables: {
input: {
project: {
id: project.id,
title: project.title,
description: project.description,
appearance: project.appearance,
objectives: project.objectives
}
}
}
}).then(() => {
this.$router.push('/portfolio');
});
}
},
}
</script>

View File

@ -1,13 +1,17 @@
<template> <template>
<modal :hide-header="true"> <modal :hide-header="true">
<div class="project-entry-modal"> <div class="project-entry-modal">
<text-form-with-help-text title="Tätigkeit" :value="value"> <text-form-with-help-text title="Tätigkeit" :value="activity" @change="activity = $event">
</text-form-with-help-text> </text-form-with-help-text>
<text-form-with-help-text title="Reflexion" :value="value"> <text-form-with-help-text title="Reflexion" :value="reflection" @change="reflection = $event">
</text-form-with-help-text> </text-form-with-help-text>
<text-form-with-help-text title="Nächste Schritte" :value="value"> <text-form-with-help-text title="Nächste Schritte" :value="nextSteps" @change="nextSteps = $event">
</text-form-with-help-text> </text-form-with-help-text>
</div> </div>
<div slot="footer">
<a class="button button--primary" data-cy="modal-save-button" v-on:click="save">Speichern</a>
<a class="button" v-on:click="hideModal">Abbrechen</a>
</div>
</modal> </modal>
</template> </template>
@ -15,15 +19,65 @@
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
import TextFormWithHelpText from '@/components/content-forms/TextFormWithHelpText'; import TextFormWithHelpText from '@/components/content-forms/TextFormWithHelpText';
import NEW_PROJECT_ENTRY_MUTATION from '@/graphql/gql/mutations/addProjectEntry.gql';
import PROJECT_QUERY from '@/graphql/gql/projectQuery.gql';
export default { export default {
components: { components: {
Modal, Modal,
TextFormWithHelpText TextFormWithHelpText
}, },
computed: {
project() {
return this.$store.state.parentProject;
},
slug() {
return this.$route.params.slug;
}
},
methods: {
save() {
this.$apollo.mutate({
mutation: NEW_PROJECT_ENTRY_MUTATION,
variables: {
input: {
projectEntry: Object.assign({}, {
nextSteps: this.nextSteps,
activity: this.activity,
reflection: this.reflection,
project: this.project
})
}
},
update: (store, {data: {addProjectEntry: {projectEntry}}}) => {
const query = PROJECT_QUERY;
const variables = {slug: this.slug};
const data = store.readQuery({query, variables});
if (data.project && data.project.entries) {
data.project.entries.edges.unshift({
node: projectEntry,
__typename: 'ProjectEntryNode'
});
store.writeQuery({query, variables, data});
}
}
}).then(() => {
this.hideModal();
});
},
hideModal() {
this.$store.dispatch('hideModal');
}
},
data() { data() {
return { return {
value: '' activity: '',
reflection: '',
nextSteps: ''
} }
} }
} }

View File

@ -1,65 +0,0 @@
<template>
<div class="portfolio">
<add-project></add-project>
<project-widget
v-for="project in projects"
v-bind="project" :key="project.id"
class="portfolio__project"
></project-widget>
</div>
</template>
<script>
import ProjectWidget from '@/components/portfolio/ProjectWidget';
import AddProject from '@/components/portfolio/AddProject';
export default {
components: {
ProjectWidget,
AddProject
},
data() {
return {
projects: [
{
id: 1,
title: 'Quartalsarbeit: Mein Lehrbetrieb',
appearance: 'green',
slug: 'quartalsarbeit'
},
{
id: 2,
title: 'Mein Projekt 2',
appearance: 'red',
slug: 'mein-projekt-2'
},
{
id: 3,
title: 'Mein Projekt 3',
appearance: 'blue',
slug: 'mein-projekt-3'
},
]
}
}
}
</script>
<style scoped lang="scss">
.portfolio {
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-row-gap: 30px;
grid-auto-rows: 200px;
width: 640px;
justify-self: center;
box-sizing: border-box;
}
</style>

View File

@ -2,27 +2,30 @@
<div class="project-entry"> <div class="project-entry">
<h3 class="project-entry__heading">Tätigkeit</h3> <h3 class="project-entry__heading">Tätigkeit</h3>
<p class="project-entry__paragraph"> <p class="project-entry__paragraph">
Ich führe das Interview mit meiner Kollegin durch. {{activity}}
</p> </p>
<h3 class="project-entry__heading">Reflexion</h3> <h3 class="project-entry__heading">Reflexion</h3>
<p class="project-entry__paragraph"> <p class="project-entry__paragraph">
Da ich geeignete Fragen hatte, konnte meine Kollegin umfangreiche Antworten zum Thema geben. Die Eingangshalle {{reflection}}
eignete sich nicht als Interviewort, da es viele Nebengeräusche hatte. Wir fanden aber dann ein freies
Klassenzimmer. Nicht nur die Fragen sind wichtig. Auch die Ortswahl, wo man das Interview aufzeichnen möchte,
ist sehr wichtig für ein erfolgreiches Interview.
</p> </p>
<h3 class="project-entry__heading"> <h3 class="project-entry__heading">
Nächste Schritte Nächste Schritte
</h3> </h3>
<p class="project-entry__paragraph"> <p class="project-entry__paragraph">
Interview im Raum Mein Lehrbetrieb ablegen. {{nextSteps}}
</p> </p>
<div class="project-entry__date"> <div class="project-entry__date">
21. Juni 2018 {{created | date }}
</div> </div>
</div> </div>
</template> </template>
<script>
export default {
props: ['activity', 'reflection', 'nextSteps', 'created']
}
</script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_functions.scss"; @import "@/styles/_functions.scss";

View File

@ -0,0 +1,65 @@
<template>
<page-form @save="$emit('save', localProject)" title="Neues Projekt">
<page-form-input label="Titel" v-model="localProject.title"></page-form-input>
<page-form-input label="Beschreibung" type="textarea" v-model="localProject.description"></page-form-input>
<page-form-input label="Ziele" type="textarea" v-model="localProject.objectives"></page-form-input>
<color-chooser
:selected-color="localProject.appearance"
v-on:input="updateColor"
></color-chooser>
<template slot="footer">
<button
type="submit"
class="button button--primary"
data-cy="save-project-button"
:class="{'button--disabled': !formValid}"
:disabled="!formValid"
>Speichern
</button>
<router-link to="/portfolio" tag="button" class="button">Abbrechen</router-link>
</template>
</page-form>
</template>
<script>
import PageForm from '@/components/page-form/PageForm';
import PageFormInput from '@/components/page-form/PageFormInput';
import ColorChooser from '@/components/ColorChooser';
export default {
props: ['project'],
components: {
PageForm,
PageFormInput,
ColorChooser
},
computed: {
formValid() {
return this.localProject.title && this.localProject.description && this.localProject.objectives;
}
},
data() {
return {
localProject: Object.assign({}, this.project),
}
},
methods: {
updateColor(newColor) {
this.localProject.appearance = newColor;
this.$store.dispatch('setSpecialContainerClass', newColor);
}
},
created() {
this.$store.dispatch('setSpecialContainerClass', this.localProject.appearance);
},
beforeDestroy() {
this.$store.dispatch('setSpecialContainerClass', '');
}
}
</script>

View File

@ -1,15 +1,19 @@
<template> <template>
<div class="project-widget" :class="widgetClass"> <div class="project-widget" :class="widgetClass">
<router-link :to="{name: 'project', params: {slug: slug}}" tag="div" class="project-widget__content"> <router-link :to="{name: 'project', params: {slug: slug}}" tag="div" class="project-widget__content">
<h3 class="project-widget__title">{{title}}</h3> <h3 class="project-widget__title">{{title}}</h3>
<entry-count-widget entry-count="4"></entry-count-widget>
<owner-widget name="Hans Muster"></owner-widget>
<entry-count-widget :entry-count="entriesCount"></entry-count-widget>
<owner-widget :name="owner"></owner-widget>
</router-link> </router-link>
<widget-footer <widget-footer v-if="isOwner" class="project-widget__footer">
entity="Eintrag" <template slot-scope="scope">
></widget-footer> <li class="popover-links__link"><a @click="$emit('delete', id)">Projekt löschen</a></li>
<li class="popover-links__link"><a @click="$emit('edit', id)">Projekt bearbeiten</a></li>
<li v-if="!final" class="popover-links__link"><a @click="share(scope)">Projekt teilen</a></li>
<li v-if="final" class="popover-links__link"><a @click="unshare(scope)">Projekt nicht mehr teilen</a></li>
</template>
</widget-footer>
</div> </div>
</template> </template>
@ -19,7 +23,7 @@
import WidgetFooter from '@/components/WidgetFooter'; import WidgetFooter from '@/components/WidgetFooter';
export default { export default {
props: ['title', 'appearance', 'slug'], props: ['title', 'appearance', 'slug', 'id', 'final', 'student', 'entriesCount', 'userId'],
components: { components: {
WidgetFooter, WidgetFooter,
@ -28,8 +32,27 @@
}, },
computed: { computed: {
widgetClass() { widgetClass () {
return `project-widget--${this.appearance}`; return `project-widget--${this.appearance}`;
},
isOwner () {
return this.student.id === this.userId;
},
owner () {
return `${this.student.firstName} ${this.student.lastName}`
}
},
methods: {
share: function (scope) {
this.updateShare(scope, true);
},
unshare: function (scope) {
this.updateShare(scope, false);
},
updateShare: function (scope, state) {
scope.hide();
this.$emit('updateShare', this.id, state);
} }
} }
} }
@ -47,14 +70,35 @@
box-sizing: border-box; box-sizing: border-box;
display: -ms-grid; display: -ms-grid;
margin-bottom: $large-spacing;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
margin-bottom: 0;
} }
grid-template-rows: 150px 1fr; grid-template-rows: 150px 1fr;
-ms-grid-rows: 150px 48px;
-ms-grid-columns: 1fr;
&__content { &__content {
padding: 23px; padding: 23px;
cursor: pointer; cursor: pointer;
-ms-grid-row: 1;
/*
* For IE10+
*/
display: -ms-grid;
-ms-grid-rows: 50px 30px 30px;
& > :nth-child(1) {
-ms-grid-row: 1;
}
& > :nth-child(2) {
-ms-grid-row: 2;
}
& > :nth-child(3) {
-ms-grid-row: 3;
}
} }
&__title { &__title {
@ -62,6 +106,10 @@
font-weight: 600; font-weight: 600;
} }
&__footer {
-ms-grid-row: 2;
}
@include skillbox-colors; @include skillbox-colors;
} }
</style> </style>

View File

@ -1,61 +0,0 @@
<template>
<room-form
:room="room"
@save="addRoom"
></room-form>
</template>
<script>
import RoomForm from '@/components/rooms/RoomForm';
import ADD_ROOM_MUTATION from '@/graphql/gql/mutations/addRoom.gql';
import ROOMS_QUERY from '@/graphql/gql/roomsQuery.gql';
const defaultColor = 'blue';
export default {
components: {
RoomForm
},
data() {
return {
room: {
appearance: defaultColor,
title: '',
description: '',
schoolClass: {}
}
}
},
methods: {
addRoom(room) {
this.$apollo.mutate({
mutation: ADD_ROOM_MUTATION,
variables: {
input: {
room: room
}
},
update: (store, {data: {addRoom: {room}}}) => {
try {
const data = store.readQuery({query: ROOMS_QUERY});
if (data.rooms) {
data.rooms.edges.push({
node: room,
__typename: 'RoomNode'
});
store.writeQuery({query: ROOMS_QUERY, data});
}
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
}).then(() => {
this.$router.push('/rooms');
});
}
},
}
</script>

View File

@ -4,13 +4,13 @@
<a @click="showMenu = !showMenu" class="room-entry__more-link"> <a @click="showMenu = !showMenu" class="room-entry__more-link">
<ellipses class="room-entry__ellipses"></ellipses> <ellipses class="room-entry__ellipses"></ellipses>
</a> </a>
<widget-popover entity="Eintrag" <widget-popover @hide-me="showMenu = false"
@delete="deleteRoomEntry"
@edit="editRoomEntry"
@hide-me="showMenu = false"
:id="id" :id="id"
class="room-entry__popover" class="room-entry__popover"
v-if="showMenu"></widget-popover> v-if="showMenu">
<li class="popover-links__link"><a @click="deleteRoomEntry(id)">Raum löschen</a></li>
<li class="popover-links__link"><a @click="editRoomEntry(id)">Raum bearbeiten</a></li>
</widget-popover>
</div> </div>
<router-link :to="{name: 'article', params: { slug: slug }}" tag="div" class="room-entry__router-link"> <router-link :to="{name: 'article', params: { slug: slug }}" tag="div" class="room-entry__router-link">
<div class="room-entry__header" v-if="image"> <div class="room-entry__header" v-if="image">

View File

@ -1,33 +1,31 @@
<template> <template>
<form class="room-form" @submit.prevent="$emit('save', localRoom)"> <page-form class="room-form" @save="$emit('save', localRoom)" title="Neues Board">
<div class="room-form__content"> <page-form-input v-model="localRoom.title" label="Titel">
<h1 class="room-form__heading">Neues Board</h1> </page-form-input>
<label class="room-form__property-heading" for="room-title">Titel</label>
<input class="skillbox-input room-form__input" v-model="localRoom.title" id="room-title"> <page-form-input v-model="localRoom.description" label="Beschreibung" type="textarea">
<label class="room-form__property-heading" for="room-description">Beschreibung</label> </page-form-input>
<textarea class="skillbox-textarea room-form__textarea" v-model="localRoom.description"
id="room-description"></textarea> <label class="room-form__property-heading" for="room-class">Klasse</label>
<label class="room-form__property-heading" for="room-class">Klasse</label> <select
<select class="skillbox-input room-form__input"
class="skillbox-input room-form__input" id="room-class"
id="room-class" v-model="localRoom.schoolClass"
v-model="localRoom.schoolClass" >
> <option :value="{}" disabled hidden>Klasse wählen</option>
<option :value="{}" disabled hidden>Klasse wählen</option> <option
<option v-for="schoolClass in schoolClasses"
v-for="schoolClass in schoolClasses" :key="schoolClass.id"
:key="schoolClass.id" v-bind:value="schoolClass"
v-bind:value="schoolClass" >{{schoolClass.name}}
>{{schoolClass.name}} </option>
</option> </select>
</select> <h2 class="room-form__property-heading">Farbe</h2>
<h2 class="room-form__property-heading">Farbe</h2> <color-chooser
<room-colors :selected-color="localRoom.appearance"
:selected-color="localRoom.appearance" v-on:input="updateColor"
v-on:input="updateColor" ></color-chooser>
></room-colors> <template slot="footer">
</div>
<div class="room-form__footer">
<button <button
type="submit" type="submit"
class="button button--primary room-form__save-button" class="button button--primary room-form__save-button"
@ -36,12 +34,14 @@
>Speichern >Speichern
</button> </button>
<router-link to="/rooms" tag="button" class="button">Abbrechen</router-link> <router-link to="/rooms" tag="button" class="button">Abbrechen</router-link>
</div> </template>
</form> </page-form>
</template> </template>
<script> <script>
import RoomColors from '@/components/rooms/RoomColors'; import ColorChooser from '@/components/ColorChooser';
import PageForm from '@/components/page-form/PageForm';
import PageFormInput from '@/components/page-form/PageFormInput';
import ME_QUERY from '@/graphql/gql/meQuery.gql'; import ME_QUERY from '@/graphql/gql/meQuery.gql';
@ -49,7 +49,9 @@
props: ['room'], props: ['room'],
components: { components: {
RoomColors ColorChooser,
PageForm,
PageFormInput
}, },
data() { data() {
@ -93,37 +95,11 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_functions.scss"; @import "@/styles/_mixins.scss";
.room-form { .room-form {
width: 710px;
min-height: 760px;
max-height: 100%;
box-sizing: border-box;
background-color: $color-white;
border-radius: $default-border-radius;
display: grid;
grid-template-rows: 1fr 65px;
&__heading {
font-size: toRem(35px);
}
&__content {
padding: 26px;
}
&__footer {
border-top: 1px solid $color-lightgrey;
padding: 16px 26px;
}
&__property-heading { &__property-heading {
display: block; @include page-form-input-heading;
font-size: toRem(21px);
font-weight: 800;
margin-bottom: 24px;
font-family: $sans-serif-font-family;
} }
&__input, &__input,

View File

@ -5,13 +5,9 @@
<room-group-widget v-bind="schoolClass"></room-group-widget> <room-group-widget v-bind="schoolClass"></room-group-widget>
<entry-count-widget :entryCount="entryCount"></entry-count-widget> <entry-count-widget :entryCount="entryCount"></entry-count-widget>
</router-link> </router-link>
<widget-footer <widget-footer v-if="canEditRoom">
v-if="canEditRoom" <li class="popover-links__link"><a @click="deleteRoom()">Raum löschen</a></li>
:on-delete="deleteRoom" <li class="popover-links__link"><a @click="editRoom()">Raum bearbeiten</a></li>
:on-edit="editRoom"
:id="id"
entity="Raum"
>
</widget-footer> </widget-footer>
</div> </div>
</template> </template>
@ -58,27 +54,28 @@
}, },
methods: { methods: {
deleteRoom(id) { deleteRoom() {
const theId = this.id
this.$apollo.mutate({ this.$apollo.mutate({
mutation: DELETE_ROOM_MUTATION, mutation: DELETE_ROOM_MUTATION,
variables: { variables: {
input: { input: {
id id: theId
} }
}, },
update(store, {data: {deleteRoom: {success}}}) { update(store, {data: {deleteRoom: {success}}}) {
if (success) { if (success) {
const data = store.readQuery({query: ROOMS_QUERY}); const data = store.readQuery({query: ROOMS_QUERY});
if (data) { if (data) {
data.rooms.edges.splice(data.rooms.edges.findIndex(edge => edge.node.id === id), 1); data.rooms.edges.splice(data.rooms.edges.findIndex(edge => edge.node.id === theId), 1);
store.writeQuery({query: ROOMS_QUERY, data}); store.writeQuery({query: ROOMS_QUERY, data});
} }
} }
} }
}) })
}, },
editRoom(id) { editRoom() {
this.$router.push({name: 'edit-room', params: {id: id}}); this.$router.push({name: 'edit-room', params: {id: this.id}});
} }
} }
} }
@ -93,6 +90,7 @@
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
} }
height: 260px;
grid-template-rows: 210px 1fr; grid-template-rows: 210px 1fr;
/*overflow: hidden;*/ /*overflow: hidden;*/
@include widget-shadow; @include widget-shadow;
@ -116,6 +114,21 @@
padding: 22px; padding: 22px;
color: $color-darkgrey-1; color: $color-darkgrey-1;
cursor: pointer; cursor: pointer;
/*
* For IE10+
*/
display: -ms-grid;
-ms-grid-rows: 80px 30px 30px;
& > :nth-child(1) {
-ms-grid-row: 1;
}
& > :nth-child(2) {
-ms-grid-row: 2;
}
& > :nth-child(3) {
-ms-grid-row: 3;
}
} }
&__title { &__title {

View File

@ -1,14 +1,13 @@
<template> <template>
<div class="room-popover" v-click-outside="hidePopover"> <div class="widget-popover" v-click-outside="hidePopover">
<a class="room-popover__link" @click="$emit('delete', id)">{{entity}} löschen</a> <ul class="widget-popover__links popover-links">
<a class="room-popover__link" @click="$emit('edit', id)">{{entity}} bearbeiten</a> <slot></slot>
</ul>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: ['id', 'entity'],
methods: { methods: {
hidePopover() { hidePopover() {
this.$emit('hide-me'); this.$emit('hide-me');
@ -21,23 +20,33 @@
@import "@/styles/_variables.scss"; @import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.room-popover { .widget-popover {
position: absolute; position: absolute;
right: 0; right: 0;
bottom: -110px; bottom: -110px;
display: grid; display: flex;
flex-direction: column;
background-color: $color-white; background-color: $color-white;
padding: 20px; padding: 20px;
z-index: 10; z-index: 10;
@include widget-shadow; @include widget-shadow;
}
.popover-links {
list-style: none;
display: grid;
&__link { &__link {
color: $color-grey; & > a {
font-family: $sans-serif-font-family; display: inline-block;
font-size: toRem(14px); color: $color-grey;
line-height: 1.5; font-family: $sans-serif-font-family;
padding: 5px 0; font-size: toRem(14px);
cursor: pointer; line-height: 1.5;
padding: 5px 0;
cursor: pointer;
}
} }
} }
</style> </style>

View File

@ -0,0 +1,9 @@
import moment from 'moment';
moment.locale('de');
export const dateFilter = value => {
if (value) {
return moment(String(value)).format('DD. MMMM YYYY');
}
};

View File

@ -0,0 +1,10 @@
#import "./fragments/projectParts.gql"
query ProjectsQuery {
projects {
edges {
node {
...ProjectParts
}
}
}
}

View File

@ -0,0 +1,7 @@
fragment ProjectEntryParts on ProjectEntryNode {
id
activity
reflection
nextSteps
created
}

View File

@ -0,0 +1,15 @@
fragment ProjectParts on ProjectNode {
id
title
appearance
description
slug
objectives
final
student {
firstName
lastName
id
}
entriesCount
}

View File

@ -0,0 +1,9 @@
#import "../fragments/projectParts.gql"
mutation AddProjectMutation($input: AddProjectInput!){
addProject(input: $input){
project {
...ProjectParts
}
errors
}
}

View File

@ -0,0 +1,9 @@
#import "../fragments/projectEntryParts.gql"
mutation AddProjectEntryMutation($input: AddProjectEntryInput!) {
addProjectEntry(input: $input) {
projectEntry {
...ProjectEntryParts
}
errors
}
}

View File

@ -0,0 +1,7 @@
mutation DeleteProject($input: DeleteProjectInput!) {
deleteProject(input: $input) {
success
errors
}
}

View File

@ -0,0 +1,8 @@
#import "../fragments/projectParts.gql"
mutation UpdateProjectMutation($input: UpdateProjectInput!){
updateProject(input: $input) {
project {
...ProjectParts
}
}
}

View File

@ -0,0 +1,15 @@
#import "./fragments/projectParts.gql"
#import "./fragments/projectEntryParts.gql"
query ProjectQuery($id: ID, $slug: String){
project(slug: $slug, id: $id) {
...ProjectParts
entries {
edges {
node {
...ProjectEntryParts
}
}
}
}
}

View File

@ -5,6 +5,7 @@ query Topic($slug: String!){
title title
teaser teaser
description description
vimeoId
modules { modules {
edges { edges {
node { node {

View File

@ -6,6 +6,10 @@
<style lang="scss"> <style lang="scss">
.blank-layout { .blank-layout {
/*
For IE11
*/
display: flex;
} }
</style> </style>

View File

@ -1,67 +1,38 @@
<template> <template>
<div class="container skillbox" :class="specialContainerClass"> <div class="container skillbox" :class="specialContainerClass">
<header class="header skillbox__header"> <header-bar class="header skillbox__header">
<top-navigation></top-navigation> </header-bar>
<router-link to="/" class="skillbox__header-logo">skillbox</router-link>
<div class="user-header">
<user-widget v-bind="me" :isProfile="isProfile"></user-widget>
<logout-widget></logout-widget>
</div>
<book-navigation v-if="showSubnavigation">
</book-navigation>
</header>
<filter-bar v-if="showFilter"></filter-bar> <mobile-header class="header skillbox__header skillbox__header--mobile"></mobile-header>
<router-view></router-view> <filter-bar v-if="showFilter" class="skillbox__filter-bar"></filter-bar>
<router-view class="skillbox__content"></router-view>
<footer class="skillbox__footer">Footer</footer> <footer class="skillbox__footer">Footer</footer>
</div> </div>
</template> </template>
<script> <script>
import TopNavigation from '@/components/TopNavigation.vue'; import FilterBar from '@/components/FilterBar';
import BookNavigation from '@/components/book-navigation/BookNavigation'; import HeaderBar from '@/components/HeaderBar';
import UserWidget from '@/components/UserWidget.vue'; import MobileHeader from '@/components/MobileHeader';
import FilterBar from '@/components/FilterBar.vue';
import LogoutWidget from '@/components/LogoutWidget.vue';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default { export default {
components: { components: {
TopNavigation,
UserWidget,
FilterBar, FilterBar,
LogoutWidget, HeaderBar,
BookNavigation MobileHeader
}, },
computed: { computed: {
showFilter() { showFilter() {
return this.$route.meta.filter; return this.$route.meta.filter;
}, },
showSubnavigation() {
return this.$route.meta.subnavigation;
},
specialContainerClass() { specialContainerClass() {
let cls = this.$store.state.specialContainerClass; let cls = this.$store.state.specialContainerClass;
return [cls ? `skillbox--${cls}` : '', {'skillbox--show-filter': this.showFilter}] return [cls ? `skillbox--${cls}` : '', {'skillbox--show-filter': this.showFilter}]
},
isProfile() {
return this.$route.meta.isProfile;
},
},
data() {
return {
me: {}
} }
}, }
apollo: {
me: {
query: ME_QUERY,
},
},
} }
</script> </script>
@ -72,7 +43,9 @@
.skillbox { .skillbox {
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
display: grid; @supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
min-height: 100vh; min-height: 100vh;
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
@ -82,81 +55,41 @@
&--show-filter { &--show-filter {
grid-template-rows: auto auto 1fr; grid-template-rows: auto auto 1fr;
-ms-grid-rows: 50px 50px 30px 1fr; // 1 extra row for gap
grid-template-areas: "h" "." "c"; grid-template-areas: "h" "." "c";
} }
/*
* For IE10+
*/
&--show-filter &__content {
-ms-grid-row: 4;
-ms-grid-column: 1;
}
&--show-filter &__filter-bar {
-ms-grid-row: 2;
-ms-grid-column: 1;
}
/* /*
* For IE10+ * For IE10+
*/ */
display: -ms-grid; display: -ms-grid;
-ms-grid-rows: auto 32px 1fr; // 1 extra row for gap -ms-grid-rows: 50px 30px 1fr; // 1 extra row for gap
-ms-grid-columns: 1fr; -ms-grid-columns: 1fr;
@include skillbox-colors; @include skillbox-colors;
&__header { &__header {
grid-area: h; grid-area: h;
display: -ms-grid; -ms-grid-row: 1;
@supports (display: grid) {
display: grid;
}
align-items: center;
justify-content: space-around;
background-color: $color-white;
grid-auto-rows: 50px;
width: 100%;
@include desktop {
grid-template-columns: 1fr 1fr 1fr;
}
/*
* For IE10+
*/
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 60px 60px;
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-column: 1;
-ms-grid-row-align: center;
}
/*
* For IE10+
*/
& > :nth-child(3) {
-ms-grid-column: 3;
-ms-grid-row-align: center;
-ms-grid-column-align: end;
justify-self: end;
}
& > :nth-child(4) {
-ms-grid-row: 2;
-ms-grid-column: 1;
-ms-grid-column-span: 3;
}
} }
&__header-logo { &__content {
color: #17A887; -ms-grid-row: 3;
font-size: 36px; -ms-grid-column: 1;
font-weight: 800;
font-family: $sans-serif-font-family;
display: flex;
justify-self: center;
/*
* For IE10+
*/
-ms-grid-column: 2;
-ms-grid-row-align: center;
-ms-grid-column-align: center;
} }
&__footer { &__footer {
@ -168,12 +101,12 @@
* For IE10+ * For IE10+
*/ */
& > :nth-child(2) { & > :nth-child(2) {
-ms-grid-row: 3;
}
& > :nth-child(3) {
-ms-grid-row: 4;
-ms-grid-column: 1; -ms-grid-column: 1;
} }
} }
.user-header {
display: flex;
}
</style> </style>

View File

@ -13,14 +13,19 @@
.layout { .layout {
&--simple { &--simple {
display: -ms-grid; display: -ms-grid;
display: grid; @supports (display: grid) {
display: grid;
}
padding: 20px; padding: 20px;
width: 100%;
@include desktop { @include desktop {
grid-template-columns: 1fr 640px 1fr; grid-template-columns: 1fr 640px 1fr;
-ms-grid-columns: 1fr 640px 1fr;
& > :nth-child(2) { & > :nth-child(2) {
grid-column: 2; grid-column: 2;
-ms-grid-column: 2;
} }
} }
@ -31,8 +36,12 @@
justify-self: end; justify-self: end;
cursor: pointer; cursor: pointer;
display:flex;
justify-content:end;
@include desktop { @include desktop {
grid-column: 3; grid-column: 3;
-ms-grid-column: 3;
} }
&__icon { &__icon {

View File

@ -13,6 +13,7 @@ import VueAnalytics from 'vue-analytics';
import { Validator, install as VeeValidate } from 'vee-validate/dist/vee-validate.minimal.esm.js'; import { Validator, install as VeeValidate } from 'vee-validate/dist/vee-validate.minimal.esm.js';
import { required, min } from 'vee-validate/dist/rules.esm.js'; import { required, min } from 'vee-validate/dist/rules.esm.js';
import veeDe from 'vee-validate/dist/locale/de'; import veeDe from 'vee-validate/dist/locale/de';
import {dateFilter} from './filters/date-filter'
Vue.config.productionTip = false; Vue.config.productionTip = false;
@ -105,6 +106,8 @@ Vue.use(VeeValidate, {
locale: 'de' locale: 'de'
}); });
Vue.filter('date', dateFilter);
/* eslint-disable no-new */ /* eslint-disable no-new */
new Vue({ new Vue({
el: '#app', el: '#app',

View File

@ -22,6 +22,8 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.book { .book {
padding-top: $large-spacing;
&__content { &__content {
display: -ms-grid; display: -ms-grid;
@supports (display: grid) { @supports (display: grid) {

View File

@ -0,0 +1,37 @@
<template>
<div class="edit-project-page">
<edit-project :project="project" v-if="this.project.id"></edit-project>
</div>
</template>
<script>
// todo: refactor this, we don't need 2 components, remove editRoom or EditRoom component
import EditProject from '@/components/portfolio/EditProject';
import PROJECT_QUERY from '@/graphql/gql/projectQuery.gql';
export default {
props: ['id'],
components: {
EditProject
},
data() {
return {
project: {}
}
},
apollo: {
project: {
query: PROJECT_QUERY,
variables() {
return {
id: this.id
}
}
}
}
}
</script>

View File

@ -5,6 +5,7 @@
</template> </template>
<script> <script>
// todo: refactor this, we don't need 2 components, remove editRoom or EditRoom component
import EditRoom from '@/components/rooms/EditRoom'; import EditRoom from '@/components/rooms/EditRoom';
import ROOM_QUERY from '@/graphql/gql/roomQuery.gql'; import ROOM_QUERY from '@/graphql/gql/roomQuery.gql';
@ -41,11 +42,3 @@
} }
} }
</script> </script>
<style scoped lang="scss">
.edit-room-page {
display: grid;
justify-items: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<project-form
@save="saveProject"
:project="project"
></project-form>
</template>
<script>
import ProjectForm from '@/components/portfolio/ProjectForm';
import ADD_PROJECT_MUTATION from '@/graphql/gql/mutations/addProject.gql';
import PROJECTS_QUERY from '@/graphql/gql/allProjects.gql';
const defaultAppearance = 'blue';
export default {
components: {
ProjectForm
},
computed: {
project() {
return {
title: '',
description: '',
objectives: '',
appearance: defaultAppearance
}
}
},
methods: {
saveProject(project) {
this.$apollo.mutate({
mutation: ADD_PROJECT_MUTATION,
variables: {
input: {
project: project
}
},
update: (store, {data: {addProject: {project}}}) => {
try {
const data = store.readQuery({query: PROJECTS_QUERY});
if (data.projects) {
data.projects.edges.unshift({
node: project,
__typename: 'ProjectNode'
});
store.writeQuery({query: PROJECTS_QUERY, data});
}
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
}).then(() => {
this.$router.push('/portfolio');
});
}
}
}
</script>

View File

@ -1,23 +1,61 @@
<template> <template>
<div class="new-room-page"> <room-form
<new-room></new-room> :room="room"
</div> @save="addRoom"
></room-form>
</template> </template>
<script> <script>
import NewRoom from '@/components/rooms/NewRoom'; import RoomForm from '@/components/rooms/RoomForm';
import ADD_ROOM_MUTATION from '@/graphql/gql/mutations/addRoom.gql';
import ROOMS_QUERY from '@/graphql/gql/roomsQuery.gql';
const defaultAppearance = 'blue';
export default { export default {
components: { components: {
NewRoom RoomForm
} },
data() {
return {
room: {
appearance: defaultAppearance,
title: '',
description: '',
schoolClass: {}
}
}
},
methods: {
addRoom(room) {
this.$apollo.mutate({
mutation: ADD_ROOM_MUTATION,
variables: {
input: {
room: room
}
},
update: (store, {data: {addRoom: {room}}}) => {
try {
const data = store.readQuery({query: ROOMS_QUERY});
if (data.rooms) {
data.rooms.edges.push({
node: room,
__typename: 'RoomNode'
});
store.writeQuery({query: ROOMS_QUERY, data});
}
} catch (e) {
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
}
}
}).then(() => {
this.$router.push('/rooms');
});
}
},
} }
</script> </script>
<style scoped lang="scss">
.new-room-page {
display: grid;
justify-items: center;
align-items: center;
}
</style>

View File

@ -1,23 +1,143 @@
<template> <template>
<div class="portfolio-page"> <div class="portfolio__page">
<portfolio></portfolio> <div class="portfolio">
<add-project class="portfolio__add-project"></add-project>
<project-widget
v-for="project in projects"
v-bind="project"
:userId="userId"
@delete="deleteProject"
@updateShare="updateShareState"
@edit="editProject"
:key="project.id"
class="portfolio__project"
></project-widget>
</div>
</div> </div>
</template> </template>
<script> <script>
import Portfolio from '@/components/portfolio/Portfolio'; import ProjectWidget from '@/components/portfolio/ProjectWidget';
import AddProject from '@/components/portfolio/AddProject';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import PROJECTS_QUERY from '@/graphql/gql/allProjects.gql';
import DELETE_PROJECT_MUTATION from '@/graphql/gql/mutations/deleteProject.gql';
import UPDATE_PROJECT_MUTATION from '@/graphql/gql/mutations/updateProject.gql';
export default { export default {
components: { components: {
Portfolio ProjectWidget,
AddProject
},
apollo: {
projects: {
query: PROJECTS_QUERY,
update(data) {
return this.$getRidOfEdges(data).projects
}
},
me: {
query: ME_QUERY
}
},
data () {
return {
projects: [],
me: {}
}
},
computed: {
userId () {
return this.me.id;
}
},
methods: {
deleteProject(id) {
this.$apollo.mutate({
mutation: DELETE_PROJECT_MUTATION,
variables: {
input: {
id
}
},
update(store, {data: {deleteProject: {success}}}) {
if (success) {
const data = store.readQuery({query: PROJECTS_QUERY});
if (data) {
data.projects.edges.splice(data.projects.edges.findIndex(edge => edge.node.id === id), 1);
store.writeQuery({query: PROJECTS_QUERY, data});
}
}
}
})
},
editProject(id) {
this.$router.push({name: 'edit-project', params: { id }});
},
updateShareState(id, state) {
const project = this.projects.filter(project => project.id === id)[0];
this.$apollo.mutate({
mutation: UPDATE_PROJECT_MUTATION,
variables: {
input: {
project: {
id: project.id,
title: project.title,
description: project.description,
appearance: project.appearance,
objectives: project.objectives,
final: state
}
}
}
})
}
} }
} }
</script> </script>
<style> <style scoped lang="scss">
.portfolio-page { @import "@/styles/_variables.scss";
display: grid;
align-content: center; .portfolio {
justify-content: center; display: flex;
flex-direction: column;
@supports (display: grid) {
display: grid;
}
grid-row-gap: 30px;
grid-auto-rows: 200px;
max-width: 640px;
width: auto;
justify-self: center;
box-sizing: border-box;
padding: $large-spacing $medium-spacing;
&__page {
display: flex;
@supports (display: grid) {
display: grid;
}
align-content: center;
justify-content: center;
padding-top: $large-spacing;
}
/*IE10*/
&__add-project {
margin-bottom: $large-spacing;
@supports (display: grid) {
margin-bottom: 0;
}
}
} }
</style> </style>

View File

@ -1,28 +1,20 @@
<template> <template>
<div class="project"> <div class="project">
<div class="project__header"> <div class="project__header">
<h1 class="project__title">Quartalsarbeit: Mein Lehrbetrieb</h1> <h1 class="project__title">{{project.title}}</h1>
<p class="project__description"> <p class="project__description">
Das ist eine Beschreibung und kann maximal 200 Zeichen enthalten. Das ist eine Beschreibung und kann maximal 200 {{project.description}}
Zeichen enthalten. Das ist eine Beschreibung und kann maximal 200 Zeichen enthalten.
</p> </p>
<h2 class="project__objectives-title">Ziele</h2> <h2 class="project__objectives-title">Ziele</h2>
<ul class="project__objectives"> <ul class="project__objectives">
<li class="project__objective">Ich kann ein Interview mit geeigneten Fragen vorbereiten.</li> <li class="project__objective" :key="index" v-for="(objective, index) in objectives">{{objective}}</li>
<li class="project__objective">Ich kann ein Interview führen und auf interessante oder ausweichende
Antworten näher eingehen.
</li>
<li class="project__objective">Ich kann ein mündlich geführtes Interview in Standardsprache
aufzeichnen.
</li>
</ul> </ul>
</div> </div>
<div class="project__content"> <div class="project__content">
<add-project-entry class="project__add-entry"></add-project-entry> <add-project-entry class="project__add-entry" :project="project.id"></add-project-entry>
<project-entry></project-entry> <project-entry v-bind="entry" v-for="(entry, index) in project.entries" :key="index"></project-entry>
<project-entry></project-entry>
</div> </div>
</div> </div>
</template> </template>
@ -31,13 +23,42 @@
import ProjectEntry from '@/components/portfolio/ProjectEntry'; import ProjectEntry from '@/components/portfolio/ProjectEntry';
import AddProjectEntry from '@/components/portfolio/AddProjectEntry'; import AddProjectEntry from '@/components/portfolio/AddProjectEntry';
import PROJECT_QUERY from '@/graphql/gql/projectQuery.gql';
export default { export default {
props: ['slug'],
components: { components: {
AddProjectEntry, AddProjectEntry,
ProjectEntry ProjectEntry
}, },
computed: {
objectives() {
return this.project.objectives ? this.project.objectives.split('\n') : [];
}
},
apollo: {
project: {
query: PROJECT_QUERY,
variables() {
return {
slug: this.slug
}
},
update(data) {
return this.$getRidOfEdges(data).project || {};
}
}
},
data() {
return {
project: {}
}
},
created() { created() {
this.$store.dispatch('setSpecialContainerClass', 'red'); this.$store.dispatch('setSpecialContainerClass', 'red');
}, },
@ -53,10 +74,8 @@
@import "@/styles/_functions.scss"; @import "@/styles/_functions.scss";
.project { .project {
margin-bottom: -50px;
&__header { &__header {
padding: 30px; padding: $large-spacing;
} }
&__add-entry { &__add-entry {
@ -88,12 +107,19 @@
&__content { &__content {
background-color: $color-grey--lighter; background-color: $color-grey--lighter;
display: grid; display: flex;
grid-template-columns: 840px; flex-direction: column;
max-width: 840px;
align-content: center;
margin: 0 auto;
@supports (display: grid) {
display: grid;
width: auto;
}
grid-template-columns: minmax(max-content, 840px);
grid-row-gap: 30px; grid-row-gap: 30px;
justify-content: center; justify-content: center;
padding-top: 30px; padding: $large-spacing $medium-spacing;
padding-bottom: 50px;
} }
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="rooms-page"> <div class="rooms-page">
<room-widget v-for="room in filteredRooms" v-bind="room" :key="room.name"></room-widget> <room-widget v-for="room in filteredRooms" v-bind="room" :key="room.name"></room-widget>
<add-room v-if="canAddRoom" data-cy="add-room"></add-room> <add-room class="rooms-page__add-room" v-if="canAddRoom" data-cy="add-room"></add-room>
</div> </div>
</template> </template>
@ -63,7 +63,10 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.rooms-page { .rooms-page {
display: -ms-grid; display: flex;
flex-wrap: wrap;
align-content: start;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
} }
@ -71,6 +74,7 @@
padding: 50px 15px; padding: 50px 15px;
@include desktop { @include desktop {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
-ms-grid-columns: 1fr 30px 1fr 30px 1fr;
padding: 50px 45px; padding: 50px 45px;
} }
grid-column-gap: 30px; grid-column-gap: 30px;
@ -81,26 +85,22 @@
justify-self: center; justify-self: center;
box-sizing: border-box; box-sizing: border-box;
/* &__add-room {
* For IE10+ visibility: hidden;
*/
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 260px;
/* @include desktop {
* SHAME SHAME SHAME visibility: visible;
* this is very hacky, but we have a dynamic amount of elements. better to be safe than sorry }
* SHAME SHAME SHAME }
*/
@for $i from 1 to 101 {
& > :nth-child(#{$i}) {
@if ($i%3) == 0 {
-ms-grid-column: 3;
} @else {
-ms-grid-column: ($i%3);
}
-ms-grid-row: floor(($i - 1)/3)+1; & > div {
flex: 0 0 30%;
margin-bottom: $large-spacing;
margin-right: 1%;
@supports (display: grid) {
margin-bottom: inherit;
margin-right: inherit;
} }
} }

View File

@ -1,6 +1,9 @@
<template> <template>
<div class="start-page"> <div class="start-page">
<h1 class="start-page__title h1"><span class="start-page__my">my</span>skillbox</h1> <header-bar class="start-page__header"></header-bar>
<mobile-header class="start-page__header start-page__header--mobile"></mobile-header>
<div class="start-page__sections start-sections"> <div class="start-page__sections start-sections">
<section-block <section-block
@ -43,20 +46,22 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import SectionBlock from '@/components/SectionBlock.vue'; import SectionBlock from '@/components/SectionBlock.vue';
import NewsTeaser from '@/components/NewsTeaser.vue'; import NewsTeaser from '@/components/NewsTeaser.vue';
import HeaderBar from '@/components/HeaderBar';
import ContentsIllustration from '@/components/illustrations/ContentsIllustration'; import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration'; import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
import RoomsIllustration from '@/components/illustrations/RoomsIllustration'; import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
import {meQuery} from '@/graphql/queries'; import {meQuery} from '@/graphql/queries';
import MobileHeader from '@/components/MobileHeader';
export default { export default {
components: { components: {
MobileHeader,
HeaderBar,
SectionBlock, SectionBlock,
NewsTeaser, NewsTeaser,
ContentsIllustration, ContentsIllustration,
@ -97,11 +102,21 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.start-page { .start-page {
display: grid; display: flex;
flex-direction: column;
justify-content: space-between;
@supports (display: grid) {
display: grid;
justify-content: stretch;
}
grid-template-rows: auto 1fr auto; grid-template-rows: auto 1fr auto;
min-height: 100vh; min-height: 100vh;
width: 100vw;
box-sizing: border-box; box-sizing: border-box;
padding-top: 2*$large-spacing;
&__header {
}
&__title { &__title {
color: $color-brand; color: $color-brand;
@ -132,6 +147,8 @@
padding-right: 120px; padding-right: 120px;
} }
display: -ms-grid; display: -ms-grid;
-ms-grid-column-align: center;
-ms-grid-columns: 1fr 1fr 1fr;
margin-bottom: 90px; margin-bottom: 90px;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
@ -143,6 +160,24 @@
grid-row-gap: 15px; grid-row-gap: 15px;
grid-column-gap: 50px; grid-column-gap: 50px;
justify-items: start; justify-items: start;
align-items: center;
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
}
} }
.news { .news {
@ -150,8 +185,39 @@
color: $color-white; color: $color-white;
padding-top: $large-spacing; padding-top: $large-spacing;
padding-bottom: $large-spacing; padding-bottom: $large-spacing;
display: grid; display: flex;
grid-template-columns: repeat(5, 1fr); justify-content: space-around;
-ms-grid-columns: 1fr 1fr 1fr;
@supports (display: grid) {
display: grid;
grid-template-columns: 1fr;
}
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(5, 1fr);
}
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
}
& > :nth-child(4) {
-ms-grid-row: 1;
-ms-grid-column: 4;
}
@include desktop { @include desktop {
/*padding-left: 120px;*/ /*padding-left: 120px;*/
@ -166,29 +232,21 @@
padding-bottom: 24px; padding-bottom: 24px;
text-align: center; text-align: center;
color: inherit; color: inherit;
margin-bottom: 0;
} }
&__more { &__more {
color: $color-white; color: $color-white;
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
border-left: 1px solid $color-lightgrey;
line-height: $default-line-height; line-height: $default-line-height;
padding-left: $medium-spacing; padding-left: $medium-spacing;
font-weight: $font-weight-bold; font-weight: $font-weight-bold;
} text-align: center;
&__teasers {
@supports (display: grid) {
display: grid;
}
@include desktop { @include desktop {
grid-template-columns: 1fr 1fr 1fr; text-align: left;
border-left: 1px solid $color-lightgrey;
} }
grid-row-gap: 15px;
grid-column-gap: 50px;
} }
} }
</style> </style>

View File

@ -1,5 +1,9 @@
<template> <template>
<div class="submissions-page"> <div class="submissions-page">
<div>
<a class="button button--primary submissions-page__back" @click="back">Zurück zur Aufgabe</a>
</div>
<assignment-with-submissions v-if="!$apollo.queries.assignment.loading" <assignment-with-submissions v-if="!$apollo.queries.assignment.loading"
:assignment="assignment"></assignment-with-submissions> :assignment="assignment"></assignment-with-submissions>
</div> </div>
@ -32,6 +36,12 @@
}, },
}, },
methods: {
back() {
this.$router.go(-1);
}
},
data() { data() {
return { return {
assignment: { assignment: {
@ -46,6 +56,11 @@
@import "@/styles/_mixins.scss"; @import "@/styles/_mixins.scss";
.submissions-page { .submissions-page {
display: grid;
grid-row-gap: $large-spacing;
grid-template-rows: auto 1fr;
@include desktop { @include desktop {
width: 800px; width: 800px;
} }

View File

@ -4,6 +4,10 @@
<p class="topic__teaser"> <p class="topic__teaser">
{{topic.teaser}} {{topic.teaser}}
</p> </p>
<div v-if="topic.vimeoId" class="topic__video-link" @click="openVideo">
<play class="topic__video-link-icon"></play>
<span class="topic__video-link-description">Video schauen</span>
</div>
<div class="topic__modules"> <div class="topic__modules">
<module-teaser v-for="module in modules" :key="module.id" v-bind="module"></module-teaser> <module-teaser v-for="module in modules" :key="module.id" v-bind="module"></module-teaser>
</div> </div>
@ -12,11 +16,13 @@
<script> <script>
import ModuleTeaser from '@/components/modules/ModuleTeaser.vue'; import ModuleTeaser from '@/components/modules/ModuleTeaser.vue';
import Play from '@/components/icons/Play';
import TOPIC_QUERY from '@/graphql/gql/topicQuery.gql'; import TOPIC_QUERY from '@/graphql/gql/topicQuery.gql';
export default { export default {
components: { components: {
ModuleTeaser ModuleTeaser,
Play
}, },
apollo: { apollo: {
@ -39,6 +45,12 @@
} }
}, },
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
}
},
data() { data() {
return { return {
topic: { topic: {
@ -60,6 +72,23 @@
color: $color-darkgrey-1; color: $color-darkgrey-1;
width: 90%; width: 90%;
@include lead-paragraph; @include lead-paragraph;
margin-bottom: $large-spacing;
}
&__video-link {
margin-bottom: $large-spacing;
cursor: pointer;
display: flex;
align-items: center;
}
&__video-link-icon {
width: 40px;
margin-right: $medium-spacing;
}
&__video-link-description {
@include heading-3;
} }
&__modules { &__modules {

View File

@ -10,7 +10,7 @@ import newRoom from '@/pages/newRoom'
import editRoom from '@/pages/editRoom' import editRoom from '@/pages/editRoom'
import article from '@/pages/article' import article from '@/pages/article'
import basicknowledge from '@/pages/basicknowledge' import basicknowledge from '@/pages/basicknowledge'
import basicknowledgeOverview from '@/pages/basicknowledge-overview' import basicknowledgeOverview from '@/pages/basicknowledgeOverview'
import submissions from '@/pages/submissions' import submissions from '@/pages/submissions'
import p404 from '@/pages/p404' import p404 from '@/pages/p404'
import start from '@/pages/start' import start from '@/pages/start'
@ -22,6 +22,10 @@ import passwordChange from '@/pages/passwordChange'
import myClasses from '@/pages/myClasses' import myClasses from '@/pages/myClasses'
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 newProject from '@/pages/newProject'
import store from '@/store/index';
const routes = [ const routes = [
{path: '/', component: start, meta: {layout: 'blank'}}, {path: '/', component: start, meta: {layout: 'blank'}},
@ -50,11 +54,18 @@ const routes = [
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true}, {path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
{path: '/room/:slug', name: 'room', component: room, props: true}, {path: '/room/:slug', name: 'room', component: room, props: true},
{path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}}, {path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}},
{path: '/basic-knowledge/', name: 'basic-knowledge-overview', component: basicknowledgeOverview, meta: {subnavigation: true}}, {
path: '/basic-knowledge/',
name: 'basic-knowledge-overview',
component: basicknowledgeOverview,
meta: {subnavigation: true}
},
{path: '/basic-knowledge/:slug', name: 'basic-knowledge', component: basicknowledge, meta: {layout: 'simple'}}, {path: '/basic-knowledge/:slug', name: 'basic-knowledge', component: basicknowledge, meta: {layout: 'simple'}},
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}}, {path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
{path: '/portfolio', name: 'portfolio', component: portfolio}, {path: '/portfolio', name: 'portfolio', component: portfolio},
{path: '/portfolio/:slug', name: 'project', component: project}, {path: '/portfolio/:slug', name: 'project', component: project, props: true},
{path: '/new-project/', name: 'new-project', component: newProject},
{path: '/edit-project/:id', name: 'edit-project', component: editProject, props: true},
{ {
path: '/book', path: '/book',
name: 'book', name: 'book',
@ -83,7 +94,8 @@ const routes = [
]; ];
Vue.use(Router); Vue.use(Router);
export default new Router({
const router = new Router({
routes, routes,
mode: 'history', mode: 'history',
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
@ -92,4 +104,8 @@ export default new Router({
} }
return {x: 0, y: 0} return {x: 0, y: 0}
} }
}) });
router.afterEach((to, from) => {
store.dispatch('showMobileNavigation', false);
});
export default router;

View File

@ -9,6 +9,7 @@ export default new Vuex.Store({
state: { state: {
specialContainerClass: '', specialContainerClass: '',
showModal: '', showModal: '',
showMobileNavigation: false,
contentBlockPosition: {}, contentBlockPosition: {},
scrollPosition: 0, scrollPosition: 0,
filterForSchoolClass: '', filterForSchoolClass: '',
@ -23,10 +24,18 @@ export default new Vuex.Store({
infographic: { infographic: {
id: 0, id: 0,
type: '' type: ''
} },
vimeoId: null
}, },
getters: {}, getters: {
showModal: state => {
return state.showModal
},
showMobileNavigation: state => {
return state.showMobileNavigation
}
},
actions: { actions: {
setSpecialContainerClass({commit}, payload) { setSpecialContainerClass({commit}, payload) {
@ -51,6 +60,7 @@ export default new Vuex.Store({
id: 0, id: 0,
type: '' type: ''
}); });
commit('setVimeoId', null);
}, },
resetContentBlockPosition({commit}) { resetContentBlockPosition({commit}) {
commit('setContentBlockPosition', {}); commit('setContentBlockPosition', {});
@ -102,6 +112,13 @@ export default new Vuex.Store({
commit('setInfographic', payload); commit('setInfographic', payload);
dispatch('showModal', 'fullscreen-infographic'); dispatch('showModal', 'fullscreen-infographic');
}, },
showFullscreenVideo({commit, dispatch}, payload) {
commit('setVimeoId', payload);
dispatch('showModal', 'fullscreen-video');
},
showMobileNavigation({commit}, payload) {
commit('setShowMobileNavigation', payload);
}
}, },
mutations: { mutations: {
@ -149,6 +166,12 @@ export default new Vuex.Store({
}, },
setInfographic(state, payload) { setInfographic(state, payload) {
state.infographic = payload; state.infographic = payload;
},
setVimeoId(state, payload) {
state.vimeoId = payload;
},
setShowMobileNavigation(state, payload) {
state.showMobileNavigation = payload;
} }
} }
}) })

View File

@ -2,15 +2,24 @@
&__header { &__header {
grid-template-rows: 50px minmax(50px, max-content) auto; grid-template-rows: 50px minmax(50px, max-content) auto;
-ms-grid-rows: 50px 50px auto;
grid-row-gap: 30px; grid-row-gap: 30px;
display: -ms-grid; display: -ms-flex;
display: grid; @supports (display: grid) {
display: grid;
}
} }
&__meta { &__meta {
border-bottom: 1px solid $color-lightgrey; border-bottom: 1px solid $color-lightgrey;
align-self: end; align-self: end;
padding: 20px 0; padding: 20px 0;
width: 100%;
margin-bottom: $large-spacing;
@supports (display: grid) {
margin-bottom: 0;
}
} }
&__title { &__title {
@ -24,8 +33,11 @@
} }
&__content { &__content {
display: -ms-grid; display: flex;
display: grid; flex-direction: column;
@supports (display: grid) {
display: grid;
}
grid-row-gap: 40px; grid-row-gap: 40px;
padding-bottom: 40px; padding-bottom: 40px;

View File

@ -132,3 +132,9 @@
font-family: $serif-font-family; font-family: $serif-font-family;
font-size: toRem(18px); font-size: toRem(18px);
} }
@mixin page-form-input-heading {
display: block;
margin-bottom: $medium-spacing;
@include heading-3;
}

10
package-lock.json generated
View File

@ -9,9 +9,9 @@
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": { "requires": {
"babel-runtime": "^6.26.0", "babel-runtime": "6.26.0",
"core-js": "^2.5.0", "core-js": "2.5.7",
"regenerator-runtime": "^0.10.5" "regenerator-runtime": "0.10.5"
} }
}, },
"babel-runtime": { "babel-runtime": {
@ -19,8 +19,8 @@
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": { "requires": {
"core-js": "^2.4.0", "core-js": "2.5.7",
"regenerator-runtime": "^0.11.0" "regenerator-runtime": "0.11.1"
}, },
"dependencies": { "dependencies": {
"regenerator-runtime": { "regenerator-runtime": {

3
server/.coveragerc Normal file
View File

@ -0,0 +1,3 @@
[report]
omit =
*/site-packages/*

View File

@ -5,3 +5,4 @@ USE_AWS=False
AWS_ACCESS_KEY_ID= AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY= AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME= AWS_STORAGE_BUCKET_NAME=
SENDGRID_API_KEY=

View File

@ -13,6 +13,8 @@ from books.schema.queries import BookQuery
from core.schema.mutations.main import CoreMutations from core.schema.mutations.main import CoreMutations
from objectives.mutations import ObjectiveMutations from objectives.mutations import ObjectiveMutations
from objectives.schema import ObjectivesQuery from objectives.schema import ObjectivesQuery
from portfolio.mutations import PortfolioMutations
from portfolio.schema import PortfolioQuery
from rooms.mutations import RoomMutations from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery from rooms.schema import RoomsQuery
from users.schema import UsersQuery from users.schema import UsersQuery
@ -20,15 +22,16 @@ from users.mutations import ProfileMutations
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery, class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
BasicKnowledgeQuery, graphene.ObjectType): BasicKnowledgeQuery, PortfolioQuery, graphene.ObjectType):
node = relay.Node.Field() node = relay.Node.Field()
if settings.DEBUG: if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug') debug = graphene.Field(DjangoDebug, name='__debug')
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
ProfileMutations, graphene.ObjectType): ProfileMutations, graphene.ObjectType):
if settings.DEBUG: if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug') debug = graphene.Field(DjangoDebug, name='__debug')

23
server/api/test_utils.py Normal file
View File

@ -0,0 +1,23 @@
from django.test import RequestFactory, TestCase
from graphene.test import Client
from api.schema import schema
from users.models import User
from users.services import create_users
def create_client(user):
request = RequestFactory().get('/')
request.user = user
return Client(schema=schema, context_value=request)
class DefaultUserTestCase(TestCase):
def setUp(self):
create_users()
self.teacher = User.objects.get(username='teacher')
self.teacher2 = User.objects.get(username='teacher2')
self.student1 = User.objects.get(username='student1')
self.student2 = User.objects.get(username='student2')
self.student_second_class = User.objects.get(username='student_second_class')

View File

@ -1,6 +1,7 @@
import io import io
import os import os
from django.apps import apps
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay.node.node import from_global_id from graphql_relay.node.node import from_global_id
@ -53,3 +54,14 @@ def get_graphql_mutation(filename):
mutation = f.read() mutation = f.read()
return mutation return mutation
def get_by_id_or_slug(model, **kwargs):
slug = kwargs.get('slug')
id = kwargs.get('id')
if id is not None:
return get_object(model, id)
if slug is not None:
return model.objects.get(slug=slug)
return None

View File

@ -1,25 +1,13 @@
from django.test import RequestFactory, TestCase
from graphene.test import Client
from graphql_relay import to_global_id from graphql_relay import to_global_id
from api.utils import get_graphql_mutation from api.test_utils import create_client, DefaultUserTestCase
from assignments.models import Assignment, StudentSubmission from assignments.models import Assignment, StudentSubmission
from books.factories import ModuleFactory
from ..factories import AssignmentFactory from ..factories import AssignmentFactory
from users.models import User
from users.services import create_users
from api.schema import schema
class AssignmentPermissionsTestCase(TestCase): class AssignmentPermissionsTestCase(DefaultUserTestCase):
def setUp(self): def setUp(self):
create_users() super(AssignmentPermissionsTestCase, self).setUp()
self.teacher = User.objects.get(username='teacher')
self.teacher2 = User.objects.get(username='teacher2')
self.student1 = User.objects.get(username='student1')
self.student2 = User.objects.get(username='student2')
self.student_second_class = User.objects.get(username='student_second_class')
self.assignment = AssignmentFactory( self.assignment = AssignmentFactory(
owner=self.teacher owner=self.teacher
) )
@ -27,10 +15,6 @@ class AssignmentPermissionsTestCase(TestCase):
self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk) self.assignment_id = to_global_id('AssignmentNode', self.assignment.pk)
self.module_id = to_global_id('ModuleNode', self.assignment.module.pk) self.module_id = to_global_id('ModuleNode', self.assignment.module.pk)
def _create_client(self, user):
request = RequestFactory().get('/')
request.user = user
return Client(schema=schema, context_value=request)
def _submit_submission(self, user=None): def _submit_submission(self, user=None):
mutation = ''' mutation = '''
@ -53,9 +37,9 @@ class AssignmentPermissionsTestCase(TestCase):
''' '''
if user is None: if user is None:
client = self._create_client(self.student1) client = create_client(self.student1)
else: else:
client = self._create_client(user) client = create_client(user)
return client.execute(mutation, variables={ return client.execute(mutation, variables={
'input': { 'input': {
@ -82,7 +66,7 @@ class AssignmentPermissionsTestCase(TestCase):
self.assertEqual(StudentSubmission.objects.count(), 1) self.assertEqual(StudentSubmission.objects.count(), 1)
def _test_visibility(self, user, count): def _test_visibility(self, user, count):
client = self._create_client(user) client = create_client(user)
query = ''' query = '''
query AssignmentWithSubmissions($id: ID!) { query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) { assignment(id: $id) {

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2019-03-11 15:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0008_auto_20190301_0954'),
]
operations = [
migrations.AddField(
model_name='topic',
name='vimeo_id',
field=models.CharField(default=None, max_length=200, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2019-03-12 13:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0009_topic_vimeo_id'),
]
operations = [
migrations.AlterField(
model_name='topic',
name='vimeo_id',
field=models.CharField(blank=True, default=None, max_length=200, null=True),
),
]

View File

@ -18,11 +18,13 @@ class Topic(StrictHierarchyPage):
order = models.PositiveIntegerField(null=False, blank=False, help_text='Order of the topic') order = models.PositiveIntegerField(null=False, blank=False, help_text='Order of the topic')
teaser = models.TextField() teaser = models.TextField()
description = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES) description = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
vimeo_id = models.CharField(max_length=200, blank=True, null=True, default=None)
content_panels = [ content_panels = [
FieldPanel('title', classname="full title"), FieldPanel('title', classname="full title"),
FieldPanel('order'), FieldPanel('order'),
FieldPanel('teaser'), FieldPanel('teaser'),
FieldPanel('vimeo_id'),
FieldPanel('description'), FieldPanel('description'),
] ]

View File

@ -118,7 +118,7 @@ class TopicNode(DjangoObjectType):
class Meta: class Meta:
model = Topic model = Topic
only_fields = [ only_fields = [
'slug', 'title', 'meta_title', 'teaser', 'description', 'slug', 'title', 'meta_title', 'teaser', 'description', 'vimeo_id'
] ]
filter_fields = { filter_fields = {
'slug': ['exact', 'icontains', 'in'], 'slug': ['exact', 'icontains', 'in'],

View File

@ -51,6 +51,7 @@ INSTALLED_APPS = [
'rooms', 'rooms',
'assignments', 'assignments',
'basicknowledge', 'basicknowledge',
'portfolio',
'statistics', 'statistics',
'wagtail.contrib.forms', 'wagtail.contrib.forms',
@ -347,4 +348,8 @@ WAGTAIL_SITE_NAME = 'skillbox'
GRAPHQL_QUERIES_DIR = os.path.join(BASE_DIR, '..', 'client', 'src', 'graphql', 'gql') GRAPHQL_QUERIES_DIR = os.path.join(BASE_DIR, '..', 'client', 'src', 'graphql', 'gql')
GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, 'mutations') GRAPHQL_MUTATIONS_DIR = os.path.join(GRAPHQL_QUERIES_DIR, 'mutations')
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'sendgrid_backend.SendgridBackend'
SENDGRID_API_KEY = os.environ.get("SENDGRID_API_KEY")
SENDGRID_SANDBOX_MODE_IN_DEBUG = False
DEFAULT_FROM_EMAIL='noreply@myskillbox.ch'

View File

@ -10,3 +10,7 @@ class DisableMigrations(object):
MIGRATION_MODULES = DisableMigrations() MIGRATION_MODULES = DisableMigrations()
# Email Settings
SENDGRID_API_KEY = ""
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -7,7 +7,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>{% block title %}skillBox{% endblock %}</title> <title>{% block title %}skillbox{% endblock %}</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content="skillBox"> <meta name="author" content="skillBox">

View File

@ -3,7 +3,7 @@
{% block body %} {% block body %}
<h1 class="logo">skillbox</h1> <h1 class="logo">myskillbox</h1>
{% if next %} {% if next %}
<div class="h4">{% trans 'Melde dich jetzt an.' %}</div> <div class="h4">{% trans 'Melde dich jetzt an.' %}</div>

View File

@ -7,6 +7,6 @@
{% endblock %} {% endblock %}
{% trans "Dein Benutzername lautet:" %} {{ user.get_username }} {% trans "Dein Benutzername lautet:" %} {{ user.get_username }}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% trans "Dein Skillbox Team" %}
{% endautoescape %} {% endautoescape %}

View File

@ -3,6 +3,7 @@ from django.conf.urls import url, include
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import re_path from django.urls import re_path
from django.views.generic import RedirectView
from wagtail.admin import urls as wagtailadmin_urls from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls from wagtail.core import urls as wagtail_urls
@ -18,7 +19,10 @@ urlpatterns = [
url(r'^cms/', include(wagtailadmin_urls)), url(r'^cms/', include(wagtailadmin_urls)),
# graphql backend # graphql backend
url(r'^api/', include('api.urls', namespace="api")) url(r'^api/', include('api.urls', namespace="api")),
#favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon@2x.png', permanent=True))
] ]
if settings.DEBUG and not settings.USE_AWS: if settings.DEBUG and not settings.USE_AWS:

View File

@ -1,6 +1,7 @@
import graphene import graphene
from graphene import relay, InputObjectType from graphene import relay, InputObjectType
from graphql_relay import from_global_id from graphql_relay import from_global_id
from rest_framework.exceptions import PermissionDenied
from api.utils import get_object from api.utils import get_object
from books.models import Module from books.models import Module
@ -67,13 +68,18 @@ class AddObjectiveGroup(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
owner = info.context.user
if not owner.has_perm('users.can_manage_school_class_content'):
raise PermissionDenied('Missing permissions')
objective_group_data = kwargs.get('objective_group') objective_group_data = kwargs.get('objective_group')
title = objective_group_data.get('title') title = objective_group_data.get('title')
if title != 'society': if title != 'society':
title = 'language_communication' title = 'language_communication'
module_id = objective_group_data.get('module') module_id = objective_group_data.get('module')
module = get_object(Module, module_id) module = get_object(Module, module_id)
owner = info.context.user
new_objective_group = ObjectiveGroup.objects.create(title=title, module=module, owner=owner) new_objective_group = ObjectiveGroup.objects.create(title=title, module=module, owner=owner)
objectives = objective_group_data.get('objectives') objectives = objective_group_data.get('objectives')
for objective in objectives: for objective in objectives:
@ -89,9 +95,15 @@ class UpdateObjectiveGroup(relay.ClientIDMutation):
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
if not user.has_perm('users.can_manage_school_class_content'):
raise PermissionDenied('Missing permissions')
objective_group_data = kwargs.get('objective_group') objective_group_data = kwargs.get('objective_group')
id = objective_group_data.get('id') id = objective_group_data.get('id')
objective_group = get_object(ObjectiveGroup, id) objective_group = get_object(ObjectiveGroup, id)
objectives = objective_group_data.get('objectives') objectives = objective_group_data.get('objectives')
existing_objective_ids = list(objective_group.objectives.values_list('id', flat=True)) existing_objective_ids = list(objective_group.objectives.values_list('id', flat=True))
for objective in objectives: for objective in objectives:

View File

@ -26,7 +26,6 @@ class ObjectiveGroupNode(DjangoObjectType):
return self.owner is not None and self.owner.pk == info.context.user.pk return self.owner is not None and self.owner.pk == info.context.user.pk
class ObjectiveNode(DjangoObjectType): class ObjectiveNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()

View File

@ -1 +0,0 @@
# Create your tests here.

View File

View File

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