mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-22 11:20:58 +08:00
Consolidates reusable UI primitives from `app/components/`,
`app/helpers/`, and `app/modifiers/` into a dedicated `app/ui-kit/`
directory under a unified `d-` prefix naming convention. This gives
Discourse a single, discoverable home for the building-block parts of
the UI: generic primitives (buttons, inputs, modals, selects), layout
pieces (page headers, breadcrumbs, stat tiles), and lightly
Discourse-flavored display widgets (user info, badges, cook-text).
Helpers and modifiers live in `app/ui-kit/helpers/` and
`app/ui-kit/modifiers/` respectively.
### Backward compatibility
No existing imports or template invocations need to change. Old import
paths (e.g. `discourse/components/d-button`, `discourse/helpers/d-icon`)
keep working via runtime AMD `loaderShim` entries in
`app/ui-kit-shims.js`, so plugins and themes need zero changes.
An ESLint rule (shipped via `@discourse/lint-configs` 2.46.0) auto-fixes
imports in the codebase to use the new `discourse/ui-kit/...` paths.
Existing consumer files in `app/`, `admin/`, `plugins/`, and `frontend/`
have been re-imported through that rule in a single sweep, so the
codebase stays consistent.
### Tests
Test module identifiers and file paths now match the ui-kit directory
layout (`tests/integration/ui-kit/...`, `module("Integration | ui-kit |
...")`), keeping the test tree symmetric with `app/ui-kit/`.
178 lines
5.1 KiB
Text
Vendored
178 lines
5.1 KiB
Text
Vendored
/* eslint-disable ember/no-classic-components */
|
|
import Component from "@ember/component";
|
|
import { fn } from "@ember/helper";
|
|
import { action } from "@ember/object";
|
|
import { getOwner } from "@ember/owner";
|
|
import { service } from "@ember/service";
|
|
import { tagName } from "@ember-decorators/component";
|
|
import { removeValueFromArray } from "discourse/lib/array-tools";
|
|
import { bind } from "discourse/lib/decorators";
|
|
import { cloneJSON } from "discourse/lib/object";
|
|
import { autoTrackedArray } from "discourse/lib/tracked-tools";
|
|
import UppyUpload from "discourse/lib/uppy/uppy-upload";
|
|
import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin";
|
|
import { clipboardHelpers } from "discourse/lib/utilities";
|
|
import DPickFilesButton from "discourse/ui-kit/d-pick-files-button";
|
|
import ChatComposerUpload from "discourse/plugins/chat/discourse/components/chat-composer-upload";
|
|
|
|
@tagName("")
|
|
export default class ChatComposerUploads extends Component {
|
|
@service capabilities;
|
|
@service mediaOptimizationWorker;
|
|
|
|
@autoTrackedArray uploads = null;
|
|
|
|
uppyUpload = new UppyUpload(getOwner(this), {
|
|
id: "chat-composer-uploader",
|
|
type: "chat-composer",
|
|
useMultipartUploadsIfAvailable: true,
|
|
|
|
uppyReady: () => {
|
|
if (this.siteSettings.composer_media_optimization_image_enabled) {
|
|
this.uppyUpload.uppyWrapper.useUploadPlugin(UppyMediaOptimization, {
|
|
optimizeFn: (data, opts) =>
|
|
this.mediaOptimizationWorker.optimizeImage(data, opts),
|
|
runParallel: !this.capabilities.isMobileDevice,
|
|
});
|
|
}
|
|
|
|
this.uppyUpload.uppyWrapper.onPreProcessProgress((file) => {
|
|
const inProgressUpload = this.inProgressUploads.find(
|
|
(item) => item.id === file.id
|
|
);
|
|
if (!inProgressUpload?.processing) {
|
|
inProgressUpload?.set("processing", true);
|
|
}
|
|
});
|
|
|
|
this.uppyUpload.uppyWrapper.onPreProcessComplete((file) => {
|
|
const inProgressUpload = this.inProgressUploads.find(
|
|
(item) => item.id === file.id
|
|
);
|
|
inProgressUpload?.set("processing", false);
|
|
});
|
|
},
|
|
|
|
uploadDone: (upload) => {
|
|
this.uploads.push(upload);
|
|
this._triggerUploadsChanged();
|
|
},
|
|
|
|
uploadDropTargetOptions: () => ({
|
|
target: this.uploadDropZone || document.body,
|
|
}),
|
|
|
|
onProgressUploadsChanged: () => {
|
|
this._triggerUploadsChanged(this.uploads, {
|
|
inProgressUploadsCount: this.inProgressUploads?.length,
|
|
});
|
|
},
|
|
});
|
|
|
|
existingUploads = null;
|
|
uploadDropZone = null;
|
|
|
|
get inProgressUploads() {
|
|
return this.uppyUpload.inProgressUploads;
|
|
}
|
|
|
|
didReceiveAttrs() {
|
|
super.didReceiveAttrs(...arguments);
|
|
if (this.inProgressUploads?.length > 0) {
|
|
this.uppyUpload.uppyWrapper.uppyInstance?.cancelAll();
|
|
}
|
|
|
|
this.uploads = this.existingUploads ? cloneJSON(this.existingUploads) : [];
|
|
}
|
|
|
|
didInsertElement() {
|
|
super.didInsertElement(...arguments);
|
|
|
|
this.composerInputEl?.addEventListener("paste", this._pasteEventListener);
|
|
}
|
|
|
|
willDestroyElement() {
|
|
super.willDestroyElement(...arguments);
|
|
|
|
this.composerInputEl?.removeEventListener(
|
|
"paste",
|
|
this._pasteEventListener
|
|
);
|
|
}
|
|
|
|
get showUploadsContainer() {
|
|
return this.uploads?.length > 0 || this.inProgressUploads.length > 0;
|
|
}
|
|
|
|
@action
|
|
cancelUploading(upload) {
|
|
this.uppyUpload.cancelSingleUpload({
|
|
fileId: upload.id,
|
|
});
|
|
this.removeUpload(upload);
|
|
}
|
|
|
|
@action
|
|
removeUpload(upload) {
|
|
removeValueFromArray(this.uploads, upload);
|
|
this._triggerUploadsChanged();
|
|
}
|
|
|
|
@bind
|
|
_pasteEventListener(event) {
|
|
if (document.activeElement !== this.composerInputEl) {
|
|
return;
|
|
}
|
|
|
|
const { canUpload, canPasteHtml, types } = clipboardHelpers(event, {
|
|
siteSettings: this.siteSettings,
|
|
canUpload: true,
|
|
});
|
|
|
|
if (!canUpload || canPasteHtml || types.includes("text/plain")) {
|
|
return;
|
|
}
|
|
|
|
if (event && event.clipboardData && event.clipboardData.files) {
|
|
this.uppyUpload.addFiles([...event.clipboardData.files], {
|
|
pasted: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
_triggerUploadsChanged() {
|
|
this.onUploadChanged?.(this.uploads, {
|
|
inProgressUploadsCount: this.inProgressUploads?.length,
|
|
});
|
|
}
|
|
|
|
<template>
|
|
<div class="chat-composer-uploads" ...attributes>
|
|
{{#if this.showUploadsContainer}}
|
|
<div class="chat-composer-uploads-container">
|
|
{{#each this.uploads as |upload|}}
|
|
<ChatComposerUpload
|
|
@upload={{upload}}
|
|
@isDone={{true}}
|
|
@onCancel={{fn this.removeUpload upload}}
|
|
/>
|
|
{{/each}}
|
|
|
|
{{#each this.inProgressUploads as |upload|}}
|
|
<ChatComposerUpload
|
|
@upload={{upload}}
|
|
@onCancel={{fn this.cancelUploading upload}}
|
|
/>
|
|
{{/each}}
|
|
</div>
|
|
{{/if}}
|
|
|
|
<DPickFilesButton
|
|
@allowMultiple={{true}}
|
|
@fileInputId={{this.fileUploadElementId}}
|
|
@fileInputClass="hidden-upload-field"
|
|
@registerFileInput={{this.uppyUpload.setup}}
|
|
/>
|
|
</div>
|
|
</template>
|
|
}
|