Skip to content
Snippets Groups Projects
Select Git revision
  • f452fa8039361590ea31ac1c206d777fdcf147af
  • main default protected
  • studip-rector
  • ci-opt
  • course-members-export-as-word
  • data-vue-app
  • pipeline-improvements
  • webpack-optimizations
  • rector
  • icon-renewal
  • http-client-and-factories
  • jsonapi-atomic-operations
  • vueify-messages
  • tic-2341
  • 135-translatable-study-areas
  • extensible-sorm-action-parameters
  • sorm-configuration-trait
  • jsonapi-mvv-routes
  • docblocks-for-magic-methods
19 results

StudipControllerTest.php

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    AvatarApp.vue 15.09 KiB
    <template>
        <div class="avatar">
            <section class="contentbox">
                <header>
                    <h1>{{ avatarAltText }}</h1>
                </header>
                <section v-if="!editingImage" class="avatar-display">
                    <img class="avatar-original" :src="avatarUrl" :alt="avatarAltText" />
                    <div class="button-wrapper">
                        <button class="button edit" @click="changeImage">
                            <span v-if="isCustomized">{{ $gettext('Ändern') }}</span
                            ><span v-else>{{ $gettext('Bild wählen') }}</span>
                        </button>
                        <button v-if="isCustomized" class="button trash" @click="showRemoveDialog = true">
                            {{ $gettext('Löschen') }}
                        </button>
                    </div>
                </section>
                <section v-show="editingImage" class="avatar-edit">
                    <div class="cropper-container">
                        <img ref="newImage" src="" @zoom="checkImageSize" />
                    </div>
                    <studip-message-box v-if="invalid" type="warning" :hideClose="false">{{
                        $gettext(
                            'Bildauswahl zu klein. Bitte wählen Sie einen größeren Ausschnitt oder ein anderes Bild aus.'
                        )
                    }}</studip-message-box>
                    <div class="cropper-actions-wrapper">
                        <div class="cropper-actions-group">
                            <div class="labeled-range-input">
                                <label for="cropper-rotate" class="sr-only">{{ $gettext('Neigungswähler') }}</label>
                                <input
                                    id="cropper-rotate"
                                    type="range"
                                    min="-45"
                                    max="45"
                                    steps="1"
                                    v-model="cropperRotate"
                                    @input="updateCropperRotate"
                                />
                                <div class="labeled-range-input-labels">
                                    <span>-45°</span>
                                    <span></span>
                                    <span>+45°</span>
                                </div>
                            </div>
                        </div>
                        <div class="cropper-actions-group">
                            <button
                                class="cropper-actions-button"
                                @click="updateCropperBaseRotation(90)"
                                :title="$gettext('nach rechts drehen')"
                            >
                                <StudipIcon shape="rotate-right" :size="24" />
                            </button>
                            <button
                                class="cropper-actions-button"
                                @click="updateCropperBaseRotation(-90)"
                                :title="$gettext('nach links drehen')"
                            >
                                <StudipIcon shape="rotate-left" :size="24" />
                            </button>
                            <button
                                class="cropper-actions-button"
                                @click="zoomCropper(+0.1)"
                                :title="$gettext('vergrößern')"
                            >
                                <StudipIcon shape="zoom-in2" :size="24" />
                            </button>
                            <button
                                class="cropper-actions-button"
                                @click="zoomCropper(-0.1)"
                                :title="$gettext('verkleinern')"
                            >
                                <StudipIcon shape="zoom-out2" :size="24" />
                            </button>
                            <button class="cropper-actions-button" @click="flip()" :title="$gettext('horizontal spiegeln')">
                                <StudipIcon shape="flip" :size="24" />
                            </button>
                            <button class="cropper-actions-button" @click="resetCropper" :title="$gettext('zurücksetzen')">
                                <StudipIcon shape="refresh" :size="24" />
                            </button>
                            <button class="cropper-actions-button" @click="changeImage" :title="$gettext('Bild auswählen')">
                                <StudipIcon shape="upload" :size="24" />
                            </button>
                        </div>
                        <div class="cropper-actions-group">
                            <button class="button accept" @click="storeAvatar" :disabled="invalid">
                                {{ $gettext('Speichern') }}
                            </button>
                            <button class="button cancel" @click="abortEditing">{{ $gettext('Abbrechen') }}</button>
                        </div>
                    </div>
                </section>
            </section>
            <input
                id="avatar-upload"
                ref="uploadFile"
                type="file"
                accept="image/gif,image/png,image/jpeg,image/webp;capture=camera"
                @change="updateUploadImage"
            />
            <studip-dialog
                v-if="showRemoveDialog"
                :title="$gettext('Bild löschen')"
                :question="$gettext('Möchten Sie dieses Bild wirklich löschen')"
                height="180"
                width="360"
                @confirm="removeAvatar"
                @close="showRemoveDialog = false"
            ></studip-dialog>
            <studip-dialog
                v-if="showChangeImageDialog"
                :title="$gettext('Bildquelle auswählen')"
                :closeText="$gettext('Abbrechen')"
                closeClass="cancel"
                height="310"
                @close="showChangeImageDialog = false"
            >
                <template v-slot:dialogContent>
                    <div class="square-button-panel">
                        <studip-square-button
                            icon="upload"
                            :title="$gettext('Bild hochladen')"
                            @click="selectUploadImage"
                        ></studip-square-button>
                        <studip-square-button
                            icon="block-gallery"
                            :title="$gettext('Aus Bilderpool auswählen')"
                            @click="selectStockImage"
                        ></studip-square-button>
                    </div>
                </template>
            </studip-dialog>
            <StockImageSelector
                v-if="showStockImageSelector"
                @close="showStockImageSelector = false"
                @select="onSelectStockImage"
            />
        </div>
    </template>
    
    <script>
    import axios from 'axios';
    import StudipDialog from '../StudipDialog.vue';
    import StudipMessageBox from '../StudipMessageBox.vue';
    import StudipSquareButton from '../StudipSquareButton.vue';
    import StockImageSelector from '../stock-images/SelectorDialog.vue';
    import { mapGetters, mapActions } from 'vuex';
    
    import Cropper from 'cropperjs';
    
    export default {
        components: {
            StudipDialog,
            StudipMessageBox,
            StudipSquareButton,
            StockImageSelector,
        },
        data() {
            return {
                showRemoveDialog: false,
                showChangeImageDialog: false,
                showStockImageSelector: false,
                selectedStockImage: false,
                uploadImage: null,
                editingImage: false,
                base64Image: null,
                cropper: null,
                invalid: false,
                cropperRotate: 0,
                cropperBaseRotation: 0,
                fliped: false,
            };
        },
        computed: {
            ...mapGetters({
                context: 'context',
                currentAvatar: 'currentAvatar',
                isCourseAvatar: 'isCourseAvatar',
                isInstituteAvatar: 'isInstituteAvatar',
                isStudygroupAvatar: 'isStudygroupAvatar',
                isUserAvatar: 'isUserAvatar',
                isCustomized: 'isCustomized',
            }),
            avatarUrl() {
                return this.currentAvatar.meta.url.normal;
            },
            avatarAltText() {
                if (this.isUserAvatar) {
                    return this.$gettext('Mein Profilbild');
                }
                if (this.isCourseAvatar) {
                    return this.$gettext('Veranstaltungsbild');
                }
                if (this.isStudygroupAvatar) {
                    return this.$gettext('Studiengruppenbild');
                }
                if (this.isInstituteAvatar) {
                    return this.$gettext('Einrichtungsbild');
                }
                return '';
            },
        },
        methods: {
            changeImage() {
                if (this.isUserAvatar) {
                    this.selectUploadImage();
                } else {
                    this.showChangeImageDialog = true;
                }
            },
            selectUploadImage() {
                this.showChangeImageDialog = false;
                this.$refs.uploadFile.click();
            },
            selectStockImage() {
                this.showChangeImageDialog = false;
                this.showStockImageSelector = true;
            },
            onSelectStockImage(stockImage) {
                if (this.cropper) {
                    this.resetCropper();
                    this.cropper.destroy();
                }
                this.base64Image = null;
                this.uploadImage = null;
                this.selectedStockImage = stockImage;
                this.showStockImageSelector = false;
                this.loadStockImage();
            },
            updateUploadImage() {
                if (this.cropper) {
                    this.resetCropper();
                    this.cropper.destroy();
                }
                this.base64Image = null;
                this.createBase64Image(this.$refs.uploadFile.files[0]);
            },
            createBase64Image(FileObject) {
                const reader = new FileReader();
                reader.onload = (event) => {
                    this.base64Image = event.target.result;
                    this.enableCropper();
                };
                reader.readAsDataURL(FileObject);
            },
            enableCropper() {
                let image = this.$refs.newImage;
                image.src = this.base64Image;
                this.cropper = new Cropper(image, {
                    aspectRatio: 1,
                    viewMode: 1,
                    autoCropArea: 0.9,
                    dragMode: 'move',
                    cropBoxMovable: false,
                    cropBoxResizable: false,
                    toggleDragModeOnDblclick: false,
                });
                this.editingImage = true;
            },
            checkImageSize() {
                const data = this.cropper.getData();
                if (data.width < 250 || data.height < 250) {
                    this.invalid = true;
                } else {
                    this.invalid = false;
                }
            },
            updateCropperBaseRotation(val) {
                this.cropperBaseRotation += val;
                if (this.cropperBaseRotation > 270) {
                    this.cropperBaseRotation = 0;
                }
                this.cropperRotate = 0;
                this.cropper.rotateTo(this.cropperBaseRotation);
            },
            updateCropperRotate() {
                this.cropper.rotateTo(parseInt(this.cropperRotate) + this.cropperBaseRotation);
            },
            resetCropper() {
                this.cropper.reset();
                this.fliped = false;
                this.cropperBaseRotation = 0;
                this.cropperRotate = 0;
            },
            zoomCropper(val) {
                this.cropper.zoom(val);
            },
            flip() {
                this.fliped ? this.cropper.scale(1, 1) : this.cropper.scale(-1, 1);
                this.fliped = !this.fliped;
            },
            abortEditing() {
                this.resetCropper();
                this.cropper.destroy();
                this.selectedStockImage = null;
                this.$refs.uploadFile.value = '';
                this.base64Image = null;
                this.editingImage = false;
                this.fliped = false;
            },
    
            loadStockImage() {
                const url = this.selectedStockImage.attributes['download-urls'].original;
                fetch(url)
                    .then((response) => response.blob())
                    .then(
                        (blob) =>
                            new Promise((resolve, reject) => {
                                const reader = new FileReader();
                                reader.onloadend = () => resolve(reader.result);
                                reader.onerror = reject;
                                reader.readAsDataURL(blob);
                            })
                    )
                    .then((base64) => {
                        this.base64Image = base64;
                        this.enableCropper();
                    });
            },
    
            storeAvatar() {
                if (this.invalid) {
                    return false;
                }
                const croppedImage = this.cropper.getCroppedCanvas().toDataURL('image/webp');
                const data = {
                    data: {
                        'range-id': this.context.id,
                        'range-type': this.context.type,
                        image: croppedImage,
                    },
                };
    
                axios.post(STUDIP.URLHelper.getURL(`jsonapi.php/v1/${this.context.type}/${this.context.id}/avatar`, {}, true), data).then((response) => {
                    location.reload();
                });
            },
    
            removeAvatar() {
                axios
                    .delete(
                        STUDIP.URLHelper.getURL(`jsonapi.php/v1/${this.context.type}/${this.context.id}/avatar`, {}, true)
                    )
                    .then((response) => {
                        location.reload();
                    });
            },
        },
    };
    </script>
    <style scoped lang="scss">
    .avatar {
        max-width: 520px;
    }
    
    .avatar-original {
        max-width: 500px;
    }
    #avatar-upload {
        display: none;
    }
    .cropper-container {
        width: 500px;
        height: 500px;
        margin-bottom: 1em;
    }
    .square-button-panel {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        width: 100%;
        justify-content: center;
    }
    
    .cropper-actions-wrapper {
        max-width: 500px;
    
        .cropper-actions-group {
            display: flex;
            justify-content: space-between;
            margin: 1em 0;
    
            .cropper-actions-button {
                background-color: transparent;
                border: none;
                cursor: pointer;
            }
    
            .labeled-range-input {
                width: 100%;
                input[type='range'] {
                    width: 100%;
                    -webkit-appearance: none;
                    appearance: none;
                    cursor: pointer;
                    height: 2px;
                    background: var(--content-color-40);
    
                    &::-webkit-slider-thumb {
                        -webkit-appearance: none;
                        appearance: none;
                        height: 16px;
                        width: 16px;
                        background-color: var(--base-color);
                        border-radius: 50%;
                        border: none;
                    }
    
                    &::-moz-range-thumb {
                        height: 16px;
                        width: 16px;
                        background-color: var(--base-color);
                        border-radius: 50%;
                        border: none;
                    }
                }
    
                .labeled-range-input-labels {
                    display: flex;
                    justify-content: space-between;
                    span {
                        margin-left: 0.5em;
                    }
                }
            }
        }
    }
    </style>