Move new feature widget to rails

This commit is contained in:
Oliver Günther
2025-08-29 12:03:15 +02:00
parent acf7679825
commit 8a4eebf4cb
16 changed files with 162 additions and 257 deletions

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -32,3 +32,4 @@
@import "work_packages/progress/modal_body_component"
@import "work_packages/reminder/modal_body_component"
@import "work_packages/split_view_component"
@import "homescreen/new_features_component"
@@ -0,0 +1,23 @@
<div class="op-new-features">
<p class="widget-box--additional-info">
<%= new_features_header %>
</p>
<div class="widget-box--description">
<div>
<p><%= new_features_title %></p>
<ul>
<% new_features.values.each do |line| %>
<li><%= line %></li>
<% end %>
</ul>
</div>
<img
class="widget-box--teaser-image op-new-features--teaser-image"
role="presentation"
src="<%= asset_path(feature_teaser_image) %>"
alt=""
/>
</div>
<%= helpers.static_link_to :current_release_notes, label: learn_more_link_text %>
</div>
@@ -0,0 +1,57 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class Homescreen::NewFeaturesComponent < ApplicationComponent
def feature_version
[
OpenProject::VERSION::MAJOR,
OpenProject::VERSION::MINOR
].join("_")
end
def feature_teaser_image
"#{feature_version}_features.svg"
end
def new_features_header
I18n.t("homescreen.blocks.new_features.header")
end
def learn_more_link_text
I18n.t("homescreen.blocks.new_features.learn_about")
end
def new_features_title
I18n.t("homescreen.blocks.new_features.#{feature_version}.new_features_title")
end
def new_features
I18n.t("homescreen.blocks.new_features.#{feature_version}.new_features_list")
end
end
@@ -0,0 +1,48 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
@import helpers
.op-new-features
&--teaser-image
background-image: var(--new-feature-teaser-image)
min-width: 170px
background-size: contain
background-repeat: no-repeat
background-position-x: right
.widget-box--description
padding-bottom: 1rem
@media (max-width: $breakpoint-md)
.widget-box--description
grid-template-columns: 1fr !important
.op-new-features--teaser-image
order: -1
justify-self: center
-12
View File
@@ -41,18 +41,6 @@ module HomescreenHelper
op_icon("icon-context icon-enterprise")
end
##
# Render a static link defined in OpenProject::Static::Links
def static_link_to(key)
link = OpenProject::Static::Links.links[key]
label = I18n.t(link[:label])
link_to label,
link[:href],
title: label,
target: "_blank", rel: "noopener"
end
##
# Determine whether we should render the links on homescreen?
def show_homescreen_links?
@@ -1,5 +1,5 @@
<%= render "homescreen/blocks/header", title: t(:label_new_features) %>
<div class="op-widget-box--body">
<opce-homescreen-new-features-block></opce-homescreen-new-features-block>
<%= render Homescreen::NewFeaturesComponent.new %>
</div>
+15
View File
@@ -2645,6 +2645,21 @@ en:
community: "OpenProject community"
upsell:
title: "Upgrade to Enterprise edition"
new_features:
header: "Read about new features and product updates."
learn_about: "Learn more about all new features"
# We need to include the version to invalidate outdated translations in other locales
"16_4":
new_features_title: >
The release contains various new features and improvements, such as:
new_features_list:
line_0: Option to auto-sync system color + contrast mode.
line_1: Dark high contrast mode for improved accessibility.
line_2: PDF export of project lists.
line_3: Custom font for all PDF exports (Enterprise add-on).
line_4: Improved meeting participants dialog.
line_5: Improved budget handling in project queries and budget planning.
line_6: Updated Home and Project overview page.
links:
upgrade_enterprise_edition: "Upgrade to Enterprise edition"
postgres_migration: "Migrating your installation to PostgreSQL"
-19
View File
@@ -327,25 +327,6 @@ en:
date: "%{attribute} is no valid date - YYYY-MM-DD expected."
general: "An error has occurred."
homescreen:
blocks:
new_features:
text_new_features: "Read about new features and product updates."
learn_about: "Learn more about all new features"
# Include the version to invalidate outdated translations in other locales.
# Otherwise, e.g. Chinese might still have the translations for 10.0 in the 12.0 release.
"16_3":
standard:
new_features_html: >
The release contains various new features and improvements, such as <br>
<ul class="%{list_styling_class}">
<li>UX/UI improvements for a more modern look.</li>
<li>Meetings: Option to mute email notifications and ICS.</li>
<li>Ongoing timers shown in the My time tracking module.</li>
<li>Progress reporting: % Complete sum also shown in Status-based mode.</li>
<li>Nextcloud Health check: Indicate missing token exchange capability (Enterprise add-on).</li>
</ul>
ical_sharing_modal:
title: "Subscribe to calendar"
inital_setup_error_message: "An error occured while fetching data."
-2
View File
@@ -186,7 +186,6 @@ import { GlobalSearchWorkPackagesComponent } from 'core-app/core/global_search/g
import {
CustomDateActionAdminComponent,
} from 'core-app/features/work-packages/components/wp-custom-actions/date-action/custom-date-action-admin.component';
import { HomescreenNewFeaturesBlockComponent } from 'core-app/features/homescreen/blocks/new-features.component';
import {
ZenModeButtonComponent,
} from 'core-app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component';
@@ -424,7 +423,6 @@ export class OpenProjectModule implements DoBootstrap {
registerCustomElement('opce-toasts-container', ToastsContainerComponent, { injector });
registerCustomElement('opce-global-search-work-packages', GlobalSearchWorkPackagesComponent, { injector });
registerCustomElement('opce-custom-date-action-admin', CustomDateActionAdminComponent, { injector });
registerCustomElement('opce-homescreen-new-features-block', HomescreenNewFeaturesBlockComponent, { injector });
registerCustomElement('opce-zen-mode-toggle-button', ZenModeButtonComponent, { injector });
registerCustomElement('opce-colors-autocompleter', ColorsAutocompleterComponent, { injector });
}
@@ -1,17 +0,0 @@
@import "helpers"
.op-new-features
&--teaser-image
background-image: var(--new-feature-teaser-image)
min-width: 170px
background-size: contain
background-repeat: no-repeat
background-position-x: right
@media (max-width: $breakpoint-md)
.widget-box--description
grid-template-columns: 1fr !important
.op-new-features--teaser-image
order: -1
justify-self: center
@@ -1,83 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { HomescreenNewFeaturesBlockComponent } from './new-features.component';
describe('shows edition-specific content', () => {
let app:HomescreenNewFeaturesBlockComponent;
let fixture:ComponentFixture<HomescreenNewFeaturesBlockComponent>;
let element:DebugElement;
beforeEach(() => {
// noinspection JSIgnoredPromiseFromCall
TestBed.configureTestingModule({
declarations: [
HomescreenNewFeaturesBlockComponent,
],
providers: [I18nService],
}).compileComponents();
fixture = TestBed.createComponent(HomescreenNewFeaturesBlockComponent);
app = fixture.debugElement.componentInstance;
element = fixture.debugElement.query(By.css('div.widget-box--description p'));
});
it('should render bim text for bim edition', fakeAsync(() => {
app.hasBimChanges = true;
app.isStandardEdition = false;
fixture.detectChanges();
// checking for missing translation key as translations are not loaded in specs
expect(element.nativeElement.textContent).toContain('.bim.new_features_html');
}));
it('should render standard text for bim edition if disabled', fakeAsync(() => {
app.hasBimChanges = false;
app.isStandardEdition = false;
fixture.detectChanges();
// checking for missing translation key as translations are not loaded in specs
expect(element.nativeElement.textContent).toContain('.standard.new_features_html');
}));
it('should render standard text for standard edition', fakeAsync(() => {
app.isStandardEdition = true;
fixture.detectChanges();
// checking for missing translation key as translations are not loaded in specs
expect(element.nativeElement.textContent).toContain('.standard.new_features_html');
}));
});
@@ -1,118 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { BcfRestApi } from 'core-app/features/bim/bcf/bcf-constants.const';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { imagePath } from 'core-app/shared/helpers/images/path-helper';
// The key used in the I18n files to distinguish between versions.
const OpVersionI18n = '16_3';
// The key used to identify the svg representing the central feature in the version.
// This might be different to OpVersionI18n for a while since the teaser text is often ready
// before the image is.
const OpVersionImage = '16_3';
const OpReleaseURL = 'https://www.openproject.org/docs/release-notes';
/** Update the teaser image to the next version */
const featureTeaserImage = `${OpVersionImage}_features.svg`;
@Component({
template: `
<div class="op-new-features">
<p class="widget-box--additional-info">
{{ text.descriptionNewFeatures }}
</p>
<div class="widget-box--description">
<p [innerHtml]="currentNewFeatureHtml"></p>
<img
class="widget-box--teaser-image op-new-features--teaser-image"
role="presentation"
[src]="new_features_image"/>
</div>
<a [href]="teaserWebsiteUrl" target="_blank">{{ text.learnAbout }}</a>
</div>
`,
selector: 'opce-homescreen-new-features-block',
styleUrls: ['./new-features.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
/**
* Component for the homescreen block to promote new features.
* When updating this for the next release, be sure to cleanup stuff is not needed any more:
* Locals (js-en.yml), Styles (new-features.component.sass), HTML (above), TS (below)
*/
export class HomescreenNewFeaturesBlockComponent {
readonly i18n = inject(I18nService);
readonly domSanitizer = inject(DomSanitizer);
public isStandardEdition:boolean;
/** Set to true if BIM has its own changes */
hasBimChanges = false;
/** Update the feature image appropriately */
new_features_image = imagePath(featureTeaserImage);
public text = {
newFeatures: this.i18n.t('js.label_new_features'),
descriptionNewFeatures: this.i18n.t('js.homescreen.blocks.new_features.text_new_features'),
learnAbout: this.i18n.t('js.homescreen.blocks.new_features.learn_about'),
};
constructor() {
this.isStandardEdition = window.OpenProject.isStandardEdition;
}
public get teaserWebsiteUrl() {
return this.domSanitizer.bypassSecurityTrustResourceUrl(OpReleaseURL);
}
public get currentNewFeatureHtml():string {
return this.translated('new_features_html');
}
private get translatedEdition():string {
if (this.hasBimChanges && !this.isStandardEdition) {
return 'bim';
}
return 'standard';
}
private translated(key:string):string {
return this.i18n.t(
`js.homescreen.blocks.new_features.${OpVersionI18n}.${this.translatedEdition}.${key}`,
{ list_styling_class: 'widget-box--arrow-links', bcf_api_link: BcfRestApi },
);
}
}
-3
View File
@@ -57,7 +57,6 @@ import {
import { OPContextMenuComponent } from 'core-app/shared/components/op-context-menu/op-context-menu.component';
import { OpenprojectPrincipalRenderingModule } from 'core-app/shared/components/principal/principal-rendering.module';
import { FocusModule } from 'core-app/shared/directives/focus/focus.module';
import { HomescreenNewFeaturesBlockComponent } from 'core-app/features/homescreen/blocks/new-features.component';
import { TablePaginationComponent } from 'core-app/shared/components/table-pagination/table-pagination.component';
import { StaticQueriesService } from 'core-app/shared/components/op-view-select/op-static-queries.service';
import { CopyToClipboardService } from './components/copy-to-clipboard/copy-to-clipboard.service';
@@ -209,8 +208,6 @@ export function bootstrapModule(injector:Injector):void {
PersistentToggleComponent,
RemoteFieldUpdaterComponent,
HomescreenNewFeaturesBlockComponent,
OpOptionListComponent,
OpProjectIncludeComponent,
OpProjectIncludeListComponent,
+9
View File
@@ -61,6 +61,10 @@ module OpenProject
help: {
href: help_link,
label: "top_menu.help_and_support"
},
current_release_notes: {
href: current_release_notes_link,
label: :label_release_notes
}
}
@@ -74,6 +78,11 @@ module OpenProject
dynamic
end
def current_release_notes_link
version = OpenProject::VERSION.to_semver(separator: "-", include_special: false)
"https://www.openproject.org/docs/release-notes/#{version}"
end
def static_links
@static_links ||= begin
yaml = Rails.root.join("config/static_links.yml").read
+8 -2
View File
@@ -97,8 +97,14 @@ module OpenProject
def to_s; STRING end
def to_semver
[MAJOR, MINOR, PATCH].join(".") + special
def to_semver(separator: ".", include_special: true)
base = [MAJOR, MINOR, PATCH].join(separator)
if include_special
base + special
else
base
end
end
private