Skip to content
Merged
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
2 changes: 2 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ $ra-font-path: "../fonts" !default;

@import 'src/dark-theme-overrides';

@import "driver.js/dist/driver";

.ts-control {
padding: 1px;
}
Expand Down
20 changes: 18 additions & 2 deletions app/controllers/users/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_account_update_params, only: [:update]
skip_before_action :check_for_first_use, only: [:edit, :update]

respond_to :html, :json

# GET /resource/sign_up
def new
authorize User
Expand Down Expand Up @@ -123,7 +125,12 @@ def configure_account_update_params
:render_style,
:auto_load_max_size
],
problem_settings: Problem::CATEGORIES
problem_settings: Problem::CATEGORIES,
tour_state: {
completed: {
add: []
}
}
)
end
end
Expand Down Expand Up @@ -191,18 +198,27 @@ def renderer_json(settings)
}
end

def tour_state_json(current_state, data)
return nil unless data
{
"completed" => (current_state["completed"] + data.dig("completed", "add")).uniq # Merge in added completions
}
end

def load_languages
@languages = [[t("devise.registrations.general_settings.interface_language.autodetect"), nil]].concat(
I18n.available_locales.map { |locale| [I18nData.languages(locale)[locale.to_s.first(2).upcase.to_s]&.capitalize, locale] }
)
end

def update_resource(resource, data)
# Transform form data to crrect types
# Transform form data to correct types
data[:pagination_settings] = pagination_json(data[:pagination_settings])
data[:renderer_settings] = renderer_json(data[:renderer_settings])
data[:tag_cloud_settings] = tag_cloud_json(data[:tag_cloud_settings])
data[:file_list_settings] = file_list_json(data[:file_list_settings])
data[:tour_state] = tour_state_json(resource.tour_state, data[:tour_state])
data.compact!
# Require password if important details have changed
if (data[:email] && (data[:email] != resource.email)) || data[:password].present?
resource.update_with_password(data)
Expand Down
15 changes: 14 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,11 @@ def nav_link(ico, text, path, options = {})
class: options[:style] || safe_join(["nav-link", (current_page?(path) ? "active" : "")], " "),
method: options[:method],
nofollow: options[:nofollow],
"aria-label": options[:aria_label]
id: options[:id],
data: options[:data],
aria: {
label: options[:aria_label]
}
)
end

Expand Down Expand Up @@ -349,4 +353,13 @@ def ai_indexable_select_options(object)
selected: object&.ai_indexable || "inherit"
)
end

def tour_attributes(id:, title:, description:)
{
"tour-id": id,
"tour-id-completed": current_user&.tour_state&.dig("completed")&.include?(id),
"tour-title": title,
"tour-description": description
}.compact
end
end
4 changes: 3 additions & 1 deletion app/javascript/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@ import SearchableSelectController from './searchable_select_controller'
import StorageServiceController from './storage_service_controller'
import TagInputController from './tag_input_controller'
import TagSectionController from './tag_section_controller'
import TourController from './tour_controller'
import UploadController from './upload_controller'
import ZxcvbnController from './zxcvbn_controller'

application.register('bulk-edit', BulkEditController)
application.register('caber-advanced', CaberAdvancedController)
application.register('carousel', CarouselController)
application.register('cocooned', CocoonedController)
application.register('copy-text', CopyTextController)
application.register('bulk-edit', BulkEditController)
application.register('editable', EditableController)
application.register('i18n', I18nController)
application.register('renderer', RendererController)
application.register('searchable-select', SearchableSelectController)
application.register('storage-service', StorageServiceController)
application.register('tag-input', TagInputController)
application.register('tag-section', TagSectionController)
application.register('tour', TourController)
application.register('upload', UploadController)
application.register('zxcvbn', ZxcvbnController)
56 changes: 56 additions & 0 deletions app/javascript/controllers/tour_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Controller } from '@hotwired/stimulus'
import { driver, Driver, DriveStep, Config, State } from 'driver.js'

