{"version":3,"file":"stylesheets/821.css?h=ef8582a385ef7d3170f9","mappings":"AA2BA,iCACA,iBACA,CACA,qCAEA,OADA,iBAEA,CCLA,yCACA,0EACA,iBACA,CACA,6CACA,iBACA,cACA,eACA,CAEA,qBACA,cACA,YACA,eACA,UACA,CCqCA,sBACA,qDAEA,UADA,WAEA,CAEA,mCACA,8BACA,uCADA,iBACA,YACA,eACA,CAEA,kCACA,6BACA,uCADA,gBACA,WACA,CCSA,mCACA,YACA,gBACA,SACA,CAEA,yCACA,YACA,gBAEA,kBACA,CAEA,iFAJA,oDASA,CALA,wCACA,+CAEA,2DAFA,mBAEA,0CACA,QACA,CAEA,uDACA,mBACA,wBADA,kBACA,sBACA,CAEA,wCACA,qBACA,CAEA,wEAEA,eACA,WACA,CACA,mCACA,qDACA,SACA,iBACA,CACA,kCACA,mCACA,YACA,mBACA,eACA,kBACA,CACA,sCAIA,wBAHA,qDAEA,aACA,uBAFA,UAGA,CAEA,yCAEA,mBADA,eACA,iBACA,CCaA,+BACA,WACA,CAEA,wCACA,qDACA,QACA,CAEA,gCACA,iBACA,CAEA,sCACA,SACA,CCpHA,sCACA,qDACA,OACA,CACA,sDACA,8BACA,uCADA,iBACA,YACA,eACA,CACA,qDACA,6BACA,uCADA,gBACA,WACA,CACA,yDAGA,0BACA,wBAHA,eACA,gBACA,uBACA,sBACA,CCzFA,oDACI,yCACA,YACA,aACA,uBACA,kBAEJ,yBACI,kEAEA,mBAEA,0CACA,oDACA,qDACA,YACA,oEACA,UACA,kBAEA,kCACI,mCAEA,sDACI,mBAKZ,+BACI,YACA,WACA,gBAGJ,qCACI,wBACA,gBACA,gBACA,cACA,gBACA,kBACA,kBAGJ,wDACI,kBACA,MACA,QACA,SACA,OAEA,UACA,WACA,YACA,UC+BJ,sBACA,WACA","sources":["webpack://@studip/core/./resources/vue/components/stock-images/ActionsWidget.vue","webpack://@studip/core/./resources/vue/components/stock-images/Thumbnail.vue","webpack://@studip/core/./resources/vue/components/stock-images/EditDialog.vue","webpack://@studip/core/./resources/vue/components/stock-images/ImagesListItem.vue","webpack://@studip/core/./resources/vue/components/stock-images/ImagesList.vue","webpack://@studip/core/./resources/vue/components/stock-images/MetadataBox.vue","webpack://@studip/core/./resources/vue/components/stock-images/UploadBox.vue","webpack://@studip/core/./resources/vue/components/stock-images/UploadDialog.vue"],"sourcesContent":["<template>\n    <SidebarWidget :title=\"$gettext('Aktionen')\">\n        <template #content>\n            <ul class=\"widget-list widget-links\">\n                <li>\n                    <studip-icon shape=\"upload\" class=\"widget-action-icon\" />\n                    <button @click=\"onUploadClick\">{{ $gettext('Bild hinzufügen') }}</button>\n                </li>\n            </ul>\n        </template>\n    </SidebarWidget>\n</template>\n<script>\nimport SidebarWidget from '../SidebarWidget.vue';\n\nexport default {\n    components: {\n        SidebarWidget,\n    },\n    methods: {\n        onUploadClick() {\n            this.$emit('initiateUpload');\n        },\n    },\n};\n</script>\n<style scoped>\n.widget-list li {\n    position: relative;\n}\n.widget-action-icon {\n    position: absolute;\n    left: 0;\n}\n</style>\n","<template>\n    <div class=\"stock-images-thumbnail\" v-if=\"url\">\n        <div :style=\"{ width }\">\n            <img :src=\"url\" :style=\"{ 'object-fit': contain ? 'contain' : 'cover' }\" role=\"presentation\" />\n        </div>\n    </div>\n</template>\n\n<script>\nexport default {\n    props: {\n        url: {\n            type: String,\n            required: true,\n        },\n        width: {\n            type: String,\n            default: '6rem',\n        },\n        contain: {\n            type: Boolean,\n            default: false\n        }\n    },\n};\n</script>\n\n<style scoped>\n.stock-images-thumbnail {\n    display: inline-flex;\n    position: relative;\n}\n.stock-images-thumbnail > div {\n    aspect-ratio: 1/1;\n    display: block;\n    overflow: hidden;\n}\n\nimg {\n    display: block;\n    height: 100%;\n    max-width: 100%;\n    width: 100%;\n}\n</style>\n","<template>\n    <studip-dialog\n        v-if=\"stockImage\"\n        height=\"720\"\n        width=\"960\"\n        :title=\"$gettext('Bild bearbeiten')\"\n        @close=\"onCancel\"\n        closeClass=\"cancel\"\n        :closeText=\"$gettext('Schließen')\"\n    >\n        <template v-slot:dialogContent>\n            <form id=\"stock-images-edit-form\" class=\"default\" @submit.prevent=\"onSubmit\">\n                <div>\n                    <ThumbnailCard\n                        :chdate=\"new Date(stockImage.attributes.chdate)\"\n                        :height=\"stockImage.attributes.height\"\n                        :mime-type=\"stockImage.attributes['mime-type']\"\n                        :mkdate=\"new Date(stockImage.attributes.mkdate)\"\n                        :size=\"stockImage.attributes.size\"\n                        :url=\"stockImage.attributes['download-urls'].small\"\n                        :width=\"stockImage.attributes.width\"\n                    />\n                </div>\n\n                <div>\n                    <AttributesFieldset :metadata=\"metadata\" :suggested-tags=\"suggestedTags\" @change=\"onChange\" />\n                </div>\n            </form>\n        </template>\n\n        <template #dialogButtons>\n            <button form=\"stock-images-edit-form\" type=\"submit\" class=\"button accept\">\n                {{ $gettext('Speichern') }}\n            </button>\n        </template>\n    </studip-dialog>\n</template>\n<script>\nimport ThumbnailCard from './ThumbnailCard.vue';\nimport AttributesFieldset from './AttributesFieldset.vue';\n\nexport default {\n    props: ['stockImage', 'suggestedTags'],\n    components: { AttributesFieldset, ThumbnailCard },\n    data: () => ({\n        metadata: {},\n    }),\n    methods: {\n        onCancel() {\n            this.$emit('cancel');\n        },\n        onChange(metadata) {\n            this.metadata = metadata;\n        },\n        onSubmit() {\n            this.$emit('confirm', { ...this.metadata });\n        },\n        resetLocalCopy() {\n            const {\n                title = '',\n                description = '',\n                author = '',\n                license = '',\n                tags = [],\n            } = this.stockImage?.attributes ?? [];\n            this.metadata = { title, description, author, license, tags };\n        },\n    },\n    mounted() {\n        this.resetLocalCopy();\n    },\n    watch: {\n        stockImage() {\n            this.resetLocalCopy();\n        },\n    },\n};\n</script>\n\n<style scoped>\nform {\n    display: flex;\n    height: 100%;\n    gap: 1.5em;\n}\n\nform > *:first-child {\n    flex-basis: 200px;\n    flex-grow: 0;\n    overflow: hidden;\n}\n\nform > *:last-child {\n    flex-basis: 30em;\n    flex-grow: 1;\n}\n</style>\n","<template>\n    <tr @click=\"onSelect\">\n        <td>\n            <label>\n                <input type=\"checkbox\" :checked=\"isChecked\" @change=\"onCheckboxChange\" />\n                <span class=\"sr-only\">{{\n                    $gettextInterpolate($gettext('%{context} auswählen'), { context: stockImage.attributes.title })\n                }}</span>\n            </label>\n        </td>\n        <td>\n            <div>\n                <Thumbnail\n                    v-if=\"thumbnailUrl\"\n                    :url=\"thumbnailUrl\"\n                    width=\"6rem\"\n                    style=\"background: var(--light-gray-color-40)\"\n                    contain\n                />\n                <div>\n                    <div>{{ stockImage.attributes.title }}</div>\n                    <div>\n                        <span class=\"stock-image-author\">{{ stockImage.attributes.author }}</span>\n                        <span class=\"stock-image-tags\">\n                            <button\n                                type=\"button\"\n                                class=\"stock-image-tag\"\n                                v-for=\"tag in stockImage.attributes.tags\"\n                                :key=\"tag\"\n                                @click=\"onTagClick(tag)\"\n                            >\n                                {{ tag }}\n                            </button>\n                        </span>\n                    </div>\n\n                    <ul class=\"stock-image-palette\" :title=\"$gettext('Bildfarben')\" role=\"presentation\">\n                        <li\n                            v-for=\"(color, index) in palette\"\n                            :key=\"index\"\n                            :style=\"`background-color: rgb(${color[0]} ${color[1]} ${color[2]});`\"\n                            :alt=\"color.join(',')\"\n                        ></li>\n                    </ul>\n                </div>\n            </div>\n        </td>\n        <td>\n            <studip-icon shape=\"file-pic\" alt=\"\" />\n            {{ imageFormat(stockImage) }}\n        </td>\n        <td><studip-file-size :size=\"stockImage.attributes.size\" /></td>\n        <td>{{ stockImage.attributes.width }} × {{ stockImage.attributes.height }}</td>\n    </tr>\n</template>\n<script>\nimport Thumbnail from './Thumbnail.vue';\nimport { getFormat } from './format.js';\n\nexport default {\n    props: {\n        stockImage: {\n            type: Object,\n            required: true,\n        },\n        isChecked: {\n            type: Boolean,\n            default: false,\n        },\n    },\n    components: {\n        Thumbnail,\n    },\n    computed: {\n        palette() {\n            return this.stockImage.attributes.palette ?? [];\n        },\n        thumbnailUrl() {\n            return (\n                this.stockImage.attributes['download-urls'].small ??\n                this.stockImage.attributes['download-urls'].original\n            );\n        },\n    },\n    methods: {\n        imageFormat(image) {\n            return getFormat(image.attributes['mime-type']);\n        },\n        onCheckboxChange() {\n            this.$emit('checked');\n        },\n        onSelect({ target }) {\n            if (!['INPUT', 'LABEL', 'BUTTON'].includes(target.tagName)) {\n                this.$emit('select');\n            }\n        },\n        onTagClick(tag) {\n            this.$emit('search', tag);\n        },\n    },\n};\n</script>\n\n<style scoped>\ntr > td:nth-child(1) {\n    height: 100%;\n    min-height: 100%;\n    padding: 0;\n}\n\ntr > td:nth-child(1) > label {\n    height: 100%;\n    min-height: 100%;\n    display: flex;\n    padding-inline: 1em;\n}\n\ntr > td:nth-child(2) > div {\n    align-items: center;\n    display: flex;\n    flex-direction: row;\n    gap: 1rem;\n}\n\ntr > td:nth-child(2) > div div:last-child {\n    flex: 1;\n    margin-inline-end: 1rem;\n}\n\ntr > td:nth-child(3) img {\n    vertical-align: middle;\n}\n\n.stock-image-author,\n.stock-image-tags {\n    font-size: 0.8em;\n    opacity: 0.75;\n}\n.stock-image-tags {\n    display: flex;\n    gap: 0.5em;\n    margin-block: 0.5em;\n}\n.stock-image-tag {\n    background-color: var(--base-color);\n    border: none;\n    color: var(--white);\n    cursor: pointer;\n    padding: 0.25em 0.5em;\n}\n.stock-image-palette {\n    display: flex;\n    width: 100%;\n    height: 0.25em;\n    padding-inline-start: 0;\n}\n\n.stock-image-palette li {\n    display: inline;\n    flex: 1;\n}\n</style>\n","<template>\n    <div>\n        <table class=\"default\">\n            <caption>\n                <div class=\"caption-container\">\n                    <div>\n                        <studip-icon shape=\"folder-public-full\" :size=\"30\" alt=\"\" />\n                        <span>{{ caption }}</span>\n                    </div>\n                </div>\n            </caption>\n            <thead>\n                <tr>\n                    <th>\n                        <label>\n                            <input type=\"checkbox\" ref=\"checkAll\" :checked=\"allChecked\" @change=\"onCheckedAllChange\" />\n                            <span class=\"sr-only\">{{ $gettext('Alle Bilder auswählen') }}</span>\n                        </label>\n                    </th>\n                    <th>{{ $gettext('Name') }}</th>\n                    <th>{{ $gettext('Format') }}</th>\n                    <th>{{ $gettext('Größe') }}</th>\n                    <th>{{ $gettext('Abmessungen') }}</th>\n                </tr>\n            </thead>\n            <tbody v-if=\"paged.length\">\n                <ImagesListItem\n                    :stock-image=\"stockImage\"\n                    v-for=\"stockImage in paged\"\n                    :key=\"stockImage.id\"\n                    :is-checked=\"checkedImages.includes(stockImage.id)\"\n                    @checked=\"$emit('checked', stockImage)\"\n                    @search=\"(query) => $emit('search', query)\"\n                    @select=\"$emit('select', stockImage)\"\n                />\n            </tbody>\n            <tbody v-else>\n                <tr>\n                    <td colspan=\"5\">\n                        <span v-if=\"query.length\">{{\n                            $gettext('Zu diesem Suchbegriff konnten keine Bilder gefunden werden.')\n                        }}</span>\n                        <span v-else>{{ $gettext('Es konnten keine Bilder gefunden werden.') }}</span>\n                    </td>\n                </tr>\n            </tbody>\n            <tfoot v-if=\"paged.length\">\n                <tr>\n                    <td colspan=\"5\">\n                        <button\n                            type=\"button\"\n                            class=\"button\"\n                            @click=\"showConfirmDelete = true\"\n                            :disabled=\"!checkedImages.length\"\n                        >\n                            {{ $gettext('Löschen') }}\n                        </button>\n                    </td>\n                </tr>\n            </tfoot>\n        </table>\n\n        <studip-dialog\n            v-if=\"showConfirmDelete\"\n            :title=\"\n                this.$ngettext(\n                    'Ausgewähltes Bild unwideruflich löschen?',\n                    'Ausgewählte Bilder unwideruflich löschen?',\n                    checkedImages.length\n                )\n            \"\n            :question=\"\n                $ngettext(\n                    'Möchten Sie das ausgewählte Bild wirklich löschen?',\n                    'Möchten Sie die ausgewählten Bilder wirklich löschen?',\n                    checkedImages.length\n                )\n            \"\n            height=\"200\"\n            width=\"450\"\n            @confirm=\"onDelete\"\n            @close=\"showConfirmDelete = false\"\n        ></studip-dialog>\n    </div>\n</template>\n\n<script>\nimport ImagesListItem from './ImagesListItem.vue';\nimport { mapActions } from 'vuex';\n\nexport default {\n    props: {\n        checkedImages: {\n            type: Array,\n            required: true,\n        },\n        perPage: {\n            type: Number,\n            default: 10,\n        },\n        page: {\n            type: Number,\n            default: 1,\n        },\n        query: {\n            type: String,\n            default: '',\n        },\n        stockImages: {\n            type: Array,\n            required: true,\n        },\n    },\n    components: {\n        ImagesListItem,\n    },\n    data: () => ({\n        latestMkdate: null,\n        showConfirmDelete: false,\n    }),\n    computed: {\n        allChecked() {\n            return this.paged.length && this.paged.every(({ id }) => this.checkedImages.includes(id));\n        },\n        caption() {\n            const n = this.stockImages.length;\n            return this.$gettextInterpolate(this.$ngettext('%{ n } Bild gefunden', '%{ n } Bilder gefunden', n), { n });\n        },\n        paged() {\n            return this.stockImages.slice((this.page - 1) * this.perPage, this.page * this.perPage);\n        },\n        totalItems() {\n            return this.stockImages.length;\n        },\n    },\n    methods: {\n        ...mapActions({ deleteStockImage: 'studip/stockImages/delete' }),\n        checkAll() {\n            this.paged\n                .filter(({ id }) => !this.checkedImages.includes(id))\n                .forEach((image) => this.$emit('checked', image));\n        },\n        checkNone() {\n            this.paged\n                .filter(({ id }) => this.checkedImages.includes(id))\n                .forEach((image) => this.$emit('checked', image));\n        },\n        onCheckedAllChange() {\n            this.allChecked ? this.checkNone() : this.checkAll();\n        },\n        onDelete() {\n            const checkedImages = [...this.checkedImages];\n            this.showConfirmDelete = false;\n            this.checkNone();\n            Promise.allSettled(checkedImages.map((id) => this.deleteStockImage(id))).then(() => {\n                this.revalidatePage();\n            });\n        },\n        revalidatePage() {\n            if (this.totalItems < this.page * this.perPage) {\n                this.$emit('open-page', Math.ceil(this.totalItems / this.perPage));\n            }\n        },\n    },\n    watch: {\n        checkedImages({ length }) {\n            this.$refs.checkAll.indeterminate = 0 < length && length < this.paged.length;\n        },\n    },\n};\n</script>\n\n<style scoped>\ntable.default {\n    height: 100%;\n}\n\n.caption-container div {\n    display: flex;\n    gap: 0.5em;\n}\n\nthead th input {\n    margin-inline: 1em;\n}\n\nthead th:first-child {\n    width: 3em;\n}\n</style>\n","<template>\n    <div class=\"upload-metadata-box\">\n        <div>\n            <ThumbnailCard\n                v-if=\"fileURL\"\n                :height=\"height ?? 0\"\n                :mime-type=\"file.type\"\n                :size=\"file.size\"\n                :url=\"fileURL\"\n                :width=\"width ?? 0\"\n            />\n        </div>\n        <div>\n            <AttributesFieldset :metadata=\"metadata\" :suggested-tags=\"suggestedTags\" @change=\"onChange\" />\n        </div>\n    </div>\n</template>\n\n<script>\nimport ThumbnailCard from './ThumbnailCard.vue';\nimport AttributesFieldset from './AttributesFieldset.vue';\nimport { getFormat } from './format.js';\n\nexport default {\n    props: ['file', 'metadata', 'suggestedTags'],\n\n    components: { AttributesFieldset, ThumbnailCard },\n\n    data: () => ({\n        fileURL: null,\n        height: null,\n        image: null,\n        width: null,\n    }),\n\n    computed: {\n        tags: {\n            get() {\n                return this.metadata.tags;\n            },\n            set(tags) {\n                this.$set(this.metadata, 'tags', tags);\n            },\n        },\n    },\n\n    methods: {\n        onChange(metadata) {\n            this.$emit('change', metadata);\n        },\n    },\n\n    mounted() {\n        this.fileURL = URL.createObjectURL(this.file);\n        this.image = new Image();\n        this.image.onload = ({ target }) => {\n            this.height = target.height;\n            this.width = target.width;\n        };\n        this.image.src = this.fileURL;\n        this.$set(this.metadata, 'title', this.file.name);\n    },\n\n    beforeDestroy() {\n        if (this.fileURL) {\n            URL.revokeObjectURL(this.fileURL);\n        }\n    },\n};\n</script>\n\n<style scoped>\n.upload-metadata-box {\n    display: flex;\n    gap: 1em;\n}\n.upload-metadata-box > div:first-child {\n    flex-basis: 200px;\n    flex-grow: 0;\n    overflow: hidden;\n}\n.upload-metadata-box > div:last-child {\n    flex-basis: 30em;\n    flex-grow: 1;\n}\n.upload-metadata-box div:first-child ul {\n    font-size: 0.9em;\n    line-height: 1.5;\n    margin-block-start: 1em;\n    padding-inline-start: 0;\n}\n</style>\n","\n#stock-images-upload-box-drag-area {\n    background-color: var(--content-color-20);\n    height: 100%;\n    margin: -15px;\n    padding: 18px 15px 10px;\n    text-align: center;\n}\n.holder {\n    align-items: center;\n    border-color: var(--content-color-60);\n    border-radius: 0.5em;\n    border-style: dashed;\n    border-width: 1px;\n    box-sizing: border-box;\n    display: flex;\n    height: 100%;\n    justify-content: center;\n    padding: 0;\n    position: relative;\n\n    &.dragging {\n        background-color: var(--base-color);\n\n        .icon-upload + strong {\n            color: var(--white);\n        }\n    }\n}\n\n.box-centered {\n    height: auto;\n    width: 100%;\n    max-height: 100%;\n}\n\n.icon-upload + strong {\n    color: var(--base-color);\n    font-size: 1.5em;\n    line-height: 1.2;\n    display: block;\n    font-weight: 500;\n    text-align: center;\n    margin: 0 2em 14px;\n}\n\n.upload-button-holder input[type='file'] {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n\n    opacity: 0;\n    width: 100%;\n    height: 100%;\n    padding: 0;\n}\n","<template>\n    <studip-dialog\n        v-if=\"show\"\n        height=\"720\"\n        width=\"960\"\n        :title=\"$gettext('Bild hinzufügen')\"\n        @close=\"onCancel\"\n        closeClass=\"cancel\"\n        :closeText=\"$gettext('Abbrechen')\"\n    >\n        <template #dialogContent>\n            <form id=\"stock-images-upload-form\" class=\"default\" @submit.prevent=\"onSubmit\">\n                <UploadBox v-if=\"state === STATES.IDLE\" @upload=\"onUpload\" />\n                <MetadataBox\n                    v-if=\"state === STATES.UPLOADED\"\n                    :file=\"file\"\n                    :metadata=\"metadata\"\n                    :suggested-tags=\"suggestedTags\"\n                    @change=\"onChangeMetadata\"\n                />\n            </form>\n        </template>\n\n        <template #dialogButtons>\n            <button\n                form=\"stock-images-upload-form\"\n                type=\"submit\"\n                class=\"button accept\"\n                :disabled=\"state !== STATES.UPLOADED\"\n            >\n                {{ $gettext('Hinzufügen') }}\n            </button>\n        </template>\n    </studip-dialog>\n</template>\n<script>\nimport MetadataBox from './MetadataBox.vue';\nimport UploadBox from './UploadBox.vue';\nimport { mapActions } from 'vuex';\n\nconst STATES = { IDLE: 'idle', UPLOADED: 'uploaded' };\n\nexport default {\n    props: ['show', 'suggestedTags'],\n    components: { MetadataBox, UploadBox },\n    data: () => ({\n        file: null,\n        metadata: {\n            title: '',\n            description: '',\n            author: '',\n            license: '',\n            tags: [],\n        },\n        state: STATES.IDLE,\n        STATES,\n    }),\n    methods: {\n        onCancel() {\n            this.$emit('cancel');\n            this.resetLocalCopy();\n        },\n        onChangeMetadata(metadata) {\n            this.metadata = metadata;\n        },\n        onSubmit() {\n            this.$emit('confirm', { file: this.file, metadata: this.metadata });\n        },\n        onUpload({ file }) {\n            this.file = file;\n            this.state = STATES.UPLOADED;\n        },\n        resetLocalCopy() {\n            this.file = null;\n            this.metadata = {};\n            this.state = STATES.IDLE;\n        },\n    },\n    watch: {\n        show() {\n            this.resetLocalCopy();\n        },\n    },\n};\n</script>\n\n<style scoped>\nform {\n    height: 100%;\n}\n</style>\n"],"names":[],"sourceRoot":""}