From 6dc695d22a42c37bcebf8664e28f7a4156ec2bce Mon Sep 17 00:00:00 2001
From: Patrizio Bekerle <patrizio.bekerle@tugraz.at>
Date: Wed, 17 Jul 2019 13:58:23 +0200
Subject: [PATCH] Initial person select web component commit

---
 packages/person-select/.gitignore             |   3 +
 packages/person-select/.gitmodules            |   6 +
 packages/person-select/README.md              |  27 ++++
 packages/person-select/favicon.ico            | Bin 0 -> 2550 bytes
 packages/person-select/i18n.js                |  29 +++++
 packages/person-select/i18n/de/select2.js     |  46 +++++++
 .../person-select/i18n/de/translation.json    |   5 +
 packages/person-select/i18n/en/select2.js     |  50 ++++++++
 .../person-select/i18n/en/translation.json    |   5 +
 .../person-select/i18next-scanner.config.js   |  12 ++
 packages/person-select/index.html             |  13 ++
 packages/person-select/index.js               |   3 +
 packages/person-select/npm-debug.log          |  45 +++++++
 packages/person-select/package.json           |  39 ++++++
 packages/person-select/person-select-demo.js  |  53 ++++++++
 packages/person-select/person-select.js       | 119 ++++++++++++++++++
 packages/person-select/rollup.config.js       |  47 +++++++
 packages/person-select/utils.js               |  42 +++++++
 packages/person-select/vars.js                |  32 +++++
 packages/person-select/vendor/auth            |   1 +
 packages/person-select/vendor/common          |   1 +
 packages/person-select/vpu-lit-element.js     |   8 ++
 22 files changed, 586 insertions(+)
 create mode 100644 packages/person-select/.gitignore
 create mode 100644 packages/person-select/.gitmodules
 create mode 100644 packages/person-select/README.md
 create mode 100644 packages/person-select/favicon.ico
 create mode 100644 packages/person-select/i18n.js
 create mode 100644 packages/person-select/i18n/de/select2.js
 create mode 100644 packages/person-select/i18n/de/translation.json
 create mode 100644 packages/person-select/i18n/en/select2.js
 create mode 100644 packages/person-select/i18n/en/translation.json
 create mode 100644 packages/person-select/i18next-scanner.config.js
 create mode 100644 packages/person-select/index.html
 create mode 100644 packages/person-select/index.js
 create mode 100644 packages/person-select/npm-debug.log
 create mode 100644 packages/person-select/package.json
 create mode 100644 packages/person-select/person-select-demo.js
 create mode 100644 packages/person-select/person-select.js
 create mode 100644 packages/person-select/rollup.config.js
 create mode 100644 packages/person-select/utils.js
 create mode 100644 packages/person-select/vars.js
 create mode 160000 packages/person-select/vendor/auth
 create mode 160000 packages/person-select/vendor/common
 create mode 100644 packages/person-select/vpu-lit-element.js

