2828 */
2929class Visualizer_Render_Library extends Visualizer_Render {
3030
31+ /** @var bool|null Cached result of _isListView() to avoid repeat DB reads per request. */
32+ private $ _list_view_cached = null ;
33+
3134 /**
3235 * Renders library page.
3336 *
@@ -78,6 +81,8 @@ private function getDisplayForm() {
7881 echo '<div class="visualizer-library-form">
7982 <form action=" ' . admin_url ( 'admin.php ' ) . '">
8083 <input type="hidden" name="page" value=" ' . Visualizer_Plugin::NAME . '"/>
84+ <input type="hidden" name="view" value=" ' . esc_attr ( $ this ->_isListView () ? 'list ' : 'grid ' ) . '"/>
85+ <span class="viz-view-toggle-group"> ' . $ this ->_getViewToggleHTML () . '</span>
8186 <select class="viz-filter" name="type">
8287 ' ;
8388
@@ -282,34 +287,58 @@ private function _renderLibrary() {
282287 echo '<div id="visualizer-content-wrapper"> ' ;
283288 echo '<div id="tsdk_banner" class="visualizer-banner"></div> ' ;
284289 if ( ! empty ( $ this ->charts ) ) {
285- echo '<div id="visualizer-library" class="visualizer-clearfix"> ' ;
286- $ count = 0 ;
287- foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
288- // show the sidebar after the first 3 charts.
289- ++$ count ;
290- $ enable_controls = false ;
291- $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
292- if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
293- $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
294- $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
295- if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
296- $ enable_controls = true ;
290+ if ( $ this ->_isListView () ) {
291+ echo '<div id="visualizer-library" class="visualizer-clearfix view-list"> ' ;
292+ echo '<table class="wp-list-table widefat striped viz-charts-table"> ' ;
293+ echo '<thead><tr> ' ;
294+ echo '<th class="col-id"> ' . esc_html__ ( 'ID ' , 'visualizer ' ) . '</th> ' ;
295+ echo '<th class="col-title"> ' . esc_html__ ( 'Title ' , 'visualizer ' ) . '</th> ' ;
296+ echo '<th class="col-type"> ' . esc_html__ ( 'Type ' , 'visualizer ' ) . '</th> ' ;
297+ echo '<th class="col-shortcode"> ' . esc_html__ ( 'Shortcode ' , 'visualizer ' ) . '</th> ' ;
298+ echo '<th class="col-actions"> ' . esc_html__ ( 'Actions ' , 'visualizer ' ) . '</th> ' ;
299+ echo '</tr></thead><tbody> ' ;
300+ foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
301+ $ enable_controls = false ;
302+ $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
303+ if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
304+ $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
305+ $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
306+ if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
307+ $ enable_controls = true ;
308+ }
297309 }
298- }
299- if ( 3 === $ count ) {
300- $ this ->_renderSidebar ();
301- $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
302- } else {
303310 $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
304311 }
305- }
306- // show the sidebar if there are less than 3 charts.
307- if ( $ count < 3 ) {
312+ echo '</tbody></table> ' ;
308313 $ this ->_renderSidebar ();
314+ echo '</div> ' ;
315+ } else {
316+ echo '<div id="visualizer-library" class="visualizer-clearfix view-grid"> ' ;
317+ $ count = 0 ;
318+ foreach ( $ this ->charts as $ placeholder_id => $ chart ) {
319+ // show the sidebar after the first 3 charts.
320+ ++$ count ;
321+ $ enable_controls = false ;
322+ $ settings = isset ( $ chart ['settings ' ] ) ? $ chart ['settings ' ] : array ();
323+ if ( ! empty ( $ settings ['controls ' ]['controlType ' ] ) ) {
324+ $ column_index = $ settings ['controls ' ]['filterColumnIndex ' ];
325+ $ column_label = $ settings ['controls ' ]['filterColumnLabel ' ];
326+ if ( 'false ' !== $ column_index || 'false ' !== $ column_label ) {
327+ $ enable_controls = true ;
328+ }
329+ }
330+ if ( 3 === $ count ) {
331+ $ this ->_renderSidebar ();
332+ }
333+ $ this ->_renderChartBox ( $ placeholder_id , $ chart ['id ' ], $ enable_controls );
334+ }
335+ if ( $ count < 3 ) {
336+ $ this ->_renderSidebar ();
337+ }
338+ echo '</div> ' ;
309339 }
310- echo '</div> ' ;
311340 } else {
312- echo '<div id="visualizer-library" class="visualizer-clearfix"> ' ;
341+ echo '<div id="visualizer-library" class="visualizer-clearfix view-grid "> ' ;
313342 echo '<div class="items"><div class="visualizer-chart"> ' ;
314343 echo '<div class="visualizer-chart-canvas visualizer-nochart-canvas"> ' ;
315344 echo '<div class="visualizer-notfound"> ' , esc_html__ ( 'No charts found ' , 'visualizer ' ), '<p><h2><a href="javascript:;" class="add-new-h2 add-new-chart"> ' , esc_html__ ( 'Add New ' , 'visualizer ' ), '</a></h2></p></div> ' ;
@@ -413,7 +442,28 @@ private function _renderChartBox( $placeholder_id, $chart_id, $with_filter = fal
413442 $ chart_status ['title ' ] = __ ( 'Click to view the error ' , 'visualizer ' );
414443 }
415444 $ shortcode = sprintf ( '[visualizer id="%s" class=""] ' , $ chart_id );
416- echo '<div class="items"><div class="visualizer-chart"><div class="visualizer-chart-title"> ' , esc_html ( $ title ), '</div> ' ;
445+
446+ if ( $ this ->_isListView () ) {
447+ // ── List view: table row ──
448+ echo '<tr class="viz-list-row"> ' ;
449+ echo '<td class="col-id"># ' . esc_html ( $ chart_id ) . '</td> ' ;
450+ echo '<td class="col-title"> ' . esc_html ( $ title ) . '</td> ' ;
451+ echo '<td class="col-type"> ' . ( ! empty ( $ chart_type ) ? '<span class="viz-chart-type-badge"> ' . esc_html ( $ chart_type ) . '</span> ' : '— ' ) . '</td> ' ;
452+ echo '<td class="col-shortcode"><code class="viz-shortcode-display"> ' . esc_html ( $ shortcode ) . '</code></td> ' ;
453+ echo '<td class="col-actions"><div class="visualizer-action-group"> ' ;
454+ echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' . $ delete_url . '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_html__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
455+ echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' . esc_attr ( $ shortcode ) . '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
456+ echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' . $ export_link . '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
457+ echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' . $ clone_url . '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
458+ echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' . esc_attr ( $ chart_id ) . '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
459+ echo '</div></td> ' ;
460+ echo '</tr> ' ;
461+ return ;
462+ }
463+
464+ // ── Grid view: card ──
465+ $ type_badge = ! empty ( $ chart_type ) ? '<span class="viz-chart-type-badge"> ' . esc_html ( $ chart_type ) . '</span> ' : '' ;
466+ echo '<div class="items"><div class="visualizer-chart"><div class="visualizer-chart-title"><span> ' . esc_html ( $ title ) . '</span> ' . $ type_badge . '</div> ' ;
417467 if ( Visualizer_Module::is_pro () && $ with_filter ) {
418468 echo '<div id="chart_wrapper_ ' . $ placeholder_id . '"> ' ;
419469 echo '<div id="control_wrapper_ ' . $ placeholder_id . '" class="vz-library-chart-filter"></div> ' ;
@@ -426,51 +476,76 @@ private function _renderChartBox( $placeholder_id, $chart_id, $with_filter = fal
426476 }
427477 echo '<div class="visualizer-chart-footer visualizer-clearfix"> ' ;
428478 echo '<div class="visualizer-action-group"> ' ;
429- echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' , $ delete_url , '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
430- echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' , esc_attr ( $ shortcode ), '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
479+ echo '<a class="visualizer-chart-action visualizer-chart-delete" href=" ' , $ delete_url , '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text"> ' . esc_html__ ( 'Delete ' , 'visualizer ' ) . '</span></a> ' ;
480+ echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-clipboard-text=" ' , esc_attr ( $ shortcode ), '"><span class="dashicons dashicons-shortcode ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Copy Shortcode ' , 'visualizer ' ) . '</span></a> ' ;
431481 if ( $ this ->can_chart_have_action ( 'image ' , $ chart_id ) ) {
432- echo '<a class="visualizer-chart-action visualizer-chart-image ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart="visualizer- ' , $ chart_id , '" data-chart-title=" ' , $ title , '"><span class="dashicons dashicons-format-image ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Download PNG ' , 'visualizer ' ) . '</span></a> ' ;
482+ echo '<a class="visualizer-chart-action visualizer-chart-image ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart="visualizer- ' , $ chart_id , '" data-chart-title=" ' , $ title , '"><span class="dashicons dashicons-format-image ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Download PNG ' , 'visualizer ' ) . '</span></a> ' ;
433483 }
434- echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ export_link , '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
435- echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' , $ clone_url , '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
436- echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ chart_id , '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_attr__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
484+ echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ export_link , '"><span class="dashicons dashicons-download ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Export CSV ' , 'visualizer ' ) . '</span></a> ' ;
485+ echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr ( $ pro_class ) . '" href=" ' , $ clone_url , '"><span class="dashicons dashicons-admin-page ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Duplicate ' , 'visualizer ' ) . '</span></a> ' ;
486+ echo '<a class="visualizer-chart-action visualizer-chart-edit ' . esc_attr ( $ pro_class ) . '" href="javascript:;" data-chart=" ' , $ chart_id , '"><span class="dashicons dashicons-admin-generic ' . esc_attr ( $ pro_class ) . '"></span><span class="tooltip-text"> ' . esc_html__ ( 'Edit ' , 'visualizer ' ) . '</span></a> ' ;
437487 echo '</div> ' ;
438488 do_action ( 'visualizer_chart_languages ' , $ chart_id );
439489 echo '<hr><div class="visualizer-chart-status"><span title=" ' . __ ( 'Chart ID ' , 'visualizer ' ) . '">( ' . $ chart_id . '):</span> <span class="visualizer-date" title=" ' . __ ( 'Last Updated ' , 'visualizer ' ) . '"> ' . $ chart_status ['date ' ] . '</span><span class="visualizer-error"><i class="dashicons ' . $ chart_status ['icon ' ] . '" data-viz-error=" ' . esc_attr ( str_replace ( '" ' , "' " , $ chart_status ['error ' ] ) ) . '" title=" ' . esc_attr ( $ chart_status ['title ' ] ) . '"></i></span></div> ' ;
440490 echo '</div> ' ;
441491 echo '</div></div> ' ;
442492 }
443493
494+ /**
495+ * Returns true when the library should render in list (no-preview) mode.
496+ *
497+ * Priority: ?view= URL param (saves to user meta) → saved user meta → grid default.
498+ *
499+ * No nonce needed: this is a bookmarkable UI preference URL. A nonce would expire
500+ * and break saved/shared links for zero real security gain — the value is allowlisted
501+ * to 'list'|'grid' before any write happens.
502+ */
503+ private function _isListView () {
504+ if ( null !== $ this ->_list_view_cached ) {
505+ return $ this ->_list_view_cached ;
506+ }
507+ if ( isset ( $ _GET ['view ' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
508+ $ view = sanitize_text_field ( wp_unslash ( $ _GET ['view ' ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
509+ if ( in_array ( $ view , array ( 'list ' , 'grid ' ), true ) ) {
510+ update_user_meta ( get_current_user_id (), 'visualizer_library_view ' , $ view );
511+ }
512+ $ this ->_list_view_cached = ( 'list ' === $ view );
513+ } else {
514+ $ saved = get_user_meta ( get_current_user_id (), 'visualizer_library_view ' , true );
515+ $ this ->_list_view_cached = ( 'list ' === $ saved );
516+ }
517+ return $ this ->_list_view_cached ;
518+ }
519+
520+ /**
521+ * Returns the HTML for the grid/list view toggle links.
522+ */
523+ private function _getViewToggleHTML () {
524+ $ is_list = $ this ->_isListView ();
525+ $ grid_url = esc_url ( add_query_arg ( 'view ' , 'grid ' ) );
526+ $ list_url = esc_url ( add_query_arg ( 'view ' , 'list ' ) );
527+ return '<a href=" ' . $ grid_url . '" class="viz-view-toggle ' . ( ! $ is_list ? ' active ' : '' ) . '" title=" ' . esc_attr__ ( 'Grid View ' , 'visualizer ' ) . '"><span class="dashicons dashicons-screenoptions"></span></a> '
528+ . '<a href=" ' . $ list_url . '" class="viz-view-toggle ' . ( $ is_list ? ' active ' : '' ) . '" title=" ' . esc_attr__ ( 'List View ' , 'visualizer ' ) . '"><span class="dashicons dashicons-list-view"></span></a> ' ;
529+ }
530+
444531 /**
445532 * Render 2-col sidebar
446533 */
447534 private function _renderSidebar () {
448535 if ( ! Visualizer_Module::is_pro () ) {
449- echo '<div class="items"> ' ;
450- echo '<div class="viz-pro"> ' ;
451- echo '<div id="visualizer-sidebar" class="one-columns"> ' ;
452- echo '<div class="visualizer-sidebar-box"> ' ;
453- echo '<h3> ' . __ ( 'Discover the power of PRO! ' , 'visualizer ' ) . '</h3><ul> ' ;
454- if ( Visualizer_Module_Admin::proFeaturesLocked () ) {
455- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( '6 more chart types ' , 'visualizer ' );
456- } else {
457- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( '11 more chart types ' , 'visualizer ' ) . '</li> ' ;
458- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Synchronize Data Periodically ' , 'visualizer ' ) . '</li> ' ;
459- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'ChartJS Charts ' , 'visualizer ' ) . '</li> ' ;
460- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Table Google chart ' , 'visualizer ' ) . '</li> ' ;
461- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Frontend Actions(Print, Export, Copy, Download) ' , 'visualizer ' ) . '</li> ' ;
462- }
463- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Spreadsheet like editor ' , 'visualizer ' ) . '</li> ' ;
464- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Import from other charts ' , 'visualizer ' ) . '</li> ' ;
465- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Use database query to create charts ' , 'visualizer ' ) . '</li> ' ;
466- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Create charts from WordPress tables ' , 'visualizer ' ) . '</li> ' ;
467- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Frontend editor ' , 'visualizer ' ) . '</li> ' ;
468- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Private charts ' , 'visualizer ' ) . '</li> ' ;
469- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'WPML support for translating charts ' , 'visualizer ' ) . '</li> ' ;
470- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Integration with Woocommerce Data endpoints ' , 'visualizer ' ) . '</li> ' ;
471- echo '<li><svg class="icon list-icon"><use xlink:href="#list-icon"></use></svg> ' . __ ( 'Auto-sync with online files ' , 'visualizer ' ) . '</li></ul> ' ;
472- echo '<p class="vz-sidebar-box-action"><a href=" ' . tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' ) . '#pro-features" target="_blank" class="button button-secondary"> ' . __ ( 'View more features ' , 'visualizer ' ) . '</a><a href=" ' . tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' ) . '#pricing" target="_blank" class="button button-primary"> ' . __ ( 'Upgrade Now ' , 'visualizer ' ) . '</a></p> ' ;
536+ $ upgrade_url = tsdk_utmify ( Visualizer_Plugin::PRO_TEASER_URL , 'sidebarMenuUpgrade ' , 'index ' );
537+ $ chart_types = Visualizer_Module_Admin::proFeaturesLocked () ? __ ( '6 more chart types ' , 'visualizer ' ) : __ ( '11 more chart types ' , 'visualizer ' );
538+ echo '<div class="items items--upsell"> ' ;
539+ echo '<div class="viz-upsell-banner"> ' ;
540+ echo '<span class="dashicons dashicons-star-filled viz-upsell-banner__icon"></span> ' ;
541+ echo '<div class="viz-upsell-banner__text"> ' ;
542+ echo '<strong> ' . esc_html__ ( 'Unlock the full power of Visualizer PRO! ' , 'visualizer ' ) . '</strong> ' ;
543+ /* translators: %s: number of additional chart types (e.g. "11 more chart types") */
544+ echo '<span> ' . sprintf ( esc_html__ ( '%s, periodic data sync, database queries, frontend editor, and more. ' , 'visualizer ' ), esc_html ( $ chart_types ) ) . '</span> ' ;
473545 echo '</div> ' ;
546+ echo '<div class="viz-upsell-banner__actions"> ' ;
547+ echo '<a href=" ' . esc_url ( $ upgrade_url . '#pro-features ' ) . '" target="_blank" class="button button-secondary"> ' . esc_html__ ( 'View Features ' , 'visualizer ' ) . '</a> ' ;
548+ echo '<a href=" ' . esc_url ( $ upgrade_url . '#pricing ' ) . '" target="_blank" class="button button-primary"> ' . esc_html__ ( 'Upgrade Now ' , 'visualizer ' ) . '</a> ' ;
474549 echo '</div> ' ;
475550 echo '</div> ' ;
476551 echo '</div> ' ;
0 commit comments