Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/summary.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@

<body>
<style>
body {
margin: 0;
padding: 0;
}
#summaryContainer {
height: 100vh;
}
#summaryContainer>.sa-question:nth-child(2) {
width: 100%;
}
Expand Down
2 changes: 2 additions & 0 deletions examples/summary.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// SurveyAnalytics.VisualizationPanel.LayoutEngine = SurveyAnalytics.PanelLayoutEngine;

var surveyId = "";
var accessKey = "";

Expand Down
8 changes: 8 additions & 0 deletions src/entries/summary.core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ export * from "../ranking";
export * from "../pivot";

export { DocumentHelper } from "../utils/index";

export * from "../layout-engine";
export * from "../muuri-layout-engine";
export * from "../panel-layout-engine";

import { VisualizationPanel } from "../visualizationPanel";
import { MuuriLayoutEngine } from "../muuri-layout-engine";
VisualizationPanel.LayoutEngine = VisualizationPanel.LayoutEngine || MuuriLayoutEngine;
6 changes: 6 additions & 0 deletions src/layout-engine.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.sa-question-layouted {

@media screen and (min-width: 1400px) {
width: calc(50% - 1px);
}
}
52 changes: 52 additions & 0 deletions src/layout-engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import "./layout-engine.scss";

/**
* A base class used to implement custom layout engines or integrate third-party layout engines with SurveyJS Dashboard.
*/
export class LayoutEngine {
constructor(protected _allowed: boolean, _itemSelector?: string, _dragEnabled?: boolean) { }

protected startCore(container: HTMLElement) { }
protected stopCore() { }
protected updateCore() { }

get allowed() {
return this._allowed;
}

/**
* Enables the dynamic layout in a given HTML element.
*
* This method should arrange visualization items based on the available screen space and allow users to reorder them via drag and drop.
*/
start(container: HTMLElement) {
if (this._allowed) {
this.startCore(container);
}
}
/**
* Disables the dynamic layout.
*/
stop() {
if (this._allowed) {
this.stopCore();
}
}
/**
* Updates the dynamic layout.
*/
update() {
if (this._allowed) {
this.updateCore();
}
}

add(elements: Array<HTMLElement>, options?: any) { }
remove(elements: Array<HTMLElement>, options?: any) { }

onMoveCallback: (order: Array<string>) => void;

destroy() {
this.stop();
}
}
23 changes: 23 additions & 0 deletions src/muuri-layout-engine.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.muuri {
.sa-question-layouted {
position: absolute;
}

.sa-question__title--draggable {
cursor: move;
&:after {
content: " ";
display: block;
float: right;
width: 15px;
height: 15px;
background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg%3E%3Cpolygon style='fill: %231ab394' points='13,5 12,6 13,7 9,7 9,3 10,4 11,3 8,0 5,3 6,4 7,3 7,7 3,7 4,6 3,5 0,8 3,11 4,10 3,9 7,9 7,13 6,12 5,13 8,16 11,13 10,12 9,13 9,9 13,9 12,10 13,11 16,8 '/%3E%3C/g%3E%3C/svg%3E ");
}
}

[dir="rtl"] .sa-question__title--draggable, [style*="direction: rtl"] .sa-question__title--draggable, [style*="direction:rtl"] .sa-question__title--draggable {
&:after {
float: left;
}
}
}
155 changes: 53 additions & 102 deletions src/layoutEngine.ts → src/muuri-layout-engine.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,53 @@
import Muuri from "muuri";

/**
* A base class used to implement custom layout engines or integrate third-party layout engines with SurveyJS Dashboard.
*/
export class LayoutEngine {
constructor(protected _allowed: boolean) { }

protected startCore(container: HTMLElement) { }
protected stopCore() { }
protected updateCore() { }

get allowed() {
return this._allowed;
}

/**
* Enables the dynamic layout in a given HTML element.
*
* This method should arrange visualization items based on the available screen space and allow users to reorder them via drag and drop.
*/
start(container: HTMLElement) {
if (this._allowed) {
this.startCore(container);
}
}
/**
* Disables the dynamic layout.
*/
stop() {
if (this._allowed) {
this.stopCore();
}
}
/**
* Updates the dynamic layout.
*/
update() {
if (this._allowed) {
this.updateCore();
}
}

add(elements: Array<HTMLElement>, options?: any) { }
remove(elements: Array<HTMLElement>, options?: any) { }

onMoveCallback: (order: Array<string>) => void;

destroy() {
this.stop();
}
}