// Connects to data-controller="tour"
export default class extends Controller {
driverObject: Driver | null = null
completed: string[] = []

connect (): void {
// Find uncompleted tour elements in page
const tourElements = document.querySelectorAll('[data-tour-id-completed="false"]')
if (tourElements.length > 0) {
// Create steps for each element
const tourSteps = [...tourElements].map((stepElement: HTMLElement) => (
{
element: '#' + stepElement.id,
popover: {
title: stepElement.dataset.tourTitle,
description: stepElement.dataset.tourDescription
}
}
))
// Create driver object
this.driverObject = driver({
onHighlighted: this.onHighlighted.bind(this),
onDestroyStarted: this.onDestroyStarted.bind(this),
showProgress: true,
steps: tourSteps
})
// Start
this.driverObject.drive()
}
}

onHighlighted (element: Element, step: DriveStep, options: { config: Config, state: State, driver: Driver }): void {
this.completed.push(element.id)
}

onDestroyStarted (): void {
// Store tour state back into current user
const xhr = new XMLHttpRequest()
xhr.open('PATCH', '/users.json', true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(JSON.stringify({
user: {
tour_state: {
completed: {
add: this.completed
}
}
}
}))
// Done, close the tour
this.driverObject?.destroy()
}
}
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class User < ApplicationRecord
attribute :tag_cloud_settings, :json
attribute :problem_settings, :json
attribute :file_list_settings, :json
attribute :tour_state, :json

has_many :access_grants, # rubocop:disable Rails/InverseOf
class_name: "Doorkeeper::AccessGrant",
Expand Down
33 changes: 24 additions & 9 deletions app/views/application/_navbar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
<div class="collapse navbar-collapse row" id="navbar">
<ul class="navbar-nav col ps-4 ps-md-0 align-self-start">
<li class="nav-item">
<%= nav_link "box", Model.model_name.human(count: 100), models_path(@filter&.to_params), text_style: "d-md-none d-lg-inline" %>
<%= nav_link "box", Model.model_name.human(count: 100), models_path(@filter&.to_params), text_style: "d-md-none d-lg-inline", id: "nav-link-models",
data: tour_attributes(id: "nav-link-models", title: Model.model_name.human(count: 100), description: t("tour.navbar.models.description")) %>
</li>
<li class="nav-item">
<%= nav_link "people", Creator.model_name.human(count: 100), creators_path(@filter&.to_params(except: :creator)), text_style: "d-md-none d-lg-inline" %>
<%= nav_link "people", Creator.model_name.human(count: 100), creators_path(@filter&.to_params(except: :creator)), text_style: "d-md-none d-lg-inline", id: "nav-link-creators",
data: tour_attributes(id: "nav-link-creators", title: Creator.model_name.human(count: 100), description: t("tour.navbar.creators.description")) %>
</li>
<li class="nav-item">
<%= nav_link "collection", Collection.model_name.human(count: 100), collections_path(@filter&.to_params(except: :collection)), text_style: "d-md-none d-lg-inline" %>
<%= nav_link "collection", Collection.model_name.human(count: 100), collections_path(@filter&.to_params(except: :collection)), text_style: "d-md-none d-lg-inline", id: "nav-link-collections",
data: tour_attributes(id: "nav-link-collections", title: Collection.model_name.human(count: 100), description: t("tour.navbar.collections.description")) %>
</li>
<% if SiteSettings.show_libraries %>
<% policy_scope(Library).find_each do |library| %>
Expand All @@ -35,11 +38,22 @@
<li class="nav-item">
<% if policy(:upload).index? %>
<div class="btn-group">
<button type="button" data-bs-toggle="dropdown" aria-expanded="false"
class="btn btn-warning btn-sm mt-1 me-1 dropdown-toggle">
<%= render Components::Icon.new(icon: "plus-circle") %>
<%= t ".add_models" %>
</button>
<%= content_tag :button,
type: "button",
id: "nav-link-upload",
class: "btn btn-warning btn-sm mt-1 me-1 dropdown-toggle",
data: {
bs_toggle: "dropdown"
}.merge(tour_attributes(
id: "nav-link-upload",
title: t(".add_models"),
description: t("tour.navbar.add_models.description")
)),
aria: {
expanded: "false"
} do
safe_join [render(Components::Icon.new(icon: "plus-circle")), t(".add_models")], " "
end %>
<ul class="dropdown-menu">
<li><%= link_to t(".upload"), new_model_path, nofollow: true, class: "dropdown-item" %></li>
<li><%= link_to t(".import_url"), new_import_path, nofollow: true, class: "dropdown-item" %>
Expand Down Expand Up @@ -105,7 +119,8 @@
<% end %>
<%- if current_user %>
<li class="nav-item">
<%= nav_link "sliders", t(".account"), edit_user_registration_path, nofollow: true, text_style: "d-md-none" %>
<%= nav_link "sliders", t(".account"), edit_user_registration_path, nofollow: true, text_style: "d-md-none", id: "nav-link-preferences",
data: tour_attributes(id: "nav-link-preferences", title: t(".account"), description: t("tour.navbar.account.description")) %>
</li>
<% end %>
<%- if SiteSettings.multiuser_enabled? %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<%= yield :head %>
</head>

<body class="<%= "#{SiteSettings.theme}-theme" %>">
<body class="<%= "#{SiteSettings.theme}-theme" %>" data-controller="tour">
<%= skip_link "content", t(".skip_to_content") %>
<%= render "application/navbar" %>
<%= yield :breadcrumbs %>
Expand Down
2 changes: 1 addition & 1 deletion config/i18n-tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ data:
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
# `i18n-tasks normalize -p` will force move the keys according to these rules
write:
- ['{collections, creators, devise, libraries, links, model_files, models, problems, settings, uploads}.*', 'config/locales/\1/%{locale}.yml']
- ['{collections, creators, devise, libraries, links, model_files, models, problems, settings, tour, uploads}.*', 'config/locales/\1/%{locale}.yml']
## Catch-all default:
- config/locales/%{locale}.yml

Expand Down
14 changes: 14 additions & 0 deletions config/locales/tour/cs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
cs:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/de.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
de:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
en:
tour:
navbar:
account:
description: In your settings, you can change your login details, preferred rendering style, pagination options, and more.
add_models:
description: Upload or import your own models here; you can keep them private, or share them publicly, it's up to you!
collections:
description: Models can be grouped together into "collections"; for instance, models with a similar theme or purpose.
creators:
description: A "creator" is the designer, publisher or author of a model.
models:
description: A "model" in Manyfold is a 3D file or set of files which go together; e.g. parts of a single assembly, or a kit. A model could also include instruction documents, pictures, and so on.
14 changes: 14 additions & 0 deletions config/locales/tour/es.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
es:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/fr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
fr:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/ja.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
ja:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/nl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
nl:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/pl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
pl:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
14 changes: 14 additions & 0 deletions config/locales/tour/pt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
pt:
tour:
navbar:
account:
description:
add_models:
description:
collections:
description:
creators:
description:
models:
description:
Loading
Loading