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
.python-version
.coverage

View File

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

259
Pipfile.lock generated
View File

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

View File

@ -36,7 +36,7 @@ aliases:
- echo "This pipeline rules!"
- *setup-tests
- 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"
- python server/manage.py dummy_data
- python server/manage.py runserver &
@ -55,6 +55,7 @@ pipelines:
branches:
master:
- step: *unittest-python
- step: *cypress-test
develop:
- step: *unittest-python
@ -67,4 +68,5 @@ pipelines:
custom:
prod:
- step: *unittest-python
- step: *cypress-test
- step: *deploy-prod

View File

@ -9,7 +9,7 @@ describe('The Login Page', () => {
cy.get('#id_password').type(`${password}{enter}`);
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', () => {
// 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>
<head>
<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>
<link href='https://fonts.googleapis.com/css?family=Material+Icons' rel="stylesheet" type="text/css">

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
<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>
</component>
</template>
@ -47,12 +48,16 @@
@import "@/styles/_mixins.scss";
.add-widget {
display: flex;
display: none;
align-items: center;
justify-content: center;
@include widget-shadow;
cursor: pointer;
@include desktop {
display: flex;
}
&__add {
width: 80px;
fill: $color-grey;

View File

@ -1,12 +1,12 @@
<template>
<div class="room-colors">
<div class="color-chooser">
<div v-for="(color, index) in colors"
:key="index"
class="room-colors__color-wrapper"
class="color-chooser__color-wrapper"
@click="$emit('input', color.name)"
:class="{'room-colors__color-wrapper--selected': selectedColor === color.name}">
<div class="room-colors__color" :class="'room-colors__color--' + color.name">
<tick class="room-colors__selected-icon" v-if="selectedColor === color.name"></tick>
:class="{'color-chooser__color-wrapper--selected': selectedColor === color.name}">
<div class="color-chooser__color" :class="'color-chooser__color--' + color.name">
<tick class="color-chooser__selected-icon" v-if="selectedColor === color.name"></tick>
</div>
</div>
</div>
@ -47,7 +47,7 @@
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.room-colors {
.color-chooser {
display: flex;
&__color-wrapper {
@ -69,7 +69,11 @@
width: 46px;
height: 46px;
border-radius: 23px;
display: grid;
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid
}
justify-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 {
font-family: $sans-serif-font-family;
line-height: 16px;
margin: 0 15px 0 20px;
margin: 0 15px 0 $large-spacing;
background: none;
color: inherit;
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;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
border: 1px solid $color-lightgrey;
display: grid;
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr 65px;
grid-template-areas: "header" "body" "footer";
-ms-grid-rows: auto 1fr 65px;
position: relative;
&--hide-header {
@ -81,6 +85,7 @@
width: 95vw;
height: auto;
grid-template-rows: 1fr;
-ms-grid-rows: 1fr;
grid-template-areas: "body";
overflow: hidden;
}
@ -107,7 +112,11 @@
}
&__backdrop {
display: grid;
display: flex;
justify-content: center;
@supports (display: grid) {
display: grid;
}
position: fixed;
top: 0;
left: 0;
@ -119,12 +128,14 @@
&__header {
grid-area: header;
-ms-grid-row: 1;
padding: 10px $modal-lateral-padding;
border-bottom: 1px solid $color-lightgrey;
}
&__body {
grid-area: body;
-ms-grid-row: 2;
padding: 10px $modal-lateral-padding;
overflow: auto;
box-sizing: border-box;
@ -150,6 +161,7 @@
&__footer {
grid-area: footer;
-ms-grid-row: 3;
border-top: 1px solid $color-lightgrey;
padding: 16px $modal-lateral-padding;
}

View File

@ -4,8 +4,8 @@
<a :href="url" target="_blank" class="teaser__title">{{title}}</a>
</h4>
<a :href="url" target="_blank" class="teaser__date">
{{date}}
</a>
{{date}}
</a>
</div>
</template>
@ -18,10 +18,21 @@
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
.news-teaser {
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;
flex-direction: column;
}

View File

@ -1,5 +1,5 @@
<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"
:class="{'top-navigation__link--active': isActive('book')}"
class="top-navigation__link">Inhalte
@ -13,6 +13,12 @@
<script>
export default {
props: {
mobile: {
default: false
}
},
methods: {
isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1;
@ -22,4 +28,39 @@
</script>
<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>

View File

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

View File

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

View File

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

View File

@ -66,8 +66,12 @@
@import "@/styles/_functions.scss";
.content-block-element-chooser-widget {
display: grid;
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
grid-template-columns: repeat(6, 1fr);
-ms-grid-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-column-gap: 0px;
font-family: $sans-serif-font-family;
text-align: center;
@ -78,6 +82,31 @@
position: relative;
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 {
content: "";
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>
<info-icon class="text-form-with-help-text__icon"></info-icon>
</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>
</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;
}
flex-direction: column;
padding: 0 15px;
padding: $large-spacing 15px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;

View File

@ -1,6 +1,6 @@
<template>
<div>
<nav class="module-navigation">
<nav v-if="false" class="module-navigation">
<div class="module-navigation__module-content">
<router-link
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>
<add-widget route="/new-project"></add-widget>
<add-widget route="/new-project" data-cy="add-project-button"></add-widget>
</template>
<script>

View File

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

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>
<modal :hide-header="true">
<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 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 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>
</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>
</template>
@ -15,15 +19,65 @@
import Modal from '@/components/Modal';
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 {
components: {
Modal,
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() {
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">
<h3 class="project-entry__heading">Tätigkeit</h3>
<p class="project-entry__paragraph">
Ich führe das Interview mit meiner Kollegin durch.
{{activity}}
</p>
<h3 class="project-entry__heading">Reflexion</h3>
<p class="project-entry__paragraph">
Da ich geeignete Fragen hatte, konnte meine Kollegin umfangreiche Antworten zum Thema geben. Die Eingangshalle
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.
{{reflection}}
</p>
<h3 class="project-entry__heading">
Nächste Schritte
</h3>
<p class="project-entry__paragraph">
Interview im Raum Mein Lehrbetrieb ablegen.
{{nextSteps}}
</p>
<div class="project-entry__date">
21. Juni 2018
{{created | date }}
</div>
</div>
</template>
<script>
export default {
props: ['activity', 'reflection', 'nextSteps', 'created']
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.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>
<div class="project-widget" :class="widgetClass">
<router-link :to="{name: 'project', params: {slug: slug}}" tag="div" class="project-widget__content">
<h3 class="project-widget__title">{{title}}</h3>
<entry-count-widget entry-count="4"></entry-count-widget>
<owner-widget name="Hans Muster"></owner-widget>
<h3 class="project-widget__title">{{title}}</h3>
<entry-count-widget :entry-count="entriesCount"></entry-count-widget>
<owner-widget :name="owner"></owner-widget>
</router-link>
<widget-footer
entity="Eintrag"
></widget-footer>
<widget-footer v-if="isOwner" class="project-widget__footer">
<template slot-scope="scope">
<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>
</template>
@ -19,7 +23,7 @@
import WidgetFooter from '@/components/WidgetFooter';
export default {
props: ['title', 'appearance', 'slug'],
props: ['title', 'appearance', 'slug', 'id', 'final', 'student', 'entriesCount', 'userId'],
components: {
WidgetFooter,
@ -28,8 +32,27 @@
},
computed: {
widgetClass() {
widgetClass () {
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;
display: -ms-grid;
margin-bottom: $large-spacing;
@supports (display: grid) {
display: grid;
margin-bottom: 0;
}
grid-template-rows: 150px 1fr;
-ms-grid-rows: 150px 48px;
-ms-grid-columns: 1fr;
&__content {
padding: 23px;
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 {
@ -62,6 +106,10 @@
font-weight: 600;
}
&__footer {
-ms-grid-row: 2;
}
@include skillbox-colors;
}
</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">
<ellipses class="room-entry__ellipses"></ellipses>
</a>
<widget-popover entity="Eintrag"
@delete="deleteRoomEntry"
@edit="editRoomEntry"
@hide-me="showMenu = false"
<widget-popover @hide-me="showMenu = false"
:id="id"
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>
<router-link :to="{name: 'article', params: { slug: slug }}" tag="div" class="room-entry__router-link">
<div class="room-entry__header" v-if="image">

View File

@ -1,33 +1,31 @@
<template>
<form class="room-form" @submit.prevent="$emit('save', localRoom)">
<div class="room-form__content">
<h1 class="room-form__heading">Neues Board</h1>
<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">
<label class="room-form__property-heading" for="room-description">Beschreibung</label>
<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>
<select
class="skillbox-input room-form__input"
id="room-class"
v-model="localRoom.schoolClass"
>
<option :value="{}" disabled hidden>Klasse wählen</option>
<option
v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
v-bind:value="schoolClass"
>{{schoolClass.name}}
</option>
</select>
<h2 class="room-form__property-heading">Farbe</h2>
<room-colors
:selected-color="localRoom.appearance"
v-on:input="updateColor"
></room-colors>
</div>
<div class="room-form__footer">
<page-form class="room-form" @save="$emit('save', localRoom)" title="Neues Board">
<page-form-input v-model="localRoom.title" label="Titel">
</page-form-input>
<page-form-input v-model="localRoom.description" label="Beschreibung" type="textarea">
</page-form-input>
<label class="room-form__property-heading" for="room-class">Klasse</label>
<select
class="skillbox-input room-form__input"
id="room-class"
v-model="localRoom.schoolClass"
>
<option :value="{}" disabled hidden>Klasse wählen</option>
<option
v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
v-bind:value="schoolClass"
>{{schoolClass.name}}
</option>
</select>
<h2 class="room-form__property-heading">Farbe</h2>
<color-chooser
:selected-color="localRoom.appearance"
v-on:input="updateColor"
></color-chooser>
<template slot="footer">
<button
type="submit"
class="button button--primary room-form__save-button"
@ -36,12 +34,14 @@
>Speichern
</button>
<router-link to="/rooms" tag="button" class="button">Abbrechen</router-link>
</div>
</form>
</template>
</page-form>
</template>
<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';
@ -49,7 +49,9 @@
props: ['room'],
components: {
RoomColors
ColorChooser,
PageForm,
PageFormInput
},
data() {
@ -93,37 +95,11 @@
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
.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 {
display: block;
font-size: toRem(21px);
font-weight: 800;
margin-bottom: 24px;
font-family: $sans-serif-font-family;
@include page-form-input-heading;
}
&__input,

View File

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

View File

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

View File

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

View File

@ -1,67 +1,38 @@
<template>
<div class="container skillbox" :class="specialContainerClass">
<header class="header skillbox__header">
<top-navigation></top-navigation>
<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>
<header-bar class="header skillbox__header">
</header-bar>
<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>
</div>
</template>
<script>
import TopNavigation from '@/components/TopNavigation.vue';
import BookNavigation from '@/components/book-navigation/BookNavigation';
import UserWidget from '@/components/UserWidget.vue';
import FilterBar from '@/components/FilterBar.vue';
import LogoutWidget from '@/components/LogoutWidget.vue';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import FilterBar from '@/components/FilterBar';
import HeaderBar from '@/components/HeaderBar';
import MobileHeader from '@/components/MobileHeader';
export default {
components: {
TopNavigation,
UserWidget,
FilterBar,
LogoutWidget,
BookNavigation
HeaderBar,
MobileHeader
},
computed: {
showFilter() {
return this.$route.meta.filter;
},
showSubnavigation() {
return this.$route.meta.subnavigation;
},
specialContainerClass() {
let cls = this.$store.state.specialContainerClass;
return [cls ? `skillbox--${cls}` : '', {'skillbox--show-filter': this.showFilter}]
},
isProfile() {
return this.$route.meta.isProfile;
},
},
data() {
return {
me: {}
}
},
apollo: {
me: {
query: ME_QUERY,
},
},
}
}
</script>
@ -72,7 +43,9 @@
.skillbox {
margin: 0 auto;
width: 100%;
display: grid;
@supports (display: grid) {
display: grid;
}
grid-template-rows: auto 1fr;
min-height: 100vh;
grid-auto-rows: 1fr;
@ -82,81 +55,41 @@
&--show-filter {
grid-template-rows: auto auto 1fr;
-ms-grid-rows: 50px 50px 30px 1fr; // 1 extra row for gap
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+
*/
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;
@include skillbox-colors;
&__header {
grid-area: h;
display: -ms-grid;
@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;
}
-ms-grid-row: 1;
}
&__header-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;
&__content {
-ms-grid-row: 3;
-ms-grid-column: 1;
}
&__footer {
@ -168,12 +101,12 @@
* For IE10+
*/
& > :nth-child(2) {
-ms-grid-row: 3;
}
& > :nth-child(3) {
-ms-grid-row: 4;
-ms-grid-column: 1;
}
}
.user-header {
display: flex;
}
</style>

View File

@ -13,14 +13,19 @@
.layout {
&--simple {
display: -ms-grid;
display: grid;
@supports (display: grid) {
display: grid;
}
padding: 20px;
width: 100%;
@include desktop {
grid-template-columns: 1fr 640px 1fr;
-ms-grid-columns: 1fr 640px 1fr;
& > :nth-child(2) {
grid-column: 2;
-ms-grid-column: 2;
}
}
@ -31,8 +36,12 @@
justify-self: end;
cursor: pointer;
display:flex;
justify-content:end;
@include desktop {
grid-column: 3;
-ms-grid-column: 3;
}
&__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 { required, min } from 'vee-validate/dist/rules.esm.js';
import veeDe from 'vee-validate/dist/locale/de';
import {dateFilter} from './filters/date-filter'
Vue.config.productionTip = false;
@ -105,6 +106,8 @@ Vue.use(VeeValidate, {
locale: 'de'
});
Vue.filter('date', dateFilter);
/* eslint-disable no-new */
new Vue({
el: '#app',

View File

@ -22,6 +22,8 @@
@import "@/styles/_mixins.scss";
.book {
padding-top: $large-spacing;
&__content {
display: -ms-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>
<script>
// todo: refactor this, we don't need 2 components, remove editRoom or EditRoom component
import EditRoom from '@/components/rooms/EditRoom';
import ROOM_QUERY from '@/graphql/gql/roomQuery.gql';
@ -41,11 +42,3 @@
}
}
</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>
<div class="new-room-page">
<new-room></new-room>
</div>
<room-form
:room="room"
@save="addRoom"
></room-form>
</template>
<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 {
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>
<style scoped lang="scss">
.new-room-page {
display: grid;
justify-items: center;
align-items: center;
}
</style>

View File

@ -1,23 +1,143 @@
<template>
<div class="portfolio-page">
<portfolio></portfolio>
<div class="portfolio__page">
<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>
</template>
<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 {
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>
<style>
.portfolio-page {
display: grid;
align-content: center;
justify-content: center;
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.portfolio {
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>

View File

@ -1,28 +1,20 @@
<template>
<div class="project">
<div class="project__header">
<h1 class="project__title">Quartalsarbeit: Mein Lehrbetrieb</h1>
<h1 class="project__title">{{project.title}}</h1>
<p class="project__description">
Das ist eine Beschreibung und kann maximal 200 Zeichen enthalten. Das ist eine Beschreibung und kann maximal 200
Zeichen enthalten. Das ist eine Beschreibung und kann maximal 200 Zeichen enthalten.
{{project.description}}
</p>
<h2 class="project__objectives-title">Ziele</h2>
<ul class="project__objectives">
<li class="project__objective">Ich kann ein Interview mit geeigneten Fragen vorbereiten.</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>
<li class="project__objective" :key="index" v-for="(objective, index) in objectives">{{objective}}</li>
</ul>
</div>
<div class="project__content">
<add-project-entry class="project__add-entry"></add-project-entry>
<project-entry></project-entry>
<project-entry></project-entry>
<add-project-entry class="project__add-entry" :project="project.id"></add-project-entry>
<project-entry v-bind="entry" v-for="(entry, index) in project.entries" :key="index"></project-entry>
</div>
</div>
</template>
@ -31,13 +23,42 @@
import ProjectEntry from '@/components/portfolio/ProjectEntry';
import AddProjectEntry from '@/components/portfolio/AddProjectEntry';
import PROJECT_QUERY from '@/graphql/gql/projectQuery.gql';
export default {
props: ['slug'],
components: {
AddProjectEntry,
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() {
this.$store.dispatch('setSpecialContainerClass', 'red');
},
@ -53,10 +74,8 @@
@import "@/styles/_functions.scss";
.project {
margin-bottom: -50px;
&__header {
padding: 30px;
padding: $large-spacing;
}
&__add-entry {
@ -88,12 +107,19 @@
&__content {
background-color: $color-grey--lighter;
display: grid;
grid-template-columns: 840px;
display: flex;
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;
justify-content: center;
padding-top: 30px;
padding-bottom: 50px;
padding: $large-spacing $medium-spacing;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="rooms-page">
<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>
</template>
@ -63,7 +63,10 @@
@import "@/styles/_mixins.scss";
.rooms-page {
display: -ms-grid;
display: flex;
flex-wrap: wrap;
align-content: start;
@supports (display: grid) {
display: grid;
}
@ -71,6 +74,7 @@
padding: 50px 15px;
@include desktop {
grid-template-columns: repeat(3, 1fr);
-ms-grid-columns: 1fr 30px 1fr 30px 1fr;
padding: 50px 45px;
}
grid-column-gap: 30px;
@ -81,26 +85,22 @@
justify-self: center;
box-sizing: border-box;
/*
* For IE10+
*/
-ms-grid-columns: 1fr 1fr 1fr;
-ms-grid-rows: 260px;
&__add-room {
visibility: hidden;
/*
* SHAME SHAME SHAME
* 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);
}
@include desktop {
visibility: visible;
}
}
-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>
<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">
<section-block
@ -43,20 +46,22 @@
</div>
</div>
</template>
<script>
import SectionBlock from '@/components/SectionBlock.vue';
import NewsTeaser from '@/components/NewsTeaser.vue';
import HeaderBar from '@/components/HeaderBar';
import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
import {meQuery} from '@/graphql/queries';
import MobileHeader from '@/components/MobileHeader';
export default {
components: {
MobileHeader,
HeaderBar,
SectionBlock,
NewsTeaser,
ContentsIllustration,
@ -97,11 +102,21 @@
@import "@/styles/_mixins.scss";
.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;
min-height: 100vh;
width: 100vw;
box-sizing: border-box;
padding-top: 2*$large-spacing;
&__header {
}
&__title {
color: $color-brand;
@ -132,6 +147,8 @@
padding-right: 120px;
}
display: -ms-grid;
-ms-grid-column-align: center;
-ms-grid-columns: 1fr 1fr 1fr;
margin-bottom: 90px;
@supports (display: grid) {
display: grid;
@ -143,6 +160,24 @@
grid-row-gap: 15px;
grid-column-gap: 50px;
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 {
@ -150,8 +185,39 @@
color: $color-white;
padding-top: $large-spacing;
padding-bottom: $large-spacing;
display: grid;
grid-template-columns: repeat(5, 1fr);
display: flex;
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 {
/*padding-left: 120px;*/
@ -166,29 +232,21 @@
padding-bottom: 24px;
text-align: center;
color: inherit;
margin-bottom: 0;
}
&__more {
color: $color-white;
font-family: $sans-serif-font-family;
border-left: 1px solid $color-lightgrey;
line-height: $default-line-height;
padding-left: $medium-spacing;
font-weight: $font-weight-bold;
}
&__teasers {
@supports (display: grid) {
display: grid;
}
text-align: center;
@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>

View File

@ -1,5 +1,9 @@
<template>
<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="assignment"></assignment-with-submissions>
</div>
@ -32,6 +36,12 @@
},
},
methods: {
back() {
this.$router.go(-1);
}
},
data() {
return {
assignment: {
@ -46,6 +56,11 @@
@import "@/styles/_mixins.scss";
.submissions-page {
display: grid;
grid-row-gap: $large-spacing;
grid-template-rows: auto 1fr;
@include desktop {
width: 800px;
}

View File

@ -4,6 +4,10 @@
<p class="topic__teaser">
{{topic.teaser}}
</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">
<module-teaser v-for="module in modules" :key="module.id" v-bind="module"></module-teaser>
</div>
@ -12,11 +16,13 @@
<script>
import ModuleTeaser from '@/components/modules/ModuleTeaser.vue';
import Play from '@/components/icons/Play';
import TOPIC_QUERY from '@/graphql/gql/topicQuery.gql';
export default {
components: {
ModuleTeaser
ModuleTeaser,
Play
},
apollo: {
@ -39,6 +45,12 @@
}
},
methods: {
openVideo() {
this.$store.dispatch('showFullscreenVideo', this.topic.vimeoId);
}
},
data() {
return {
topic: {
@ -60,6 +72,23 @@
color: $color-darkgrey-1;
width: 90%;
@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 {

View File

@ -10,7 +10,7 @@ import newRoom from '@/pages/newRoom'
import editRoom from '@/pages/editRoom'
import article from '@/pages/article'
import basicknowledge from '@/pages/basicknowledge'
import basicknowledgeOverview from '@/pages/basicknowledge-overview'
import basicknowledgeOverview from '@/pages/basicknowledgeOverview'
import submissions from '@/pages/submissions'
import p404 from '@/pages/p404'
import start from '@/pages/start'
@ -22,6 +22,10 @@ import passwordChange from '@/pages/passwordChange'
import myClasses from '@/pages/myClasses'
import activity from '@/pages/activity'
import Router from 'vue-router'
import editProject from '@/pages/editProject'
import newProject from '@/pages/newProject'
import store from '@/store/index';
const routes = [
{path: '/', component: start, meta: {layout: 'blank'}},
@ -50,11 +54,18 @@ const routes = [
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
{path: '/room/:slug', name: 'room', component: room, props: true},
{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: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
{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',
name: 'book',
@ -83,7 +94,8 @@ const routes = [
];
Vue.use(Router);
export default new Router({
const router = new Router({
routes,
mode: 'history',
scrollBehavior(to, from, savedPosition) {
@ -92,4 +104,8 @@ export default new Router({
}
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: {
specialContainerClass: '',
showModal: '',
showMobileNavigation: false,
contentBlockPosition: {},
scrollPosition: 0,
filterForSchoolClass: '',
@ -23,10 +24,18 @@ export default new Vuex.Store({
infographic: {
id: 0,
type: ''
}
},
vimeoId: null
},
getters: {},
getters: {
showModal: state => {
return state.showModal
},
showMobileNavigation: state => {
return state.showMobileNavigation
}
},
actions: {
setSpecialContainerClass({commit}, payload) {
@ -51,6 +60,7 @@ export default new Vuex.Store({
id: 0,
type: ''
});
commit('setVimeoId', null);
},
resetContentBlockPosition({commit}) {
commit('setContentBlockPosition', {});
@ -102,6 +112,13 @@ export default new Vuex.Store({
commit('setInfographic', payload);
dispatch('showModal', 'fullscreen-infographic');
},
showFullscreenVideo({commit, dispatch}, payload) {
commit('setVimeoId', payload);
dispatch('showModal', 'fullscreen-video');
},
showMobileNavigation({commit}, payload) {
commit('setShowMobileNavigation', payload);
}
},
mutations: {
@ -149,6 +166,12 @@ export default new Vuex.Store({
},
setInfographic(state, payload) {
state.infographic = payload;
},
setVimeoId(state, payload) {
state.vimeoId = payload;
},
setShowMobileNavigation(state, payload) {
state.showMobileNavigation = payload;
}
}
})

View File

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

View File

@ -132,3 +132,9 @@
font-family: $serif-font-family;
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",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": {
"babel-runtime": "^6.26.0",
"core-js": "^2.5.0",
"regenerator-runtime": "^0.10.5"
"babel-runtime": "6.26.0",
"core-js": "2.5.7",
"regenerator-runtime": "0.10.5"
}
},
"babel-runtime": {
@ -19,8 +19,8 @@
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
"core-js": "2.5.7",
"regenerator-runtime": "0.11.1"
},
"dependencies": {
"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_SECRET_ACCESS_KEY=
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 objectives.mutations import ObjectiveMutations
from objectives.schema import ObjectivesQuery
from portfolio.mutations import PortfolioMutations
from portfolio.schema import PortfolioQuery
from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery
from users.schema import UsersQuery
@ -20,15 +22,16 @@ from users.mutations import ProfileMutations
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
BasicKnowledgeQuery, graphene.ObjectType):
BasicKnowledgeQuery, PortfolioQuery, graphene.ObjectType):
node = relay.Node.Field()
if settings.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):
if settings.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 os
from django.apps import apps
from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay.node.node import from_global_id
@ -53,3 +54,14 @@ def get_graphql_mutation(filename):
mutation = f.read()
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 api.utils import get_graphql_mutation
from api.test_utils import create_client, DefaultUserTestCase
from assignments.models import Assignment, StudentSubmission
from books.factories import ModuleFactory
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):
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')
super(AssignmentPermissionsTestCase, self).setUp()
self.assignment = AssignmentFactory(
owner=self.teacher
)
@ -27,10 +15,6 @@ class AssignmentPermissionsTestCase(TestCase):
self.assignment_id = to_global_id('AssignmentNode', self.assignment.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):
mutation = '''
@ -53,9 +37,9 @@ class AssignmentPermissionsTestCase(TestCase):
'''
if user is None:
client = self._create_client(self.student1)
client = create_client(self.student1)
else:
client = self._create_client(user)
client = create_client(user)
return client.execute(mutation, variables={
'input': {
@ -82,7 +66,7 @@ class AssignmentPermissionsTestCase(TestCase):
self.assertEqual(StudentSubmission.objects.count(), 1)
def _test_visibility(self, user, count):
client = self._create_client(user)
client = create_client(user)
query = '''
query AssignmentWithSubmissions($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')
teaser = models.TextField()
description = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
vimeo_id = models.CharField(max_length=200, blank=True, null=True, default=None)
content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('order'),
FieldPanel('teaser'),
FieldPanel('vimeo_id'),
FieldPanel('description'),
]

View File

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

View File

@ -51,6 +51,7 @@ INSTALLED_APPS = [
'rooms',
'assignments',
'basicknowledge',
'portfolio',
'statistics',
'wagtail.contrib.forms',
@ -347,4 +348,8 @@ WAGTAIL_SITE_NAME = 'skillbox'
GRAPHQL_QUERIES_DIR = os.path.join(BASE_DIR, '..', 'client', 'src', 'graphql', 'gql')
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()
# 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 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="author" content="skillBox">

View File

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

View File

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

View File

@ -3,6 +3,7 @@ from django.conf.urls import url, include
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import re_path
from django.views.generic import RedirectView
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
@ -18,7 +19,10 @@ urlpatterns = [
url(r'^cms/', include(wagtailadmin_urls)),
# 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:

View File

@ -1,6 +1,7 @@
import graphene
from graphene import relay, InputObjectType
from graphql_relay import from_global_id
from rest_framework.exceptions import PermissionDenied
from api.utils import get_object
from books.models import Module
@ -67,13 +68,18 @@ class AddObjectiveGroup(relay.ClientIDMutation):
@classmethod
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')
title = objective_group_data.get('title')
if title != 'society':
title = 'language_communication'
module_id = objective_group_data.get('module')
module = get_object(Module, module_id)
owner = info.context.user
new_objective_group = ObjectiveGroup.objects.create(title=title, module=module, owner=owner)
objectives = objective_group_data.get('objectives')
for objective in objectives:
@ -89,9 +95,15 @@ class UpdateObjectiveGroup(relay.ClientIDMutation):
@classmethod
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')
id = objective_group_data.get('id')
objective_group = get_object(ObjectiveGroup, id)
objectives = objective_group_data.get('objectives')
existing_objective_ids = list(objective_group.objectives.values_list('id', flat=True))
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
class ObjectiveNode(DjangoObjectType):
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