export class MuuriLayoutEngine extends LayoutEngine {
private _muuri: any = undefined;
private _layoutingTimer: any = undefined;

constructor(allowed: boolean, private _selector: string, private dragEnabled = true) {
super(allowed);
}

protected startCore(container: HTMLElement) {
this._muuri = new Muuri(container, {
dragStartPredicate: {
handle: ".sa-question__title--draggable",
},
items: this._selector,
dragEnabled: this.dragEnabled,
});
this._muuri.on(
"dragReleaseEnd",
(item: any) => {
const newOrder = item.getGrid().getItems().map(gridItem => gridItem.getElement().dataset.question);
this.onMoveCallback && this.onMoveCallback(newOrder);
}
);
}
protected stopCore() {
this._muuri.off("dragReleaseEnd");
this._muuri.destroy();
this._muuri = undefined;
}
protected updateCore() {
if(!this._muuri) return;
if (this._layoutingTimer !== undefined) {
clearTimeout(this._layoutingTimer);
}
this._layoutingTimer = setTimeout(() => {
this._layoutingTimer = undefined;
if(!this._muuri) return;
this._muuri.refreshItems();
this._muuri.layout();
}, 10);
}

add(elements: Array<HTMLElement>, options?: any) {
if (this._allowed) this._muuri.add(elements, options);
}
remove(elements: Array<HTMLElement>, options?: any) {
if (this._allowed) this._muuri.remove(elements, options);
}
}
import Muuri from "muuri";
import { LayoutEngine } from "./layout-engine";
import "./muuri-layout-engine.scss";

export class MuuriLayoutEngine extends LayoutEngine {
private _muuri: any = undefined;
private _layoutingTimer: any = undefined;

constructor(allowed: boolean, private _selector: string, private dragEnabled = true) {
super(allowed);
}

protected startCore(container: HTMLElement) {
this._muuri = new Muuri(container, {
dragStartPredicate: {
handle: ".sa-question__title--draggable",
},
items: this._selector,
dragEnabled: this.dragEnabled,
});
this._muuri.on(
"dragReleaseEnd",
(item: any) => {
const newOrder = item.getGrid().getItems().map(gridItem => gridItem.getElement().dataset.question);
this.onMoveCallback && this.onMoveCallback(newOrder);
}
);
}
protected stopCore() {
this._muuri.off("dragReleaseEnd");
this._muuri.destroy();
this._muuri = undefined;
}
protected updateCore() {
if(!this._muuri) return;
if (this._layoutingTimer !== undefined) {
clearTimeout(this._layoutingTimer);
}
this._layoutingTimer = setTimeout(() => {
this._layoutingTimer = undefined;
if(!this._muuri) return;
this._muuri.refreshItems();
this._muuri.layout();
}, 10);
}

add(elements: Array<HTMLElement>, options?: any) {
if (this._allowed) this._muuri.add(elements, options);
}
remove(elements: Array<HTMLElement>, options?: any) {
if (this._allowed) this._muuri.remove(elements, options);
}
}
106 changes: 106 additions & 0 deletions src/panel-layout-engine.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
.sa-panel-layout__container {
position: relative;
// background: rgba(255, 255, 255, 0.08);
background: linear-gradient(135deg, #1a2a6c, #2a5298);
border-radius: 10px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
// transition: all 0.3s ease;
resize: both;
overflow: auto;
min-height: 600px;
max-width: 100%;
height: calc(100% - 64px);
width: calc(100% - 2px);
}

.sa-panel-layout__grid-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
background-size: 25px 25px;
pointer-events: none;
z-index: 0;
}

.sa-panel-layout__layout-item {
position: absolute;
border: 1px solid #4a90e2;
background: rgba(26, 40, 62, 0.8);
border-radius: 6px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
transition: box-shadow 0.3s ease, border-color 0.3s ease;
overflow: hidden;
z-index: 10;
}

.sa-panel-layout__layout-item:hover {
border-color: #66b3ff;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

.sa-panel-layout__layout-item.dragging {
box-shadow: 0 0 0 2px #66b3ff, 0 10px 25px rgba(0, 0, 0, 0.6);
z-index: 100;
opacity: 0.9;
}

sa-panel-layout__layout-item.resizing {
box-shadow: 0 0 0 2px #4caf50, 0 10px 25px rgba(0, 0, 0, 0.6);
z-index: 100;
}

.sa-panel-layout__item-header {
padding: 10px 15px;
background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
cursor: move;
display: flex;
justify-content: space-between;
align-items: center;
}

.sa-panel-layout__item-title {
font-weight: 600;
font-size: 16px;
color: #66b3ff;
}

.sa-panel-layout__item-coords {
font-size: 12px;
color: #aaa;
font-family: monospace;
}

.sa-panel-layout__item-content {
margin-top: 16px;
height: calc(100% - 80px);
overflow: auto;
}

.sa-panel-layout__resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 20px;
height: 20px;
cursor: nwse-resize;
z-index: 20;
}

.sa-panel-layout__resize-handle::after {
content: "";
position: absolute;
bottom: 4px;
right: 4px;
width: 12px;
height: 12px;
border-right: 2px solid #4a90e2;
border-bottom: 2px solid #4a90e2;
border-radius: 2px;
}
Loading
Loading