diff --git a/addon/components/dropdown-button.hbs b/addon/components/dropdown-button.hbs index 4d5cfcc..11d486e 100644 --- a/addon/components/dropdown-button.hbs +++ b/addon/components/dropdown-button.hbs @@ -1,4 +1,4 @@ - + {{#if @buttonComponent}} {{component @buttonComponent buttonComponentArgs=this.buttonComponentArgs text=@text class=(concat @buttonClass (if dd.isOpen ' dd-is-open')) wrapperClass=@buttonWrapperClass type=this.type active=@active size=this.buttonSize isLoading=@isLoading disabled=@disabled textClass=@textClass helpText=@helpText tooltipPlacement=@tooltipPlacement img=@img imgClass=@imgClass alt=@alt}} diff --git a/addon/components/file-icon.hbs b/addon/components/file-icon.hbs index f5ecbdd..5c65059 100644 --- a/addon/components/file-icon.hbs +++ b/addon/components/file-icon.hbs @@ -1,8 +1,10 @@ -
+
- - {{this.extension}} - + {{#unless @hideExtension}} + + {{this.extension}} + + {{/unless}}
{{yield}}
diff --git a/addon/components/file-icon.js b/addon/components/file-icon.js index 8e76b48..1195f47 100644 --- a/addon/components/file-icon.js +++ b/addon/components/file-icon.js @@ -16,24 +16,14 @@ export default class FileIconComponent extends Component { } getExtension(file) { - return getWithDefault( - { - 'application/vnd.ms-excel': 'xls', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xls', - 'vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xls', - 'vnd.ms-excel': 'xls', - 'text/csv': 'csv', - 'text/tsv': 'tsv', - xlsx: 'xls', - xls: 'xls', - xlsb: 'xls', - xlsm: 'xls', - docx: 'doc', - docm: 'doc', - }, - getWithDefault(file, 'extension', 'xls'), - 'xls' - ); + if (!file || (!file.original_filename && !file.url && !file.path)) { + return null; + } + + // Prefer to use the original filename if available, then URL, then path + const filename = file.original_filename || file.url || file.path; + const extensionMatch = filename.match(/\.(.+)$/); + return extensionMatch ? extensionMatch[1] : null; } getIcon(file) { diff --git a/addon/components/file.hbs b/addon/components/file.hbs new file mode 100644 index 0000000..c5488a9 --- /dev/null +++ b/addon/components/file.hbs @@ -0,0 +1,41 @@ +
+
+
+ + + +
+
+
+ {{#if this.isImage}} + {{@file.original_filename}} + {{else}} +
+ +
+ {{/if}} +
+
+
+ {{truncate-filename @file.original_filename}} +
+
+
+
+
\ No newline at end of file diff --git a/addon/components/file.js b/addon/components/file.js new file mode 100644 index 0000000..509ef96 --- /dev/null +++ b/addon/components/file.js @@ -0,0 +1,43 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class FileComponent extends Component { + @tracked file; + @tracked isImage = false; + + constructor(owner, { file }) { + super(...arguments); + + this.file = file; + this.isImage = this.isImageFile(file); + } + + @action onDropdownItemClick(action, dd) { + if (typeof dd.actions === 'object' && typeof dd.actions.close === 'function') { + dd.actions.close(); + } + + if (typeof this.args[action] === 'function') { + this.args[action](this.file); + } + } + + isImageFile(file) { + if (!file || (!file.original_filename && !file.url && !file.path)) { + return false; + } + + const filename = file.original_filename || file.url || file.path; + const extensionMatch = filename.match(/\.(.+)$/); + + if (!extensionMatch) { + return false; + } + + const extension = extensionMatch[1].toLowerCase(); + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp']; + + return imageExtensions.includes(extension); + } +} diff --git a/addon/helpers/truncate-filename.js b/addon/helpers/truncate-filename.js new file mode 100644 index 0000000..4e630b5 --- /dev/null +++ b/addon/helpers/truncate-filename.js @@ -0,0 +1,20 @@ +import { helper } from '@ember/component/helper'; + +export default helper(function truncateFilename([filename, maxLength = 20]) { + if (!filename || typeof filename !== 'string' || filename.length <= maxLength) { + return filename; + } + + const extensionMatch = filename.match(/\.(.+)$/); + const extension = extensionMatch ? extensionMatch[0] : ''; + const baseName = filename.slice(0, -extension.length); + + if (maxLength <= extension.length) { + // If the maximum length is less than or equal to the extension's length, return only the extension + return `...${extension}`; + } + + const truncated = baseName.slice(0, maxLength - extension.length - 3) + '...'; + + return truncated + extension; +}); diff --git a/addon/styles/layout/legacy.css b/addon/styles/layout/legacy.css index 18b528e..7458fbb 100644 --- a/addon/styles/layout/legacy.css +++ b/addon/styles/layout/legacy.css @@ -737,7 +737,7 @@ body[data-theme='dark'] .ui-combo-box .options-list a.combo-box-option:hover { .file-dropzone { @apply w-full rounded-lg px-4 py-8 bg-gray-50 text-gray-800 text-center flex flex-col items-center justify-center border-2 border-dashed border-gray-200; - min-height: 14rem; + min-height: 10rem; } body[data-theme='dark'] .file-dropzone { diff --git a/addon/styles/layout/next.css b/addon/styles/layout/next.css index f7ff45c..77fd08a 100644 --- a/addon/styles/layout/next.css +++ b/addon/styles/layout/next.css @@ -1,3 +1,9 @@ +body { + height: 100%; + min-height: 100%; + max-height: 100%; +} + body, html, button, a, * { cursor: default; } @@ -70,8 +76,9 @@ body[data-theme='dark'] .next-mobile-navbar .next-mobile-navbar-tabs > .next-mob @apply text-gray-800 flex flex-col bg-white border-t-0; overflow: hidden; width: 100%; - height: 100%; - min-height: 100%; + height: 100%; + min-height: 100%; + max-height: 100%; align-items: stretch; border-top: none; } @@ -997,7 +1004,7 @@ body[data-theme='dark'] .next-content-panel-wrapper .next-content-panel-containe height: 57px; flex-shrink: 0; min-width: 100vw; - max-width: 100vw; + max-width: 100%; -moz-user-select: none; user-select: none; -webkit-user-select: none; @@ -1873,4 +1880,64 @@ input.order-list-overlay-search:focus { .thread-comment-conent-paragraph-wrapper { min-height: 1.75rem; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: .65rem; + max-width: 96px; + height: 135px; + max-height: 160px; + border: 1px #d1d5db solid; + border-radius: .5rem; +} + +body[data-theme="dark"] .x-fleetbase-file > .x-fleetbase-file-wrapper { + border: 1px #374151 solid; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper:hover { + border: 1px #3b82f6 solid; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper .x-fleetbase-file-name { + background-color: #3b82f6; + padding: .15rem; + border-radius: .15rem; + text-align: center; + font-size: 0.75rem; + line-height: 1rem; + color: #fff; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper .x-fleetbase-file-preview { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 4rem; + height: 4rem; + margin-bottom: .5rem; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper .x-fleetbase-file-actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: end; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper .x-fleetbase-file-actions #x-fleetbase-file-actions-dropdown { + position: absolute; + right: 0; + top: 0; + margin: 0.25rem; +} + +.x-fleetbase-file > .x-fleetbase-file-wrapper .x-fleetbase-file-actions #x-fleetbase-file-actions-dropdown .ember-basic-dropdown-trigger button.btn { + padding: 0.15rem 0.65rem; } \ No newline at end of file diff --git a/app/components/file.js b/app/components/file.js new file mode 100644 index 0000000..dfdb575 --- /dev/null +++ b/app/components/file.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-ui/components/file'; diff --git a/app/helpers/truncate-filename.js b/app/helpers/truncate-filename.js new file mode 100644 index 0000000..b3d9880 --- /dev/null +++ b/app/helpers/truncate-filename.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-ui/helpers/truncate-filename'; diff --git a/tests/integration/components/file-test.js b/tests/integration/components/file-test.js new file mode 100644 index 0000000..8ddc8eb --- /dev/null +++ b/tests/integration/components/file-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | file', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom().hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom().hasText('template block text'); + }); +}); diff --git a/tests/integration/helpers/truncate-filename-test.js b/tests/integration/helpers/truncate-filename-test.js new file mode 100644 index 0000000..4c278b3 --- /dev/null +++ b/tests/integration/helpers/truncate-filename-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Helper | truncate-filename', function (hooks) { + setupRenderingTest(hooks); + + // TODO: Replace this with your real tests. + test('it renders', async function (assert) { + this.set('inputValue', '1234'); + + await render(hbs`{{truncate-filename this.inputValue}}`); + + assert.dom().hasText('1234'); + }); +});