diff --git a/packages/person-select/.gitignore b/packages/person-select/.gitignore
new file mode 100644
index 00000000..28e5452d
--- /dev/null
+++ b/packages/person-select/.gitignore
@@ -0,0 +1,3 @@
+dist
+node_modules
+.idea
\ No newline at end of file
diff --git a/packages/person-select/.gitmodules b/packages/person-select/.gitmodules
new file mode 100644
index 00000000..f997e57d
--- /dev/null
+++ b/packages/person-select/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "vendor/auth"]
+	path = vendor/auth
+	url = git@gitlab.tugraz.at:VPU/WebComponents/Auth.git
+[submodule "vendor/common"]
+	path = vendor/common
+	url = git@gitlab.tugraz.at:VPU/WebComponents/Common.git
diff --git a/packages/person-select/README.md b/packages/person-select/README.md
new file mode 100644
index 00000000..0cca240d
--- /dev/null
+++ b/packages/person-select/README.md
@@ -0,0 +1,27 @@
+## VPU Person Select Web Component
+
+[GitLab Repository](https://gitlab.tugraz.at/VPU/WebComponents/PersonSelect)
+
+## Local development
+
+```bash
+# get the source
+git clone git@gitlab.tugraz.at:VPU/WebComponents/PersonSelect.git
+cd PersonSelect
+git submodule update --init
+
+# we are creating the symbolic links to our git sub-modules
+# (there was no proper script to do this automatically before a "node install"
+npm run setup
+
+# install dependencies
+npm install
+
+# constantly builds dist/bundle.js 
+npm run watch-local
+
+# run local webserver
+cd dist; php -S localhost:8002
+```
+
+Jump to <http://localhost:8002> and you should get a Single Sign On login page.
diff --git a/packages/person-select/favicon.ico b/packages/person-select/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..f6cf22d1afc22f071d2b64c7779bc5214c3adae5
GIT binary patch
literal 2550
zcmZQzU}Ruo5D);-91Iz(3=C=v3{buTLk0^2Lmw*xg9b>9fq_AR0iuq9fq}t+5kx{U
z68r!E{|pQa{~0DTTx0nE<_yEJYwsDZJ%7$HVdYu|hX2M43=C-u3=A_F7#PklFo3wm
z#taO`X$%a;GZ`3+L1O<QO45uO7}C-h7}91kFr=MfU`YE9(KgeVfnjDE1H;Uj3=A{R
zFfh#g4>pS7j4=blnKTB5Gcy?&&YWRjIP;%@;eQ$f!~dBK4FAtC7=ySB#tg;`X$-~;
zGZ~B-&M+7={0BS9*w~oC*f@>B*mx#`vGExOW8?o2C#4xP7^kH%7^lr-FitzeV4U_J
z;>?-G48}9l7>sAmWH6q2hQWB|e~8o17&923Nn<cRGn2vi%ozsbGyfTk{~I$H|4(Bu
z{y&q!80?P!;1B@0B8>qAXELNQoMA{~_|E|LvavBknsFLKn(<7AG~+W2X~zE{!H{Om
zkd~Ilkd`)+Aua6;Bv@t|Go;N-V@R7hlOb&;NbEl(sLmKOq@9_`kap$_L)w}D;Gj$U
zZ_JSPe<nlP|1%6{K>lVp0|`RonG6tk1{^ChjX|N4#xT<u#QzTo>NI19nZ{`hGt*`=
z%uGAOFw^)y!_1k+3^Qk@G0dDflVRpekXipB(E+wQjbY}QnG7?}oMD)G=07-!X8t#3
zn0Y3RVdnpt3^V_qVVL><KPUx2V*(TcGZ{cG14Zu{V`GLh#%T;^jAt^OF+Rg^#`r%Z
zYC-NuOJg{bHk08D$Q^0_A<;e4nBmOKG=?)XXEL0bd4}Q4%>R%iaK@P7%$YQXGiPQp
zoH=ub;mn!;;AC+I<d*+w3}^n&WH<u~53pN6sR0xVAUi=x)tKSGaT>#a<CzTqjn6Rr
zH~tSvPHDyrXVTIb{-@1k_@8!$;Y`|pND`ZA%<z9^8pHpYGa3HRJj3vR=6_JKWB7l@
znBo7KG=~3YW-|OgbB5vnng0y`{~I%$`Jcw{|Nl&e|NqZ0q@{u4D2*ZQKgbS{*&sD(
z42ld(8RFyP8OqDc8QR*~7^Y2|#&G)dX@*UkHZfekex2d&-Mb8rA3tVz_39PF$B!Qw
ze*E~s@ZtA=h9wIYFf=qYfb$^(I6MA_P@s4R<%9qK8RlI}XAod61>+Q628P?~wlQq0
zO=L(CPiHu~?l8mTy^|Ss)%G&jvhy<B<Xg=UC0oa^rAUM!%<3M)xjF9`YL9+lc&VSx
za9-d%gYn~S45u}ZKnnwkC?+N*CS(Ip`5+z`Gk{12FbQHYfmskfm<bUCGr<Iu4>cLY
zgEGMsIv*@Z1_3JiknzL+3=AOLpvb@g!Yzyp3?TfUpMe34!EwRB0Mmn7&Vou?1_lQf
zG8{6Zw7^0SMw9A!m_8UytbUk!d^E@`ba{{%2&0Q*<6~0?5(8m$_1MJF)qwaQbs!94
zBV%l0$nr2YNF5;zQwyUBsYjQG>4VYe@*|rM3l$g*Dq}zxRK|cXsEh$&a2W$CYtYIW
E03q~Dvj6}9

literal 0
HcmV?d00001

diff --git a/packages/person-select/i18n.js b/packages/person-select/i18n.js
new file mode 100644
index 00000000..a2380632
--- /dev/null
+++ b/packages/person-select/i18n.js
@@ -0,0 +1,29 @@
+import i18next from 'i18next';
+
+import de from './i18n/de/translation.json';
+import en from './i18n/en/translation.json';
+
+const i18n = i18next.createInstance();
+
+i18n.init({
+    lng: 'de',
+    fallbackLng: ['de'],
+    debug: false,
+    initImmediate: false, // Don't init async
+    resources: {
+        en: {translation: en},
+        de: {translation: de}
+    },
+});
+
+console.assert(i18n.isInitialized);
+
+function dateTimeFormat(date, options) {
+    return new Intl.DateTimeFormat(i18n.languages, options).format(date);
+}
+
+function numberFormat(number, options) {
+    return new Intl.NumberFormat(i18n.languages, options).format(number);
+}
+
+export {i18n, dateTimeFormat, numberFormat};
diff --git a/packages/person-select/i18n/de/select2.js b/packages/person-select/i18n/de/select2.js
new file mode 100644
index 00000000..b4711db2
--- /dev/null
+++ b/packages/person-select/i18n/de/select2.js
@@ -0,0 +1,46 @@
+/**
+ * Content from https://github.com/select2/select2/blob/master/src/js/select2/i18n/de.js
+ */
+module.exports = function () {
+    // German
+    return {
+        errorLoading: function () {
+            return 'Die Ergebnisse konnten nicht geladen werden.';
+        },
+        inputTooLong: function (args) {
+            var overChars = args.input.length - args.maximum;
+
+            return 'Bitte ' + overChars + ' Zeichen weniger eingeben';
+        },
+        inputTooShort: function (args) {
+            var remainingChars = args.minimum - args.input.length;
+
+            return 'Bitte ' + remainingChars + ' Zeichen mehr eingeben';
+        },
+        loadingMore: function () {
+            return 'Lade mehr Ergebnisse…';
+        },
+        maximumSelected: function (args) {
+            var message = 'Sie können nur ' + args.maximum + ' Eintr';
+
+            if (args.maximum === 1) {
+                message += 'ag';
+            } else {
+                message += 'äge';
+            }
+
+            message += ' auswählen';
+
+            return message;
+        },
+        noResults: function () {
+            return 'Keine Übereinstimmungen gefunden';
+        },
+        searching: function () {
+            return 'Suche…';
+        },
+        removeAllItems: function () {
+            return 'Entferne alle Gegenstände';
+        }
+    };
+};
\ No newline at end of file
diff --git a/packages/person-select/i18n/de/translation.json b/packages/person-select/i18n/de/translation.json
new file mode 100644
index 00000000..85eb19d2
--- /dev/null
+++ b/packages/person-select/i18n/de/translation.json
@@ -0,0 +1,5 @@
+{
+  "person-select": {
+    "placeholder": "Bitte wählen Sie eine Person aus"
+  }
+}
diff --git a/packages/person-select/i18n/en/select2.js b/packages/person-select/i18n/en/select2.js
new file mode 100644
index 00000000..f644ab73
--- /dev/null
+++ b/packages/person-select/i18n/en/select2.js
@@ -0,0 +1,50 @@
+/**
+ * Content from https://github.com/select2/select2/blob/master/src/js/select2/i18n/en.js
+ */
+module.exports = function () {
+    // English
+    return {
+        errorLoading: function () {
+            return 'The results could not be loaded.';
+        },
+        inputTooLong: function (args) {
+            var overChars = args.input.length - args.maximum;
+
+            var message = 'Please delete ' + overChars + ' character';
+
+            if (overChars != 1) {
+                message += 's';
+            }
+
+            return message;
+        },
+        inputTooShort: function (args) {
+            var remainingChars = args.minimum - args.input.length;
+
+            var message = 'Please enter ' + remainingChars + ' or more characters';
+
+            return message;
+        },
+        loadingMore: function () {
+            return 'Loading more results…';
+        },
+        maximumSelected: function (args) {
+            var message = 'You can only select ' + args.maximum + ' item';
+
+            if (args.maximum != 1) {
+                message += 's';
+            }
+
+            return message;
+        },
+        noResults: function () {
+            return 'No results found';
+        },
+        searching: function () {
+            return 'Searching…';
+        },
+        removeAllItems: function () {
+            return 'Remove all items';
+        }
+    };
+};
\ No newline at end of file
diff --git a/packages/person-select/i18n/en/translation.json b/packages/person-select/i18n/en/translation.json
new file mode 100644
index 00000000..a9e21164
--- /dev/null
+++ b/packages/person-select/i18n/en/translation.json
@@ -0,0 +1,5 @@
+{
+  "person-select": {
+    "placeholder": "Please select a person"
+  }
+}
diff --git a/packages/person-select/i18next-scanner.config.js b/packages/person-select/i18next-scanner.config.js
new file mode 100644
index 00000000..6c112e37
--- /dev/null
+++ b/packages/person-select/i18next-scanner.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+    input: [
+        '*.js',
+    ],
+    output: './',
+    options: {
+        debug: false,
+        removeUnusedKeys: true,
+        sort: true,
+        lngs: ['en','de'],
+    },
+}
diff --git a/packages/person-select/index.html b/packages/person-select/index.html
new file mode 100644
index 00000000..2210c651
--- /dev/null
+++ b/packages/person-select/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <script src="webcomponents-loader.js"></script>
+    <script type="module" id="vpu-person-select-wc-src" src="bundle.js"></script>
+</head>
+
+<body>
+
+<vpu-person-select-demo lang="de"></vpu-person-select-demo>
+</body>
+</html>
diff --git a/packages/person-select/index.js b/packages/person-select/index.js
new file mode 100644
index 00000000..1c6013ae
--- /dev/null
+++ b/packages/person-select/index.js
@@ -0,0 +1,3 @@
+import './node_modules/vpu-auth/vpu-auth';
+import './person-select.js';
+import './person-select-demo.js';
diff --git a/packages/person-select/npm-debug.log b/packages/person-select/npm-debug.log
new file mode 100644
index 00000000..3582bbbf
--- /dev/null
+++ b/packages/person-select/npm-debug.log
@@ -0,0 +1,45 @@
+0 info it worked if it ends with ok
+1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'watch-local' ]
+2 info using npm@3.5.2
+3 info using node@v8.10.0
+4 verbose run-script [ 'prewatch-local', 'watch-local', 'postwatch-local' ]
+5 info lifecycle vpu-person-select-wc@1.0.0~prewatch-local: vpu-person-select-wc@1.0.0
+6 silly lifecycle vpu-person-select-wc@1.0.0~prewatch-local: no script for prewatch-local, continuing
+7 info lifecycle vpu-person-select-wc@1.0.0~watch-local: vpu-person-select-wc@1.0.0
+8 verbose lifecycle vpu-person-select-wc@1.0.0~watch-local: unsafe-perm in lifecycle true
+9 verbose lifecycle vpu-person-select-wc@1.0.0~watch-local: PATH: /usr/share/npm/bin/node-gyp-bin:/home/pbeke/Code/VPU/WebComponents/PersonSelect/node_modules/.bin:/usr/local/sbin:/snap/bin:/snap/bin:/snap/bin:/usr/local/bin:/snap/bin:/snap/bin:/snap/bin:/usr/sbin:/snap/bin:/snap/bin:/snap/bin:/usr/bin:/snap/bin:/snap/bin:/snap/bin:/sbin:/snap/bin:/snap/bin:/snap/bin:/bin:/snap/bin:/snap/bin:/snap/bin:/usr/games:/snap/bin:/snap/bin:/snap/bin:/usr/local/games:/snap/bin:/snap/bin:/snap/bin
+10 verbose lifecycle vpu-person-select-wc@1.0.0~watch-local: CWD: /home/pbeke/Code/VPU/WebComponents/PersonSelect
+11 silly lifecycle vpu-person-select-wc@1.0.0~watch-local: Args: [ '-c', 'rollup -c --watch' ]
+12 silly lifecycle vpu-person-select-wc@1.0.0~watch-local: Returned: code: 1  signal: null
+13 info lifecycle vpu-person-select-wc@1.0.0~watch-local: Failed to exec watch-local script
+14 verbose stack Error: vpu-person-select-wc@1.0.0 watch-local: `rollup -c --watch`
+14 verbose stack Exit status 1
+14 verbose stack     at EventEmitter.<anonymous> (/usr/share/npm/lib/utils/lifecycle.js:232:16)
+14 verbose stack     at emitTwo (events.js:126:13)
+14 verbose stack     at EventEmitter.emit (events.js:214:7)
+14 verbose stack     at ChildProcess.<anonymous> (/usr/share/npm/lib/utils/spawn.js:24:14)
+14 verbose stack     at emitTwo (events.js:126:13)
+14 verbose stack     at ChildProcess.emit (events.js:214:7)
+14 verbose stack     at maybeClose (internal/child_process.js:925:16)
+14 verbose stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:209:5)
+15 verbose pkgid vpu-person-select-wc@1.0.0
+16 verbose cwd /home/pbeke/Code/VPU/WebComponents/PersonSelect
+17 error Linux 4.18.0-25-generic
+18 error argv "/usr/bin/node" "/usr/bin/npm" "run" "watch-local"
+19 error node v8.10.0
+20 error npm  v3.5.2
+21 error code ELIFECYCLE
+22 error vpu-person-select-wc@1.0.0 watch-local: `rollup -c --watch`
+22 error Exit status 1
+23 error Failed at the vpu-person-select-wc@1.0.0 watch-local script 'rollup -c --watch'.
+23 error Make sure you have the latest version of node.js and npm installed.
+23 error If you do, this is most likely a problem with the vpu-person-select-wc package,
+23 error not with npm itself.
+23 error Tell the author that this fails on your system:
+23 error     rollup -c --watch
+23 error You can get information on how to open an issue for this project with:
+23 error     npm bugs vpu-person-select-wc
+23 error Or if that isn't available, you can get their info via:
+23 error     npm owner ls vpu-person-select-wc
+23 error There is likely additional logging output above.
+24 verbose exit [ 1, true ]
diff --git a/packages/person-select/package.json b/packages/person-select/package.json
new file mode 100644
index 00000000..f164a5c6
--- /dev/null
+++ b/packages/person-select/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "vpu-person-select-wc",
+  "version": "1.0.0",
+  "devDependencies": {
+    "node-sass": "^4.12.0",
+    "rollup": "^1.11.3",
+    "rollup-plugin-commonjs": "^9.3.4",
+    "rollup-plugin-copy": "^2.0.1",
+    "rollup-plugin-node-resolve": "^4.2.3",
+    "rollup-plugin-postcss": "^2.0.3",
+    "rollup-plugin-serve": "^1.0.1",
+    "rollup-plugin-terser": "^4.0.4",
+    "rollup-plugin-json": "^4.0.0",
+    "rollup-plugin-replace": "^2.2.0",
+    "i18next-scanner": "^2.10.2"
+  },
+  "dependencies": {
+    "@webcomponents/webcomponentsjs": "^2.2.10",
+    "i18next": "^17.0.3",
+    "jquery": "^3.4.1",
+    "lit-element": "^2.1.0",
+    "select2": "^4.0.7",
+    "vpu-auth-wc": "*",
+    "vpu-common": "*"
+  },
+  "scripts": {
+    "setup": "mkdir -p node_modules && cd node_modules && ln -sfn ../vendor/common vpu-common && ln -sfn ../vendor/auth vpu-auth",
+    "clean": "rm dist/*",
+    "build": "npm run build-local",
+    "build-local": "rollup -c",
+    "build-dev": "rollup -c --environment BUILD:development",
+    "build-prod": "rollup -c --environment BUILD:production",
+    "build-demo": "rollup -c --environment BUILD:demo",
+    "i18next": "i18next-scanner",
+    "watch": "npm run watch-local",
+    "watch-local": "rollup -c --watch",
+    "watch-dev": "rollup -c --watch --environment BUILD:development"
+  }
+}
diff --git a/packages/person-select/person-select-demo.js b/packages/person-select/person-select-demo.js
new file mode 100644
index 00000000..e12c3fa1
--- /dev/null
+++ b/packages/person-select/person-select-demo.js
@@ -0,0 +1,53 @@
+import utils from './utils.js';
+import {i18n} from './i18n.js';
+import {html, LitElement} from 'lit-element';
+
+class PersonSelectDemo extends LitElement {
+    constructor() {
+        super();
+        this.lang = 'de';
+    }
+
+    static get properties() {
+        return {
+            lang: { type: String },
+        };
+    }
+
+    connectedCallback() {
+        super.connectedCallback();
+        i18n.changeLanguage(this.lang);
+
+        this.updateComplete.then(()=>{
+        });
+    }
+
+    render() {
+        return html`
+            <style>
+            </style>
+            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
+
+            <section class="section">
+                <div class="container">
+                    <h1 class="title">Person-Select-Demo</h1>
+                </div>
+                <div class="container">
+                    <vpu-auth lang="${this.lang}" client-id="${utils.setting('keyCloakClientId')}" load-person force-login></vpu-auth>
+                </div>
+                <div class="container">
+                    <form>
+                        <div class="field">
+                            <label class="label">Person</label>
+                            <div class="control">
+                                <vpu-library-person-select lang="${this.lang}"></vpu-library-person-select>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </section>
+        `;
+    }
+}
+
+customElements.define('vpu-person-select-demo', PersonSelectDemo);
diff --git a/packages/person-select/person-select.js b/packages/person-select/person-select.js
new file mode 100644
index 00000000..1f935b81
--- /dev/null
+++ b/packages/person-select/person-select.js
@@ -0,0 +1,119 @@
+import $ from 'jquery';
+import utils from './utils.js';
+import select2 from 'select2';
+import select2LangDe from './i18n/de/select2'
+import select2LangEn from './i18n/en/select2'
+import JSONLD from 'vpu-common/jsonld';
+import {html} from 'lit-element';
+import {i18n, dateTimeFormat, numberFormat} from './i18n.js';
+import VPULitElement from "./vpu-lit-element";
+
+select2(window, $);
+
+class PersonSelect extends VPULitElement {
+
+    constructor() {
+        super();
+        this.lang = 'de';
+    }
+
+    static get properties() {
+        return {
+            lang: { type: String },
+        };
+    }
+
+    connectedCallback() {
+        super.connectedCallback();
+        i18n.changeLanguage(this.lang);
+
+        this.updateComplete.then(()=>{
+            const that = this;
+            const $that = $(this);
+            let lastResult = {};
+
+            JSONLD.initialize(utils.getAPiUrl(), function (jsonld) {
+                // find the correct api url for a person
+                const apiUrl = jsonld.getApiUrlForIdentifier("http://schema.org/Person");
+                // const apiUrl = jsonld.getApiUrlForEntityName("Event");
+
+                // the mapping we need for Select2
+                const localContext = {
+                    "id": "@id",
+                    "text": "http://schema.org/name"
+                };
+
+                that.$('#person-select').select2({
+                    width: '100%',
+                    language: that.lang === "de" ? select2LangDe() : select2LangEn(),
+                    minimumInputLength: 2,
+                    placeholder: i18n.t('person-select.placeholder'),
+                    dropdownParent: that.$('#person-select-dropdown'),
+                    ajax: {
+                        delay: 250,
+                        url: apiUrl,
+                        contentType: "application/ld+json",
+                        beforeSend: function( jqXHR ) {
+                            jqXHR.setRequestHeader('Authorization', 'Bearer ' + window.VPUAuthToken);
+                        },
+                        data: function (params) {
+                            return {
+                                search: params.term,
+                                'library-only': 1
+                            };
+                        },
+                        processResults: function (data) {
+                            console.log(data);
+                            lastResult = data;
+
+                            const results = jsonld.transformMembers(data, localContext);
+
+                            console.log("results");
+                            console.log(results);
+
+                            return {
+                                results: results
+                            };
+                        }
+                    }
+                }).on("select2:select", function(e) {
+                    // set value custom element
+                    const identifier = e.params.data.id;
+                    $that.attr("value", identifier);
+                    $that.val(identifier);
+
+                    const object = utils.findObjectInApiResults(identifier, lastResult);
+                    $that.attr("data-object", JSON.stringify(object));
+
+                    // fire a change event
+                    that.dispatchEvent(new CustomEvent('change', {
+                        detail: {
+                            value: identifier,
+                        },
+                        bubbles: true
+                    }));
+                });
+            });
+        })
+    }
+
+    render() {
+        const select2CSS = utils.getAssetURL('select2/css/select2.min.css');
+
+        return html`
+            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
+            <link rel="stylesheet" href="${select2CSS}">
+            <style>
+                #person-select {
+                    width: 100%;
+                }
+            </style>
+
+            <!-- https://select2.org-->
+            <select id="person-select" name="person" class="select"></select>
+            <div id="person-select-dropdown"></div>
+        `;
+    }
+}
+
+customElements.define('vpu-library-person-select', PersonSelect);
diff --git a/packages/person-select/rollup.config.js b/packages/person-select/rollup.config.js
new file mode 100644
index 00000000..f1d12b5a
--- /dev/null
+++ b/packages/person-select/rollup.config.js
@@ -0,0 +1,47 @@
+import resolve from 'rollup-plugin-node-resolve';
+import commonjs from 'rollup-plugin-commonjs';
+import postcss from 'rollup-plugin-postcss';
+import copy from 'rollup-plugin-copy';
+import {terser} from "rollup-plugin-terser";
+import json from 'rollup-plugin-json';
+import replace from "rollup-plugin-replace";
+
+const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : 'local';
+console.log("build: " + build);
+
+export default {
+    input: 'index.js',
+    output: {
+        file: 'dist/bundle.js',
+        format: 'esm'
+    },
+    plugins: [
+        resolve(),
+        commonjs(),
+        json(),
+        replace({
+            "process.env.BUILD": '"' + build + '"',
+        }),
+        postcss({
+            inject: false,
+            minimize: false,
+            plugins: []
+        }),
+        terser(),
+        copy({
+            targets: [
+                'index.html',
+                'favicon.ico',
+                'node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js',
+                'node_modules/@webcomponents/webcomponentsjs/bundles',
+            ],
+            outputFolder: 'dist'
+        }),
+        copy({
+            targets: [
+                'node_modules/select2/dist/css',
+            ],
+            outputFolder: 'dist/select2'
+        })
+    ]
+};
diff --git a/packages/person-select/utils.js b/packages/person-select/utils.js
new file mode 100644
index 00000000..3a4386e9
--- /dev/null
+++ b/packages/person-select/utils.js
@@ -0,0 +1,42 @@
+const vars = require("./vars");
+
+module.exports = {
+    getAssetURL: (path) => {
+        const elm = document.getElementById('vpu-library-shelving-wc-src');
+        if (!elm)
+            return path;
+        const url = elm.src;
+        // newer browsers only
+        //var url = import.meta.url;
+        return new URL(path, url).href;
+    },
+
+    getAPiUrl: function(path = "", withPrefix = true) {
+        return vars.apiBaseUrl + (withPrefix ? vars.apiUrlPrefix : "") + path;
+    },
+
+    /**
+     * Finds an object in a JSON result by identifier
+     *
+     * @param identifier
+     * @param results
+     * @param identifierAttribute
+     */
+    findObjectInApiResults: (identifier, results, identifierAttribute = "@id") => {
+        const members = results["hydra:member"];
+
+        for (const object of members){
+            if (object[identifierAttribute] === identifier) {
+                return object;
+            }
+        }
+    },
+
+    /**
+     * Reads a setting
+     *
+     * @param key
+     * @returns {*}
+     */
+    setting: (key) => vars[key]
+};
diff --git a/packages/person-select/vars.js b/packages/person-select/vars.js
new file mode 100644
index 00000000..d34d7b63
--- /dev/null
+++ b/packages/person-select/vars.js
@@ -0,0 +1,32 @@
+
+switch(process.env.BUILD) {
+    case "development":
+        module.exports = {
+            apiBaseUrl: 'https://mw-dev.tugraz.at',
+            apiUrlPrefix: '',
+            keyCloakClientId: 'auth-dev-mw-frontend',
+        };
+
+        break;
+    case "production":
+        module.exports = {
+            apiBaseUrl: 'https://mw.tugraz.at',
+            apiUrlPrefix: '',
+            keyCloakClientId: 'auth-prod-mw-frontend',
+        };
+        break;
+    case "demo":
+        module.exports = {
+            apiBaseUrl: 'https://api-demo.tugraz.at',
+            apiUrlPrefix: '',
+            keyCloakClientId: 'auth-dev-mw-frontend',
+        };
+        break;
+    case "local":
+    default:
+        module.exports = {
+            apiBaseUrl: 'http://127.0.0.1:8000',
+            apiUrlPrefix: '',
+            keyCloakClientId: 'auth-dev-mw-frontend-local',
+        };
+}
diff --git a/packages/person-select/vendor/auth b/packages/person-select/vendor/auth
new file mode 160000
index 00000000..0d7dedf0
--- /dev/null
+++ b/packages/person-select/vendor/auth
@@ -0,0 +1 @@
+Subproject commit 0d7dedf0776b0907bc6371398867dcc943bd9729
diff --git a/packages/person-select/vendor/common b/packages/person-select/vendor/common
new file mode 160000
index 00000000..c3547c33
--- /dev/null
+++ b/packages/person-select/vendor/common
@@ -0,0 +1 @@
+Subproject commit c3547c33cd647446e5bc4873ed49bc760801c098
diff --git a/packages/person-select/vpu-lit-element.js b/packages/person-select/vpu-lit-element.js
new file mode 100644
index 00000000..cee389d5
--- /dev/null
+++ b/packages/person-select/vpu-lit-element.js
@@ -0,0 +1,8 @@
+import {LitElement} from "lit-element";
+import $ from "jquery";
+
+export default class VPULitElement extends LitElement {
+    $(selector) {
+        return $(this.shadowRoot === null ? this.querySelector(selector) : this.shadowRoot.querySelector(selector));
+    }
+}
-- 
GitLab