Skip to content

Commit

Permalink
Implement files pagination on all screens (#6952)
Browse files Browse the repository at this point in the history
* Work for #6794: implement file responsiveness

* Add markup test

* Add f test

* Work for #6794: fix styles. Update vr tests

* Update f test

* Small refactor

* Work for #6794: fix templates in all frameworks

* Fix angular markup tests
  • Loading branch information
dk981234 committed Sep 14, 2023
1 parent 69b2b7c commit f70cd9a
Show file tree
Hide file tree
Showing 18 changed files with 461 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@
<svg *ngIf="model.cssClasses.removeButtonIconId" [iconName]="model.cssClasses.removeButtonIconId" [size]="'auto'"
[title]="model.clearButtonCaption" sv-ng-svg-icon></svg>
</button>
<sv-action-bar *ngIf="model.mobileFileNavigatorVisible" [model]="model.mobileFileNavigator"></sv-action-bar>
<sv-action-bar *ngIf="model.fileNavigatorVisible" [model]="model.fileNavigator"></sv-action-bar>
</div>
</div>
4 changes: 2 additions & 2 deletions packages/survey-vue3-ui/src/File.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@
></sv-svg-icon>
</button>
<sv-action-bar
v-if="question.mobileFileNavigatorVisible"
:model="question.mobileFileNavigator"
v-if="question.fileNavigatorVisible"
:model="question.fileNavigator"
></sv-action-bar>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/defaultCss/defaultV2Css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ export var defaultV2Css = {
expression: "sd-expression",
file: {
root: "sd-file",
rootDragging: "sd-file--dragging",
other: "sd-input sd-comment",
placeholderInput: "sd-visuallyhidden",
preview: "sd-file__preview",
Expand Down
29 changes: 18 additions & 11 deletions src/defaultV2-theme/blocks/sd-file.scss
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
.sd-file {
min-height: calcSize(36);
position: relative;
font-size: calcFontSize(1);
line-height: calcLineHeight(1.5);
min-height: calcSize(36);
box-sizing: border-box;
padding: 0 calcSize(6);

.sv-action-bar {
padding: calcSize(1) 0;
justify-content: center;
position: absolute;
width: 100%;
left: 0;
bottom: 0;

.sv-action-bar-item {
Expand All @@ -20,6 +23,7 @@
#fileIndex {
.sv-action-bar-item {
padding: calcSize(0.5) 0;
font-weight: 600;

&:hover {
background-color: $background;
Expand All @@ -37,27 +41,29 @@
display: flex;
flex-direction: column;
position: absolute;
position: absolute;
left: calcSize(0);
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px dashed $border;
justify-content: center;
align-items: center;
padding: 0 calcSize(8);
border: 1px dashed $border;
}

.sd-file__decorator--drag {
z-index: 1;
border: 1px solid $primary;
box-shadow: inset 0 0 0 1px $primary;
background: $primary-light;
z-index: 1;
box-shadow: inset 0 0 0 1px $primary;
}

.sd-file__no-file-chosen {
display: none;
}

.sd-file__drag-area-placeholder {
padding: 0 calcSize(8);
text-align: center;
word-break: break-word;
white-space: normal;
Expand All @@ -67,7 +73,7 @@
}

.sd-root-modern--mobile {
.sd-file__drag-area-placeholder {
.sd-file__decorator {
padding: 0 calcSize(4);
}
}
Expand Down Expand Up @@ -124,16 +130,15 @@
}

.sd-file__list {
overflow: auto;
display: flex;
gap: calcSize(4);
box-sizing: content-box;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
justify-content: center;
padding: calcSize(10.5) 0;
min-height: calcSize(15);
max-height: calcSize(15);
position: absolute;
width: 100%;
}

Expand All @@ -143,7 +148,7 @@
align-items: center;
flex-direction: column;
min-height: 100%;
margin: 0 auto;
margin: 0;

.sd-file__default-image {
width: calcSize(7);
Expand Down Expand Up @@ -228,6 +233,8 @@
}

.sd-file__list {
position: absolute;
left: 0;
padding: 0;
height: 100%;
max-height: 100%;
Expand Down Expand Up @@ -273,7 +280,7 @@
}

.sd-file__drag-area {
position: absolute;
position: static;
width: 100%;
height: 100%;
}
4 changes: 2 additions & 2 deletions src/knockout/templates/question-file.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
<!-- /ko -->
<!-- /ko -->
<!-- ko template: { name: 'survey-question-file-clean-button', data: {question: question, showRemoveButton: question.showRemoveButtonBottom} } --><!-- /ko -->
<!-- ko if: question.mobileFileNavigatorVisible -->
<!-- ko component: { name: 'sv-action-bar', params: { model: question.mobileFileNavigator } } -->
<!-- ko if: question.fileNavigatorVisible -->
<!-- ko component: { name: 'sv-action-bar', params: { model: question.fileNavigator } } -->
<!-- /ko -->
<!-- /ko -->
</div>
Expand Down
102 changes: 91 additions & 11 deletions src/question_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { EventBase, ComputedUpdater } from "./base";
import { UploadingFileError, ExceedSizeError } from "./error";
import { SurveyError } from "./survey-error";
import { CssClassBuilder } from "./utils/cssClassBuilder";
import { confirmActionAsync, detectIEOrEdge, loadFileFromBase64 } from "./utils/utils";
import { classesToSelector, confirmActionAsync, detectIEOrEdge, isElementVisible, loadFileFromBase64 } from "./utils/utils";
import { ActionContainer } from "./actions/container";
import { Action } from "./actions/action";
import { Helpers } from "./helpers";
Expand Down Expand Up @@ -39,6 +39,9 @@ export class QuestionFileModel extends Question {
@property({ defaultValue: "empty" }) currentState: string;

@property({ defaultValue: 0 }) indexToShow: number;
@property({ defaultValue: 1, onSet: (_, target) => {
target.updateFileNavigator();
} }) pageSize: number;
@property({ defaultValue: false }) containsMultiplyFiles: boolean;
/**
* Specifies whether users can capture and upload a photo. Applies only to mobile devices.
Expand All @@ -47,13 +50,19 @@ export class QuestionFileModel extends Question {
*/
@property() allowCameraAccess: boolean;

public mobileFileNavigator: ActionContainer = new ActionContainer();
public fileNavigator: ActionContainer = new ActionContainer();
protected prevFileAction: Action;
protected nextFileAction: Action;
protected fileIndexAction: Action;

get mobileFileNavigatorVisible(): boolean {
return this.isMobile && this.containsMultiplyFiles;
get fileNavigatorVisible(): boolean {
const isUploading = this.isUploading;
const containsMultipleFiles = this.containsMultiplyFiles;
const needToShowFileNavigator = this.pageSize < this.previewValue.length;
return !isUploading && containsMultipleFiles && needToShowFileNavigator && this.isDefaultV2Theme;
}
private get pagesCount() {
return Math.ceil(this.previewValue.length / this.pageSize);
}

constructor(name: string) {
Expand All @@ -67,19 +76,19 @@ export class QuestionFileModel extends Question {
id: "prevPage",
iconSize: 16,
action: () => {
this.indexToShow = this.previewValue.length && ((this.indexToShow - 1 + this.previewValue.length) % this.previewValue.length) || 0;
this.indexToShow = this.previewValue.length && ((this.indexToShow - 1 + this.pagesCount) % this.pagesCount) || 0;
this.fileIndexAction.title = this.getFileIndexCaption();
}
});
this.nextFileAction = new Action({
id: "nextPage",
iconSize: 16,
action: () => {
this.indexToShow = this.previewValue.length && ((this.indexToShow + 1) % this.previewValue.length) || 0;
this.indexToShow = this.previewValue.length && ((this.indexToShow + 1) % this.pagesCount) || 0;
this.fileIndexAction.title = this.getFileIndexCaption();
}
});
this.mobileFileNavigator.actions = [this.prevFileAction, this.fileIndexAction, this.nextFileAction];
this.fileNavigator.actions = [this.prevFileAction, this.fileIndexAction, this.nextFileAction];
}
protected updateElementCssCore(cssClasses: any): void {
super.updateElementCssCore(cssClasses);
Expand All @@ -88,16 +97,39 @@ export class QuestionFileModel extends Question {
//this.mobileFileNavigator.cssClasses = this.survey.getCss().actionBar;
}
private getFileIndexCaption(): string {
return this.getLocalizationFormatString("indexText", this.indexToShow + 1, this.previewValue.length);
return this.getLocalizationFormatString("indexText", this.indexToShow + 1, this.pagesCount);
}
private updateFileNavigator() {
this.indexToShow = this.previewValue.length && ((this.indexToShow + this.pagesCount) % this.pagesCount) || 0;
this.fileIndexAction.title = this.getFileIndexCaption();
}
private prevPreviewLength = 0;
private previewValueChanged() {
this.indexToShow = this.previewValue.length > 0 ? (this.indexToShow > 0 ? this.indexToShow - 1 : 0) : 0;
if(this.previewValue.length !== this.prevPreviewLength) {
if(this.previewValue.length > 0) {
if(this.prevPreviewLength > this.previewValue.length) {
this.indexToShow = this.indexToShow >= this.pagesCount && this.indexToShow > 0 ? this.pagesCount - 1 : this.indexToShow;
} else {
this.indexToShow = Math.floor(this.prevPreviewLength / this.pageSize);
}
} else {
this.indexToShow = 0;
}
}
this.fileIndexAction.title = this.getFileIndexCaption();
this.containsMultiplyFiles = this.previewValue.length > 1;
if(this.previewValue.length > 0 && !this.calculatedGapBetweenItems && !this.calculatedItemWidth) {
setTimeout(() => {
this.processResponsiveness(0, this._width);
});
}
this.prevPreviewLength = this.previewValue.length;
}

public isPreviewVisible(index: number) {
return !this.isMobile || index === this.indexToShow;
const isFileNavigatorVisible = this.fileNavigatorVisible;
const isPreviewVisible = (this.indexToShow * this.pageSize <= index && index < (this.indexToShow + 1) * this.pageSize);
return !isFileNavigatorVisible || isPreviewVisible;
}

public getType(): string {
Expand Down Expand Up @@ -521,6 +553,7 @@ export class QuestionFileModel extends Question {
public get fileRootCss(): string {
return new CssClassBuilder()
.append(this.cssClasses.root)
.append(this.cssClasses.rootDragging, this.isDragging)
.append(this.cssClasses.single, !this.allowMultiple)
.append(this.cssClasses.singleImage, !this.allowMultiple && this.isAnswered && this.canPreviewImage(this.value[0]))
.append(this.cssClasses.mobile, this.isMobile)
Expand Down Expand Up @@ -558,7 +591,54 @@ export class QuestionFileModel extends Question {
super.endLoadingFromJson();
this.loadPreview(this.value);
}

protected needResponsiveness(): boolean {
return this.supportResponsiveness() && this.isDefaultV2Theme;
}
protected supportResponsiveness(): boolean {
return true;
}
protected getObservedElementSelector(): string {
return classesToSelector(this.cssClasses.dragArea);
}
private getFileListSelector(): string {
return classesToSelector(this.cssClasses.fileList);
}
private calcAvailableItemsCount = (availableWidth: number, itemWidth: number, gap: number): number => {
let itemsCount = Math.floor(availableWidth / (itemWidth + gap));
if ((itemsCount + 1) * (itemWidth + gap) - gap <= availableWidth) itemsCount++;
return itemsCount;
};
private calculatedGapBetweenItems: number;
private calculatedItemWidth: number;
private _width: number;
public triggerResponsiveness(hard?: boolean): void {
if(hard) {
this.calculatedGapBetweenItems = undefined;
this.calculatedItemWidth = undefined;
}
super.triggerResponsiveness();
}
protected processResponsiveness(_: number, availableWidth: number): boolean {
this._width = availableWidth;
if(this.rootElement) {
if((!this.calculatedGapBetweenItems || !this.calculatedItemWidth) && this.allowMultiple) {
const fileListSelector = this.getFileListSelector();
const fileListElement = fileListSelector ? this.rootElement.querySelector(this.getFileListSelector()) : undefined;
if(fileListElement) {
this.calculatedGapBetweenItems = Math.ceil(Number.parseFloat(window.getComputedStyle(fileListElement).gap));
const firstVisibleItem = Array.from(fileListElement.children).filter((_, index) => this.isPreviewVisible(index))[0];
if(firstVisibleItem) {
this.calculatedItemWidth = Math.ceil(Number.parseFloat(window.getComputedStyle(firstVisibleItem).width));
}
}
}
}
if(this.calculatedGapBetweenItems && this.calculatedItemWidth) {
this.pageSize = this.calcAvailableItemsCount(availableWidth, this.calculatedItemWidth, this.calculatedGapBetweenItems);
return true;
}
return false;
}
//#region
// web-based methods
private rootElement: HTMLElement;
Expand Down
4 changes: 2 additions & 2 deletions src/react/reactquestion_file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class SurveyQuestionFile extends SurveyQuestionElementBase {
this.question.cssClasses.removeButtonBottom
): null;

let mobileFileNavigator = this.question.mobileFileNavigatorVisible?(<SurveyActionBar model = {this.question.mobileFileNavigator}></SurveyActionBar>):null;
let fileNavigator = this.question.fileNavigatorVisible?(<SurveyActionBar model = {this.question.fileNavigator}></SurveyActionBar>):null;
fileInput = (
this.isDisplayMode ?
<input
Expand Down Expand Up @@ -77,7 +77,7 @@ export class SurveyQuestionFile extends SurveyQuestionElementBase {
{clearButton}
{preview}
{clearButtonBottom}
{mobileFileNavigator}
{fileNavigator}
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/vue/file.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@
></sv-svg-icon>
</button>
<sv-action-bar
v-if="question.mobileFileNavigatorVisible"
:model="question.mobileFileNavigator"
v-if="question.fileNavigatorVisible"
:model="question.fileNavigator"
></sv-action-bar>
</div>
</div>
Expand Down
Loading

0 comments on commit f70cd9a

Please sign in to comment.