Skip to content

Commit

Permalink
Merge pull request #4 from datlechin/feat/trigger-click-reset-tour-guide
Browse files Browse the repository at this point in the history
feat: Trigger click & Reset tour guide
  • Loading branch information
datlechin committed Jun 13, 2024
2 parents 3dd8539 + 02019bb commit 681d5cb
Show file tree
Hide file tree
Showing 28 changed files with 222 additions and 52 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"require": {
"flarum/core": "^1.2.0"
},
"require-dev": {
"laravel/pint": "^1.16"
},
"authors": [
{
"name": "Ngo Quoc Dat",
Expand Down
15 changes: 13 additions & 2 deletions extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Datlechin\FlarumSimpleTourGuide\Api\Controller\ListTourGuideStepController;
use Datlechin\FlarumSimpleTourGuide\Api\Controller\UpdateTourGuideStepController;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\User\Event\Saving;
use Flarum\User\User;
use Flarum\Extend;

Expand All @@ -34,7 +35,11 @@
(new Extend\ApiSerializer(UserSerializer::class))
->attribute(
'tourGuideDismissedAt',
fn(UserSerializer $serializer, User $model, array $attributes) => $model->tour_guide_dismissed_at
fn (UserSerializer $serializer, User $user, array $attributes) => $user->tour_guide_dismissed_at,
)
->attribute(
'canResetTourGuide',
fn (UserSerializer $serializer, User $user) => $serializer->getActor()->can('resetTourGuide', $user),
),

(new Extend\Routes('api'))
Expand All @@ -47,5 +52,11 @@
(new Extend\Settings())
->serializeToForum('datlechin-simple-tour-guide.showProgress', 'datlechin-simple-tour-guide.show_progress', 'boolval')
->serializeToForum('datlechin-simple-tour-guide.allowDismiss', 'datlechin-simple-tour-guide.allow_dismiss', 'boolval')
->serializeToForum('datlechin-simple-tour-guide.steps', 'datlechin-simple-tour-guide.steps')
->serializeToForum('datlechin-simple-tour-guide.steps', 'datlechin-simple-tour-guide.steps'),

(new Extend\Event())
->listen(Saving::class, Listener\SaveTourGuideDismissedAtToDatabase::class),

(new Extend\Policy())
->modelPolicy(User::class, Access\UserPolicy::class),
];
13 changes: 13 additions & 0 deletions js/src/admin/components/EditTourGuideStepModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type Mithril from 'mithril';
import Stream from 'flarum/common/utils/Stream';
import TourGuideStep from 'src/common/models/TourGuideStep';
import extractText from 'flarum/common/utils/extractText';
import Switch from 'flarum/common/components/Switch';

export interface EditTourGuideStepModalAttrs extends IInternalModalAttrs {
model?: TourGuideStep;
Expand All @@ -17,6 +18,7 @@ export default class EditTourGuideStepModal extends Modal<EditTourGuideStepModal
tourTitle!: Stream<string>;
description!: Stream<string>;
target!: Stream<string>;
isTriggerClick!: Stream<boolean>;

oninit(vnode: Mithril.Vnode) {
super.oninit(vnode);
Expand All @@ -26,6 +28,7 @@ export default class EditTourGuideStepModal extends Modal<EditTourGuideStepModal
this.tourTitle = Stream(this.tourGuideStep.title() || '');
this.description = Stream(this.tourGuideStep.description() || '');
this.target = Stream(this.tourGuideStep.target() || '');
this.isTriggerClick = Stream(this.tourGuideStep.isTriggerClick() || false);
}

className() {
Expand Down Expand Up @@ -92,6 +95,15 @@ export default class EditTourGuideStepModal extends Modal<EditTourGuideStepModal
10
);

items.add(
'isTriggerClick',
<div className="Form-group">
<Switch onchange={this.isTriggerClick} state={this.isTriggerClick()} loading={this.loading}>
{app.translator.trans('datlechin-simple-tour-guide.admin.trigger_click')}
</Switch>
</div>
);

items.add(
'submit',
<div className="Form-group">
Expand All @@ -115,6 +127,7 @@ export default class EditTourGuideStepModal extends Modal<EditTourGuideStepModal
title: this.tourTitle(),
description: this.description(),
target: this.target(),
is_trigger_click: this.isTriggerClick(),
};
}

Expand Down
8 changes: 7 additions & 1 deletion js/src/admin/components/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class SettingsPage extends ExtensionPage {
})}
<div className="Form-group">
<Button className="Button" icon="fas fa-plus" onclick={() => app.modal.show(EditTourGuideStepModal)}>
Create new tour
{app.translator.trans('datlechin-simple-tour-guide.admin.add_step')}
</Button>
</div>
{this.loading ? (
Expand All @@ -54,6 +54,7 @@ export default class SettingsPage extends ExtensionPage {
<tr>
<th>{app.translator.trans('datlechin-simple-tour-guide.admin.title')}</th>
<th>{app.translator.trans('datlechin-simple-tour-guide.admin.target')}</th>
<th>{app.translator.trans('datlechin-simple-tour-guide.admin.trigger_click')}</th>
</tr>
</thead>
<tbody>
Expand All @@ -68,6 +69,11 @@ export default class SettingsPage extends ExtensionPage {
onclick={() => app.modal.show(EditTourGuideStepModal, { model: step })}
/>
</td>
<td>
{step.isTriggerClick()
? app.translator.trans('datlechin-simple-tour-guide.admin.yes')
: app.translator.trans('datlechin-simple-tour-guide.admin.no')}
</td>
</tr>
))}
</tbody>
Expand Down
12 changes: 11 additions & 1 deletion js/src/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@ import SettingsPage from './components/SettingsPage';
export { default as extend } from '../common';

app.initializers.add('datlechin/flarum-simple-tour-guide', () => {
app.extensionData.for('datlechin-simple-tour-guide').registerPage(SettingsPage);
app.extensionData
.for('datlechin-simple-tour-guide')
.registerPage(SettingsPage)
.registerPermission(
{
icon: 'fas fa-sync',
label: app.translator.trans('datlechin-simple-tour-guide.admin.permissions.reset_tour_guide'),
permission: 'resetTourGuide',
},
'moderate'
);
});
7 changes: 6 additions & 1 deletion js/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import Extend from 'flarum/common/extenders';
import TourGuideStep from './models/TourGuideStep';
import User from 'flarum/common/models/User';

export default [new Extend.Store().add('tour-guide-steps', TourGuideStep)];
export default [
new Extend.Model(User).attribute<boolean>('tourGuideDismissedAt').attribute<boolean>('canResetTourGuide'),

new Extend.Store().add('tour-guide-steps', TourGuideStep),
];
4 changes: 4 additions & 0 deletions js/src/common/models/TourGuideStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ export default class TourGuideStep extends Model {
target() {
return Model.attribute<string>('target').call(this);
}

isTriggerClick() {
return Model.attribute<boolean>('isTriggerClick').call(this);
}
}
32 changes: 32 additions & 0 deletions js/src/forum/addResetTourGuideUserControlButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import app from 'flarum/forum/app';
import { extend } from 'flarum/common/extend';
import extractText from 'flarum/common/utils/extractText';
import UserControls from 'flarum/forum/utils/UserControls';
import ItemList from 'flarum/common/utils/ItemList';
import User from 'flarum/common/models/User';
import Button from 'flarum/common/components/Button';

export default function () {
// @ts-ignore
extend(UserControls, 'userControls', function (items: ItemList<import('mithril').Children>, user: User) {
const actor = app.session.user;

// @ts-ignore
if (!actor.canResetTourGuide() || !user.attribute('tourGuideDismissedAt')) {
return;
}

const resetTourGuide = (user: User) => {
if (confirm(extractText(app.translator.trans('datlechin-simple-tour-guide.forum.user_controls.reset_tour_guide_confirmation')))) {
user.save({ tourGuideDismissedAt: null }).then(() => m.redraw());
}
};

items.add(
'reset-tour-guide',
<Button icon="fas fa-sync" onclick={() => resetTourGuide(user)}>
{app.translator.trans('datlechin-simple-tour-guide.forum.user_controls.reset_tour_guide')}
</Button>
);
});
}
13 changes: 11 additions & 2 deletions js/src/forum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import app from 'flarum/forum/app';
import { DriveStep, driver } from 'driver.js';
import extractText from 'flarum/common/utils/extractText';
import TourGuideStep from 'src/common/models/TourGuideStep';
import addResetTourGuideUserControlButton from './addResetTourGuideUserControlButton';
export { default as extend } from '../common';

app.initializers.add('datlechin/flarum-simple-tour-guide', () => {
addResetTourGuideUserControlButton();

document.addEventListener('DOMContentLoaded', async () => {
const user = app.session.user;

if (!user || user.attribute('tourGuideDismissedAt')) {
// @ts-ignore
if (!user || user.tourGuideDismissedAt()) {
return;
}

Expand Down Expand Up @@ -41,11 +45,16 @@ app.initializers.add('datlechin/flarum-simple-tour-guide', () => {
title: step.title(),
description: step.description(),
},
onDeselected: (element) => {
if (step.isTriggerClick()) {
// @ts-ignore
element.click();
}
},
};
});
};

extractText;
const driverObj = driver({
showProgress: getSetting('showProgress'),
allowClose: getSetting('allowClose'),
Expand Down
10 changes: 10 additions & 0 deletions locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@ datlechin-simple-tour-guide:
allow_close_label: Allow close
allow_close_help: Allow users closing the popover by click on the backdrop.

permissions:
reset_tour_guide: Reset tour guide

edit_modal:
edit_step: Edit {title}
create_step: Create new step
delete_step: Delete step
delete_step_confirmation: Are you sure you want to delete this step?

add_step: Add new step
title: Title
title_placeholder: The title of the step.
description: Description
description_placeholder: The description of the step.
target: Target
target_placeholder: "The target element of the step (e.g. #element-id)."
trigger_click: Trigger click
yes: Yes
no: No

forum:
progress_text: "{current} of {total}"
next_btn_text: Next
prev_btn_text: Prev
done_btn_text: Done
user_controls:
reset_tour_guide: Start tour again
reset_tour_guide_confirmation: Are you sure you want to force this user to start the tour again?
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
$schema->table('users', function (Blueprint $table) {
$table->dropColumn('tour_guide_dismissed_at');
});
}
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ function (Blueprint $table) {
$table->string('description');
$table->string('target');
$table->timestamps();
}
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

use Illuminate\Database\Schema\Blueprint;

use Illuminate\Database\Schema\Builder;

return [
'up' => function (Builder $schema) {
$schema->table('tour_guide_steps', function (Blueprint $table) {
$table->boolean('is_trigger_click')->default(false);
});
},
'down' => function (Builder $schema) {
$schema->table('tour_guide_steps', function (Blueprint $table) {
$table->dropColumn('is_trigger_click');
});
},
];
18 changes: 18 additions & 0 deletions src/Access/UserPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Datlechin\FlarumSimpleTourGuide\Access;

use Flarum\User\User;
use Flarum\User\Access\AbstractPolicy;

class UserPolicy extends AbstractPolicy
{
public function resetTourGuide(User $actor, User $user)
{
if ($user->hasPermission('resetTourGuide')) {
return $this->allow();
}

return $this->deny();
}
}
7 changes: 2 additions & 5 deletions src/Api/Controller/CreateTourGuideStepController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@ class CreateTourGuideStepController extends AbstractCreateController
{
public $serializer = TourGuideStepSerializer::class;

protected $bus;

public function __construct(Dispatcher $bus)
public function __construct(protected Dispatcher $bus)
{
$this->bus = $bus;
}

protected function data(ServerRequestInterface $request, Document $document)
{
return $this->bus->dispatch(
new CreateTourGuideStep(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', []))
new CreateTourGuideStep(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data', [])),
);
}
}
7 changes: 2 additions & 5 deletions src/Api/Controller/DeleteTourGuideStepController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@

class DeleteTourGuideStepController extends AbstractDeleteController
{
protected $bus;

public function __construct(Dispatcher $bus)
public function __construct(protected Dispatcher $bus)
{
$this->bus = $bus;
}

protected function delete(ServerRequestInterface $request)
{
$this->bus->dispatch(
new DeleteTourGuideStep(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
new DeleteTourGuideStep(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request)),
);
}
}
2 changes: 1 addition & 1 deletion src/Api/Controller/DismissTourGuideController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);

if (!$actor->exists) {
if (! $actor->exists) {
return new EmptyResponse(403);
}

Expand Down
5 changes: 1 addition & 4 deletions src/Api/Controller/UpdateTourGuideStepController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ class UpdateTourGuideStepController extends AbstractShowController
{
public $serializer = TourGuideStepSerializer::class;

protected $bus;

public function __construct(Dispatcher $bus)
public function __construct(protected Dispatcher $bus)
{
$this->bus = $bus;
}

protected function data(ServerRequestInterface $request, Document $document)
Expand Down
Loading

0 comments on commit 681d5cb

Please sign in to comment.